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

159 lines
5.9 KiB
C#

using Omika.Diagnostics.Referential;
namespace Omika.Diagnostics.Tests.Fakes;
/// <summary>
/// Fake en mémoire de <see cref="IDiagnosticReferenceRepository"/> 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 <see cref="GetUnreferencedAsync"/>) sont injectés via
/// <see cref="SeedObserved"/> (miroir du DISTINCT diagnostic_event, sans base réelle).
/// </summary>
public sealed class FakeDiagnosticReferenceRepository : IDiagnosticReferenceRepository
{
private readonly List<DiagnosticReferenceRecord> _references = [];
private readonly List<(string Type, string Code, long Count, DateTimeOffset LastSeen)> _observed = [];
private long _nextId = 1;
public IReadOnlyList<DiagnosticReferenceRecord> 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<IReadOnlyList<DiagnosticReferenceRecord>> ListAsync(
CancellationToken cancellationToken = default)
{
IReadOnlyList<DiagnosticReferenceRecord> 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<DiagnosticReferenceRecord?> 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<DiagnosticReferenceRecord?> GetByIdAsync(
long id,
CancellationToken cancellationToken = default)
{
return Task.FromResult(_references.FirstOrDefault(r => r.Id == id));
}
public Task<DiagnosticReferenceRecord?> 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<DiagnosticReferenceRecord?>(null);
}
if (_references.Any(r => r.ReferenceType == referenceType && r.Code == normalizedCode))
{
return Task.FromResult<DiagnosticReferenceRecord?>(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<DiagnosticReferenceRecord?>(record);
}
public Task<bool> 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<IReadOnlyDictionary<string, string>> GetLabelsAsync(
string referenceType,
CancellationToken cancellationToken = default)
{
var labels = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (DiagnosticReferenceRecord r in _references.Where(r => r.ReferenceType == referenceType))
{
labels[r.Code] = r.Label;
}
return Task.FromResult<IReadOnlyDictionary<string, string>>(labels);
}
public Task<IReadOnlyList<DiagnosticUnreferencedCode>> 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<IReadOnlyList<DiagnosticUnreferencedCode>>(result);
}
}