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