using Omika.Diagnostics.Models; using Omika.Diagnostics.Queries; using Omika.Diagnostics.Tests.Fakes; namespace Omika.Diagnostics.Tests; /// /// Suppression d'un groupe d'erreurs complet (bouton « Supprimer le groupe » /Erreurs). /// Le repository supprime TOUTES les occurrences d'un groupe (aucune borne de période), /// restreintes aux niveaux d'erreur réels. Tests sur ; /// la production exécute la même sémantique via IS NOT DISTINCT FROM + prédicat /// d'erreur réel. /// public class DiagnosticErrorGroupDeleteTests { private static DiagnosticEventRecord Ev( string eventCode = "ERR", DateTimeOffset? ts = null, string? errorCode = null, string? exceptionType = null, string? component = null, string level = "Error") => new() { EventId = Guid.NewGuid(), EventCode = eventCode, TimestampUtc = ts ?? DateTimeOffset.UtcNow, ErrorCode = errorCode, ExceptionType = exceptionType, Component = component, Level = level, Priority = "P2", Source = DiagnosticSources.Mobile, IngestedAtUtc = DateTimeOffset.UtcNow }; /// /// Identité de groupe aux clés NORMALISÉES (même normalisation que /// , qui re-normalise à la réception). /// private static DiagnosticErrorGroupIdentity Identity( string eventCode = "ERR", string? errorCode = null, string? exceptionType = null, string? component = null) => new(eventCode, DiagnosticErrorGrouping.NormalizeKey(errorCode), DiagnosticErrorGrouping.NormalizeKey(exceptionType), DiagnosticErrorGrouping.NormalizeKey(component)); // --------------------------------------- D1 : supprime tout le groupe ---- [Fact] public async Task DeleteErrorGroup_RemovesAllOccurrences() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("ERR", exceptionType: "System.TimeoutException"), Ev("ERR", exceptionType: "System.TimeoutException"), Ev("ERR", exceptionType: "System.TimeoutException") }); long deleted = await repo.DeleteErrorGroupAsync( Identity(exceptionType: "system.timeoutexception")); Assert.Equal(3, deleted); DiagnosticQueryResult remaining = await repo.QueryAsync(new DiagnosticEventQuery { PageSize = 10 }); Assert.Equal(0, remaining.TotalCount); } // ------------------------------- D2 : aucune borne de période (tout le groupe) ---- [Fact] public async Task DeleteErrorGroup_NoPeriodBound_RemovesAncientAndRecent() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("ERR", DateTimeOffset.UtcNow.AddYears(-2), exceptionType: "System.Exception"), Ev("ERR", DateTimeOffset.UtcNow, exceptionType: "System.Exception") }); long deleted = await repo.DeleteErrorGroupAsync( Identity(exceptionType: "system.exception")); Assert.Equal(2, deleted); } // ------------------------------- D3 : casse/espaces → même groupe ---- [Fact] public async Task DeleteErrorGroup_NormalizesKeys() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("ERR", exceptionType: "System.TimeoutException", component: "Omika.Api"), Ev("ERR", exceptionType: "system.timeoutexception", component: " omika.api ") }); long deleted = await repo.DeleteErrorGroupAsync( Identity(exceptionType: "SYSTEM.TIMEOUTEXCEPTION", component: "OMIKA.API")); Assert.Equal(2, deleted); } // ------------------------------- D4 : NULL ErrorCode → retrouvé ---- [Fact] public async Task DeleteErrorGroup_NullErrorCode_IsMatched() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("ERR", errorCode: null), Ev("ERR", errorCode: string.Empty), Ev("ERR", errorCode: " ") }); long deleted = await repo.DeleteErrorGroupAsync(Identity(errorCode: null)); Assert.Equal(3, deleted); // null / '' / espaces → tous normalisés en NULL } // ------------------------------- D5 : autre EventCode → exclu ---- [Fact] public async Task DeleteErrorGroup_DifferentEventCode_IsExcluded() { var repo = new FakeDiagnosticEventRepository(); DiagnosticEventRecord keep = Ev("OTHER", exceptionType: "System.Exception"); await repo.InsertBatchAsync(new[] { Ev("ERR", exceptionType: "System.Exception"), keep }); long deleted = await repo.DeleteErrorGroupAsync( Identity(eventCode: "ERR", exceptionType: "system.exception")); Assert.Equal(1, deleted); DiagnosticQueryResult remaining = await repo.QueryAsync(new DiagnosticEventQuery { PageSize = 10 }); DiagnosticEventRecord item = Assert.Single(remaining.Items); Assert.Equal(keep.EventId, item.EventId); } // ------------------------------- D6 : non-erreur de même signature → conservé ---- [Fact] public async Task DeleteErrorGroup_NonErrorSameSignature_IsKept() { var repo = new FakeDiagnosticEventRepository(); DiagnosticEventRecord info = Ev("ERR", exceptionType: "System.Exception", level: "info"); await repo.InsertBatchAsync(new[] { Ev("ERR", exceptionType: "System.Exception", level: "error"), info }); long deleted = await repo.DeleteErrorGroupAsync( Identity(exceptionType: "system.exception")); Assert.Equal(1, deleted); DiagnosticQueryResult remaining = await repo.QueryAsync(new DiagnosticEventQuery { PageSize = 10 }); DiagnosticEventRecord item = Assert.Single(remaining.Items); Assert.Equal(info.EventId, item.EventId); } // ------------------------------- D7 : groupe inexistant → 0 ---- [Fact] public async Task DeleteErrorGroup_Nonexistent_ReturnsZero() { var repo = new FakeDiagnosticEventRepository(); await repo.InsertBatchAsync(new[] { Ev("ERR", exceptionType: "System.Exception") }); long deleted = await repo.DeleteErrorGroupAsync( Identity(exceptionType: "system.nullreferenceexception")); Assert.Equal(0, deleted); DiagnosticQueryResult remaining = await repo.QueryAsync(new DiagnosticEventQuery { PageSize = 10 }); Assert.Equal(1, remaining.TotalCount); } // ------------------------------- D8 : table vide → 0 ---- [Fact] public async Task DeleteErrorGroup_EmptyTable_ReturnsZero() { var repo = new FakeDiagnosticEventRepository(); long deleted = await repo.DeleteErrorGroupAsync(Identity(exceptionType: "system.exception")); Assert.Equal(0, deleted); } // ------------------------------- D9 : ne supprime pas les autres groupes ---- [Fact] public async Task DeleteErrorGroup_LeavesOtherGroups() { var repo = new FakeDiagnosticEventRepository(); DiagnosticEventRecord other = Ev("ERR", exceptionType: "System.NullReferenceException"); await repo.InsertBatchAsync(new[] { Ev("ERR", exceptionType: "System.TimeoutException"), other }); long deleted = await repo.DeleteErrorGroupAsync( Identity(exceptionType: "system.timeoutexception")); Assert.Equal(1, deleted); DiagnosticQueryResult remaining = await repo.QueryAsync(new DiagnosticEventQuery { PageSize = 10 }); DiagnosticEventRecord item = Assert.Single(remaining.Items); Assert.Equal(other.EventId, item.EventId); } }