[Server] Throw the SDK's own RuntimeException from FileSessionStore (#404)
The constructor guarded its unwritable-directory case with the global `\RuntimeException`, the only throw in `src/` still outside `Mcp\Exception\ExceptionInterface`. A consumer wrapping server setup in a `catch (ExceptionInterface)` missed it and got an unhandled SPL exception on a misconfigured session directory instead. `Mcp\Exception\RuntimeException` extends `\RuntimeException`, so this is additive: existing `catch (\RuntimeException)` blocks keep working. The constructor had no test coverage at all, so this adds one for the throw plus the directory-creation and write/read paths around it.
This commit is contained in:
parent
d9b3c0ba4e
commit
a987a3a248
2 changed files with 92 additions and 1 deletions
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
namespace Mcp\Server\Session;
|
||||
|
||||
use Mcp\Exception\RuntimeException;
|
||||
use Mcp\Server\NativeClock;
|
||||
use Psr\Clock\ClockInterface;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
|
@ -31,7 +32,7 @@ class FileSessionStore implements SessionStoreInterface
|
|||
}
|
||||
|
||||
if (!is_dir($this->directory) || !is_writable($this->directory)) {
|
||||
throw new \RuntimeException(\sprintf('Session directory "%s" is not writable.', $this->directory));
|
||||
throw new RuntimeException(\sprintf('Session directory "%s" is not writable.', $this->directory));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
90
tests/Unit/Server/Session/FileSessionStoreTest.php
Normal file
90
tests/Unit/Server/Session/FileSessionStoreTest.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Tests\Unit\Server\Session;
|
||||
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Exception\RuntimeException;
|
||||
use Mcp\Server\Session\FileSessionStore;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Uid\UuidV4;
|
||||
|
||||
class FileSessionStoreTest extends TestCase
|
||||
{
|
||||
private string $directory;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->directory = sys_get_temp_dir().'/mcp-file-session-store-'.bin2hex(random_bytes(6));
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (!is_dir($this->directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@chmod($this->directory, 0775);
|
||||
|
||||
foreach (glob($this->directory.'/*') ?: [] as $file) {
|
||||
@unlink($file);
|
||||
}
|
||||
|
||||
@rmdir($this->directory);
|
||||
}
|
||||
|
||||
#[TestDox('creates the session directory when it does not exist yet')]
|
||||
public function testCreatesMissingDirectory(): void
|
||||
{
|
||||
new FileSessionStore($this->directory);
|
||||
|
||||
$this->assertDirectoryExists($this->directory);
|
||||
}
|
||||
|
||||
#[TestDox('round-trips a session payload through the filesystem')]
|
||||
public function testWriteThenRead(): void
|
||||
{
|
||||
$store = new FileSessionStore($this->directory);
|
||||
$id = new UuidV4();
|
||||
|
||||
$store->write($id, 'payload');
|
||||
|
||||
$this->assertTrue($store->exists($id));
|
||||
$this->assertSame('payload', $store->read($id));
|
||||
}
|
||||
|
||||
#[TestDox('rejects an unwritable directory with the SDK\'s own exception')]
|
||||
public function testUnwritableDirectoryThrowsPackageException(): void
|
||||
{
|
||||
mkdir($this->directory, 0775, true);
|
||||
chmod($this->directory, 0555);
|
||||
clearstatcache(true, $this->directory);
|
||||
|
||||
if (is_writable($this->directory)) {
|
||||
$this->markTestSkipped('Permission bits do not restrict writes here (running as root, or a filesystem that ignores them).');
|
||||
}
|
||||
|
||||
// The store's only throw must stay inside the package hierarchy, so a
|
||||
// consumer catching ExceptionInterface sees it rather than a bare SPL
|
||||
// RuntimeException escaping the SDK.
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage(\sprintf('Session directory "%s" is not writable.', $this->directory));
|
||||
|
||||
try {
|
||||
new FileSessionStore($this->directory);
|
||||
} catch (RuntimeException $e) {
|
||||
$this->assertInstanceOf(ExceptionInterface::class, $e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue