422, meme // sur un POST (les champs Information n'y sont pas renseignables). $supplier = $this->minimalSupplier(); $supplier->setDescription('Une description'); // les autres champs Information restent null $processor = $this->makeProcessor( payload: ['description' => 'Une description'], user: $this->commercialeUser(), ); $this->expectException(ValidationException::class); $processor->process($supplier, $this->operation()); } public function testCommercialeIncompleteInformationOnMainOnlyPatchIsUnprocessable(): void { // RG-2.03 : pour une Commerciale, la completude Information est exigee // meme quand le payload ne touche PAS l'onglet Information (ici // companyName seul) -> 422. $supplier = $this->minimalSupplier(); $supplier->setCompanyName('Renamed Co'); $processor = $this->makeProcessor( granted: ['commercial.suppliers.manage'], payload: ['companyName' => 'Renamed Co'], user: $this->commercialeUser(), managed: true, originalData: [ 'companyName' => 'TEST CO', 'isArchived' => false, ], ); $this->expectException(ValidationException::class); $processor->process($supplier, $this->operation()); } public function testCommercialeCompleteInformationPasses(): void { // RG-2.03 satisfaite : tous les champs Information renseignes -> 200. $supplier = $this->completeInformationSupplier(); $processor = $this->makeProcessor( granted: ['commercial.suppliers.manage'], payload: ['description' => 'desc'], user: $this->commercialeUser(), ); self::assertInstanceOf(Supplier::class, $processor->process($supplier, $this->operation())); } public function testNonCommercialeSkipsInformationCompleteness(): void { // Meme onglet Information incomplet, mais user non-Commerciale -> aucun // blocage (la completude est specifique a la Commerciale). $supplier = $this->minimalSupplier(); $supplier->setDescription('Une description'); $processor = $this->makeProcessor( payload: ['description' => 'Une description'], user: null, ); self::assertInstanceOf(Supplier::class, $processor->process($supplier, $this->operation())); } /** * @param list $granted Permissions accordees a l'utilisateur courant * @param array $payload Corps JSON simule de la requete * @param bool $managed true = entite geree par l'ORM (PATCH), false = creation (POST) * @param array $originalData Etat persiste simule (getOriginalEntityData) */ private function makeProcessor( array $granted = [], array $payload = [], ?UserInterface $user = null, bool $managed = false, array $originalData = [], ): SupplierProcessor { $persist = new class implements ProcessorInterface { public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed { return $data; } }; $security = $this->createStub(Security::class); $security->method('isGranted')->willReturnCallback( static fn (mixed $attribute): bool => is_string($attribute) && in_array($attribute, $granted, true), ); $security->method('getUser')->willReturn($user); $requestStack = new RequestStack(); $requestStack->push(new Request([], [], [], [], [], [], json_encode($payload, JSON_THROW_ON_ERROR))); $uow = $this->createMock(UnitOfWork::class); $uow->method('getOriginalEntityData')->willReturn($originalData); $em = $this->createMock(EntityManagerInterface::class); $em->method('contains')->willReturn($managed); $em->method('getUnitOfWork')->willReturn($uow); return new SupplierProcessor( $persist, new SupplierFieldNormalizer(), new SupplierInformationCompletenessValidator(), $security, $requestStack, $em, ); } private function minimalSupplier(): Supplier { $supplier = new Supplier(); $supplier->setCompanyName('Test Co'); return $supplier; } private function completeInformationSupplier(): Supplier { $supplier = $this->minimalSupplier(); $supplier->setDescription('desc'); $supplier->setCompetitors('concurrents'); $supplier->setFoundedAt(new DateTimeImmutable('2010-01-01')); $supplier->setEmployeesCount(10); $supplier->setRevenueAmount('1000.00'); $supplier->setDirectorName('Marie Durand'); $supplier->setProfitAmount('100.00'); $supplier->setVolumeForecast(500); return $supplier; } private function operation(): Operation { return $this->createStub(Operation::class); } private function commercialeUser(): UserInterface { return new class implements UserInterface, BusinessRoleAwareInterface { public function hasBusinessRole(string $roleCode): bool { return BusinessRoles::COMMERCIALE === $roleCode; } public function getRoles(): array { return ['ROLE_USER']; } public function eraseCredentials(): void {} public function getUserIdentifier(): string { return 'commerciale-test'; } }; } }