Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
using Omika.Core.Contracts.Mobile.Sync;
|
||||
using Omika.Core.Interfaces.Mobile;
|
||||
using Omika.Core.Services.Mobile;
|
||||
|
||||
namespace Omika.Tests.Mobile;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de la réception et du suivi des opérations de synchronisation.
|
||||
/// </summary>
|
||||
public class SyncOperationServiceTests
|
||||
{
|
||||
private const string DeviceId = "device-1";
|
||||
|
||||
private static MobileTokenIdentity Identity() => new()
|
||||
{
|
||||
CodeClient = "BATIK.01",
|
||||
DeviceId = DeviceId,
|
||||
UserId = "user-1",
|
||||
CollaborateurId = "collab-1",
|
||||
NumeroLicence = "lic-1"
|
||||
};
|
||||
|
||||
private static SyncOperation ValidOperation(SyncOperationType type = SyncOperationType.Create)
|
||||
{
|
||||
return new SyncOperation
|
||||
{
|
||||
OperationId = Guid.NewGuid(),
|
||||
DeviceId = DeviceId,
|
||||
EntityType = "Client",
|
||||
OperationType = type,
|
||||
EntityGuid = Guid.NewGuid(),
|
||||
Payload = "{}"
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Receive_Valid_Operation_Returns_Received()
|
||||
{
|
||||
// Arrange
|
||||
var repo = new FakeSyncTransferRepository();
|
||||
var service = new SyncOperationService(repo);
|
||||
var op = ValidOperation();
|
||||
|
||||
// Act
|
||||
var receipt = await service.ReceiveAsync(op, Identity());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("RECEIVED", receipt.Status);
|
||||
Assert.Equal(op.OperationId, receipt.OperationId);
|
||||
Assert.True(repo.Inserted);
|
||||
Assert.True(repo.Logged);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Receive_Empty_OperationId_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var repo = new FakeSyncTransferRepository();
|
||||
var service = new SyncOperationService(repo);
|
||||
var op = ValidOperation() with { OperationId = Guid.Empty };
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => service.ReceiveAsync(op, Identity()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Receive_Incoherent_DeviceId_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var repo = new FakeSyncTransferRepository();
|
||||
var service = new SyncOperationService(repo);
|
||||
var op = ValidOperation();
|
||||
|
||||
// Act & Assert : DeviceId de l'opération != identité authentifiée.
|
||||
var otherIdentity = Identity() with { DeviceId = "autre-device" };
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(
|
||||
() => service.ReceiveAsync(op, otherIdentity));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Receive_Duplicate_Operation_Does_Not_Insert_Twice()
|
||||
{
|
||||
// Arrange
|
||||
var repo = new FakeSyncTransferRepository();
|
||||
var service = new SyncOperationService(repo);
|
||||
var op = ValidOperation();
|
||||
|
||||
// Premier envoi.
|
||||
await service.ReceiveAsync(op, Identity());
|
||||
|
||||
// Le repository signale que l'opération existe déjà.
|
||||
repo.ExistingState = SyncOperationState.Pending;
|
||||
|
||||
// Act : second envoi (dupliqué).
|
||||
var receipt = await service.ReceiveAsync(op, Identity());
|
||||
|
||||
// Assert : RECEIVED sans nouvelle insertion.
|
||||
Assert.Equal("RECEIVED", receipt.Status);
|
||||
Assert.Equal(1, repo.InsertCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatus_Unknown_Operation_Returns_StatusNotAvailable()
|
||||
{
|
||||
// Arrange
|
||||
var repo = new FakeSyncTransferRepository();
|
||||
var service = new SyncOperationService(repo);
|
||||
|
||||
// Act
|
||||
var status = await service.GetStatusAsync(Guid.NewGuid(), DeviceId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(SyncStatus.StatusNotAvailable, status.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatus_Pending_Operation_Returns_Pending()
|
||||
{
|
||||
// Arrange
|
||||
var repo = new FakeSyncTransferRepository
|
||||
{
|
||||
Status = new SyncStatusResponse
|
||||
{
|
||||
OperationId = Guid.NewGuid(),
|
||||
Status = SyncStatus.Pending,
|
||||
DeviceId = DeviceId
|
||||
}
|
||||
};
|
||||
var service = new SyncOperationService(repo);
|
||||
|
||||
// Act
|
||||
var status = await service.GetStatusAsync(repo.Status.OperationId, DeviceId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(SyncStatus.Pending, status.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatus_Success_Operation_Returns_Success()
|
||||
{
|
||||
// Arrange
|
||||
var repo = new FakeSyncTransferRepository
|
||||
{
|
||||
Status = new SyncStatusResponse
|
||||
{
|
||||
OperationId = Guid.NewGuid(),
|
||||
Status = SyncStatus.Success,
|
||||
DeviceId = DeviceId
|
||||
}
|
||||
};
|
||||
var service = new SyncOperationService(repo);
|
||||
|
||||
// Act
|
||||
var status = await service.GetStatusAsync(repo.Status.OperationId, DeviceId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(SyncStatus.Success, status.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatus_Rejected_Operation_Returns_Rejected()
|
||||
{
|
||||
// Arrange
|
||||
var repo = new FakeSyncTransferRepository
|
||||
{
|
||||
Status = new SyncStatusResponse
|
||||
{
|
||||
OperationId = Guid.NewGuid(),
|
||||
Status = SyncStatus.Rejected,
|
||||
DeviceId = DeviceId
|
||||
}
|
||||
};
|
||||
var service = new SyncOperationService(repo);
|
||||
|
||||
// Act
|
||||
var status = await service.GetStatusAsync(repo.Status.OperationId, DeviceId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(SyncStatus.Rejected, status.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatus_Empty_OperationId_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var repo = new FakeSyncTransferRepository();
|
||||
var service = new SyncOperationService(repo);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => service.GetStatusAsync(Guid.Empty, DeviceId));
|
||||
}
|
||||
|
||||
// ===== Fake =====
|
||||
|
||||
private sealed class FakeSyncTransferRepository : ISyncTransferRepository
|
||||
{
|
||||
public bool Inserted { get; private set; }
|
||||
public bool Logged { get; private set; }
|
||||
public int InsertCount { get; private set; }
|
||||
public SyncOperationState ExistingState { get; set; } = SyncOperationState.Unknown;
|
||||
public SyncStatusResponse? Status { get; set; }
|
||||
|
||||
public Task<bool> ExistsAsync(Guid operationId, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(ExistingState != SyncOperationState.Unknown);
|
||||
|
||||
public Task<SyncOperationState> GetExistingStateAsync(Guid operationId, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(ExistingState);
|
||||
|
||||
public Task InsertToSrvAsync(SyncEnvelope envelope, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Inserted = true;
|
||||
InsertCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<SyncStatusResponse?> GetStatusAsync(Guid operationId, string deviceId, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(Status);
|
||||
|
||||
public Task<SyncLogEntry?> GetTerminalResultAsync(Guid operationId, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<SyncLogEntry?>(null);
|
||||
|
||||
public Task WriteLogAsync(SyncLogEntry entry, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Logged = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
public Task<int> PurgeLogsAsync(DateTime cutoffUtc, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(0);
|
||||
|
||||
public Task<int> TransferToReceptionAsync(CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(0);
|
||||
|
||||
public Task<IReadOnlyList<SyncEnvelope>> GetPendingOperationsAsync(CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<IReadOnlyList<SyncEnvelope>>(Array.Empty<SyncEnvelope>());
|
||||
|
||||
public Task MarkSuccessAsync(SyncLogEntry entry, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task MarkRejectedAsync(SyncLogEntry entry, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task IncrementRetryAsync(Guid operationId, int newRetryCount, DateTime nextAttemptUtc, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user