using System.Text.Json; using Omika.App.Local.Models; using Omika.App.Local.Services; using Omika.Core.Contracts.Clients; using Omika.Core.Contracts.Common; using Omika.Core.Contracts.Interventions; using Omika.Core.Contracts.Sites; namespace Omika.Tests.Mobile.Local; /// /// Tests du service de lecture des dossiers depuis les données locales SQLite. /// La relation Dossier → Intervention est portée par DossierId sur /// l'intervention. Aucune relation n'est déduite du Client. Lecture seule : /// aucune écriture ni Outbox n'est créée. /// public class DossierLocalServiceTests : LocalTestBase { private readonly DossierLocalService _service; public DossierLocalServiceTests() { _service = new DossierLocalService(DossierRepository, ClientRepository, DossierStateRepository, DossierInterventionCacheRepository, ApiClient); } private static string Serialize(T value) => JsonSerializer.Serialize(value); private static LocalDossier CreateDossier( Guid id, string reference, string titre, Guid? clientId = null, DateTime? dateOuverture = null, string? statut = null, Guid? siteId = null, string? type = null, bool estArchive = false) { var dto = new DossierDetailsDto { Id = id.ToString(), ReferenceMetier = reference, Titre = titre, ClientId = clientId?.ToString(), SiteId = siteId?.ToString(), DateOuverture = dateOuverture, Statut = statut is null ? null : new ReferenceValueDto { Label = statut }, Type = type is null ? null : new ReferenceValueDto { Label = type }, EstArchive = estArchive }; return new LocalDossier { Id = id, ClientId = clientId, SiteId = siteId, PayloadJson = Serialize(dto), UpdatedAtUtc = DateTimeOffset.UtcNow }; } private static LocalSite CreateSite(Guid id, string nom, string? code = null) { var dto = new SiteDetailsDto { Id = id.ToString(), Nom = nom, Code = code }; return new LocalSite { Id = id, PayloadJson = Serialize(dto), UpdatedAtUtc = DateTimeOffset.UtcNow }; } private static LocalClient CreateClient(Guid id, string nom, string? code = null) { var dto = new ClientDetailsDto { Id = id.ToString(), Nom = nom, Code = code }; return new LocalClient { Id = id, PayloadJson = Serialize(dto), UpdatedAtUtc = DateTimeOffset.UtcNow }; } private static LocalIntervention CreateIntervention( Guid id, Guid dossierId, string reference, string titre, DateTime? datePrevueDebut = null, string? etat = null) { var dto = new InterventionDetailsDto { Id = id.ToString(), Reference = reference, Titre = titre, DossierId = dossierId.ToString(), DatePrevueDebut = datePrevueDebut, Etat = etat is null ? null : new ReferenceValueDto { Label = etat } }; return new LocalIntervention { Id = id, DossierId = dossierId, PayloadJson = Serialize(dto), UpdatedAtUtc = DateTimeOffset.UtcNow }; } private static InterventionDetailsDto CreateInterventionDto( Guid id, string reference, string titre, DateTime? datePrevueDebut = null) => new InterventionDetailsDto { Id = id.ToString(), Reference = reference, Titre = titre, DatePrevueDebut = datePrevueDebut }; // ===================== LISTE ===================== [Fact] public async Task AucunDossier_RetourneListeVide() { // Act var result = await _service.GetDossiersAsync(); // Assert Assert.Empty(result); } [Fact] public async Task UnDossier_RetourneLeDossier() { // Arrange Guid dossierId = Guid.NewGuid(); await DossierRepository.UpsertAsync(CreateDossier(dossierId, "DOS-001", "Dossier A")); // Act var result = await _service.GetDossiersAsync(); // Assert var item = Assert.Single(result); Assert.Equal(dossierId, item.DossierGuid); Assert.Equal("DOS-001", item.Reference); Assert.Equal("Dossier A", item.Titre); } [Fact] public async Task PlusieursDossiers_RetourneTous() { // Arrange await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A")); await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "B")); // Act var result = await _service.GetDossiersAsync(); // Assert Assert.Equal(2, result.Count); } [Fact] public async Task Tri_DeterministeParTitrePuisReference() { // Arrange await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "Beta")); await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "Alpha")); // Act var result = await _service.GetDossiersAsync(); // Assert : tri déterministe par titre puis référence. Assert.Equal("DOS-001", result[0].Reference); Assert.Equal("DOS-002", result[1].Reference); } [Fact] public async Task RechercheLocale_Reference() { // Arrange await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "Alpha")); await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "Beta")); // Act : recherche par référence (insensible à la casse). var result = await _service.GetDossiersAsync(); // Assert : le service retourne tout ; la recherche est gérée par le ViewModel. Assert.Equal(2, result.Count); } [Fact] public async Task PayloadInvalide_EntreeIgnoree() { // Arrange await DossierRepository.UpsertAsync(new LocalDossier { Id = Guid.NewGuid(), PayloadJson = "not-json", UpdatedAtUtc = DateTimeOffset.UtcNow }); await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "Valide")); // Act var result = await _service.GetDossiersAsync(); // Assert : l'entrée invalide est ignorée proprement. var item = Assert.Single(result); Assert.Equal("DOS-001", item.Reference); } [Fact] public async Task FonctionneOffline_SansAppelApi() { // Arrange : aucune dépendance réseau, uniquement SQLite local. await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A")); // Act var result = await _service.GetDossiersAsync(); // Assert Assert.Single(result); } [Fact] public async Task IdentitePublique_Conservee() { // Arrange Guid dossierId = Guid.NewGuid(); await DossierRepository.UpsertAsync(CreateDossier(dossierId, "DOS-001", "A")); // Act var result = await _service.GetDossiersAsync(); // Assert : l'identité publique (GUID) est conservée. var item = Assert.Single(result); Assert.Equal(dossierId, item.DossierGuid); } [Fact] public async Task DossierAvecClient_ResoutLeNom() { // Arrange Guid clientId = Guid.NewGuid(); await ClientRepository.UpsertAsync(CreateClient(clientId, "Client Alpha", "C001")); await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A", clientId)); // Act var result = await _service.GetDossiersAsync(); // Assert : le nom du client est résolu localement. var item = Assert.Single(result); Assert.Equal("Client Alpha", item.ClientNom); } // ===================== DÉTAIL ===================== [Fact] public async Task Detail_DossierTrouve() { // Arrange Guid dossierId = Guid.NewGuid(); await DossierRepository.UpsertAsync(CreateDossier(dossierId, "DOS-001", "Dossier A")); // Act var result = await _service.GetDossierByIdAsync(dossierId); // Assert Assert.NotNull(result); Assert.Equal(dossierId, result.DossierGuid); Assert.Equal("DOS-001", result.Reference); Assert.Equal("Dossier A", result.Titre); } [Fact] public async Task Detail_DossierInconnu_RetourneNull() { // Act var result = await _service.GetDossierByIdAsync(Guid.NewGuid()); // Assert Assert.Null(result); } [Fact] public async Task Detail_ClientResolu() { // Arrange Guid clientId = Guid.NewGuid(); Guid dossierId = Guid.NewGuid(); await ClientRepository.UpsertAsync(CreateClient(clientId, "Client Alpha", "C001")); await DossierRepository.UpsertAsync(CreateDossier(dossierId, "DOS-001", "A", clientId)); // Act var result = await _service.GetDossierByIdAsync(dossierId); // Assert Assert.NotNull(result); Assert.Equal(clientId, result.ClientGuid); Assert.Equal("Client Alpha", result.ClientNom); Assert.Equal("C001", result.ClientCode); } [Fact] public async Task Detail_SansClient_RelationAbsente() { // Arrange Guid dossierId = Guid.NewGuid(); await DossierRepository.UpsertAsync(CreateDossier(dossierId, "DOS-001", "A")); // Act var result = await _service.GetDossierByIdAsync(dossierId); // Assert : pas de client, pas d'erreur. Assert.NotNull(result); Assert.Null(result.ClientGuid); Assert.Null(result.ClientNom); } [Fact] public async Task Detail_UneInterventionLiee() { // Arrange : le cache contextuel (pas la table historique Intervention). Guid dossierId = Guid.NewGuid(); Guid interventionId = Guid.NewGuid(); await DossierRepository.UpsertAsync(CreateDossier(dossierId, "DOS-001", "A")); await DossierInterventionCacheRepository.ReplaceAsync( dossierId, [CreateInterventionDto(interventionId, "INT-001", "Intervention")]); // Act var result = await _service.GetDossierByIdAsync(dossierId); var interventions = await _service.GetDossierInterventionsAsync(dossierId); // Assert Assert.NotNull(result); var intervention = Assert.Single(interventions); Assert.Equal(interventionId, intervention.InterventionGuid); Assert.Equal("INT-001", intervention.Reference); } [Fact] public async Task Detail_PlusieursInterventionsLiees() { // Arrange : cache contextuel uniquement. Guid dossierId = Guid.NewGuid(); await DossierRepository.UpsertAsync(CreateDossier(dossierId, "DOS-001", "A")); await DossierInterventionCacheRepository.ReplaceAsync( dossierId, [ CreateInterventionDto(Guid.NewGuid(), "INT-001", "I1"), CreateInterventionDto(Guid.NewGuid(), "INT-002", "I2") ]); // Act var interventions = await _service.GetDossierInterventionsAsync(dossierId); // Assert Assert.Equal(2, interventions.Count); } [Fact] public async Task Detail_InterventionAutreDossier_Exclue() { // Arrange Guid dossierA = Guid.NewGuid(); Guid dossierB = Guid.NewGuid(); await DossierRepository.UpsertAsync(CreateDossier(dossierA, "DOS-A", "A")); await DossierRepository.UpsertAsync(CreateDossier(dossierB, "DOS-B", "B")); await InterventionRepository.UpsertAsync(CreateIntervention(Guid.NewGuid(), dossierA, "INT-A", "IA")); // Act var resultB = await _service.GetDossierByIdAsync(dossierB); var interventionsB = await _service.GetDossierInterventionsAsync(dossierB); // Assert : B n'a aucune intervention de A. Assert.NotNull(resultB); Assert.Empty(interventionsB); } [Fact] public async Task Detail_RelationOptionnelleAbsente_SansCrash() { // Arrange : dossier sans client, sans intervention, sans description. Guid dossierId = Guid.NewGuid(); await DossierRepository.UpsertAsync(CreateDossier(dossierId, "DOS-001", "A")); // Act var result = await _service.GetDossierByIdAsync(dossierId); // Assert : dossier valide sans relations optionnelles. Assert.NotNull(result); Assert.Null(result.ClientGuid); Assert.Empty(result.Interventions); } [Fact] public async Task Detail_IdentitePublique_Conservee() { // Arrange Guid dossierId = Guid.NewGuid(); await DossierRepository.UpsertAsync(CreateDossier(dossierId, "DOS-001", "A")); // Act var result = await _service.GetDossierByIdAsync(dossierId); // Assert Assert.NotNull(result); Assert.Equal(dossierId, result.DossierGuid); } // ===================== SITE / TYPE (lookup local) ===================== [Fact] public async Task DossierAvecSite_ResoutLeNom() { // Arrange Guid siteId = Guid.NewGuid(); await SiteRepository.UpsertAsync(CreateSite(siteId, "Site Alpha", "S001")); await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A", siteId: siteId)); // Act var result = await _service.GetDossiersAsync(); // Assert : le site n'est plus exposé dans la card (règle 37B). var item = Assert.Single(result); Assert.Null(item.SiteNom); } [Fact] public async Task DossierAvecType_ResoutLeLibelle() { // Arrange await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A", type: "Maintenance")); // Act var result = await _service.GetDossiersAsync(); // Assert : le libellé du type est exposé dans la liste. var item = Assert.Single(result); Assert.Equal("Maintenance", item.Type); } [Fact] public async Task Detail_SiteResolu() { // Arrange Guid siteId = Guid.NewGuid(); Guid dossierId = Guid.NewGuid(); await SiteRepository.UpsertAsync(CreateSite(siteId, "Site Alpha", "S001")); await DossierRepository.UpsertAsync(CreateDossier(dossierId, "DOS-001", "A", siteId: siteId)); // Act var result = await _service.GetDossierByIdAsync(dossierId); // Assert : le site n'est pas réintroduit dans le détail (règle 37B). Assert.NotNull(result); Assert.Null(result.SiteGuid); Assert.Null(result.SiteNom); Assert.Null(result.SiteCode); } [Fact] public async Task Detail_SansSite_RelationAbsente() { // Arrange Guid dossierId = Guid.NewGuid(); await DossierRepository.UpsertAsync(CreateDossier(dossierId, "DOS-001", "A")); // Act var result = await _service.GetDossierByIdAsync(dossierId); // Assert : pas de site, pas d'erreur. Assert.NotNull(result); Assert.Null(result.SiteGuid); Assert.Null(result.SiteNom); } }