168 lines
6.8 KiB
C#
168 lines
6.8 KiB
C#
using System.Net.Http;
|
|
using System.Text.Json;
|
|
using Omika.App.Local.Abstractions;
|
|
using Omika.App.Local.Models;
|
|
using Omika.App.Local.Repositories;
|
|
using Omika.App.Local.Services;
|
|
using Omika.Core.Contracts.Interventions;
|
|
|
|
namespace Omika.Tests.Mobile.Local;
|
|
|
|
/// <summary>
|
|
/// Tests du service de périmètre du compte rendu (anti-doublon du bouton Créer).
|
|
///
|
|
/// Ce service ne fait que dériver « existe / n'existe pas » de la lecture du compte
|
|
/// rendu : c'est donc lui qui, le 09/10 en production, a propagé l'échec réseau
|
|
/// « Connection failure » jusqu'au ViewModel, faute de garde dans la couche locale.
|
|
/// Les tests vérifient qu'un échec réseau ne lève plus jamais et que la copie locale
|
|
/// reste la source de vérité hors ligne.
|
|
///
|
|
/// Limite assumée (documentée) : hors ligne et sans copie locale, « inconnu » est
|
|
/// traité comme « absent » — le compte rendu sera créé localement puis synchronisé.
|
|
/// </summary>
|
|
public class CompteRenduScopeServiceTests : LocalTestBase
|
|
{
|
|
private readonly CompteRenduScopeService _scopeService;
|
|
|
|
public CompteRenduScopeServiceTests()
|
|
=> _scopeService = new CompteRenduScopeService(
|
|
new InterventionLocalService(
|
|
InterventionRepository, ClientRepository, SiteRepository, DossierRepository,
|
|
RendezVousRepository, DocumentMetadataRepository, CollaborateurRepository,
|
|
OutboxService, ReconciliationService, ApiClient, InterventionCacheRepository,
|
|
ContextInterventionCacheRepository, Database, Network));
|
|
|
|
/// <summary>
|
|
/// Client API dont la lecture de compte rendu est pilotable par le test.
|
|
///
|
|
/// L'interface est redéclarée volontairement : <c>GetCompteRenduByRendezVousAsync</c>
|
|
/// est une implémentation par défaut de <see cref="IMobileApiClient"/>, et une classe
|
|
/// dérivée ne peut la remplacer qu'en réimplémentant explicitement l'interface.
|
|
/// </summary>
|
|
private sealed class CompteRenduApiClient : StubMobileApiClient, IMobileApiClient
|
|
{
|
|
public int CompteRenduCalls { get; private set; }
|
|
|
|
public Exception? ExceptionToThrow { get; set; }
|
|
|
|
public CompteRenduDto? Result { get; set; }
|
|
|
|
public Task<CompteRenduDto?> GetCompteRenduByRendezVousAsync(
|
|
Guid rendezVousGuid,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
CompteRenduCalls++;
|
|
return ExceptionToThrow is { } ex
|
|
? Task.FromException<CompteRenduDto?>(ex)
|
|
: Task.FromResult(Result);
|
|
}
|
|
}
|
|
|
|
private static AgendaItem CreateRendezVous(Guid rendezVousGuid) => new()
|
|
{
|
|
RendezVousGuid = rendezVousGuid,
|
|
Titre = "RDV",
|
|
Debut = DateTime.Now.AddDays(-1).AddHours(10),
|
|
Fin = DateTime.Now.AddDays(-1).AddHours(12)
|
|
};
|
|
|
|
private CompteRenduScopeService CreateScopeService(IMobileApiClient apiClient, bool online)
|
|
{
|
|
Network.Online = online;
|
|
return new CompteRenduScopeService(
|
|
new InterventionLocalService(
|
|
InterventionRepository, ClientRepository, SiteRepository, DossierRepository,
|
|
RendezVousRepository, DocumentMetadataRepository, CollaborateurRepository,
|
|
OutboxService, ReconciliationService, apiClient, InterventionCacheRepository,
|
|
ContextInterventionCacheRepository, Database, Network));
|
|
}
|
|
|
|
private async Task SeedCompteRenduLocalAsync(Guid rendezVousGuid, string note)
|
|
{
|
|
var repository = new CompteRenduLocalRepository(Database);
|
|
await repository.UpsertAsync(new LocalCompteRendu
|
|
{
|
|
RendezVousId = rendezVousGuid,
|
|
PayloadJson = JsonSerializer.Serialize(new CompteRenduDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
RendezVousId = rendezVousGuid.ToString("D"),
|
|
NoteCollaborateur = note
|
|
}),
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ticket de production du 09/10 : le serveur est injoignable au moment du contrôle
|
|
/// anti-doublon. La vérification doit répondre « pas de compte rendu connu » au lieu
|
|
/// de lever — c'est ce qui permet la création hors ligne au lieu d'une erreur P1.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CompteRenduExistsAsync_EchecReseauSansCopieLocale_RenvoieFauxSansLever()
|
|
{
|
|
var client = new CompteRenduApiClient
|
|
{
|
|
ExceptionToThrow = new HttpRequestException("Connection failure")
|
|
};
|
|
CompteRenduScopeService scope = CreateScopeService(client, online: true);
|
|
|
|
bool existe = await scope.CompteRenduExistsAsync(CreateRendezVous(Guid.NewGuid()));
|
|
|
|
Assert.False(existe);
|
|
Assert.Equal(1, client.CompteRenduCalls);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hors ligne, la copie locale suffit à préserver l'anti-doublon : un compte rendu
|
|
/// déjà connu localement n'est pas recréé, et aucun appel réseau n'est tenté.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CompteRenduExistsAsync_CopieLocalePresente_HorsLigne_RenvoieVrai()
|
|
{
|
|
Guid rendezVousGuid = Guid.NewGuid();
|
|
await SeedCompteRenduLocalAsync(rendezVousGuid, "Note locale");
|
|
var client = new CompteRenduApiClient();
|
|
CompteRenduScopeService scope = CreateScopeService(client, online: false);
|
|
|
|
bool existe = await scope.CompteRenduExistsAsync(CreateRendezVous(rendezVousGuid));
|
|
|
|
Assert.True(existe);
|
|
Assert.Equal(0, client.CompteRenduCalls);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contre-épreuve : la création est refusée quand le compte rendu existe déjà dans
|
|
/// le périmètre local (le doublon reste impossible hors ligne, sans réseau).
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CanCreateCompteRenduAsync_CopieLocalePresente_HorsLigne_RefuseLaCreation()
|
|
{
|
|
Guid rendezVousGuid = Guid.NewGuid();
|
|
await SeedCompteRenduLocalAsync(rendezVousGuid, "Note locale");
|
|
CompteRenduScopeService scope = CreateScopeService(new CompteRenduApiClient(), online: false);
|
|
|
|
bool peutCreer = await scope.CanCreateCompteRenduAsync(CreateRendezVous(rendezVousGuid));
|
|
|
|
Assert.False(peutCreer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Échec réseau sans copie locale : aucune validation ne peut être affirmée —
|
|
/// « non validé » est retourné sans exception (jamais de blocage de l'écran).
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CompteRenduIsValidatedAsync_EchecReseauSansCopieLocale_RenvoieFaux()
|
|
{
|
|
var client = new CompteRenduApiClient
|
|
{
|
|
ExceptionToThrow = new HttpRequestException("Connection failure")
|
|
};
|
|
CompteRenduScopeService scope = CreateScopeService(client, online: true);
|
|
|
|
bool valide = await scope.CompteRenduIsValidatedAsync(CreateRendezVous(Guid.NewGuid()));
|
|
|
|
Assert.False(valide);
|
|
}
|
|
}
|