purge manuelle obligatoire. * * @internal */ abstract class AbstractCommercialApiTestCase extends AbstractApiTestCase { protected const string TEST_CATEGORY_PREFIX = 'test_cli_cat_'; /** * Codes pilotant les RG (RG-1.03 distributor/broker, RG-1.29 adresse) : ils * doivent matcher exactement, donc createCategory() les fetch-or-create par * code. Les autres codes sont traites comme de simples libelles generiques et * produisent une categorie a code UNIQUE (cf. createCategory). */ private const array RG_EXACT_CODES = ['DISTRIBUTEUR', 'COURTIER']; protected function tearDown(): void { $this->cleanupCommercialTestData(); parent::tearDown(); } protected function createAdminClient(): Client { return $this->authenticatedClient('admin', 'admin'); } /** * Recupere (ou cree) le type unique CLIENT (refonte ERP-78). Idempotent : la * contrainte d'unicite sur category_type.code interdit les doublons. */ protected function clientCategoryType(): CategoryType { $em = $this->getEm(); $existing = $em->getRepository(CategoryType::class)->findOneBy(['code' => 'CLIENT']); if (null !== $existing) { return $existing; } $type = new CategoryType(); $type->setCode('CLIENT'); $type->setLabel('Client'); $em->persist($type); $em->flush(); return $type; } /** * Cree une Category de test sous le type unique CLIENT (ERP-78). * * - Code RG (DISTRIBUTEUR / COURTIER) : fetch-or-create par code EXACT — le * code doit matcher la regle de gestion, et l'appel repete dans un test * renvoie la meme categorie (pas de violation de uq_category_code). * - Autre code (SECTEUR, AUTRE, ...) : simple libelle generique -> categorie * a code UNIQUE (suffixe aleatoire). Garantit que deux categories * « generiques » d'un meme test sont DISTINCTES (ex: detection de * changement de categorie dans les tests RBAC). */ protected function createCategory(string $code = 'SECTEUR'): Category { $em = $this->getEm(); if (in_array($code, self::RG_EXACT_CODES, true)) { $existing = $em->getRepository(Category::class)->findOneBy(['code' => $code, 'deletedAt' => null]); if (null !== $existing) { return $existing; } $effectiveCode = $code; $name = self::TEST_CATEGORY_PREFIX.strtolower($code); } else { $suffix = substr(bin2hex(random_bytes(4)), 0, 8); $effectiveCode = strtoupper($code).'_'.strtoupper($suffix); $name = self::TEST_CATEGORY_PREFIX.strtolower($code).'_'.$suffix; } $category = new Category(); $category->setName($name); $category->setCode($effectiveCode); $category->setCategoryType($this->clientCategoryType()); $em->persist($category); $em->flush(); return $category; } /** * Seede directement un Client en base (sans passer par l'API), pour les * tests de liste / archivage. Le client porte une categorie du code donne * (defaut SECTEUR — categorie generique non interdite sur adresse). */ protected function seedClient(string $companyName, bool $isArchived = false, string $categoryCode = 'SECTEUR'): ClientEntity { $em = $this->getEm(); $client = new ClientEntity(); // Stocke en MAJUSCULES pour refleter l'etat normalise (RG-1.18) qu'aurait // produit le ClientProcessor via l'API. $client->setCompanyName(mb_strtoupper($companyName, 'UTF-8')); $client->setLastName('Seed'); $client->setPhonePrimary('0102030405'); $client->setEmail(strtolower(str_replace(' ', '', $companyName)).'@seed.test'); $client->addCategory($this->createCategory($categoryCode)); $client->setIsArchived($isArchived); if ($isArchived) { $client->setArchivedAt(new DateTimeImmutable()); } $em->persist($client); $em->flush(); return $client; } private function cleanupCommercialTestData(): void { $em = $this->getEm(); // Clients d'abord (la jointure client_category est purgee par // ON DELETE CASCADE ; les auto-references distributor/broker sont // ON DELETE SET NULL). $em->createQuery('DELETE FROM '.ClientEntity::class)->execute(); // Categories de test ensuite (FK client_category deja purgee). $em->createQuery( 'DELETE FROM '.Category::class.' c WHERE c.name LIKE :prefix', )->setParameter('prefix', self::TEST_CATEGORY_PREFIX.'%')->execute(); // Users / roles jetables. $em->createQuery( 'DELETE FROM '.User::class.' u WHERE u.username LIKE :prefix', )->setParameter('prefix', 'test_%')->execute(); $em->createQuery( 'DELETE FROM '.Role::class.' r WHERE r.code LIKE :prefix', )->setParameter('prefix', 'test_%')->execute(); } }