Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
@@ -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;
}
}
}