using Omika.Diagnostics.Users; namespace Omika.Diagnostics.Tests.Fakes; /// /// Fake en mémoire de pour les tests sans PostgreSQL. /// Reproduit la sémantique du repository Npgsql : unicité insensible à la casse, tri par /// username, hash stocké (jamais de mot de passe en clair). /// public sealed class FakeDiagnosticUserRepository : IDiagnosticUserRepository { private readonly List _users = []; private long _nextId = 1; public IReadOnlyList Seed(params DiagnosticUserRecord[] users) { foreach (DiagnosticUserRecord u in users) { if (u.Id == 0) { u.Id = _nextId++; } else { _nextId = Math.Max(_nextId, u.Id + 1); } _users.Add(u); } return _users; } public Task GetByUsernameAsync( string username, CancellationToken cancellationToken = default) { string? normalized = username?.Trim(); DiagnosticUserRecord? found = _users.FirstOrDefault(u => string.Equals(u.Username, normalized, StringComparison.OrdinalIgnoreCase)); return Task.FromResult(found); } public Task> ListAsync( CancellationToken cancellationToken = default) { IReadOnlyList ordered = _users .OrderBy(u => u.Username, StringComparer.OrdinalIgnoreCase) .ToList(); return Task.FromResult(ordered); } public Task CreateAsync( string username, string passwordHash, string role, CancellationToken cancellationToken = default) { string? normalized = username?.Trim(); if (string.IsNullOrWhiteSpace(normalized)) { return Task.FromResult(null); } if (_users.Any(u => string.Equals(u.Username, normalized, StringComparison.OrdinalIgnoreCase))) { return Task.FromResult(null); // doublon. } DateTimeOffset now = DateTimeOffset.UtcNow; var record = new DiagnosticUserRecord { Id = _nextId++, Username = normalized, PasswordHash = passwordHash, Role = role, Enabled = true, CreatedAtUtc = now, UpdatedAtUtc = now, PasswordChangedAtUtc = now, LastLoginAtUtc = null }; _users.Add(record); return Task.FromResult(record); } public Task SetEnabledAsync( long id, bool enabled, CancellationToken cancellationToken = default) { DiagnosticUserRecord? user = _users.FirstOrDefault(u => u.Id == id); if (user is null) { return Task.FromResult(false); } user.Enabled = enabled; user.UpdatedAtUtc = DateTimeOffset.UtcNow; return Task.FromResult(true); } public Task SetRoleAsync( long id, string role, CancellationToken cancellationToken = default) { DiagnosticUserRecord? user = _users.FirstOrDefault(u => u.Id == id); if (user is null) { return Task.FromResult(false); } user.Role = role; user.UpdatedAtUtc = DateTimeOffset.UtcNow; return Task.FromResult(true); } public Task UpdatePasswordHashAsync( long id, string passwordHash, CancellationToken cancellationToken = default) { DiagnosticUserRecord? user = _users.FirstOrDefault(u => u.Id == id); if (user is null) { return Task.FromResult(false); } user.PasswordHash = passwordHash; user.PasswordChangedAtUtc = DateTimeOffset.UtcNow; user.UpdatedAtUtc = DateTimeOffset.UtcNow; return Task.FromResult(true); } public Task UpdateLastLoginAsync( string username, CancellationToken cancellationToken = default) { DiagnosticUserRecord? user = _users.FirstOrDefault(u => string.Equals(u.Username, username?.Trim(), StringComparison.OrdinalIgnoreCase)); if (user is not null) { user.LastLoginAtUtc = DateTimeOffset.UtcNow; } return Task.CompletedTask; } }