getEm(); $em->getConnection()->executeStatement('DELETE FROM weighbridge_dsd_counter'); $em->createQuery('DELETE FROM '.User::class.' u WHERE u.username LIKE :p') ->setParameter('p', 'testuser_%')->execute() ; $em->createQuery('DELETE FROM '.Role::class.' r WHERE r.code LIKE :p') ->setParameter('p', 'test_%')->execute() ; parent::tearDown(); } public function testAutoWeighingReturnsWeightInBoundsAndDsd(): void { $client = $this->manageClientWithCurrentSite(); $response = $client->request('POST', '/api/weighbridge_readings', [ 'headers' => ['Content-Type' => 'application/ld+json'], 'json' => ['mode' => 'AUTO'], ]); self::assertResponseStatusCodeSame(200); $data = $response->toArray(); self::assertSame('AUTO', $data['mode']); self::assertIsInt($data['weight']); self::assertGreaterThanOrEqual(10000, $data['weight']); self::assertLessThanOrEqual(50000, $data['weight']); self::assertIsInt($data['dsd']); self::assertGreaterThanOrEqual(1, $data['dsd']); // manualNumber est null en mode bascule (cle potentiellement omise si // skip_null_values est actif — tolerant aux deux cas). self::assertNull($data['manualNumber'] ?? null); } public function testManualWeighingKeepsWeightAndAllocatesDsd(): void { $client = $this->manageClientWithCurrentSite(); $response = $client->request('POST', '/api/weighbridge_readings', [ 'headers' => ['Content-Type' => 'application/ld+json'], 'json' => ['mode' => 'MANUAL', 'weight' => 23187, 'manualNumber' => 'PAP-555'], ]); self::assertResponseStatusCodeSame(200); $data = $response->toArray(); self::assertSame('MANUAL', $data['mode']); self::assertSame(23187, $data['weight']); self::assertSame('PAP-555', $data['manualNumber']); self::assertGreaterThanOrEqual(1, $data['dsd']); } public function testManagePermissionIsRequired(): void { // Un user portant uniquement `view` ne peut pas declencher de pesee. $credentials = $this->createUserWithPermission('logistique.weighing_tickets.view'); $client = $this->authenticatedClient($credentials['username'], $credentials['password']); $client->request('POST', '/api/weighbridge_readings', [ 'headers' => ['Content-Type' => 'application/ld+json'], 'json' => ['mode' => 'AUTO'], ]); self::assertResponseStatusCodeSame(403); } public function testInvalidModeIsRejected(): void { $client = $this->manageClientWithCurrentSite(); $response = $client->request('POST', '/api/weighbridge_readings', [ 'headers' => ['Content-Type' => 'application/ld+json'], 'json' => ['mode' => 'INVALID'], ]); // Garde-fou ERP-101 : la 422 doit cibler `mode` (Assert\Choice), pas juste // un bon code HTTP — sinon une violation sur le mauvais champ passerait. self::assertResponseStatusCodeSame(422); self::assertViolationOnPath($response, 'mode'); } public function testManualWeighingRequiresWeight(): void { $client = $this->manageClientWithCurrentSite(); $response = $client->request('POST', '/api/weighbridge_readings', [ 'headers' => ['Content-Type' => 'application/ld+json'], 'json' => ['mode' => 'MANUAL'], ]); // Garde-fou ERP-101 : la 422 doit cibler `weight` (Callback validateManualWeight). self::assertResponseStatusCodeSame(422); self::assertViolationOnPath($response, 'weight'); } /** * Garde-fou ERP-101 (miroir AbstractWeighingTicketApiTestCase) : une 422 doit * porter une violation sur le `propertyPath` attendu, consommable inline par * useFormErrors cote front, pas seulement le bon statut HTTP. */ private static function assertViolationOnPath(object $response, string $path): void { $paths = array_column($response->toArray(false)['violations'] ?? [], 'propertyPath'); self::assertContains( $path, $paths, sprintf('Aucune violation sur "%s" (paths: %s).', $path, implode(', ', $paths)), ); } /** * Cree un user non-admin portant `logistique.weighing_tickets.manage`, lui * positionne un site courant (l'endpoint est cloisonne par site, § 2.3) et * renvoie un client authentifie. */ private function manageClientWithCurrentSite(): Client { $credentials = $this->createUserWithPermission('logistique.weighing_tickets.manage'); $em = $this->getEm(); $user = $em->getRepository(User::class)->findOneBy(['username' => $credentials['username']]); self::assertInstanceOf(User::class, $user); $site = $em->getRepository(Site::class)->findAll()[0]; $user->setCurrentSite($site); $em->flush(); return $this->authenticatedClient($credentials['username'], $credentials['password']); } }