Skip to content

Commit c459d92

Browse files
fix(tests): use dynamic port for http integration tests
1 parent 7e029ca commit c459d92

File tree

3 files changed

+35
-22
lines changed

3 files changed

+35
-22
lines changed

tests/Integration/HttpServerTransportTest.php

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
const HTTP_SERVER_SCRIPT_PATH = __DIR__ . '/../Fixtures/ServerScripts/HttpTestServer.php';
1818
const HTTP_PROCESS_TIMEOUT_SECONDS = 8;
1919
const HTTP_SERVER_HOST = '127.0.0.1';
20-
const HTTP_SERVER_PORT = 8991;
2120
const HTTP_MCP_PATH_PREFIX = 'mcp_http_integration';
2221

2322
beforeEach(function () {
2423
$this->loop = Loop::get();
24+
$this->port = findFreePort();
2525

2626
if (!is_file(HTTP_SERVER_SCRIPT_PATH)) {
2727
$this->markTestSkipped("Server script not found: " . HTTP_SERVER_SCRIPT_PATH);
@@ -34,7 +34,7 @@
3434
$commandPhpPath = str_contains($phpPath, ' ') ? '"' . $phpPath . '"' : $phpPath;
3535
$commandArgs = [
3636
escapeshellarg(HTTP_SERVER_HOST),
37-
escapeshellarg((string)HTTP_SERVER_PORT),
37+
escapeshellarg((string)$this->port),
3838
escapeshellarg(HTTP_MCP_PATH_PREFIX)
3939
];
4040
$commandScriptPath = escapeshellarg(HTTP_SERVER_SCRIPT_PATH);
@@ -82,7 +82,7 @@
8282

8383
it('starts the http server, initializes, calls a tool, and closes', function () {
8484
$this->sseClient = new MockSseClient();
85-
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . HTTP_SERVER_PORT . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
85+
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . $this->port . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
8686

8787
// 1. Connect
8888
await($this->sseClient->connect($sseBaseUrl));
@@ -125,7 +125,7 @@
125125

126126
it('can handle invalid JSON from client', function () {
127127
$this->sseClient = new MockSseClient();
128-
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . HTTP_SERVER_PORT . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
128+
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . $this->port . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
129129

130130
// 1. Connect
131131
await($this->sseClient->connect($sseBaseUrl));
@@ -159,7 +159,7 @@
159159

160160
it('can handle request for non-existent method after initialization', function () {
161161
$this->sseClient = new MockSseClient();
162-
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . HTTP_SERVER_PORT . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
162+
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . $this->port . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
163163

164164
// 1. Connect
165165
await($this->sseClient->connect($sseBaseUrl));
@@ -188,7 +188,7 @@
188188

189189
it('can handle batch requests correctly over HTTP/SSE', function () {
190190
$this->sseClient = new MockSseClient();
191-
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . HTTP_SERVER_PORT . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
191+
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . $this->port . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
192192

193193
// 1. Connect
194194
await($this->sseClient->connect($sseBaseUrl));
@@ -246,7 +246,7 @@
246246

247247
it('can handle tool list request over HTTP/SSE', function () {
248248
$this->sseClient = new MockSseClient();
249-
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . HTTP_SERVER_PORT . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
249+
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . $this->port . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
250250
await($this->sseClient->connect($sseBaseUrl));
251251
await(delay(0.05, $this->loop));
252252

@@ -268,7 +268,7 @@
268268

269269
it('can read a registered resource over HTTP/SSE', function () {
270270
$this->sseClient = new MockSseClient();
271-
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . HTTP_SERVER_PORT . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
271+
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . $this->port . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
272272
await($this->sseClient->connect($sseBaseUrl));
273273
await(delay(0.05, $this->loop));
274274

@@ -292,7 +292,7 @@
292292

293293
it('can get a registered prompt over HTTP/SSE', function () {
294294
$this->sseClient = new MockSseClient();
295-
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . HTTP_SERVER_PORT . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
295+
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . $this->port . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
296296
await($this->sseClient->connect($sseBaseUrl));
297297
await(delay(0.05, $this->loop));
298298

@@ -318,7 +318,7 @@
318318

319319
it('rejects subsequent requests if client does not send initialized notification', function () {
320320
$this->sseClient = new MockSseClient();
321-
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . HTTP_SERVER_PORT . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
321+
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . $this->port . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
322322
await($this->sseClient->connect($sseBaseUrl));
323323
await(delay(0.05, $this->loop));
324324

@@ -349,7 +349,7 @@
349349

350350
it('returns 404 for POST to /message without valid clientId in query', function () {
351351
$this->sseClient = new MockSseClient();
352-
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . HTTP_SERVER_PORT . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
352+
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . $this->port . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
353353
await($this->sseClient->connect($sseBaseUrl));
354354
await(delay(0.05, $this->loop));
355355
$validEndpointUrl = $this->sseClient->endpointUrl;
@@ -377,7 +377,7 @@
377377

378378
it('returns 404 for POST to /message with clientId for a disconnected SSE stream', function () {
379379
$this->sseClient = new MockSseClient();
380-
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . HTTP_SERVER_PORT . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
380+
$sseBaseUrl = "http://" . HTTP_SERVER_HOST . ":" . $this->port . "/" . HTTP_MCP_PATH_PREFIX . "/sse";
381381

382382
await($this->sseClient->connect($sseBaseUrl));
383383
await(delay(0.05, $this->loop));
@@ -404,7 +404,7 @@
404404

405405
it('returns 404 for unknown paths', function () {
406406
$browser = new Browser($this->loop);
407-
$unknownUrl = "http://" . HTTP_SERVER_HOST . ":" . HTTP_SERVER_PORT . "/unknown/path";
407+
$unknownUrl = "http://" . HTTP_SERVER_HOST . ":" . $this->port . "/unknown/path";
408408

409409
$promise = $browser->get($unknownUrl);
410410

tests/Integration/StreamableHttpServerTransportTest.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
const STREAMABLE_HTTP_SCRIPT_PATH = __DIR__ . '/../Fixtures/ServerScripts/StreamableHttpTestServer.php';
1717
const STREAMABLE_HTTP_PROCESS_TIMEOUT = 9;
1818
const STREAMABLE_HTTP_HOST = '127.0.0.1';
19-
const STREAMABLE_HTTP_PORT = 8992;
2019
const STREAMABLE_MCP_PATH = 'mcp_streamable_json_mode';
2120

2221
beforeEach(function () {
@@ -30,18 +29,19 @@
3029
$phpPath = PHP_BINARY ?: 'php';
3130
$commandPhpPath = str_contains($phpPath, ' ') ? '"' . $phpPath . '"' : $phpPath;
3231
$commandScriptPath = escapeshellarg(STREAMABLE_HTTP_SCRIPT_PATH);
32+
$this->port = findFreePort();
3333

3434
$jsonModeCommandArgs = [
3535
escapeshellarg(STREAMABLE_HTTP_HOST),
36-
escapeshellarg((string)STREAMABLE_HTTP_PORT),
36+
escapeshellarg((string)$this->port),
3737
escapeshellarg(STREAMABLE_MCP_PATH),
3838
escapeshellarg('true'), // enableJsonResponse = true
3939
];
4040
$this->jsonModeCommand = $commandPhpPath . ' ' . $commandScriptPath . ' ' . implode(' ', $jsonModeCommandArgs);
4141

4242
$streamModeCommandArgs = [
4343
escapeshellarg(STREAMABLE_HTTP_HOST),
44-
escapeshellarg((string)STREAMABLE_HTTP_PORT),
44+
escapeshellarg((string)$this->port),
4545
escapeshellarg(STREAMABLE_MCP_PATH),
4646
escapeshellarg('false'), // enableJsonResponse = false
4747
];
@@ -73,7 +73,7 @@
7373
$this->process = new Process($this->jsonModeCommand, getcwd() ?: null, null, []);
7474
$this->process->start();
7575

76-
$this->jsonClient = new MockJsonHttpClient(STREAMABLE_HTTP_HOST, STREAMABLE_HTTP_PORT, STREAMABLE_MCP_PATH);
76+
$this->jsonClient = new MockJsonHttpClient(STREAMABLE_HTTP_HOST, $this->port, STREAMABLE_MCP_PATH);
7777

7878
await(delay(0.2));
7979
});
@@ -283,7 +283,7 @@
283283
beforeEach(function () {
284284
$this->process = new Process($this->streamModeCommand, getcwd() ?: null, null, []);
285285
$this->process->start();
286-
$this->streamClient = new MockStreamHttpClient(STREAMABLE_HTTP_HOST, STREAMABLE_HTTP_PORT, STREAMABLE_MCP_PATH);
286+
$this->streamClient = new MockStreamHttpClient(STREAMABLE_HTTP_HOST, $this->port, STREAMABLE_MCP_PATH);
287287
await(delay(0.2));
288288
});
289289
afterEach(function () {
@@ -482,7 +482,7 @@
482482
it('responds to OPTIONS request with CORS headers', function () {
483483
$this->process = new Process($this->jsonModeCommand, getcwd() ?: null, null, []);
484484
$this->process->start();
485-
$this->jsonClient = new MockJsonHttpClient(STREAMABLE_HTTP_HOST, STREAMABLE_HTTP_PORT, STREAMABLE_MCP_PATH);
485+
$this->jsonClient = new MockJsonHttpClient(STREAMABLE_HTTP_HOST, $this->port, STREAMABLE_MCP_PATH);
486486
await(delay(0.1));
487487

488488
$browser = new Browser();
@@ -501,11 +501,11 @@
501501
it('returns 404 for unknown paths', function () {
502502
$this->process = new Process($this->jsonModeCommand, getcwd() ?: null, null, []);
503503
$this->process->start();
504-
$this->jsonClient = new MockJsonHttpClient(STREAMABLE_HTTP_HOST, STREAMABLE_HTTP_PORT, STREAMABLE_MCP_PATH);
504+
$this->jsonClient = new MockJsonHttpClient(STREAMABLE_HTTP_HOST, $this->port, STREAMABLE_MCP_PATH);
505505
await(delay(0.1));
506506

507507
$browser = new Browser();
508-
$unknownUrl = "http://" . STREAMABLE_HTTP_HOST . ":" . STREAMABLE_HTTP_PORT . "/completely/unknown/path";
508+
$unknownUrl = "http://" . STREAMABLE_HTTP_HOST . ":" . $this->port . "/completely/unknown/path";
509509

510510
$promise = $browser->get($unknownUrl);
511511

@@ -522,7 +522,7 @@
522522
it('can delete client session with DELETE request', function () {
523523
$this->process = new Process($this->jsonModeCommand, getcwd() ?: null, null, []);
524524
$this->process->start();
525-
$this->jsonClient = new MockJsonHttpClient(STREAMABLE_HTTP_HOST, STREAMABLE_HTTP_PORT, STREAMABLE_MCP_PATH);
525+
$this->jsonClient = new MockJsonHttpClient(STREAMABLE_HTTP_HOST, $this->port, STREAMABLE_MCP_PATH);
526526
await(delay(0.1));
527527

528528
// 1. Initialize

tests/Pest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use React\EventLoop\TimerInterface;
66
use React\Promise\Promise;
77
use React\Promise\PromiseInterface;
8+
use React\Socket\SocketServer;
89

910
function getPrivateProperty(object $object, string $propertyName)
1011
{
@@ -79,3 +80,15 @@ function timeout(PromiseInterface $promise, $time, ?LoopInterface $loop = null)
7980
});
8081
}, $canceller);
8182
}
83+
84+
function findFreePort()
85+
{
86+
$server = new SocketServer('127.0.0.1:0');
87+
$address = $server->getAddress();
88+
$port = $address ? parse_url($address, PHP_URL_PORT) : null;
89+
$server->close();
90+
if (!$port) {
91+
throw new \RuntimeException("Could not find a free port for testing.");
92+
}
93+
return (int)$port;
94+
}

0 commit comments

Comments
 (0)