using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using SchotzIadKmTracker.Api.Configuration;
using SchotzIadKmTracker.Api.Data;
using SchotzIadKmTracker.Api.Models.Entities;
using SchotzIadKmTracker.Api.Services;
namespace SchotzIadKmTracker.Tests.Auth;
///
/// Banc d'essai pour tester en isolation.
///
/// Utilise un adossé à une base SQLite EN MÉMOIRE
/// ("Data Source=:memory:") dont la connexion est maintenue ouverte pour la
/// durée du harnais (la base disparaît à la fermeture de la connexion).
/// On emploie de vrais et
/// (JWT signé HS256 avec une clé >= 32 octets), et on mocke
/// (Moq) afin de capturer le lien de réinitialisation transmis.
///
/// Chaque instance crée une base fraîche : l'isolation entre itérations de
/// propriété est obtenue en instanciant un nouveau harnais par cas.
///
public sealed class AuthTestHarness : IDisposable
{
// Clé secrète >= 32 octets (exigée par HMAC-SHA256).
public const string SecretKey = "cle-secrete-de-test-suffisamment-longue-1234567890";
private readonly SqliteConnection _connection;
public AppDbContext Context { get; }
public AuthService Service { get; }
public Mock EmailMock { get; }
public JwtService Jwt { get; }
public PasswordHasher Hasher { get; }
public JwtOptions JwtOptions { get; }
public AuthOptions AuthOptions { get; }
/// Dernier lien de réinitialisation capturé via le mock IEmailService.
public string? DernierLienReset { get; private set; }
/// Nombre d'appels à EnvoyerLienResetAsync capturés.
public int NbAppelsEmail { get; private set; }
public AuthTestHarness(int jwtExpirationMinutes = 720, int resetTokenLifetimeMinutes = 60)
{
_connection = new SqliteConnection("Data Source=:memory:");
_connection.Open(); // maintenue ouverte : garde la base en vie
var options = new DbContextOptionsBuilder()
.UseSqlite(_connection)
.Options;
Context = new AppDbContext(options);
Context.Database.EnsureCreated();
Hasher = new PasswordHasher();
JwtOptions = new JwtOptions
{
SecretKey = SecretKey,
Issuer = "SchotzIadKmTracker",
Audience = "SchotzIadKmTracker",
ExpirationMinutes = jwtExpirationMinutes,
};
AuthOptions = new AuthOptions
{
ResetTokenLifetimeMinutes = resetTokenLifetimeMinutes,
FrontendBaseUrl = "http://localhost:8080",
};
Jwt = new JwtService(Options.Create(JwtOptions));
EmailMock = new Mock();
EmailMock
.Setup(e => e.EnvoyerLienResetAsync(It.IsAny(), It.IsAny(), It.IsAny()))
.Callback((_, lien, _) =>
{
DernierLienReset = lien;
NbAppelsEmail++;
})
.Returns(Task.CompletedTask);
Service = new AuthService(
Context,
Hasher,
Jwt,
EmailMock.Object,
Options.Create(AuthOptions),
Options.Create(JwtOptions),
NullLogger.Instance);
}
/// Extrait la valeur du paramètre "token" de la query string d'un lien de reset.
public static string ExtraireToken(string lienReset)
{
var uri = new Uri(lienReset);
var query = uri.Query.TrimStart('?');
foreach (var paire in query.Split('&', StringSplitOptions.RemoveEmptyEntries))
{
var kv = paire.Split('=', 2);
if (kv.Length == 2 && kv[0] == "token")
{
return Uri.UnescapeDataString(kv[1]);
}
}
throw new InvalidOperationException($"Aucun paramètre 'token' dans le lien : {lienReset}");
}
public void Dispose()
{
Context.Dispose();
_connection.Dispose();
}
}