|
16 | 16 | use ApiPlatform\JsonSchema\Schema; |
17 | 17 | use ApiPlatform\JsonSchema\SchemaFactoryInterface; |
18 | 18 | use ApiPlatform\Mcp\JsonSchema\SchemaFactory; |
| 19 | +use ApiPlatform\Metadata\McpResource; |
| 20 | +use ApiPlatform\Metadata\McpTool; |
| 21 | +use ApiPlatform\Metadata\McpToolCollection; |
19 | 22 | use PHPUnit\Framework\TestCase; |
20 | 23 |
|
21 | 24 | class SchemaFactoryTest extends TestCase |
@@ -422,4 +425,68 @@ public function testRefInsideAnyOfIsResolved(): void |
422 | 425 | $this->assertSame(['type' => 'null'], $related['anyOf'][1]); |
423 | 426 | $this->assertArrayNotHasKey('type', $related); |
424 | 427 | } |
| 428 | + |
| 429 | + /** |
| 430 | + * A single-item MCP operation has no routed item URI (Mcp\Routing\IriConverter returns null), |
| 431 | + * so the output schema must advertise `gen_id` as false to drop the required `@id`. |
| 432 | + */ |
| 433 | + public function testSingleItemMcpOperationForcesGenIdFalse(): void |
| 434 | + { |
| 435 | + foreach ([new McpTool(class: 'App\\Dummy'), new McpResource(uri: 'app://dummy', class: 'App\\Dummy')] as $operation) { |
| 436 | + $captured = null; |
| 437 | + $inner = $this->createMock(SchemaFactoryInterface::class); |
| 438 | + $inner->method('buildSchema')->willReturnCallback(function (...$args) use (&$captured) { |
| 439 | + $captured = $args[5] ?? null; |
| 440 | + |
| 441 | + return $this->emptyObjectSchema(); |
| 442 | + }); |
| 443 | + |
| 444 | + $factory = new SchemaFactory($inner); |
| 445 | + $factory->buildSchema('App\\Dummy', 'jsonld', Schema::TYPE_OUTPUT, $operation); |
| 446 | + |
| 447 | + $this->assertFalse($captured['gen_id'] ?? null, $operation::class.' must force gen_id to false'); |
| 448 | + } |
| 449 | + } |
| 450 | + |
| 451 | + public function testMcpToolCollectionDoesNotForceGenIdFalse(): void |
| 452 | + { |
| 453 | + $captured = 'untouched'; |
| 454 | + $inner = $this->createMock(SchemaFactoryInterface::class); |
| 455 | + $inner->method('buildSchema')->willReturnCallback(function (...$args) use (&$captured) { |
| 456 | + $captured = $args[5] ?? null; |
| 457 | + |
| 458 | + return $this->emptyObjectSchema(); |
| 459 | + }); |
| 460 | + |
| 461 | + $factory = new SchemaFactory($inner); |
| 462 | + $factory->buildSchema('App\\Dummy', 'jsonld', Schema::TYPE_OUTPUT, new McpToolCollection(class: 'App\\Dummy')); |
| 463 | + |
| 464 | + $this->assertNull($captured); |
| 465 | + } |
| 466 | + |
| 467 | + public function testInputSchemaDoesNotForceGenIdFalse(): void |
| 468 | + { |
| 469 | + $captured = 'untouched'; |
| 470 | + $inner = $this->createMock(SchemaFactoryInterface::class); |
| 471 | + $inner->method('buildSchema')->willReturnCallback(function (...$args) use (&$captured) { |
| 472 | + $captured = $args[5] ?? null; |
| 473 | + |
| 474 | + return $this->emptyObjectSchema(); |
| 475 | + }); |
| 476 | + |
| 477 | + $factory = new SchemaFactory($inner); |
| 478 | + $factory->buildSchema('App\\Dummy', 'json', Schema::TYPE_INPUT, new McpTool(class: 'App\\Dummy')); |
| 479 | + |
| 480 | + $this->assertNull($captured); |
| 481 | + } |
| 482 | + |
| 483 | + private function emptyObjectSchema(): Schema |
| 484 | + { |
| 485 | + $schema = new Schema(Schema::VERSION_JSON_SCHEMA); |
| 486 | + unset($schema['$schema']); |
| 487 | + $schema->getDefinitions()['Dummy'] = new \ArrayObject(['type' => 'object']); |
| 488 | + $schema['$ref'] = '#/definitions/Dummy'; |
| 489 | + |
| 490 | + return $schema; |
| 491 | + } |
425 | 492 | } |
0 commit comments