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>
This commit is contained in:
328
tests/Command/PaymentReminderCommandTest.php
Normal file
328
tests/Command/PaymentReminderCommandTest.php
Normal file
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\PaymentReminderCommand;
|
||||
use App\Entity\Advert;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\OrderNumber;
|
||||
use App\Entity\PaymentReminder;
|
||||
use App\Service\ActionService;
|
||||
use App\Service\MailerService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Twig\Environment;
|
||||
|
||||
class PaymentReminderCommandTest extends TestCase
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
private MailerService $mailer;
|
||||
private Environment $twig;
|
||||
private LoggerInterface $logger;
|
||||
private ActionService $actionService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->em = $this->createMock(EntityManagerInterface::class);
|
||||
$this->mailer = $this->createMock(MailerService::class);
|
||||
$this->twig = $this->createStub(Environment::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->actionService = $this->createMock(ActionService::class);
|
||||
}
|
||||
|
||||
private function makeCommand(): PaymentReminderCommand
|
||||
{
|
||||
return new PaymentReminderCommand(
|
||||
$this->em,
|
||||
$this->mailer,
|
||||
$this->twig,
|
||||
$this->logger,
|
||||
$this->actionService,
|
||||
);
|
||||
}
|
||||
|
||||
private function makeAdvert(string $numOrder, ?string $email = 'client@example.com', int $daysAgo = 20): Advert
|
||||
{
|
||||
$orderNumber = new OrderNumber($numOrder);
|
||||
$customer = $this->createStub(Customer::class);
|
||||
$customer->method('getEmail')->willReturn($email);
|
||||
$customer->method('getFullName')->willReturn('Jean Dupont');
|
||||
|
||||
$advert = $this->createMock(Advert::class);
|
||||
$advert->method('getOrderNumber')->willReturn($orderNumber);
|
||||
$advert->method('getCustomer')->willReturn($customer);
|
||||
$advert->method('getState')->willReturn(Advert::STATE_SEND);
|
||||
|
||||
$updatedAt = new \DateTimeImmutable('-'.$daysAgo.' days');
|
||||
$advert->method('getUpdatedAt')->willReturn($updatedAt);
|
||||
$advert->method('getCreatedAt')->willReturn($updatedAt);
|
||||
|
||||
return $advert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub the PaymentReminder QueryBuilder so getNextStep returns a specific list of already-done steps.
|
||||
* Also stubs the Advert repository findBy to return the provided adverts.
|
||||
*
|
||||
* @param array<Advert> $adverts
|
||||
*/
|
||||
private function stubReminderRepo(array $existingSteps, array $adverts = []): void
|
||||
{
|
||||
$query = $this->createStub(Query::class);
|
||||
$query->method('getSingleColumnResult')->willReturn($existingSteps);
|
||||
|
||||
$qb = $this->createStub(QueryBuilder::class);
|
||||
$qb->method('select')->willReturnSelf();
|
||||
$qb->method('where')->willReturnSelf();
|
||||
$qb->method('setParameter')->willReturnSelf();
|
||||
$qb->method('getQuery')->willReturn($query);
|
||||
|
||||
$reminderRepo = $this->createStub(EntityRepository::class);
|
||||
$reminderRepo->method('createQueryBuilder')->willReturn($qb);
|
||||
|
||||
$advertRepo = $this->createStub(EntityRepository::class);
|
||||
$advertRepo->method('findBy')->willReturn($adverts);
|
||||
|
||||
$this->em->method('getRepository')->willReturnCallback(
|
||||
fn (string $class) => match ($class) {
|
||||
PaymentReminder::class => $reminderRepo,
|
||||
default => $advertRepo,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function testNoAdvertsReturnsSuccess(): void
|
||||
{
|
||||
$repo = $this->createStub(EntityRepository::class);
|
||||
$repo->method('findBy')->willReturn([]);
|
||||
|
||||
$this->em->method('getRepository')->with(Advert::class)->willReturn($repo);
|
||||
|
||||
$this->mailer->expects($this->never())->method('sendEmail');
|
||||
|
||||
$tester = new CommandTester($this->makeCommand());
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('Aucun avis de paiement en attente', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testAdvertWithNoCustomerIsSkipped(): void
|
||||
{
|
||||
$advert = $this->createStub(Advert::class);
|
||||
$advert->method('getCustomer')->willReturn(null);
|
||||
|
||||
$advertRepo = $this->createStub(EntityRepository::class);
|
||||
$advertRepo->method('findBy')->willReturn([$advert]);
|
||||
|
||||
$this->em->method('getRepository')->willReturn($advertRepo);
|
||||
|
||||
$this->mailer->expects($this->never())->method('sendEmail');
|
||||
|
||||
$tester = new CommandTester($this->makeCommand());
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('relance(s) envoyee(s)', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testAdvertWithNoEmailIsSkipped(): void
|
||||
{
|
||||
$advert = $this->makeAdvert('04/2026-00001', null, 20);
|
||||
|
||||
$advertRepo = $this->createStub(EntityRepository::class);
|
||||
$advertRepo->method('findBy')->willReturn([$advert]);
|
||||
|
||||
$this->em->method('getRepository')->willReturn($advertRepo);
|
||||
|
||||
$this->mailer->expects($this->never())->method('sendEmail');
|
||||
|
||||
$tester = new CommandTester($this->makeCommand());
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('relance(s) envoyee(s)', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testAdvertWithNoEligibleStepIsSkipped(): void
|
||||
{
|
||||
// Only 5 days old — no step requires less than 15 days
|
||||
$advert = $this->makeAdvert('04/2026-00001', 'client@example.com', 5);
|
||||
|
||||
$this->stubReminderRepo([], [$advert]);
|
||||
|
||||
$this->mailer->expects($this->never())->method('sendEmail');
|
||||
|
||||
$tester = new CommandTester($this->makeCommand());
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('0 relance(s) envoyee(s)', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testAdvertWithAllStepsDoneIsSkipped(): void
|
||||
{
|
||||
// 30 days old — many steps eligible, but all already done
|
||||
$advert = $this->makeAdvert('04/2026-00001', 'client@example.com', 30);
|
||||
|
||||
// All steps already done
|
||||
$allSteps = array_keys(PaymentReminder::STEPS_CONFIG);
|
||||
$this->stubReminderRepo($allSteps, [$advert]);
|
||||
|
||||
$this->mailer->expects($this->never())->method('sendEmail');
|
||||
|
||||
$tester = new CommandTester($this->makeCommand());
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('0 relance(s) envoyee(s)', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testSendsFirstReminderAt15Days(): void
|
||||
{
|
||||
// 16 days old, no steps done yet -> should trigger reminder_15
|
||||
$advert = $this->makeAdvert('04/2026-00001', 'client@example.com', 16);
|
||||
|
||||
$this->stubReminderRepo([], [$advert]);
|
||||
|
||||
$this->twig->method('render')->willReturn('<p>Email content</p>');
|
||||
|
||||
// Expect 2 emails: client + admin notification
|
||||
$this->mailer->expects($this->exactly(2))->method('sendEmail');
|
||||
|
||||
$this->em->method('persist');
|
||||
$this->em->method('flush');
|
||||
|
||||
$tester = new CommandTester($this->makeCommand());
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('1 relance(s) envoyee(s)', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testSuspensionStepCallsSuspendCustomer(): void
|
||||
{
|
||||
// 30 days old, all steps before suspension already done
|
||||
$advert = $this->makeAdvert('04/2026-00001', 'client@example.com', 30);
|
||||
|
||||
$customer = $advert->getCustomer();
|
||||
|
||||
// All steps before STEP_SUSPENSION_1 are done; days=30 >= 29 for suspension_1
|
||||
$doneSteps = [
|
||||
PaymentReminder::STEP_REMINDER_15,
|
||||
PaymentReminder::STEP_WARNING_10,
|
||||
PaymentReminder::STEP_SUSPENSION_WARNING_5,
|
||||
PaymentReminder::STEP_FINAL_REMINDER_3,
|
||||
];
|
||||
$this->stubReminderRepo($doneSteps, [$advert]);
|
||||
|
||||
$this->twig->method('render')->willReturn('<p>Email</p>');
|
||||
$this->mailer->method('sendEmail');
|
||||
$this->em->method('persist');
|
||||
$this->em->method('flush');
|
||||
|
||||
$this->actionService->expects($this->once())
|
||||
->method('suspendCustomer')
|
||||
->with($customer, $this->stringContains('Impaye'));
|
||||
|
||||
$tester = new CommandTester($this->makeCommand());
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('1 relance(s) envoyee(s)', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testTerminationWarningStepCallsDisableCustomer(): void
|
||||
{
|
||||
// 46 days old -> termination_warning step (>= 45 days)
|
||||
$advert = $this->makeAdvert('04/2026-00001', 'client@example.com', 46);
|
||||
|
||||
$customer = $advert->getCustomer();
|
||||
|
||||
// All earlier steps done
|
||||
$doneSteps = [
|
||||
PaymentReminder::STEP_REMINDER_15,
|
||||
PaymentReminder::STEP_WARNING_10,
|
||||
PaymentReminder::STEP_SUSPENSION_WARNING_5,
|
||||
PaymentReminder::STEP_FINAL_REMINDER_3,
|
||||
PaymentReminder::STEP_SUSPENSION_1,
|
||||
PaymentReminder::STEP_FORMAL_NOTICE,
|
||||
];
|
||||
$this->stubReminderRepo($doneSteps, [$advert]);
|
||||
|
||||
$this->twig->method('render')->willReturn('<p>Email</p>');
|
||||
$this->mailer->method('sendEmail');
|
||||
$this->em->method('persist');
|
||||
$this->em->method('flush');
|
||||
|
||||
$this->actionService->expects($this->once())
|
||||
->method('disableCustomer')
|
||||
->with($customer, $this->stringContains('Pre-resiliation'));
|
||||
|
||||
$tester = new CommandTester($this->makeCommand());
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
}
|
||||
|
||||
public function testTerminationStepCallsMarkForDeletion(): void
|
||||
{
|
||||
// 61 days old -> termination_30 step (>= 60 days)
|
||||
$advert = $this->makeAdvert('04/2026-00001', 'client@example.com', 61);
|
||||
|
||||
$customer = $advert->getCustomer();
|
||||
|
||||
// All earlier steps done
|
||||
$doneSteps = [
|
||||
PaymentReminder::STEP_REMINDER_15,
|
||||
PaymentReminder::STEP_WARNING_10,
|
||||
PaymentReminder::STEP_SUSPENSION_WARNING_5,
|
||||
PaymentReminder::STEP_FINAL_REMINDER_3,
|
||||
PaymentReminder::STEP_SUSPENSION_1,
|
||||
PaymentReminder::STEP_FORMAL_NOTICE,
|
||||
PaymentReminder::STEP_TERMINATION_WARNING,
|
||||
];
|
||||
$this->stubReminderRepo($doneSteps, [$advert]);
|
||||
|
||||
$this->twig->method('render')->willReturn('<p>Email</p>');
|
||||
$this->mailer->method('sendEmail');
|
||||
$this->em->method('persist');
|
||||
$this->em->expects($this->atLeastOnce())->method('flush');
|
||||
|
||||
$this->actionService->expects($this->once())
|
||||
->method('markForDeletion')
|
||||
->with($customer, $this->stringContains('Resiliation'));
|
||||
|
||||
// setState(STATE_CANCEL) should be called on the advert
|
||||
$advert->expects($this->once())->method('setState')->with(Advert::STATE_CANCEL);
|
||||
|
||||
$tester = new CommandTester($this->makeCommand());
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
}
|
||||
|
||||
public function testExceptionInStepIsLoggedAndContinues(): void
|
||||
{
|
||||
$advert = $this->makeAdvert('04/2026-00001', 'client@example.com', 20);
|
||||
|
||||
$this->stubReminderRepo([], [$advert]);
|
||||
|
||||
// Twig throws an exception when rendering
|
||||
$this->twig->method('render')->willThrowException(new \RuntimeException('Twig error'));
|
||||
|
||||
$this->logger->expects($this->once())->method('error');
|
||||
|
||||
$tester = new CommandTester($this->makeCommand());
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('0 relance(s) envoyee(s)', $tester->getDisplay());
|
||||
$this->assertStringContainsString('Erreur', $tester->getDisplay());
|
||||
}
|
||||
}
|
||||
199
tests/Command/ReminderFacturesPrestataireCommandTest.php
Normal file
199
tests/Command/ReminderFacturesPrestataireCommandTest.php
Normal file
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\ReminderFacturesPrestataireCommand;
|
||||
use App\Entity\FacturePrestataire;
|
||||
use App\Entity\Prestataire;
|
||||
use App\Service\MailerService;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
class ReminderFacturesPrestataireCommandTest extends TestCase
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
private MailerService $mailer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->em = $this->createMock(EntityManagerInterface::class);
|
||||
$this->mailer = $this->createMock(MailerService::class);
|
||||
}
|
||||
|
||||
private function makePrestataire(string $raisonSociale, ?string $siret = null): Prestataire
|
||||
{
|
||||
$p = new Prestataire($raisonSociale);
|
||||
if (null !== $siret) {
|
||||
$p->setSiret($siret);
|
||||
}
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
private function makeFacturePrestataire(int $year, int $month): FacturePrestataire
|
||||
{
|
||||
$f = $this->createStub(FacturePrestataire::class);
|
||||
$f->method('getYear')->willReturn($year);
|
||||
$f->method('getMonth')->willReturn($month);
|
||||
|
||||
return $f;
|
||||
}
|
||||
|
||||
private function stubRepository(array $prestataires): void
|
||||
{
|
||||
$repo = $this->createStub(EntityRepository::class);
|
||||
$repo->method('findBy')->willReturn($prestataires);
|
||||
|
||||
$this->em->method('getRepository')
|
||||
->with(Prestataire::class)
|
||||
->willReturn($repo);
|
||||
}
|
||||
|
||||
public function testNoActivePrestatairesReturnsSuccess(): void
|
||||
{
|
||||
$this->stubRepository([]);
|
||||
|
||||
$this->mailer->expects($this->never())->method('sendEmail');
|
||||
|
||||
$command = new ReminderFacturesPrestataireCommand($this->em, $this->mailer);
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('Toutes les factures prestataires', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testAllPrestatairesHaveFactureForPreviousMonth(): void
|
||||
{
|
||||
$now = new \DateTimeImmutable();
|
||||
$prev = $now->modify('first day of last month');
|
||||
$year = (int) $prev->format('Y');
|
||||
$month = (int) $prev->format('n');
|
||||
|
||||
$presta = $this->makePrestataire('ACME Corp', '12345678900011');
|
||||
$facture = $this->makeFacturePrestataire($year, $month);
|
||||
$presta->getFactures()->add($facture);
|
||||
|
||||
$this->stubRepository([$presta]);
|
||||
|
||||
$this->mailer->expects($this->never())->method('sendEmail');
|
||||
|
||||
$command = new ReminderFacturesPrestataireCommand($this->em, $this->mailer);
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('Toutes les factures prestataires', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testMissingFacturesSendsReminderEmail(): void
|
||||
{
|
||||
$presta1 = $this->makePrestataire('ACME Corp', '12345678900011');
|
||||
$presta2 = $this->makePrestataire('Beta SARL');
|
||||
|
||||
// presta1 has no factures for the previous month, presta2 has none at all
|
||||
$this->stubRepository([$presta1, $presta2]);
|
||||
|
||||
$this->mailer->expects($this->once())
|
||||
->method('getAdminEmail')
|
||||
->willReturn('admin@e-cosplay.fr');
|
||||
|
||||
$this->mailer->expects($this->once())
|
||||
->method('sendEmail')
|
||||
->with(
|
||||
'admin@e-cosplay.fr',
|
||||
$this->stringContains('Rappel : factures prestataires'),
|
||||
$this->stringContains('ACME Corp'),
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
|
||||
$command = new ReminderFacturesPrestataireCommand($this->em, $this->mailer);
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$display = $tester->getDisplay();
|
||||
$this->assertStringContainsString('2 facture(s) prestataire(s) manquante(s)', $display);
|
||||
$this->assertStringContainsString('rappel envoye', $display);
|
||||
}
|
||||
|
||||
public function testPrestataireWithFactureForDifferentMonthStillMissing(): void
|
||||
{
|
||||
$now = new \DateTimeImmutable();
|
||||
$prev = $now->modify('first day of last month');
|
||||
$year = (int) $prev->format('Y');
|
||||
$month = (int) $prev->format('n');
|
||||
|
||||
// Facture for a different month (two months ago)
|
||||
$wrongMonth = $month === 1 ? 12 : $month - 1;
|
||||
$wrongYear = $month === 1 ? $year - 1 : $year;
|
||||
|
||||
$presta = $this->makePrestataire('Old Corp', '99999999900011');
|
||||
$oldFacture = $this->makeFacturePrestataire($wrongYear, $wrongMonth);
|
||||
$presta->getFactures()->add($oldFacture);
|
||||
|
||||
$this->stubRepository([$presta]);
|
||||
|
||||
$this->mailer->method('getAdminEmail')->willReturn('admin@e-cosplay.fr');
|
||||
$this->mailer->expects($this->once())->method('sendEmail');
|
||||
|
||||
$command = new ReminderFacturesPrestataireCommand($this->em, $this->mailer);
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertStringContainsString('1 facture(s) prestataire(s) manquante(s)', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testEmailContainsPrestataireDetails(): void
|
||||
{
|
||||
$presta = $this->makePrestataire('Dupont & Cie', '11122233300011');
|
||||
|
||||
$this->stubRepository([$presta]);
|
||||
|
||||
$this->mailer->method('getAdminEmail')->willReturn('admin@e-cosplay.fr');
|
||||
|
||||
$capturedHtml = null;
|
||||
$this->mailer->method('sendEmail')
|
||||
->willReturnCallback(function (string $to, string $subject, string $html) use (&$capturedHtml) {
|
||||
$capturedHtml = $html;
|
||||
});
|
||||
|
||||
$command = new ReminderFacturesPrestataireCommand($this->em, $this->mailer);
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertNotNull($capturedHtml);
|
||||
$this->assertStringContainsString('Dupont & Cie', $capturedHtml);
|
||||
$this->assertStringContainsString('11122233300011', $capturedHtml);
|
||||
$this->assertStringContainsString('crm.e-cosplay.fr/admin/prestataires', $capturedHtml);
|
||||
}
|
||||
|
||||
public function testPrestataireWithoutSiretSendsEmailWithoutSiret(): void
|
||||
{
|
||||
$presta = $this->makePrestataire('Anonymous Corp');
|
||||
|
||||
$this->stubRepository([$presta]);
|
||||
|
||||
$this->mailer->method('getAdminEmail')->willReturn('admin@e-cosplay.fr');
|
||||
|
||||
$capturedHtml = null;
|
||||
$this->mailer->method('sendEmail')
|
||||
->willReturnCallback(function (string $to, string $subject, string $html) use (&$capturedHtml) {
|
||||
$capturedHtml = $html;
|
||||
});
|
||||
|
||||
$command = new ReminderFacturesPrestataireCommand($this->em, $this->mailer);
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([]);
|
||||
|
||||
$this->assertSame(0, $tester->getStatusCode());
|
||||
$this->assertNotNull($capturedHtml);
|
||||
$this->assertStringContainsString('Anonymous Corp', $capturedHtml);
|
||||
}
|
||||
}
|
||||
264
tests/Controller/Admin/ComptabiliteControllerTest.php
Normal file
264
tests/Controller/Admin/ComptabiliteControllerTest.php
Normal file
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Controller\Admin;
|
||||
|
||||
use App\Controller\Admin\ComptabiliteController;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
class ComptabiliteControllerTest extends TestCase
|
||||
{
|
||||
private function buildEmWithQueryBuilder(): EntityManagerInterface
|
||||
{
|
||||
$stubEm = $this->createStub(EntityManagerInterface::class);
|
||||
|
||||
$query = $this->getMockBuilder(Query::class)
|
||||
->setConstructorArgs([$stubEm])
|
||||
->onlyMethods(['getResult', '_doExecute', 'getSQL'])
|
||||
->getMock();
|
||||
$query->method('getResult')->willReturn([]);
|
||||
|
||||
$qb = $this->createStub(QueryBuilder::class);
|
||||
$qb->method('select')->willReturnSelf();
|
||||
$qb->method('from')->willReturnSelf();
|
||||
$qb->method('where')->willReturnSelf();
|
||||
$qb->method('andWhere')->willReturnSelf();
|
||||
$qb->method('setParameter')->willReturnSelf();
|
||||
$qb->method('orderBy')->willReturnSelf();
|
||||
$qb->method('getQuery')->willReturn($query);
|
||||
|
||||
$em = $this->createStub(EntityManagerInterface::class);
|
||||
$em->method('createQueryBuilder')->willReturn($qb);
|
||||
|
||||
return $em;
|
||||
}
|
||||
|
||||
private function buildKernel(): KernelInterface
|
||||
{
|
||||
$tmpDir = sys_get_temp_dir().'/comptabilite_test_'.uniqid();
|
||||
mkdir($tmpDir.'/public', 0777, true);
|
||||
// logo.jpg is optional — ComptaPdf checks file_exists before using it
|
||||
|
||||
$kernel = $this->createStub(KernelInterface::class);
|
||||
$kernel->method('getProjectDir')->willReturn($tmpDir);
|
||||
|
||||
return $kernel;
|
||||
}
|
||||
|
||||
private function buildController(): ComptabiliteController
|
||||
{
|
||||
$em = $this->buildEmWithQueryBuilder();
|
||||
$kernel = $this->buildKernel();
|
||||
|
||||
$controller = new ComptabiliteController($em, $kernel, false, 'http://docuseal.example');
|
||||
|
||||
$session = new Session(new MockArraySessionStorage());
|
||||
$stack = $this->createStub(RequestStack::class);
|
||||
$stack->method('getSession')->willReturn($session);
|
||||
|
||||
$twig = $this->createStub(Environment::class);
|
||||
$twig->method('render')->willReturn('<html></html>');
|
||||
|
||||
$container = $this->createStub(ContainerInterface::class);
|
||||
$container->method('has')->willReturnMap([
|
||||
['twig', true],
|
||||
['router', true],
|
||||
['security.authorization_checker', true],
|
||||
['security.token_storage', true],
|
||||
['request_stack', true],
|
||||
['parameter_bag', true],
|
||||
['serializer', false],
|
||||
]);
|
||||
$container->method('get')->willReturnMap([
|
||||
['twig', $twig],
|
||||
['router', $this->createStub(RouterInterface::class)],
|
||||
['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
|
||||
['security.token_storage', $this->createStub(TokenStorageInterface::class)],
|
||||
['request_stack', $stack],
|
||||
['parameter_bag', $this->createStub(ParameterBagInterface::class)],
|
||||
]);
|
||||
$controller->setContainer($container);
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
||||
public function testIndexReturns200(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$response = $controller->index();
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testExportJournalVentesCsv(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current', 'format' => 'csv']);
|
||||
$response = $controller->exportJournalVentes($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$contentType = $response->headers->get('Content-Type') ?? '';
|
||||
$this->assertStringContainsString('text/csv', $contentType);
|
||||
}
|
||||
|
||||
public function testExportJournalVentesJson(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current', 'format' => 'json']);
|
||||
$response = $controller->exportJournalVentes($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$contentType = $response->headers->get('Content-Type') ?? '';
|
||||
$this->assertStringContainsString('application/json', $contentType);
|
||||
}
|
||||
|
||||
public function testExportJournalVentesPreviousPeriod(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'previous', 'format' => 'csv']);
|
||||
$response = $controller->exportJournalVentes($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testExportJournalVentesCustomPeriod(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'custom', 'from' => '2026-01-01', 'to' => '2026-03-31', 'format' => 'csv']);
|
||||
$response = $controller->exportJournalVentes($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testExportCommissionsStripeCsv(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current', 'format' => 'csv']);
|
||||
$response = $controller->exportCommissionsStripe($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$contentType = $response->headers->get('Content-Type') ?? '';
|
||||
$this->assertStringContainsString('text/csv', $contentType);
|
||||
}
|
||||
|
||||
public function testExportCommissionsStripeJson(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current', 'format' => 'json']);
|
||||
$response = $controller->exportCommissionsStripe($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$contentType = $response->headers->get('Content-Type') ?? '';
|
||||
$this->assertStringContainsString('application/json', $contentType);
|
||||
}
|
||||
|
||||
public function testExportCoutsServicesCsv(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current', 'format' => 'csv']);
|
||||
$response = $controller->exportCoutsServices($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$contentType = $response->headers->get('Content-Type') ?? '';
|
||||
$this->assertStringContainsString('text/csv', $contentType);
|
||||
}
|
||||
|
||||
public function testExportPdfJournalVentes(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current']);
|
||||
$response = $controller->exportPdf('journal-ventes', $request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('application/pdf', $response->headers->get('Content-Type'));
|
||||
}
|
||||
|
||||
public function testExportPdfCommissionsStripe(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current']);
|
||||
$response = $controller->exportPdf('commissions-stripe', $request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('application/pdf', $response->headers->get('Content-Type'));
|
||||
}
|
||||
|
||||
public function testExportPdfCoutsServices(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current']);
|
||||
$response = $controller->exportPdf('couts-services', $request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('application/pdf', $response->headers->get('Content-Type'));
|
||||
}
|
||||
|
||||
public function testExportPdfBalanceAgee(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current']);
|
||||
$response = $controller->exportPdf('balance-agee', $request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('application/pdf', $response->headers->get('Content-Type'));
|
||||
}
|
||||
|
||||
public function testRapportFinancierReturnsPdf(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current']);
|
||||
$response = $controller->rapportFinancier($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('application/pdf', $response->headers->get('Content-Type'));
|
||||
}
|
||||
|
||||
public function testRapportFinancierPreviousPeriod(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'previous']);
|
||||
$response = $controller->rapportFinancier($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('application/pdf', $response->headers->get('Content-Type'));
|
||||
}
|
||||
|
||||
public function testExportGrandLivreCsv(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current', 'format' => 'csv']);
|
||||
$response = $controller->exportGrandLivre($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$contentType = $response->headers->get('Content-Type') ?? '';
|
||||
$this->assertStringContainsString('text/csv', $contentType);
|
||||
}
|
||||
|
||||
public function testExportFecCsv(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current', 'format' => 'csv']);
|
||||
$response = $controller->exportFec($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$contentType = $response->headers->get('Content-Type') ?? '';
|
||||
$this->assertStringContainsString('text/csv', $contentType);
|
||||
}
|
||||
|
||||
public function testExportBalanceAgeeCsv(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['format' => 'csv']);
|
||||
$response = $controller->exportBalanceAgee($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$contentType = $response->headers->get('Content-Type') ?? '';
|
||||
$this->assertStringContainsString('text/csv', $contentType);
|
||||
}
|
||||
|
||||
public function testExportReglementsCsv(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['period' => 'current', 'format' => 'csv']);
|
||||
$response = $controller->exportReglements($request);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$contentType = $response->headers->get('Content-Type') ?? '';
|
||||
$this->assertStringContainsString('text/csv', $contentType);
|
||||
}
|
||||
}
|
||||
190
tests/Controller/Admin/FactureControllerTest.php
Normal file
190
tests/Controller/Admin/FactureControllerTest.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Controller\Admin;
|
||||
|
||||
use App\Controller\Admin\FactureController;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Facture;
|
||||
use App\Service\MeilisearchService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
class FactureControllerTest extends TestCase
|
||||
{
|
||||
private function buildController(?EntityManagerInterface $em = null, ?MeilisearchService $meilisearch = null): FactureController
|
||||
{
|
||||
$em ??= $this->createStub(EntityManagerInterface::class);
|
||||
$meilisearch ??= $this->createStub(MeilisearchService::class);
|
||||
|
||||
$controller = new FactureController($em, $meilisearch);
|
||||
|
||||
$session = new Session(new MockArraySessionStorage());
|
||||
$stack = $this->createStub(RequestStack::class);
|
||||
$stack->method('getSession')->willReturn($session);
|
||||
|
||||
$twig = $this->createStub(Environment::class);
|
||||
$twig->method('render')->willReturn('<html></html>');
|
||||
|
||||
$router = $this->createStub(RouterInterface::class);
|
||||
$router->method('generate')->willReturn('/redirect');
|
||||
|
||||
$container = $this->createStub(ContainerInterface::class);
|
||||
$container->method('has')->willReturnMap([
|
||||
['twig', true],
|
||||
['router', true],
|
||||
['security.authorization_checker', true],
|
||||
['security.token_storage', true],
|
||||
['request_stack', true],
|
||||
['parameter_bag', true],
|
||||
['serializer', false],
|
||||
]);
|
||||
$container->method('get')->willReturnMap([
|
||||
['twig', $twig],
|
||||
['router', $router],
|
||||
['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
|
||||
['security.token_storage', $this->createStub(TokenStorageInterface::class)],
|
||||
['request_stack', $stack],
|
||||
['parameter_bag', $this->createStub(ParameterBagInterface::class)],
|
||||
]);
|
||||
$controller->setContainer($container);
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// search
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testSearchReturnsEmptyWhenQueryBlank(): void
|
||||
{
|
||||
$controller = $this->buildController();
|
||||
$request = new Request(['q' => '']);
|
||||
$response = $controller->search(1, $request);
|
||||
|
||||
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('[]', $response->getContent());
|
||||
}
|
||||
|
||||
public function testSearchReturnsMeilisearchResults(): void
|
||||
{
|
||||
$hits = [['id' => 1, 'invoiceNumber' => 'F-2026-001']];
|
||||
|
||||
$meilisearch = $this->createStub(MeilisearchService::class);
|
||||
$meilisearch->method('searchFactures')->willReturn($hits);
|
||||
|
||||
$controller = $this->buildController(null, $meilisearch);
|
||||
$request = new Request(['q' => 'F-2026']);
|
||||
$response = $controller->search(1, $request);
|
||||
|
||||
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
|
||||
$data = json_decode($response->getContent(), true);
|
||||
$this->assertCount(1, $data);
|
||||
$this->assertSame('F-2026-001', $data[0]['invoiceNumber']);
|
||||
}
|
||||
|
||||
public function testSearchPassesCustomerIdFilter(): void
|
||||
{
|
||||
$meilisearch = $this->createMock(MeilisearchService::class);
|
||||
$meilisearch->expects($this->once())
|
||||
->method('searchFactures')
|
||||
->with('test', 20, 42)
|
||||
->willReturn([]);
|
||||
|
||||
$controller = $this->buildController(null, $meilisearch);
|
||||
$request = new Request(['q' => 'test']);
|
||||
$response = $controller->search(42, $request);
|
||||
|
||||
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// send — facture with no PDF
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testSendRedirectsWhenNoPdfGenerated(): void
|
||||
{
|
||||
$customer = $this->createStub(Customer::class);
|
||||
$customer->method('getId')->willReturn(5);
|
||||
|
||||
$facture = $this->createStub(Facture::class);
|
||||
$facture->method('getFacturePdf')->willReturn(null);
|
||||
$facture->method('getCustomer')->willReturn($customer);
|
||||
|
||||
$factureRepo = $this->createStub(EntityRepository::class);
|
||||
$factureRepo->method('find')->willReturn($facture);
|
||||
|
||||
$em = $this->createStub(EntityManagerInterface::class);
|
||||
$em->method('getRepository')->willReturn($factureRepo);
|
||||
|
||||
$controller = $this->buildController($em);
|
||||
|
||||
$mailer = $this->createStub(\App\Service\MailerService::class);
|
||||
$twig = $this->createStub(Environment::class);
|
||||
$urlGenerator = $this->createStub(\Symfony\Component\Routing\Generator\UrlGeneratorInterface::class);
|
||||
$urlGenerator->method('generate')->willReturn('http://localhost/facture/verify/1/abc');
|
||||
|
||||
$response = $controller->send(1, $mailer, $twig, $urlGenerator, '/tmp');
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testSendThrows404WhenFactureNotFound(): void
|
||||
{
|
||||
$factureRepo = $this->createStub(EntityRepository::class);
|
||||
$factureRepo->method('find')->willReturn(null);
|
||||
|
||||
$em = $this->createStub(EntityManagerInterface::class);
|
||||
$em->method('getRepository')->willReturn($factureRepo);
|
||||
|
||||
$controller = $this->buildController($em);
|
||||
|
||||
$mailer = $this->createStub(\App\Service\MailerService::class);
|
||||
$twig = $this->createStub(Environment::class);
|
||||
$urlGenerator = $this->createStub(\Symfony\Component\Routing\Generator\UrlGeneratorInterface::class);
|
||||
|
||||
$this->expectException(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class);
|
||||
$controller->send(999, $mailer, $twig, $urlGenerator, '/tmp');
|
||||
}
|
||||
|
||||
public function testSendRedirectsWhenCustomerHasNoEmail(): void
|
||||
{
|
||||
$customer = $this->createStub(Customer::class);
|
||||
$customer->method('getId')->willReturn(5);
|
||||
$customer->method('getEmail')->willReturn(null);
|
||||
|
||||
$facture = $this->createStub(Facture::class);
|
||||
$facture->method('getFacturePdf')->willReturn('facture.pdf');
|
||||
$facture->method('getCustomer')->willReturn($customer);
|
||||
|
||||
$factureRepo = $this->createStub(EntityRepository::class);
|
||||
$factureRepo->method('find')->willReturn($facture);
|
||||
|
||||
$em = $this->createStub(EntityManagerInterface::class);
|
||||
$em->method('getRepository')->willReturn($factureRepo);
|
||||
|
||||
$controller = $this->buildController($em);
|
||||
|
||||
$mailer = $this->createStub(\App\Service\MailerService::class);
|
||||
$twig = $this->createStub(Environment::class);
|
||||
$urlGenerator = $this->createStub(\Symfony\Component\Routing\Generator\UrlGeneratorInterface::class);
|
||||
|
||||
$response = $controller->send(1, $mailer, $twig, $urlGenerator, '/tmp');
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
425
tests/Controller/Admin/PrestatairesControllerTest.php
Normal file
425
tests/Controller/Admin/PrestatairesControllerTest.php
Normal file
@@ -0,0 +1,425 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Controller\Admin;
|
||||
|
||||
use App\Controller\Admin\PrestatairesController;
|
||||
use App\Entity\FacturePrestataire;
|
||||
use App\Entity\Prestataire;
|
||||
use App\Repository\PrestataireRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface as HttpResponseInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
class PrestatairesControllerTest extends TestCase
|
||||
{
|
||||
private function buildController(?EntityManagerInterface $em = null): PrestatairesController
|
||||
{
|
||||
$em ??= $this->createStub(EntityManagerInterface::class);
|
||||
|
||||
$controller = new PrestatairesController($em);
|
||||
|
||||
$session = new Session(new MockArraySessionStorage());
|
||||
$stack = $this->createStub(RequestStack::class);
|
||||
$stack->method('getSession')->willReturn($session);
|
||||
|
||||
$twig = $this->createStub(Environment::class);
|
||||
$twig->method('render')->willReturn('<html></html>');
|
||||
|
||||
$router = $this->createStub(RouterInterface::class);
|
||||
$router->method('generate')->willReturn('/redirect');
|
||||
|
||||
$container = $this->createStub(ContainerInterface::class);
|
||||
$container->method('has')->willReturnMap([
|
||||
['twig', true],
|
||||
['router', true],
|
||||
['security.authorization_checker', true],
|
||||
['security.token_storage', true],
|
||||
['request_stack', true],
|
||||
['parameter_bag', true],
|
||||
['serializer', false],
|
||||
]);
|
||||
$container->method('get')->willReturnMap([
|
||||
['twig', $twig],
|
||||
['router', $router],
|
||||
['security.authorization_checker', $this->createStub(AuthorizationCheckerInterface::class)],
|
||||
['security.token_storage', $this->createStub(TokenStorageInterface::class)],
|
||||
['request_stack', $stack],
|
||||
['parameter_bag', $this->createStub(ParameterBagInterface::class)],
|
||||
]);
|
||||
$controller->setContainer($container);
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
||||
private function buildPrestataire(int $id = 1, string $raisonSociale = 'ACME SA'): Prestataire
|
||||
{
|
||||
$prestataire = new Prestataire($raisonSociale);
|
||||
// Force a non-null id via Reflection
|
||||
$ref = new \ReflectionProperty(Prestataire::class, 'id');
|
||||
$ref->setAccessible(true);
|
||||
$ref->setValue($prestataire, $id);
|
||||
|
||||
return $prestataire;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// index
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testIndexReturns200(): void
|
||||
{
|
||||
$repo = $this->createStub(PrestataireRepository::class);
|
||||
$repo->method('findBy')->willReturn([]);
|
||||
|
||||
$controller = $this->buildController();
|
||||
$response = $controller->index($repo);
|
||||
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testIndexWithPrestataires(): void
|
||||
{
|
||||
$repo = $this->createStub(PrestataireRepository::class);
|
||||
$repo->method('findBy')->willReturn([
|
||||
$this->buildPrestataire(1, 'ACME SA'),
|
||||
$this->buildPrestataire(2, 'Example SAS'),
|
||||
]);
|
||||
|
||||
$controller = $this->buildController();
|
||||
$response = $controller->index($repo);
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// create
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testCreateRedirectsOnSuccess(): void
|
||||
{
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->expects($this->once())->method('persist');
|
||||
$em->expects($this->once())->method('flush');
|
||||
|
||||
$controller = $this->buildController($em);
|
||||
|
||||
$request = new Request([], ['raisonSociale' => 'Nouveau Prestataire', 'email' => 'test@example.com']);
|
||||
$response = $controller->create($request);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testCreateRedirectsWhenRaisonSocialeEmpty(): void
|
||||
{
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->expects($this->never())->method('persist');
|
||||
|
||||
$controller = $this->buildController($em);
|
||||
|
||||
$request = new Request([], ['raisonSociale' => ' ']);
|
||||
$response = $controller->create($request);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// show
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testShowReturns200(): void
|
||||
{
|
||||
$prestataire = $this->buildPrestataire();
|
||||
$controller = $this->buildController();
|
||||
$response = $controller->show($prestataire);
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// edit
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testEditFlushesAndRedirects(): void
|
||||
{
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->expects($this->once())->method('flush');
|
||||
|
||||
$prestataire = $this->buildPrestataire();
|
||||
$controller = $this->buildController($em);
|
||||
|
||||
$request = new Request([], [
|
||||
'raisonSociale' => 'ACME Modifie',
|
||||
'email' => 'contact@acme.fr',
|
||||
'phone' => '0600000000',
|
||||
'siret' => '12345678901234',
|
||||
'address' => '1 rue de la Paix',
|
||||
'zipCode' => '75001',
|
||||
'city' => 'Paris',
|
||||
]);
|
||||
$response = $controller->edit($prestataire, $request);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testEditKeepsRaisonSocialeWhenEmpty(): void
|
||||
{
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->expects($this->once())->method('flush');
|
||||
|
||||
$prestataire = $this->buildPrestataire(1, 'Nom Original');
|
||||
$controller = $this->buildController($em);
|
||||
|
||||
// Empty raisonSociale should keep the original
|
||||
$request = new Request([], ['raisonSociale' => '']);
|
||||
$controller->edit($prestataire, $request);
|
||||
|
||||
$this->assertSame('Nom Original', $prestataire->getRaisonSociale());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// delete
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testDeleteRemovesAndRedirects(): void
|
||||
{
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->expects($this->once())->method('remove');
|
||||
$em->expects($this->once())->method('flush');
|
||||
|
||||
$prestataire = $this->buildPrestataire();
|
||||
$controller = $this->buildController($em);
|
||||
|
||||
$response = $controller->delete($prestataire);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// addFacture
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testAddFactureRedirectsOnInvalidData(): void
|
||||
{
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->expects($this->never())->method('persist');
|
||||
|
||||
$prestataire = $this->buildPrestataire();
|
||||
$controller = $this->buildController($em);
|
||||
|
||||
// Missing numFacture
|
||||
$request = new Request([], ['numFacture' => '', 'year' => 2026, 'month' => 3]);
|
||||
$response = $controller->addFacture($prestataire, $request);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testAddFactureRedirectsOnInvalidYear(): void
|
||||
{
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->expects($this->never())->method('persist');
|
||||
|
||||
$prestataire = $this->buildPrestataire();
|
||||
$controller = $this->buildController($em);
|
||||
|
||||
$request = new Request([], ['numFacture' => 'FAC001', 'year' => 2010, 'month' => 3, 'montantHt' => '100', 'montantTtc' => '120']);
|
||||
$response = $controller->addFacture($prestataire, $request);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testAddFacturePersistsAndRedirects(): void
|
||||
{
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->expects($this->once())->method('persist');
|
||||
$em->expects($this->once())->method('flush');
|
||||
|
||||
$prestataire = $this->buildPrestataire();
|
||||
$controller = $this->buildController($em);
|
||||
|
||||
$request = new Request([], [
|
||||
'numFacture' => 'FAC-2026-001',
|
||||
'year' => 2026,
|
||||
'month' => 3,
|
||||
'montantHt' => '500.00',
|
||||
'montantTtc' => '600.00',
|
||||
]);
|
||||
$response = $controller->addFacture($prestataire, $request);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// markPaid
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testMarkPaidFlushesAndRedirects(): void
|
||||
{
|
||||
$prestataire = $this->buildPrestataire(1);
|
||||
|
||||
$facture = $this->createMock(FacturePrestataire::class);
|
||||
$facture->method('getPrestataire')->willReturn($prestataire);
|
||||
$facture->method('getNumFacture')->willReturn('FAC-001');
|
||||
|
||||
$factureRepo = $this->createMock(EntityRepository::class);
|
||||
$factureRepo->method('find')->with(42)->willReturn($facture);
|
||||
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->method('getRepository')->with(FacturePrestataire::class)->willReturn($factureRepo);
|
||||
$em->expects($this->once())->method('flush');
|
||||
|
||||
$controller = $this->buildController($em);
|
||||
$response = $controller->markPaid($prestataire, 42);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testMarkPaidIgnoresMismatchedPrestataire(): void
|
||||
{
|
||||
$prestataire = $this->buildPrestataire(1);
|
||||
$otherPrestataire = $this->buildPrestataire(2, 'Other');
|
||||
|
||||
$facture = $this->createMock(FacturePrestataire::class);
|
||||
$facture->method('getPrestataire')->willReturn($otherPrestataire);
|
||||
|
||||
$factureRepo = $this->createStub(EntityRepository::class);
|
||||
$factureRepo->method('find')->willReturn($facture);
|
||||
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->method('getRepository')->willReturn($factureRepo);
|
||||
$em->expects($this->never())->method('flush');
|
||||
|
||||
$controller = $this->buildController($em);
|
||||
$response = $controller->markPaid($prestataire, 99);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testMarkPaidWhenFactureNotFound(): void
|
||||
{
|
||||
$prestataire = $this->buildPrestataire(1);
|
||||
|
||||
$factureRepo = $this->createStub(EntityRepository::class);
|
||||
$factureRepo->method('find')->willReturn(null);
|
||||
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->method('getRepository')->willReturn($factureRepo);
|
||||
$em->expects($this->never())->method('flush');
|
||||
|
||||
$controller = $this->buildController($em);
|
||||
$response = $controller->markPaid($prestataire, 999);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// deleteFacture
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testDeleteFactureRemovesAndRedirects(): void
|
||||
{
|
||||
$prestataire = $this->buildPrestataire(1);
|
||||
|
||||
$facture = $this->createMock(FacturePrestataire::class);
|
||||
$facture->method('getPrestataire')->willReturn($prestataire);
|
||||
|
||||
$factureRepo = $this->createStub(EntityRepository::class);
|
||||
$factureRepo->method('find')->willReturn($facture);
|
||||
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->method('getRepository')->willReturn($factureRepo);
|
||||
$em->expects($this->once())->method('remove');
|
||||
$em->expects($this->once())->method('flush');
|
||||
|
||||
$controller = $this->buildController($em);
|
||||
$response = $controller->deleteFacture($prestataire, 42);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testDeleteFactureWhenNotFound(): void
|
||||
{
|
||||
$prestataire = $this->buildPrestataire(1);
|
||||
|
||||
$factureRepo = $this->createStub(EntityRepository::class);
|
||||
$factureRepo->method('find')->willReturn(null);
|
||||
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->method('getRepository')->willReturn($factureRepo);
|
||||
$em->expects($this->never())->method('remove');
|
||||
|
||||
$controller = $this->buildController($em);
|
||||
$response = $controller->deleteFacture($prestataire, 999);
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// entrepriseSearch
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testEntrepriseSearchReturnsEmptyWhenQueryTooShort(): void
|
||||
{
|
||||
$httpClient = $this->createStub(HttpClientInterface::class);
|
||||
$controller = $this->buildController();
|
||||
|
||||
$request = new Request(['q' => 'a']);
|
||||
$response = $controller->entrepriseSearch($request, $httpClient);
|
||||
|
||||
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
|
||||
$data = json_decode($response->getContent(), true);
|
||||
$this->assertSame([], $data['results']);
|
||||
$this->assertSame(0, $data['total_results']);
|
||||
}
|
||||
|
||||
public function testEntrepriseSearchForwardsApiResponse(): void
|
||||
{
|
||||
$apiData = ['results' => [['nom_complet' => 'ACME SA']], 'total_results' => 1];
|
||||
|
||||
$httpResponse = $this->createStub(HttpResponseInterface::class);
|
||||
$httpResponse->method('toArray')->willReturn($apiData);
|
||||
|
||||
$httpClient = $this->createStub(HttpClientInterface::class);
|
||||
$httpClient->method('request')->willReturn($httpResponse);
|
||||
|
||||
$controller = $this->buildController();
|
||||
|
||||
$request = new Request(['q' => 'ACME']);
|
||||
$response = $controller->entrepriseSearch($request, $httpClient);
|
||||
|
||||
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getContent(), true);
|
||||
$this->assertSame(1, $data['total_results']);
|
||||
}
|
||||
|
||||
public function testEntrepriseSearchHandlesHttpError(): void
|
||||
{
|
||||
$httpClient = $this->createStub(HttpClientInterface::class);
|
||||
$httpClient->method('request')->willThrowException(new \RuntimeException('Network error'));
|
||||
|
||||
$controller = $this->buildController();
|
||||
|
||||
$request = new Request(['q' => 'ACME']);
|
||||
$response = $controller->entrepriseSearch($request, $httpClient);
|
||||
|
||||
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||
$this->assertSame(502, $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
138
tests/Controller/FactureVerifyControllerTest.php
Normal file
138
tests/Controller/FactureVerifyControllerTest.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Controller\FactureVerifyController;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Facture;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Twig\Environment;
|
||||
|
||||
class FactureVerifyControllerTest extends TestCase
|
||||
{
|
||||
private function buildController(?Environment $twig = null): FactureVerifyController
|
||||
{
|
||||
$twig ??= $this->createStub(Environment::class);
|
||||
$twig->method('render')->willReturn('<html></html>');
|
||||
|
||||
$container = $this->createStub(ContainerInterface::class);
|
||||
$container->method('has')->willReturnMap([
|
||||
['twig', true],
|
||||
['parameter_bag', true],
|
||||
]);
|
||||
$container->method('get')->willReturnMap([
|
||||
['twig', $twig],
|
||||
['parameter_bag', $this->createStub(ParameterBagInterface::class)],
|
||||
]);
|
||||
|
||||
$controller = new FactureVerifyController();
|
||||
$controller->setContainer($container);
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
||||
private function buildEmWithFacture(?Facture $facture): EntityManagerInterface
|
||||
{
|
||||
$factureRepo = $this->createStub(EntityRepository::class);
|
||||
$factureRepo->method('find')->willReturn($facture);
|
||||
|
||||
$em = $this->createStub(EntityManagerInterface::class);
|
||||
$em->method('getRepository')->willReturn($factureRepo);
|
||||
|
||||
return $em;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Valid HMAC — facture found with correct hmac
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testIndexReturnsVerifyPageWhenHmacMatches(): void
|
||||
{
|
||||
$customer = $this->createStub(Customer::class);
|
||||
|
||||
$facture = $this->createStub(Facture::class);
|
||||
$facture->method('getHmac')->willReturn('abc123');
|
||||
$facture->method('getCustomer')->willReturn($customer);
|
||||
|
||||
$em = $this->buildEmWithFacture($facture);
|
||||
$controller = $this->buildController();
|
||||
|
||||
$response = $controller->index(1, 'abc123', $em);
|
||||
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Invalid HMAC — facture found but wrong hmac
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testIndexReturnsInvalidPageWhenHmacMismatch(): void
|
||||
{
|
||||
$facture = $this->createStub(Facture::class);
|
||||
$facture->method('getHmac')->willReturn('correct_hmac');
|
||||
|
||||
$em = $this->buildEmWithFacture($facture);
|
||||
|
||||
$twig = $this->createStub(Environment::class);
|
||||
$twig->method('render')->willReturn('<html>Invalid</html>');
|
||||
|
||||
$controller = $this->buildController($twig);
|
||||
|
||||
$response = $controller->index(1, 'wrong_hmac', $em);
|
||||
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Facture not found
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testIndexReturnsInvalidPageWhenFactureNotFound(): void
|
||||
{
|
||||
$em = $this->buildEmWithFacture(null);
|
||||
$controller = $this->buildController();
|
||||
|
||||
$response = $controller->index(999, 'any_hmac', $em);
|
||||
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// HMAC timing-safe comparison (hash_equals)
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
public function testIndexUsesHashEqualsForComparison(): void
|
||||
{
|
||||
// Craft two hmac values that differ only in length to ensure no
|
||||
// trivial == comparison is used (hash_equals handles timing safety)
|
||||
$storedHmac = hash_hmac('sha256', 'data', 'secret');
|
||||
$differentHmac = hash_hmac('sha256', 'other', 'secret');
|
||||
|
||||
$facture = $this->createStub(Facture::class);
|
||||
$facture->method('getHmac')->willReturn($storedHmac);
|
||||
|
||||
$em = $this->buildEmWithFacture($facture);
|
||||
$controller = $this->buildController();
|
||||
|
||||
// Wrong hmac -> invalid page (still 200 but renders invalid template)
|
||||
$response = $controller->index(1, $differentHmac, $em);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
|
||||
// Correct hmac -> verify page
|
||||
$facture2 = $this->createStub(Facture::class);
|
||||
$facture2->method('getHmac')->willReturn($storedHmac);
|
||||
$facture2->method('getCustomer')->willReturn(null);
|
||||
|
||||
$em2 = $this->buildEmWithFacture($facture2);
|
||||
$response2 = $controller->index(1, $storedHmac, $em2);
|
||||
$this->assertSame(200, $response2->getStatusCode());
|
||||
}
|
||||
}
|
||||
156
tests/Entity/ActionLogTest.php
Normal file
156
tests/Entity/ActionLogTest.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\ActionLog;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\User;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ActionLogTest extends TestCase
|
||||
{
|
||||
private function createCustomer(): Customer
|
||||
{
|
||||
$user = new User();
|
||||
$user->setEmail('test@test.com');
|
||||
$user->setFirstName('John');
|
||||
$user->setLastName('Doe');
|
||||
$user->setPassword('hashed');
|
||||
|
||||
return new Customer($user);
|
||||
}
|
||||
|
||||
public function testConstructorDefaults(): void
|
||||
{
|
||||
$log = new ActionLog(ActionLog::ACTION_SUSPEND_CUSTOMER, 'Client suspendu.');
|
||||
|
||||
$this->assertNull($log->getId());
|
||||
$this->assertSame(ActionLog::ACTION_SUSPEND_CUSTOMER, $log->getAction());
|
||||
$this->assertSame('Client suspendu.', $log->getMessage());
|
||||
$this->assertSame('info', $log->getSeverity());
|
||||
$this->assertTrue($log->isSuccess());
|
||||
$this->assertNull($log->getCustomer());
|
||||
$this->assertNull($log->getEntityId());
|
||||
$this->assertNull($log->getEntityType());
|
||||
$this->assertNull($log->getContext());
|
||||
$this->assertNull($log->getPreviousState());
|
||||
$this->assertNull($log->getNewState());
|
||||
$this->assertNull($log->getErrorMessage());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $log->getCreatedAt());
|
||||
}
|
||||
|
||||
public function testConstructorWithAllArgs(): void
|
||||
{
|
||||
$log = new ActionLog(ActionLog::ACTION_FORMAL_NOTICE, 'Mise en demeure envoyée.', 'critical', false);
|
||||
|
||||
$this->assertSame('critical', $log->getSeverity());
|
||||
$this->assertFalse($log->isSuccess());
|
||||
}
|
||||
|
||||
public function testCustomer(): void
|
||||
{
|
||||
$log = new ActionLog(ActionLog::ACTION_SUSPEND_CUSTOMER, 'Test');
|
||||
$this->assertNull($log->getCustomer());
|
||||
|
||||
$customer = $this->createCustomer();
|
||||
$log->setCustomer($customer);
|
||||
$this->assertSame($customer, $log->getCustomer());
|
||||
|
||||
$log->setCustomer(null);
|
||||
$this->assertNull($log->getCustomer());
|
||||
}
|
||||
|
||||
public function testEntityIdAndType(): void
|
||||
{
|
||||
$log = new ActionLog(ActionLog::ACTION_SUSPEND_WEBSITE, 'Test');
|
||||
|
||||
$log->setEntityId(123);
|
||||
$this->assertSame(123, $log->getEntityId());
|
||||
|
||||
$log->setEntityType('Website');
|
||||
$this->assertSame('Website', $log->getEntityType());
|
||||
|
||||
$log->setEntityId(null);
|
||||
$this->assertNull($log->getEntityId());
|
||||
|
||||
$log->setEntityType(null);
|
||||
$this->assertNull($log->getEntityType());
|
||||
}
|
||||
|
||||
public function testContext(): void
|
||||
{
|
||||
$log = new ActionLog(ActionLog::ACTION_SUSPEND_CUSTOMER, 'Test');
|
||||
$this->assertNull($log->getContext());
|
||||
|
||||
$log->setContext('{"key":"value"}');
|
||||
$this->assertSame('{"key":"value"}', $log->getContext());
|
||||
|
||||
$log->setContext(null);
|
||||
$this->assertNull($log->getContext());
|
||||
}
|
||||
|
||||
public function testPreviousAndNewState(): void
|
||||
{
|
||||
$log = new ActionLog(ActionLog::ACTION_SUSPEND_CUSTOMER, 'Test');
|
||||
|
||||
$log->setPreviousState('active');
|
||||
$this->assertSame('active', $log->getPreviousState());
|
||||
|
||||
$log->setNewState('suspended');
|
||||
$this->assertSame('suspended', $log->getNewState());
|
||||
|
||||
$log->setPreviousState(null);
|
||||
$this->assertNull($log->getPreviousState());
|
||||
|
||||
$log->setNewState(null);
|
||||
$this->assertNull($log->getNewState());
|
||||
}
|
||||
|
||||
public function testSetErrorMessageSetsSuccessFalse(): void
|
||||
{
|
||||
$log = new ActionLog(ActionLog::ACTION_SUSPEND_CUSTOMER, 'Test');
|
||||
$this->assertTrue($log->isSuccess());
|
||||
$this->assertNull($log->getErrorMessage());
|
||||
|
||||
$log->setErrorMessage('Une erreur inattendue s\'est produite.');
|
||||
$this->assertSame("Une erreur inattendue s'est produite.", $log->getErrorMessage());
|
||||
$this->assertFalse($log->isSuccess());
|
||||
}
|
||||
|
||||
public function testSetErrorMessageNull(): void
|
||||
{
|
||||
$log = new ActionLog(ActionLog::ACTION_SUSPEND_CUSTOMER, 'Test', 'info', false);
|
||||
|
||||
$log->setErrorMessage(null);
|
||||
$this->assertNull($log->getErrorMessage());
|
||||
// Setting null still sets success to false
|
||||
$this->assertFalse($log->isSuccess());
|
||||
}
|
||||
|
||||
public function testActionConstants(): void
|
||||
{
|
||||
$this->assertSame('suspend_customer', ActionLog::ACTION_SUSPEND_CUSTOMER);
|
||||
$this->assertSame('unsuspend_customer', ActionLog::ACTION_UNSUSPEND_CUSTOMER);
|
||||
$this->assertSame('suspend_website', ActionLog::ACTION_SUSPEND_WEBSITE);
|
||||
$this->assertSame('unsuspend_website', ActionLog::ACTION_UNSUSPEND_WEBSITE);
|
||||
$this->assertSame('suspend_domain_email', ActionLog::ACTION_SUSPEND_DOMAIN_EMAIL);
|
||||
$this->assertSame('unsuspend_domain_email', ActionLog::ACTION_UNSUSPEND_DOMAIN_EMAIL);
|
||||
$this->assertSame('disable_customer', ActionLog::ACTION_DISABLE_CUSTOMER);
|
||||
$this->assertSame('delete_customer_data', ActionLog::ACTION_DELETE_CUSTOMER_DATA);
|
||||
$this->assertSame('formal_notice', ActionLog::ACTION_FORMAL_NOTICE);
|
||||
$this->assertSame('termination', ActionLog::ACTION_TERMINATION);
|
||||
}
|
||||
|
||||
public function testSettersReturnStatic(): void
|
||||
{
|
||||
$log = new ActionLog(ActionLog::ACTION_SUSPEND_CUSTOMER, 'Test');
|
||||
|
||||
$this->assertSame($log, $log->setCustomer(null));
|
||||
$this->assertSame($log, $log->setEntityId(1));
|
||||
$this->assertSame($log, $log->setEntityType('T'));
|
||||
$this->assertSame($log, $log->setContext('{}'));
|
||||
$this->assertSame($log, $log->setPreviousState('a'));
|
||||
$this->assertSame($log, $log->setNewState('b'));
|
||||
$this->assertSame($log, $log->setErrorMessage('err'));
|
||||
}
|
||||
}
|
||||
97
tests/Entity/AdvertEventTest.php
Normal file
97
tests/Entity/AdvertEventTest.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Advert;
|
||||
use App\Entity\AdvertEvent;
|
||||
use App\Entity\OrderNumber;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AdvertEventTest extends TestCase
|
||||
{
|
||||
private function createAdvert(): Advert
|
||||
{
|
||||
$order = new OrderNumber('04/2026-99002');
|
||||
|
||||
return new Advert($order, 'test-secret');
|
||||
}
|
||||
|
||||
public function testConstructorMinimalArgs(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$event = new AdvertEvent($advert, AdvertEvent::TYPE_VIEW);
|
||||
|
||||
$this->assertNull($event->getId());
|
||||
$this->assertSame($advert, $event->getAdvert());
|
||||
$this->assertSame(AdvertEvent::TYPE_VIEW, $event->getType());
|
||||
$this->assertNull($event->getDetails());
|
||||
$this->assertNull($event->getIp());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $event->getCreatedAt());
|
||||
}
|
||||
|
||||
public function testConstructorWithAllArgs(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$event = new AdvertEvent($advert, AdvertEvent::TYPE_PAY, 'Paiement CB', '192.168.1.1');
|
||||
|
||||
$this->assertSame(AdvertEvent::TYPE_PAY, $event->getType());
|
||||
$this->assertSame('Paiement CB', $event->getDetails());
|
||||
$this->assertSame('192.168.1.1', $event->getIp());
|
||||
}
|
||||
|
||||
public function testTypeConstants(): void
|
||||
{
|
||||
$this->assertSame('view', AdvertEvent::TYPE_VIEW);
|
||||
$this->assertSame('pay', AdvertEvent::TYPE_PAY);
|
||||
$this->assertSame('mail_open', AdvertEvent::TYPE_MAIL_OPEN);
|
||||
$this->assertSame('mail_send', AdvertEvent::TYPE_MAIL_SEND);
|
||||
$this->assertSame('reminder', AdvertEvent::TYPE_REMINDER);
|
||||
}
|
||||
|
||||
public function testGetTypeLabelView(): void
|
||||
{
|
||||
$event = new AdvertEvent($this->createAdvert(), AdvertEvent::TYPE_VIEW);
|
||||
$this->assertSame('Page consultee', $event->getTypeLabel());
|
||||
}
|
||||
|
||||
public function testGetTypeLabelPay(): void
|
||||
{
|
||||
$event = new AdvertEvent($this->createAdvert(), AdvertEvent::TYPE_PAY);
|
||||
$this->assertSame('Paiement effectue', $event->getTypeLabel());
|
||||
}
|
||||
|
||||
public function testGetTypeLabelMailOpen(): void
|
||||
{
|
||||
$event = new AdvertEvent($this->createAdvert(), AdvertEvent::TYPE_MAIL_OPEN);
|
||||
$this->assertSame('Email ouvert', $event->getTypeLabel());
|
||||
}
|
||||
|
||||
public function testGetTypeLabelMailSend(): void
|
||||
{
|
||||
$event = new AdvertEvent($this->createAdvert(), AdvertEvent::TYPE_MAIL_SEND);
|
||||
$this->assertSame('Email envoye', $event->getTypeLabel());
|
||||
}
|
||||
|
||||
public function testGetTypeLabelReminder(): void
|
||||
{
|
||||
$event = new AdvertEvent($this->createAdvert(), AdvertEvent::TYPE_REMINDER);
|
||||
$this->assertSame('Relance envoyee', $event->getTypeLabel());
|
||||
}
|
||||
|
||||
public function testGetTypeLabelUnknown(): void
|
||||
{
|
||||
$event = new AdvertEvent($this->createAdvert(), 'custom_type');
|
||||
$this->assertSame('custom_type', $event->getTypeLabel());
|
||||
}
|
||||
|
||||
public function testCreatedAtTimestamp(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$before = new \DateTimeImmutable();
|
||||
$event = new AdvertEvent($advert, AdvertEvent::TYPE_VIEW);
|
||||
$after = new \DateTimeImmutable();
|
||||
|
||||
$this->assertGreaterThanOrEqual($before, $event->getCreatedAt());
|
||||
$this->assertLessThanOrEqual($after, $event->getCreatedAt());
|
||||
}
|
||||
}
|
||||
79
tests/Entity/AdvertPaymentTest.php
Normal file
79
tests/Entity/AdvertPaymentTest.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Advert;
|
||||
use App\Entity\AdvertPayment;
|
||||
use App\Entity\OrderNumber;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AdvertPaymentTest extends TestCase
|
||||
{
|
||||
private function createAdvert(): Advert
|
||||
{
|
||||
$order = new OrderNumber('04/2026-99001');
|
||||
|
||||
return new Advert($order, 'test-secret');
|
||||
}
|
||||
|
||||
public function testConstructor(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$payment = new AdvertPayment($advert, AdvertPayment::TYPE_SUCCESS, '199.00');
|
||||
|
||||
$this->assertNull($payment->getId());
|
||||
$this->assertSame($advert, $payment->getAdvert());
|
||||
$this->assertSame(AdvertPayment::TYPE_SUCCESS, $payment->getType());
|
||||
$this->assertSame('199.00', $payment->getAmount());
|
||||
$this->assertNull($payment->getMethod());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $payment->getCreatedAt());
|
||||
}
|
||||
|
||||
public function testTypeRefused(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$payment = new AdvertPayment($advert, AdvertPayment::TYPE_REFUSED, '0.00');
|
||||
|
||||
$this->assertSame(AdvertPayment::TYPE_REFUSED, $payment->getType());
|
||||
}
|
||||
|
||||
public function testMethod(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$payment = new AdvertPayment($advert, AdvertPayment::TYPE_SUCCESS, '50.00');
|
||||
|
||||
$this->assertNull($payment->getMethod());
|
||||
|
||||
$payment->setMethod('card');
|
||||
$this->assertSame('card', $payment->getMethod());
|
||||
|
||||
$payment->setMethod(null);
|
||||
$this->assertNull($payment->getMethod());
|
||||
}
|
||||
|
||||
public function testMethodReturnsStatic(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$payment = new AdvertPayment($advert, AdvertPayment::TYPE_SUCCESS, '50.00');
|
||||
|
||||
$result = $payment->setMethod('sepa');
|
||||
$this->assertSame($payment, $result);
|
||||
}
|
||||
|
||||
public function testTypeConstants(): void
|
||||
{
|
||||
$this->assertSame('success', AdvertPayment::TYPE_SUCCESS);
|
||||
$this->assertSame('refused', AdvertPayment::TYPE_REFUSED);
|
||||
}
|
||||
|
||||
public function testCreatedAtIsImmutable(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$before = new \DateTimeImmutable();
|
||||
$payment = new AdvertPayment($advert, AdvertPayment::TYPE_SUCCESS, '99.00');
|
||||
$after = new \DateTimeImmutable();
|
||||
|
||||
$this->assertGreaterThanOrEqual($before, $payment->getCreatedAt());
|
||||
$this->assertLessThanOrEqual($after, $payment->getCreatedAt());
|
||||
}
|
||||
}
|
||||
75
tests/Entity/DocusealEventTest.php
Normal file
75
tests/Entity/DocusealEventTest.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\DocusealEvent;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DocusealEventTest extends TestCase
|
||||
{
|
||||
public function testConstructorMinimalArgs(): void
|
||||
{
|
||||
$event = new DocusealEvent('submission', 'completed');
|
||||
|
||||
$this->assertNull($event->getId());
|
||||
$this->assertSame('submission', $event->getType());
|
||||
$this->assertSame('completed', $event->getEventType());
|
||||
$this->assertNull($event->getSubmissionId());
|
||||
$this->assertNull($event->getSubmitterId());
|
||||
$this->assertNull($event->getPayload());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $event->getCreatedAt());
|
||||
}
|
||||
|
||||
public function testConstructorWithAllArgs(): void
|
||||
{
|
||||
$payload = '{"submission_id":42,"submitter_id":7,"status":"completed"}';
|
||||
$event = new DocusealEvent('submitter', 'opened', 42, 7, $payload);
|
||||
|
||||
$this->assertSame('submitter', $event->getType());
|
||||
$this->assertSame('opened', $event->getEventType());
|
||||
$this->assertSame(42, $event->getSubmissionId());
|
||||
$this->assertSame(7, $event->getSubmitterId());
|
||||
$this->assertSame($payload, $event->getPayload());
|
||||
}
|
||||
|
||||
public function testSubmissionIdNullable(): void
|
||||
{
|
||||
$event = new DocusealEvent('submission', 'completed', null, null);
|
||||
$this->assertNull($event->getSubmissionId());
|
||||
}
|
||||
|
||||
public function testSubmitterIdNullable(): void
|
||||
{
|
||||
$event = new DocusealEvent('submission', 'completed', 10, null);
|
||||
$this->assertSame(10, $event->getSubmissionId());
|
||||
$this->assertNull($event->getSubmitterId());
|
||||
}
|
||||
|
||||
public function testPayloadNullable(): void
|
||||
{
|
||||
$event = new DocusealEvent('submission', 'completed', 1, 2, null);
|
||||
$this->assertNull($event->getPayload());
|
||||
}
|
||||
|
||||
public function testCreatedAtTimestamp(): void
|
||||
{
|
||||
$before = new \DateTimeImmutable();
|
||||
$event = new DocusealEvent('submission', 'completed');
|
||||
$after = new \DateTimeImmutable();
|
||||
|
||||
$this->assertGreaterThanOrEqual($before, $event->getCreatedAt());
|
||||
$this->assertLessThanOrEqual($after, $event->getCreatedAt());
|
||||
}
|
||||
|
||||
public function testTypesAreImmutableAfterConstruction(): void
|
||||
{
|
||||
$event = new DocusealEvent('form', 'signed', 100, 200, '{}');
|
||||
|
||||
// Getters must return exactly what was passed in
|
||||
$this->assertSame('form', $event->getType());
|
||||
$this->assertSame('signed', $event->getEventType());
|
||||
$this->assertSame(100, $event->getSubmissionId());
|
||||
$this->assertSame(200, $event->getSubmitterId());
|
||||
$this->assertSame('{}', $event->getPayload());
|
||||
}
|
||||
}
|
||||
120
tests/Entity/FactureLineTest.php
Normal file
120
tests/Entity/FactureLineTest.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Facture;
|
||||
use App\Entity\FactureLine;
|
||||
use App\Entity\OrderNumber;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FactureLineTest extends TestCase
|
||||
{
|
||||
private function createFacture(): Facture
|
||||
{
|
||||
$order = new OrderNumber('04/2026-88001');
|
||||
|
||||
return new Facture($order, 'test-secret');
|
||||
}
|
||||
|
||||
public function testConstructorMinimalArgs(): void
|
||||
{
|
||||
$facture = $this->createFacture();
|
||||
$line = new FactureLine($facture, 'Hébergement web');
|
||||
|
||||
$this->assertNull($line->getId());
|
||||
$this->assertSame($facture, $line->getFacture());
|
||||
$this->assertSame('Hébergement web', $line->getTitle());
|
||||
$this->assertSame('0.00', $line->getPriceHt());
|
||||
$this->assertSame(0, $line->getPos());
|
||||
$this->assertNull($line->getDescription());
|
||||
$this->assertNull($line->getType());
|
||||
$this->assertNull($line->getServiceId());
|
||||
}
|
||||
|
||||
public function testConstructorWithAllArgs(): void
|
||||
{
|
||||
$facture = $this->createFacture();
|
||||
$line = new FactureLine($facture, 'Nom de domaine .fr', '12.00', 2);
|
||||
|
||||
$this->assertSame('Nom de domaine .fr', $line->getTitle());
|
||||
$this->assertSame('12.00', $line->getPriceHt());
|
||||
$this->assertSame(2, $line->getPos());
|
||||
}
|
||||
|
||||
public function testTitle(): void
|
||||
{
|
||||
$facture = $this->createFacture();
|
||||
$line = new FactureLine($facture, 'Initial');
|
||||
|
||||
$line->setTitle('Nouveau titre');
|
||||
$this->assertSame('Nouveau titre', $line->getTitle());
|
||||
}
|
||||
|
||||
public function testPriceHt(): void
|
||||
{
|
||||
$facture = $this->createFacture();
|
||||
$line = new FactureLine($facture, 'Test');
|
||||
|
||||
$line->setPriceHt('249.99');
|
||||
$this->assertSame('249.99', $line->getPriceHt());
|
||||
}
|
||||
|
||||
public function testPos(): void
|
||||
{
|
||||
$facture = $this->createFacture();
|
||||
$line = new FactureLine($facture, 'Test');
|
||||
|
||||
$this->assertSame(0, $line->getPos());
|
||||
$line->setPos(5);
|
||||
$this->assertSame(5, $line->getPos());
|
||||
}
|
||||
|
||||
public function testDescription(): void
|
||||
{
|
||||
$facture = $this->createFacture();
|
||||
$line = new FactureLine($facture, 'Test');
|
||||
|
||||
$this->assertNull($line->getDescription());
|
||||
$line->setDescription('Description détaillée du service.');
|
||||
$this->assertSame('Description détaillée du service.', $line->getDescription());
|
||||
$line->setDescription(null);
|
||||
$this->assertNull($line->getDescription());
|
||||
}
|
||||
|
||||
public function testType(): void
|
||||
{
|
||||
$facture = $this->createFacture();
|
||||
$line = new FactureLine($facture, 'Test');
|
||||
|
||||
$this->assertNull($line->getType());
|
||||
$line->setType('hosting');
|
||||
$this->assertSame('hosting', $line->getType());
|
||||
$line->setType(null);
|
||||
$this->assertNull($line->getType());
|
||||
}
|
||||
|
||||
public function testServiceId(): void
|
||||
{
|
||||
$facture = $this->createFacture();
|
||||
$line = new FactureLine($facture, 'Test');
|
||||
|
||||
$this->assertNull($line->getServiceId());
|
||||
$line->setServiceId(42);
|
||||
$this->assertSame(42, $line->getServiceId());
|
||||
$line->setServiceId(null);
|
||||
$this->assertNull($line->getServiceId());
|
||||
}
|
||||
|
||||
public function testSettersReturnStatic(): void
|
||||
{
|
||||
$facture = $this->createFacture();
|
||||
$line = new FactureLine($facture, 'Test');
|
||||
|
||||
$this->assertSame($line, $line->setTitle('T'));
|
||||
$this->assertSame($line, $line->setPriceHt('1.00'));
|
||||
$this->assertSame($line, $line->setPos(1));
|
||||
$this->assertSame($line, $line->setDescription('D'));
|
||||
$this->assertSame($line, $line->setType('type'));
|
||||
$this->assertSame($line, $line->setServiceId(1));
|
||||
}
|
||||
}
|
||||
140
tests/Entity/FacturePrestataireTest.php
Normal file
140
tests/Entity/FacturePrestataireTest.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\FacturePrestataire;
|
||||
use App\Entity\Prestataire;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
class FacturePrestataireTest extends TestCase
|
||||
{
|
||||
private function createPrestataire(): Prestataire
|
||||
{
|
||||
return new Prestataire('Test Prestataire SARL');
|
||||
}
|
||||
|
||||
public function testConstructor(): void
|
||||
{
|
||||
$p = $this->createPrestataire();
|
||||
$f = new FacturePrestataire($p, 'FACT-2026-001', 2026, 4);
|
||||
|
||||
$this->assertNull($f->getId());
|
||||
$this->assertSame($p, $f->getPrestataire());
|
||||
$this->assertSame('FACT-2026-001', $f->getNumFacture());
|
||||
$this->assertSame(2026, $f->getYear());
|
||||
$this->assertSame(4, $f->getMonth());
|
||||
$this->assertSame('0.00', $f->getMontantHt());
|
||||
$this->assertSame('0.00', $f->getMontantTtc());
|
||||
$this->assertFalse($f->isPaid());
|
||||
$this->assertNull($f->getPaidAt());
|
||||
$this->assertNull($f->getFacturePdf());
|
||||
$this->assertNull($f->getFacturePdfFile());
|
||||
$this->assertNull($f->getUpdatedAt());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $f->getCreatedAt());
|
||||
}
|
||||
|
||||
public function testNumFacture(): void
|
||||
{
|
||||
$f = new FacturePrestataire($this->createPrestataire(), 'OLD-001', 2026, 1);
|
||||
$f->setNumFacture('NEW-002');
|
||||
$this->assertSame('NEW-002', $f->getNumFacture());
|
||||
}
|
||||
|
||||
public function testMontants(): void
|
||||
{
|
||||
$f = new FacturePrestataire($this->createPrestataire(), 'FACT-001', 2026, 3);
|
||||
|
||||
$f->setMontantHt('1500.00');
|
||||
$this->assertSame('1500.00', $f->getMontantHt());
|
||||
|
||||
$f->setMontantTtc('1800.00');
|
||||
$this->assertSame('1800.00', $f->getMontantTtc());
|
||||
}
|
||||
|
||||
public function testYearAndMonth(): void
|
||||
{
|
||||
$f = new FacturePrestataire($this->createPrestataire(), 'FACT-001', 2025, 12);
|
||||
$f->setYear(2026);
|
||||
$this->assertSame(2026, $f->getYear());
|
||||
$f->setMonth(1);
|
||||
$this->assertSame(1, $f->getMonth());
|
||||
}
|
||||
|
||||
public function testIsPaid(): void
|
||||
{
|
||||
$f = new FacturePrestataire($this->createPrestataire(), 'FACT-001', 2026, 4);
|
||||
$this->assertFalse($f->isPaid());
|
||||
|
||||
$f->setIsPaid(true);
|
||||
$this->assertTrue($f->isPaid());
|
||||
|
||||
$f->setIsPaid(false);
|
||||
$this->assertFalse($f->isPaid());
|
||||
}
|
||||
|
||||
public function testPaidAt(): void
|
||||
{
|
||||
$f = new FacturePrestataire($this->createPrestataire(), 'FACT-001', 2026, 4);
|
||||
$this->assertNull($f->getPaidAt());
|
||||
|
||||
$now = new \DateTimeImmutable();
|
||||
$f->setPaidAt($now);
|
||||
$this->assertSame($now, $f->getPaidAt());
|
||||
|
||||
$f->setPaidAt(null);
|
||||
$this->assertNull($f->getPaidAt());
|
||||
}
|
||||
|
||||
public function testFacturePdf(): void
|
||||
{
|
||||
$f = new FacturePrestataire($this->createPrestataire(), 'FACT-001', 2026, 4);
|
||||
$this->assertNull($f->getFacturePdf());
|
||||
|
||||
$f->setFacturePdf('facture-2026-001.pdf');
|
||||
$this->assertSame('facture-2026-001.pdf', $f->getFacturePdf());
|
||||
|
||||
$f->setFacturePdf(null);
|
||||
$this->assertNull($f->getFacturePdf());
|
||||
}
|
||||
|
||||
public function testSetFacturePdfFileTriggersUpdatedAt(): void
|
||||
{
|
||||
$f = new FacturePrestataire($this->createPrestataire(), 'FACT-001', 2026, 4);
|
||||
$this->assertNull($f->getUpdatedAt());
|
||||
|
||||
$file = $this->createStub(File::class);
|
||||
$f->setFacturePdfFile($file);
|
||||
|
||||
$this->assertSame($file, $f->getFacturePdfFile());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $f->getUpdatedAt());
|
||||
}
|
||||
|
||||
public function testSetFacturePdfFileNullDoesNotSetUpdatedAt(): void
|
||||
{
|
||||
$f = new FacturePrestataire($this->createPrestataire(), 'FACT-001', 2026, 4);
|
||||
$f->setFacturePdfFile(null);
|
||||
$this->assertNull($f->getUpdatedAt());
|
||||
$this->assertNull($f->getFacturePdfFile());
|
||||
}
|
||||
|
||||
public function testGetPeriodLabelKnownMonths(): void
|
||||
{
|
||||
$months = [
|
||||
1 => 'Janvier', 2 => 'Fevrier', 3 => 'Mars', 4 => 'Avril',
|
||||
5 => 'Mai', 6 => 'Juin', 7 => 'Juillet', 8 => 'Aout',
|
||||
9 => 'Septembre', 10 => 'Octobre', 11 => 'Novembre', 12 => 'Decembre',
|
||||
];
|
||||
|
||||
foreach ($months as $num => $name) {
|
||||
$f = new FacturePrestataire($this->createPrestataire(), 'F', 2026, $num);
|
||||
$this->assertSame($name.' 2026', $f->getPeriodLabel());
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetPeriodLabelUnknownMonth(): void
|
||||
{
|
||||
$f = new FacturePrestataire($this->createPrestataire(), 'F', 2026, 13);
|
||||
$this->assertSame('13 2026', $f->getPeriodLabel());
|
||||
}
|
||||
}
|
||||
138
tests/Entity/PaymentReminderTest.php
Normal file
138
tests/Entity/PaymentReminderTest.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Advert;
|
||||
use App\Entity\OrderNumber;
|
||||
use App\Entity\PaymentReminder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PaymentReminderTest extends TestCase
|
||||
{
|
||||
private function createAdvert(): Advert
|
||||
{
|
||||
$order = new OrderNumber('04/2026-99003');
|
||||
|
||||
return new Advert($order, 'test-secret');
|
||||
}
|
||||
|
||||
public function testConstructorMinimalArgs(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$reminder = new PaymentReminder($advert, PaymentReminder::STEP_REMINDER_15);
|
||||
|
||||
$this->assertNull($reminder->getId());
|
||||
$this->assertSame($advert, $reminder->getAdvert());
|
||||
$this->assertSame(PaymentReminder::STEP_REMINDER_15, $reminder->getStep());
|
||||
$this->assertNull($reminder->getDetails());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $reminder->getSentAt());
|
||||
}
|
||||
|
||||
public function testConstructorWithDetails(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$reminder = new PaymentReminder($advert, PaymentReminder::STEP_FORMAL_NOTICE, 'Email envoyé à client@test.fr');
|
||||
|
||||
$this->assertSame('Email envoyé à client@test.fr', $reminder->getDetails());
|
||||
}
|
||||
|
||||
public function testStepConstants(): void
|
||||
{
|
||||
$this->assertSame('reminder_15', PaymentReminder::STEP_REMINDER_15);
|
||||
$this->assertSame('warning_10', PaymentReminder::STEP_WARNING_10);
|
||||
$this->assertSame('suspension_5', PaymentReminder::STEP_SUSPENSION_WARNING_5);
|
||||
$this->assertSame('final_reminder_3', PaymentReminder::STEP_FINAL_REMINDER_3);
|
||||
$this->assertSame('suspension_1', PaymentReminder::STEP_SUSPENSION_1);
|
||||
$this->assertSame('formal_notice', PaymentReminder::STEP_FORMAL_NOTICE);
|
||||
$this->assertSame('termination_15', PaymentReminder::STEP_TERMINATION_WARNING);
|
||||
$this->assertSame('termination_30', PaymentReminder::STEP_TERMINATION);
|
||||
}
|
||||
|
||||
public function testGetStepLabelKnownSteps(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
|
||||
$cases = [
|
||||
PaymentReminder::STEP_REMINDER_15 => 'Rappel de paiement',
|
||||
PaymentReminder::STEP_WARNING_10 => 'Rappel + avertissement',
|
||||
PaymentReminder::STEP_SUSPENSION_WARNING_5 => 'Avertissement suspension services',
|
||||
PaymentReminder::STEP_FINAL_REMINDER_3 => 'Ultime rappel',
|
||||
PaymentReminder::STEP_SUSPENSION_1 => 'Suspension des services',
|
||||
PaymentReminder::STEP_FORMAL_NOTICE => 'Mise en demeure',
|
||||
PaymentReminder::STEP_TERMINATION_WARNING => 'Avertissement resiliation + suppression donnees',
|
||||
PaymentReminder::STEP_TERMINATION => 'Resiliation contrat + recouvrement legal',
|
||||
];
|
||||
|
||||
foreach ($cases as $step => $expectedLabel) {
|
||||
$reminder = new PaymentReminder($advert, $step);
|
||||
$this->assertSame($expectedLabel, $reminder->getStepLabel(), "Label mismatch for step: $step");
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetStepLabelUnknownStep(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$reminder = new PaymentReminder($advert, 'unknown_step');
|
||||
$this->assertSame('unknown_step', $reminder->getStepLabel());
|
||||
}
|
||||
|
||||
public function testGetSeverityKnownSteps(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
|
||||
$cases = [
|
||||
PaymentReminder::STEP_REMINDER_15 => 'info',
|
||||
PaymentReminder::STEP_WARNING_10 => 'warning',
|
||||
PaymentReminder::STEP_SUSPENSION_WARNING_5 => 'danger',
|
||||
PaymentReminder::STEP_FINAL_REMINDER_3 => 'danger',
|
||||
PaymentReminder::STEP_SUSPENSION_1 => 'critical',
|
||||
PaymentReminder::STEP_FORMAL_NOTICE => 'critical',
|
||||
PaymentReminder::STEP_TERMINATION_WARNING => 'critical',
|
||||
PaymentReminder::STEP_TERMINATION => 'critical',
|
||||
];
|
||||
|
||||
foreach ($cases as $step => $expectedSeverity) {
|
||||
$reminder = new PaymentReminder($advert, $step);
|
||||
$this->assertSame($expectedSeverity, $reminder->getSeverity(), "Severity mismatch for step: $step");
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetSeverityUnknownStep(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$reminder = new PaymentReminder($advert, 'unknown_step');
|
||||
$this->assertSame('info', $reminder->getSeverity());
|
||||
}
|
||||
|
||||
public function testStepsConfigCoversAllStepConstants(): void
|
||||
{
|
||||
$stepConstants = [
|
||||
PaymentReminder::STEP_REMINDER_15,
|
||||
PaymentReminder::STEP_WARNING_10,
|
||||
PaymentReminder::STEP_SUSPENSION_WARNING_5,
|
||||
PaymentReminder::STEP_FINAL_REMINDER_3,
|
||||
PaymentReminder::STEP_SUSPENSION_1,
|
||||
PaymentReminder::STEP_FORMAL_NOTICE,
|
||||
PaymentReminder::STEP_TERMINATION_WARNING,
|
||||
PaymentReminder::STEP_TERMINATION,
|
||||
];
|
||||
|
||||
foreach ($stepConstants as $step) {
|
||||
$this->assertArrayHasKey($step, PaymentReminder::STEPS_CONFIG, "STEPS_CONFIG is missing step: $step");
|
||||
$this->assertArrayHasKey('days', PaymentReminder::STEPS_CONFIG[$step]);
|
||||
$this->assertArrayHasKey('label', PaymentReminder::STEPS_CONFIG[$step]);
|
||||
$this->assertArrayHasKey('severity', PaymentReminder::STEPS_CONFIG[$step]);
|
||||
}
|
||||
}
|
||||
|
||||
public function testSentAtTimestamp(): void
|
||||
{
|
||||
$advert = $this->createAdvert();
|
||||
$before = new \DateTimeImmutable();
|
||||
$reminder = new PaymentReminder($advert, PaymentReminder::STEP_REMINDER_15);
|
||||
$after = new \DateTimeImmutable();
|
||||
|
||||
$this->assertGreaterThanOrEqual($before, $reminder->getSentAt());
|
||||
$this->assertLessThanOrEqual($after, $reminder->getSentAt());
|
||||
}
|
||||
}
|
||||
171
tests/Entity/PrestataireTest.php
Normal file
171
tests/Entity/PrestataireTest.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\FacturePrestataire;
|
||||
use App\Entity\Prestataire;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PrestataireTest extends TestCase
|
||||
{
|
||||
public function testConstructor(): void
|
||||
{
|
||||
$p = new Prestataire('Dupont SARL');
|
||||
|
||||
$this->assertNull($p->getId());
|
||||
$this->assertSame('Dupont SARL', $p->getRaisonSociale());
|
||||
$this->assertSame(Prestataire::STATE_ACTIVE, $p->getState());
|
||||
$this->assertNull($p->getSiret());
|
||||
$this->assertNull($p->getEmail());
|
||||
$this->assertNull($p->getPhone());
|
||||
$this->assertNull($p->getAddress());
|
||||
$this->assertNull($p->getZipCode());
|
||||
$this->assertNull($p->getCity());
|
||||
$this->assertNull($p->getUpdatedAt());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $p->getCreatedAt());
|
||||
$this->assertCount(0, $p->getFactures());
|
||||
}
|
||||
|
||||
public function testRaisonSociale(): void
|
||||
{
|
||||
$p = new Prestataire('Initial');
|
||||
$p->setRaisonSociale('Nouveau Nom SAS');
|
||||
$this->assertSame('Nouveau Nom SAS', $p->getRaisonSociale());
|
||||
}
|
||||
|
||||
public function testSiret(): void
|
||||
{
|
||||
$p = new Prestataire('Test');
|
||||
$this->assertNull($p->getSiret());
|
||||
$p->setSiret('12345678901234');
|
||||
$this->assertSame('12345678901234', $p->getSiret());
|
||||
$p->setSiret(null);
|
||||
$this->assertNull($p->getSiret());
|
||||
}
|
||||
|
||||
public function testEmail(): void
|
||||
{
|
||||
$p = new Prestataire('Test');
|
||||
$this->assertNull($p->getEmail());
|
||||
$p->setEmail('contact@prestataire.fr');
|
||||
$this->assertSame('contact@prestataire.fr', $p->getEmail());
|
||||
$p->setEmail(null);
|
||||
$this->assertNull($p->getEmail());
|
||||
}
|
||||
|
||||
public function testPhone(): void
|
||||
{
|
||||
$p = new Prestataire('Test');
|
||||
$this->assertNull($p->getPhone());
|
||||
$p->setPhone('0612345678');
|
||||
$this->assertSame('0612345678', $p->getPhone());
|
||||
$p->setPhone(null);
|
||||
$this->assertNull($p->getPhone());
|
||||
}
|
||||
|
||||
public function testAddress(): void
|
||||
{
|
||||
$p = new Prestataire('Test');
|
||||
$this->assertNull($p->getAddress());
|
||||
$this->assertNull($p->getZipCode());
|
||||
$this->assertNull($p->getCity());
|
||||
|
||||
$p->setAddress('10 rue de la Paix');
|
||||
$p->setZipCode('75001');
|
||||
$p->setCity('Paris');
|
||||
|
||||
$this->assertSame('10 rue de la Paix', $p->getAddress());
|
||||
$this->assertSame('75001', $p->getZipCode());
|
||||
$this->assertSame('Paris', $p->getCity());
|
||||
}
|
||||
|
||||
public function testGetFullAddress(): void
|
||||
{
|
||||
$p = new Prestataire('Test');
|
||||
$this->assertSame('', $p->getFullAddress());
|
||||
|
||||
$p->setAddress('10 rue de la Paix');
|
||||
$p->setZipCode('75001');
|
||||
$p->setCity('Paris');
|
||||
|
||||
$this->assertSame('10 rue de la Paix 75001 Paris', $p->getFullAddress());
|
||||
}
|
||||
|
||||
public function testGetFullAddressPartial(): void
|
||||
{
|
||||
$p = new Prestataire('Test');
|
||||
$p->setCity('Lyon');
|
||||
$this->assertSame('Lyon', $p->getFullAddress());
|
||||
}
|
||||
|
||||
public function testState(): void
|
||||
{
|
||||
$p = new Prestataire('Test');
|
||||
$this->assertSame(Prestataire::STATE_ACTIVE, $p->getState());
|
||||
$this->assertNull($p->getUpdatedAt());
|
||||
|
||||
$p->setState(Prestataire::STATE_INACTIVE);
|
||||
$this->assertSame(Prestataire::STATE_INACTIVE, $p->getState());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $p->getUpdatedAt());
|
||||
}
|
||||
|
||||
public function testAddFacture(): void
|
||||
{
|
||||
$p = new Prestataire('Test');
|
||||
$this->assertCount(0, $p->getFactures());
|
||||
|
||||
$f = new FacturePrestataire($p, 'FACT-001', 2026, 4);
|
||||
$p->addFacture($f);
|
||||
$this->assertCount(1, $p->getFactures());
|
||||
|
||||
// Adding same instance twice must not duplicate
|
||||
$p->addFacture($f);
|
||||
$this->assertCount(1, $p->getFactures());
|
||||
}
|
||||
|
||||
public function testGetTotalPaidHtNoFactures(): void
|
||||
{
|
||||
$p = new Prestataire('Test');
|
||||
$this->assertSame(0.0, $p->getTotalPaidHt());
|
||||
}
|
||||
|
||||
public function testGetTotalPaidHtOnlyPaid(): void
|
||||
{
|
||||
$p = new Prestataire('Test');
|
||||
|
||||
$f1 = new FacturePrestataire($p, 'FACT-001', 2026, 1);
|
||||
$f1->setMontantHt('1000.00');
|
||||
$f1->setIsPaid(true);
|
||||
$p->addFacture($f1);
|
||||
|
||||
$f2 = new FacturePrestataire($p, 'FACT-002', 2026, 2);
|
||||
$f2->setMontantHt('500.00');
|
||||
$f2->setIsPaid(false);
|
||||
$p->addFacture($f2);
|
||||
|
||||
$this->assertSame(1000.0, $p->getTotalPaidHt());
|
||||
}
|
||||
|
||||
public function testGetTotalPaidHtMultiplePaid(): void
|
||||
{
|
||||
$p = new Prestataire('Test');
|
||||
|
||||
$f1 = new FacturePrestataire($p, 'FACT-001', 2026, 1);
|
||||
$f1->setMontantHt('250.50');
|
||||
$f1->setIsPaid(true);
|
||||
$p->addFacture($f1);
|
||||
|
||||
$f2 = new FacturePrestataire($p, 'FACT-002', 2026, 2);
|
||||
$f2->setMontantHt('749.50');
|
||||
$f2->setIsPaid(true);
|
||||
$p->addFacture($f2);
|
||||
|
||||
$this->assertEqualsWithDelta(1000.0, $p->getTotalPaidHt(), 0.001);
|
||||
}
|
||||
|
||||
public function testStateConstants(): void
|
||||
{
|
||||
$this->assertSame('active', Prestataire::STATE_ACTIVE);
|
||||
$this->assertSame('inactive', Prestataire::STATE_INACTIVE);
|
||||
}
|
||||
}
|
||||
174
tests/Service/Pdf/ComptaPdfTest.php
Normal file
174
tests/Service/Pdf/ComptaPdfTest.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Service\Pdf;
|
||||
|
||||
use App\Service\Pdf\ComptaPdf;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
class ComptaPdfTest extends TestCase
|
||||
{
|
||||
private KernelInterface $kernel;
|
||||
private string $projectDir;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->projectDir = sys_get_temp_dir().'/compta-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 makePdf(string $title = 'Journal des ventes', string $from = '01/01/2026', string $to = '31/03/2026'): ComptaPdf
|
||||
{
|
||||
return new ComptaPdf($this->kernel, $title, $from, $to);
|
||||
}
|
||||
|
||||
public function testGenerateEmptyDataProducesValidPdf(): void
|
||||
{
|
||||
$pdf = $this->makePdf();
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testGenerateWithRowsProducesValidPdf(): void
|
||||
{
|
||||
$pdf = $this->makePdf('Journal des ventes');
|
||||
$pdf->setData([
|
||||
[
|
||||
'JournalCode' => 'VTE',
|
||||
'JournalLib' => 'Ventes',
|
||||
'EcritureNum' => 'E001',
|
||||
'EcritureDate' => '01/01/2026',
|
||||
'CompteNum' => '411000',
|
||||
'CompteLib' => 'Clients',
|
||||
'Debit' => '100.00',
|
||||
'Credit' => '0.00',
|
||||
'EcritureLib' => 'Facture 001',
|
||||
],
|
||||
[
|
||||
'JournalCode' => 'VTE',
|
||||
'JournalLib' => 'Ventes',
|
||||
'EcritureNum' => 'E002',
|
||||
'EcritureDate' => '15/01/2026',
|
||||
'CompteNum' => '706000',
|
||||
'CompteLib' => 'Prestations',
|
||||
'Debit' => '0.00',
|
||||
'Credit' => '100.00',
|
||||
'EcritureLib' => 'Facture 001',
|
||||
],
|
||||
]);
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testGenerateWithDebitCreditSummarised(): void
|
||||
{
|
||||
$pdf = $this->makePdf('Grand Livre');
|
||||
$pdf->setData([
|
||||
['Debit' => '200.00', 'Credit' => '50.00', 'EcritureLib' => 'Test'],
|
||||
['Debit' => '300.00', 'Credit' => '100.00', 'EcritureLib' => 'Test2'],
|
||||
]);
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
// The PDF should contain total text; check binary content for encoded values
|
||||
$this->assertGreaterThan(1000, \strlen($output));
|
||||
}
|
||||
|
||||
public function testGenerateWithUnknownColumnsDistributesWidth(): void
|
||||
{
|
||||
$pdf = $this->makePdf('Rapport custom');
|
||||
$pdf->setData([
|
||||
['ColA' => 'Valeur1', 'ColB' => 'Valeur2', 'ColC' => '42.00'],
|
||||
['ColA' => 'Valeur3', 'ColB' => 'Valeur4', 'ColC' => '99.00'],
|
||||
]);
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testGenerateWithLogoFile(): void
|
||||
{
|
||||
// Create a minimal valid JPEG file in the project public dir
|
||||
// We use a 1x1 white JPEG for testing
|
||||
$jpegData = base64_decode('/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AJQAB/9k=');
|
||||
file_put_contents($this->projectDir.'/public/logo.jpg', $jpegData);
|
||||
|
||||
$pdf = $this->makePdf();
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testSetDataWithEmptyRowsGeneratesNoDataMessage(): void
|
||||
{
|
||||
$pdf = $this->makePdf();
|
||||
$pdf->setData([]);
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
// Should still produce a valid PDF even with empty data
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testMultiplePagesWithManyRows(): void
|
||||
{
|
||||
$rows = [];
|
||||
for ($i = 1; $i <= 100; ++$i) {
|
||||
$rows[] = [
|
||||
'JournalCode' => 'VTE',
|
||||
'JournalLib' => 'Ventes',
|
||||
'EcritureNum' => 'E'.str_pad((string) $i, 4, '0', STR_PAD_LEFT),
|
||||
'EcritureDate' => '01/01/2026',
|
||||
'CompteNum' => '411000',
|
||||
'CompteLib' => 'Clients',
|
||||
'Debit' => number_format($i * 10.5, 2),
|
||||
'Credit' => '0.00',
|
||||
'EcritureLib' => 'Ligne '.$i,
|
||||
];
|
||||
}
|
||||
|
||||
$pdf = $this->makePdf('Export FEC');
|
||||
$pdf->setData($rows);
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
}
|
||||
230
tests/Service/Pdf/FacturePdfTest.php
Normal file
230
tests/Service/Pdf/FacturePdfTest.php
Normal file
@@ -0,0 +1,230 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
169
tests/Service/Pdf/RapportFinancierPdfTest.php
Normal file
169
tests/Service/Pdf/RapportFinancierPdfTest.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Service\Pdf;
|
||||
|
||||
use App\Service\Pdf\RapportFinancierPdf;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
class RapportFinancierPdfTest extends TestCase
|
||||
{
|
||||
private KernelInterface $kernel;
|
||||
private string $projectDir;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->projectDir = sys_get_temp_dir().'/rapport-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 makePdf(string $from = '01/01/2026', string $to = '31/12/2026'): RapportFinancierPdf
|
||||
{
|
||||
return new RapportFinancierPdf($this->kernel, $from, $to);
|
||||
}
|
||||
|
||||
public function testGenerateEmptyDataProducesValidPdf(): void
|
||||
{
|
||||
$pdf = $this->makePdf();
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testGenerateWithRecettesAndDepensesProducesValidPdf(): void
|
||||
{
|
||||
$pdf = $this->makePdf();
|
||||
$pdf->setData(
|
||||
[
|
||||
'Hebergement Web' => 1200.00,
|
||||
'Noms de domaine' => 350.00,
|
||||
'Messagerie' => 480.00,
|
||||
],
|
||||
[
|
||||
'Serveur dedie' => 600.00,
|
||||
'Registrar' => 200.00,
|
||||
'Divers' => 50.00,
|
||||
],
|
||||
);
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testGenerateWithPositiveBilanProducesValidPdf(): void
|
||||
{
|
||||
$pdf = $this->makePdf();
|
||||
// Recettes > Depenses => excedent/equilibre
|
||||
$pdf->setData(
|
||||
['Service A' => 5000.00, 'Service B' => 3000.00],
|
||||
['Depense 1' => 1000.00],
|
||||
);
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
$this->assertGreaterThan(1000, \strlen($output));
|
||||
}
|
||||
|
||||
public function testGenerateWithDeficitProducesValidPdf(): void
|
||||
{
|
||||
$pdf = $this->makePdf();
|
||||
// Depenses > Recettes => deficit
|
||||
$pdf->setData(
|
||||
['Petite recette' => 100.00],
|
||||
['Grosse depense' => 9999.00],
|
||||
);
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testGenerateWithExcedentLabelProducesValidPdf(): void
|
||||
{
|
||||
$pdf = $this->makePdf();
|
||||
// Marge > 30% of recettes => 'EXCEDENT'
|
||||
$recettes = ['Service' => 10000.00];
|
||||
$depenses = ['Depense' => 1000.00]; // 9000 marge > 3000 (30%)
|
||||
$pdf->setData($recettes, $depenses);
|
||||
$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);
|
||||
|
||||
$pdf = $this->makePdf();
|
||||
$pdf->setData(['Service' => 500.00], ['Charge' => 200.00]);
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testGenerateWithManyRecettesAndDepenses(): void
|
||||
{
|
||||
$recettes = [];
|
||||
$depenses = [];
|
||||
for ($i = 1; $i <= 20; ++$i) {
|
||||
$recettes['Recette '.$i] = $i * 100.0;
|
||||
$depenses['Depense '.$i] = $i * 50.0;
|
||||
}
|
||||
|
||||
$pdf = $this->makePdf('01/01/2025', '31/12/2025');
|
||||
$pdf->setData($recettes, $depenses);
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testGenerateWithZeroTotalsEquilibre(): void
|
||||
{
|
||||
$pdf = $this->makePdf();
|
||||
// Both zero => marge = 0, isPositif = true, but marge (0) <= recettes*0.3 (0) => 'EQUILIBRE'
|
||||
$pdf->setData([], []);
|
||||
$pdf->generate();
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user