491 lines
15 KiB
C#
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);
|
|
}
|
|
}
|