aad949c10c
Couvre les 20 nouveaux outils MCP Directory (5 par entite : create/get/list/ update/delete) avec un focus sur les guards et invariants : - exactly-one-parent (Contact/Address/CommercialReport) - ROLE_ADMIN - ISO 3166 alpha-2 + normalisation uppercase (Address) - enum ReportType + defaults note/today + parsing date (CommercialReport) - author auto-rempli par CommercialReportAuthorListener (token storage) - collections vides dans get-prestataire enrichi - ordre DESC sur occurredAt pour list-commercial-reports - delete renvoie null apres em.clear() 38 tests / 105 assertions. Suite complete passe a 217/217.
230 lines
8.5 KiB
PHP
230 lines
8.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Functional\Mcp\Directory;
|
|
|
|
use App\Module\Core\Domain\Entity\User;
|
|
use App\Module\Directory\Domain\Entity\Client;
|
|
use App\Module\Directory\Domain\Entity\Prestataire;
|
|
use App\Module\Directory\Domain\Entity\Prospect;
|
|
use App\Module\Directory\Domain\Repository\AddressRepositoryInterface;
|
|
use App\Module\Directory\Domain\Repository\ClientRepositoryInterface;
|
|
use App\Module\Directory\Domain\Repository\PrestataireRepositoryInterface;
|
|
use App\Module\Directory\Domain\Repository\ProspectRepositoryInterface;
|
|
use App\Module\Directory\Infrastructure\Mcp\Tool\CreateAddressTool;
|
|
use App\Module\Directory\Infrastructure\Mcp\Tool\DeleteAddressTool;
|
|
use App\Module\Directory\Infrastructure\Mcp\Tool\GetAddressTool;
|
|
use App\Module\Directory\Infrastructure\Mcp\Tool\ListAddressesTool;
|
|
use App\Module\Directory\Infrastructure\Mcp\Tool\UpdateAddressTool;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use InvalidArgumentException;
|
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
|
use Symfony\Bundle\SecurityBundle\Security;
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
class AddressLifecycleTest extends KernelTestCase
|
|
{
|
|
private EntityManagerInterface $em;
|
|
private User $admin;
|
|
private Client $client;
|
|
private Prospect $prospect;
|
|
private Prestataire $prestataire;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
self::bootKernel();
|
|
$this->em = self::getContainer()->get(EntityManagerInterface::class);
|
|
|
|
$this->admin = new User();
|
|
$this->admin->setUsername('mcp-address-admin-'.uniqid());
|
|
$this->admin->setPassword('x');
|
|
$this->admin->setRoles(['ROLE_ADMIN']);
|
|
$this->em->persist($this->admin);
|
|
|
|
$this->client = new Client();
|
|
$this->client->setName('Test Client '.uniqid());
|
|
$this->em->persist($this->client);
|
|
|
|
$this->prospect = new Prospect();
|
|
$this->prospect->setCompany('Test Prospect '.uniqid());
|
|
$this->em->persist($this->prospect);
|
|
|
|
$this->prestataire = new Prestataire();
|
|
$this->prestataire->setName('Test Prestataire '.uniqid());
|
|
$this->em->persist($this->prestataire);
|
|
|
|
$this->em->flush();
|
|
}
|
|
|
|
public function testCreateRequiresExactlyOneParent(): void
|
|
{
|
|
try {
|
|
($this->createTool())(null, null, null, 'Home');
|
|
self::fail('Expected error when no parent provided.');
|
|
} catch (InvalidArgumentException $e) {
|
|
self::assertStringContainsString('Exactly one of clientId, prospectId or prestataireId', $e->getMessage());
|
|
}
|
|
|
|
try {
|
|
($this->createTool())($this->client->getId(), null, $this->prestataire->getId(), 'Dup');
|
|
self::fail('Expected error when two parents provided.');
|
|
} catch (InvalidArgumentException $e) {
|
|
self::assertStringContainsString('Exactly one of clientId, prospectId or prestataireId', $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function testCreateCountryDefaultsToFRWhenOmitted(): void
|
|
{
|
|
$data = json_decode(($this->createTool())($this->client->getId(), null, null, 'HQ'), true);
|
|
|
|
self::assertSame('FR', $data['country']);
|
|
}
|
|
|
|
public function testCreateRejectsNonIso3166Country(): void
|
|
{
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$this->expectExceptionMessage('country must be a 2-letter ISO 3166 alpha-2 code');
|
|
($this->createTool())($this->client->getId(), null, null, 'HQ', null, null, null, null, 'France');
|
|
}
|
|
|
|
public function testCreateNormalizesCountryToUppercase(): void
|
|
{
|
|
$data = json_decode(($this->createTool())($this->client->getId(), null, null, 'HQ', null, null, null, null, 'be'), true);
|
|
|
|
self::assertSame('BE', $data['country']);
|
|
}
|
|
|
|
public function testCreateOnEachParentWorks(): void
|
|
{
|
|
$clientAddr = json_decode(($this->createTool())($this->client->getId(), null, null, 'CHQ'), true);
|
|
self::assertSame($this->client->getId(), $clientAddr['clientId']);
|
|
self::assertNull($clientAddr['prospectId']);
|
|
|
|
$prospectAddr = json_decode(($this->createTool())(null, $this->prospect->getId(), null, 'PHQ'), true);
|
|
self::assertSame($this->prospect->getId(), $prospectAddr['prospectId']);
|
|
|
|
$prestAddr = json_decode(($this->createTool())(null, null, $this->prestataire->getId(), 'XHQ'), true);
|
|
self::assertSame($this->prestataire->getId(), $prestAddr['prestataireId']);
|
|
}
|
|
|
|
public function testGetReturnsAddress(): void
|
|
{
|
|
$created = json_decode(($this->createTool())($this->client->getId(), null, null, 'Office', '1 rue X', null, '75001', 'Paris', 'FR'), true);
|
|
|
|
$data = json_decode(($this->getTool())((int) $created['id']), true);
|
|
|
|
self::assertSame('Office', $data['label']);
|
|
self::assertSame('1 rue X', $data['street']);
|
|
self::assertSame('75001', $data['postalCode']);
|
|
self::assertSame('Paris', $data['city']);
|
|
self::assertSame('FR', $data['country']);
|
|
}
|
|
|
|
public function testListFilteredByClient(): void
|
|
{
|
|
($this->createTool())($this->client->getId(), null, null, 'A');
|
|
($this->createTool())($this->client->getId(), null, null, 'B');
|
|
($this->createTool())(null, null, $this->prestataire->getId(), 'Z');
|
|
|
|
$data = json_decode(($this->listTool())($this->client->getId(), null, null), true);
|
|
|
|
self::assertCount(2, $data);
|
|
self::assertSame('A', $data[0]['label']);
|
|
self::assertSame('B', $data[1]['label']);
|
|
}
|
|
|
|
public function testUpdateRejectsNonIso3166Country(): void
|
|
{
|
|
$created = json_decode(($this->createTool())($this->client->getId(), null, null, 'X'), true);
|
|
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$this->expectExceptionMessage('country must be a 2-letter ISO 3166 alpha-2 code');
|
|
($this->updateTool())((int) $created['id'], null, null, null, null, null, 'Belgium');
|
|
}
|
|
|
|
public function testUpdateOnlyTouchesProvidedFields(): void
|
|
{
|
|
$created = json_decode(($this->createTool())($this->client->getId(), null, null, 'Old', '1 rue X', null, '75001', 'Paris', 'FR'), true);
|
|
|
|
$data = json_decode(($this->updateTool())((int) $created['id'], 'New', null, null, '75002', null, 'be'), true);
|
|
|
|
self::assertSame('New', $data['label']); // changed
|
|
self::assertSame('1 rue X', $data['street']); // unchanged
|
|
self::assertSame('75002', $data['postalCode']); // changed
|
|
self::assertSame('Paris', $data['city']); // unchanged
|
|
self::assertSame('BE', $data['country']); // changed + uppercased
|
|
}
|
|
|
|
public function testDeleteRemovesAddress(): void
|
|
{
|
|
$created = json_decode(($this->createTool())($this->client->getId(), null, null, 'Bye'), true);
|
|
$id = (int) $created['id'];
|
|
|
|
$data = json_decode(($this->deleteTool())($id), true);
|
|
|
|
self::assertTrue($data['success']);
|
|
|
|
$this->em->clear();
|
|
self::assertNull(self::getContainer()->get(AddressRepositoryInterface::class)->findById($id));
|
|
}
|
|
|
|
private function securityFor(bool $admin = true): Security
|
|
{
|
|
$security = $this->createMock(Security::class);
|
|
$security->method('isGranted')->willReturn($admin);
|
|
$security->method('getUser')->willReturn($admin ? $this->admin : null);
|
|
|
|
return $security;
|
|
}
|
|
|
|
private function createTool(): CreateAddressTool
|
|
{
|
|
$c = self::getContainer();
|
|
|
|
return new CreateAddressTool(
|
|
$this->em,
|
|
$c->get(ClientRepositoryInterface::class),
|
|
$c->get(ProspectRepositoryInterface::class),
|
|
$c->get(PrestataireRepositoryInterface::class),
|
|
$this->securityFor(),
|
|
);
|
|
}
|
|
|
|
private function getTool(): GetAddressTool
|
|
{
|
|
return new GetAddressTool(
|
|
self::getContainer()->get(AddressRepositoryInterface::class),
|
|
$this->securityFor(),
|
|
);
|
|
}
|
|
|
|
private function listTool(): ListAddressesTool
|
|
{
|
|
return new ListAddressesTool(
|
|
self::getContainer()->get(AddressRepositoryInterface::class),
|
|
$this->securityFor(),
|
|
);
|
|
}
|
|
|
|
private function updateTool(): UpdateAddressTool
|
|
{
|
|
return new UpdateAddressTool(
|
|
self::getContainer()->get(AddressRepositoryInterface::class),
|
|
$this->em,
|
|
$this->securityFor(),
|
|
);
|
|
}
|
|
|
|
private function deleteTool(): DeleteAddressTool
|
|
{
|
|
return new DeleteAddressTool(
|
|
self::getContainer()->get(AddressRepositoryInterface::class),
|
|
$this->em,
|
|
$this->securityFor(),
|
|
);
|
|
}
|
|
}
|