using System.Reflection;
using Omika.Core.Contracts.Clients;
using Omika.Core.Contracts.Common;
using Omika.Core.Contracts.Mobile.Sync;
using Omika.Core.Domain.Commands;
using Omika.Core.Domain.Entities.Clients;
using Omika.Core.Domain.ValueObjects;
using Omika.Core.Interfaces;
using Omika.Core.Interfaces.Mobile;
using Omika.Core.Mapping;
using Omika.Core.Services;
namespace Omika.Tests.Services;
///
/// Tests unitaires de avec un fake
/// en mémoire. Aucune base réelle.
///
public class ClientServiceTests
{
private static readonly IMobileEntityIdentityResolver IdentityResolver = new FakeIdentityResolver();
// =========================================================================
// SEARCH
// =========================================================================
[Fact]
public async Task SearchAsync_CallsRepositoryOnce()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest());
Assert.Equal(1, repo.SearchCallCount);
}
[Fact]
public async Task SearchAsync_TransmitsPage()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { Page = 3 });
Assert.Equal(3, repo.LastSearchCriteria?.Page);
}
[Fact]
public async Task SearchAsync_TransmitsPageSize()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { PageSize = 50 });
Assert.Equal(50, repo.LastSearchCriteria?.PageSize);
}
[Fact]
public async Task SearchAsync_TransmitsSearch()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { Search = "test" });
Assert.Equal("test", repo.LastSearchCriteria?.Search);
}
[Fact]
public async Task SearchAsync_TransmitsNom()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { Nom = "Dupont" });
Assert.Equal("Dupont", repo.LastSearchCriteria?.Nom);
}
[Fact]
public async Task SearchAsync_TransmitsCode()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { Code = "C001" });
Assert.Equal("C001", repo.LastSearchCriteria?.Code);
}
[Fact]
public async Task SearchAsync_TransmitsVille()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { Ville = "Paris" });
Assert.Equal("Paris", repo.LastSearchCriteria?.Ville);
}
[Fact]
public async Task SearchAsync_TransmitsType()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { Type = "Entreprise" });
Assert.Equal("Entreprise", repo.LastSearchCriteria?.Type);
}
[Fact]
public async Task SearchAsync_TransmitsStatut()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { Statut = "Actif" });
Assert.Equal("Actif", repo.LastSearchCriteria?.Statut);
}
[Fact]
public async Task SearchAsync_TransmitsEstArchive()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { EstArchive = true });
Assert.True(repo.LastSearchCriteria?.EstArchive);
}
[Fact]
public async Task SearchAsync_TransmitsEstBloque()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { EstBloque = true });
Assert.True(repo.LastSearchCriteria?.EstBloque);
}
[Fact]
public async Task SearchAsync_TransmitsSortBy()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { SortBy = "Code" });
Assert.Equal("Code", repo.LastSearchCriteria?.SortBy);
}
[Fact]
public async Task SearchAsync_TransmitsSortDescending()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.SearchAsync(new SearchClientsRequest { SortDescending = true });
Assert.True(repo.LastSearchCriteria?.SortDescending);
}
[Fact]
public async Task SearchAsync_TransmitsCancellationToken()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
using var cts = new CancellationTokenSource();
await service.SearchAsync(new SearchClientsRequest(), cts.Token);
Assert.Equal(cts.Token, repo.LastCancellationToken);
}
[Fact]
public async Task SearchAsync_EmptyResult_ReturnsEmptyPage()
{
var repo = new FakeClientRepository
{
SearchResult = new PagedResult([], 1, 20, 0, 0)
};
var service = new ClientService(repo, IdentityResolver);
PagedResult result = await service.SearchAsync(
new SearchClientsRequest());
Assert.Empty(result.Items);
Assert.Equal(0, result.TotalCount);
Assert.Equal(0, result.TotalPages);
Assert.Equal(1, result.Page);
Assert.Equal(20, result.PageSize);
}
[Fact]
public async Task SearchAsync_SingleClient_MapsToSummaryDto()
{
var client = new Client
{
Code = "C001",
Nom = "Dupont",
Type = new ReferenceValue("Entreprise") { Key = "ENTREPRISE" },
Statut = new ReferenceValue("Actif") { Key = "ACTIF" },
AdressePrincipale = new Adresse
{
AdressePostale = new PostalAddress(City: "Paris")
},
Coordonnees = new ContactDetails(Phone: "0102030405"),
EstBloque = false,
EstArchive = false
};
var repo = new FakeClientRepository
{
SearchResult = new PagedResult([client], 1, 20, 1, 1)
};
var service = new ClientService(repo, IdentityResolver);
PagedResult result = await service.SearchAsync(
new SearchClientsRequest());
ClientSummaryDto dto = Assert.Single(result.Items);
Assert.Equal("C001", dto.Code);
Assert.Equal("Dupont", dto.Nom);
Assert.Equal("Entreprise", dto.Type);
Assert.Equal("Actif", dto.Statut);
Assert.Equal("Paris", dto.Ville);
Assert.Equal("0102030405", dto.Telephone);
Assert.False(dto.EstBloque);
Assert.False(dto.EstArchive);
}
[Fact]
public async Task SearchAsync_MultipleClients_MapsAllToDtos()
{
var clients = new[]
{
new Client { Nom = "Alpha", Code = "A" },
new Client { Nom = "Beta", Code = "B" },
new Client { Nom = "Gamma", Code = "C" }
};
var repo = new FakeClientRepository
{
SearchResult = new PagedResult(clients, 1, 20, 3, 1)
};
var service = new ClientService(repo, IdentityResolver);
PagedResult result = await service.SearchAsync(
new SearchClientsRequest());
Assert.Equal(3, result.Items.Count);
Assert.Equal(3, result.TotalCount);
Assert.Equal("Alpha", result.Items[0].Nom);
Assert.Equal("Beta", result.Items[1].Nom);
Assert.Equal("Gamma", result.Items[2].Nom);
}
[Fact]
public async Task SearchAsync_PreservesTotalCount()
{
var repo = new FakeClientRepository
{
SearchResult = new PagedResult([], 2, 10, 42, 5)
};
var service = new ClientService(repo, IdentityResolver);
PagedResult result = await service.SearchAsync(
new SearchClientsRequest());
Assert.Equal(42, result.TotalCount);
Assert.Equal(2, result.Page);
Assert.Equal(10, result.PageSize);
Assert.Equal(5, result.TotalPages);
}
[Fact]
public async Task SearchAsync_IdComesFromClientContractMapper()
{
var client = new Client { Nom = "Test" };
var expectedId = client.Id.ToString();
var repo = new FakeClientRepository
{
SearchResult = new PagedResult([client], 1, 20, 1, 1)
};
var service = new ClientService(repo, IdentityResolver);
PagedResult result = await service.SearchAsync(
new SearchClientsRequest());
Assert.Equal(expectedId, result.Items[0].Id);
}
[Fact]
public async Task SearchAsync_ServiceDoesNotRePaginate()
{
// Le service ne doit pas altérer la pagination du repository.
var repo = new FakeClientRepository
{
SearchResult = new PagedResult(
[new Client { Nom = "A" }, new Client { Nom = "B" }], 3, 15, 100, 7)
};
var service = new ClientService(repo, IdentityResolver);
PagedResult result = await service.SearchAsync(
new SearchClientsRequest { Page = 1, PageSize = 5 });
// Le repo reçoit la demande mais retourne ce qu'il veut.
// Le service doit préserver le résultat du repo, pas refaire la pagination.
Assert.Equal(2, result.Items.Count);
Assert.Equal(100, result.TotalCount);
Assert.Equal(7, result.TotalPages);
}
// =========================================================================
// DETAIL
// =========================================================================
[Fact]
public async Task GetByIdAsync_TransmitsExactId()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.GetByIdAsync("test-id-123");
Assert.Equal("test-id-123", repo.LastDetailsId);
}
[Fact]
public async Task GetByIdAsync_TransmitsCancellationToken()
{
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
using var cts = new CancellationTokenSource();
await service.GetByIdAsync("42", cts.Token);
Assert.Equal(cts.Token, repo.LastCancellationToken);
}
[Fact]
public async Task GetByIdAsync_RepositoryReturnsNull_ReturnsNull()
{
var repo = new FakeClientRepository { DetailsResult = null };
var service = new ClientService(repo, IdentityResolver);
ClientDetailsDto? result = await service.GetByIdAsync("999");
Assert.Null(result);
}
[Fact]
public async Task GetByIdAsync_MapsToDetailsDto()
{
var client = new Client
{
Code = "C001",
Nom = "Dupont",
Type = new ReferenceValue("Entreprise") { Key = "ENTREPRISE" },
Statut = new ReferenceValue("Actif") { Key = "ACTIF" },
EstBloque = false,
EstArchive = false
};
var repo = new FakeClientRepository { DetailsResult = client };
var service = new ClientService(repo, IdentityResolver);
ClientDetailsDto? result = await service.GetByIdAsync("42");
Assert.NotNull(result);
Assert.Equal("C001", result.Code);
Assert.Equal("Dupont", result.Nom);
Assert.Equal("Entreprise", result.Type?.Label);
Assert.Equal("Actif", result.Statut?.Label);
}
[Fact]
public async Task GetByIdAsync_AdressesMapped()
{
var client = new Client { Nom = "Test" };
client.AddAdresse(new Adresse
{
Etiquette = "Siège",
AdressePostale = new PostalAddress(City: "Paris"),
EstPrincipale = true
});
var repo = new FakeClientRepository { DetailsResult = client };
var service = new ClientService(repo, IdentityResolver);
ClientDetailsDto? result = await service.GetByIdAsync("42");
Assert.NotNull(result);
Assert.Single(result.Adresses);
Assert.Equal("Siège", result.Adresses[0].Etiquette);
Assert.Equal("Paris", result.Adresses[0].AdressePostale?.City);
}
[Fact]
public async Task GetByIdAsync_ContactsMapped()
{
var client = new Client { Nom = "Test" };
client.AddContact(new Contact { Nom = "Martin", Prenom = "Jean" });
var repo = new FakeClientRepository { DetailsResult = client };
var service = new ClientService(repo, IdentityResolver);
ClientDetailsDto? result = await service.GetByIdAsync("42");
Assert.NotNull(result);
Assert.Single(result.Contacts);
Assert.Equal("Martin", result.Contacts[0].Nom);
Assert.Equal("Jean Martin", result.Contacts[0].NomAffiche);
}
[Fact]
public async Task GetByIdAsync_SitesMapped()
{
var client = new Client { Nom = "Test" };
client.AddSite(new Site { Code = "S1", Nom = "Site 1" });
var repo = new FakeClientRepository { DetailsResult = client };
var service = new ClientService(repo, IdentityResolver);
ClientDetailsDto? result = await service.GetByIdAsync("42");
Assert.NotNull(result);
Assert.Single(result.Sites);
Assert.Equal("S1", result.Sites[0].Code);
}
[Fact]
public async Task GetByIdAsync_IdComesFromMapper()
{
var client = new Client { Nom = "Test" };
var expectedId = client.Id.ToString();
var repo = new FakeClientRepository { DetailsResult = client };
var service = new ClientService(repo, IdentityResolver);
ClientDetailsDto? result = await service.GetByIdAsync("42");
Assert.NotNull(result);
Assert.Equal(expectedId, result.Id);
}
[Fact]
public async Task GetByIdAsync_NoGuidParsingInService()
{
// Le service transmet l'id tel quel, sans parsing.
var repo = new FakeClientRepository();
var service = new ClientService(repo, IdentityResolver);
await service.GetByIdAsync("not-a-guid-nor-int");
Assert.Equal("not-a-guid-nor-int", repo.LastDetailsId);
}
[Fact]
public async Task GetByIdAsync_RepositoryException_Propagated()
{
var repo = new FakeClientRepository
{
ThrowOnDetails = new InvalidOperationException("Infrastructure error")
};
var service = new ClientService(repo, IdentityResolver);
var ex = await Assert.ThrowsAsync(
() => service.GetByIdAsync("42"));
Assert.Equal("Infrastructure error", ex.Message);
}
// =========================================================================
// CREATE
// =========================================================================
[Fact]
public async Task CreateAsync_CallsRepositoryOnce()
{
var repo = new FakeClientRepository
{
CreateResult = new Client { Nom = "Nouveau", Code = "C001" }
};
var service = new ClientService(repo, IdentityResolver);
await service.CreateAsync(new CreateClientRequest { Nom = "Nouveau" });
Assert.Equal(1, repo.CreateCallCount);
}
[Fact]
public async Task CreateAsync_MapsAllProperties()
{
var repo = new FakeClientRepository
{
CreateResult = new Client { Nom = "Nouveau" }
};
var service = new ClientService(repo, IdentityResolver);
await service.CreateAsync(new CreateClientRequest
{
Code = "C001",
Nom = "Nouveau",
Type = new ReferenceValueDto { Key = "ENT" },
Statut = new ReferenceValueDto { Key = "ACT" },
FormeJuridique = new ReferenceValueDto { Key = "SARL" },
CodeTva = "FR123",
CodeComptable = "411",
Siren = "123456789",
Siret = "12345678900014",
Pays = "FR",
Coordonnees = new ContactDetailsDto { Email = "a@b.c", Phone = "0102" },
InformationsComplementaires = "Info sup",
AdressePrincipale = new AddressDto
{
Label = "Etiquette",
Line1 = "1 rue",
PostalCode = "75001",
City = "Paris"
}
});
Assert.Equal("C001", repo.LastCreateCommand!.Code);
Assert.Equal("Nouveau", repo.LastCreateCommand!.Nom);
Assert.Equal("ENT", repo.LastCreateCommand!.TypeKey);
Assert.Equal("ACT", repo.LastCreateCommand!.StatutKey);
Assert.Equal("SARL", repo.LastCreateCommand!.FormeJuridique);
Assert.Equal("FR123", repo.LastCreateCommand!.CodeTva);
Assert.Equal("411", repo.LastCreateCommand!.CodeComptable);
Assert.Equal("123456789", repo.LastCreateCommand!.Siren);
Assert.Equal("12345678900014", repo.LastCreateCommand!.Siret);
Assert.Equal("FR", repo.LastCreateCommand!.Pays);
Assert.Equal("a@b.c", repo.LastCreateCommand!.Email);
Assert.Equal("0102", repo.LastCreateCommand!.Telephone);
Assert.Equal("Info sup", repo.LastCreateCommand!.InformationsComplementaires);
Assert.Equal("Etiquette", repo.LastCreateCommand!.AdresseEtiquette);
Assert.Equal("1 rue", repo.LastCreateCommand!.AdresseLigne1);
Assert.Equal("75001", repo.LastCreateCommand!.AdresseCodePostal);
Assert.Equal("Paris", repo.LastCreateCommand!.AdresseVille);
}
[Fact]
public async Task CreateAsync_ResultMappedToDetails()
{
var repo = new FakeClientRepository
{
CreateResult = new Client { Nom = "Nouveau", Code = "C001" }
};
var service = new ClientService(repo, IdentityResolver);
ClientDetailsDto result = await service.CreateAsync(new CreateClientRequest { Nom = "Nouveau" });
Assert.Equal("Nouveau", result.Nom);
Assert.Equal("C001", result.Code);
}
[Fact]
public async Task CreateAsync_CancellationTokenTransmitted()
{
var repo = new FakeClientRepository
{
CreateResult = new Client { Nom = "Nouveau" }
};
var service = new ClientService(repo, IdentityResolver);
using var cts = new CancellationTokenSource();
await service.CreateAsync(new CreateClientRequest { Nom = "Nouveau" }, cts.Token);
Assert.Equal(cts.Token, repo.LastCreateCancellationToken);
}
[Fact]
public async Task CreateAsync_ExceptionPropagated()
{
var repo = new FakeClientRepository
{
ThrowOnCreate = new InvalidOperationException("Panne")
};
var service = new ClientService(repo, IdentityResolver);
var ex = await Assert.ThrowsAsync(
() => service.CreateAsync(new CreateClientRequest { Nom = "Nouveau" }));
Assert.Equal("Panne", ex.Message);
}
// =========================================================================
// UPDATE
// =========================================================================
[Fact]
public async Task UpdateAsync_MapsAllProperties()
{
var repo = new FakeClientRepository
{
UpdateResult = new Client { Nom = "Updated", Code = "C002" }
};
var service = new ClientService(repo, IdentityResolver);
await service.UpdateAsync("42", new UpdateClientRequest
{
Code = "C002",
Nom = "Updated",
Type = new ReferenceValueDto { Key = "ENT" },
Statut = new ReferenceValueDto { Key = "ACT" },
FormeJuridique = new ReferenceValueDto { Key = "SARL" },
CodeTva = "FR123",
CodeComptable = "411",
Siren = "123456789",
Siret = "12345678900014",
Pays = "FR",
Coordonnees = new ContactDetailsDto { Email = "a@b.c", Phone = "0102" },
InformationsComplementaires = "Info sup",
EstBloque = true
});
Assert.Equal(1, repo.UpdateCallCount);
Assert.Equal("42", repo.LastUpdateId);
Assert.Equal("C002", repo.LastUpdateCommand!.Code);
Assert.Equal("Updated", repo.LastUpdateCommand!.Nom);
Assert.Equal("ENT", repo.LastUpdateCommand!.TypeKey);
Assert.Equal("ACT", repo.LastUpdateCommand!.StatutKey);
Assert.Equal("SARL", repo.LastUpdateCommand!.FormeJuridique);
Assert.Equal("FR123", repo.LastUpdateCommand!.CodeTva);
Assert.Equal("411", repo.LastUpdateCommand!.CodeComptable);
Assert.Equal("123456789", repo.LastUpdateCommand!.Siren);
Assert.Equal("12345678900014", repo.LastUpdateCommand!.Siret);
Assert.Equal("FR", repo.LastUpdateCommand!.Pays);
Assert.Equal("a@b.c", repo.LastUpdateCommand!.Email);
Assert.Equal("0102", repo.LastUpdateCommand!.Telephone);
Assert.Equal("Info sup", repo.LastUpdateCommand!.InformationsComplementaires);
Assert.True(repo.LastUpdateCommand!.EstBloque);
}
[Fact]
public async Task UpdateAsync_NullsPreserved()
{
var repo = new FakeClientRepository { UpdateResult = new Client { Nom = "X" } };
var service = new ClientService(repo, IdentityResolver);
await service.UpdateAsync("42", new UpdateClientRequest());
Assert.Null(repo.LastUpdateCommand!.TypeKey);
Assert.Null(repo.LastUpdateCommand!.StatutKey);
Assert.Null(repo.LastUpdateCommand!.FormeJuridique);
Assert.Null(repo.LastUpdateCommand!.Email);
Assert.Null(repo.LastUpdateCommand!.Telephone);
}
[Fact]
public async Task UpdateAsync_RepoReturnsNull_ReturnsNull()
{
var repo = new FakeClientRepository { UpdateResult = null };
var service = new ClientService(repo, IdentityResolver);
ClientDetailsDto? result = await service.UpdateAsync("42", new UpdateClientRequest());
Assert.Null(result);
}
[Fact]
public async Task UpdateAsync_IdTransmittedExactly()
{
var repo = new FakeClientRepository
{
UpdateResult = new Client { Nom = "XY" }
};
var service = new ClientService(repo, IdentityResolver);
await service.UpdateAsync("my-guid-123", new UpdateClientRequest());
Assert.Equal("my-guid-123", repo.LastUpdateId);
}
[Fact]
public async Task UpdateAsync_CancellationTokenTransmitted()
{
var repo = new FakeClientRepository { UpdateResult = new Client { Nom = "X" } };
var service = new ClientService(repo, IdentityResolver);
using var cts = new CancellationTokenSource();
await service.UpdateAsync("42", new UpdateClientRequest(), cts.Token);
Assert.Equal(cts.Token, repo.LastUpdateCancellationToken);
}
[Fact]
public async Task UpdateAsync_ExceptionPropagated()
{
var repo = new FakeClientRepository
{
ThrowOnUpdate = new InvalidOperationException("Panne")
};
var service = new ClientService(repo, IdentityResolver);
var ex = await Assert.ThrowsAsync(
() => service.UpdateAsync("42", new UpdateClientRequest()));
Assert.Equal("Panne", ex.Message);
}
// =========================================================================
// ARCHIVE
// =========================================================================
[Fact]
public async Task ArchiveAsync_TruePropagated()
{
var repo = new FakeClientRepository { ArchiveResult = true };
var service = new ClientService(repo, IdentityResolver);
bool result = await service.ArchiveAsync("42");
Assert.True(result);
Assert.Equal(1, repo.ArchiveCallCount);
}
[Fact]
public async Task ArchiveAsync_FalsePropagated()
{
var repo = new FakeClientRepository { ArchiveResult = false };
var service = new ClientService(repo, IdentityResolver);
bool result = await service.ArchiveAsync("42");
Assert.False(result);
}
[Fact]
public async Task ArchiveAsync_IdTransmittedExactly()
{
var repo = new FakeClientRepository { ArchiveResult = true };
var service = new ClientService(repo, IdentityResolver);
await service.ArchiveAsync("my-guid-123");
Assert.Equal("my-guid-123", repo.LastArchiveId);
}
[Fact]
public async Task ArchiveAsync_CancellationTokenTransmitted()
{
var repo = new FakeClientRepository { ArchiveResult = true };
var service = new ClientService(repo, IdentityResolver);
using var cts = new CancellationTokenSource();
await service.ArchiveAsync("42", cts.Token);
Assert.Equal(cts.Token, repo.LastArchiveCancellationToken);
}
[Fact]
public async Task ArchiveAsync_ExceptionPropagated()
{
var repo = new FakeClientRepository
{
ThrowOnArchive = new InvalidOperationException("Panne")
};
var service = new ClientService(repo, IdentityResolver);
var ex = await Assert.ThrowsAsync(
() => service.ArchiveAsync("42"));
Assert.Equal("Panne", ex.Message);
}
// =========================================================================
// ARCHITECTURE
// =========================================================================
[Fact]
public void Service_ImplementsIClientService()
{
Assert.True(typeof(IClientService).IsAssignableFrom(typeof(ClientService)));
}
[Fact]
public void Service_DependsOnIClientRepository()
{
var type = typeof(ClientService);
bool hasRepoParam = type.GetConstructors()
.Any(c => c.GetParameters()
.Any(p => p.ParameterType == typeof(IClientRepository)));
Assert.True(hasRepoParam, "ClientService doit dépendre de IClientRepository.");
}
[Fact]
public void Service_HasNoOmikaDataReferences()
{
var type = typeof(ClientService);
var refs = GetReferencedTypes(type);
Assert.DoesNotContain(refs, t =>
t.Namespace is not null && t.Namespace.StartsWith("Omika.Data"));
}
[Fact]
public void Service_HasNoBatikReferences()
{
var type = typeof(ClientService);
var refs = GetReferencedTypes(type);
Assert.DoesNotContain(refs, t =>
t.Name.Contains("Batik", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void Service_HasNoHfsqlReferences()
{
var type = typeof(ClientService);
var refs = GetReferencedTypes(type);
Assert.DoesNotContain(refs, t =>
t.Name.Contains("Row", StringComparison.OrdinalIgnoreCase)
&& t.Namespace is not null
&& (t.Namespace.Contains("HfSql", StringComparison.OrdinalIgnoreCase)
|| t.Namespace.Contains("Models.Omika", StringComparison.OrdinalIgnoreCase)));
}
[Fact]
public void Service_HasNoSqlStatements()
{
string source = ReadSourceFile(typeof(ClientService));
Assert.DoesNotContain("SELECT ", source, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("FROM ", source, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Service_HasNoApplicationRuntimeContext()
{
var type = typeof(ClientService);
var refs = GetReferencedTypes(type);
Assert.DoesNotContain(refs, t =>
t.Name.Contains("ApplicationRuntimeContext", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void Service_HasNoDatabaseSchemaVersion()
{
var type = typeof(ClientService);
var refs = GetReferencedTypes(type);
Assert.DoesNotContain(refs, t =>
t.Name.Contains("DatabaseSchemaVersion", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void Service_HasNoOdbcReferences()
{
var type = typeof(ClientService);
var refs = GetReferencedTypes(type);
Assert.DoesNotContain(refs, t =>
t.Namespace is not null && t.Namespace.StartsWith("System.Data.Odbc", StringComparison.Ordinal));
}
// =========================================================================
// HELPERS
// =========================================================================
private static IEnumerable GetReferencedTypes(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();
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.Core", "Services", type.Name + ".cs"));
}
// =========================================================================
// FAKE REPOSITORY
// =========================================================================
private sealed class FakeClientRepository : IClientRepository
{
public int SearchCallCount { get; private set; }
public int DetailsCallCount { get; private set; }
public int UpdateCallCount { get; private set; }
public int ArchiveCallCount { get; private set; }
public int CreateCallCount { get; private set; }
public ClientSearchCriteria? LastSearchCriteria { get; private set; }
public string? LastDetailsId { get; private set; }
public string? LastUpdateId { get; private set; }
public string? LastArchiveId { get; private set; }
public ClientUpdateCommand? LastUpdateCommand { get; private set; }
public ClientCreateCommand? LastCreateCommand { get; private set; }
public CancellationToken LastUpdateCancellationToken { get; private set; }
public CancellationToken LastArchiveCancellationToken { get; private set; }
public CancellationToken LastCreateCancellationToken { get; private set; }
public CancellationToken LastCancellationToken { get; private set; }
public PagedResult? SearchResult { get; set; }
public Client? DetailsResult { get; set; }
public Client? UpdateResult { get; set; }
public Client? CreateResult { get; set; }
public bool ArchiveResult { get; set; }
public Exception? ThrowOnSearch { get; set; }
public Exception? ThrowOnDetails { get; set; }
public Exception? ThrowOnUpdate { get; set; }
public Exception? ThrowOnArchive { get; set; }
public Exception? ThrowOnCreate { get; set; }
public Task CreateAsync(
ClientCreateCommand command,
CancellationToken cancellationToken = default)
{
CreateCallCount++;
LastCreateCommand = command;
LastCreateCancellationToken = cancellationToken;
if (ThrowOnCreate is not null)
throw ThrowOnCreate;
return Task.FromResult(CreateResult ?? new Client { Nom = command.Nom });
}
public Task> SearchAsync(
ClientSearchCriteria criteria,
CancellationToken cancellationToken = default)
{
SearchCallCount++;
LastSearchCriteria = criteria;
LastCancellationToken = cancellationToken;
if (ThrowOnSearch is not null)
throw ThrowOnSearch;
return Task.FromResult(SearchResult
?? new PagedResult([], 1, 20, 0, 0));
}
public Task> SearchDetailsAsync(
ClientSearchCriteria criteria,
CancellationToken cancellationToken = default)
{
SearchCallCount++;
LastSearchCriteria = criteria;
LastCancellationToken = cancellationToken;
if (ThrowOnSearch is not null)
throw ThrowOnSearch;
return Task.FromResult(SearchResult
?? new PagedResult([], 1, 20, 0, 0));
}
public Task GetDetailsAsync(
string id,
CancellationToken cancellationToken = default)
{
DetailsCallCount++;
LastDetailsId = id;
LastCancellationToken = cancellationToken;
if (ThrowOnDetails is not null)
throw ThrowOnDetails;
return Task.FromResult(DetailsResult);
}
public Task UpdateAsync(
string id, ClientUpdateCommand command,
CancellationToken cancellationToken = default)
{
UpdateCallCount++;
LastUpdateId = id;
LastUpdateCommand = command;
LastUpdateCancellationToken = cancellationToken;
if (ThrowOnUpdate is not null)
throw ThrowOnUpdate;
return Task.FromResult(UpdateResult);
}
public Task ArchiveAsync(
string id,
CancellationToken cancellationToken = default)
{
ArchiveCallCount++;
LastArchiveId = id;
LastArchiveCancellationToken = cancellationToken;
if (ThrowOnArchive is not null)
throw ThrowOnArchive;
return Task.FromResult(ArchiveResult);
}
}
private sealed class FakeIdentityResolver : IMobileEntityIdentityResolver
{
public Guid CreateCanonicalIdentity(string entityType, PhysicalEntityIdentity physical)
=> physical.Guid ?? Guid.NewGuid();
public PhysicalEntityIdentity? ResolvePhysicalIdentity(string entityType, Guid publicGuid)
=> new() { Guid = publicGuid };
public bool IsCanonical(string entityType, Guid publicGuid)
=> publicGuid != Guid.Empty;
}
}