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