- 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
170 lines
6.4 KiB
C#
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();
|
|
}
|
|
}
|