using Omika.Diagnostics.Security; using Omika.Diagnostics.Tests.Fakes; using Omika.Diagnostics.Users; namespace Omika.Diagnostics.Tests; /// /// T23 — Authentification hybride : utilisateurs PostgreSQL en priorité, sinon opérateurs /// bootstrap. Échec générique (jamais « inconnu » vs « désactivé »). DB autoritaire. /// public class DiagnosticUserAuthenticatorTests { private const string Password = "correct-password-123"; private static DiagnosticUserRecord DbUser(string username, string password, string role = DiagnosticRoles.Support, bool enabled = true) => new() { Id = 1, Username = username, PasswordHash = DiagnosticPasswordHasher.Hash(password), Role = role, Enabled = enabled }; private static DiagnosticOperator Operator(string username, string password, params string[] roles) => new(username, DiagnosticPasswordHasher.Hash(password), roles); // -------------------------------------------------- DB prioritaire ---- [Fact] public async Task DbUser_CorrectPassword_Succeeds() { var repo = new FakeDiagnosticUserRepository(); repo.Seed(DbUser("alice", Password, DiagnosticRoles.Admin)); DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync( repo, Array.Empty(), "alice", Password); Assert.True(result.Success); Assert.True(result.IsDatabaseUser); Assert.Equal("alice", result.Username); Assert.Contains(DiagnosticRoles.Admin, result.Roles); } [Fact] public async Task DbUser_WrongPassword_Fails() { var repo = new FakeDiagnosticUserRepository(); repo.Seed(DbUser("alice", Password)); DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync( repo, Array.Empty(), "alice", "wrong-password-123"); Assert.False(result.Success); } [Fact] public async Task DbUser_Disabled_Fails_EvenWithCorrectPassword() { var repo = new FakeDiagnosticUserRepository(); repo.Seed(DbUser("alice", Password, enabled: false)); DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync( repo, Array.Empty(), "alice", Password); Assert.False(result.Success); } // -------------------------------------------------- fallback opérateurs ---- [Fact] public async Task UnknownUser_OperatorFallback_Succeeds() { var repo = new FakeDiagnosticUserRepository(); DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync( repo, new[] { Operator("bootstrap", Password, DiagnosticRoles.Admin) }, "bootstrap", Password); Assert.True(result.Success); Assert.False(result.IsDatabaseUser); Assert.Equal("bootstrap", result.Username); Assert.Contains(DiagnosticRoles.Admin, result.Roles); } [Fact] public async Task UnknownUser_NoOperators_Fails() { var repo = new FakeDiagnosticUserRepository(); DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync( repo, Array.Empty(), "inconnu", Password); Assert.False(result.Success); } [Fact] public async Task Operator_WrongPassword_Fails() { var repo = new FakeDiagnosticUserRepository(); DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync( repo, new[] { Operator("bootstrap", Password, DiagnosticRoles.Admin) }, "bootstrap", "wrong-password-123"); Assert.False(result.Success); } // -------------------------------------------------- priorité DB (§36) ---- [Fact] public async Task DbIsAuthoritative_DisabledDbUser_DoesNotFallbackToOperator() { var repo = new FakeDiagnosticUserRepository(); repo.Seed(DbUser("dup", Password, enabled: false)); // Même username existe aussi en configuration (bootstrap) avec un mot de passe valide : // la DB doit rester autoritaire → échec, PAS de fallback. DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync( repo, new[] { Operator("dup", Password, DiagnosticRoles.Admin) }, "dup", Password); Assert.False(result.Success); } // -------------------------------------------------- cas limites ---- [Theory] [InlineData("", "password-123")] [InlineData(" ", "password-123")] [InlineData("alice", "")] [InlineData("alice", " ")] public async Task BlankInput_Fails(string username, string password) { var repo = new FakeDiagnosticUserRepository(); repo.Seed(DbUser("alice", Password)); DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync( repo, Array.Empty(), username, password); Assert.False(result.Success); } [Fact] public async Task DbLookup_CaseInsensitive() { var repo = new FakeDiagnosticUserRepository(); repo.Seed(DbUser("Alice.Dupont", Password, DiagnosticRoles.Support)); DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync( repo, Array.Empty(), "alice.dupont", Password); Assert.True(result.Success); Assert.Equal("Alice.Dupont", result.Username); } }