226 lines
8.0 KiB
C#
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);
|
|
}
|
|
}
|