Skip to content

Commit 1d3d55f

Browse files
marcod85CodeWithKyrian
authored andcommitted
add missing tests
1 parent 8b81780 commit 1d3d55f

12 files changed

+132
-82
lines changed

src/Context.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
final class Context
88
{
99
public function __construct(
10-
public readonly ?ServerRequestInterface $request,
11-
public readonly SessionInterface $session
10+
public readonly SessionInterface $session,
11+
public readonly ?ServerRequestInterface $request = null,
1212
)
1313
{
1414
}

src/Elements/RegisteredElement.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function __construct(
3131
$this->isManual = $isManual;
3232
}
3333

34-
public function handle(ContainerInterface $container, array $arguments, ?Context $requestContext = null): mixed
34+
public function handle(ContainerInterface $container, array $arguments, Context $requestContext): mixed
3535
{
3636
if (is_string($this->handler)) {
3737
if (class_exists($this->handler) && method_exists($this->handler, '__invoke')) {
@@ -67,7 +67,7 @@ public function handle(ContainerInterface $container, array $arguments, ?Context
6767
}
6868

6969

70-
protected function prepareArguments(\ReflectionFunctionAbstract $reflection, array $arguments, ?Context $requestContext): array
70+
protected function prepareArguments(\ReflectionFunctionAbstract $reflection, array $arguments, Context $requestContext): array
7171
{
7272
$finalArgs = [];
7373

@@ -77,7 +77,7 @@ protected function prepareArguments(\ReflectionFunctionAbstract $reflection, arr
7777
$paramType = $parameter->getType();
7878
$paramPosition = $parameter->getPosition();
7979

80-
if ($paramType?->getName() === Context::class) {
80+
if ($paramType instanceof ReflectionNamedType && $paramType->getName() === Context::class) {
8181
$finalArgs[$paramPosition] = $requestContext;
8282

8383
continue;

src/Protocol.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ public function processMessage(Request|Notification|BatchRequest $message, strin
141141
}
142142

143143
$requestContext = new Context(
144-
$context['request'] ?? null,
145144
$session,
145+
$context['request'] ?? null,
146146
);
147147

148148
$response = null;

tests/Fixtures/General/VariousTypesHandler.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpMcp\Server\Tests\Fixtures\General;
44

5+
use PhpMcp\Server\Context;
56
use PhpMcp\Server\Tests\Fixtures\Enums\BackedIntEnum;
67
use PhpMcp\Server\Tests\Fixtures\Enums\BackedStringEnum;
78
use PhpMcp\Server\Tests\Fixtures\Enums\UnitEnum;
@@ -142,4 +143,11 @@ public function comprehensiveArgumentTest(
142143
public function methodCausesTypeError(int $mustBeInt): void
143144
{
144145
}
146+
147+
public function contextArg(Context $context): array {
148+
return [
149+
'session' => $context->session->get('testKey'),
150+
'request' => $context->request->getHeaderLine('testHeader'),
151+
];
152+
}
145153
}

tests/Fixtures/Utils/SchemaGeneratorFixture.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpMcp\Server\Tests\Fixtures\Utils;
44

55
use PhpMcp\Server\Attributes\Schema;
6+
use PhpMcp\Server\Context;
67
use PhpMcp\Server\Tests\Fixtures\Enums\BackedIntEnum;
78
use PhpMcp\Server\Tests\Fixtures\Enums\BackedStringEnum;
89
use PhpMcp\Server\Tests\Fixtures\Enums\UnitEnum;
@@ -47,6 +48,10 @@ public function typeHintsWithDocBlock(string $email, int $score, bool $verified)
4748
{
4849
}
4950

51+
public function contextParameter(Context $context): void
52+
{
53+
}
54+
5055
// ===== METHOD-LEVEL SCHEMA SCENARIOS =====
5156

5257
/**

tests/Integration/SchemaGenerationTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@
5858
expect($schema['required'])->toEqualCanonicalizing(['email', 'score', 'verified']);
5959
});
6060

61+
it('ignores Context parameter for schema', function () {
62+
$method = new ReflectionMethod(SchemaGeneratorFixture::class, 'contextParameter');
63+
$schema = $this->schemaGenerator->generate($method);
64+
65+
expect($schema)->toEqual([
66+
'type' => 'object',
67+
'properties' => new stdClass()
68+
]);
69+
});
70+
6171
it('uses the complete schema definition provided by a method-level #[Schema(definition: ...)] attribute', function () {
6272
$method = new ReflectionMethod(SchemaGeneratorFixture::class, 'methodLevelCompleteDefinition');
6373
$schema = $this->schemaGenerator->generate($method);

tests/Unit/DispatcherTest.php

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace PhpMcp\Server\Tests\Unit;
44

5-
use Grpc\Call;
65
use Mockery;
76
use Mockery\MockInterface;
87
use PhpMcp\Schema\ClientCapabilities;
@@ -73,6 +72,7 @@
7372
$this->session = Mockery::mock(SessionInterface::class);
7473
/** @var MockInterface&ContainerInterface $container */
7574
$this->container = Mockery::mock(ContainerInterface::class);
75+
$this->context = new Context(Mockery::mock(SessionInterface::class));
7676

7777
$configuration = new Configuration(
7878
serverInfo: Implementation::make('DispatcherTestServer', '1.0'),
@@ -106,21 +106,21 @@
106106
$this->session->shouldReceive('set')->with('client_info', Mockery::on(fn($value) => $value['name'] === 'client' && $value['version'] === '1.0'))->once();
107107
$this->session->shouldReceive('set')->with('protocol_version', Protocol::LATEST_PROTOCOL_VERSION)->once();
108108

109-
$result = $this->dispatcher->handleRequest($request, $this->session, new Context(null, Mockery::mock(SessionInterface::class)));
109+
$result = $this->dispatcher->handleRequest($request, $this->session, $this->context);
110110
expect($result)->toBeInstanceOf(InitializeResult::class);
111111
expect($result->protocolVersion)->toBe(Protocol::LATEST_PROTOCOL_VERSION);
112112
expect($result->serverInfo->name)->toBe('DispatcherTestServer');
113113
});
114114

115115
it('routes to handlePing for ping request', function () {
116116
$request = new JsonRpcRequest('2.0', 'id1', 'ping', []);
117-
$result = $this->dispatcher->handleRequest($request, $this->session, new Context(null, Mockery::mock(SessionInterface::class)));
117+
$result = $this->dispatcher->handleRequest($request, $this->session, $this->context);
118118
expect($result)->toBeInstanceOf(EmptyResult::class);
119119
});
120120

121121
it('throws MethodNotFound for unknown request method', function () {
122122
$rawRequest = new JsonRpcRequest('2.0', 'id1', 'unknown/method', []);
123-
$this->dispatcher->handleRequest($rawRequest, $this->session, new Context(null, Mockery::mock(SessionInterface::class)));
123+
$this->dispatcher->handleRequest($rawRequest, $this->session, $this->context);
124124
})->throws(McpServerException::class, "Method 'unknown/method' not found.");
125125

126126
it('routes to handleNotificationInitialized for initialized notification', function () {
@@ -202,14 +202,13 @@
202202
$args = ['a' => 10, 'b' => 5];
203203
$toolSchema = ToolSchema::make($toolName, ['type' => 'object', 'properties' => ['a' => ['type' => 'integer'], 'b' => ['type' => 'integer']]]);
204204
$registeredToolMock = Mockery::mock(RegisteredTool::class, [$toolSchema, 'MyToolHandler', 'handleTool', false]);
205-
$context = new Context(null, Mockery::mock(SessionInterface::class));
206205

207206
$this->registry->shouldReceive('getTool')->with($toolName)->andReturn($registeredToolMock);
208207
$this->schemaValidator->shouldReceive('validateAgainstJsonSchema')->with($args, $toolSchema->inputSchema)->andReturn([]); // No validation errors
209-
$registeredToolMock->shouldReceive('call')->with($this->container, $args, $context)->andReturn([TextContent::make("Result: 15")]);
208+
$registeredToolMock->shouldReceive('call')->with($this->container, $args, $this->context)->andReturn([TextContent::make("Result: 15")]);
210209

211210
$request = CallToolRequest::make(1, $toolName, $args);
212-
$result = $this->dispatcher->handleToolCall($request, $context);
211+
$result = $this->dispatcher->handleToolCall($request, $this->context);
213212

214213
expect($result)->toBeInstanceOf(CallToolResult::class);
215214
expect($result->content[0]->text)->toBe("Result: 15");
@@ -219,7 +218,7 @@
219218
it('can handle tool call request and throw exception if tool not found', function () {
220219
$this->registry->shouldReceive('getTool')->with('unknown-tool')->andReturn(null);
221220
$request = CallToolRequest::make(1, 'unknown-tool', []);
222-
$this->dispatcher->handleToolCall($request, new Context(null, Mockery::mock(SessionInterface::class)));
221+
$this->dispatcher->handleToolCall($request, $this->context);
223222
})->throws(McpServerException::class, "Tool 'unknown-tool' not found.");
224223

225224
it('can handle tool call request and throw exception if argument validation fails', function () {
@@ -234,7 +233,7 @@
234233

235234
$request = CallToolRequest::make(1, $toolName, $args);
236235
try {
237-
$this->dispatcher->handleToolCall($request, new Context(null, Mockery::mock(SessionInterface::class)));
236+
$this->dispatcher->handleToolCall($request, $this->context);
238237
} catch (McpServerException $e) {
239238
expect($e->getMessage())->toContain("Invalid parameters for tool 'strict-tool'");
240239
expect($e->getData()['validation_errors'])->toBeArray();
@@ -251,7 +250,7 @@
251250
$registeredToolMock->shouldReceive('call')->andThrow(new \RuntimeException("Tool crashed!"));
252251

253252
$request = CallToolRequest::make(1, $toolName, []);
254-
$result = $this->dispatcher->handleToolCall($request, new Context(null, Mockery::mock(SessionInterface::class)));
253+
$result = $this->dispatcher->handleToolCall($request, $this->context);
255254

256255
expect($result->isError)->toBeTrue();
257256
expect($result->content[0]->text)->toBe("Tool execution failed: Tool crashed!");
@@ -268,13 +267,12 @@
268267

269268

270269
$request = CallToolRequest::make(1, $toolName, []);
271-
$result = $this->dispatcher->handleToolCall($request, new Context(null, Mockery::mock(SessionInterface::class)));
270+
$result = $this->dispatcher->handleToolCall($request, $this->context);
272271

273272
expect($result->isError)->toBeTrue();
274273
expect($result->content[0]->text)->toBe("Failed to serialize tool result: Unencodable.");
275274
});
276275

277-
278276
it('can handle resources list request and return paginated resources', function () {
279277
$resourceSchemas = [
280278
ResourceSchema::make('res://1', 'Resource1'),
@@ -338,10 +336,10 @@
338336
$resourceContents = [TextContent::make('File content')];
339337

340338
$this->registry->shouldReceive('getResource')->with($uri)->andReturn($registeredResourceMock);
341-
$registeredResourceMock->shouldReceive('read')->with($this->container, $uri)->andReturn($resourceContents);
339+
$registeredResourceMock->shouldReceive('read')->with($this->container, $uri, $this->context)->andReturn($resourceContents);
342340

343341
$request = ReadResourceRequest::make(1, $uri);
344-
$result = $this->dispatcher->handleResourceRead($request);
342+
$result = $this->dispatcher->handleResourceRead($request, $this->context);
345343

346344
expect($result)->toBeInstanceOf(ReadResourceResult::class);
347345
expect($result->contents)->toEqual($resourceContents);
@@ -350,7 +348,7 @@
350348
it('can handle resource read request and throw exception if resource not found', function () {
351349
$this->registry->shouldReceive('getResource')->with('unknown://uri')->andReturn(null);
352350
$request = ReadResourceRequest::make(1, 'unknown://uri');
353-
$this->dispatcher->handleResourceRead($request);
351+
$this->dispatcher->handleResourceRead($request, $this->context);
354352
})->throws(McpServerException::class, "Resource URI 'unknown://uri' not found.");
355353

356354
it('can handle resource subscribe request and call subscription manager', function () {
@@ -396,10 +394,10 @@
396394
$promptMessages = [PromptMessage::make(Role::User, TextContent::make("Summary for 2024-07-16"))];
397395

398396
$this->registry->shouldReceive('getPrompt')->with($promptName)->andReturn($registeredPromptMock);
399-
$registeredPromptMock->shouldReceive('get')->with($this->container, $args)->andReturn($promptMessages);
397+
$registeredPromptMock->shouldReceive('get')->with($this->container, $args, $this->context)->andReturn($promptMessages);
400398

401399
$request = GetPromptRequest::make(1, $promptName, $args);
402-
$result = $this->dispatcher->handlePromptGet($request, $this->session);
400+
$result = $this->dispatcher->handlePromptGet($request, $this->context);
403401

404402
expect($result)->toBeInstanceOf(GetPromptResult::class);
405403
expect($result->messages)->toEqual($promptMessages);
@@ -413,7 +411,7 @@
413411
$this->registry->shouldReceive('getPrompt')->with($promptName)->andReturn($registeredPromptMock);
414412

415413
$request = GetPromptRequest::make(1, $promptName, ['other_arg' => 'value']); // 'topic' is missing
416-
$this->dispatcher->handlePromptGet($request, $this->session);
414+
$this->dispatcher->handlePromptGet($request, $this->context);
417415
})->throws(McpServerException::class, "Missing required argument 'topic' for prompt 'needs-topic'.");
418416

419417

0 commit comments

Comments
 (0)