using Omika.Diagnostics.Security; namespace Omika.Diagnostics.Tests; /// /// T23 — Règles de validation des utilisateurs (username/password/rôle) + protections /// d'administration (dernier Admin, auto-désactivation, auto-rétrogradation) + hash factice. /// public class DiagnosticUserPolicyTests { // -------------------------------------------------- username ---- [Theory] [InlineData("jean.dupont")] [InlineData("admin-local")] [InlineData("a_b-c.d")] [InlineData("ABC123")] public void Username_Valid(string username) => Assert.True(DiagnosticUserRules.IsValidUsername(username)); [Theory] [InlineData("")] [InlineData(" ")] [InlineData("avec espace")] [InlineData("accentué")] [InlineData("a/b")] [InlineData("a@b")] [InlineData("a;b")] public void Username_Invalid(string username) => Assert.False(DiagnosticUserRules.IsValidUsername(username)); [Fact] public void Username_TooLong_Rejected() { string tooLong = new string('a', DiagnosticUserRules.MaxUsernameLength + 1); Assert.False(DiagnosticUserRules.IsValidUsername(tooLong)); Assert.True(DiagnosticUserRules.IsValidUsername(new string('a', DiagnosticUserRules.MaxUsernameLength))); } [Fact] public void NormalizeUsername_Trims() { Assert.Equal("jean", DiagnosticUserRules.NormalizeUsername(" jean ")); Assert.Null(DiagnosticUserRules.NormalizeUsername(" ")); Assert.Null(DiagnosticUserRules.NormalizeUsername(null)); } // -------------------------------------------------- password ---- [Fact] public void Password_Minimum12() { Assert.True(DiagnosticUserRules.IsValidPassword("123456789012")); Assert.True(DiagnosticUserRules.IsValidPassword("abcdefghijkl")); Assert.False(DiagnosticUserRules.IsValidPassword("12345678901")); // 11 Assert.False(DiagnosticUserRules.IsValidPassword("")); Assert.False(DiagnosticUserRules.IsValidPassword(null)); } // -------------------------------------------------- role ---- [Fact] public void Role_OnlySupportOrAdmin() { Assert.True(DiagnosticUserRules.IsValidRole("Support")); Assert.True(DiagnosticUserRules.IsValidRole("Admin")); Assert.True(DiagnosticUserRules.IsValidRole("admin")); Assert.False(DiagnosticUserRules.IsValidRole("Owner")); Assert.False(DiagnosticUserRules.IsValidRole("")); Assert.False(DiagnosticUserRules.IsValidRole(null)); } // -------------------------------------------------- guard : dernier Admin ---- [Fact] public void Guard_Disable_LastActiveAdmin_Refused() { bool ok = DiagnosticUserGuard.CanDisable("admin1", "admin2", targetIsActiveAdmin: true, activeAdminCount: 1, out string? error); Assert.False(ok); Assert.Equal(DiagnosticUserGuard.LastAdminMessage, error); } [Fact] public void Guard_Disable_NonLastAdmin_Allowed() { bool ok = DiagnosticUserGuard.CanDisable("admin1", "admin2", targetIsActiveAdmin: true, activeAdminCount: 2, out string? error); Assert.True(ok); Assert.Null(error); } [Fact] public void Guard_Disable_SupportUser_Allowed() { bool ok = DiagnosticUserGuard.CanDisable("admin1", "support1", targetIsActiveAdmin: false, activeAdminCount: 1, out string? error); Assert.True(ok); Assert.Null(error); } // -------------------------------------------------- guard : auto-désactivation ---- [Fact] public void Guard_Disable_Self_Refused() { bool ok = DiagnosticUserGuard.CanDisable("admin1", "admin1", targetIsActiveAdmin: true, activeAdminCount: 2, out string? error); Assert.False(ok); Assert.Equal(DiagnosticUserGuard.SelfDisableMessage, error); } // -------------------------------------------------- guard : auto-rétrogradation ---- [Fact] public void Guard_Demote_LastActiveAdmin_Refused() { bool ok = DiagnosticUserGuard.CanDemoteFromAdmin("admin1", "admin2", activeAdminCount: 1, out string? error); Assert.False(ok); Assert.Equal(DiagnosticUserGuard.LastAdminMessage, error); } [Fact] public void Guard_Demote_Self_Refused() { bool ok = DiagnosticUserGuard.CanDemoteFromAdmin("admin1", "admin1", activeAdminCount: 2, out string? error); Assert.False(ok); Assert.Equal(DiagnosticUserGuard.SelfDemoteMessage, error); } [Fact] public void Guard_Demote_NonLastAdmin_Allowed() { bool ok = DiagnosticUserGuard.CanDemoteFromAdmin("admin1", "admin2", activeAdminCount: 2, out string? error); Assert.True(ok); Assert.Null(error); } // -------------------------------------------------- hash factice ---- [Fact] public void DummyHash_ValidFormatAndDoesNotMatchArbitraryPassword() { Assert.False(DiagnosticPasswordHasher.Verify("un-mot-de-passe-arbitraire", DiagnosticPasswordHasher.DummyHash)); // Format valide : 3 parties, itérations positives (compatible Verify). string[] parts = DiagnosticPasswordHasher.DummyHash.Split('.'); Assert.Equal(3, parts.Length); Assert.True(int.TryParse(parts[0], out int iterations) && iterations > 0); } }