cleanupFieldSalesTestData(); parent::tearDown(); } /** * Seede un Client minimal (companyName uniquement — les categories sont une * contrainte Assert non rejouee hors API). */ protected function seedClient(string $companyName): Client { $em = $this->getEm(); $client = new Client(); $client->setCompanyName(self::TEST_CLIENT_PREFIX.mb_strtoupper($companyName, 'UTF-8')); $em->persist($client); $em->flush(); return $client; } /** * Seede une adresse de prospection geolocalisee rattachee a $client. Les * sites/categories (Assert\Count min 1) ne sont pas rejoues hors API : on * persiste directement les colonnes NOT NULL + des coordonnees. */ protected function seedClientAddress(Client $client, float $lat = 47.218, float $lng = -1.553): ClientAddress { $em = $this->getEm(); $address = new ClientAddress(); $address->setClient($client); $address->setIsProspect(true); $address->setPostalCode('44000'); $address->setCity('NANTES'); $address->setStreet('1 rue de Test'); $address->setLatitude($lat); $address->setLongitude($lng); $em->persist($address); $em->flush(); return $address; } /** * Seede une tournee appartenant a $owner (sans passer par l'API). */ protected function seedTour(User $owner, string $label = 'Tournée test'): Tour { $em = $this->getEm(); $tour = new Tour(); $tour->setOwner($owner); $tour->setLabel($label); $tour->setTourDate(new DateTimeImmutable('2026-07-01')); $em->persist($tour); $em->flush(); return $tour; } /** * Recupere un User par username (ex: 'admin', ou un username jetable cree par * createUserWithPermission). */ protected function getUserByUsername(string $username): User { $user = $this->getEm()->getRepository(User::class)->findOneBy(['username' => $username]); self::assertInstanceOf(User::class, $user, sprintf('User "%s" introuvable.', $username)); return $user; } /** * Indexe les violations d'un corps 422 par propertyPath. * * @param array $body * * @return array */ protected function violationsByPath(array $body): array { $byPath = []; foreach ($body['violations'] ?? [] as $v) { $byPath[$v['propertyPath']] = $v['message']; } return $byPath; } private function cleanupFieldSalesTestData(): void { $em = $this->getEm(); // Etapes purgees par CASCADE a la suppression des tournees. $em->createQuery('DELETE FROM '.Tour::class)->execute(); // Adresses puis clients de test (FK client_address.client_id CASCADE). $em->createQuery( 'DELETE FROM '.ClientAddress::class.' a WHERE a.client IN (' .'SELECT c.id FROM '.Client::class.' c WHERE c.companyName LIKE :prefix)', )->setParameter('prefix', self::TEST_CLIENT_PREFIX.'%')->execute(); $em->createQuery( 'DELETE FROM '.Client::class.' c WHERE c.companyName LIKE :prefix', )->setParameter('prefix', self::TEST_CLIENT_PREFIX.'%')->execute(); $em->createQuery( 'DELETE FROM '.User::class.' u WHERE u.username LIKE :prefix', )->setParameter('prefix', 'testuser_%')->execute(); $em->createQuery( 'DELETE FROM '.Role::class.' r WHERE r.code LIKE :prefix', )->setParameter('prefix', 'test_%')->execute(); } }