using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using Moq; using SchotzIadKmTracker.Api.Data; using SchotzIadKmTracker.Api.Models.DTOs; using SchotzIadKmTracker.Api.Models.Entities; using SchotzIadKmTracker.Api.Repositories; using SchotzIadKmTracker.Api.Services; using Xunit; namespace SchotzIadKmTracker.Tests.PdfExport; /// /// Tests d'intégration de (vérification manuelle demandée après les /// changements de nommage de fichier, logo, précision des distances et restructuration des /// en-têtes mensuel/annuel). Branche les services réels sur une base SQLite en mémoire ; /// seul est mocké (jamais appelé ici, aucune adresse de domicile /// à géocoder dans les scénarios testés). /// public class PdfServiceIntegrationTests : IDisposable { private readonly SqliteConnection _connection; private readonly AppDbContext _context; private readonly PdfService _pdfService; public PdfServiceIntegrationTests() { // Licence QuestPDF (usage communautaire, gratuit) — requise avant toute génération de // PDF ; configurée normalement au démarrage de l'application (Program.cs), reproduite // ici car ce test instancie PdfService directement sans démarrer le host. QuestPDF.Settings.License = QuestPDF.Infrastructure.LicenseType.Community; _connection = new SqliteConnection("Data Source=:memory:"); _connection.Open(); var options = new DbContextOptionsBuilder().UseSqlite(_connection).Options; _context = new AppDbContext(options); _context.Database.EnsureCreated(); var precisionDistanceService = new PrecisionDistanceService(_context); var baremeService = new BaremeService(_context); var resumeService = new ResumeService(_context, precisionDistanceService); var montantRegleService = new MontantRegleService(_context, baremeService, precisionDistanceService); var remboursementService = new RemboursementService( _context, baremeService, montantRegleService, precisionDistanceService); var vehiculeRepository = new VehiculeRepository(_context); var vehiculeService = new VehiculeService(vehiculeRepository, _context); var adresseCacheRepository = new AdresseCacheRepository(_context); var adresseCacheService = new AdresseCacheService(adresseCacheRepository, _context); var banClientMock = new Mock(); var profilService = new ProfilService( _context, banClientMock.Object, adresseCacheService, NullLogger.Instance); _pdfService = new PdfService( resumeService, profilService, vehiculeService, baremeService, montantRegleService, remboursementService, precisionDistanceService, _context); } /// Prépare un profil, un véhicule, un trajet et une tranche de barème de test. private async Task PreparerScenarioAsync(string matricule, string plaque, int precision) { _context.Profil.Add(new Profil { Id = 1, Nom = "Dupont", Prenom = "Jean", Matricule = matricule, AdresseDomicile = null }); var vehicule = new Vehicule { Denomination = "Véhicule de test", Plaque = plaque, PuissanceFiscale = 5, DateEffet = new DateOnly(2020, 1, 1) }; _context.Vehicules.Add(vehicule); _context.TranchesBareme.Add(new TrancheBareme { Annee = 2026, PuissanceFiscale = 5, KmMin = 0m, KmMax = null, Coefficient = 0.350m, Base = 0m }); await _context.SaveChangesAsync(); _context.Trajets.Add(new Trajet { Date = new DateOnly(2026, 3, 15), AdresseDepart = "Départ test", AdresseArrivee = "Arrivée test", Distance = 12.3456m, AllerRetour = false, Position = 1m, VehiculeId = vehicule.Id, PuissanceFiscale = 5 }); await _context.SaveChangesAsync(); await precisionServiceDefinir(precision); return vehicule.Id; } private async Task precisionServiceDefinir(int precision) { var service = new PrecisionDistanceService(_context); await service.DefinirAsync(precision, CancellationToken.None); } [Fact] public async Task GenererRapportMensuelAsync_produit_un_pdf_et_un_nom_de_fichier_conforme() { var vehiculeId = await PreparerScenarioAsync("AB1234", "AB-123-CD", precision: 0); var resultat = await _pdfService.GenererRapportMensuelAsync(2026, 3, vehiculeId, CancellationToken.None); resultat.Contenu.Should().NotBeEmpty(); // En-tête PDF standard : les octets générés doivent être un vrai document PDF. System.Text.Encoding.ASCII.GetString(resultat.Contenu, 0, 4).Should().Be("%PDF"); resultat.NomFichier.Should().Be("202603_Trajets_AB1234_AB123CD.pdf"); } [Fact] public async Task GenererRapportAnnuelAsync_produit_un_pdf_et_un_nom_de_fichier_conforme() { var vehiculeId = await PreparerScenarioAsync("XY9876", "ZZ-999-ZZ", precision: 2); var resultat = await _pdfService.GenererRapportAnnuelAsync(2026, vehiculeId, CancellationToken.None); resultat.Contenu.Should().NotBeEmpty(); System.Text.Encoding.ASCII.GetString(resultat.Contenu, 0, 4).Should().Be("%PDF"); resultat.NomFichier.Should().Be("2026_Trajets_XY9876_ZZ999ZZ.pdf"); } [Fact] public async Task GenererRapportMensuelAsync_omet_les_segments_matricule_et_plaque_absents() { // Matricule vide (chaîne vide) : le segment doit être omis du nom de fichier. var vehiculeId = await PreparerScenarioAsync(string.Empty, "CD-456-EF", precision: 1); var resultat = await _pdfService.GenererRapportMensuelAsync(2026, 3, vehiculeId, CancellationToken.None); resultat.NomFichier.Should().Be("202603_Trajets_CD456EF.pdf"); } public void Dispose() { _context.Dispose(); _connection.Dispose(); } }