Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user