188 lines
7.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|