Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,791 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Omika.Core.Compatibility;
|
||||
using Omika.Core.Contracts.Common;
|
||||
using Omika.Core.Domain.Commands;
|
||||
using Omika.Core.Domain.Entities.Organisation;
|
||||
using Omika.Core.Interfaces;
|
||||
using Omika.Core.Runtime;
|
||||
using Omika.Data;
|
||||
using Omika.Data.Configuration;
|
||||
using Omika.Data.Connection;
|
||||
using Omika.Data.Repositories;
|
||||
using Omika.Data.Repositories.Batik;
|
||||
using Omika.Data.Repositories.Omika;
|
||||
|
||||
namespace Omika.Tests.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Tests unitaires de <see cref="AdaptiveCollaborateurRepository"/> : délégation,
|
||||
/// comportement Unknown, propagation d'erreurs, architecture, et DI.
|
||||
/// </summary>
|
||||
[Collection("ApplicationRuntimeContext")]
|
||||
public class AdaptiveCollaborateurRepositoryTests : IDisposable
|
||||
{
|
||||
public AdaptiveCollaborateurRepositoryTests()
|
||||
{
|
||||
ApplicationRuntimeContext.Reset();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ApplicationRuntimeContext.Reset();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DELEGATION SEARCH
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_BatikDetected_DelegatesToBatikRepository()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
batikConnection.CollaborateurRows = [BatikCollaborateurDict(1, "Dupont", "Jean")];
|
||||
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
await adaptive.SearchAsync(new CollaborateurSearchCriteria { Search = "Dupont" });
|
||||
|
||||
Assert.True(batikConnection.QueryCount > 0, "Batik aurait dû exécuter des requêtes.");
|
||||
Assert.Equal(0, omikaConnection.QueryCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_OmikaDetected_DelegatesToOmikaRepository()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
omikaConnection.CollaborateurRows = [OmikaCollaborateurDict(1, "Dupont", "Jean")];
|
||||
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
await adaptive.SearchAsync(new CollaborateurSearchCriteria { Search = "Dupont" });
|
||||
|
||||
Assert.Equal(0, batikConnection.QueryCount);
|
||||
Assert.True(omikaConnection.QueryCount > 0, "Omika aurait dû exécuter des requêtes.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_UnknownDetected_ThrowsInvalidOperationException()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => adaptive.SearchAsync(new CollaborateurSearchCriteria()));
|
||||
|
||||
Assert.Contains("not been detected", ex.Message);
|
||||
Assert.Equal(0, batikConnection.QueryCount);
|
||||
Assert.Equal(0, omikaConnection.QueryCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_EmptyCriteria_DoesNotCrash()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
batikConnection.CollaborateurRows = [];
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
PagedResult<Collaborateur> result = await adaptive.SearchAsync(new CollaborateurSearchCriteria());
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result.Items);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DELEGATION DETAIL
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public async Task GetDetailsAsync_BatikDetected_DelegatesToBatik()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
batikConnection.CollaborateurRows = [BatikCollaborateurDict(42, "Dupont", "Jean")];
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
Collaborateur? result = await adaptive.GetDetailsAsync("42");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.True(batikConnection.QueryCount > 0, "Batik aurait dû exécuter des requêtes.");
|
||||
Assert.Equal(0, omikaConnection.QueryCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDetailsAsync_OmikaDetected_DelegatesToOmika()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
omikaConnection.CollaborateurRows = [OmikaCollaborateurDict(42, "Dupont", "Jean")];
|
||||
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
Collaborateur? result = await adaptive.GetDetailsAsync("42");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(0, batikConnection.QueryCount);
|
||||
Assert.True(omikaConnection.QueryCount > 0, "Omika aurait dû exécuter des requêtes.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDetailsAsync_UnknownDetected_ThrowsInvalidOperationException()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => adaptive.GetDetailsAsync("42"));
|
||||
|
||||
Assert.Contains("not been detected", ex.Message);
|
||||
Assert.Equal(0, batikConnection.QueryCount);
|
||||
Assert.Equal(0, omikaConnection.QueryCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDetailsAsync_NullFromRepository_ReturnsNull()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
batikConnection.CollaborateurRows = [];
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
Collaborateur? result = await adaptive.GetDetailsAsync("999");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PROPAGATION D'ERREURS
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_BatikInfrastructureError_Propagated()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection { ThrowOnQuery = true };
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => adaptive.SearchAsync(new CollaborateurSearchCriteria()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDetailsAsync_OmikaInfrastructureError_Propagated()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
var omikaConnection = new SpyingHfsqlConnection { ThrowOnQuery = true };
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => adaptive.GetDetailsAsync("42"));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DI TESTS
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public void AddOmikaData_RegistersBatikCollaborateurRepositoryAsScoped()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddOmikaData();
|
||||
|
||||
var descriptor = services.Single(d => d.ServiceType == typeof(BatikCollaborateurRepository));
|
||||
Assert.Equal(ServiceLifetime.Scoped, descriptor.Lifetime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOmikaData_RegistersOmikaCollaborateurRepositoryAsScoped()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddOmikaData();
|
||||
|
||||
var descriptor = services.Single(d => d.ServiceType == typeof(OmikaCollaborateurRepository));
|
||||
Assert.Equal(ServiceLifetime.Scoped, descriptor.Lifetime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOmikaData_RegistersICollaborateurRepositoryAsScoped()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddOmikaData();
|
||||
|
||||
var descriptor = services.Single(d => d.ServiceType == typeof(ICollaborateurRepository));
|
||||
Assert.Equal(ServiceLifetime.Scoped, descriptor.Lifetime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOmikaData_ICollaborateurRepositoryPointsToAdaptiveCollaborateurRepository()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddOmikaData();
|
||||
|
||||
var descriptor = services.Single(d => d.ServiceType == typeof(ICollaborateurRepository));
|
||||
Assert.Equal(typeof(AdaptiveCollaborateurRepository), descriptor.ImplementationType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOmikaData_OnlyOneICollaborateurRepositoryRegistered()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddOmikaData();
|
||||
|
||||
var descriptors = services.Where(d => d.ServiceType == typeof(ICollaborateurRepository)).ToList();
|
||||
Assert.Single(descriptors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddOmikaData_ResolvesAllCollaborateurRepositories_WithFakeConnection()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
|
||||
services.AddOmikaData();
|
||||
|
||||
var oldDescriptor = services.Single(d => d.ServiceType == typeof(IHfsqlConnection));
|
||||
services.Remove(oldDescriptor);
|
||||
services.AddScoped<IHfsqlConnection, MinimalFakeHfsqlConnection>();
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
await using var scope = provider.CreateAsyncScope();
|
||||
|
||||
var collaborateurRepo = scope.ServiceProvider.GetRequiredService<ICollaborateurRepository>();
|
||||
Assert.IsType<AdaptiveCollaborateurRepository>(collaborateurRepo);
|
||||
|
||||
var batikRepo = scope.ServiceProvider.GetRequiredService<BatikCollaborateurRepository>();
|
||||
Assert.NotNull(batikRepo);
|
||||
|
||||
var omikaRepo = scope.ServiceProvider.GetRequiredService<OmikaCollaborateurRepository>();
|
||||
Assert.NotNull(omikaRepo);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DELEGATION UPDATE
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_BatikDetected_DelegatesToBatik()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
batikConnection.CollaborateurRows = [BatikCollaborateurDict(42, "Dupont", "Jean")];
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
await adaptive.UpdateAsync("42", new CollaborateurUpdateCommand { Nom = "Updated" },
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.True(batikConnection.QueryCount > 0, "Batik aurait dû exécuter des requêtes.");
|
||||
Assert.Equal(0, omikaConnection.QueryCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_OmikaDetected_DelegatesToOmika()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
omikaConnection.CollaborateurRows = [OmikaCollaborateurDict(42, "Dupont", "Jean")];
|
||||
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
await adaptive.UpdateAsync(Guid.NewGuid().ToString("D"), new CollaborateurUpdateCommand { Nom = "Updated" },
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Equal(0, batikConnection.QueryCount);
|
||||
Assert.True(omikaConnection.QueryCount > 0, "Omika aurait dû exécuter des requêtes.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_UnknownDetected_ThrowsInvalidOperationException()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => adaptive.UpdateAsync("42", new CollaborateurUpdateCommand()));
|
||||
|
||||
Assert.Contains("not been detected", ex.Message);
|
||||
Assert.Equal(0, batikConnection.QueryCount);
|
||||
Assert.Equal(0, omikaConnection.QueryCount);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DELEGATION ARCHIVE
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public async Task ArchiveAsync_BatikDetected_Existing_ReturnsTrue()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
batikConnection.CollaborateurRows = [BatikCollaborateurDict(42, "Dupont", "Jean")];
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
bool result = await adaptive.ArchiveAsync("42");
|
||||
|
||||
Assert.True(result);
|
||||
Assert.True(batikConnection.QueryCount > 0, "Batik aurait dû exécuter des requêtes.");
|
||||
Assert.Equal(0, omikaConnection.QueryCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArchiveAsync_OmikaDetected_Existing_ReturnsTrue()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
omikaConnection.CollaborateurRows = [OmikaCollaborateurDict(42, "Dupont", "Jean")];
|
||||
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
bool result = await adaptive.ArchiveAsync(Guid.NewGuid().ToString("D"));
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(0, batikConnection.QueryCount);
|
||||
Assert.True(omikaConnection.QueryCount > 0, "Omika aurait dû exécuter des requêtes.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArchiveAsync_UnknownDetected_ThrowsInvalidOperationException()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
|
||||
var batikConnection = new SpyingHfsqlConnection();
|
||||
var omikaConnection = new SpyingHfsqlConnection();
|
||||
var batikRepo = new BatikCollaborateurRepository(batikConnection);
|
||||
var omikaRepo = new OmikaCollaborateurRepository(omikaConnection);
|
||||
var adaptive = new AdaptiveCollaborateurRepository(batikRepo, omikaRepo);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => adaptive.ArchiveAsync("42"));
|
||||
|
||||
Assert.Contains("not been detected", ex.Message);
|
||||
Assert.Equal(0, batikConnection.QueryCount);
|
||||
Assert.Equal(0, omikaConnection.QueryCount);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// RÉGRESSION : GUID Collaborateur TRONQUÉ (32 caractères, WinDev)
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public async Task GetUtilisateurByCollaborateurAsync_Omika_GuidTronque_InterrogeToutesLesFormes()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
||||
|
||||
var connection = new SpyingHfsqlConnection();
|
||||
// GUID physique stocké TRONQUÉ à 32 caractères (WinDev a omis les 4
|
||||
// derniers hexadécimaux), comme observé en base réelle.
|
||||
connection.CollaborateurRows =
|
||||
[
|
||||
new Dictionary<string, object?>
|
||||
{
|
||||
["CO_PKID"] = 281474976710675L,
|
||||
["GUID"] = "62224eca-4565-4f47-9197-5c743940"
|
||||
}
|
||||
];
|
||||
|
||||
var repo = new OmikaCollaborateurRepository(connection);
|
||||
|
||||
// Identifiant canonique (forme « D » complétée par PhysicalGuidParser : + "0000").
|
||||
await repo.GetUtilisateurByCollaborateurAsync("62224eca-4565-4f47-9197-5c7439400000");
|
||||
|
||||
// La résolution doit tester TOUTES les formes de stockage (D, N, tronquée),
|
||||
// pas seulement la forme « D » (sinon le GUID tronqué ne matcherait jamais).
|
||||
string sqlCollaborateur = connection.AllSql.First(
|
||||
sql => sql.Contains("FROM Collaborateur", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Contains("GUID IN (", sqlCollaborateur);
|
||||
Assert.Contains("?, ?, ?", sqlCollaborateur);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDetailsAsync_Omika_GuidTronque_InterrogeToutesLesFormes()
|
||||
{
|
||||
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
||||
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
||||
|
||||
var connection = new SpyingHfsqlConnection();
|
||||
// GUID physique stocké TRONQUÉ à 32 caractères (WinDev a omis les 4
|
||||
// derniers hexadécimaux), comme observé en base réelle.
|
||||
connection.CollaborateurRows =
|
||||
[
|
||||
new Dictionary<string, object?>
|
||||
{
|
||||
["CO_PKID"] = 281474976710675L,
|
||||
["GUID"] = "62224eca-4565-4f47-9197-5c743940",
|
||||
["CO_Nom"] = "Dupont",
|
||||
["CO_Prenom"] = "Jean"
|
||||
}
|
||||
];
|
||||
|
||||
var repo = new OmikaCollaborateurRepository(connection);
|
||||
|
||||
// Identifiant canonique (forme « D » complétée par PhysicalGuidParser : + "0000").
|
||||
await repo.GetDetailsAsync("62224eca-4565-4f47-9197-5c7439400000");
|
||||
|
||||
// La lecture par identifiant doit tester TOUTES les formes de stockage
|
||||
// (D, N, tronquée) : un GUID = ? en forme « D » ne matcherait jamais un
|
||||
// GUID physique tronqué → « Collaborateur introuvable ».
|
||||
string sqlCollaborateur = connection.AllSql.First(
|
||||
sql => sql.Contains("FROM Collaborateur", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.Contains("GUID IN (", sqlCollaborateur);
|
||||
Assert.Contains("?, ?, ?", sqlCollaborateur);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// ARCHITECTURE
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public void Repository_ImplementsICollaborateurRepository()
|
||||
{
|
||||
Assert.True(typeof(ICollaborateurRepository).IsAssignableFrom(typeof(AdaptiveCollaborateurRepository)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Repository_ContainsNoSql()
|
||||
{
|
||||
string source = ReadSourceFile(typeof(AdaptiveCollaborateurRepository));
|
||||
|
||||
Assert.DoesNotContain("SELECT ", source, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("FROM ", source, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("WHERE ", source, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Repository_ContainsNoOdbcTypes()
|
||||
{
|
||||
var type = typeof(AdaptiveCollaborateurRepository);
|
||||
var referenced = GetPublicSurfaceTypes(type);
|
||||
|
||||
Assert.DoesNotContain(referenced, t =>
|
||||
t.Namespace is not null && t.Namespace.StartsWith("System.Data.Odbc", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Repository_ContainsNoHfsqlRowReferences()
|
||||
{
|
||||
var type = typeof(AdaptiveCollaborateurRepository);
|
||||
var referenced = GetPublicSurfaceTypes(type);
|
||||
|
||||
Assert.DoesNotContain(referenced, t =>
|
||||
t.Name.Contains("Row", StringComparison.OrdinalIgnoreCase)
|
||||
&& t.Namespace is not null
|
||||
&& t.Namespace.Contains("HfSql", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Repository_DoesNotDependOnHfsqlConnection()
|
||||
{
|
||||
var type = typeof(AdaptiveCollaborateurRepository);
|
||||
|
||||
bool hasHfsqlConnectionCtor = type.GetConstructors()
|
||||
.Any(c => c.GetParameters()
|
||||
.Any(p => typeof(IHfsqlConnection).IsAssignableFrom(p.ParameterType)));
|
||||
|
||||
Assert.False(hasHfsqlConnectionCtor,
|
||||
"AdaptiveCollaborateurRepository ne doit pas dépendre directement de IHfsqlConnection.");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// HELPERS
|
||||
// =========================================================================
|
||||
|
||||
private static IEnumerable<Type> GetPublicSurfaceTypes(Type type)
|
||||
{
|
||||
var members = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
|
||||
.Select(m => m.DeclaringType)
|
||||
.Where(t => t is not null)
|
||||
.Cast<Type>();
|
||||
|
||||
var parameters = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.SelectMany(m => m.GetParameters())
|
||||
.Select(p => p.ParameterType);
|
||||
|
||||
var returnTypes = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.Select(m => m.ReturnType);
|
||||
|
||||
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.Select(p => p.PropertyType);
|
||||
|
||||
var ctorParams = type.GetConstructors()
|
||||
.SelectMany(c => c.GetParameters())
|
||||
.Select(p => p.ParameterType);
|
||||
|
||||
return members.Concat(parameters).Concat(returnTypes).Concat(properties).Concat(ctorParams);
|
||||
}
|
||||
|
||||
private static string ReadSourceFile(Type type)
|
||||
{
|
||||
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", type.Name + ".cs"));
|
||||
}
|
||||
|
||||
// --- Dictionnaires de lignes physiques ----------------------------------
|
||||
|
||||
private static Dictionary<string, object?> BatikCollaborateurDict(
|
||||
long pkid, string? nom, string? prenom)
|
||||
=> new()
|
||||
{
|
||||
["CL_PKID"] = pkid,
|
||||
["CL_Nom"] = nom,
|
||||
["CL_Prenom"] = prenom,
|
||||
["CL_Code"] = "JD",
|
||||
["CL_EMail"] = "jean@example.com",
|
||||
["CL_Telephone"] = "0102030405",
|
||||
["CL_IDCivilite"] = 0L,
|
||||
["CL_IDStatut"] = 0L,
|
||||
["CL_IDFonction"] = 0L,
|
||||
["CL_IDSociete"] = 0L,
|
||||
["CL_CouleurPlanning"] = "#FF0000",
|
||||
["CL_Actif"] = true,
|
||||
["Archive"] = false
|
||||
};
|
||||
|
||||
private static Dictionary<string, object?> OmikaCollaborateurDict(
|
||||
long pkid, string? nom, string? prenom)
|
||||
=> new()
|
||||
{
|
||||
["CL_PKID"] = pkid,
|
||||
["CL_Nom"] = nom,
|
||||
["CL_Prenom"] = prenom,
|
||||
["CL_Code"] = "JD",
|
||||
["CL_EMail"] = "jean@example.com",
|
||||
["CL_Telephone"] = "0102030405",
|
||||
["CL_CleCivilite"] = null,
|
||||
["CL_CleStatut"] = null,
|
||||
["CL_CleFonction"] = null,
|
||||
["CL_IdSociete"] = 0L,
|
||||
["CL_CouleurPlanning"] = "#FF0000",
|
||||
["CL_Actif"] = true,
|
||||
["GUID"] = null,
|
||||
["Archive"] = false
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// SpyingHfsqlConnection
|
||||
// =========================================================================
|
||||
|
||||
private sealed class SpyingHfsqlConnection : IHfsqlConnection
|
||||
{
|
||||
public HfsqlOptions Options => new();
|
||||
public HfsqlConnectionState State { get; private set; } = HfsqlConnectionState.Closed;
|
||||
|
||||
public int QueryCount { get; private set; }
|
||||
public bool ThrowOnQuery { get; set; }
|
||||
public string? LastSql { get; private set; }
|
||||
public IReadOnlyList<object?>? LastParameters { get; private set; }
|
||||
public CancellationToken LastCancellationToken { get; private set; }
|
||||
public IReadOnlyList<string> AllSql => _allSql;
|
||||
private readonly List<string> _allSql = [];
|
||||
|
||||
public IReadOnlyList<IReadOnlyDictionary<string, object?>> CollaborateurRows { get; set; } = [];
|
||||
|
||||
private void Record(string sql, IReadOnlyList<object?>? parameters, CancellationToken cancellationToken)
|
||||
{
|
||||
LastSql = sql;
|
||||
LastParameters = parameters;
|
||||
LastCancellationToken = cancellationToken;
|
||||
QueryCount++;
|
||||
_allSql.Add(sql);
|
||||
}
|
||||
|
||||
public Task OpenAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
State = HfsqlConnectionState.Open;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CloseAsync()
|
||||
{
|
||||
State = HfsqlConnectionState.Closed;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<object?> ExecuteScalarAsync(string sql, IReadOnlyList<object?>? parameters = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Record(sql, parameters, cancellationToken);
|
||||
return Task.FromResult<object?>(null);
|
||||
}
|
||||
|
||||
public Task<int> ExecuteNonQueryAsync(string sql, IReadOnlyList<object?>? parameters = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Record(sql, parameters, cancellationToken);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IReadOnlyDictionary<string, object?>>> QueryAsync(
|
||||
string sql, IReadOnlyList<object?>? parameters = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Record(sql, parameters, cancellationToken);
|
||||
|
||||
if (ThrowOnQuery)
|
||||
throw new InvalidOperationException("Panne d'infrastructure simulée.");
|
||||
|
||||
return Task.FromResult(ResolveRows(sql));
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<HfsqlColumnMetadata>> GetColumnsAsync(
|
||||
string tableName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult<IReadOnlyList<HfsqlColumnMetadata>>([]);
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
State = HfsqlConnectionState.Closed;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private IReadOnlyList<IReadOnlyDictionary<string, object?>> ResolveRows(string sql)
|
||||
{
|
||||
if (ContainsFrom(sql, "Collaborateur") || ContainsFrom(sql, "Collaborateurs"))
|
||||
return CollaborateurRows;
|
||||
return [];
|
||||
}
|
||||
|
||||
private static bool ContainsFrom(string sql, string table)
|
||||
{
|
||||
string marker = $"FROM {table}";
|
||||
int index = sql.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
|
||||
while (index >= 0)
|
||||
{
|
||||
int next = index + marker.Length;
|
||||
if (next >= sql.Length || (!char.IsLetterOrDigit(sql[next]) && sql[next] != '_'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
index = sql.IndexOf(marker, index + 1, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// MinimalFakeHfsqlConnection (DI)
|
||||
// =========================================================================
|
||||
|
||||
private sealed class MinimalFakeHfsqlConnection : IHfsqlConnection
|
||||
{
|
||||
public HfsqlOptions Options => new();
|
||||
public HfsqlConnectionState State { get; private set; } = HfsqlConnectionState.Closed;
|
||||
|
||||
public Task OpenAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
State = HfsqlConnectionState.Open;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CloseAsync()
|
||||
{
|
||||
State = HfsqlConnectionState.Closed;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<object?> ExecuteScalarAsync(string sql, IReadOnlyList<object?>? parameters = null, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<object?>(null);
|
||||
|
||||
public Task<int> ExecuteNonQueryAsync(string sql, IReadOnlyList<object?>? parameters = null, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(0);
|
||||
|
||||
public Task<IReadOnlyList<IReadOnlyDictionary<string, object?>>> QueryAsync(
|
||||
string sql, IReadOnlyList<object?>? parameters = null, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<IReadOnlyList<IReadOnlyDictionary<string, object?>>>([]);
|
||||
|
||||
public Task<IReadOnlyList<HfsqlColumnMetadata>> GetColumnsAsync(
|
||||
string tableName, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<IReadOnlyList<HfsqlColumnMetadata>>([]);
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
State = HfsqlConnectionState.Closed;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user