1069 lines
44 KiB
C#
1069 lines
44 KiB
C#
using System.Reflection;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Omika.Core.Compatibility;
|
|
using Omika.Core.Contracts.Clients;
|
|
using Omika.Core.Contracts.Common;
|
|
using Omika.Core.Domain.Commands;
|
|
using Omika.Core.Domain.Entities.Clients;
|
|
using Omika.Core.Interfaces;
|
|
using Omika.Core.Runtime;
|
|
using Omika.Data;
|
|
using Omika.Data.Configuration;
|
|
using Omika.Data.Connection;
|
|
using Omika.Data.HfSql.Metadata;
|
|
using Omika.Data.Repositories;
|
|
using Omika.Data.Repositories.Batik;
|
|
using Omika.Data.Repositories.Omika;
|
|
using Omika.Data.SchemaDetection;
|
|
|
|
namespace Omika.Tests.Repositories;
|
|
|
|
/// <summary>
|
|
/// Tests unitaires de <see cref="AdaptiveClientRepository"/> : délégation,
|
|
/// comportement Unknown, propagation d'erreurs, architecture, et DI.
|
|
///
|
|
/// Les tests qui manipulent le contexte statique global
|
|
/// <see cref="ApplicationRuntimeContext"/> sont exécutés dans la collection
|
|
/// <c>"ApplicationRuntimeContext"</c> qui sérialise leur exécution.
|
|
///
|
|
/// Stratégie : les repositories Batik/Omika sont des instances réelles (sealed),
|
|
/// connectées à un <see cref="SpyingHfsqlConnection"/> qui enregistre les SQL.
|
|
/// On distingue le repository appelé par les colonnes caractéristiques dans le SQL
|
|
/// (Omika inclut GUID, CL_CleType ; Batik non).
|
|
/// </summary>
|
|
[Collection("ApplicationRuntimeContext")]
|
|
public class AdaptiveClientRepositoryTests : IDisposable
|
|
{
|
|
public AdaptiveClientRepositoryTests()
|
|
{
|
|
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();
|
|
// Donner assez de données pour que le pipeline SearchAsync fonctionne.
|
|
batikConnection.ClientRows = [BatikClientDict(1, "Test", "C001")];
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
await adaptive.SearchAsync(new ClientSearchCriteria { Nom = "Test" });
|
|
|
|
// Batik a exécuté des requêtes SQL, Omika aucune.
|
|
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.ClientRows = [OmikaClientDict(1, "Test", "C001")];
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
await adaptive.SearchAsync(new ClientSearchCriteria { Nom = "Test" });
|
|
|
|
// Omika a exécuté des requêtes SQL, Batik aucune.
|
|
Assert.Equal(0, batikConnection.QueryCount);
|
|
Assert.True(omikaConnection.QueryCount > 0, "Omika aurait dû exécuter des requêtes.");
|
|
}
|
|
|
|
// =========================================================================
|
|
// DELEGATION CREATE
|
|
// =========================================================================
|
|
|
|
[Fact]
|
|
public async Task CreateAsync_BatikDetected_DelegatesToBatikRepository()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
// Le fake ne fournit pas d'identité auto-générée : le pipeline Create
|
|
// lève en fin de parcours, mais la délégation vers Batik a bien eu lieu.
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
adaptive.CreateAsync(new ClientCreateCommand { Nom = "Test" }));
|
|
|
|
// Batik a exécuté des requêtes SQL (INSERT + référentiels), Omika aucune.
|
|
Assert.True(batikConnection.QueryCount > 0, "Batik aurait dû exécuter des requêtes.");
|
|
Assert.Equal(0, omikaConnection.QueryCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateAsync_OmikaDetected_DelegatesToOmikaRepository()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
adaptive.CreateAsync(new ClientCreateCommand { Nom = "Test" }));
|
|
|
|
// Omika a exécuté des requêtes SQL (INSERT + référentiels), Batik aucune.
|
|
Assert.Equal(0, batikConnection.QueryCount);
|
|
Assert.True(omikaConnection.QueryCount > 0, "Omika aurait dû exécuter des requêtes.");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_BatikDetected_UsesBatikingSqlColumns()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
batikConnection.ClientRows = [BatikClientDict(1, "Test", "C001")];
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
await adaptive.SearchAsync(new ClientSearchCriteria());
|
|
|
|
// Le SQL Batik contient CL_RaisonBlocage (majuscule L) mais pas GUID.
|
|
string clientSql = batikConnection.AllSql.First(s => s.Contains("FROM Client", StringComparison.OrdinalIgnoreCase));
|
|
Assert.Contains("CL_RaisonBlocage", clientSql);
|
|
Assert.DoesNotContain("GUID", clientSql);
|
|
Assert.DoesNotContain("CL_CleType", clientSql);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_OmikaDetected_UsesOmikaSqlColumns()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
omikaConnection.ClientRows = [OmikaClientDict(1, "Test", "C001")];
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
await adaptive.SearchAsync(new ClientSearchCriteria());
|
|
|
|
// Le SQL Omika contient GUID, CL_CleType (colonnes spécifiques Omika).
|
|
string clientSql = omikaConnection.AllSql.First(s => s.Contains("FROM Client", StringComparison.OrdinalIgnoreCase));
|
|
Assert.Contains("GUID", clientSql);
|
|
Assert.Contains("CL_CleType", clientSql);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_UnknownDetected_ThrowsInvalidOperationException()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
// DetectedVersion reste Unknown.
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => adaptive.SearchAsync(new ClientSearchCriteria()));
|
|
|
|
Assert.Contains("not been detected", ex.Message);
|
|
// Aucun repository n'a été contacté.
|
|
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.ClientRows = [];
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
PagedResult<Client> result = await adaptive.SearchAsync(new ClientSearchCriteria());
|
|
|
|
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.ClientRows = [BatikClientDict(42, "Dupont", "C001")];
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
Client? 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.ClientRows = [OmikaClientDict(42, "Dupont", "C001")];
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
Client? 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_BatikDetected_UsesClPkidQuery()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
batikConnection.ClientRows = [BatikClientDict(42, "Dupont", "C001")];
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
await adaptive.GetDetailsAsync("42");
|
|
|
|
// Batik interroge par CL_PKID = ? (pas de GUID).
|
|
string clientSql = batikConnection.AllSql.First(s => s.Contains("FROM Client", StringComparison.OrdinalIgnoreCase));
|
|
Assert.Contains("CL_PKID", clientSql);
|
|
Assert.DoesNotContain("GUID", clientSql);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDetailsAsync_OmikaDetected_UsesGuidOrClPkidQuery()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
var guid = Guid.NewGuid();
|
|
omikaConnection.ClientRows = [OmikaClientDict(42, "Dupont", "C001", guid: guid.ToString("D"))];
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
await adaptive.GetDetailsAsync(guid.ToString("D"));
|
|
|
|
// Omika interroge par GUID = ?.
|
|
string clientSql = omikaConnection.AllSql.First(s => s.Contains("FROM Client", StringComparison.OrdinalIgnoreCase));
|
|
Assert.Contains("GUID", clientSql);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDetailsAsync_UnknownDetected_ThrowsInvalidOperationException()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(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.ClientRows = []; // Aucun client → null.
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
Client? result = await adaptive.GetDetailsAsync("999");
|
|
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetDetailsAsync_CancellationTokenTransmitted()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
omikaConnection.ClientRows = [OmikaClientDict(42, "Dupont", "C001")];
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
using var cts = new CancellationTokenSource();
|
|
|
|
await adaptive.GetDetailsAsync("42", cts.Token);
|
|
|
|
Assert.Equal(cts.Token, omikaConnection.LastCancellationToken);
|
|
}
|
|
|
|
// =========================================================================
|
|
// 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 BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => adaptive.SearchAsync(new ClientSearchCriteria()));
|
|
}
|
|
|
|
[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 BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => adaptive.GetDetailsAsync("42"));
|
|
}
|
|
|
|
// =========================================================================
|
|
// DI TESTS
|
|
// =========================================================================
|
|
|
|
[Fact]
|
|
public void AddOmikaData_RegistersBatikClientRepositoryAsScoped()
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddOmikaData();
|
|
|
|
var descriptor = services.Single(d => d.ServiceType == typeof(BatikClientRepository));
|
|
Assert.Equal(ServiceLifetime.Scoped, descriptor.Lifetime);
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOmikaData_RegistersOmikaClientRepositoryAsScoped()
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddOmikaData();
|
|
|
|
var descriptor = services.Single(d => d.ServiceType == typeof(OmikaClientRepository));
|
|
Assert.Equal(ServiceLifetime.Scoped, descriptor.Lifetime);
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOmikaData_RegistersIClientRepositoryAsScoped()
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddOmikaData();
|
|
|
|
var descriptor = services.Single(d => d.ServiceType == typeof(IClientRepository));
|
|
Assert.Equal(ServiceLifetime.Scoped, descriptor.Lifetime);
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOmikaData_IClientRepositoryPointsToAdaptiveClientRepository()
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddOmikaData();
|
|
|
|
var descriptor = services.Single(d => d.ServiceType == typeof(IClientRepository));
|
|
Assert.Equal(typeof(AdaptiveClientRepository), descriptor.ImplementationType);
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOmikaData_BatikRepositoryDoesNotDependOnIClientRepository()
|
|
{
|
|
// BatikClientRepository doit être enregistré par son type concret,
|
|
// sans dépendre de IClientRepository (pas de résolution récursive).
|
|
var services = new ServiceCollection();
|
|
services.AddOmikaData();
|
|
|
|
var descriptor = services.Single(d => d.ServiceType == typeof(BatikClientRepository));
|
|
Assert.Equal(ServiceLifetime.Scoped, descriptor.Lifetime);
|
|
// ImplementationFactory est null car BatikClientRepository est enregistré
|
|
// par type concret, pas via une factory qui résoudrait IClientRepository.
|
|
Assert.Null(descriptor.ImplementationFactory);
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOmikaData_PhysicalRepositoriesRegisteredIndividually()
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddOmikaData();
|
|
|
|
// Les repositories physiques doivent être enregistrés par leur type concret.
|
|
var batikDescriptor = services.Single(d => d.ServiceType == typeof(BatikClientRepository));
|
|
var omikaDescriptor = services.Single(d => d.ServiceType == typeof(OmikaClientRepository));
|
|
|
|
Assert.Equal(ServiceLifetime.Scoped, batikDescriptor.Lifetime);
|
|
Assert.Equal(ServiceLifetime.Scoped, omikaDescriptor.Lifetime);
|
|
// Les types d'implémentation correspondent aux repositories concrets.
|
|
Assert.Equal(typeof(BatikClientRepository), batikDescriptor.ImplementationType);
|
|
Assert.Equal(typeof(OmikaClientRepository), omikaDescriptor.ImplementationType);
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOmikaData_HfsqlConnectionRemainsScoped()
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddOmikaData();
|
|
|
|
var descriptor = services.Single(d => d.ServiceType == typeof(IHfsqlConnection));
|
|
Assert.Equal(ServiceLifetime.Scoped, descriptor.Lifetime);
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOmikaData_OnlyOneIClientRepositoryRegistered()
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddOmikaData();
|
|
|
|
var descriptors = services.Where(d => d.ServiceType == typeof(IClientRepository)).ToList();
|
|
Assert.Single(descriptors);
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOmikaData_ResolvesAllClientRepositories_WithFakeConnection()
|
|
{
|
|
// Construit le graphe DI réel : remplace IHfsqlConnection par un fake
|
|
// mémoire pour éviter toute connexion ODBC/HFSQL réelle.
|
|
var services = new ServiceCollection();
|
|
|
|
// Configuration minimale (vide) pour satisfaire IConfiguration requis
|
|
// par AddOptions<HfsqlOptions>().Configure<IConfiguration>(...).
|
|
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
|
|
|
|
services.AddOmikaData();
|
|
|
|
// Remplacer IHfsqlConnection → HfsqlConnection par un fake Scoped.
|
|
var oldDescriptor = services.Single(d => d.ServiceType == typeof(IHfsqlConnection));
|
|
services.Remove(oldDescriptor);
|
|
services.AddScoped<IHfsqlConnection, MinimalFakeHfsqlConnection>();
|
|
|
|
var provider = services.BuildServiceProvider();
|
|
using var scope = provider.CreateScope();
|
|
|
|
// IClientRepository doit se résoudre en AdaptiveClientRepository.
|
|
var clientRepo = scope.ServiceProvider.GetRequiredService<IClientRepository>();
|
|
Assert.IsType<AdaptiveClientRepository>(clientRepo);
|
|
|
|
// Les repositories physiques sont résolvables individuellement.
|
|
var batikRepo = scope.ServiceProvider.GetRequiredService<BatikClientRepository>();
|
|
Assert.NotNull(batikRepo);
|
|
|
|
var omikaRepo = scope.ServiceProvider.GetRequiredService<OmikaClientRepository>();
|
|
Assert.NotNull(omikaRepo);
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOmikaData_IClientRepositoryIsScoped()
|
|
{
|
|
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();
|
|
|
|
// Même scope → même instance.
|
|
using var scope1 = provider.CreateScope();
|
|
var repoA = scope1.ServiceProvider.GetRequiredService<IClientRepository>();
|
|
var repoB = scope1.ServiceProvider.GetRequiredService<IClientRepository>();
|
|
Assert.Same(repoA, repoB);
|
|
|
|
// Scope différent → instance différente.
|
|
using var scope2 = provider.CreateScope();
|
|
var repoC = scope2.ServiceProvider.GetRequiredService<IClientRepository>();
|
|
Assert.NotSame(repoA, repoC);
|
|
}
|
|
|
|
// =========================================================================
|
|
// DELEGATION UPDATE
|
|
// =========================================================================
|
|
|
|
[Fact]
|
|
public async Task UpdateAsync_BatikDetected_DelegatesToBatik()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
batikConnection.ClientRows = [BatikClientDict(42, "Dupont", "C001")];
|
|
batikConnection.ParametreMetierRows = [];
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
await adaptive.UpdateAsync("42", new ClientUpdateCommand { 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.ClientRows = [OmikaClientDict(42, "Dupont", "C001")];
|
|
omikaConnection.ParametreMetierRows = [];
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
await adaptive.UpdateAsync("42", new ClientUpdateCommand { 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);
|
|
// DetectedVersion reste Unknown.
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => adaptive.UpdateAsync("42", new ClientUpdateCommand()));
|
|
|
|
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.ClientRows = [BatikClientDict(42, "Dupont", "C001")];
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(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_BatikDetected_Absent_ReturnsFalse()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Batik);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
batikConnection.ClientRows = [];
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
bool result = await adaptive.ArchiveAsync("42");
|
|
|
|
Assert.False(result);
|
|
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.ClientRows = [OmikaClientDict(42, "Dupont", "C001")];
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
bool result = await adaptive.ArchiveAsync("42");
|
|
|
|
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_OmikaDetected_Absent_ReturnsFalse()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
ApplicationRuntimeContext.SetDetectedVersion(DatabaseSchemaVersion.Omika);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
omikaConnection.ClientRows = [];
|
|
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(batikRepo, omikaRepo);
|
|
|
|
bool result = await adaptive.ArchiveAsync("42");
|
|
|
|
Assert.False(result);
|
|
Assert.Equal(0, batikConnection.QueryCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ArchiveAsync_UnknownDetected_ThrowsInvalidOperationException()
|
|
{
|
|
ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto);
|
|
|
|
var batikConnection = new SpyingHfsqlConnection();
|
|
var omikaConnection = new SpyingHfsqlConnection();
|
|
var batikRepo = new BatikClientRepository(batikConnection);
|
|
var omikaRepo = new OmikaClientRepository(omikaConnection);
|
|
var adaptive = new AdaptiveClientRepository(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);
|
|
}
|
|
|
|
// =========================================================================
|
|
// ARCHITECTURE
|
|
// =========================================================================
|
|
|
|
[Fact]
|
|
public void Repository_ImplementsIClientRepository()
|
|
{
|
|
Assert.True(typeof(IClientRepository).IsAssignableFrom(typeof(AdaptiveClientRepository)));
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_ContainsNoSql()
|
|
{
|
|
string source = ReadSourceFile(typeof(AdaptiveClientRepository));
|
|
|
|
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(AdaptiveClientRepository);
|
|
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_ContainsNoGuidOrLongParsing()
|
|
{
|
|
string source = ReadSourceFile(typeof(AdaptiveClientRepository));
|
|
|
|
Assert.DoesNotContain("Guid.", source, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("long.TryParse", source, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("long.Parse", source, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_ContainsNoHfsqlRowReferences()
|
|
{
|
|
var type = typeof(AdaptiveClientRepository);
|
|
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_ContainsNoBatikMapperReferences()
|
|
{
|
|
var type = typeof(AdaptiveClientRepository);
|
|
var referenced = GetPublicSurfaceTypes(type);
|
|
|
|
Assert.DoesNotContain(referenced, t =>
|
|
t.Namespace is not null
|
|
&& t.Namespace.Contains("Mapping.Batik", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_ContainsNoOmikaMapperReferences()
|
|
{
|
|
var type = typeof(AdaptiveClientRepository);
|
|
var referenced = GetPublicSurfaceTypes(type);
|
|
|
|
Assert.DoesNotContain(referenced, t =>
|
|
t.Namespace is not null
|
|
&& t.Namespace.Contains("Mapping.Omika", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_DoesNotDependOnHfsqlConnection()
|
|
{
|
|
var type = typeof(AdaptiveClientRepository);
|
|
|
|
bool hasHfsqlConnectionCtor = type.GetConstructors()
|
|
.Any(c => c.GetParameters()
|
|
.Any(p => typeof(IHfsqlConnection).IsAssignableFrom(p.ParameterType)));
|
|
|
|
Assert.False(hasHfsqlConnectionCtor,
|
|
"AdaptiveClientRepository ne doit pas dépendre directement de IHfsqlConnection.");
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_DoesNotDependOnDatabaseSchemaDetector()
|
|
{
|
|
var type = typeof(AdaptiveClientRepository);
|
|
var referenced = GetPublicSurfaceTypes(type);
|
|
|
|
Assert.DoesNotContain(referenced, t =>
|
|
t.Name.Contains("DatabaseSchemaDetector", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
// =========================================================================
|
|
// 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?> BatikClientDict(
|
|
long pkid, string? nom, string? code,
|
|
long typeId = 0, long statutId = 0, long adressePrincipaleId = 0)
|
|
=> new()
|
|
{
|
|
["CL_PKID"] = pkid,
|
|
["CL_Nom"] = nom,
|
|
["CL_CodeClient"] = code,
|
|
["CL_IDType"] = typeId,
|
|
["CL_IDStatut"] = statutId,
|
|
["CL_IDAdressePrincipale"] = adressePrincipaleId,
|
|
["CL_Blocage"] = false,
|
|
["Archive"] = false
|
|
};
|
|
|
|
private static Dictionary<string, object?> OmikaClientDict(
|
|
long pkid, string? nom, string? code,
|
|
long typeId = 0, long statutId = 0, long adressePrincipaleId = 0,
|
|
string? guid = null, string? typeCle = null, string? statutCle = null)
|
|
=> new()
|
|
{
|
|
["CL_PKID"] = pkid,
|
|
["CL_Nom"] = nom,
|
|
["CL_CodeClient"] = code,
|
|
["CL_IDType"] = typeId,
|
|
["CL_IDStatut"] = statutId,
|
|
["CL_CleType"] = typeCle,
|
|
["CL_CleStatut"] = statutCle,
|
|
["CL_IDAdressePrincipale"] = adressePrincipaleId,
|
|
["CL_Blocage"] = false,
|
|
["Archive"] = false,
|
|
["Cl_RaisonBlocage"] = null,
|
|
["CL_Email"] = null,
|
|
["CL_Tel"] = null,
|
|
["CL_Fax"] = null,
|
|
["CL_IDImport"] = null,
|
|
["CL_CodeComptable"] = null,
|
|
["CL_CodeTVA"] = null,
|
|
["CL_SIREN"] = null,
|
|
["CL_SIRET"] = null,
|
|
["CL_Pays"] = null,
|
|
["CL_InfoSup"] = null,
|
|
["CL_FormeJuridique"] = null,
|
|
["CL_RepertoireDocs"] = null,
|
|
["CL_IDContactPrincipal"] = 0L,
|
|
["CL_IDLieuPrincipal"] = 0L,
|
|
["CL_IDApporteur"] = 0L,
|
|
["CL_IDApportOrigine"] = 0L,
|
|
["CL_IDModeReglement"] = 0L,
|
|
["CL_CleApporteur"] = null,
|
|
["CL_CleApporteurOrigine"] = null,
|
|
["CL_CleModeReglement"] = null,
|
|
["GUID"] = guid,
|
|
["ModifDate"] = DateTime.MinValue,
|
|
["CreationDate"] = DateTime.MinValue
|
|
};
|
|
|
|
// =========================================================================
|
|
// 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?>> ClientRows { get; set; } = [];
|
|
public IReadOnlyList<IReadOnlyDictionary<string, object?>> ParametreMetierRows { get; set; } = [];
|
|
public IReadOnlyList<IReadOnlyDictionary<string, object?>> AdresseRows { get; set; } = [];
|
|
public IReadOnlyList<IReadOnlyDictionary<string, object?>> LieuRows { get; set; } = [];
|
|
public IReadOnlyList<IReadOnlyDictionary<string, object?>> PersonneRows { get; set; } = [];
|
|
public IReadOnlyList<IReadOnlyDictionary<string, object?>> ClientAdresseRows { get; set; } = [];
|
|
public IReadOnlyList<IReadOnlyDictionary<string, object?>> ClientLieuRows { get; set; } = [];
|
|
public IReadOnlyList<IReadOnlyDictionary<string, object?>> LieuPersonneRows { 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)
|
|
=> Task.FromResult<IReadOnlyList<HfsqlColumnMetadata>>([]);
|
|
|
|
public ValueTask DisposeAsync()
|
|
{
|
|
State = HfsqlConnectionState.Closed;
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
|
|
private IReadOnlyList<IReadOnlyDictionary<string, object?>> ResolveRows(string sql)
|
|
{
|
|
if (ContainsFrom(sql, "Client_Adresse")) return ClientAdresseRows;
|
|
if (ContainsFrom(sql, "Client_Lieu")) return ClientLieuRows;
|
|
if (ContainsFrom(sql, "Lieu_Personne")) return LieuPersonneRows;
|
|
if (ContainsFrom(sql, "ParametreMetier")) return ParametreMetierRows;
|
|
if (ContainsFrom(sql, "Adresse")) return AdresseRows;
|
|
if (ContainsFrom(sql, "Lieu")) return LieuRows;
|
|
if (ContainsFrom(sql, "Personne")) return PersonneRows;
|
|
if (ContainsFrom(sql, "Client")) return ClientRows;
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implémentation minimale de <see cref="IHfsqlConnection"/> utilisée
|
|
/// uniquement pour les tests de résolution DI. Ne contacte aucune base.
|
|
/// </summary>
|
|
private sealed class MinimalFakeHfsqlConnection : IHfsqlConnection, IDisposable
|
|
{
|
|
public HfsqlOptions Options => new();
|
|
public HfsqlConnectionState State => HfsqlConnectionState.Closed;
|
|
|
|
public void Dispose() { }
|
|
public Task OpenAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
|
public Task CloseAsync() => 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() => ValueTask.CompletedTask;
|
|
}
|
|
}
|