|
19 | 19 | use Mcp\Server; |
20 | 20 | use Mcp\Server\RequestContext; |
21 | 21 | use Mcp\Server\Session\FileSessionLock; |
| 22 | +use Mcp\Server\Session\InMemorySessionStore; |
22 | 23 | use Mcp\Server\Session\Session; |
23 | 24 | use Mcp\Server\Session\SessionLockInterface; |
24 | 25 | use Mcp\Server\Suspension\RequestSuspension; |
|
34 | 35 | use PHPUnit\Framework\Attributes\DataProvider; |
35 | 36 | use PHPUnit\Framework\Attributes\TestDox; |
36 | 37 | use PHPUnit\Framework\TestCase; |
| 38 | +use Psr\Clock\ClockInterface; |
37 | 39 | use Psr\Http\Message\ResponseFactoryInterface; |
38 | 40 | use Psr\Http\Message\ResponseInterface; |
39 | 41 | use Psr\Http\Message\ServerRequestInterface; |
@@ -149,6 +151,40 @@ public function testDuplicateSessionIdHeadersReturnBadRequest(): void |
149 | 151 | $this->assertStringContainsString('must not be repeated', (string) $response->getBody()); |
150 | 152 | } |
151 | 153 |
|
| 154 | + #[TestDox('a DELETE of an unknown or already ended session is answered 404')] |
| 155 | + public function testDeleteOfAnEndedSessionIsNotFound(): void |
| 156 | + { |
| 157 | + $server = Server::builder()->setServerInfo('test', '1.0')->build(); |
| 158 | + $sessionId = $this->initialize($server); |
| 159 | + |
| 160 | + $this->assertSame(200, $this->delete($server, $sessionId)->getStatusCode()); |
| 161 | + |
| 162 | + $response = $this->delete($server, $sessionId); |
| 163 | + |
| 164 | + $this->assertSame(404, $response->getStatusCode()); |
| 165 | + $this->assertStringContainsString('Session not found', (string) $response->getBody()); |
| 166 | + } |
| 167 | + |
| 168 | + #[TestDox('a DELETE of an expired session is answered 404')] |
| 169 | + public function testDeleteOfAnExpiredSessionIsNotFound(): void |
| 170 | + { |
| 171 | + $clock = new class implements ClockInterface { |
| 172 | + public \DateTimeImmutable $now; |
| 173 | + |
| 174 | + public function now(): \DateTimeImmutable |
| 175 | + { |
| 176 | + return $this->now; |
| 177 | + } |
| 178 | + }; |
| 179 | + $clock->now = new \DateTimeImmutable('2026-10-28 12:00:00'); |
| 180 | + $server = Server::builder()->setServerInfo('test', '1.0')->setSession(new InMemorySessionStore(60, $clock))->build(); |
| 181 | + $sessionId = $this->initialize($server); |
| 182 | + |
| 183 | + $clock->now = $clock->now->modify('+61 seconds'); |
| 184 | + |
| 185 | + $this->assertSame(404, $this->delete($server, $sessionId)->getStatusCode()); |
| 186 | + } |
| 187 | + |
152 | 188 | #[TestDox('pass-through middleware disables defaults without a warning log')] |
153 | 189 | public function testPassthroughMiddlewareDisablesDefaultsWithoutWarning(): void |
154 | 190 | { |
@@ -841,6 +877,31 @@ private function post(InterleavingSessionStore $store, string $body, string $ses |
841 | 877 | return $builder->build()->run(new StreamableHttpTransport($request, $this->factory, $this->factory)); |
842 | 878 | } |
843 | 879 |
|
| 880 | + /** |
| 881 | + * Opens a session on $server and returns its id. |
| 882 | + */ |
| 883 | + private function initialize(Server $server): string |
| 884 | + { |
| 885 | + $request = $this->factory |
| 886 | + ->createServerRequest('POST', 'http://localhost/') |
| 887 | + ->withHeader('Host', 'localhost') |
| 888 | + ->withHeader('Content-Type', 'application/json') |
| 889 | + ->withHeader('Accept', 'application/json, text/event-stream') |
| 890 | + ->withBody($this->factory->createStream('{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}')); |
| 891 | + |
| 892 | + return $server->run(new StreamableHttpTransport($request, $this->factory, $this->factory))->getHeaderLine(StreamableHttpTransport::SESSION_HEADER); |
| 893 | + } |
| 894 | + |
| 895 | + private function delete(Server $server, string $sessionId): ResponseInterface |
| 896 | + { |
| 897 | + $request = $this->factory |
| 898 | + ->createServerRequest('DELETE', 'http://localhost/') |
| 899 | + ->withHeader('Host', 'localhost') |
| 900 | + ->withHeader(StreamableHttpTransport::SESSION_HEADER, $sessionId); |
| 901 | + |
| 902 | + return $server->run(new StreamableHttpTransport($request, $this->factory, $this->factory)); |
| 903 | + } |
| 904 | + |
844 | 905 | /** |
845 | 906 | * Reads an SSE response; the stream calls ob_flush() itself, so the output is captured by a handler, not a plain buffer. |
846 | 907 | */ |
|
0 commit comments