using System; using System.Collections.Generic; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Omika.App.Local.Abstractions; using Omika.App.Local.Models; using Omika.App.Local.Services; using Omika.Core.Contracts.Mobile.Sync; using Omika.Core.Contracts.Planning; namespace Omika.Tests.Mobile.Local; /// /// Tests du service local de finalisation d'un rendez-vous : mise à jour SQLite du /// statut « Fait » (jamais de suppression) + publication du changement d'état. /// public class RendezVousFinalisationLocalServiceTests { private sealed class FakeRendezVousRepository : ILocalRepository { public string TableName => "RendezVous"; private readonly Dictionary _items = []; public void Seed(LocalRendezVous entity) => _items[entity.Id] = entity; public Task UpsertAsync(LocalRendezVous entity, CancellationToken cancellationToken = default) { _items[entity.Id] = entity; return Task.CompletedTask; } public Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) => Task.FromResult(_items.TryGetValue(id, out LocalRendezVous? entity) ? entity : null); public Task> GetAllAsync(CancellationToken cancellationToken = default) => Task.FromResult>([.. _items.Values]); public Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) { _items.Remove(id); return Task.CompletedTask; } public Task ClearAsync(CancellationToken cancellationToken = default) { _items.Clear(); return Task.CompletedTask; } public Task CountAsync(CancellationToken cancellationToken = default) => Task.FromResult(_items.Count); } private static RendezVousDetailsDto DtoConfirmé(Guid guid) => new() { Id = guid.ToString("D"), Titre = "RDV Client", Statut = new Omika.Core.Contracts.Common.ReferenceValueDto { Key = "S.RDV.CONF", Label = "Confirmé" }, Debut = DateTime.Now.AddDays(-1), Fin = DateTime.Now.AddDays(-1).AddHours(2) }; [Fact] public async Task MarquerFaitAsync_AppliqueStatutFaitLocalementEtPublie() { // Arrange : un rendez-vous local au statut « Confirmé ». Guid rendezVousGuid = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"); var repository = new FakeRendezVousRepository(); repository.Seed(new LocalRendezVous { Id = rendezVousGuid, PayloadJson = JsonSerializer.Serialize(DtoConfirmé(rendezVousGuid)) }); var notifier = new RendezVousStateNotifier(); RendezVousStateChange? published = null; notifier.StateChanged += (_, change) => published = change; var service = new RendezVousFinalisationLocalService(repository, notifier); // Act await service.MarquerFaitAsync(rendezVousGuid); // Assert : la copie locale porte le statut « Fait » (GUID conservé). LocalRendezVous? updated = await repository.GetByIdAsync(rendezVousGuid); Assert.NotNull(updated); RendezVousDetailsDto? dto = JsonSerializer.Deserialize(updated!.PayloadJson); Assert.Equal(RendezVousStatut.FaitKey, dto!.Statut?.Key); Assert.Equal(RendezVousStatut.FaitLabel, dto.Statut?.Label); Assert.Null(dto.Annulation); // Assert : le changement d'état a été publié (propagation aux vues). Assert.NotNull(published); Assert.Equal(rendezVousGuid, published!.RendezVousGuid); Assert.False(published.EstAnnule); Assert.True(published.EstFait); Assert.Equal(RendezVousStatut.FaitKey, published.StatutKey); Assert.Equal(RendezVousStatut.FaitLabel, published.StatutLabel); } [Fact] public async Task MarquerFaitAsync_RendezVousLocalIntrouvable_PublieToutDeMeme() { // Arrange : aucun rendez-vous dans le snapshot local (ex. agenda d'équipe). Guid rendezVousGuid = Guid.NewGuid(); var notifier = new RendezVousStateNotifier(); RendezVousStateChange? published = null; notifier.StateChanged += (_, change) => published = change; var service = new RendezVousFinalisationLocalService(new FakeRendezVousRepository(), notifier); // Act : ne doit pas lever d'exception. await service.MarquerFaitAsync(rendezVousGuid); // Assert : l'événement est publié malgré l'absence locale (UI immédiate). Assert.NotNull(published); Assert.Equal(rendezVousGuid, published!.RendezVousGuid); Assert.True(published.EstFait); } }