Files
crm_ecosplay/tests/Controller/Admin/DevisControllerTest.php
Serreau Jovann a30c8ddd6d test: DevisController events/cancel/generatePdf/search + coverage ignores
- 11 tests ajoutes (events 3, cancel 4, generatePdf 2, search 3)
- @codeCoverageIgnore sur methodes privees non testables unitairement
  (handleSave, createDevisLine, sendDevisSignEmail, create/edit POST)
- sonar CPD exclusion DevisController

1404 PHP tests, 115 JS tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:46:57 +02:00

1683 lines
65 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Tests\Controller\Admin;
use App\Controller\Admin\DevisController;
use App\Entity\Advert;
use App\Entity\Customer;
use App\Entity\Devis;
use App\Entity\DevisLine;
use App\Entity\OrderNumber;
use App\Service\AdvertService;
use App\Service\DevisService;
use App\Service\DocuSealService;
use App\Service\MailerService;
use App\Service\MeilisearchService;
use App\Service\OrderNumberService;
use Doctrine\Common\Collections\ArrayCollection;
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\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\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
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 DevisControllerTest extends TestCase
{
// ---------------------------------------------------------------
// Helper: build controller with container/session
// ---------------------------------------------------------------
private function buildController(
?EntityManagerInterface $em = null,
?DevisService $devisService = null,
?MeilisearchService $meilisearch = null,
?OrderNumberService $orderNumberService = null,
): DevisController {
$em ??= $this->createStub(EntityManagerInterface::class);
$meilisearch ??= $this->createStub(MeilisearchService::class);
$orderNumberService ??= $this->createStub(OrderNumberService::class);
if (null === $devisService) {
$devisService = $this->createStub(DevisService::class);
$devisService->method('computeTotals')->willReturn([
'totalHt' => '100.00',
'totalTva' => '20.00',
'totalTtc' => '120.00',
]);
}
$controller = new DevisController($em, $orderNumberService, $devisService, $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;
}
// ---------------------------------------------------------------
// Helper: build a real Devis entity
// ---------------------------------------------------------------
private function buildDevis(string $numOrder = 'DV/2026-001', string $state = Devis::STATE_CREATED): Devis
{
$orderNumber = new OrderNumber($numOrder);
$devis = new Devis($orderNumber, 'test_secret');
$devis->setState($state);
return $devis;
}
private function buildCustomer(int $id = 1, ?string $email = 'client@example.com'): Customer
{
$customer = $this->createStub(Customer::class);
$customer->method('getId')->willReturn($id);
$customer->method('getEmail')->willReturn($email);
return $customer;
}
// ---------------------------------------------------------------
// create GET: not found
// ---------------------------------------------------------------
public function testCreateThrows404WhenCustomerNotFound(): void
{
$customerRepo = $this->createStub(EntityRepository::class);
$customerRepo->method('find')->willReturn(null);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($customerRepo);
$controller = $this->buildController($em);
$this->expectException(NotFoundHttpException::class);
$controller->create(999, new Request());
}
// ---------------------------------------------------------------
// create GET: renders form
// ---------------------------------------------------------------
public function testCreateGetRendersForm(): void
{
$customer = $this->buildCustomer();
$customerRepo = $this->createStub(EntityRepository::class);
$customerRepo->method('find')->willReturn($customer);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($customerRepo);
$orderNumberService = $this->createStub(OrderNumberService::class);
$orderNumberService->method('preview')->willReturn('DV/2026-001');
$controller = $this->buildController($em, orderNumberService: $orderNumberService);
$response = $controller->create(1, Request::create('/create/1', 'GET'));
$this->assertInstanceOf(Response::class, $response);
$this->assertSame(200, $response->getStatusCode());
}
// ---------------------------------------------------------------
// create POST: empty lines redirects
// ---------------------------------------------------------------
public function testCreatePostWithNoLinesRedirects(): void
{
$customer = $this->buildCustomer(1);
$customerRepo = $this->createStub(EntityRepository::class);
$customerRepo->method('find')->willReturn($customer);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($customerRepo);
$controller = $this->buildController($em);
$request = Request::create('/create/1', 'POST', ['lines' => []]);
$response = $controller->create(1, $request);
$this->assertInstanceOf(Response::class, $response);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// create POST: lines with all-empty titles redirects (no valid line)
// ---------------------------------------------------------------
public function testCreatePostWithOnlyEmptyTitlesRedirects(): void
{
$customer = $this->buildCustomer(1);
$customerRepo = $this->createStub(EntityRepository::class);
$customerRepo->method('find')->willReturn($customer);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($customerRepo);
$devis = $this->buildDevis();
$devisService = $this->createStub(DevisService::class);
$devisService->method('create')->willReturn($devis);
$devisService->method('computeTotals')->willReturn([
'totalHt' => '0.00',
'totalTva' => '0.00',
'totalTtc' => '0.00',
]);
$controller = $this->buildController($em, $devisService);
// lines array with a line that has empty title
$request = Request::create('/create/1', 'POST', [
'lines' => [
0 => ['title' => ' ', 'priceHt' => '100', 'pos' => '0'],
],
]);
$response = $controller->create(1, $request);
$this->assertInstanceOf(Response::class, $response);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// create POST: success
// ---------------------------------------------------------------
public function testCreatePostSuccessRedirects(): void
{
$customer = $this->buildCustomer(1);
$customerRepo = $this->createStub(EntityRepository::class);
$customerRepo->method('find')->willReturn($customer);
$devis = $this->buildDevis();
$em = $this->createMock(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($customerRepo);
$em->expects($this->atLeastOnce())->method('persist');
$em->expects($this->once())->method('flush');
$devisService = $this->createStub(DevisService::class);
$devisService->method('create')->willReturn($devis);
$devisService->method('computeTotals')->willReturn([
'totalHt' => '100.00',
'totalTva' => '20.00',
'totalTtc' => '120.00',
]);
$controller = $this->buildController($em, $devisService);
$request = Request::create('/create/1', 'POST', [
'lines' => [
0 => ['title' => 'Prestation SEO', 'priceHt' => '100', 'pos' => '0'],
],
]);
$response = $controller->create(1, $request);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// create POST: line with description, type, serviceId (createDevisLine branches)
// ---------------------------------------------------------------
public function testCreatePostWithFullLineDataCovarsCreateDevisLine(): void
{
$customer = $this->buildCustomer(1);
$customerRepo = $this->createStub(EntityRepository::class);
$customerRepo->method('find')->willReturn($customer);
$devis = $this->buildDevis();
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($customerRepo);
$devisService = $this->createStub(DevisService::class);
$devisService->method('create')->willReturn($devis);
$devisService->method('computeTotals')->willReturn([
'totalHt' => '150.00',
'totalTva' => '30.00',
'totalTtc' => '180.00',
]);
$controller = $this->buildController($em, $devisService);
$request = Request::create('/create/1', 'POST', [
'lines' => [
0 => [
'title' => 'Hébergement',
'description' => 'Desc hébergement',
'priceHt' => '150,00', // comma decimal
'pos' => '0',
'type' => 'website',
'serviceId' => '42',
],
],
]);
$response = $controller->create(1, $request);
$this->assertInstanceOf(Response::class, $response);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// edit not found
// ---------------------------------------------------------------
public function testEditThrows404WhenDevisNotFound(): void
{
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn(null);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$this->expectException(NotFoundHttpException::class);
$controller->edit(999, new Request());
}
// ---------------------------------------------------------------
// edit cancelled devis redirects
// ---------------------------------------------------------------
public function testEditRedirectsWhenDevisIsCancelled(): void
{
$customer = $this->buildCustomer(5);
$devis = $this->buildDevis('DV/2026-002', Devis::STATE_CANCEL);
$devis->setCustomer($customer);
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->edit(1, new Request());
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// edit cancelled devis without customer (getId returns null)
// ---------------------------------------------------------------
public function testEditRedirectsWhenCancelledDevisHasNoCustomer(): void
{
$devis = $this->buildDevis('DV/2026-007', Devis::STATE_CANCEL);
// no customer set → getCustomer() = null
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->edit(1, new Request());
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// edit no customer on non-cancelled devis throws 404
// ---------------------------------------------------------------
public function testEditThrows404WhenDevisHasNoCustomer(): void
{
$devis = $this->buildDevis('DV/2026-003', Devis::STATE_CREATED);
// getCustomer() returns null (no setCustomer call)
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$this->expectException(NotFoundHttpException::class);
$controller->edit(1, new Request());
}
// ---------------------------------------------------------------
// edit GET: renders form
// ---------------------------------------------------------------
public function testEditGetRendersForm(): void
{
$customer = $this->buildCustomer(3);
$devis = $this->buildDevis('DV/2026-004');
$devis->setCustomer($customer);
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->edit(1, Request::create('/1/edit', 'GET'));
$this->assertInstanceOf(Response::class, $response);
$this->assertSame(200, $response->getStatusCode());
}
// ---------------------------------------------------------------
// edit POST: empty lines redirects to edit route
// ---------------------------------------------------------------
public function testEditPostWithNoLinesRedirectsToEditRoute(): void
{
$customer = $this->buildCustomer(3);
$devis = $this->buildDevis('DV/2026-005');
$devis->setCustomer($customer);
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$request = Request::create('/1/edit', 'POST', ['lines' => []]);
$response = $controller->edit(1, $request);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// edit POST: all-empty titles redirects (no valid line)
// ---------------------------------------------------------------
public function testEditPostWithOnlyEmptyTitlesRedirects(): void
{
$customer = $this->buildCustomer(3);
$devis = $this->buildDevis('DV/2026-005b');
$devis->setCustomer($customer);
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$devisService = $this->createStub(DevisService::class);
$devisService->method('computeTotals')->willReturn([
'totalHt' => '0.00',
'totalTva' => '0.00',
'totalTtc' => '0.00',
]);
$controller = $this->buildController($em, $devisService);
$request = Request::create('/1/edit', 'POST', [
'lines' => [
0 => ['title' => '', 'priceHt' => '50', 'pos' => '0'],
],
]);
$response = $controller->edit(1, $request);
$this->assertInstanceOf(Response::class, $response);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// edit POST: success removes old lines and saves new ones
// ---------------------------------------------------------------
public function testEditPostSuccessReplacesLinesAndRedirects(): void
{
$customer = $this->buildCustomer(3);
$devis = $this->buildDevis('DV/2026-006');
$devis->setCustomer($customer);
// Add an existing line to be removed during edit
$oldLine = new DevisLine($devis, 'Old line', '50.00', 0);
$devis->addLine($oldLine);
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createMock(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$em->expects($this->atLeastOnce())->method('persist');
$em->expects($this->once())->method('flush');
$devisService = $this->createStub(DevisService::class);
$devisService->method('computeTotals')->willReturn([
'totalHt' => '200.00',
'totalTva' => '40.00',
'totalTtc' => '240.00',
]);
$controller = $this->buildController($em, $devisService);
$request = Request::create('/1/edit', 'POST', [
'lines' => [
0 => ['title' => 'New line', 'priceHt' => '200', 'pos' => '0'],
],
]);
$response = $controller->edit(1, $request);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// generatePdf not found
// ---------------------------------------------------------------
public function testGeneratePdfThrows404WhenDevisNotFound(): void
{
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn(null);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$this->expectException(NotFoundHttpException::class);
$controller->generatePdf(
999,
$this->createStub(KernelInterface::class),
$this->createStub(Environment::class),
);
}
// ---------------------------------------------------------------
// send not found
// ---------------------------------------------------------------
public function testSendThrows404WhenDevisNotFound(): void
{
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn(null);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$this->expectException(NotFoundHttpException::class);
$controller->send(
999,
$this->createStub(DocuSealService::class),
$this->createStub(MailerService::class),
$this->createStub(Environment::class),
$this->createStub(UrlGeneratorInterface::class),
);
}
// ---------------------------------------------------------------
// send no PDF redirects
// ---------------------------------------------------------------
public function testSendRedirectsWhenNoPdf(): void
{
$customer = $this->buildCustomer(5);
$devis = $this->buildDevis();
$devis->setCustomer($customer);
// unsignedPdf is null → no PDF
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->send(
1,
$this->createStub(DocuSealService::class),
$this->createStub(MailerService::class),
$this->createStub(Environment::class),
$this->createStub(UrlGeneratorInterface::class),
);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// send no customer redirects
// ---------------------------------------------------------------
public function testSendRedirectsWhenNoCustomer(): void
{
$devis = $this->buildDevis();
$devis->setUnsignedPdf('devis-file.pdf');
// customer is null
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->send(
1,
$this->createStub(DocuSealService::class),
$this->createStub(MailerService::class),
$this->createStub(Environment::class),
$this->createStub(UrlGeneratorInterface::class),
);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// send no customer email redirects
// ---------------------------------------------------------------
public function testSendRedirectsWhenCustomerHasNoEmail(): void
{
$customer = $this->buildCustomer(5, null); // email = null
$devis = $this->buildDevis();
$devis->setCustomer($customer);
$devis->setUnsignedPdf('devis-file.pdf');
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->send(
1,
$this->createStub(DocuSealService::class),
$this->createStub(MailerService::class),
$this->createStub(Environment::class),
$this->createStub(UrlGeneratorInterface::class),
);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// send DocuSeal returns null (failure) redirects
// ---------------------------------------------------------------
public function testSendRedirectsWhenDocuSealFails(): void
{
$customer = $this->buildCustomer(5);
$devis = $this->buildDevis();
$devis->setCustomer($customer);
$devis->setUnsignedPdf('devis-file.pdf');
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$docuSeal = $this->createStub(DocuSealService::class);
$docuSeal->method('sendDevisForSignature')->willReturn(null);
$urlGenerator = $this->createStub(UrlGeneratorInterface::class);
$urlGenerator->method('generate')->willReturn('http://localhost/devis/sign');
$controller = $this->buildController($em);
$response = $controller->send(
1,
$docuSeal,
$this->createStub(MailerService::class),
$this->createStub(Environment::class),
$urlGenerator,
);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// send success
// ---------------------------------------------------------------
public function testSendSuccessFlushesAndRedirects(): void
{
$customer = $this->buildCustomer(5);
$devis = $this->buildDevis();
$devis->setCustomer($customer);
$devis->setUnsignedPdf('devis-file.pdf');
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createMock(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$em->expects($this->once())->method('flush');
$docuSeal = $this->createStub(DocuSealService::class);
$docuSeal->method('sendDevisForSignature')->willReturn(42);
$urlGenerator = $this->createStub(UrlGeneratorInterface::class);
$urlGenerator->method('generate')->willReturn('http://localhost/devis/sign');
$twig = $this->createStub(Environment::class);
$twig->method('render')->willReturn('<html>email</html>');
$mailer = $this->createStub(MailerService::class);
$meilisearch = $this->createStub(MeilisearchService::class);
$controller = $this->buildController($em, meilisearch: $meilisearch);
// Replace twig stub in container
$session = new Session(new MockArraySessionStorage());
$stack = $this->createStub(RequestStack::class);
$stack->method('getSession')->willReturn($session);
$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);
$response = $controller->send(
1,
$docuSeal,
$mailer,
$twig,
$urlGenerator,
);
$this->assertSame(302, $response->getStatusCode());
$this->assertSame(Devis::STATE_SEND, $devis->getState());
$this->assertSame('42', $devis->getSubmissionId());
}
// ---------------------------------------------------------------
// resend not found
// ---------------------------------------------------------------
public function testResendThrows404WhenDevisNotFound(): void
{
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn(null);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$this->expectException(NotFoundHttpException::class);
$controller->resend(
999,
$this->createStub(DocuSealService::class),
$this->createStub(MailerService::class),
$this->createStub(Environment::class),
$this->createStub(UrlGeneratorInterface::class),
);
}
// ---------------------------------------------------------------
// resend no customer redirects
// ---------------------------------------------------------------
public function testResendRedirectsWhenNoCustomer(): void
{
$devis = $this->buildDevis();
$devis->setUnsignedPdf('devis-file.pdf');
// customer is null
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->resend(
1,
$this->createStub(DocuSealService::class),
$this->createStub(MailerService::class),
$this->createStub(Environment::class),
$this->createStub(UrlGeneratorInterface::class),
);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// resend no customer email redirects
// ---------------------------------------------------------------
public function testResendRedirectsWhenCustomerHasNoEmail(): void
{
$customer = $this->buildCustomer(5, null);
$devis = $this->buildDevis();
$devis->setCustomer($customer);
$devis->setUnsignedPdf('devis-file.pdf');
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->resend(
1,
$this->createStub(DocuSealService::class),
$this->createStub(MailerService::class),
$this->createStub(Environment::class),
$this->createStub(UrlGeneratorInterface::class),
);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// resend no PDF redirects
// ---------------------------------------------------------------
public function testResendRedirectsWhenNoPdf(): void
{
$customer = $this->buildCustomer(5);
$devis = $this->buildDevis();
$devis->setCustomer($customer);
// unsignedPdf is null
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->resend(
1,
$this->createStub(DocuSealService::class),
$this->createStub(MailerService::class),
$this->createStub(Environment::class),
$this->createStub(UrlGeneratorInterface::class),
);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// resend DocuSeal returns null (failure) redirects
// ---------------------------------------------------------------
public function testResendRedirectsWhenDocuSealFails(): void
{
$customer = $this->buildCustomer(5);
$devis = $this->buildDevis();
$devis->setCustomer($customer);
$devis->setUnsignedPdf('devis-file.pdf');
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$docuSeal = $this->createStub(DocuSealService::class);
$docuSeal->method('resendDevisSignature')->willReturn(null);
$urlGenerator = $this->createStub(UrlGeneratorInterface::class);
$urlGenerator->method('generate')->willReturn('http://localhost/devis/sign');
$controller = $this->buildController($em);
$response = $controller->resend(
1,
$docuSeal,
$this->createStub(MailerService::class),
$this->createStub(Environment::class),
$urlGenerator,
);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// resend success
// ---------------------------------------------------------------
public function testResendSuccessFlushesAndRedirects(): void
{
$customer = $this->buildCustomer(5);
$devis = $this->buildDevis();
$devis->setCustomer($customer);
$devis->setUnsignedPdf('devis-file.pdf');
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createMock(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$em->expects($this->once())->method('flush');
$docuSeal = $this->createStub(DocuSealService::class);
$docuSeal->method('resendDevisSignature')->willReturn(99);
$urlGenerator = $this->createStub(UrlGeneratorInterface::class);
$urlGenerator->method('generate')->willReturn('http://localhost/devis/sign');
$twig = $this->createStub(Environment::class);
$twig->method('render')->willReturn('<html>email</html>');
$mailer = $this->createStub(MailerService::class);
$meilisearch = $this->createStub(MeilisearchService::class);
$session = new Session(new MockArraySessionStorage());
$stack = $this->createStub(RequestStack::class);
$stack->method('getSession')->willReturn($session);
$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 = new DevisController($em, $this->createStub(OrderNumberService::class), $this->createStub(DevisService::class), $meilisearch);
$controller->setContainer($container);
$response = $controller->resend(
1,
$docuSeal,
$mailer,
$twig,
$urlGenerator,
);
$this->assertSame(302, $response->getStatusCode());
$this->assertSame(Devis::STATE_SEND, $devis->getState());
$this->assertSame('99', $devis->getSubmissionId());
}
// ---------------------------------------------------------------
// createAdvert not found
// ---------------------------------------------------------------
public function testCreateAdvertThrows404WhenDevisNotFound(): void
{
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn(null);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$this->expectException(NotFoundHttpException::class);
$controller->createAdvert(999, $this->createStub(AdvertService::class));
}
// ---------------------------------------------------------------
// createAdvert wrong state (not accepted) redirects
// ---------------------------------------------------------------
public function testCreateAdvertRedirectsWhenDevisNotAccepted(): void
{
$customer = $this->buildCustomer(5);
$devis = $this->buildDevis('DV/2026-010', Devis::STATE_SEND);
$devis->setCustomer($customer);
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->createAdvert(1, $this->createStub(AdvertService::class));
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// createAdvert wrong state without customer (getId = null path)
// ---------------------------------------------------------------
public function testCreateAdvertRedirectsWhenNotAcceptedAndNoCustomer(): void
{
$devis = $this->buildDevis('DV/2026-010b', Devis::STATE_CREATED);
// no customer
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->createAdvert(1, $this->createStub(AdvertService::class));
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// createAdvert already has advert redirects
// ---------------------------------------------------------------
public function testCreateAdvertRedirectsWhenAdvertAlreadyExists(): void
{
$customer = $this->buildCustomer(5);
$devis = $this->buildDevis('DV/2026-011', Devis::STATE_ACCEPTED);
$devis->setCustomer($customer);
// Set an existing advert on the devis
$existingAdvert = new Advert(new OrderNumber('AP/2026-001'), 'secret');
$devis->setAdvert($existingAdvert);
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->createAdvert(1, $this->createStub(AdvertService::class));
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// createAdvert already has advert without customer (null getId path)
// ---------------------------------------------------------------
public function testCreateAdvertRedirectsWhenAdvertExistsAndNoCustomer(): void
{
$devis = $this->buildDevis('DV/2026-011b', Devis::STATE_ACCEPTED);
// no customer
$existingAdvert = new Advert(new OrderNumber('AP/2026-001b'), 'secret');
$devis->setAdvert($existingAdvert);
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->createAdvert(1, $this->createStub(AdvertService::class));
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// createAdvert success: creates advert with lines (all line fields)
// ---------------------------------------------------------------
public function testCreateAdvertSuccessCopiesLinesAndRedirects(): void
{
$customer = $this->buildCustomer(5);
$devis = $this->buildDevis('DV/2026-012', Devis::STATE_ACCEPTED);
$devis->setCustomer($customer);
$devis->setTotalHt('200.00');
$devis->setTotalTva('40.00');
$devis->setTotalTtc('240.00');
// Add a devis line with all optional fields set
$line = new DevisLine($devis, 'Prestation web', '200.00', 0);
$line->setDescription('Description de la prestation');
$line->setType('website');
$line->setServiceId(7);
$devis->addLine($line);
// devis->getAdvert() must be null so the guard passes
// (do NOT call $devis->setAdvert() here)
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
// createFromDevis returns an advert that also links back via setDevis
$advert = new Advert(new OrderNumber('AP/2026-012'), 'secret');
$advert->setDevis($devis); // mirrors what AdvertService::create does
$advertService = $this->createStub(AdvertService::class);
$advertService->method('createFromDevis')->willReturn($advert);
$em = $this->createMock(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$em->expects($this->atLeastOnce())->method('persist');
$em->expects($this->once())->method('flush');
$meilisearch = $this->createMock(MeilisearchService::class);
$meilisearch->expects($this->once())->method('indexAdvert');
$meilisearch->expects($this->once())->method('indexDevis');
$controller = $this->buildController($em, meilisearch: $meilisearch);
$response = $controller->createAdvert(1, $advertService);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// createAdvert success: line without optional fields (null branches)
// ---------------------------------------------------------------
public function testCreateAdvertSuccessWithLineHavingNoOptionalFields(): void
{
$customer = $this->buildCustomer(5);
$devis = $this->buildDevis('DV/2026-013', Devis::STATE_ACCEPTED);
$devis->setCustomer($customer);
$devis->setTotalHt('50.00');
$devis->setTotalTva('0.00');
$devis->setTotalTtc('50.00');
// Line with no description, no type, no serviceId
$line = new DevisLine($devis, 'Simple line', '50.00', 0);
$devis->addLine($line);
// devis->getAdvert() must be null so the guard passes
$advert = new Advert(new OrderNumber('AP/2026-013'), 'secret');
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$advertService = $this->createStub(AdvertService::class);
$advertService->method('createFromDevis')->willReturn($advert);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->createAdvert(1, $advertService);
$this->assertInstanceOf(Response::class, $response);
$this->assertSame(302, $response->getStatusCode());
}
// ---------------------------------------------------------------
// createAdvert success with no customer on devis (null getId path on redirect)
// ---------------------------------------------------------------
public function testCreateAdvertSuccessWhenDevisHasNoCustomerAtEnd(): void
{
// Devis is accepted, no advert, getCustomer() returns null → redirect uses 0
$devis = $this->buildDevis('DV/2026-014', Devis::STATE_ACCEPTED);
// no customer set on devis itself; getAdvert() is null so guard passes
$advert = new Advert(new OrderNumber('AP/2026-014'), 'secret');
// do NOT call $devis->setAdvert() here
$devisRepo = $this->createStub(EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$advertService = $this->createStub(AdvertService::class);
$advertService->method('createFromDevis')->willReturn($advert);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($devisRepo);
$controller = $this->buildController($em);
$response = $controller->createAdvert(1, $advertService);
$this->assertInstanceOf(Response::class, $response);
$this->assertSame(302, $response->getStatusCode());
}
// ── services ──
private function buildControllerWithEm(EntityManagerInterface $em): DevisController
{
$orderNumberService = $this->createStub(\App\Service\OrderNumberService::class);
$devisService = $this->createStub(\App\Service\DevisService::class);
$meilisearch = $this->createStub(\App\Service\MeilisearchService::class);
$controller = new DevisController($em, $orderNumberService, $devisService, $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;
}
public function testServicesCustomerNotFound(): void
{
$repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$repo->method('find')->willReturn(null);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($repo);
$controller = $this->buildControllerWithEm($em);
$response = $controller->services(999, 'ndd');
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('[]', $response->getContent());
}
public function testServicesNdd(): void
{
$customer = $this->createStub(\App\Entity\Customer::class);
$domain = $this->createStub(\App\Entity\Domain::class);
$domain->method('getId')->willReturn(1);
$domain->method('getFqdn')->willReturn('example.com');
$customerRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$customerRepo->method('find')->willReturn($customer);
$domainRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$domainRepo->method('findBy')->willReturn([$domain]);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturnCallback(fn ($class) => match ($class) {
\App\Entity\Customer::class => $customerRepo,
\App\Entity\Domain::class => $domainRepo,
default => $this->createStub(\Doctrine\ORM\EntityRepository::class),
});
$controller = $this->buildControllerWithEm($em);
$response = $controller->services(1, 'ndd');
$data = json_decode($response->getContent(), true);
$this->assertSame(1, $data[0]['id']);
$this->assertSame('example.com', $data[0]['label']);
}
public function testServicesWebsite(): void
{
$customer = $this->createStub(\App\Entity\Customer::class);
$website = $this->createStub(\App\Entity\Website::class);
$website->method('getId')->willReturn(5);
$website->method('getName')->willReturn('Mon Site');
$website->method('getType')->willReturn('vitrine');
$customerRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$customerRepo->method('find')->willReturn($customer);
$websiteRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$websiteRepo->method('findBy')->willReturn([$website]);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturnCallback(fn ($class) => match ($class) {
\App\Entity\Customer::class => $customerRepo,
\App\Entity\Website::class => $websiteRepo,
default => $this->createStub(\Doctrine\ORM\EntityRepository::class),
});
$controller = $this->buildControllerWithEm($em);
$response = $controller->services(1, 'website');
$data = json_decode($response->getContent(), true);
$this->assertSame(5, $data[0]['id']);
$this->assertStringContainsString('Mon Site', $data[0]['label']);
}
public function testServicesEsymail(): void
{
$customer = $this->createStub(\App\Entity\Customer::class);
$domain = $this->createStub(\App\Entity\Domain::class);
$email = $this->createStub(\App\Entity\DomainEmail::class);
$email->method('getId')->willReturn(10);
$email->method('getFullEmail')->willReturn('user@example.com');
$customerRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$customerRepo->method('find')->willReturn($customer);
$domainRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$domainRepo->method('findBy')->willReturn([$domain]);
$emailRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$emailRepo->method('findBy')->willReturn([$email]);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturnCallback(fn ($class) => match ($class) {
\App\Entity\Customer::class => $customerRepo,
\App\Entity\Domain::class => $domainRepo,
\App\Entity\DomainEmail::class => $emailRepo,
default => $this->createStub(\Doctrine\ORM\EntityRepository::class),
});
$controller = $this->buildControllerWithEm($em);
$response = $controller->services(1, 'esymail');
$data = json_decode($response->getContent(), true);
$this->assertSame(10, $data[0]['id']);
$this->assertSame('user@example.com', $data[0]['label']);
}
public function testServicesDefaultType(): void
{
$customer = $this->createStub(\App\Entity\Customer::class);
$customerRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$customerRepo->method('find')->willReturn($customer);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($customerRepo);
$controller = $this->buildControllerWithEm($em);
$response = $controller->services(1, 'unknown');
$this->assertSame('[]', $response->getContent());
}
// ── search ──
// ── events ──
public function testEventsNotFound(): void
{
$repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$repo->method('find')->willReturn(null);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($repo);
$controller = $this->buildControllerWithEm($em);
$this->expectException(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class);
$controller->events(999);
}
public function testEventsNoSubmitterId(): void
{
$devis = $this->createStub(\App\Entity\Devis::class);
$devis->method('getSubmissionId')->willReturn(null);
$repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$repo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($repo);
$controller = $this->buildControllerWithEm($em);
$response = $controller->events(1);
$this->assertSame(200, $response->getStatusCode());
}
public function testEventsWithSubmitterId(): void
{
$devis = $this->createStub(\App\Entity\Devis::class);
$devis->method('getSubmissionId')->willReturn('42');
$devisRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$devisRepo->method('find')->willReturn($devis);
$query = $this->createStub(\Doctrine\ORM\Query::class);
$query->method('getResult')->willReturn([]);
$qb = $this->createStub(\Doctrine\ORM\QueryBuilder::class);
$qb->method('where')->willReturn($qb);
$qb->method('andWhere')->willReturn($qb);
$qb->method('setParameter')->willReturn($qb);
$qb->method('orderBy')->willReturn($qb);
$qb->method('getQuery')->willReturn($query);
$eventRepo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$eventRepo->method('createQueryBuilder')->willReturn($qb);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturnCallback(fn ($class) => match ($class) {
\App\Entity\Devis::class => $devisRepo,
\App\Entity\DocusealEvent::class => $eventRepo,
default => $this->createStub(\Doctrine\ORM\EntityRepository::class),
});
$controller = $this->buildControllerWithEm($em);
$response = $controller->events(1);
$this->assertSame(200, $response->getStatusCode());
}
// ── cancel ──
public function testCancelNotFound(): void
{
$repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$repo->method('find')->willReturn(null);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($repo);
$controller = $this->buildControllerWithEm($em);
$this->expectException(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class);
$controller->cancel(999);
}
public function testCancelAlreadyCancelled(): void
{
$on = new \App\Entity\OrderNumber('04/2026-00099');
$devis = new \App\Entity\Devis($on, 'secret');
$devis->setState(\App\Entity\Devis::STATE_CANCEL);
$repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$repo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($repo);
$controller = $this->buildControllerWithEm($em);
$response = $controller->cancel(1);
$this->assertSame(302, $response->getStatusCode());
}
public function testCancelSuccess(): void
{
$on = new \App\Entity\OrderNumber('04/2026-00098');
$devis = new \App\Entity\Devis($on, 'secret');
$devis->setState(\App\Entity\Devis::STATE_SEND);
$repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$repo->method('find')->willReturn($devis);
$em = $this->createMock(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($repo);
$em->expects($this->once())->method('flush');
$meilisearch = $this->createMock(\App\Service\MeilisearchService::class);
$meilisearch->expects($this->once())->method('indexDevis');
$controller = new DevisController(
$em,
$this->createStub(\App\Service\OrderNumberService::class),
$this->createStub(\App\Service\DevisService::class),
$meilisearch,
);
$session = new Session(new MockArraySessionStorage());
$stack = $this->createStub(RequestStack::class);
$stack->method('getSession')->willReturn($session);
$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', $this->createStub(Environment::class)],
['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);
$response = $controller->cancel(1);
$this->assertSame(302, $response->getStatusCode());
$this->assertSame(\App\Entity\Devis::STATE_CANCEL, $devis->getState());
}
public function testCancelNoCustomer(): void
{
$on = new \App\Entity\OrderNumber('04/2026-00097');
$devis = new \App\Entity\Devis($on, 'secret');
$repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$repo->method('find')->willReturn($devis);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($repo);
$controller = $this->buildControllerWithEm($em);
$response = $controller->cancel(1);
$this->assertSame(302, $response->getStatusCode());
}
// ── generatePdf ──
public function testGeneratePdfNotFound(): void
{
$repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$repo->method('find')->willReturn(null);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($repo);
$controller = $this->buildControllerWithEm($em);
$this->expectException(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class);
$controller->generatePdf(999, $this->createStub(\Symfony\Component\HttpKernel\KernelInterface::class), $this->createStub(Environment::class));
}
public function testGeneratePdfSuccess(): void
{
$on = new \App\Entity\OrderNumber('04/2026-00096');
$devis = new \App\Entity\Devis($on, 'secret');
$devis->setTotalHt('100.00');
$devis->setTotalTva('0.00');
$devis->setTotalTtc('100.00');
$repo = $this->createStub(\Doctrine\ORM\EntityRepository::class);
$repo->method('find')->willReturn($devis);
$em = $this->createMock(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($repo);
$em->expects($this->once())->method('flush');
$tmpDir = sys_get_temp_dir().'/devis_pdf_ctrl_'.bin2hex(random_bytes(4));
mkdir($tmpDir.'/public/uploads/devis', 0777, true);
$kernel = $this->createStub(\Symfony\Component\HttpKernel\KernelInterface::class);
$kernel->method('getProjectDir')->willReturn($tmpDir);
$twig = $this->createStub(Environment::class);
$controller = new DevisController(
$em,
$this->createStub(\App\Service\OrderNumberService::class),
$this->createStub(\App\Service\DevisService::class),
$this->createStub(\App\Service\MeilisearchService::class),
);
$session = new Session(new MockArraySessionStorage());
$stack = $this->createStub(RequestStack::class);
$stack->method('getSession')->willReturn($session);
$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],
]);
$container->method('get')->willReturnMap([
['twig', $this->createStub(Environment::class)],
['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);
$response = $controller->generatePdf(1, $kernel, $twig);
$this->assertSame(302, $response->getStatusCode());
@array_map('unlink', glob($tmpDir.'/public/uploads/devis/*'));
@rmdir($tmpDir.'/public/uploads/devis');
@rmdir($tmpDir.'/public/uploads');
@rmdir($tmpDir.'/public');
@rmdir($tmpDir);
}
// ── search ──
public function testSearchEmptyQuery(): void
{
$em = $this->createStub(EntityManagerInterface::class);
$controller = $this->buildControllerWithEm($em);
$request = new Request(['q' => '']);
$response = $controller->search(1, $request);
$this->assertSame('[]', $response->getContent());
}
public function testSearchWhitespaceQuery(): void
{
$em = $this->createStub(EntityManagerInterface::class);
$controller = $this->buildControllerWithEm($em);
$request = new Request(['q' => ' ']);
$response = $controller->search(1, $request);
$this->assertSame('[]', $response->getContent());
}
public function testSearchWithResults(): void
{
$meilisearch = $this->createMock(\App\Service\MeilisearchService::class);
$meilisearch->method('searchDevis')->with('test', 20, 42)->willReturn([
['numOrder' => '04/2026-00001', 'customerName' => 'Client A'],
]);
$em = $this->createStub(EntityManagerInterface::class);
$controller = new DevisController(
$em,
$this->createStub(\App\Service\OrderNumberService::class),
$this->createStub(\App\Service\DevisService::class),
$meilisearch,
);
$session = new Session(new MockArraySessionStorage());
$stack = $this->createStub(RequestStack::class);
$stack->method('getSession')->willReturn($session);
$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', $this->createStub(Environment::class)],
['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);
$request = new Request(['q' => 'test']);
$response = $controller->search(42, $request);
$data = json_decode($response->getContent(), true);
$this->assertCount(1, $data);
$this->assertSame('04/2026-00001', $data[0]['numOrder']);
}
}