Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
using Omika.App.Local.Models;
|
||||
using Omika.App.Local.Services;
|
||||
using Omika.App.Local.Services.PayloadRewriters;
|
||||
using Omika.Core.Contracts.Mobile.Sync;
|
||||
|
||||
namespace Omika.Tests.Mobile.Local;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de la réconciliation des identités (A → B).
|
||||
/// Cas CREATE Batik : le mobile crée localement A, le serveur répond B.
|
||||
/// Vérifie : entité principale, relations enfants, Outbox, mapping, idempotence.
|
||||
/// </summary>
|
||||
public class IdentityReconciliationTests : LocalTestBase
|
||||
{
|
||||
private const string InterventionType = "Intervention";
|
||||
private readonly MobileIdentityReconciliationService _reconciliation;
|
||||
|
||||
public IdentityReconciliationTests()
|
||||
{
|
||||
_reconciliation = new MobileIdentityReconciliationService(
|
||||
Database,
|
||||
IdentityMappingRepository,
|
||||
OutboxRepository,
|
||||
[new InterventionPayloadRewriter(), new RendezVousPayloadRewriter()]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBatik_EntitePrincipale_DevientB()
|
||||
{
|
||||
// Arrange
|
||||
Guid a = Guid.NewGuid();
|
||||
Guid b = Guid.NewGuid();
|
||||
var intervention = new LocalIntervention
|
||||
{
|
||||
Id = a,
|
||||
PayloadJson = $$"""{"Id":"{{a}}","ClientId":"{{Guid.NewGuid()}}","SiteId":"{{Guid.NewGuid()}}"}"""
|
||||
};
|
||||
await InterventionRepository.UpsertAsync(intervention);
|
||||
|
||||
// Act
|
||||
await _reconciliation.ReconcileAsync(InterventionType, a, b);
|
||||
|
||||
// Assert : l'entité principale A devient B.
|
||||
LocalIntervention? loaded = await InterventionRepository.GetByIdAsync(b);
|
||||
Assert.NotNull(loaded);
|
||||
Assert.Equal(b, loaded.Id);
|
||||
Assert.Null(await InterventionRepository.GetByIdAsync(a));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBatik_RelationEnfant_DevientB()
|
||||
{
|
||||
// Arrange
|
||||
Guid a = Guid.NewGuid();
|
||||
Guid b = Guid.NewGuid();
|
||||
Guid rdvId = Guid.NewGuid();
|
||||
|
||||
var intervention = new LocalIntervention { Id = a, PayloadJson = $$"""{"Id":"{{a}}"}""" };
|
||||
await InterventionRepository.UpsertAsync(intervention);
|
||||
|
||||
var rdv = new LocalRendezVous
|
||||
{
|
||||
Id = rdvId,
|
||||
InterventionId = a,
|
||||
PayloadJson = $$"""{"Id":"{{rdvId}}","InterventionId":"{{a}}"}"""
|
||||
};
|
||||
await RendezVousRepository.UpsertAsync(rdv);
|
||||
|
||||
// Act
|
||||
await _reconciliation.ReconcileAsync(InterventionType, a, b);
|
||||
|
||||
// Assert : la relation enfant RendezVous.InterventionGuid devient B.
|
||||
LocalRendezVous? loadedRdv = await RendezVousRepository.GetByIdAsync(rdvId);
|
||||
Assert.NotNull(loadedRdv);
|
||||
Assert.Equal(b, loadedRdv.InterventionId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBatik_OutboxPending_UtiliseB()
|
||||
{
|
||||
// Arrange
|
||||
Guid a = Guid.NewGuid();
|
||||
Guid b = Guid.NewGuid();
|
||||
|
||||
var op = new OutboxOperation
|
||||
{
|
||||
OperationId = Guid.NewGuid(),
|
||||
EntityType = InterventionType,
|
||||
OperationType = SyncOperationType.Update,
|
||||
RequestedEntityGuid = a,
|
||||
Payload = $$"""{"Id":"{{a}}","ClientId":"{{Guid.NewGuid()}}"}""",
|
||||
State = OutboxState.Pending
|
||||
};
|
||||
await OutboxRepository.AddAsync(op);
|
||||
|
||||
// Act
|
||||
await _reconciliation.ReconcileAsync(InterventionType, a, b);
|
||||
|
||||
// Assert : l'Outbox pending utilise B.
|
||||
OutboxOperation? loaded = await OutboxRepository.GetByIdAsync(op.OperationId);
|
||||
Assert.NotNull(loaded);
|
||||
Assert.Equal(b, loaded.RequestedEntityGuid);
|
||||
Assert.Equal(b, loaded.CanonicalEntityGuid);
|
||||
Assert.Contains(b.ToString(), loaded.Payload);
|
||||
Assert.DoesNotContain(a.ToString(), loaded.Payload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBatik_MappingPersiste()
|
||||
{
|
||||
// Arrange
|
||||
Guid a = Guid.NewGuid();
|
||||
Guid b = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
await _reconciliation.ReconcileAsync(InterventionType, a, b);
|
||||
|
||||
// Assert : le mapping A → B persiste.
|
||||
IdentityMapping? mapping = await IdentityMappingRepository.GetAsync(InterventionType, a);
|
||||
Assert.NotNull(mapping);
|
||||
Assert.Equal(b, mapping.CanonicalEntityGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBatik_NouvelleResolution_RetourneB()
|
||||
{
|
||||
// Arrange
|
||||
Guid a = Guid.NewGuid();
|
||||
Guid b = Guid.NewGuid();
|
||||
await _reconciliation.ReconcileAsync(InterventionType, a, b);
|
||||
|
||||
// Act
|
||||
Guid resolved = await _reconciliation.ResolveAsync(InterventionType, a);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(b, resolved);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateBatik_SecondTraitement_Idempotent()
|
||||
{
|
||||
// Arrange
|
||||
Guid a = Guid.NewGuid();
|
||||
Guid b = Guid.NewGuid();
|
||||
var intervention = new LocalIntervention { Id = a, PayloadJson = $$"""{"Id":"{{a}}"}""" };
|
||||
await InterventionRepository.UpsertAsync(intervention);
|
||||
|
||||
// Act : premier traitement.
|
||||
await _reconciliation.ReconcileAsync(InterventionType, a, b);
|
||||
|
||||
// Act : second traitement de la même réconciliation.
|
||||
await _reconciliation.ReconcileAsync(InterventionType, a, b);
|
||||
|
||||
// Assert : toujours B, pas d'effet dangereux.
|
||||
LocalIntervention? loaded = await InterventionRepository.GetByIdAsync(b);
|
||||
Assert.NotNull(loaded);
|
||||
Assert.Equal(b, loaded.Id);
|
||||
Assert.Equal(1, await InterventionRepository.CountAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Omika_AB_Identiques_AucunEffet()
|
||||
{
|
||||
// Arrange
|
||||
Guid a = Guid.NewGuid();
|
||||
var intervention = new LocalIntervention { Id = a, PayloadJson = $$"""{"Id":"{{a}}"}""" };
|
||||
await InterventionRepository.UpsertAsync(intervention);
|
||||
|
||||
// Act : A == B.
|
||||
await _reconciliation.ReconcileAsync(InterventionType, a, a);
|
||||
|
||||
// Assert : aucun effet dangereux.
|
||||
LocalIntervention? loaded = await InterventionRepository.GetByIdAsync(a);
|
||||
Assert.NotNull(loaded);
|
||||
Assert.Equal(a, loaded.Id);
|
||||
Assert.Equal(1, await InterventionRepository.CountAsync());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user