Add SIRET/RNA verification, organizer management, registration flow pages
SIRET/RNA verification: - Create SiretService with API gouv lookup + JOAFE RNA lookup + cache pool (24h) - Verification page: declared info vs API data side by side - Display NAF code + label (from naf.json), nature juridique code + label - Association/Entreprise/EI badges, ESS badge, RNA, coordonnees lat/long - JOAFE section: objet, regime, domaine, dates, lieu, PDF download link - Tranche effectif with readable labels - Refresh cache button - Page restricted to non-approved organizers only Organizer approval flow: - Approval form with offer (free/basic/custom) and commission rate (default 3%) - Add commissionRate field to User entity + migration - Rejection form with required reason textarea, sent in email - Edit page for approved organizers: all fields modifiable - Modify button in approved organizers table Registration flow pages: - Post-registration success page with email verification message - Organizer gets additional 48h staff review notice - Post-email-verification page: confirmed for buyers, 48h notice for organizers Dashboard: - Simplified Meilisearch sync to single button Tests: SiretServiceTest (9), AdminControllerTest (31), RegistrationControllerTest updated, UserTest updated Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -404,6 +404,122 @@ class AdminControllerTest extends WebTestCase
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
public function testSiretCheckPage(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$admin = $this->createUser(['ROLE_ROOT']);
|
||||
$orga = $this->createOrganizer($em);
|
||||
|
||||
$client->loginUser($admin);
|
||||
$client->request('GET', '/admin/organisateur/'.$orga->getId().'/siret');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
public function testSiretCheckRedirectsIfApproved(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$admin = $this->createUser(['ROLE_ROOT']);
|
||||
$orga = $this->createOrganizer($em);
|
||||
$orga->setIsApproved(true);
|
||||
$em->flush();
|
||||
|
||||
$client->loginUser($admin);
|
||||
$client->request('GET', '/admin/organisateur/'.$orga->getId().'/siret');
|
||||
|
||||
self::assertResponseRedirects('/admin/organisateurs');
|
||||
}
|
||||
|
||||
public function testSiretCheckWithoutSiret(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$admin = $this->createUser(['ROLE_ROOT']);
|
||||
|
||||
$orga = new User();
|
||||
$orga->setEmail('test-no-siret-'.uniqid().'@example.com');
|
||||
$orga->setFirstName('No');
|
||||
$orga->setLastName('Siret');
|
||||
$orga->setPassword('$2y$13$hashed');
|
||||
$orga->setRoles(['ROLE_ORGANIZER']);
|
||||
$em->persist($orga);
|
||||
$em->flush();
|
||||
|
||||
$client->loginUser($admin);
|
||||
$client->request('GET', '/admin/organisateur/'.$orga->getId().'/siret');
|
||||
|
||||
self::assertResponseRedirects('/admin/organisateurs');
|
||||
}
|
||||
|
||||
public function testSiretRefresh(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$admin = $this->createUser(['ROLE_ROOT']);
|
||||
$orga = $this->createOrganizer($em);
|
||||
|
||||
$client->loginUser($admin);
|
||||
$client->request('POST', '/admin/organisateur/'.$orga->getId().'/siret/refresh');
|
||||
|
||||
self::assertResponseRedirects('/admin/organisateur/'.$orga->getId().'/siret');
|
||||
}
|
||||
|
||||
public function testEditOrganizerPage(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$admin = $this->createUser(['ROLE_ROOT']);
|
||||
$orga = $this->createOrganizer($em);
|
||||
$orga->setIsApproved(true);
|
||||
$orga->setOffer('free');
|
||||
$orga->setCommissionRate(3.0);
|
||||
$em->flush();
|
||||
|
||||
$client->loginUser($admin);
|
||||
$client->request('GET', '/admin/organisateur/'.$orga->getId().'/modifier');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
public function testEditOrganizerSubmit(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$admin = $this->createUser(['ROLE_ROOT']);
|
||||
$orga = $this->createOrganizer($em);
|
||||
$orga->setIsApproved(true);
|
||||
$orga->setOffer('free');
|
||||
$orga->setCommissionRate(3.0);
|
||||
$em->flush();
|
||||
|
||||
$client->loginUser($admin);
|
||||
$client->request('POST', '/admin/organisateur/'.$orga->getId().'/modifier', [
|
||||
'offer' => 'custom',
|
||||
'commission_rate' => '0.5',
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/admin/organisateurs?tab=approved');
|
||||
|
||||
$em->refresh($orga);
|
||||
self::assertSame('custom', $orga->getOffer());
|
||||
self::assertSame(0.5, $orga->getCommissionRate());
|
||||
}
|
||||
|
||||
public function testEditOrganizerRedirectsIfNotApproved(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$admin = $this->createUser(['ROLE_ROOT']);
|
||||
$orga = $this->createOrganizer($em);
|
||||
|
||||
$client->loginUser($admin);
|
||||
$client->request('GET', '/admin/organisateur/'.$orga->getId().'/modifier');
|
||||
|
||||
self::assertResponseRedirects('/admin/organisateurs');
|
||||
}
|
||||
|
||||
public function testApproveOrganizer(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
@@ -422,12 +538,17 @@ class AdminControllerTest extends WebTestCase
|
||||
static::getContainer()->set(MeilisearchService::class, $meilisearch);
|
||||
|
||||
$client->loginUser($admin);
|
||||
$client->request('POST', '/admin/organisateur/'.$orga->getId().'/approuver');
|
||||
$client->request('POST', '/admin/organisateur/'.$orga->getId().'/approuver', [
|
||||
'offer' => 'basic',
|
||||
'commission_rate' => '1.5',
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/admin/organisateurs');
|
||||
|
||||
$em->refresh($orga);
|
||||
self::assertTrue($orga->isApproved());
|
||||
self::assertSame('basic', $orga->getOffer());
|
||||
self::assertSame(1.5, $orga->getCommissionRate());
|
||||
}
|
||||
|
||||
public function testRejectOrganizer(): void
|
||||
@@ -444,7 +565,9 @@ class AdminControllerTest extends WebTestCase
|
||||
static::getContainer()->set(MailerService::class, $mailer);
|
||||
|
||||
$client->loginUser($admin);
|
||||
$client->request('POST', '/admin/organisateur/'.$orgaId.'/refuser');
|
||||
$client->request('POST', '/admin/organisateur/'.$orgaId.'/refuser', [
|
||||
'reason' => 'SIRET invalide, activite non conforme.',
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/admin/organisateurs');
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Tests\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Service\MailerService;
|
||||
use App\Service\MeilisearchService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
@@ -51,7 +52,8 @@ class RegistrationControllerTest extends WebTestCase
|
||||
'password' => 'Password123!',
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/connexion');
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorTextContains('h1', 'Compte cree');
|
||||
}
|
||||
|
||||
public function testRegistrationAsOrganizer(): void
|
||||
@@ -76,7 +78,9 @@ class RegistrationControllerTest extends WebTestCase
|
||||
'phone' => '0612345678',
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/connexion');
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorTextContains('h1', 'Compte cree');
|
||||
self::assertSelectorTextContains('body', '48h');
|
||||
}
|
||||
|
||||
public function testRegistrationWithDuplicateEmail(): void
|
||||
@@ -110,6 +114,9 @@ class RegistrationControllerTest extends WebTestCase
|
||||
$client = static::createClient();
|
||||
$em = static::getContainer()->get(EntityManagerInterface::class);
|
||||
|
||||
$meilisearch = $this->createMock(MeilisearchService::class);
|
||||
static::getContainer()->set(MeilisearchService::class, $meilisearch);
|
||||
|
||||
$user = new User();
|
||||
$user->setEmail('test-verify-'.uniqid().'@example.com');
|
||||
$user->setFirstName('Test');
|
||||
@@ -122,7 +129,8 @@ class RegistrationControllerTest extends WebTestCase
|
||||
$token = $user->getEmailVerificationToken();
|
||||
$client->request('GET', '/verification-email/'.$token);
|
||||
|
||||
self::assertResponseRedirects('/connexion');
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorTextContains('h1', 'Email verifie');
|
||||
|
||||
$em->refresh($user);
|
||||
self::assertTrue($user->isVerified());
|
||||
@@ -139,6 +147,9 @@ class RegistrationControllerTest extends WebTestCase
|
||||
$mailer->expects(self::exactly(2))->method('sendEmail');
|
||||
static::getContainer()->set(MailerService::class, $mailer);
|
||||
|
||||
$meilisearch = $this->createMock(MeilisearchService::class);
|
||||
static::getContainer()->set(MeilisearchService::class, $meilisearch);
|
||||
|
||||
$user = new User();
|
||||
$user->setEmail('test-orga-verify-'.uniqid().'@example.com');
|
||||
$user->setFirstName('Marie');
|
||||
@@ -158,7 +169,8 @@ class RegistrationControllerTest extends WebTestCase
|
||||
$token = $user->getEmailVerificationToken();
|
||||
$client->request('GET', '/verification-email/'.$token);
|
||||
|
||||
self::assertResponseRedirects('/connexion');
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorTextContains('body', '48h');
|
||||
}
|
||||
|
||||
public function testVerifyEmailWithInvalidToken(): void
|
||||
|
||||
@@ -136,12 +136,14 @@ class UserTest extends TestCase
|
||||
|
||||
self::assertFalse($user->isApproved());
|
||||
self::assertNull($user->getOffer());
|
||||
self::assertNull($user->getCommissionRate());
|
||||
|
||||
$result = $user->setIsApproved(true)->setOffer('custom');
|
||||
$result = $user->setIsApproved(true)->setOffer('custom')->setCommissionRate(1.5);
|
||||
|
||||
self::assertSame($user, $result);
|
||||
self::assertTrue($user->isApproved());
|
||||
self::assertSame('custom', $user->getOffer());
|
||||
self::assertSame(1.5, $user->getCommissionRate());
|
||||
}
|
||||
|
||||
public function testEmailVerificationFields(): void
|
||||
|
||||
153
tests/Service/SiretServiceTest.php
Normal file
153
tests/Service/SiretServiceTest.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\Service\SiretService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
class SiretServiceTest extends TestCase
|
||||
{
|
||||
public function testLookupReturnsDataWithLabels(): void
|
||||
{
|
||||
$response = $this->createMock(ResponseInterface::class);
|
||||
$response->method('toArray')->willReturn([
|
||||
'results' => [[
|
||||
'nom_complet' => 'E-COSPLAY',
|
||||
'nature_juridique' => '9220',
|
||||
'activite_principale' => '93.29Z',
|
||||
'siren' => '943121517',
|
||||
]],
|
||||
]);
|
||||
|
||||
$httpClient = $this->createMock(HttpClientInterface::class);
|
||||
$httpClient->method('request')->willReturn($response);
|
||||
|
||||
$cache = $this->createMock(CacheInterface::class);
|
||||
$cache->method('get')->willReturnCallback(fn (string $key, callable $callback) => $callback($this->createMock(ItemInterface::class)));
|
||||
|
||||
$projectDir = \dirname(__DIR__, 2);
|
||||
$service = new SiretService($httpClient, $cache, $projectDir);
|
||||
$data = $service->lookup('94312151700016');
|
||||
|
||||
self::assertNotNull($data);
|
||||
self::assertSame('E-COSPLAY', $data['nom_complet']);
|
||||
self::assertSame('Association declaree', $data['libelle_nature_juridique']);
|
||||
self::assertNotNull($data['libelle_activite_principale']);
|
||||
}
|
||||
|
||||
public function testLookupReturnsNullWhenNotFound(): void
|
||||
{
|
||||
$response = $this->createMock(ResponseInterface::class);
|
||||
$response->method('toArray')->willReturn(['results' => []]);
|
||||
|
||||
$httpClient = $this->createMock(HttpClientInterface::class);
|
||||
$httpClient->method('request')->willReturn($response);
|
||||
|
||||
$cache = $this->createMock(CacheInterface::class);
|
||||
$cache->method('get')->willReturnCallback(fn (string $key, callable $callback) => $callback($this->createMock(ItemInterface::class)));
|
||||
|
||||
$service = new SiretService($httpClient, $cache, \dirname(__DIR__, 2));
|
||||
|
||||
self::assertNull($service->lookup('00000000000000'));
|
||||
}
|
||||
|
||||
public function testLookupReturnsNullOnApiError(): void
|
||||
{
|
||||
$httpClient = $this->createMock(HttpClientInterface::class);
|
||||
$httpClient->method('request')->willThrowException(new \RuntimeException('API down'));
|
||||
|
||||
$cache = $this->createMock(CacheInterface::class);
|
||||
$cache->method('get')->willReturnCallback(fn (string $key, callable $callback) => $callback($this->createMock(ItemInterface::class)));
|
||||
|
||||
$service = new SiretService($httpClient, $cache, \dirname(__DIR__, 2));
|
||||
|
||||
self::assertNull($service->lookup('00000000000000'));
|
||||
}
|
||||
|
||||
public function testGetNatureJuridiqueLabel(): void
|
||||
{
|
||||
$httpClient = $this->createMock(HttpClientInterface::class);
|
||||
$cache = $this->createMock(CacheInterface::class);
|
||||
$service = new SiretService($httpClient, $cache, \dirname(__DIR__, 2));
|
||||
|
||||
self::assertSame('Association declaree', $service->getNatureJuridiqueLabel('9220'));
|
||||
self::assertSame('SAS', $service->getNatureJuridiqueLabel('5710'));
|
||||
self::assertNull($service->getNatureJuridiqueLabel('0000'));
|
||||
}
|
||||
|
||||
public function testGetNafLabel(): void
|
||||
{
|
||||
$httpClient = $this->createMock(HttpClientInterface::class);
|
||||
$cache = $this->createMock(CacheInterface::class);
|
||||
$service = new SiretService($httpClient, $cache, \dirname(__DIR__, 2));
|
||||
|
||||
self::assertNotNull($service->getNafLabel('93.29Z'));
|
||||
self::assertNull($service->getNafLabel('XX.XXX'));
|
||||
}
|
||||
|
||||
public function testLookupRnaReturnsData(): void
|
||||
{
|
||||
$response = $this->createMock(ResponseInterface::class);
|
||||
$response->method('toArray')->willReturn([
|
||||
'records' => [[
|
||||
'fields' => [
|
||||
'numero_rna' => 'W022006988',
|
||||
'objet' => 'promotion du cosplay',
|
||||
'association_type_libelle' => 'Associations loi du 1er juillet 1901',
|
||||
],
|
||||
]],
|
||||
]);
|
||||
|
||||
$httpClient = $this->createMock(HttpClientInterface::class);
|
||||
$httpClient->method('request')->willReturn($response);
|
||||
|
||||
$cache = $this->createMock(CacheInterface::class);
|
||||
$cache->method('get')->willReturnCallback(fn (string $key, callable $callback) => $callback($this->createMock(ItemInterface::class)));
|
||||
|
||||
$service = new SiretService($httpClient, $cache, \dirname(__DIR__, 2));
|
||||
$data = $service->lookupRna('W022006988');
|
||||
|
||||
self::assertNotNull($data);
|
||||
self::assertSame('W022006988', $data['numero_rna']);
|
||||
self::assertSame('promotion du cosplay', $data['objet']);
|
||||
}
|
||||
|
||||
public function testLookupRnaReturnsNullWhenNotFound(): void
|
||||
{
|
||||
$response = $this->createMock(ResponseInterface::class);
|
||||
$response->method('toArray')->willReturn(['records' => []]);
|
||||
|
||||
$httpClient = $this->createMock(HttpClientInterface::class);
|
||||
$httpClient->method('request')->willReturn($response);
|
||||
|
||||
$cache = $this->createMock(CacheInterface::class);
|
||||
$cache->method('get')->willReturnCallback(fn (string $key, callable $callback) => $callback($this->createMock(ItemInterface::class)));
|
||||
|
||||
$service = new SiretService($httpClient, $cache, \dirname(__DIR__, 2));
|
||||
|
||||
self::assertNull($service->lookupRna('W000000000'));
|
||||
}
|
||||
|
||||
public function testClearCacheWithRna(): void
|
||||
{
|
||||
$httpClient = $this->createMock(HttpClientInterface::class);
|
||||
$cache = $this->createMock(CacheInterface::class);
|
||||
$cache->expects(self::exactly(2))->method('delete');
|
||||
|
||||
$service = new SiretService($httpClient, $cache, \dirname(__DIR__, 2));
|
||||
$service->clearCache('12345678901234', 'W022006988');
|
||||
}
|
||||
|
||||
public function testGetNafLabelMissingFile(): void
|
||||
{
|
||||
$httpClient = $this->createMock(HttpClientInterface::class);
|
||||
$cache = $this->createMock(CacheInterface::class);
|
||||
$service = new SiretService($httpClient, $cache, '/nonexistent');
|
||||
|
||||
self::assertNull($service->getNafLabel('93.29Z'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user