Skip to content

Commit 06f4cfb

Browse files
[MCP SDK] Add StreamTransportTest for existing behavior
1 parent 22a3555 commit 06f4cfb

File tree

4 files changed

+153
-1
lines changed

4 files changed

+153
-1
lines changed

src/mcp-sdk/phpunit.xml.dist

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
executionOrder="depends,defects"
77
beStrictAboutOutputDuringTests="true"
88
failOnRisky="true"
9-
failOnWarning="true">
9+
failOnWarning="true"
10+
bootstrap="tests/bootstrap.php">
1011

1112
<testsuites>
1213
<testsuite name="default">
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 Symfony\AI\McpSdk\Server\Transport\Sse;
13+
14+
use Symfony\AI\McpSdk\Tests\Server\Transport\Sse\StreamTransportTest;
15+
16+
if (!\function_exists(__NAMESPACE__.'\\connection_aborted')) {
17+
function connection_aborted(): int
18+
{
19+
return StreamTransportTest::$connectionAborted;
20+
}
21+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 Symfony\AI\McpSdk\Tests\Server\Transport\Sse;
13+
14+
use PHPUnit\Framework\Attributes\CoversClass;
15+
use PHPUnit\Framework\Attributes\Small;
16+
use PHPUnit\Framework\TestCase;
17+
use Symfony\AI\McpSdk\Server\Transport\Sse\StoreInterface;
18+
use Symfony\AI\McpSdk\Server\Transport\Sse\StreamTransport;
19+
use Symfony\Component\Uid\Uuid;
20+
21+
#[Small]
22+
#[CoversClass(StreamTransport::class)]
23+
class StreamTransportTest extends TestCase
24+
{
25+
public static int $connectionAborted = 0;
26+
27+
protected function tearDown(): void
28+
{
29+
parent::tearDown();
30+
self::$connectionAborted = 0;
31+
}
32+
33+
public function testInitializeEmitsEndpointEvent()
34+
{
35+
$endpoint = 'https://example.test/mcp/messages';
36+
$transport = new StreamTransport($endpoint, $this->createMock(StoreInterface::class), Uuid::v7());
37+
$actual = $this->capture(fn () => $transport->initialize());
38+
39+
$this->assertSame('event: endpoint'.\PHP_EOL.'data: '.$endpoint.\PHP_EOL.\PHP_EOL, $actual);
40+
}
41+
42+
public function testReceiveYieldsSingleMessageFromStore()
43+
{
44+
$id = Uuid::v7();
45+
$store = $this->createMock(StoreInterface::class);
46+
$store->expects($this->once())
47+
->method('pop')
48+
->with($id)
49+
->willReturn('hello');
50+
51+
$transport = new StreamTransport('x', $store, $id);
52+
$generator = $transport->receive();
53+
54+
$this->assertInstanceOf(\Generator::class, $generator);
55+
$this->assertSame('hello', $generator->current());
56+
$generator->next();
57+
$this->assertFalse($generator->valid());
58+
}
59+
60+
public function testSendEmitsMessageEventAndFlushes()
61+
{
62+
$transport = new StreamTransport('x', $this->createMock(StoreInterface::class), Uuid::v7());
63+
$actual = $this->capture(fn () => $transport->send('payload'));
64+
65+
$this->assertSame('event: message'.\PHP_EOL.'data: payload'.\PHP_EOL.\PHP_EOL, $actual);
66+
}
67+
68+
public function testMultipleSendsProduceMultipleResponses()
69+
{
70+
$transport = new StreamTransport('x', $this->createMock(StoreInterface::class), Uuid::v7());
71+
$first = $this->capture(fn () => $transport->send('one'));
72+
$second = $this->capture(fn () => $transport->send('two'));
73+
74+
$this->assertSame([
75+
'event: message'.\PHP_EOL.'data: one'.\PHP_EOL.\PHP_EOL,
76+
'event: message'.\PHP_EOL.'data: two'.\PHP_EOL.\PHP_EOL,
77+
], [$first, $second]);
78+
}
79+
80+
public function testCloseRemovesSessionFromStore()
81+
{
82+
$id = Uuid::v7();
83+
$store = $this->createMock(StoreInterface::class);
84+
$store->expects($this->once())
85+
->method('remove')
86+
->with($id);
87+
88+
$transport = new StreamTransport('x', $store, $id);
89+
$transport->close();
90+
}
91+
92+
public function testIsConnectedRespectsConnectionAbortedPolyfill()
93+
{
94+
$transport = new StreamTransport('x', $this->createMock(StoreInterface::class), Uuid::v7());
95+
96+
self::$connectionAborted = 0;
97+
$this->assertTrue($transport->isConnected());
98+
99+
self::$connectionAborted = 1;
100+
$this->assertFalse($transport->isConnected());
101+
}
102+
103+
private function capture(callable $fn): string
104+
{
105+
$buffer = '';
106+
ob_start(function (string $chunk) use (&$buffer) {
107+
$buffer .= $chunk;
108+
109+
return '';
110+
});
111+
112+
$fn();
113+
114+
ob_end_flush();
115+
116+
return $buffer;
117+
}
118+
}

src/mcp-sdk/tests/bootstrap.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
require __DIR__.'/Fixtures/Sse/connection_aborted_polyfill.php';

0 commit comments

Comments
 (0)