Files
Omika/tests/Omika.Tests/Repositories/BatikRendezVousQuarantineTests.cs
T
2026-10-23 18:46:50 +02:00

188 lines
7.3 KiB
C#

using Omika.Core.Contracts.Common;
using Omika.Core.Contracts.Planning;
using Omika.Core.Domain.Commands;
using Omika.Core.Domain.Entities.Planning;
using Omika.Data.Configuration;
using Omika.Data.Connection;
using Omika.Data.HfSql.Models.Batik;
using Omika.Data.Repositories.Batik;
namespace Omika.Tests.Repositories;
/// <summary>
/// Tests ciblés de la quarantaine read-only des rendez-vous Batik dont la
/// plage de dates est physiquement invalide (PL_DateFin < PL_DateDebut).
///
/// Politique : une telle ligne n'est PAS transformée en objet domaine
/// (DateTimeRange resterait strict). Elle est détectée, comptée, signalée et
/// exclue du résultat métier, sans modifier les données HFSQL.
/// </summary>
public class BatikRendezVousQuarantineTests
{
// =========================================================================
// TEST 1 — ligne normale
// =========================================================================
[Fact]
public async Task SearchAsync_ValidRow_IsMappedAndReturned()
{
var connection = new QuarantineHfsqlConnection();
connection.RendezVousRows =
[
RendezVousDict(1, "Réunion", new DateTime(2026, 1, 15, 9, 0, 0), new DateTime(2026, 1, 15, 10, 0, 0))
];
var repo = new BatikRendezVousRepository(connection);
PagedResult<RendezVous> result = await repo.SearchAsync(new RendezVousSearchCriteria());
Assert.NotNull(result);
Assert.Single(result.Items);
Assert.Equal(1, result.TotalCount);
Assert.Equal("Réunion", result.Items[0].Titre);
Assert.Equal(new DateTime(2026, 1, 15, 9, 0, 0), result.Items[0].Periode.Start);
Assert.Equal(new DateTime(2026, 1, 15, 10, 0, 0), result.Items[0].Periode.End);
}
// =========================================================================
// TEST 2 — ligne invalide
// =========================================================================
[Fact]
public async Task SearchAsync_InvalidRow_IsExcludedWithoutException()
{
var connection = new QuarantineHfsqlConnection();
connection.RendezVousRows =
[
RendezVousDict(281474976710664, "Anomalie", new DateTime(2022, 10, 15, 7, 30, 0), new DateTime(2022, 10, 14, 11, 30, 0))
];
var repo = new BatikRendezVousRepository(connection);
// Aucune DomainValidationException ne doit être levée.
PagedResult<RendezVous> result = await repo.SearchAsync(new RendezVousSearchCriteria());
Assert.NotNull(result);
Assert.Empty(result.Items);
Assert.Equal(0, result.TotalCount);
}
// =========================================================================
// TEST 3 — mélange : 3 valides + 2 invalides
// =========================================================================
[Fact]
public async Task SearchAsync_MixedRows_ReturnsOnlyValidOnes()
{
var connection = new QuarantineHfsqlConnection();
connection.RendezVousRows =
[
RendezVousDict(1, "Valide 1", new DateTime(2026, 1, 15, 9, 0, 0), new DateTime(2026, 1, 15, 10, 0, 0)),
RendezVousDict(2, "Invalide 1", new DateTime(2022, 10, 15, 7, 30, 0), new DateTime(2022, 10, 14, 11, 30, 0)),
RendezVousDict(3, "Valide 2", new DateTime(2026, 1, 16, 9, 0, 0), new DateTime(2026, 1, 16, 10, 0, 0)),
RendezVousDict(4, "Invalide 2", new DateTime(2022, 11, 15, 7, 30, 0), new DateTime(2022, 11, 14, 11, 30, 0)),
RendezVousDict(5, "Valide 3", new DateTime(2026, 1, 17, 9, 0, 0), new DateTime(2026, 1, 17, 10, 0, 0))
];
var repo = new BatikRendezVousRepository(connection);
PagedResult<RendezVous> result = await repo.SearchAsync(new RendezVousSearchCriteria());
Assert.NotNull(result);
Assert.Equal(3, result.Items.Count);
Assert.Equal(3, result.TotalCount);
Assert.DoesNotContain(result.Items, r => r.Titre.StartsWith("Invalide", StringComparison.Ordinal));
}
// =========================================================================
// HELPERS
// =========================================================================
private static Dictionary<string, object?> RendezVousDict(
long pkid, string? objet, DateTime debut, DateTime fin)
=> new()
{
["PL_PKID"] = pkid,
["PL_Objet"] = objet,
["PL_DateDebut"] = debut,
["PL_DateFin"] = fin,
["PL_IDTypeActivite"] = 0L,
["PL_IDStatut"] = 0L,
["PL_IDCollaborateur"] = 0L,
["PL_IDOrigineAnnul"] = 0UL,
["PL_IDMotifAnnul"] = 0UL,
["PL_Prive"] = false,
["PL_Superposable"] = false,
["Archive"] = false
};
// =========================================================================
// QuarantineHfsqlConnection
// =========================================================================
private sealed class QuarantineHfsqlConnection : IHfsqlConnection
{
public HfsqlOptions Options => new();
public HfsqlConnectionState State { get; private set; } = HfsqlConnectionState.Closed;
public IReadOnlyList<IReadOnlyDictionary<string, object?>> RendezVousRows { get; set; } = [];
public Task OpenAsync(CancellationToken cancellationToken = default)
{
State = HfsqlConnectionState.Open;
return Task.CompletedTask;
}
public Task CloseAsync()
{
State = HfsqlConnectionState.Closed;
return Task.CompletedTask;
}
public Task<object?> ExecuteScalarAsync(string sql, IReadOnlyList<object?>? parameters = null, CancellationToken cancellationToken = default)
=> Task.FromResult<object?>(null);
public Task<int> ExecuteNonQueryAsync(string sql, IReadOnlyList<object?>? parameters = null, CancellationToken cancellationToken = default)
=> Task.FromResult(0);
public Task<IReadOnlyList<IReadOnlyDictionary<string, object?>>> QueryAsync(
string sql, IReadOnlyList<object?>? parameters = null, CancellationToken cancellationToken = default)
{
if (ContainsFrom(sql, "Planning_RDV"))
return Task.FromResult(RendezVousRows);
return Task.FromResult<IReadOnlyList<IReadOnlyDictionary<string, object?>>>([]);
}
public Task<IReadOnlyList<HfsqlColumnMetadata>> GetColumnsAsync(
string tableName, CancellationToken cancellationToken = default)
=> Task.FromResult<IReadOnlyList<HfsqlColumnMetadata>>([]);
public ValueTask DisposeAsync()
{
State = HfsqlConnectionState.Closed;
return ValueTask.CompletedTask;
}
private static bool ContainsFrom(string sql, string table)
{
string marker = $"FROM {table}";
int index = sql.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
while (index >= 0)
{
int next = index + marker.Length;
if (next >= sql.Length || (!char.IsLetterOrDigit(sql[next]) && sql[next] != '_'))
{
return true;
}
index = sql.IndexOf(marker, index + 1, StringComparison.OrdinalIgnoreCase);
}
return false;
}
}
}