Files
Lesstime/tests/Functional/Mcp/Directory/PrestataireLifecycleTest.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

184 lines
6.6 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\Repository\AddressRepositoryInterface;
use App\Module\Directory\Domain\Repository\CommercialReportRepositoryInterface;
use App\Module\Directory\Domain\Repository\ContactRepositoryInterface;
use App\Module\Directory\Domain\Repository\PrestataireRepositoryInterface;
use App\Module\Directory\Infrastructure\Mcp\Tool\CreatePrestataireTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\DeletePrestataireTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\GetPrestataireTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\ListPrestatairesTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\UpdatePrestataireTool;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
/**
* @internal
*/
class PrestataireLifecycleTest extends KernelTestCase
{
private EntityManagerInterface $em;
private User $admin;
protected function setUp(): void
{
self::bootKernel();
$this->em = self::getContainer()->get(EntityManagerInterface::class);
$this->admin = new User();
$this->admin->setUsername('mcp-prest-admin-'.uniqid());
$this->admin->setPassword('x');
$this->admin->setRoles(['ROLE_ADMIN']);
$this->em->persist($this->admin);
$this->em->flush();
}
public function testCreatePersistsAllFields(): void
{
$json = ($this->createTool(admin: true))('ACME Cleaning', 'contact@acme.example', '+33100000000', 'https://acme.example');
$data = json_decode($json, true);
self::assertIsInt($data['id']);
self::assertSame('ACME Cleaning', $data['name']);
self::assertSame('contact@acme.example', $data['email']);
self::assertSame('+33100000000', $data['phone']);
self::assertSame('https://acme.example', $data['website']);
}
public function testCreateRequiresAdmin(): void
{
$this->expectException(AccessDeniedException::class);
($this->createTool(admin: false))('Should not pass');
}
public function testGetReturnsEmptyCollectionsWhenNoChildren(): void
{
$created = json_decode(($this->createTool(admin: true))('Lonely Prest'), true);
$json = ($this->getTool(admin: true))((int) $created['id']);
$data = json_decode($json, true);
self::assertSame($created['id'], $data['id']);
self::assertSame([], $data['contacts']);
self::assertSame([], $data['addresses']);
self::assertSame([], $data['reports']);
}
public function testGetUnknownIdThrows(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Prestataire with ID 999999 not found.');
($this->getTool(admin: true))(999999);
}
public function testUpdateOnlyTouchesProvidedFields(): void
{
$created = json_decode(($this->createTool(admin: true))('Before', 'before@x.test', '+33000000000', 'https://before.test'), true);
$json = ($this->updateTool(admin: true))((int) $created['id'], null, 'after@x.test', null, null);
$data = json_decode($json, true);
self::assertSame('Before', $data['name']); // unchanged
self::assertSame('after@x.test', $data['email']); // changed
self::assertSame('+33000000000', $data['phone']); // unchanged
self::assertSame('https://before.test', $data['website']); // unchanged
}
public function testListReturnsAllPrestatairesOrderedByName(): void
{
// Unique prefix isolates this test from data leaked by prior PHPUnit
// runs (DAMA rollback is not active in this project).
$prefix = 'list-test-'.uniqid().'-';
($this->createTool(admin: true))($prefix.'Zeta');
($this->createTool(admin: true))($prefix.'Alpha');
($this->createTool(admin: true))($prefix.'Mu');
$data = json_decode(($this->listTool(admin: true))(), true);
$names = array_values(array_filter(
array_column($data, 'name'),
fn ($n) => str_starts_with((string) $n, $prefix),
));
self::assertSame([$prefix.'Alpha', $prefix.'Mu', $prefix.'Zeta'], $names);
}
public function testDeleteRemovesPrestataire(): void
{
$created = json_decode(($this->createTool(admin: true))('To be removed'), true);
$id = (int) $created['id'];
$json = ($this->deleteTool(admin: true))($id);
$data = json_decode($json, true);
self::assertTrue($data['success']);
self::assertStringContainsString('"To be removed"', $data['message']);
$this->em->clear();
self::assertNull(self::getContainer()->get(PrestataireRepositoryInterface::class)->findById($id));
}
private function securityFor(bool $admin): Security
{
$security = $this->createMock(Security::class);
$security->method('isGranted')->willReturn($admin);
$security->method('getUser')->willReturn($admin ? $this->admin : null);
return $security;
}
private function createTool(bool $admin): CreatePrestataireTool
{
return new CreatePrestataireTool(
$this->em,
$this->securityFor($admin),
);
}
private function getTool(bool $admin): GetPrestataireTool
{
$c = self::getContainer();
return new GetPrestataireTool(
$c->get(PrestataireRepositoryInterface::class),
$c->get(ContactRepositoryInterface::class),
$c->get(AddressRepositoryInterface::class),
$c->get(CommercialReportRepositoryInterface::class),
$this->securityFor($admin),
);
}
private function updateTool(bool $admin): UpdatePrestataireTool
{
return new UpdatePrestataireTool(
self::getContainer()->get(PrestataireRepositoryInterface::class),
$this->em,
$this->securityFor($admin),
);
}
private function listTool(bool $admin): ListPrestatairesTool
{
return new ListPrestatairesTool(
self::getContainer()->get(PrestataireRepositoryInterface::class),
$this->securityFor($admin),
);
}
private function deleteTool(bool $admin): DeletePrestataireTool
{
return new DeletePrestataireTool(
self::getContainer()->get(PrestataireRepositoryInterface::class),
$this->em,
$this->securityFor($admin),
);
}
}