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);
}
}