Files
Omika/tests/Omika.Tests/Mobile/Local/DossierLocalServiceTests.cs
2026-10-23 18:46:50 +02:00

491 lines
15 KiB
C#

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;
/// <summary>
/// Tests du service de lecture des dossiers depuis les données locales SQLite.
/// La relation Dossier → Intervention est portée par <c>DossierId</c> sur
/// l'intervention. Aucune relation n'est déduite du Client. Lecture seule :
/// aucune écriture ni Outbox n'est créée.
/// </summary>
public class DossierLocalServiceTests : LocalTestBase
{
private readonly DossierLocalService _service;
public DossierLocalServiceTests()
{
_service = new DossierLocalService(DossierRepository, ClientRepository, DossierStateRepository, DossierInterventionCacheRepository, ApiClient);
}
private static string Serialize<T>(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);
}
}