diff --git a/tests/Controller/Admin/DevisControllerTest.php b/tests/Controller/Admin/DevisControllerTest.php new file mode 100644 index 0000000..974c8ba --- /dev/null +++ b/tests/Controller/Admin/DevisControllerTest.php @@ -0,0 +1,1216 @@ +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(''); + + $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('email'); + + $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('email'); + + $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()); + } +}