Skip to content

Commit f3bea09

Browse files
author
larry.sulebalogun
committed
10 - Added the list prompts handler with test class
1 parent 9590e9c commit f3bea09

File tree

2 files changed

+225
-2
lines changed

2 files changed

+225
-2
lines changed

src/Server/RequestHandler/ListPromptsHandler.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,13 @@ public function handle(ListPromptsRequest|HasMethodInterface $message): Response
3838
{
3939
\assert($message instanceof ListPromptsRequest);
4040

41-
$cursor = null;
4241
$prompts = $this->registry->getPrompts($this->pageSize, $message->cursor);
43-
$nextCursor = (null !== $cursor && \count($prompts) === $this->pageSize) ? $cursor : null;
42+
43+
$nextCursor = $this->registry->calculateNextCursor(
44+
$this->registry->getPromptsCount(),
45+
$message->cursor,
46+
\count($prompts)
47+
);
4448

4549
return new Response(
4650
$message->getId(),
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the official PHP MCP SDK.
5+
*
6+
* A collaboration between Symfony and the PHP Foundation.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Mcp\Tests\Server\RequestHandler;
13+
14+
use Mcp\Capability\Registry;
15+
use Mcp\Exception\InvalidCursorException;
16+
use Mcp\Schema\Prompt;
17+
use Mcp\Schema\Request\ListPromptsRequest;
18+
use Mcp\Schema\Result\ListPromptsResult;
19+
use Mcp\Server\RequestHandler\ListPromptsHandler;
20+
use PHPUnit\Framework\Attributes\TestDox;
21+
use PHPUnit\Framework\TestCase;
22+
use ReflectionClass;
23+
24+
class ListPromptsHandlerTest extends TestCase
25+
{
26+
private Registry $registry;
27+
private ListPromptsHandler $handler;
28+
29+
protected function setUp(): void
30+
{
31+
$this->registry = new Registry();
32+
$this->handler = new ListPromptsHandler($this->registry, pageSize: 3); // Use small page size for testing
33+
}
34+
35+
#[TestDox('Returns first page when no cursor provided')]
36+
public function testReturnsFirstPageWhenNoCursorProvided(): void
37+
{
38+
// Arrange
39+
$this->addPromptsToRegistry(5);
40+
$request = $this->createListPromptsRequest();
41+
42+
// Act
43+
$response = $this->handler->handle($request);
44+
45+
// Assert
46+
$this->assertInstanceOf(ListPromptsResult::class, $response->result);
47+
$this->assertCount(3, $response->result->prompts);
48+
$this->assertNotNull($response->result->nextCursor);
49+
50+
$this->assertEquals('prompt_0', $response->result->prompts[0]->name);
51+
$this->assertEquals('prompt_1', $response->result->prompts[1]->name);
52+
$this->assertEquals('prompt_2', $response->result->prompts[2]->name);
53+
}
54+
55+
#[TestDox('Returns paginated prompts with cursor')]
56+
public function testReturnsPaginatedPromptsWithCursor(): void
57+
{
58+
// Arrange
59+
$this->addPromptsToRegistry(10);
60+
$request = $this->createListPromptsRequest(cursor: null);
61+
62+
// Act
63+
$response = $this->handler->handle($request);
64+
65+
// Assert
66+
$this->assertInstanceOf(ListPromptsResult::class, $response->result);
67+
$this->assertCount(3, $response->result->prompts);
68+
$this->assertNotNull($response->result->nextCursor);
69+
70+
$this->assertEquals('prompt_0', $response->result->prompts[0]->name);
71+
$this->assertEquals('prompt_1', $response->result->prompts[1]->name);
72+
$this->assertEquals('prompt_2', $response->result->prompts[2]->name);
73+
}
74+
75+
#[TestDox('Returns second page with cursor')]
76+
public function testReturnsSecondPageWithCursor(): void
77+
{
78+
// Arrange
79+
$this->addPromptsToRegistry(10);
80+
$firstPageRequest = $this->createListPromptsRequest();
81+
$firstPageResponse = $this->handler->handle($firstPageRequest);
82+
83+
$secondPageRequest = $this->createListPromptsRequest(cursor: $firstPageResponse->result->nextCursor);
84+
85+
// Act
86+
$response = $this->handler->handle($secondPageRequest);
87+
88+
// Assert
89+
$this->assertInstanceOf(ListPromptsResult::class, $response->result);
90+
$this->assertCount(3, $response->result->prompts);
91+
$this->assertNotNull($response->result->nextCursor);
92+
93+
$this->assertEquals('prompt_3', $response->result->prompts[0]->name);
94+
$this->assertEquals('prompt_4', $response->result->prompts[1]->name);
95+
$this->assertEquals('prompt_5', $response->result->prompts[2]->name);
96+
}
97+
98+
#[TestDox('Returns last page with null cursor')]
99+
public function testReturnsLastPageWithNullCursor(): void
100+
{
101+
// Arrange
102+
$this->addPromptsToRegistry(5);
103+
$firstPageRequest = $this->createListPromptsRequest();
104+
$firstPageResponse = $this->handler->handle($firstPageRequest);
105+
106+
$secondPageRequest = $this->createListPromptsRequest(cursor: $firstPageResponse->result->nextCursor);
107+
108+
// Act
109+
$response = $this->handler->handle($secondPageRequest);
110+
111+
// Assert
112+
$this->assertInstanceOf(ListPromptsResult::class, $response->result);
113+
$this->assertCount(2, $response->result->prompts);
114+
$this->assertNull($response->result->nextCursor);
115+
116+
$this->assertEquals('prompt_3', $response->result->prompts[0]->name);
117+
$this->assertEquals('prompt_4', $response->result->prompts[1]->name);
118+
}
119+
120+
#[TestDox('Handles empty registry')]
121+
public function testHandlesEmptyRegistry(): void
122+
{
123+
// Arrange
124+
$request = $this->createListPromptsRequest();
125+
126+
// Act
127+
$response = $this->handler->handle($request);
128+
129+
// Assert
130+
$this->assertInstanceOf(ListPromptsResult::class, $response->result);
131+
$this->assertCount(0, $response->result->prompts);
132+
$this->assertNull($response->result->nextCursor);
133+
}
134+
135+
#[TestDox('Throws exception for invalid cursor')]
136+
public function testThrowsExceptionForInvalidCursor(): void
137+
{
138+
// Arrange
139+
$this->addPromptsToRegistry(5);
140+
$request = $this->createListPromptsRequest(cursor: 'invalid-cursor');
141+
142+
// Assert
143+
$this->expectException(InvalidCursorException::class);
144+
145+
// Act
146+
$this->handler->handle($request);
147+
}
148+
149+
#[TestDox('Throws exception for cursor beyond bounds')]
150+
public function testThrowsExceptionForCursorBeyondBounds(): void
151+
{
152+
// Arrange
153+
$this->addPromptsToRegistry(5);
154+
$outOfBoundsCursor = base64_encode('1000');
155+
$request = $this->createListPromptsRequest(cursor: $outOfBoundsCursor);
156+
157+
// Assert
158+
$this->expectException(InvalidCursorException::class);
159+
160+
// Act
161+
$this->handler->handle($request);
162+
}
163+
164+
#[TestDox('Handles cursor at exact boundary')]
165+
public function testHandlesCursorAtExactBoundary(): void
166+
{
167+
// Arrange
168+
$this->addPromptsToRegistry(6);
169+
$exactBoundaryCursor = base64_encode('6');
170+
$request = $this->createListPromptsRequest(cursor: $exactBoundaryCursor);
171+
172+
// Act
173+
$response = $this->handler->handle($request);
174+
175+
// Assert
176+
$this->assertInstanceOf(ListPromptsResult::class, $response->result);
177+
$this->assertCount(0, $response->result->prompts);
178+
$this->assertNull($response->result->nextCursor);
179+
}
180+
181+
#[TestDox('Maintains stable cursors across calls')]
182+
public function testMaintainsStableCursorsAcrossCalls(): void
183+
{
184+
// Arrange
185+
$this->addPromptsToRegistry(10);
186+
187+
// Act
188+
$request = $this->createListPromptsRequest();
189+
$response1 = $this->handler->handle($request);
190+
$response2 = $this->handler->handle($request);
191+
192+
// Assert
193+
$this->assertEquals($response1->result->nextCursor, $response2->result->nextCursor);
194+
$this->assertEquals($response1->result->prompts, $response2->result->prompts);
195+
}
196+
197+
private function addPromptsToRegistry(int $count): void
198+
{
199+
for ($i = 0; $i < $count; $i++) {
200+
$prompt = new Prompt(
201+
name: "prompt_$i",
202+
description: "Test prompt $i"
203+
);
204+
205+
$this->registry->registerPrompt($prompt, fn() => null);
206+
}
207+
}
208+
209+
private function createListPromptsRequest(?string $cursor = null): ListPromptsRequest
210+
{
211+
$listPromptsRequest = new ListPromptsRequest(cursor: $cursor);
212+
213+
$reflection = new ReflectionClass($listPromptsRequest);
214+
$idProperty = $reflection->getProperty('id');
215+
$idProperty->setValue($listPromptsRequest, 'test-request-id');
216+
217+
return $listPromptsRequest;
218+
}
219+
}

0 commit comments

Comments
 (0)