using System.Text.Json; using Omika.App.Local.Abstractions; using Omika.App.Local.Infrastructure; using Omika.App.Local.Models; using Omika.App.Local.Services; using Omika.Core.Contracts.Clients; using Omika.Core.Contracts.Interventions; using Omika.Core.Contracts.Mobile.Bootstrap; using Omika.Core.Contracts.Mobile.Documents; using Omika.Core.Contracts.Mobile.Interventions; using Omika.Core.Contracts.Mobile.License; using Omika.Core.Contracts.Mobile.Snapshot; using Omika.Core.Contracts.Mobile.Sync; using Omika.Core.Contracts.Organisation; using Omika.Core.Contracts.Sites; namespace Omika.Tests.Mobile.Local; public sealed class InterventionContextualRelationsTests : LocalTestBase { [Fact] public async Task ClientDetail_IgnoreLaTableInterventionHistorique() { Guid clientId = Guid.NewGuid(); Guid interventionId = Guid.NewGuid(); await ClientRepository.UpsertAsync(Client(clientId, "Client A")); await InterventionRepository.UpsertAsync(LocalIntervention(interventionId, clientId: clientId)); var service = new ClientLocalService(ClientRepository, DossierRepository, ContextInterventionCacheRepository, ClientStateRepository, ApiClient); ClientDetail? result = await service.GetClientByIdAsync(clientId); Assert.NotNull(result); Assert.False(result.InterventionsCacheAvailable); Assert.Empty(result.Interventions); } [Fact] public async Task ClientRefreshOnline_RechercheParClientId_EtNeTouchePasInterventionCache() { Guid clientId = Guid.NewGuid(); Guid interventionId = Guid.NewGuid(); await ClientRepository.UpsertAsync(Client(clientId, "Client A")); var api = new RecordingApiClient([Result(Intervention(interventionId, clientId: clientId))]); var service = new ClientLocalService(ClientRepository, DossierRepository, ContextInterventionCacheRepository, ClientStateRepository, api); ClientDetail? result = await service.RefreshClientInterventionsAsync(clientId); Assert.NotNull(result); Assert.Equal(clientId.ToString(), api.LastSearchRequest?.ClientId); Assert.Null(api.LastSearchRequest?.SiteId); Assert.Null(api.LastSearchRequest?.CollaborateurId); var intervention = Assert.Single(result.Interventions); Assert.Equal(interventionId, intervention.InterventionGuid); Assert.Null(await InterventionCacheRepository.GetByIdAsync(interventionId)); } [Fact] public async Task ClientOffline_AvecCache_AfficheLeCache() { Guid clientId = Guid.NewGuid(); Guid interventionId = Guid.NewGuid(); await ClientRepository.UpsertAsync(Client(clientId, "Client A")); await ContextInterventionCacheRepository.ReplaceAsync( InterventionContextTypes.Client, clientId, [Intervention(interventionId, clientId: clientId)]); var service = new ClientLocalService(ClientRepository, DossierRepository, ContextInterventionCacheRepository, ClientStateRepository, ApiClient); ClientDetail? result = await service.GetClientByIdAsync(clientId); Assert.NotNull(result); Assert.True(result.InterventionsCacheAvailable); Assert.Equal(interventionId, Assert.Single(result.Interventions).InterventionGuid); } [Fact] public async Task ClientOffline_SansCache_NeDeclarePasZeroCommeCertitude() { Guid clientId = Guid.NewGuid(); await ClientRepository.UpsertAsync(Client(clientId, "Client A")); var service = new ClientLocalService(ClientRepository, DossierRepository, ContextInterventionCacheRepository, ClientStateRepository, ApiClient); ClientDetail? result = await service.GetClientByIdAsync(clientId); Assert.NotNull(result); Assert.False(result.InterventionsCacheAvailable); Assert.Empty(result.Interventions); } [Fact] public async Task SiteRefreshOnline_RechercheParSiteId() { Guid siteId = Guid.NewGuid(); Guid interventionId = Guid.NewGuid(); await SiteRepository.UpsertAsync(Site(siteId, "Site A")); var api = new RecordingApiClient([Result(Intervention(interventionId, siteId: siteId))]); var service = new SiteLocalService(SiteRepository, ClientRepository, PersonneRepository, RendezVousRepository, InterventionRepository, ContextInterventionCacheRepository, SiteStateRepository, api); SiteDetail? result = await service.RefreshSiteInterventionsAsync(siteId); Assert.NotNull(result); Assert.Equal(siteId.ToString(), api.LastSearchRequest?.SiteId); Assert.Null(api.LastSearchRequest?.ClientId); Assert.Null(api.LastSearchRequest?.CollaborateurId); Assert.Equal(interventionId, Assert.Single(result.Interventions).InterventionGuid); } [Fact] public async Task CollaborateurRefreshOnline_RechercheParCollaborateurId() { Guid collaborateurId = Guid.NewGuid(); Guid interventionId = Guid.NewGuid(); await CollaborateurRepository.UpsertAsync(Collaborateur(collaborateurId)); var api = new RecordingApiClient([Result(Intervention(interventionId, collaborateurId: collaborateurId))]); var service = new CollaborateurLocalService(CollaborateurRepository, RendezVousRepository, ContextInterventionCacheRepository, api, SessionService); CollaborateurDetail? result = await service.RefreshCollaborateurInterventionsAsync(collaborateurId); Assert.NotNull(result); Assert.Equal(collaborateurId.ToString(), api.LastSearchRequest?.CollaborateurId); Assert.Null(api.LastSearchRequest?.ClientId); Assert.Null(api.LastSearchRequest?.SiteId); Assert.Equal(interventionId, Assert.Single(result.Interventions).InterventionGuid); } [Fact] public async Task RechercheContextuelle_NeModifiePasDerniereRechercheNiFavoris() { Guid clientId = Guid.NewGuid(); Guid interventionId = Guid.NewGuid(); await ClientRepository.UpsertAsync(Client(clientId, "Client A")); await InterventionCacheRepository.UpsertAsync(new CachedIntervention { Id = interventionId, PayloadJson = JsonSerializer.Serialize(Intervention(interventionId, clientId: clientId)), IsInLastSearch = false, IsPinned = true }); var api = new RecordingApiClient([Result(Intervention(interventionId, clientId: clientId))]); var service = new ClientLocalService(ClientRepository, DossierRepository, ContextInterventionCacheRepository, ClientStateRepository, api); await service.RefreshClientInterventionsAsync(clientId); CachedIntervention? cached = await InterventionCacheRepository.GetByIdAsync(interventionId); Assert.NotNull(cached); Assert.False(cached.IsInLastSearch); Assert.True(cached.IsPinned); } [Fact] public async Task ListesPrincipales_NeDeclenchentAucuneRechercheIntervention() { var api = new RecordingApiClient([]); await ClientRepository.UpsertAsync(Client(Guid.NewGuid(), "Client A")); await SiteRepository.UpsertAsync(Site(Guid.NewGuid(), "Site A")); await CollaborateurRepository.UpsertAsync(Collaborateur(Guid.NewGuid())); var clientService = new ClientLocalService(ClientRepository, DossierRepository, ContextInterventionCacheRepository, ClientStateRepository, api); var siteService = new SiteLocalService(SiteRepository, ClientRepository, PersonneRepository, RendezVousRepository, InterventionRepository, ContextInterventionCacheRepository, SiteStateRepository, api); var collaborateurService = new CollaborateurLocalService(CollaborateurRepository, RendezVousRepository, ContextInterventionCacheRepository, api, SessionService); await clientService.GetClientsAsync(); await siteService.GetSitesAsync(); await collaborateurService.GetCollaborateursAsync(); Assert.Equal(0, api.SearchCalls); } [Fact] public async Task InterventionContextuelle_OuvreLeDetailDepuisPayloadJson() { Guid clientId = Guid.NewGuid(); Guid interventionId = Guid.NewGuid(); await ClientRepository.UpsertAsync(Client(clientId, "Client A")); await ContextInterventionCacheRepository.ReplaceAsync( InterventionContextTypes.Client, clientId, [Intervention(interventionId, clientId: clientId, reference: "INT-CTX")]); var service = new InterventionLocalService( InterventionRepository, ClientRepository, SiteRepository, DossierRepository, RendezVousRepository, DocumentMetadataRepository, CollaborateurRepository, OutboxService, ReconciliationService, ApiClient, InterventionCacheRepository, ContextInterventionCacheRepository, Database); InterventionDetail? detail = await service.GetInterventionByIdAsync(interventionId); Assert.NotNull(detail); Assert.Equal("INT-CTX", detail.Reference); Assert.Equal(clientId, detail.ClientGuid); } private static LocalClient Client(Guid id, string nom) => new() { Id = id, PayloadJson = JsonSerializer.Serialize(new ClientDetailsDto { Id = id.ToString(), Nom = nom }), UpdatedAtUtc = DateTimeOffset.UtcNow }; private static LocalSite Site(Guid id, string nom) => new() { Id = id, PayloadJson = JsonSerializer.Serialize(new SiteDetailsDto { Id = id.ToString(), Nom = nom }), UpdatedAtUtc = DateTimeOffset.UtcNow }; private static LocalCollaborateur Collaborateur(Guid id) => new() { Id = id, PayloadJson = JsonSerializer.Serialize(new CollaborateurDetailsDto { Id = id.ToString(), Nom = "Dupont", Prenom = "Jean" }), UpdatedAtUtc = DateTimeOffset.UtcNow }; private static InterventionDetailsDto Intervention( Guid id, Guid? clientId = null, Guid? siteId = null, Guid? collaborateurId = null, string reference = "INT-001") => new() { Id = id.ToString(), Reference = reference, Titre = "Intervention", ClientId = clientId?.ToString(), SiteId = siteId?.ToString(), CollaborateurIds = collaborateurId is null ? [] : [collaborateurId.Value.ToString()], EstArchive = false }; private static LocalIntervention LocalIntervention(Guid id, Guid? clientId = null) => new() { Id = id, ClientId = clientId, PayloadJson = JsonSerializer.Serialize(Intervention(id, clientId: clientId)), UpdatedAtUtc = DateTimeOffset.UtcNow }; private static MobileInterventionSearchResult Result(InterventionDetailsDto detail) => new() { Detail = detail }; private sealed class RecordingApiClient : IMobileApiClient { private readonly IReadOnlyList _results; public RecordingApiClient(IReadOnlyList results) { _results = results; } public string? AccessToken { get; set; } public int SearchCalls { get; private set; } public MobileInterventionSearchRequest? LastSearchRequest { get; private set; } public Task> SearchInterventionsAsync( MobileInterventionSearchRequest request, CancellationToken cancellationToken = default) { SearchCalls++; LastSearchRequest = request; return Task.FromResult(_results); } public Task ActivateAsync(LicenseActivateRequest request, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task ValidateAsync(LicenseValidateRequest request, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task GetDevelopmentSessionAsync(string deviceId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task GetBootstrapAsync(CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task SendOperationAsync(SyncOperation operation, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task GetStatusAsync(Guid operationId, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task GetSnapshotAsync(CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task> GetInterventionsByIdsAsync(MobileInterventionsByIdsRequest request, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task DownloadDocumentAsync(Guid documentGuid, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task UploadDocumentAsync(DocumentUploadRequest request, CancellationToken cancellationToken = default) => throw new NotSupportedException(); } }