using Omika.Core.Domain.Entities.Security; using Omika.Data.Configuration; using Omika.Data.Connection; using Omika.Data.Repositories; namespace Omika.Tests.Repositories; /// /// Tests du repository d'authentification Back-Office. /// Vérifie que la requête est paramétrée (aucune concaténation du login ni du /// hash), que la comparaison du login est insensible à la casse, que le hash est /// comparé tel quel, et que la ligne jointe est correctement mappée. /// public class BackOfficeAuthRepositoryTests { private const string Login = "DFO"; private const string Hash = "FAKE-HASH-0123456789ABCDEF0123456789ABCDEF"; [Fact] public async Task GetByLoginAndPasswordHashAsync_RequeteParametree_RetourneUtilisateur() { var connection = new FakeHfsqlConnection(HfsqlConnectionState.Open) { QueryResult = [ new Dictionary { ["UT_PKID"] = 42L, ["UT_Login"] = Login, ["UT_IDCollaborateur"] = 7L, ["UT_AccesBO"] = true, ["CO_PKID"] = 7L, ["CO_AccesBO"] = true, ["CO_Actif"] = true } ] }; var repository = new BackOfficeAuthRepository(connection); BackOfficeAuthUser? user = await repository.GetByLoginAndPasswordHashAsync(Login, Hash); Assert.NotNull(user); Assert.Equal(42, user.UserId); Assert.Equal(Login, user.Login); Assert.Equal(7, user.CollaborateurId); Assert.Equal(7, user.CollaborateurPkId); Assert.True(user.UtilisateurAccesBO); Assert.True(user.CollaborateurAccesBO); Assert.True(user.CollaborateurActif); // Requête paramétrée : login et hash ne sont jamais concaténés au SQL. Assert.Contains("UPPER(Utilisateur.UT_Login) = ?", connection.LastSql); Assert.Contains("Utilisateur.UT_MotDePasse = ?", connection.LastSql); Assert.Contains("LEFT JOIN Collaborateur", connection.LastSql); Assert.DoesNotContain(Login, connection.LastSql); Assert.DoesNotContain(Hash, connection.LastSql); Assert.NotNull(connection.LastParameters); Assert.Equal(2, connection.LastParameters!.Count); Assert.Equal(Login.ToUpperInvariant(), connection.LastParameters[0]); Assert.Equal(Hash, connection.LastParameters[1]); // hash comparé tel quel } [Fact] public async Task GetByLoginAndPasswordHashAsync_LoginMinuscules_EstUppercasedDansLeParametre() { var connection = new FakeHfsqlConnection(HfsqlConnectionState.Open) { QueryResult = [] }; var repository = new BackOfficeAuthRepository(connection); await repository.GetByLoginAndPasswordHashAsync("dfo", Hash); Assert.NotNull(connection.LastParameters); Assert.Equal("DFO", connection.LastParameters![0]); } [Fact] public async Task GetByLoginAndPasswordHashAsync_AucuneLigne_RetourneNull() { var connection = new FakeHfsqlConnection(HfsqlConnectionState.Open) { QueryResult = [] }; var repository = new BackOfficeAuthRepository(connection); Assert.Null(await repository.GetByLoginAndPasswordHashAsync(Login, Hash)); } [Fact] public async Task GetByLoginAndPasswordHashAsync_ChampsVides_RetourneNullSansRequete() { var connection = new FakeHfsqlConnection(HfsqlConnectionState.Open); var repository = new BackOfficeAuthRepository(connection); Assert.Null(await repository.GetByLoginAndPasswordHashAsync(" ", Hash)); Assert.Null(await repository.GetByLoginAndPasswordHashAsync(Login, " ")); Assert.Equal(0, connection.QueryCount); } [Fact] public async Task GetByLoginAndPasswordHashAsync_ColonnesQualifiees_RetourneUtilisateur() { // Le provider peut renvoyer les colonnes sous forme qualifiée // (ex. « Utilisateur.UT_PKID ») : le mapping doit rester insensible. var connection = new FakeHfsqlConnection(HfsqlConnectionState.Open) { QueryResult = [ new Dictionary { ["Utilisateur.UT_PKID"] = 42L, ["Utilisateur.UT_Login"] = Login, ["Utilisateur.UT_IDCollaborateur"] = 7L, ["Utilisateur.UT_AccesBO"] = false, ["Collaborateur.CO_PKID"] = 7L, ["Collaborateur.CO_AccesBO"] = true, ["Collaborateur.CO_Actif"] = true } ] }; var repository = new BackOfficeAuthRepository(connection); BackOfficeAuthUser? user = await repository.GetByLoginAndPasswordHashAsync(Login, Hash); Assert.NotNull(user); Assert.Equal(42, user.UserId); Assert.Equal(7, user.CollaborateurId); Assert.True(user.CollaborateurAccesBO); } private sealed class FakeHfsqlConnection : IHfsqlConnection { public FakeHfsqlConnection(HfsqlConnectionState state) => State = state; public HfsqlOptions Options => new(); public HfsqlConnectionState State { get; private set; } public string? LastSql { get; private set; } public IReadOnlyList? LastParameters { get; private set; } public int QueryCount { get; private set; } public IReadOnlyList> QueryResult { get; set; } = []; public Task OpenAsync(CancellationToken cancellationToken = default) { State = HfsqlConnectionState.Open; return Task.CompletedTask; } public Task CloseAsync() { State = HfsqlConnectionState.Closed; return Task.CompletedTask; } public ValueTask DisposeAsync() => ValueTask.CompletedTask; public Task ExecuteScalarAsync(string sql, IReadOnlyList? parameters = null, CancellationToken cancellationToken = default) => Task.FromResult(null); public Task ExecuteNonQueryAsync(string sql, IReadOnlyList? parameters = null, CancellationToken cancellationToken = default) => Task.FromResult(0); public Task>> QueryAsync(string sql, IReadOnlyList? parameters = null, CancellationToken cancellationToken = default) { QueryCount++; LastSql = sql; LastParameters = parameters; return Task.FromResult(QueryResult); } public Task> GetColumnsAsync(string tableName, CancellationToken cancellationToken = default) => Task.FromResult>([]); } }