using Omika.Diagnostics.Referential; namespace Omika.Diagnostics.Tests.Fakes; /// /// Fake en mémoire de pour les tests sans /// PostgreSQL. Reproduit la sémantique du repository Npgsql : normalisation TRIM+UPPER, /// unicité (type, code), code immuable, archivage (enabled=false) sans DELETE. Les codes /// « observés » (pour ) sont injectés via /// (miroir du DISTINCT diagnostic_event, sans base réelle). /// public sealed class FakeDiagnosticReferenceRepository : IDiagnosticReferenceRepository { private readonly List _references = []; private readonly List<(string Type, string Code, long Count, DateTimeOffset LastSeen)> _observed = []; private long _nextId = 1; public IReadOnlyList Seed(params DiagnosticReferenceRecord[] records) { foreach (DiagnosticReferenceRecord r in records) { if (r.Id == 0) { r.Id = _nextId++; } else { _nextId = Math.Max(_nextId, r.Id + 1); } _references.Add(r); } return _references; } public void SeedObserved(string type, string code, long count, DateTimeOffset lastSeen) => _observed.Add((type, code, count, lastSeen)); public Task> ListAsync( CancellationToken cancellationToken = default) { IReadOnlyList ordered = _references .OrderBy(r => r.ReferenceType, StringComparer.Ordinal) .ThenBy(r => r.SortOrder) .ThenBy(r => r.Code, StringComparer.Ordinal) .ToList(); return Task.FromResult(ordered); } public Task GetByTypeAndCodeAsync( string referenceType, string code, CancellationToken cancellationToken = default) { string? normalized = DiagnosticReferenceNormalization.NormalizeCode(code); DiagnosticReferenceRecord? found = _references.FirstOrDefault(r => r.ReferenceType == referenceType && r.Code == normalized); return Task.FromResult(found); } public Task GetByIdAsync( long id, CancellationToken cancellationToken = default) { return Task.FromResult(_references.FirstOrDefault(r => r.Id == id)); } public Task CreateAsync( string referenceType, string code, string label, string? description, bool enabled, int sortOrder, CancellationToken cancellationToken = default) { string? normalizedCode = DiagnosticReferenceNormalization.NormalizeCode(code); string? normalizedLabel = DiagnosticReferenceNormalization.NormalizeLabel(label); if (!DiagnosticReferenceTypes.IsSupported(referenceType) || normalizedCode is null || normalizedLabel is null) { return Task.FromResult(null); } if (_references.Any(r => r.ReferenceType == referenceType && r.Code == normalizedCode)) { return Task.FromResult(null); // doublon. } DateTimeOffset now = DateTimeOffset.UtcNow; var record = new DiagnosticReferenceRecord { Id = _nextId++, ReferenceType = referenceType, Code = normalizedCode, Label = normalizedLabel, Description = DiagnosticReferenceNormalization.NormalizeLabel(description), Enabled = enabled, SortOrder = sortOrder, CreatedAtUtc = now, UpdatedAtUtc = now }; _references.Add(record); return Task.FromResult(record); } public Task UpdateAsync( long id, string label, string? description, bool enabled, int sortOrder, CancellationToken cancellationToken = default) { string? normalizedLabel = DiagnosticReferenceNormalization.NormalizeLabel(label); DiagnosticReferenceRecord? record = _references.FirstOrDefault(r => r.Id == id); if (record is null || normalizedLabel is null) { return Task.FromResult(false); } record.Label = normalizedLabel; record.Description = DiagnosticReferenceNormalization.NormalizeLabel(description); record.Enabled = enabled; record.SortOrder = sortOrder; record.UpdatedAtUtc = DateTimeOffset.UtcNow; return Task.FromResult(true); } public Task> GetLabelsAsync( string referenceType, CancellationToken cancellationToken = default) { var labels = new Dictionary(StringComparer.Ordinal); foreach (DiagnosticReferenceRecord r in _references.Where(r => r.ReferenceType == referenceType)) { labels[r.Code] = r.Label; } return Task.FromResult>(labels); } public Task> GetUnreferencedAsync( int limit, CancellationToken cancellationToken = default) { var result = _observed .Where(o => !_references.Any(r => r.ReferenceType == o.Type && r.Code == o.Code)) .Select(o => new DiagnosticUnreferencedCode(o.Type, o.Code, o.Count, o.LastSeen)) .OrderByDescending(x => x.ObservedCount) .ThenBy(x => x.ReferenceType, StringComparer.Ordinal) .ThenBy(x => x.Code, StringComparer.Ordinal) .Take(Math.Clamp(limit, 1, 200)) .ToList(); return Task.FromResult>(result); } }