131 lines
6.6 KiB
C#
131 lines
6.6 KiB
C#
using System.Reflection;
|
|
using Omika.Data.HfSql.Metadata;
|
|
using Omika.Data.HfSql.Models.Omika;
|
|
using Omika.Data.Repositories.Omika;
|
|
|
|
namespace Omika.Tests.Repositories;
|
|
|
|
/// <summary>
|
|
/// Garde-fou structurel : chaque colonne référencée par les SELECT des
|
|
/// repositories Omika doit exister dans le modèle physique de la Row cible
|
|
/// (attribut <see cref="HfSqlColumnAttribute.ColumnName"/>).
|
|
///
|
|
/// Contexte : la recherche mobile (lot 36C) échouait en HTTP 500 sur
|
|
/// OMIKA_TEST car <see cref="OmikaRendezVousRepository"/> sélectionnait des
|
|
/// colonnes inexistantes dans le schéma Omika (ex. RVC_IDRdv, RVCT_IDRdv,
|
|
/// RVC_GUIDColb au lieu de RVC_GUID_RDV, RVC_GUID_Colb...). Ce test reproduit
|
|
/// ce défaut sans base réelle : il vérifie la cohérence SQL ↔ modèle.
|
|
/// </summary>
|
|
public class OmikaSqlColumnConsistencyTests
|
|
{
|
|
public static IEnumerable<object[]> Cases()
|
|
{
|
|
// Recherche mobile (enrichissement) : rendez-vous, clients, sites, interventions.
|
|
yield return [typeof(OmikaRendezVousRepository), "SqlSelectRendezVousColumns", typeof(RDVPlanningRow)];
|
|
yield return [typeof(OmikaRendezVousRepository), "SqlSelectCollaborateurColumns", typeof(RDVCollaborateurRow)];
|
|
yield return [typeof(OmikaRendezVousRepository), "SqlSelectContactColumns", typeof(RDVContactRow)];
|
|
yield return [typeof(OmikaRendezVousRepository), "SqlSelectParametreMetierColumns", typeof(ParametreMetierRow)];
|
|
|
|
yield return [typeof(OmikaClientRepository), "SqlSelectClientColumns", typeof(ClientRow)];
|
|
yield return [typeof(OmikaClientRepository), "SqlSelectAdresseColumns", typeof(AdresseRow)];
|
|
yield return [typeof(OmikaClientRepository), "SqlSelectLieuColumns", typeof(LieuRow)];
|
|
yield return [typeof(OmikaClientRepository), "SqlSelectPersonneColumns", typeof(PersonneRow)];
|
|
yield return [typeof(OmikaClientRepository), "SqlSelectParametreMetierColumns", typeof(ParametreMetierRow)];
|
|
yield return [typeof(OmikaClientRepository), "SqlSelectClientAdresseColumns", typeof(ClientAdresseRow)];
|
|
yield return [typeof(OmikaClientRepository), "SqlSelectClientLieuColumns", typeof(ClientLieuRow)];
|
|
yield return [typeof(OmikaClientRepository), "SqlSelectLieuPersonneColumns", typeof(LieuPersonneRow)];
|
|
|
|
yield return [typeof(OmikaSiteRepository), "SqlSelectLieuColumns", typeof(LieuRow)];
|
|
|
|
yield return [typeof(OmikaInterventionRepository), "SqlSelectInterventionColumns", typeof(InterventionRow)];
|
|
yield return [typeof(OmikaInterventionRepository), "SqlSelectParametreMetierColumns", typeof(ParametreMetierRow)];
|
|
yield return [typeof(OmikaInterventionRepository), "SqlSelectInterventionPersonneColumns", typeof(InterventionPersonneRow)];
|
|
yield return [typeof(OmikaInterventionRepository), "SqlSelectEtatInterventionColumns", typeof(EtatInterventionRow)];
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(Cases))]
|
|
public void ColonnesSelect_ExistentDansLeModelePhysique(Type repositoryType, string fieldName, Type rowType)
|
|
{
|
|
string sql = GetPrivateConstString(repositoryType, fieldName);
|
|
var physicalColumns = GetPhysicalColumns(rowType);
|
|
|
|
foreach (string column in ParseColumns(sql))
|
|
{
|
|
Assert.True(
|
|
physicalColumns.Contains(column, StringComparer.OrdinalIgnoreCase),
|
|
$"{repositoryType.Name}.{fieldName} référence la colonne « {column} » absente du modèle {rowType.Name}.");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void RendezVousRepository_GetDetailsAsync_NUtilisePasDeColonneNumeriqueFantome()
|
|
{
|
|
string source = ReadRendezVousRepositorySource();
|
|
|
|
// Colonnes de liaison numériques fantômes interdites : elles proviennent
|
|
// d'une transposition incorrecte du modèle Batik (*_IDRdv) alors que le
|
|
// modèle Omika relie les tables satellites du rendez-vous par GUID.
|
|
Assert.DoesNotContain("RVC_IDRdv", source, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("RVCT_IDRdv", source, StringComparison.OrdinalIgnoreCase);
|
|
|
|
// GetDetailsAsync doit adresser RDV_Collaborateur et RDV_Contact par leur
|
|
// GUID de liaison (RVC_GUID_RDV / RVCT_GUID_RDV), avec le GUID du
|
|
// rendez-vous racine (rendezVousRow.GUID) — exactement comme SearchDetailsAsync.
|
|
// Les affectations ARCHIVÉES sont exclues : un collaborateur retiré du
|
|
// rendez-vous ne fait plus partie du groupe.
|
|
Assert.Contains("FROM RDV_Collaborateur", source, StringComparison.Ordinal);
|
|
Assert.Contains("WHERE RVC_GUID_RDV = ? AND Archive = ?", source, StringComparison.Ordinal);
|
|
Assert.Contains("FROM RDV_Contact WHERE RVCT_GUID_RDV = ?", source, StringComparison.Ordinal);
|
|
Assert.Contains("[rendezVousRow.GUID, false]", source, StringComparison.Ordinal);
|
|
}
|
|
|
|
private static string ReadRendezVousRepositorySource()
|
|
{
|
|
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
|
var dir = new DirectoryInfo(baseDir);
|
|
while (dir != null && !File.Exists(Path.Combine(dir.FullName, "Omika.slnx")))
|
|
{
|
|
dir = dir.Parent;
|
|
}
|
|
|
|
string root = dir?.FullName ?? baseDir;
|
|
return File.ReadAllText(Path.Combine(
|
|
root, "src", "Omika.Data", "Repositories", "Omika", "OmikaRendezVousRepository.cs"));
|
|
}
|
|
|
|
private static string GetPrivateConstString(Type repositoryType, string fieldName)
|
|
{
|
|
FieldInfo? field = repositoryType.GetField(
|
|
fieldName,
|
|
BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public);
|
|
|
|
Assert.True(field is not null, $"Champ « {fieldName} » introuvable dans {repositoryType.Name}.");
|
|
Assert.True(field!.IsLiteral, $"{repositoryType.Name}.{fieldName} n'est pas une constante compile-time.");
|
|
|
|
string? value = field.GetRawConstantValue() as string;
|
|
Assert.False(string.IsNullOrWhiteSpace(value), $"{repositoryType.Name}.{fieldName} est vide.");
|
|
return value!;
|
|
}
|
|
|
|
private static HashSet<string> GetPhysicalColumns(Type rowType)
|
|
{
|
|
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (PropertyInfo property in rowType.GetProperties())
|
|
{
|
|
HfSqlColumnAttribute? attr = property.GetCustomAttribute<HfSqlColumnAttribute>();
|
|
if (attr is not null)
|
|
{
|
|
columns.Add(attr.ColumnName);
|
|
}
|
|
}
|
|
|
|
Assert.NotEmpty(columns);
|
|
return columns;
|
|
}
|
|
|
|
private static IEnumerable<string> ParseColumns(string sql)
|
|
=> sql
|
|
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
|
}
|