Files
Lesstime/tests/Functional/Mcp/Directory/ContactLifecycleTest.php
T
matthieu aad949c10c
Pull Request — Quality gate / Frontend (build) (pull_request) Successful in 40s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m20s
test(directory) : tests fonctionnels MCP pour Prestataire/Contact/Address/CommercialReport
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.
2026-06-24 21:08:06 +02:00

217 lines
7.7 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\ClientRepositoryInterface;
use App\Module\Directory\Domain\Repository\ContactRepositoryInterface;
use App\Module\Directory\Domain\Repository\PrestataireRepositoryInterface;
use App\Module\Directory\Domain\Repository\ProspectRepositoryInterface;
use App\Module\Directory\Infrastructure\Mcp\Tool\CreateContactTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\DeleteContactTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\GetContactTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\ListContactsTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\UpdateContactTool;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Bundle\SecurityBundle\Security;
/**
* @internal
*/
class ContactLifecycleTest 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-contact-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, 'Anon');
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(), $this->prospect->getId(), null, '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 testCreateWithUnknownClientThrows(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Client with ID 999999 not found.');
($this->createTool())(999999, null, null, 'Anon');
}
public function testCreateOnEachParentWorks(): void
{
foreach (
[
['clientId', $this->client->getId()],
['prospectId', $this->prospect->getId()],
['prestataireId', $this->prestataire->getId()],
] as [$field, $id]
) {
$args = [null, null, null, 'John', 'Doe-'.$field, 'CTO', 'john@x.test'];
$idx = ['clientId' => 0, 'prospectId' => 1, 'prestataireId' => 2][$field];
$args[$idx] = $id;
$data = json_decode(($this->createTool())(...$args), true);
self::assertSame('Doe-'.$field, $data['lastName']);
self::assertSame($id, $data[$field]);
}
}
public function testGetReturnsContact(): void
{
$created = json_decode(($this->createTool())($this->client->getId(), null, null, 'Jane', 'Smith'), true);
$data = json_decode(($this->getTool())((int) $created['id']), true);
self::assertSame('Jane', $data['firstName']);
self::assertSame('Smith', $data['lastName']);
self::assertSame($this->client->getId(), $data['clientId']);
}
public function testListFilteredByPrestataire(): void
{
($this->createTool())(null, null, $this->prestataire->getId(), 'A', 'A-Last');
($this->createTool())(null, null, $this->prestataire->getId(), 'B', 'B-Last');
($this->createTool())($this->client->getId(), null, null, 'Z', 'Z-Last');
$data = json_decode(($this->listTool())(null, null, $this->prestataire->getId()), true);
self::assertCount(2, $data);
self::assertSame('A-Last', $data[0]['lastName']);
self::assertSame('B-Last', $data[1]['lastName']);
}
public function testListRejectsMultipleFilters(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('At most one of clientId, prospectId or prestataireId');
($this->listTool())($this->client->getId(), $this->prospect->getId(), null);
}
public function testUpdateOnlyTouchesProvidedFields(): void
{
$created = json_decode(($this->createTool())(null, null, $this->prestataire->getId(), 'Old', 'Last', 'CTO', 'old@x.test'), true);
$data = json_decode(($this->updateTool())((int) $created['id'], 'New', null, null, 'new@x.test'), true);
self::assertSame('New', $data['firstName']); // changed
self::assertSame('Last', $data['lastName']); // unchanged
self::assertSame('CTO', $data['jobTitle']); // unchanged
self::assertSame('new@x.test', $data['email']); // changed
}
public function testDeleteRemovesContact(): 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(ContactRepositoryInterface::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(): CreateContactTool
{
$c = self::getContainer();
return new CreateContactTool(
$this->em,
$c->get(ClientRepositoryInterface::class),
$c->get(ProspectRepositoryInterface::class),
$c->get(PrestataireRepositoryInterface::class),
$this->securityFor(),
);
}
private function getTool(): GetContactTool
{
return new GetContactTool(
self::getContainer()->get(ContactRepositoryInterface::class),
$this->securityFor(),
);
}
private function listTool(): ListContactsTool
{
return new ListContactsTool(
self::getContainer()->get(ContactRepositoryInterface::class),
$this->securityFor(),
);
}
private function updateTool(): UpdateContactTool
{
return new UpdateContactTool(
self::getContainer()->get(ContactRepositoryInterface::class),
$this->em,
$this->securityFor(),
);
}
private function deleteTool(): DeleteContactTool
{
return new DeleteContactTool(
self::getContainer()->get(ContactRepositoryInterface::class),
$this->em,
$this->securityFor(),
);
}
}