Files
MadJax 61298d6384 Refonte export PDF, colonne commentaire et UX résumés
- Export PDF: nouveau nommage AAAAMM/AAAA_Trajets_MATRICULE_PLAQUE.pdf,
  logo carré embarqué en en-tête, km formatés selon Precision_Distance,
  restructuration en-têtes (Domicile déplacé, période/année fusionnées
  au titre)
- Correction bug: le nom de fichier suggéré au téléchargement ne
  remontait pas au frontend (CORS WithExposedHeaders manquant +
  composants résumé ne lisaient pas Content-Disposition)
- Résumé mensuel: colonne Commentaire, sélecteur temporel unifié
  (auto-chargement), km selon précision
- Résumé annuel: bouton Export PDF repositionné, km selon précision
- Panneau totaux: renommage "Remboursement indicatif" + infobulles
  d'aide (icône) au lieu du texte permanent
- Ajout de PdfServiceIntegrationTests (3 tests), 65/65 tests backend OK
- Mise à jour requirements.md et design.md en conséquence
2026-09-28 08:58:23 +02:00

170 lines
6.4 KiB
C#

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;
/// <summary>
/// Tests d'intégration de <see cref="PdfService"/> (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 <see cref="IBANClient"/> est mocké (jamais appelé ici, aucune adresse de domicile
/// à géocoder dans les scénarios testés).
/// </summary>
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<AppDbContext>().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<IBANClient>();
var profilService = new ProfilService(
_context, banClientMock.Object, adresseCacheService, NullLogger<ProfilService>.Instance);
_pdfService = new PdfService(
resumeService,
profilService,
vehiculeService,
baremeService,
montantRegleService,
remboursementService,
precisionDistanceService,
_context);
}
/// <summary>Prépare un profil, un véhicule, un trajet et une tranche de barème de test.</summary>
private async Task<int> 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();
}
}