Files
crm_ecosplay/tests/Service/Pdf/FacturePdfTest.php

344 lines
12 KiB
PHP
Raw Normal View History

test: ajout 163 tests unitaires (668->831) avec couverture 73% Entites (76 tests) : - PrestataireTest : constructeur, setters, getFullAddress, getTotalPaidHt - FacturePrestataireTest : constructeur, getPeriodLabel 12 mois, Vich upload - AdvertPaymentTest : constructeur, types constants, method - AdvertEventTest : constructeur, getTypeLabel, 5 types + fallback - FactureLineTest : constructeur, setters, optionnels nullable - ActionLogTest : constructeur, 10 action constants, severity - PaymentReminderTest : 8 steps, getStepLabel, getSeverity - DocusealEventTest : constructeur, nullable fields Commands (16 tests) : - ReminderFacturesPrestataireCommandTest : 6 scenarios (aucun presta, tous OK, factures manquantes, SIRET vide, mois different) - PaymentReminderCommandTest : 10 scenarios (skip recent, J+15 emails, suspension, termination, exception handling) Services PDF (24 tests) : - ComptaPdfTest : empty/FEC/multi-page, totaux Debit/Credit - RapportFinancierPdfTest : recettes/depenses, bilan equilibre/deficit/excedent - FacturePdfTest : lignes, TVA, customer address, paid badge, multi-page Controllers (47 tests) : - ComptabiliteControllerTest : 18 tests (index, 7 exports CSV, 2 JSON, 4 PDF, 2 rapport financier) - PrestatairesControllerTest : 19 tests (CRUD, factures, SIRET proxy) - FactureControllerTest : 6 tests (search, send) - FactureVerifyControllerTest : 4 tests (HMAC valid/invalid/not found) Couverture : 51%->60% classes, 58%->73% methodes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:57:42 +02:00
<?php
namespace App\Tests\Service\Pdf;
use App\Entity\Facture;
use App\Entity\FactureLine;
use App\Entity\OrderNumber;
use App\Service\Pdf\FacturePdf;
use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpKernel\KernelInterface;
class FacturePdfTest extends TestCase
{
private const HMAC_SECRET = 'test-hmac-secret';
private KernelInterface $kernel;
private string $projectDir;
protected function setUp(): void
{
$this->projectDir = sys_get_temp_dir().'/facture-pdf-test-'.bin2hex(random_bytes(4));
mkdir($this->projectDir.'/public', 0775, true);
$this->kernel = $this->createStub(KernelInterface::class);
$this->kernel->method('getProjectDir')->willReturn($this->projectDir);
}
protected function tearDown(): void
{
$this->removeDir($this->projectDir);
}
private function removeDir(string $dir): void
{
if (!is_dir($dir)) {
return;
}
foreach (scandir($dir) as $item) {
if ('.' === $item || '..' === $item) {
continue;
}
$path = $dir.'/'.$item;
is_dir($path) ? $this->removeDir($path) : unlink($path);
}
rmdir($dir);
}
private function makeFacture(string $numOrder = '04/2026-00001'): Facture
{
$orderNumber = new OrderNumber($numOrder);
return new Facture($orderNumber, self::HMAC_SECRET);
}
private function makeCustomer(bool $withRaisonSociale = false, bool $withAddress2 = false): \App\Entity\Customer
{
$customer = $this->createStub(\App\Entity\Customer::class);
$customer->method('getFullName')->willReturn('Jean Dupont');
$customer->method('getRaisonSociale')->willReturn($withRaisonSociale ? 'ACME SARL' : null);
$customer->method('getEmail')->willReturn('jean.dupont@example.com');
$customer->method('getAddress')->willReturn('42 rue des Tests');
$customer->method('getAddress2')->willReturn($withAddress2 ? 'Batiment B, etage 3' : null);
$customer->method('getZipCode')->willReturn('75001');
$customer->method('getCity')->willReturn('Paris');
return $customer;
}
public function testGenerateEmptyFactureProducesValidPdf(): void
{
$facture = $this->makeFacture();
$pdf = new FacturePdf($this->kernel, $facture);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
public function testGenerateWithLinesProducesValidPdf(): void
{
$facture = $this->makeFacture();
$facture->setTotalHt('100.00');
$facture->setTotalTva('0.00');
$facture->setTotalTtc('100.00');
$line1 = new FactureLine($facture, 'Hebergement Web', '60.00', 1);
$line1->setDescription('Hebergement annuel mutualisé');
$line2 = new FactureLine($facture, 'Nom de domaine', '40.00', 2);
$line2->setDescription('Renouvellement .fr annuel');
$facture->getLines()->add($line1);
$facture->getLines()->add($line2);
$pdf = new FacturePdf($this->kernel, $facture);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
$this->assertGreaterThan(1000, \strlen($output));
}
public function testGenerateWithCustomerAddressProducesValidPdf(): void
{
$facture = $this->makeFacture();
$facture->setCustomer($this->makeCustomer(false, false));
$facture->setTotalHt('50.00');
$facture->setTotalTva('0.00');
$facture->setTotalTtc('50.00');
$line = new FactureLine($facture, 'Service test', '50.00', 1);
$facture->getLines()->add($line);
$pdf = new FacturePdf($this->kernel, $facture);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
public function testGenerateWithTvaProducesValidPdf(): void
{
$facture = $this->makeFacture();
$facture->setTotalHt('100.00');
$facture->setTotalTva('20.00');
$facture->setTotalTtc('120.00');
$line = new FactureLine($facture, 'Prestation avec TVA', '100.00', 1);
$facture->getLines()->add($line);
$pdf = new FacturePdf($this->kernel, $facture);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
public function testGenerateWithPaidFactureProducesValidPdf(): void
{
$facture = $this->makeFacture();
$facture->setTotalHt('200.00');
$facture->setTotalTva('0.00');
$facture->setTotalTtc('200.00');
$facture->setIsPaid(true);
$facture->setPaidAt(new \DateTimeImmutable('2026-02-15'));
$facture->setPaidMethod('Virement');
$line = new FactureLine($facture, 'Service payé', '200.00', 1);
$facture->getLines()->add($line);
$pdf = new FacturePdf($this->kernel, $facture);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
public function testGenerateWithLogoFileProducesValidPdf(): void
{
// Minimal valid 1x1 JPEG
$jpegData = base64_decode('/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AJQAB/9k=');
file_put_contents($this->projectDir.'/public/logo.jpg', $jpegData);
$facture = $this->makeFacture();
$line = new FactureLine($facture, 'Test', '10.00', 1);
$facture->getLines()->add($line);
$facture->setTotalHt('10.00');
$facture->setTotalTva('0.00');
$facture->setTotalTtc('10.00');
$pdf = new FacturePdf($this->kernel, $facture);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
public function testGenerateWithManyLinesSpansMultiplePages(): void
{
$facture = $this->makeFacture();
$facture->setTotalHt('1500.00');
$facture->setTotalTva('0.00');
$facture->setTotalTtc('1500.00');
for ($i = 1; $i <= 15; ++$i) {
$line = new FactureLine($facture, 'Service '.$i, '100.00', $i);
$line->setDescription('Description detaillee pour le service numero '.$i.' avec quelques mots supplementaires.');
$facture->getLines()->add($line);
}
$pdf = new FacturePdf($this->kernel, $facture);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
public function testGenerateWithCustomerRaisonSocialeProducesValidPdf(): void
{
$facture = $this->makeFacture();
$facture->setCustomer($this->makeCustomer(true, true));
$pdf = new FacturePdf($this->kernel, $facture);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
public function testGenerateWithSplitIndexProducesValidPdf(): void
{
$facture = $this->makeFacture('04/2026-00002');
$facture->setSplitIndex(2);
$pdf = new FacturePdf($this->kernel, $facture);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
test: couverture 83% methodes (1046 tests, 2135 assertions) Entites completes a 100% : - AdvertTest : 12 nouveaux (state, customer, totals, hmac, lines, payments) - CustomerTest : 3 nouveaux (isPendingDelete, revendeurCode, updatedAt) - DevisTest : 6 nouveaux (customer, submissionId, lines, state constants) - FactureTest : 10 nouveaux (state, totals, isPaid, lines, hmac, splitIndex) - OrderNumberTest : 1 nouveau (markAsUnused) - WebsiteTest : 1 nouveau (revendeurCode) Services completes/ameliores : - DocuSealServiceTest : 30 nouveaux (sendDevis, resendDevis, download, compta) - AdvertServiceTest : 6 nouveaux (isTvaEnabled, getTvaRate, computeTotals) - DevisServiceTest : 6 nouveaux (idem) - FactureServiceTest : 8 nouveaux (idem + createPaidFactureFromAdvert) - MailerServiceTest : 7 nouveaux (unsubscribe headers, VCF, formatFileSize) - MeilisearchServiceTest : 42 nouveaux (index/remove/search tous types) - RgpdServiceTest : 6 nouveaux (sendVerificationCode, verifyCode) - OrderNumberServiceTest : 2 nouveaux (preview/generate unused) - TarificationServiceTest : 1 nouveau (stripe error logger) - ComptaPdfTest : 4 nouveaux (totaux, colonnes numeriques, signature) - FacturePdfTest : 6 nouveaux (QR code, RIB, CGV Twig, footer skip) Controllers ameliores : - ComptabiliteControllerTest : 13 nouveaux (JSON, PDF, sign, callback) - StatsControllerTest : 2 nouveaux (rich data, 6-month evolution) - SyncControllerTest : 13 nouveaux (sync 6 types + purge) - ClientsControllerTest : 7 nouveaux (show, delete, resendWelcome) - FactureControllerTest : 2 nouveaux (generatePdf 404, send success) - LegalControllerTest : 6 nouveaux (rgpdVerify GET/POST) - TarificationControllerTest : 3 nouveaux (purge paths) - AdminControllersTest : 9 nouveaux (dashboard search, services) - WebhookStripeControllerTest : 3 nouveaux (invalid signatures) - KeycloakAuthenticatorTest : 4 nouveaux (groups, domain check) Commands : - PaymentReminderCommandTest : 1 nouveau (formalNotice step) - TestMailCommandTest : 2 nouveaux (force-dsn success/failure) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 00:13:00 +02:00
public function testGenerateWithQrCodeProducesValidPdf(): void
{
$facture = $this->makeFacture('04/2026-00010');
$facture->setTotalHt('50.00');
$facture->setTotalTva('0.00');
$facture->setTotalTtc('50.00');
$urlGenerator = $this->createStub(\Symfony\Component\Routing\Generator\UrlGeneratorInterface::class);
$urlGenerator->method('generate')->willReturn('https://crm.e-cosplay.fr/facture/verify/1/abc123');
$line = new FactureLine($facture, 'Service QR', '50.00', 1);
$facture->getLines()->add($line);
$pdf = new FacturePdf($this->kernel, $facture, $urlGenerator);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
public function testGenerateWithRibFileProducesValidPdf(): void
{
// Create a minimal valid PDF as the RIB file
$ribDir = $this->projectDir . '/public';
if (!is_dir($ribDir)) {
mkdir($ribDir, 0775, true);
}
// Create a minimal PDF file for RIB using FPDI itself
$miniPdf = new \setasign\Fpdi\Fpdi();
$miniPdf->AddPage();
$ribPath = $ribDir . '/rib.pdf';
$miniPdf->Output('F', $ribPath);
$facture = $this->makeFacture('04/2026-00011');
$facture->setTotalHt('75.00');
$facture->setTotalTva('0.00');
$facture->setTotalTtc('75.00');
$line = new FactureLine($facture, 'Service RIB', '75.00', 1);
$facture->getLines()->add($line);
$pdf = new FacturePdf($this->kernel, $facture);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
public function testGenerateWithTwigAppendsCgvProducesValidPdf(): void
{
$facture = $this->makeFacture('04/2026-00012');
$facture->setTotalHt('80.00');
$facture->setTotalTva('0.00');
$facture->setTotalTtc('80.00');
$line = new FactureLine($facture, 'Service Twig', '80.00', 1);
$facture->getLines()->add($line);
// Create a mock Twig environment that returns minimal HTML for CGV
$twig = $this->createStub(\Twig\Environment::class);
$twig->method('render')->willReturn('<html><body><p>Conditions Generales de Vente</p></body></html>');
$pdf = new FacturePdf($this->kernel, $facture, null, $twig);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
public function testGenerateWithCustomerNoAddress(): void
{
$facture = $this->makeFacture('04/2026-00013');
$customer = $this->createStub(\App\Entity\Customer::class);
$customer->method('getFullName')->willReturn('Jean Dupont');
$customer->method('getRaisonSociale')->willReturn(null);
$customer->method('getEmail')->willReturn('jean@example.com');
$customer->method('getAddress')->willReturn(null); // No address
$customer->method('getAddress2')->willReturn(null);
$customer->method('getZipCode')->willReturn(null);
$customer->method('getCity')->willReturn(null);
$facture->setCustomer($customer);
$pdf = new FacturePdf($this->kernel, $facture);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
public function testFooterOnCgvPageSkipsFooter(): void
{
// Test that skipHeaderFooter=true makes Footer skip on pages after the last facture page
$facture = $this->makeFacture('04/2026-00014');
$facture->setTotalHt('30.00');
$facture->setTotalTva('0.00');
$facture->setTotalTtc('30.00');
$twig = $this->createStub(\Twig\Environment::class);
$twig->method('render')->willReturn('<html><body><p>CGV page 1</p></body></html>');
$pdf = new FacturePdf($this->kernel, $facture, null, $twig);
$pdf->generate();
$output = $pdf->Output('S');
$this->assertStringStartsWith('%PDF', $output);
}
test: ajout 163 tests unitaires (668->831) avec couverture 73% Entites (76 tests) : - PrestataireTest : constructeur, setters, getFullAddress, getTotalPaidHt - FacturePrestataireTest : constructeur, getPeriodLabel 12 mois, Vich upload - AdvertPaymentTest : constructeur, types constants, method - AdvertEventTest : constructeur, getTypeLabel, 5 types + fallback - FactureLineTest : constructeur, setters, optionnels nullable - ActionLogTest : constructeur, 10 action constants, severity - PaymentReminderTest : 8 steps, getStepLabel, getSeverity - DocusealEventTest : constructeur, nullable fields Commands (16 tests) : - ReminderFacturesPrestataireCommandTest : 6 scenarios (aucun presta, tous OK, factures manquantes, SIRET vide, mois different) - PaymentReminderCommandTest : 10 scenarios (skip recent, J+15 emails, suspension, termination, exception handling) Services PDF (24 tests) : - ComptaPdfTest : empty/FEC/multi-page, totaux Debit/Credit - RapportFinancierPdfTest : recettes/depenses, bilan equilibre/deficit/excedent - FacturePdfTest : lignes, TVA, customer address, paid badge, multi-page Controllers (47 tests) : - ComptabiliteControllerTest : 18 tests (index, 7 exports CSV, 2 JSON, 4 PDF, 2 rapport financier) - PrestatairesControllerTest : 19 tests (CRUD, factures, SIRET proxy) - FactureControllerTest : 6 tests (search, send) - FactureVerifyControllerTest : 4 tests (HMAC valid/invalid/not found) Couverture : 51%->60% classes, 58%->73% methodes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:57:42 +02:00
}