From 2f173e766d08de9843005da762c2cc9ecf541315 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 16 Mar 2026 14:38:55 +0100 Subject: [PATCH] feat(mcp) : add CRUD tools for Pieces, Composants, Machines - 5 tools each: list, get, create, update, delete - Piece: includes typePiece, constructeurs, prix (string) - Composant: includes typeComposant, constructeurs, prix (string) - Machine: includes site (required), constructeurs - 40 MCP tests pass total Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Tool/Composant/CreateComposantTool.php | 80 +++++++++++++ .../Tool/Composant/DeleteComposantTool.php | 42 +++++++ src/Mcp/Tool/Composant/GetComposantTool.php | 59 ++++++++++ src/Mcp/Tool/Composant/ListComposantsTool.php | 55 +++++++++ .../Tool/Composant/UpdateComposantTool.php | 93 +++++++++++++++ src/Mcp/Tool/Machine/CreateMachineTool.php | 74 ++++++++++++ src/Mcp/Tool/Machine/DeleteMachineTool.php | 42 +++++++ src/Mcp/Tool/Machine/GetMachineTool.php | 58 ++++++++++ src/Mcp/Tool/Machine/ListMachinesTool.php | 55 +++++++++ src/Mcp/Tool/Machine/UpdateMachineTool.php | 85 ++++++++++++++ src/Mcp/Tool/Piece/CreatePieceTool.php | 80 +++++++++++++ src/Mcp/Tool/Piece/DeletePieceTool.php | 42 +++++++ src/Mcp/Tool/Piece/GetPieceTool.php | 59 ++++++++++ src/Mcp/Tool/Piece/ListPiecesTool.php | 55 +++++++++ src/Mcp/Tool/Piece/UpdatePieceTool.php | 93 +++++++++++++++ .../Tool/Composant/ComposantsCrudToolTest.php | 99 ++++++++++++++++ .../Mcp/Tool/Machine/MachinesCrudToolTest.php | 107 ++++++++++++++++++ tests/Mcp/Tool/Piece/PiecesCrudToolTest.php | 99 ++++++++++++++++ 18 files changed, 1277 insertions(+) create mode 100644 src/Mcp/Tool/Composant/CreateComposantTool.php create mode 100644 src/Mcp/Tool/Composant/DeleteComposantTool.php create mode 100644 src/Mcp/Tool/Composant/GetComposantTool.php create mode 100644 src/Mcp/Tool/Composant/ListComposantsTool.php create mode 100644 src/Mcp/Tool/Composant/UpdateComposantTool.php create mode 100644 src/Mcp/Tool/Machine/CreateMachineTool.php create mode 100644 src/Mcp/Tool/Machine/DeleteMachineTool.php create mode 100644 src/Mcp/Tool/Machine/GetMachineTool.php create mode 100644 src/Mcp/Tool/Machine/ListMachinesTool.php create mode 100644 src/Mcp/Tool/Machine/UpdateMachineTool.php create mode 100644 src/Mcp/Tool/Piece/CreatePieceTool.php create mode 100644 src/Mcp/Tool/Piece/DeletePieceTool.php create mode 100644 src/Mcp/Tool/Piece/GetPieceTool.php create mode 100644 src/Mcp/Tool/Piece/ListPiecesTool.php create mode 100644 src/Mcp/Tool/Piece/UpdatePieceTool.php create mode 100644 tests/Mcp/Tool/Composant/ComposantsCrudToolTest.php create mode 100644 tests/Mcp/Tool/Machine/MachinesCrudToolTest.php create mode 100644 tests/Mcp/Tool/Piece/PiecesCrudToolTest.php diff --git a/src/Mcp/Tool/Composant/CreateComposantTool.php b/src/Mcp/Tool/Composant/CreateComposantTool.php new file mode 100644 index 0000000..adcd93e --- /dev/null +++ b/src/Mcp/Tool/Composant/CreateComposantTool.php @@ -0,0 +1,80 @@ +requireRole($this->security, 'ROLE_GESTIONNAIRE'); + + $composant = new Composant(); + $composant->setName($name); + + if ('' !== $reference) { + $composant->setReference($reference); + } + if ('' !== $description) { + $composant->setDescription($description); + } + if ('' !== $prix) { + $composant->setPrix($prix); + } + + if ('' !== $modelTypeId) { + $modelType = $this->modelTypes->find($modelTypeId); + if (!$modelType) { + $this->mcpError('not_found', "ModelType not found: {$modelTypeId}"); + } + $composant->setTypeComposant($modelType); + } + + foreach ($constructeurIds as $cId) { + $c = $this->constructeurs->find($cId); + if (!$c) { + $this->mcpError('not_found', "Constructeur not found: {$cId}"); + } + $composant->addConstructeur($c); + } + + $this->em->persist($composant); + $this->em->flush(); + + return $this->jsonResponse([ + 'id' => $composant->getId(), + 'name' => $composant->getName(), + ]); + } +} diff --git a/src/Mcp/Tool/Composant/DeleteComposantTool.php b/src/Mcp/Tool/Composant/DeleteComposantTool.php new file mode 100644 index 0000000..dff0c06 --- /dev/null +++ b/src/Mcp/Tool/Composant/DeleteComposantTool.php @@ -0,0 +1,42 @@ +requireRole($this->security, 'ROLE_GESTIONNAIRE'); + + $composant = $this->composants->find($composantId); + + if (!$composant) { + $this->mcpError('not_found', "Composant not found: {$composantId}"); + } + + $this->em->remove($composant); + $this->em->flush(); + + return $this->jsonResponse(['deleted' => true, 'id' => $composantId]); + } +} diff --git a/src/Mcp/Tool/Composant/GetComposantTool.php b/src/Mcp/Tool/Composant/GetComposantTool.php new file mode 100644 index 0000000..ef74ba0 --- /dev/null +++ b/src/Mcp/Tool/Composant/GetComposantTool.php @@ -0,0 +1,59 @@ +composants->find($composantId); + + if (!$composant) { + $this->mcpError('not_found', "Composant not found: {$composantId}"); + } + + $constructeurs = []; + foreach ($composant->getConstructeurs() as $c) { + $constructeurs[] = [ + 'id' => $c->getId(), + 'name' => $c->getName(), + ]; + } + + $typeComposant = null; + if ($composant->getTypeComposant()) { + $typeComposant = [ + 'id' => $composant->getTypeComposant()->getId(), + 'name' => $composant->getTypeComposant()->getName(), + ]; + } + + return $this->jsonResponse([ + 'id' => $composant->getId(), + 'name' => $composant->getName(), + 'reference' => $composant->getReference(), + 'description' => $composant->getDescription(), + 'prix' => $composant->getPrix(), + 'typeComposant' => $typeComposant, + 'constructeurs' => $constructeurs, + 'createdAt' => $composant->getCreatedAt()->format('Y-m-d H:i:s'), + 'updatedAt' => $composant->getUpdatedAt()->format('Y-m-d H:i:s'), + ]); + } +} diff --git a/src/Mcp/Tool/Composant/ListComposantsTool.php b/src/Mcp/Tool/Composant/ListComposantsTool.php new file mode 100644 index 0000000..3ab6051 --- /dev/null +++ b/src/Mcp/Tool/Composant/ListComposantsTool.php @@ -0,0 +1,55 @@ +paginationParams($page, $limit); + + $countQb = $this->composants->createQueryBuilder('c') + ->select('COUNT(c.id)') + ; + + $qb = $this->composants->createQueryBuilder('c') + ->select('c.id', 'c.name', 'c.reference', 'c.prix') + ->orderBy('c.name', 'ASC') + ; + + if ('' !== $search) { + $countQb->andWhere('LOWER(c.name) LIKE LOWER(:search) OR LOWER(c.reference) LIKE LOWER(:search)') + ->setParameter('search', "%{$search}%") + ; + $qb->andWhere('LOWER(c.name) LIKE LOWER(:search) OR LOWER(c.reference) LIKE LOWER(:search)') + ->setParameter('search', "%{$search}%") + ; + } + + $total = (int) $countQb->getQuery()->getSingleScalarResult(); + + $items = $qb->setFirstResult($p['offset']) + ->setMaxResults($p['limit']) + ->getQuery() + ->getArrayResult() + ; + + return $this->paginatedResponse($items, $total, $p['page'], $p['limit']); + } +} diff --git a/src/Mcp/Tool/Composant/UpdateComposantTool.php b/src/Mcp/Tool/Composant/UpdateComposantTool.php new file mode 100644 index 0000000..e174441 --- /dev/null +++ b/src/Mcp/Tool/Composant/UpdateComposantTool.php @@ -0,0 +1,93 @@ +requireRole($this->security, 'ROLE_GESTIONNAIRE'); + + $composant = $this->composants->find($composantId); + + if (!$composant) { + $this->mcpError('not_found', "Composant not found: {$composantId}"); + } + + if (null !== $name) { + $composant->setName($name); + } + if (null !== $reference) { + $composant->setReference($reference); + } + if (null !== $description) { + $composant->setDescription($description); + } + if (null !== $prix) { + $composant->setPrix($prix); + } + + if (null !== $modelTypeId) { + if ('' === $modelTypeId) { + $composant->setTypeComposant(null); + } else { + $modelType = $this->modelTypes->find($modelTypeId); + if (!$modelType) { + $this->mcpError('not_found', "ModelType not found: {$modelTypeId}"); + } + $composant->setTypeComposant($modelType); + } + } + + if (null !== $constructeurIds) { + foreach ($composant->getConstructeurs()->toArray() as $existing) { + $composant->removeConstructeur($existing); + } + foreach ($constructeurIds as $cId) { + $c = $this->constructeurs->find($cId); + if (!$c) { + $this->mcpError('not_found', "Constructeur not found: {$cId}"); + } + $composant->addConstructeur($c); + } + } + + $this->em->flush(); + + return $this->jsonResponse(['id' => $composant->getId(), 'name' => $composant->getName()]); + } +} diff --git a/src/Mcp/Tool/Machine/CreateMachineTool.php b/src/Mcp/Tool/Machine/CreateMachineTool.php new file mode 100644 index 0000000..fc31f42 --- /dev/null +++ b/src/Mcp/Tool/Machine/CreateMachineTool.php @@ -0,0 +1,74 @@ +requireRole($this->security, 'ROLE_GESTIONNAIRE'); + + $site = $this->sites->find($siteId); + if (!$site) { + $this->mcpError('not_found', "Site not found: {$siteId}"); + } + + $machine = new Machine(); + $machine->setName($name); + $machine->setSite($site); + + if ('' !== $reference) { + $machine->setReference($reference); + } + if ('' !== $prix) { + $machine->setPrix($prix); + } + + foreach ($constructeurIds as $cId) { + $c = $this->constructeurs->find($cId); + if (!$c) { + $this->mcpError('not_found', "Constructeur not found: {$cId}"); + } + $machine->addConstructeur($c); + } + + $this->em->persist($machine); + $this->em->flush(); + + return $this->jsonResponse([ + 'id' => $machine->getId(), + 'name' => $machine->getName(), + ]); + } +} diff --git a/src/Mcp/Tool/Machine/DeleteMachineTool.php b/src/Mcp/Tool/Machine/DeleteMachineTool.php new file mode 100644 index 0000000..61988d2 --- /dev/null +++ b/src/Mcp/Tool/Machine/DeleteMachineTool.php @@ -0,0 +1,42 @@ +requireRole($this->security, 'ROLE_GESTIONNAIRE'); + + $machine = $this->machines->find($machineId); + + if (!$machine) { + $this->mcpError('not_found', "Machine not found: {$machineId}"); + } + + $this->em->remove($machine); + $this->em->flush(); + + return $this->jsonResponse(['deleted' => true, 'id' => $machineId]); + } +} diff --git a/src/Mcp/Tool/Machine/GetMachineTool.php b/src/Mcp/Tool/Machine/GetMachineTool.php new file mode 100644 index 0000000..ebea397 --- /dev/null +++ b/src/Mcp/Tool/Machine/GetMachineTool.php @@ -0,0 +1,58 @@ +machines->find($machineId); + + if (!$machine) { + $this->mcpError('not_found', "Machine not found: {$machineId}"); + } + + $constructeurs = []; + foreach ($machine->getConstructeurs() as $c) { + $constructeurs[] = [ + 'id' => $c->getId(), + 'name' => $c->getName(), + ]; + } + + $site = null; + if ($machine->getSite()) { + $site = [ + 'id' => $machine->getSite()->getId(), + 'name' => $machine->getSite()->getName(), + ]; + } + + return $this->jsonResponse([ + 'id' => $machine->getId(), + 'name' => $machine->getName(), + 'reference' => $machine->getReference(), + 'prix' => $machine->getPrix(), + 'site' => $site, + 'constructeurs' => $constructeurs, + 'createdAt' => $machine->getCreatedAt()->format('Y-m-d H:i:s'), + 'updatedAt' => $machine->getUpdatedAt()->format('Y-m-d H:i:s'), + ]); + } +} diff --git a/src/Mcp/Tool/Machine/ListMachinesTool.php b/src/Mcp/Tool/Machine/ListMachinesTool.php new file mode 100644 index 0000000..9513f05 --- /dev/null +++ b/src/Mcp/Tool/Machine/ListMachinesTool.php @@ -0,0 +1,55 @@ +paginationParams($page, $limit); + + $countQb = $this->machines->createQueryBuilder('m') + ->select('COUNT(m.id)') + ; + + $qb = $this->machines->createQueryBuilder('m') + ->select('m.id', 'm.name', 'm.reference', 'm.prix') + ->orderBy('m.name', 'ASC') + ; + + if ('' !== $search) { + $countQb->andWhere('LOWER(m.name) LIKE LOWER(:search) OR LOWER(m.reference) LIKE LOWER(:search)') + ->setParameter('search', "%{$search}%") + ; + $qb->andWhere('LOWER(m.name) LIKE LOWER(:search) OR LOWER(m.reference) LIKE LOWER(:search)') + ->setParameter('search', "%{$search}%") + ; + } + + $total = (int) $countQb->getQuery()->getSingleScalarResult(); + + $items = $qb->setFirstResult($p['offset']) + ->setMaxResults($p['limit']) + ->getQuery() + ->getArrayResult() + ; + + return $this->paginatedResponse($items, $total, $p['page'], $p['limit']); + } +} diff --git a/src/Mcp/Tool/Machine/UpdateMachineTool.php b/src/Mcp/Tool/Machine/UpdateMachineTool.php new file mode 100644 index 0000000..27e01fe --- /dev/null +++ b/src/Mcp/Tool/Machine/UpdateMachineTool.php @@ -0,0 +1,85 @@ +requireRole($this->security, 'ROLE_GESTIONNAIRE'); + + $machine = $this->machines->find($machineId); + + if (!$machine) { + $this->mcpError('not_found', "Machine not found: {$machineId}"); + } + + if (null !== $name) { + $machine->setName($name); + } + if (null !== $reference) { + $machine->setReference($reference); + } + if (null !== $prix) { + $machine->setPrix($prix); + } + + if (null !== $siteId) { + $site = $this->sites->find($siteId); + if (!$site) { + $this->mcpError('not_found', "Site not found: {$siteId}"); + } + $machine->setSite($site); + } + + if (null !== $constructeurIds) { + foreach ($machine->getConstructeurs()->toArray() as $existing) { + $machine->removeConstructeur($existing); + } + foreach ($constructeurIds as $cId) { + $c = $this->constructeurs->find($cId); + if (!$c) { + $this->mcpError('not_found', "Constructeur not found: {$cId}"); + } + $machine->addConstructeur($c); + } + } + + $this->em->flush(); + + return $this->jsonResponse(['id' => $machine->getId(), 'name' => $machine->getName()]); + } +} diff --git a/src/Mcp/Tool/Piece/CreatePieceTool.php b/src/Mcp/Tool/Piece/CreatePieceTool.php new file mode 100644 index 0000000..7207d20 --- /dev/null +++ b/src/Mcp/Tool/Piece/CreatePieceTool.php @@ -0,0 +1,80 @@ +requireRole($this->security, 'ROLE_GESTIONNAIRE'); + + $piece = new Piece(); + $piece->setName($name); + + if ('' !== $reference) { + $piece->setReference($reference); + } + if ('' !== $description) { + $piece->setDescription($description); + } + if ('' !== $prix) { + $piece->setPrix($prix); + } + + if ('' !== $modelTypeId) { + $modelType = $this->modelTypes->find($modelTypeId); + if (!$modelType) { + $this->mcpError('not_found', "ModelType not found: {$modelTypeId}"); + } + $piece->setTypePiece($modelType); + } + + foreach ($constructeurIds as $cId) { + $c = $this->constructeurs->find($cId); + if (!$c) { + $this->mcpError('not_found', "Constructeur not found: {$cId}"); + } + $piece->addConstructeur($c); + } + + $this->em->persist($piece); + $this->em->flush(); + + return $this->jsonResponse([ + 'id' => $piece->getId(), + 'name' => $piece->getName(), + ]); + } +} diff --git a/src/Mcp/Tool/Piece/DeletePieceTool.php b/src/Mcp/Tool/Piece/DeletePieceTool.php new file mode 100644 index 0000000..384d5df --- /dev/null +++ b/src/Mcp/Tool/Piece/DeletePieceTool.php @@ -0,0 +1,42 @@ +requireRole($this->security, 'ROLE_GESTIONNAIRE'); + + $piece = $this->pieces->find($pieceId); + + if (!$piece) { + $this->mcpError('not_found', "Piece not found: {$pieceId}"); + } + + $this->em->remove($piece); + $this->em->flush(); + + return $this->jsonResponse(['deleted' => true, 'id' => $pieceId]); + } +} diff --git a/src/Mcp/Tool/Piece/GetPieceTool.php b/src/Mcp/Tool/Piece/GetPieceTool.php new file mode 100644 index 0000000..be43163 --- /dev/null +++ b/src/Mcp/Tool/Piece/GetPieceTool.php @@ -0,0 +1,59 @@ +pieces->find($pieceId); + + if (!$piece) { + $this->mcpError('not_found', "Piece not found: {$pieceId}"); + } + + $constructeurs = []; + foreach ($piece->getConstructeurs() as $c) { + $constructeurs[] = [ + 'id' => $c->getId(), + 'name' => $c->getName(), + ]; + } + + $typePiece = null; + if ($piece->getTypePiece()) { + $typePiece = [ + 'id' => $piece->getTypePiece()->getId(), + 'name' => $piece->getTypePiece()->getName(), + ]; + } + + return $this->jsonResponse([ + 'id' => $piece->getId(), + 'name' => $piece->getName(), + 'reference' => $piece->getReference(), + 'description' => $piece->getDescription(), + 'prix' => $piece->getPrix(), + 'typePiece' => $typePiece, + 'constructeurs' => $constructeurs, + 'createdAt' => $piece->getCreatedAt()->format('Y-m-d H:i:s'), + 'updatedAt' => $piece->getUpdatedAt()->format('Y-m-d H:i:s'), + ]); + } +} diff --git a/src/Mcp/Tool/Piece/ListPiecesTool.php b/src/Mcp/Tool/Piece/ListPiecesTool.php new file mode 100644 index 0000000..ffd913b --- /dev/null +++ b/src/Mcp/Tool/Piece/ListPiecesTool.php @@ -0,0 +1,55 @@ +paginationParams($page, $limit); + + $countQb = $this->pieces->createQueryBuilder('pi') + ->select('COUNT(pi.id)') + ; + + $qb = $this->pieces->createQueryBuilder('pi') + ->select('pi.id', 'pi.name', 'pi.reference', 'pi.prix') + ->orderBy('pi.name', 'ASC') + ; + + if ('' !== $search) { + $countQb->andWhere('LOWER(pi.name) LIKE LOWER(:search) OR LOWER(pi.reference) LIKE LOWER(:search)') + ->setParameter('search', "%{$search}%") + ; + $qb->andWhere('LOWER(pi.name) LIKE LOWER(:search) OR LOWER(pi.reference) LIKE LOWER(:search)') + ->setParameter('search', "%{$search}%") + ; + } + + $total = (int) $countQb->getQuery()->getSingleScalarResult(); + + $items = $qb->setFirstResult($p['offset']) + ->setMaxResults($p['limit']) + ->getQuery() + ->getArrayResult() + ; + + return $this->paginatedResponse($items, $total, $p['page'], $p['limit']); + } +} diff --git a/src/Mcp/Tool/Piece/UpdatePieceTool.php b/src/Mcp/Tool/Piece/UpdatePieceTool.php new file mode 100644 index 0000000..8d76175 --- /dev/null +++ b/src/Mcp/Tool/Piece/UpdatePieceTool.php @@ -0,0 +1,93 @@ +requireRole($this->security, 'ROLE_GESTIONNAIRE'); + + $piece = $this->pieces->find($pieceId); + + if (!$piece) { + $this->mcpError('not_found', "Piece not found: {$pieceId}"); + } + + if (null !== $name) { + $piece->setName($name); + } + if (null !== $reference) { + $piece->setReference($reference); + } + if (null !== $description) { + $piece->setDescription($description); + } + if (null !== $prix) { + $piece->setPrix($prix); + } + + if (null !== $modelTypeId) { + if ('' === $modelTypeId) { + $piece->setTypePiece(null); + } else { + $modelType = $this->modelTypes->find($modelTypeId); + if (!$modelType) { + $this->mcpError('not_found', "ModelType not found: {$modelTypeId}"); + } + $piece->setTypePiece($modelType); + } + } + + if (null !== $constructeurIds) { + foreach ($piece->getConstructeurs()->toArray() as $existing) { + $piece->removeConstructeur($existing); + } + foreach ($constructeurIds as $cId) { + $c = $this->constructeurs->find($cId); + if (!$c) { + $this->mcpError('not_found', "Constructeur not found: {$cId}"); + } + $piece->addConstructeur($c); + } + } + + $this->em->flush(); + + return $this->jsonResponse(['id' => $piece->getId(), 'name' => $piece->getName()]); + } +} diff --git a/tests/Mcp/Tool/Composant/ComposantsCrudToolTest.php b/tests/Mcp/Tool/Composant/ComposantsCrudToolTest.php new file mode 100644 index 0000000..8effc57 --- /dev/null +++ b/tests/Mcp/Tool/Composant/ComposantsCrudToolTest.php @@ -0,0 +1,99 @@ +createComposant(name: 'Composant Alpha'); + $this->createComposant(name: 'Composant Beta'); + $session = $this->createMcpClient('ROLE_VIEWER'); + + $data = $this->callMcpTool($session, 'list_composants'); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertGreaterThanOrEqual(2, $data['_parsed']['total']); + } + + public function testGetComposant(): void + { + $constructeur = $this->createConstructeur(name: 'Fournisseur Comp'); + $modelType = $this->createModelType(name: 'Type Composant', code: 'TC-001', category: ModelCategory::COMPONENT); + $composant = $this->createComposant(name: 'Composant Gamma', type: $modelType); + + // Add constructeur to composant + $composant->addConstructeur($constructeur); + $this->getEntityManager()->flush(); + + $session = $this->createMcpClient('ROLE_VIEWER'); + + $data = $this->callMcpTool($session, 'get_composant', ['composantId' => $composant->getId()]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertSame('Composant Gamma', $data['_parsed']['name']); + $this->assertNotNull($data['_parsed']['typeComposant']); + $this->assertSame('Type Composant', $data['_parsed']['typeComposant']['name']); + $this->assertCount(1, $data['_parsed']['constructeurs']); + $this->assertSame('Fournisseur Comp', $data['_parsed']['constructeurs'][0]['name']); + } + + public function testCreateComposant(): void + { + $session = $this->createMcpClient('ROLE_GESTIONNAIRE'); + + $data = $this->callMcpTool($session, 'create_composant', [ + 'name' => 'Composant Nouveau', + 'reference' => 'REF-COMP', + 'description' => 'Un composant de test', + 'prix' => '42.99', + ]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertSame('Composant Nouveau', $data['_parsed']['name']); + $this->assertNotEmpty($data['_parsed']['id']); + } + + public function testCreateComposantRequiresGestionnaire(): void + { + $session = $this->createMcpClient('ROLE_VIEWER'); + + $data = $this->callMcpTool($session, 'create_composant', ['name' => 'Forbidden']); + + $this->assertArrayHasKey('error', $data, 'Should fail with VIEWER role'); + } + + public function testUpdateComposant(): void + { + $composant = $this->createComposant(name: 'Old Composant'); + $session = $this->createMcpClient('ROLE_GESTIONNAIRE'); + + $data = $this->callMcpTool($session, 'update_composant', [ + 'composantId' => $composant->getId(), + 'name' => 'Updated Composant', + 'prix' => '99.00', + ]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertSame('Updated Composant', $data['_parsed']['name']); + } + + public function testDeleteComposant(): void + { + $composant = $this->createComposant(name: 'To Delete'); + $session = $this->createMcpClient('ROLE_GESTIONNAIRE'); + + $data = $this->callMcpTool($session, 'delete_composant', ['composantId' => $composant->getId()]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertTrue($data['_parsed']['deleted']); + } +} diff --git a/tests/Mcp/Tool/Machine/MachinesCrudToolTest.php b/tests/Mcp/Tool/Machine/MachinesCrudToolTest.php new file mode 100644 index 0000000..84fa134 --- /dev/null +++ b/tests/Mcp/Tool/Machine/MachinesCrudToolTest.php @@ -0,0 +1,107 @@ +createSite(name: 'Site Usine'); + $this->createMachine(name: 'Machine Alpha', site: $site); + $this->createMachine(name: 'Machine Beta', site: $site); + $session = $this->createMcpClient('ROLE_VIEWER'); + + $data = $this->callMcpTool($session, 'list_machines'); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertGreaterThanOrEqual(2, $data['_parsed']['total']); + } + + public function testGetMachine(): void + { + $site = $this->createSite(name: 'Site Principal'); + $constructeur = $this->createConstructeur(name: 'Fournisseur M'); + $machine = $this->createMachine(name: 'Machine Gamma', site: $site, reference: 'REF-M001'); + + // Add constructeur to machine + $machine->addConstructeur($constructeur); + $this->getEntityManager()->flush(); + + $session = $this->createMcpClient('ROLE_VIEWER'); + + $data = $this->callMcpTool($session, 'get_machine', ['machineId' => $machine->getId()]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertSame('Machine Gamma', $data['_parsed']['name']); + $this->assertSame('REF-M001', $data['_parsed']['reference']); + $this->assertNotNull($data['_parsed']['site']); + $this->assertSame('Site Principal', $data['_parsed']['site']['name']); + $this->assertCount(1, $data['_parsed']['constructeurs']); + $this->assertSame('Fournisseur M', $data['_parsed']['constructeurs'][0]['name']); + } + + public function testCreateMachine(): void + { + $site = $this->createSite(name: 'Site Création'); + $session = $this->createMcpClient('ROLE_GESTIONNAIRE'); + + $data = $this->callMcpTool($session, 'create_machine', [ + 'name' => 'Machine Nouvelle', + 'siteId' => $site->getId(), + 'reference' => 'REF-NEW', + 'prix' => '42.99', + ]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertSame('Machine Nouvelle', $data['_parsed']['name']); + $this->assertNotEmpty($data['_parsed']['id']); + } + + public function testCreateMachineRequiresGestionnaire(): void + { + $site = $this->createSite(name: 'Site Forbidden'); + $session = $this->createMcpClient('ROLE_VIEWER'); + + $data = $this->callMcpTool($session, 'create_machine', [ + 'name' => 'Forbidden', + 'siteId' => $site->getId(), + ]); + + $this->assertArrayHasKey('error', $data, 'Should fail with VIEWER role'); + } + + public function testUpdateMachine(): void + { + $site = $this->createSite(name: 'Site Update'); + $machine = $this->createMachine(name: 'Old Machine', site: $site); + $session = $this->createMcpClient('ROLE_GESTIONNAIRE'); + + $data = $this->callMcpTool($session, 'update_machine', [ + 'machineId' => $machine->getId(), + 'name' => 'Updated Machine', + 'prix' => '99.00', + ]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertSame('Updated Machine', $data['_parsed']['name']); + } + + public function testDeleteMachine(): void + { + $site = $this->createSite(name: 'Site Delete'); + $machine = $this->createMachine(name: 'To Delete', site: $site); + $session = $this->createMcpClient('ROLE_GESTIONNAIRE'); + + $data = $this->callMcpTool($session, 'delete_machine', ['machineId' => $machine->getId()]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertTrue($data['_parsed']['deleted']); + } +} diff --git a/tests/Mcp/Tool/Piece/PiecesCrudToolTest.php b/tests/Mcp/Tool/Piece/PiecesCrudToolTest.php new file mode 100644 index 0000000..8483c3b --- /dev/null +++ b/tests/Mcp/Tool/Piece/PiecesCrudToolTest.php @@ -0,0 +1,99 @@ +createPiece(name: 'Piece Alpha'); + $this->createPiece(name: 'Piece Beta', reference: 'REF-BETA'); + $session = $this->createMcpClient('ROLE_VIEWER'); + + $data = $this->callMcpTool($session, 'list_pieces'); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertGreaterThanOrEqual(2, $data['_parsed']['total']); + } + + public function testGetPiece(): void + { + $constructeur = $this->createConstructeur(name: 'Fournisseur Piece'); + $modelType = $this->createModelType(name: 'Type Piece', code: 'TP-001', category: ModelCategory::PIECE); + $piece = $this->createPiece(name: 'Piece Gamma', reference: 'REF-001', type: $modelType); + + // Add constructeur to piece + $piece->addConstructeur($constructeur); + $this->getEntityManager()->flush(); + + $session = $this->createMcpClient('ROLE_VIEWER'); + + $data = $this->callMcpTool($session, 'get_piece', ['pieceId' => $piece->getId()]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertSame('Piece Gamma', $data['_parsed']['name']); + $this->assertSame('REF-001', $data['_parsed']['reference']); + $this->assertNotNull($data['_parsed']['typePiece']); + $this->assertSame('Type Piece', $data['_parsed']['typePiece']['name']); + $this->assertCount(1, $data['_parsed']['constructeurs']); + $this->assertSame('Fournisseur Piece', $data['_parsed']['constructeurs'][0]['name']); + } + + public function testCreatePiece(): void + { + $session = $this->createMcpClient('ROLE_GESTIONNAIRE'); + + $data = $this->callMcpTool($session, 'create_piece', [ + 'name' => 'Piece Nouveau', + 'reference' => 'REF-NEW', + 'prix' => '42.99', + ]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertSame('Piece Nouveau', $data['_parsed']['name']); + $this->assertNotEmpty($data['_parsed']['id']); + } + + public function testCreatePieceRequiresGestionnaire(): void + { + $session = $this->createMcpClient('ROLE_VIEWER'); + + $data = $this->callMcpTool($session, 'create_piece', ['name' => 'Forbidden']); + + $this->assertArrayHasKey('error', $data, 'Should fail with VIEWER role'); + } + + public function testUpdatePiece(): void + { + $piece = $this->createPiece(name: 'Old Piece'); + $session = $this->createMcpClient('ROLE_GESTIONNAIRE'); + + $data = $this->callMcpTool($session, 'update_piece', [ + 'pieceId' => $piece->getId(), + 'name' => 'Updated Piece', + 'prix' => '99.00', + ]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertSame('Updated Piece', $data['_parsed']['name']); + } + + public function testDeletePiece(): void + { + $piece = $this->createPiece(name: 'To Delete'); + $session = $this->createMcpClient('ROLE_GESTIONNAIRE'); + + $data = $this->callMcpTool($session, 'delete_piece', ['pieceId' => $piece->getId()]); + + $this->assertArrayHasKey('_parsed', $data); + $this->assertTrue($data['_parsed']['deleted']); + } +}