Skip to content

Commit e50551d

Browse files
committed
Removing inspector tests, implemented application tests instead
1 parent 345b94d commit e50551d

16 files changed

+386
-126
lines changed

phpunit.xml.dist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
<testsuite name="unit">
1313
<directory>tests/Unit</directory>
1414
</testsuite>
15-
<testsuite name="inspector">
16-
<directory>tests/Inspector</directory>
15+
<testsuite name="application">
16+
<directory>tests/Application</directory>
1717
</testsuite>
1818
</testsuites>
1919

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the official PHP MCP SDK.
7+
*
8+
* A collaboration between Symfony and the PHP Foundation.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Mcp\Tests\Application;
15+
16+
use Mcp\Schema\JsonRpc\MessageInterface;
17+
use PHPUnit\Framework\TestCase;
18+
use Symfony\Component\Process\Process;
19+
20+
abstract class ApplicationTestCase extends TestCase
21+
{
22+
/**
23+
* @param list<string> $messages
24+
* @param array<string,string> $env
25+
*
26+
* @return array<string, array<string, array<string,mixed>>>
27+
*/
28+
protected function runServer(array $messages, float $timeout = 5.0, array $env = []): array
29+
{
30+
if (0 === \count($messages)) {
31+
return [];
32+
}
33+
34+
$process = new Process([
35+
'php',
36+
$this->getServerScript(),
37+
], \dirname(__DIR__, 2), [] === $env ? null : $env, null, $timeout);
38+
39+
$process->setInput($this->formatInput($messages));
40+
$process->mustRun();
41+
42+
return $this->decodeJsonLines($process->getOutput());
43+
}
44+
45+
abstract protected function getServerScript(): string;
46+
47+
/**
48+
* @param mixed[] $params
49+
*
50+
* @throws \JsonException
51+
*/
52+
protected function jsonRequest(string $method, ?array $params = null, ?string $id = null): string
53+
{
54+
$payload = [
55+
'jsonrpc' => MessageInterface::JSONRPC_VERSION,
56+
'id' => $id,
57+
'method' => $method,
58+
];
59+
60+
if (null !== $params) {
61+
$payload['params'] = $params;
62+
}
63+
64+
return (string) json_encode($payload, \JSON_THROW_ON_ERROR);
65+
}
66+
67+
/**
68+
* @param list<string> $messages
69+
*/
70+
private function formatInput(array $messages): string
71+
{
72+
return implode("\n", $messages)."\n";
73+
}
74+
75+
/**
76+
* @return array<string, array<string, array<string,mixed>>>
77+
*/
78+
private function decodeJsonLines(string $output): array
79+
{
80+
$output = trim($output);
81+
$responses = [];
82+
83+
if ('' === $output) {
84+
return $responses;
85+
}
86+
87+
foreach (preg_split('/\R+/', $output) as $line) {
88+
if ('' === $line) {
89+
continue;
90+
}
91+
92+
try {
93+
$decoded = json_decode($line, true, 512, \JSON_THROW_ON_ERROR);
94+
} catch (\JsonException) {
95+
continue;
96+
}
97+
98+
if (!\is_array($decoded)) {
99+
continue;
100+
}
101+
102+
$id = $decoded['id'] ?? null;
103+
104+
if (\is_string($id) || \is_int($id)) {
105+
$responses[(string) $id] = $decoded;
106+
}
107+
}
108+
109+
return $responses;
110+
}
111+
112+
protected function getSnapshotFilePath(string $method): string
113+
{
114+
$className = substr(static::class, strrpos(static::class, '\\') + 1);
115+
116+
return __DIR__.'/snapshots/'.$className.'-'.str_replace('/', '_', $method).'.json';
117+
}
118+
119+
/**
120+
* @return array<string,mixed>
121+
*
122+
* @throws \JsonException
123+
*/
124+
protected function loadSnapshot(string $method): array
125+
{
126+
$path = $this->getSnapshotFilePath($method);
127+
128+
$contents = file_get_contents($path);
129+
$this->assertNotFalse($contents, 'Failed to read snapshot: '.$path);
130+
131+
return json_decode($contents, true, 512, \JSON_THROW_ON_ERROR);
132+
}
133+
134+
/**
135+
* @param array<string, array<string, mixed>> $response
136+
*
137+
* @throws \JsonException
138+
*/
139+
protected function assertResponseMatchesSnapshot(array $response, string $method): void
140+
{
141+
$this->assertArrayHasKey('result', $response);
142+
$actual = $response['result'];
143+
144+
$expected = $this->loadSnapshot($method);
145+
146+
$this->assertEquals($expected, $actual, 'Response payload does not match snapshot '.$this->getSnapshotFilePath($method));
147+
}
148+
149+
/**
150+
* @param array<string, array<string, mixed>> $capabilities
151+
* @param string[] $clientInfo
152+
*
153+
* @throws \JsonException
154+
*/
155+
protected function initializeMessage(
156+
?string $id = null,
157+
string $protocolVersion = MessageInterface::PROTOCOL_VERSION,
158+
array $capabilities = [],
159+
array $clientInfo = [
160+
'name' => 'test-suite',
161+
'version' => '1.0.0',
162+
],
163+
): string {
164+
$id ??= uniqid();
165+
166+
return $this->jsonRequest('initialize', [
167+
'protocolVersion' => $protocolVersion,
168+
'capabilities' => $capabilities,
169+
'clientInfo' => $clientInfo,
170+
], $id);
171+
}
172+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the official PHP MCP SDK.
7+
*
8+
* A collaboration between Symfony and the PHP Foundation.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Mcp\Tests\Application;
15+
16+
final class ManualStdioExampleTest extends ApplicationTestCase
17+
{
18+
/**
19+
* @throws \JsonException
20+
*/
21+
public function testInitializeResponseMatchesSnapshot(): void
22+
{
23+
$initializeId = uniqid('i_');
24+
25+
$responses = $this->runServer([
26+
$this->initializeMessage($initializeId),
27+
]);
28+
29+
$this->assertArrayHasKey($initializeId, $responses);
30+
31+
$initialize = $responses[$initializeId];
32+
$this->assertResponseMatchesSnapshot($initialize, 'initialize');
33+
}
34+
35+
/**
36+
* @throws \JsonException
37+
*/
38+
public function testToolsListMatchesSnapshot(): void
39+
{
40+
$toolsListId = uniqid('t_');
41+
42+
$responses = $this->runServer([
43+
$this->initializeMessage(),
44+
$this->jsonRequest('tools/list', id: $toolsListId),
45+
]);
46+
47+
$this->assertArrayHasKey($toolsListId, $responses);
48+
$toolsList = $responses[$toolsListId];
49+
$this->assertResponseMatchesSnapshot($toolsList, 'tools/list');
50+
}
51+
52+
/**
53+
* @throws \JsonException
54+
*/
55+
public function testPromptsListMatchesSnapshot(): void
56+
{
57+
$promptsListId = uniqid('t_');
58+
59+
$responses = $this->runServer([
60+
$this->initializeMessage(),
61+
$this->jsonRequest('prompts/list', id: $promptsListId),
62+
]);
63+
64+
$this->assertArrayHasKey($promptsListId, $responses);
65+
$promptsList = $responses[$promptsListId];
66+
$this->assertResponseMatchesSnapshot($promptsList, 'prompts/list');
67+
}
68+
69+
/**
70+
* @throws \JsonException
71+
*/
72+
public function testResourcesListMatchesSnapshot(): void
73+
{
74+
$resourcesListId = uniqid('t_');
75+
76+
$responses = $this->runServer([
77+
$this->initializeMessage(),
78+
$this->jsonRequest('resources/list', id: $resourcesListId),
79+
]);
80+
81+
$this->assertArrayHasKey($resourcesListId, $responses);
82+
$resourcesList = $responses[$resourcesListId];
83+
$this->assertResponseMatchesSnapshot($resourcesList, 'resources/list');
84+
}
85+
86+
/**
87+
* @throws \JsonException
88+
*/
89+
public function testResourceTemplatesListMatchesSnapshot(): void
90+
{
91+
$templatesListId = uniqid('t_');
92+
93+
$responses = $this->runServer([
94+
$this->initializeMessage(),
95+
$this->jsonRequest('resources/templates/list', id: $templatesListId),
96+
]);
97+
98+
$this->assertArrayHasKey($templatesListId, $responses);
99+
$templatesList = $responses[$templatesListId];
100+
$this->assertResponseMatchesSnapshot($templatesList, 'resources/templates/list');
101+
}
102+
103+
protected function getServerScript(): string
104+
{
105+
return \dirname(__DIR__, 2).'/examples/stdio-explicit-registration/server.php';
106+
}
107+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the official PHP MCP SDK.
7+
*
8+
* A collaboration between Symfony and the PHP Foundation.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Application;
15+
16+
use Mcp\Tests\Application\ApplicationTestCase;
17+
18+
final class StdioCalculatorExampleTest extends ApplicationTestCase
19+
{
20+
/**
21+
* @throws \JsonException
22+
*/
23+
public function testToolsListMatchesSnapshot(): void
24+
{
25+
$toolsListId = uniqid('t_');
26+
27+
$responses = $this->runServer([
28+
$this->initializeMessage(),
29+
$this->jsonRequest('tools/list', id: $toolsListId),
30+
]);
31+
32+
$this->assertArrayHasKey($toolsListId, $responses);
33+
$toolsList = $responses[$toolsListId];
34+
$this->assertResponseMatchesSnapshot($toolsList, 'tools/list');
35+
}
36+
37+
/**
38+
* @throws \JsonException
39+
*/
40+
public function testPromptsListMatchesSnapshot(): void
41+
{
42+
$promptsListId = uniqid('t_');
43+
44+
$responses = $this->runServer([
45+
$this->initializeMessage(),
46+
$this->jsonRequest('prompts/list', id: $promptsListId),
47+
]);
48+
49+
$this->assertArrayHasKey($promptsListId, $responses);
50+
$promptsList = $responses[$promptsListId];
51+
$this->assertResponseMatchesSnapshot($promptsList, 'prompts/list');
52+
}
53+
54+
/**
55+
* @throws \JsonException
56+
*/
57+
public function testResourcesListMatchesSnapshot(): void
58+
{
59+
$resourcesListId = uniqid('t_');
60+
61+
$responses = $this->runServer([
62+
$this->initializeMessage(),
63+
$this->jsonRequest('resources/list', id: $resourcesListId),
64+
]);
65+
66+
$this->assertArrayHasKey($resourcesListId, $responses);
67+
$resourcesList = $responses[$resourcesListId];
68+
$this->assertResponseMatchesSnapshot($resourcesList, 'resources/list');
69+
}
70+
71+
/**
72+
* @throws \JsonException
73+
*/
74+
public function testResourceTemplatesListMatchesSnapshot(): void
75+
{
76+
$templatesListId = uniqid('t_');
77+
78+
$responses = $this->runServer([
79+
$this->initializeMessage(),
80+
$this->jsonRequest('resources/templates/list', id: $templatesListId),
81+
]);
82+
83+
$this->assertArrayHasKey($templatesListId, $responses);
84+
$templatesList = $responses[$templatesListId];
85+
$this->assertResponseMatchesSnapshot($templatesList, 'resources/templates/list');
86+
}
87+
88+
protected function getServerScript(): string
89+
{
90+
return \dirname(__DIR__, 2).'/examples/stdio-discovery-calculator/server.php';
91+
}
92+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"capabilities": {
3+
"prompts": {},
4+
"resources": {},
5+
"tools": {},
6+
"completions": {}
7+
},
8+
"serverInfo": {
9+
"name": "Manual Reg Server",
10+
"version": "1.0.0"
11+
},
12+
"protocolVersion": "2025-06-18"
13+
}

0 commit comments

Comments
 (0)