using System.Reflection; 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; namespace Omika.Tests.Architecture; /// /// Tests du contrat canonique : indépendance /// vis-à-vis d'Omika.Data, de HFSQL/ODBC et des modèles physiques, périmètre /// lecture seule et compatibilité avec une implémentation en mémoire. /// public class IClientRepositoryContractTests { private static Type RepositoryType => typeof(IClientRepository); private static Assembly CoreAssembly => typeof(IClientRepository).Assembly; [Fact] public void Interface_ExistsInOmikaCore() { Assert.True(typeof(IClientRepository).IsInterface); Assert.Equal("Omika.Core", CoreAssembly.GetName().Name); } [Fact] public void Core_DoesNotReferenceOmikaData() { Assert.DoesNotContain( CoreAssembly.GetReferencedAssemblies(), assembly => assembly.Name == "Omika.Data"); } [Fact] public void Core_DoesNotReferenceOdbcOrHfSql() { Assert.DoesNotContain( CoreAssembly.GetReferencedAssemblies(), assembly => assembly.Name == "System.Data.Odbc"); } [Fact] public void Signatures_DoNotExposePhysicalRowTypes() { foreach (MethodInfo method in RepositoryType.GetMethods()) { Assert.False( method.ReturnType.Name.EndsWith("Row", StringComparison.Ordinal), $"Le retour de « {method.Name} » expose un type physique « {method.ReturnType.Name} »."); foreach (ParameterInfo parameter in method.GetParameters()) { Assert.False( parameter.ParameterType.Name.EndsWith("Row", StringComparison.Ordinal), $"Le paramètre « {parameter.Name} » de « {method.Name} » expose un type physique « {parameter.ParameterType.Name} »."); } } } [Fact] public void HasSearchMethod_ReturningCanonicalPagedResult() { MethodInfo? search = RepositoryType.GetMethod("SearchAsync"); Assert.NotNull(search); Assert.Equal(typeof(Task>), search.ReturnType); } [Fact] public void HasDetailsMethod_ReturningNullableClient() { MethodInfo? details = RepositoryType.GetMethod("GetDetailsAsync"); Assert.NotNull(details); Assert.Equal(typeof(Task), details.ReturnType); } [Fact] public void AllMethods_AcceptCancellationToken() { foreach (MethodInfo method in RepositoryType.GetMethods()) { Assert.Contains( method.GetParameters(), parameter => parameter.ParameterType == typeof(CancellationToken)); } } [Fact] public void HasNoDeleteMethods() { // CreateAsync, UpdateAsync et ArchiveAsync existent maintenant (écriture // supportée). Seule la suppression physique (Delete) reste interdite. foreach (MethodInfo method in RepositoryType.GetMethods()) { Assert.DoesNotContain("Delete", method.Name, StringComparison.OrdinalIgnoreCase); } } [Fact] public void HasNoBatikOrOmikaSpecificMethods() { foreach (MethodInfo method in RepositoryType.GetMethods()) { Assert.DoesNotContain("Batik", method.Name, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("Omika", method.Name, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("Legacy", method.Name, StringComparison.OrdinalIgnoreCase); } } [Fact] public void CanBeImplementedByInMemoryFake() { var fake = new InMemoryClientRepository(); PagedResult result = fake.SearchAsync(new ClientSearchCriteria()).GetAwaiter().GetResult(); Client? missing = fake.GetDetailsAsync("CL-001").GetAwaiter().GetResult(); Assert.Empty(result.Items); Assert.Null(missing); } /// /// Implémentation en mémoire minimale prouvant que le contrat est /// implémentable sans aucune dépendance physique. /// private sealed class InMemoryClientRepository : IClientRepository { public Task CreateAsync( ClientCreateCommand command, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(command); return Task.FromResult(new Client { Nom = command.Nom }); } public Task> SearchAsync( ClientSearchCriteria criteria, CancellationToken cancellationToken = default) => Task.FromResult(new PagedResult([], 1, 20, 0, 0)); public Task> SearchDetailsAsync( ClientSearchCriteria criteria, CancellationToken cancellationToken = default) => Task.FromResult(new PagedResult([], 1, 20, 0, 0)); public Task GetDetailsAsync( string id, CancellationToken cancellationToken = default) => Task.FromResult(null); public Task UpdateAsync( string id, Omika.Core.Domain.Commands.ClientUpdateCommand command, CancellationToken cancellationToken = default) => Task.FromResult(null); public Task ArchiveAsync( string id, CancellationToken cancellationToken = default) => Task.FromResult(false); } }