Files
crm_ecosplay/tests/Service/Pdf/FacturePdfTest.php
Serreau Jovann 8bda02888c 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

344 lines
12 KiB
PHP

<?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);
}
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);
}
}