Files
crm_ecosplay/tests/Controller/Admin/DevisControllerTest.php

1385 lines
52 KiB
PHP
Raw Normal View History

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