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

226 lines
8.0 KiB
C#

using Omika.Diagnostics.Models;
using Omika.Diagnostics.Queries;
using Omika.Diagnostics.Tests.Fakes;
namespace Omika.Diagnostics.Tests;
/// <summary>
/// Tâche 2 — Recherche texte bornée. Les tests s'appuient sur le
/// <see cref="FakeDiagnosticEventRepository"/> (in-memory) ; l'implémentation
/// PostgreSQL de production exécute les mêmes contrats via ILIKE paramétré.
/// </summary>
public class DiagnosticSearchTests
{
private static readonly DateTimeOffset From = new(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
private static readonly DateTimeOffset To = new(2025, 2, 1, 0, 0, 0, TimeSpan.Zero);
private static DiagnosticEventRecord Ev(
string? message = null,
string? exceptionType = null,
string? stackTrace = null,
string eventCode = "ERR",
DateTimeOffset? ts = null,
string? component = null,
string? errorCode = null,
string level = "Error",
string priority = "P2",
string? appVersion = null,
string? platform = null,
string? actorKey = null) =>
new()
{
EventId = Guid.NewGuid(),
EventCode = eventCode,
TimestampUtc = ts ?? From.AddMinutes(1),
Component = component,
ErrorCode = errorCode,
Message = message,
ExceptionType = exceptionType,
StackTrace = stackTrace,
Level = level,
Priority = priority,
AppVersion = appVersion,
Platform = platform,
ActorKey = actorKey,
Source = DiagnosticSources.Mobile,
IngestedAtUtc = DateTimeOffset.UtcNow
};
// ------------------------------------------------------- R1 : message ----
[Fact]
public async Task Search_Message_ContainsTerm_IsFound()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev(message: "Connexion serveur impossible")
});
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.SearchAsync(
new DiagnosticSearchQuery { FromUtc = From, ToUtc = To, SearchText = "serveur" });
DiagnosticEventRecord item = Assert.Single(result.Items);
Assert.Equal("Connexion serveur impossible", item.Message);
Assert.Equal(1, result.TotalCount);
}
// ------------------------------------------------- R2 : insensible casse ----
[Fact]
public async Task Search_IsCaseInsensitive()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev(exceptionType: "System.NullReferenceException")
});
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.SearchAsync(
new DiagnosticSearchQuery { FromUtc = From, ToUtc = To, SearchText = "nullreference" });
Assert.Single(result.Items);
}
// ------------------------------------------------------ R3 : hors période ----
[Fact]
public async Task Search_OutOfPeriod_IsExcluded()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev(message: "serveur", ts: From.AddMinutes(-5)), // avant FromUtc
Ev(message: "serveur", ts: To.AddMinutes(5)) // après ToUtc (borne exclusive)
});
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.SearchAsync(
new DiagnosticSearchQuery { FromUtc = From, ToUtc = To, SearchText = "serveur" });
Assert.Empty(result.Items);
Assert.Equal(0, result.TotalCount);
}
// ----------------------------------------------------- R4 : filtres ----
[Fact]
public async Task Search_RespectsOtherFilters()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev(message: "serveur", component: "Omika.Api"),
Ev(message: "serveur", component: "Omika.Worker")
});
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.SearchAsync(
new DiagnosticSearchQuery
{
FromUtc = From,
ToUtc = To,
SearchText = "serveur",
Component = "Omika.Api"
});
DiagnosticEventRecord item = Assert.Single(result.Items);
Assert.Equal("Omika.Api", item.Component);
}
// -------------------------------------- R5 : recherche vide/trop courte ----
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("a")] // < MinSearchLength (2) → recherche désactivée
public async Task Search_EmptyOrTooShort_DisablesTextFilter(string? searchText)
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev(message: "Connexion serveur impossible"),
Ev(message: "Autre message")
});
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.SearchAsync(
new DiagnosticSearchQuery { FromUtc = From, ToUtc = To, SearchText = searchText });
// Pas de filtre texte : tous les événements de la période sont retournés
// (jamais un ILIKE '%%').
Assert.Equal(2, result.TotalCount);
}
// ----------------------------------------- exception_type recherchable ----
[Fact]
public async Task Search_MatchesExceptionType()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev(message: "échec", exceptionType: "System.TimeoutException")
});
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.SearchAsync(
new DiagnosticSearchQuery { FromUtc = From, ToUtc = To, SearchText = "timeout" });
Assert.Single(result.Items);
}
// ------------------------------------------------ pagination serveur ----
[Fact]
public async Task Search_RespectsPagination()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev(message: "serveur A", ts: From.AddMinutes(1)),
Ev(message: "serveur B", ts: From.AddMinutes(2)),
Ev(message: "serveur C", ts: From.AddMinutes(3))
});
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.SearchAsync(
new DiagnosticSearchQuery
{
FromUtc = From,
ToUtc = To,
SearchText = "serveur",
Page = 2,
PageSize = 1
});
Assert.Equal(3, result.TotalCount);
DiagnosticEventRecord item = Assert.Single(result.Items);
Assert.Equal("serveur B", item.Message); // tri timestamp DESC → page 2 = 2e plus récent
}
// ------------------------------------------ validation période obligatoire ----
[Theory]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Search_Validate_RejectsMissingPeriod(bool missingFrom, bool missingTo)
{
var repo = new FakeDiagnosticEventRepository();
var query = new DiagnosticSearchQuery
{
FromUtc = missingFrom ? null : From,
ToUtc = missingTo ? null : To,
SearchText = "serveur"
};
await Assert.ThrowsAsync<ArgumentException>(() => repo.SearchAsync(query));
}
// ---------------------------------------- joker % littéral (pas un joker) ----
[Fact]
public async Task Search_PercentIsLiteral()
{
var repo = new FakeDiagnosticEventRepository();
await repo.InsertBatchAsync(new[]
{
Ev(message: "100% réussi"),
Ev(message: "sans pourcent")
});
DiagnosticQueryResult<DiagnosticEventRecord> result = await repo.SearchAsync(
new DiagnosticSearchQuery { FromUtc = From, ToUtc = To, SearchText = "100%" });
DiagnosticEventRecord item = Assert.Single(result.Items);
Assert.Equal("100% réussi", item.Message);
}
}