using System.Data.Common; using Omika.Core.Domain.Commands; using Omika.Data.Configuration; using Omika.Data.Connection; using Omika.Data.Repositories.Batik; using Omika.Data.Repositories.Omika; namespace Omika.Tests.Repositories; /// /// Vérifie que l'INSERT dans Client_Adresse n'écrit AUCUNE colonne d'audit /// (CreationDate/User, ModifDate/User) — absentes du schéma canonique Batik et /// Omika — et que le mapping des colonnes métier est correct dans chaque schéma. /// public class ClientAdresseInsertTests { [Fact] public async Task Omika_ClientAdresse_Insert_SansColonneAudit() { var connection = new SpyingHfsqlConnection(); var repository = new OmikaClientRepository(connection); await Assert.ThrowsAsync(() => repository.CreateAsync(CreateCommandWithAddress())); var insert = connection.Commands.Single(c => c.Sql.StartsWith("INSERT INTO Client_Adresse")); Assert.DoesNotContain("CreationDate", insert.Sql); Assert.DoesNotContain("CreationUser", insert.Sql); Assert.DoesNotContain("ModifDate", insert.Sql); Assert.DoesNotContain("ModifUser", insert.Sql); Assert.Contains("CA_IDAdresse", insert.Sql); Assert.Contains("CA_IDClient", insert.Sql); Assert.Contains("CA_IDTypeAdresse", insert.Sql); Assert.Contains("CA_Principale", insert.Sql); // Clé composée HFSQL auto-calculée : jamais écrite explicitement en Omika. Assert.DoesNotContain("CA_IDCli_IDAd", insert.Sql); } [Fact] public async Task Omika_ClientAdresse_ParametresCorrects() { var connection = new SpyingHfsqlConnection(); var repository = new OmikaClientRepository(connection); await Assert.ThrowsAsync(() => repository.CreateAsync(CreateCommandWithAddress())); var insert = connection.Commands.Single(c => c.Sql.StartsWith("INSERT INTO Client_Adresse")); object?[] p = insert.Parameters; Assert.Equal(4, p.Length); Assert.Equal(1L, p[0]); // CA_IDAdresse (identité auto-générée = 1) Assert.Equal(1L, p[1]); // CA_IDClient (identité auto-générée = 1) Assert.Equal(0, p[2]); // CA_IDTypeAdresse Assert.Equal(true, p[3]); // CA_Principale } [Fact] public async Task Batik_ClientAdresse_Insert_SansColonneAudit() { var connection = new SpyingHfsqlConnection(); var repository = new BatikClientRepository(connection); await Assert.ThrowsAsync(() => repository.CreateAsync(CreateCommandWithAddress())); var insert = connection.Commands.Single(c => c.Sql.StartsWith("INSERT INTO Client_Adresse")); Assert.DoesNotContain("CreationDate", insert.Sql); Assert.DoesNotContain("CreationUser", insert.Sql); Assert.DoesNotContain("ModifDate", insert.Sql); Assert.DoesNotContain("ModifUser", insert.Sql); Assert.Contains("CA_IDAdresse", insert.Sql); Assert.Contains("CA_IDClient", insert.Sql); Assert.Contains("CA_IDTypeAdresse", insert.Sql); Assert.Contains("CA_Principale", insert.Sql); // Batik écrit la clé composée explicitement (comportement historique). Assert.Contains("CA_IDCli_IDAd", insert.Sql); } [Fact] public async Task Batik_ClientAdresse_ParametresCorrects() { var connection = new SpyingHfsqlConnection(); var repository = new BatikClientRepository(connection); await Assert.ThrowsAsync(() => repository.CreateAsync(CreateCommandWithAddress())); var insert = connection.Commands.Single(c => c.Sql.StartsWith("INSERT INTO Client_Adresse")); object?[] p = insert.Parameters; Assert.Equal(5, p.Length); Assert.Equal(1L, p[0]); // CA_IDAdresse Assert.Equal(1L, p[1]); // CA_IDClient Assert.Equal("11", p[2]); // CA_IDCli_IDAd = clientId + adresseId Assert.Equal(0, p[3]); // CA_IDTypeAdresse Assert.Equal(true, p[4]); // CA_Principale } private static ClientCreateCommand CreateCommandWithAddress() => new() { Nom = "Test", Code = "T-001", AdresseLigne1 = "1 rue du Test", AdresseCodePostal = "75000", AdresseVille = "Paris" }; private sealed record ExecutedCommand(string Sql, object?[] Parameters); private sealed class SpyingHfsqlConnection : IHfsqlConnection, IHfsqlWriteConnection { public HfsqlOptions Options => new(); public HfsqlConnectionState State { get; private set; } = HfsqlConnectionState.Closed; public DbTransaction? ActiveTransaction => null; public IReadOnlyList Commands => _commands; private readonly List _commands = []; private void Record(string sql, IReadOnlyList? parameters) => _commands.Add(new ExecutedCommand(sql, parameters?.ToArray() ?? Array.Empty())); public Task OpenAsync(CancellationToken cancellationToken = default) { State = HfsqlConnectionState.Open; return Task.CompletedTask; } public Task CloseAsync() { State = HfsqlConnectionState.Closed; return Task.CompletedTask; } public ValueTask DisposeAsync() { State = HfsqlConnectionState.Closed; return ValueTask.CompletedTask; } public Task ExecuteScalarAsync(string sql, IReadOnlyList? parameters = null, CancellationToken cancellationToken = default) => Task.FromResult(null); public Task ExecuteNonQueryAsync(string sql, IReadOnlyList? parameters = null, CancellationToken cancellationToken = default) { Record(sql, parameters); return Task.FromResult(0); } public Task>> QueryAsync(string sql, IReadOnlyList? parameters = null, CancellationToken cancellationToken = default) { Record(sql, parameters); return Task.FromResult>>([]); } public Task> GetColumnsAsync(string tableName, CancellationToken cancellationToken = default) => Task.FromResult>([]); public Task BeginTransactionAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task CommitAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task RollbackAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task> ExecuteInTransactionAsync(IReadOnlyList operations, CancellationToken cancellationToken = default, DbTransaction? transaction = null) { foreach (HfsqlWriteOperation operation in operations) { Record(operation.Sql, operation.Parameters); } // Une identité auto-générée (1) pour chaque opération. return Task.FromResult>(operations.Select(_ => (long?)1L).ToList()); } } }