Encapsulate session handling from protocol to new SessionManager (#247)
This commit is contained in:
parent
a6ed01bec0
commit
ae320646bf
10 changed files with 444 additions and 200 deletions
|
|
@ -7,6 +7,7 @@ All notable changes to `mcp/sdk` will be documented in this file.
|
|||
|
||||
* Rename `Mcp\Server\Session\Psr16StoreSession` to `Mcp\Server\Session\Psr16SessionStore`
|
||||
* Add missing handlers for resource subscribe/unsubscribe and persist subscriptions via session
|
||||
* Introduce `SessionManager` to encapsulate session handling (replaces `SessionFactory`) and move garbage collection logic from `Protocol`.
|
||||
|
||||
0.3.0
|
||||
-----
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ use Mcp\Capability\Registry\Loader\DiscoveryLoader;
|
|||
use Mcp\Capability\Registry\Loader\LoaderInterface;
|
||||
use Mcp\Capability\Registry\ReferenceHandler;
|
||||
use Mcp\Capability\RegistryInterface;
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\JsonRpc\MessageFactory;
|
||||
use Mcp\Schema\Annotations;
|
||||
use Mcp\Schema\Enum\ProtocolVersion;
|
||||
|
|
@ -36,8 +37,8 @@ use Mcp\Server\Handler\Request\RequestHandlerInterface;
|
|||
use Mcp\Server\Resource\SessionSubscriptionManager;
|
||||
use Mcp\Server\Resource\SubscriptionManagerInterface;
|
||||
use Mcp\Server\Session\InMemorySessionStore;
|
||||
use Mcp\Server\Session\SessionFactory;
|
||||
use Mcp\Server\Session\SessionFactoryInterface;
|
||||
use Mcp\Server\Session\SessionManager;
|
||||
use Mcp\Server\Session\SessionManagerInterface;
|
||||
use Mcp\Server\Session\SessionStoreInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
|
|
@ -70,12 +71,10 @@ final class Builder
|
|||
|
||||
private ?DiscovererInterface $discoverer = null;
|
||||
|
||||
private ?SessionFactoryInterface $sessionFactory = null;
|
||||
private ?SessionManagerInterface $sessionManager = null;
|
||||
|
||||
private ?SessionStoreInterface $sessionStore = null;
|
||||
|
||||
private int $sessionTtl = 3600;
|
||||
|
||||
private int $paginationLimit = 50;
|
||||
|
||||
private ?string $instructions = null;
|
||||
|
|
@ -321,13 +320,15 @@ final class Builder
|
|||
}
|
||||
|
||||
public function setSession(
|
||||
SessionStoreInterface $sessionStore,
|
||||
SessionFactoryInterface $sessionFactory = new SessionFactory(),
|
||||
int $ttl = 3600,
|
||||
?SessionStoreInterface $sessionStore = null,
|
||||
?SessionManagerInterface $sessionManager = null,
|
||||
): self {
|
||||
$this->sessionFactory = $sessionFactory;
|
||||
$this->sessionStore = $sessionStore;
|
||||
$this->sessionTtl = $ttl;
|
||||
$this->sessionManager = $sessionManager;
|
||||
|
||||
if (null !== $sessionManager && null !== $sessionStore) {
|
||||
throw new InvalidArgumentException('Cannot set both SessionStore and SessionManager. Set only one or the other.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
@ -506,9 +507,10 @@ final class Builder
|
|||
new ArrayLoader($this->tools, $this->resources, $this->resourceTemplates, $this->prompts, $logger, $this->schemaGenerator),
|
||||
];
|
||||
|
||||
$sessionTtl = $this->sessionTtl ?? 3600;
|
||||
$sessionFactory = $this->sessionFactory ?? new SessionFactory();
|
||||
$sessionStore = $this->sessionStore ?? new InMemorySessionStore($sessionTtl);
|
||||
$sessionManager = $this->sessionManager ?? new SessionManager(
|
||||
$this->sessionStore ?? new InMemorySessionStore(),
|
||||
$logger,
|
||||
);
|
||||
|
||||
if (null !== $this->discoveryBasePath) {
|
||||
$discoverer = $this->discoverer ?? $this->createDiscoverer($logger);
|
||||
|
|
@ -561,8 +563,7 @@ final class Builder
|
|||
requestHandlers: $requestHandlers,
|
||||
notificationHandlers: $notificationHandlers,
|
||||
messageFactory: $messageFactory,
|
||||
sessionFactory: $sessionFactory,
|
||||
sessionStore: $sessionStore,
|
||||
sessionManager: $sessionManager,
|
||||
logger: $logger,
|
||||
eventDispatcher: $this->eventDispatcher,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -25,9 +25,8 @@ use Mcp\Schema\JsonRpc\ResultInterface;
|
|||
use Mcp\Schema\Request\InitializeRequest;
|
||||
use Mcp\Server\Handler\Notification\NotificationHandlerInterface;
|
||||
use Mcp\Server\Handler\Request\RequestHandlerInterface;
|
||||
use Mcp\Server\Session\SessionFactoryInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
use Mcp\Server\Session\SessionStoreInterface;
|
||||
use Mcp\Server\Session\SessionManagerInterface;
|
||||
use Mcp\Server\Transport\TransportInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
@ -70,8 +69,7 @@ class Protocol
|
|||
private readonly array $requestHandlers,
|
||||
private readonly array $notificationHandlers,
|
||||
private readonly MessageFactory $messageFactory,
|
||||
private readonly SessionFactoryInterface $sessionFactory,
|
||||
private readonly SessionStoreInterface $sessionStore,
|
||||
private readonly SessionManagerInterface $sessionManager,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
private readonly ?EventDispatcherInterface $eventDispatcher = null,
|
||||
) {
|
||||
|
|
@ -112,7 +110,7 @@ class Protocol
|
|||
{
|
||||
$this->logger->info('Received message to process.', ['message' => $input]);
|
||||
|
||||
$this->gcSessions();
|
||||
$this->sessionManager->gc();
|
||||
|
||||
try {
|
||||
$messages = $this->messageFactory->create($input);
|
||||
|
|
@ -410,7 +408,7 @@ class Protocol
|
|||
*/
|
||||
public function consumeOutgoingMessages(Uuid $sessionId): array
|
||||
{
|
||||
$session = $this->sessionFactory->createWithId($sessionId, $this->sessionStore);
|
||||
$session = $this->sessionManager->createWithId($sessionId);
|
||||
$queue = $session->get(self::SESSION_OUTGOING_QUEUE, []);
|
||||
$session->set(self::SESSION_OUTGOING_QUEUE, []);
|
||||
$session->save();
|
||||
|
|
@ -429,7 +427,7 @@ class Protocol
|
|||
*/
|
||||
public function checkResponse(int $requestId, Uuid $sessionId): Response|Error|null
|
||||
{
|
||||
$session = $this->sessionFactory->createWithId($sessionId, $this->sessionStore);
|
||||
$session = $this->sessionManager->createWithId($sessionId);
|
||||
$responseData = $session->get(self::SESSION_RESPONSES.".{$requestId}");
|
||||
|
||||
if (null === $responseData) {
|
||||
|
|
@ -471,7 +469,7 @@ class Protocol
|
|||
*/
|
||||
public function getPendingRequests(Uuid $sessionId): array
|
||||
{
|
||||
$session = $this->sessionFactory->createWithId($sessionId, $this->sessionStore);
|
||||
$session = $this->sessionManager->createWithId($sessionId);
|
||||
|
||||
return $session->get(self::SESSION_PENDING_REQUESTS, []);
|
||||
}
|
||||
|
|
@ -498,7 +496,7 @@ class Protocol
|
|||
return;
|
||||
}
|
||||
|
||||
$session = $this->sessionFactory->createWithId($sessionId, $this->sessionStore);
|
||||
$session = $this->sessionManager->createWithId($sessionId);
|
||||
|
||||
$payloadSessionId = $yieldedValue['session_id'] ?? null;
|
||||
if (\is_string($payloadSessionId) && $payloadSessionId !== $sessionId->toRfc4122()) {
|
||||
|
|
@ -582,7 +580,7 @@ class Protocol
|
|||
return null;
|
||||
}
|
||||
|
||||
$session = $this->sessionFactory->create($this->sessionStore);
|
||||
$session = $this->sessionManager->create();
|
||||
$this->logger->debug('Created new session for initialize', [
|
||||
'session_id' => $session->getId()->toRfc4122(),
|
||||
]);
|
||||
|
|
@ -599,33 +597,14 @@ class Protocol
|
|||
return null;
|
||||
}
|
||||
|
||||
if (!$this->sessionStore->exists($sessionId)) {
|
||||
if (!$this->sessionManager->exists($sessionId)) {
|
||||
$error = Error::forInvalidRequest('Session not found or has expired.');
|
||||
$this->sendResponse($transport, $error, null, ['status_code' => 404]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->sessionFactory->createWithId($sessionId, $this->sessionStore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run garbage collection on expired sessions.
|
||||
* Uses the session store's internal TTL configuration.
|
||||
*/
|
||||
private function gcSessions(): void
|
||||
{
|
||||
if (random_int(0, 100) > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$deletedSessions = $this->sessionStore->gc();
|
||||
if (!empty($deletedSessions)) {
|
||||
$this->logger->debug('Garbage collected expired sessions.', [
|
||||
'count' => \count($deletedSessions),
|
||||
'session_ids' => array_map(static fn (Uuid $id) => $id->toRfc4122(), $deletedSessions),
|
||||
]);
|
||||
}
|
||||
return $this->sessionManager->createWithId($sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -633,7 +612,7 @@ class Protocol
|
|||
*/
|
||||
public function destroySession(Uuid $sessionId): void
|
||||
{
|
||||
$this->sessionStore->destroy($sessionId);
|
||||
$this->sessionManager->destroy($sessionId);
|
||||
$this->logger->info('Session destroyed.', ['session_id' => $sessionId->toRfc4122()]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,11 +44,6 @@ class Session implements SessionInterface
|
|||
return $this->id;
|
||||
}
|
||||
|
||||
public function getStore(): SessionStoreInterface
|
||||
{
|
||||
return $this->store;
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
return $this->store->write($this->id, json_encode($this->readData(), \JSON_THROW_ON_ERROR));
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
<?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\Server\Session;
|
||||
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
/**
|
||||
* Default implementation of SessionFactoryInterface.
|
||||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
class SessionFactory implements SessionFactoryInterface
|
||||
{
|
||||
public function create(SessionStoreInterface $store): SessionInterface
|
||||
{
|
||||
return new Session($store, Uuid::v4());
|
||||
}
|
||||
|
||||
public function createWithId(Uuid $id, SessionStoreInterface $store): SessionInterface
|
||||
{
|
||||
return new Session($store, $id);
|
||||
}
|
||||
}
|
||||
|
|
@ -77,9 +77,4 @@ interface SessionInterface extends \JsonSerializable
|
|||
* @param array<string, mixed> $attributes
|
||||
*/
|
||||
public function hydrate(array $attributes): void;
|
||||
|
||||
/**
|
||||
* Get the session store instance.
|
||||
*/
|
||||
public function getStore(): SessionStoreInterface;
|
||||
}
|
||||
|
|
|
|||
69
src/Server/Session/SessionManager.php
Normal file
69
src/Server/Session/SessionManager.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?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\Server\Session;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
/**
|
||||
* Default implementation of SessionManagerInterface.
|
||||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
class SessionManager implements SessionManagerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SessionStoreInterface $store,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function create(): SessionInterface
|
||||
{
|
||||
return new Session($this->store, Uuid::v4());
|
||||
}
|
||||
|
||||
public function createWithId(Uuid $id): SessionInterface
|
||||
{
|
||||
return new Session($this->store, $id);
|
||||
}
|
||||
|
||||
public function exists(Uuid $id): bool
|
||||
{
|
||||
return $this->store->exists($id);
|
||||
}
|
||||
|
||||
public function destroy(Uuid $id): bool
|
||||
{
|
||||
return $this->store->destroy($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run garbage collection on expired sessions.
|
||||
* Uses the session store's internal TTL configuration.
|
||||
*/
|
||||
public function gc(): void
|
||||
{
|
||||
if (random_int(0, 100) > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$deletedSessions = $this->store->gc();
|
||||
if (!empty($deletedSessions)) {
|
||||
$this->logger->debug('Garbage collected expired sessions.', [
|
||||
'count' => \count($deletedSessions),
|
||||
'session_ids' => array_map(static fn (Uuid $id) => $id->toRfc4122(), $deletedSessions),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/Server/Session/SessionManagerInterface.php
Normal file
47
src/Server/Session/SessionManagerInterface.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?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\Server\Session;
|
||||
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
/**
|
||||
* Factory interface for creating session instances.
|
||||
* This allows for different session implementations and custom initialization logic.
|
||||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
interface SessionManagerInterface
|
||||
{
|
||||
/**
|
||||
* Creates a new session with an auto-generated UUID.
|
||||
* This is the standard factory method for creating sessions.
|
||||
*/
|
||||
public function create(): SessionInterface;
|
||||
|
||||
/**
|
||||
* Creates a session with a specific UUID.
|
||||
* Use this when you need to reconstruct a session with a known ID.
|
||||
*/
|
||||
public function createWithId(Uuid $id): SessionInterface;
|
||||
|
||||
/**
|
||||
* Checks if a session with the given UUID exists.
|
||||
*/
|
||||
public function exists(Uuid $id): bool;
|
||||
|
||||
/**
|
||||
* Destroys the session with the given UUID.
|
||||
*/
|
||||
public function destroy(Uuid $id): bool;
|
||||
|
||||
public function gc(): void;
|
||||
}
|
||||
|
|
@ -24,9 +24,8 @@ use Mcp\Schema\Request\CallToolRequest;
|
|||
use Mcp\Server\Handler\Notification\NotificationHandlerInterface;
|
||||
use Mcp\Server\Handler\Request\RequestHandlerInterface;
|
||||
use Mcp\Server\Protocol;
|
||||
use Mcp\Server\Session\SessionFactoryInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
use Mcp\Server\Session\SessionStoreInterface;
|
||||
use Mcp\Server\Session\SessionManagerInterface;
|
||||
use Mcp\Server\Transport\TransportInterface;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
|
@ -36,15 +35,13 @@ use Symfony\Component\Uid\Uuid;
|
|||
|
||||
final class ProtocolTest extends TestCase
|
||||
{
|
||||
private MockObject&SessionFactoryInterface $sessionFactory;
|
||||
private MockObject&SessionStoreInterface $sessionStore;
|
||||
private MockObject&SessionManagerInterface $sessionManager;
|
||||
/** @var MockObject&TransportInterface<mixed> */
|
||||
private MockObject&TransportInterface $transport;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->sessionFactory = $this->createMock(SessionFactoryInterface::class);
|
||||
$this->sessionStore = $this->createMock(SessionStoreInterface::class);
|
||||
$this->sessionManager = $this->createMock(SessionManagerInterface::class);
|
||||
$this->transport = $this->createMock(TransportInterface::class);
|
||||
}
|
||||
|
||||
|
|
@ -65,15 +62,14 @@ final class ProtocolTest extends TestCase
|
|||
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [],
|
||||
notificationHandlers: [$handlerA, $handlerB, $handlerC],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$sessionId = Uuid::v4();
|
||||
|
|
@ -101,8 +97,8 @@ final class ProtocolTest extends TestCase
|
|||
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
$session->method('getId')->willReturn(Uuid::v4());
|
||||
|
||||
// Configure session mock for queue operations
|
||||
|
|
@ -130,8 +126,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [$handlerA, $handlerB, $handlerC],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$sessionId = Uuid::v4();
|
||||
|
|
@ -168,8 +163,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$sessionId = Uuid::v4();
|
||||
|
|
@ -199,8 +193,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$protocol->processInput(
|
||||
|
|
@ -231,8 +224,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$protocol->processInput(
|
||||
|
|
@ -245,7 +237,7 @@ final class ProtocolTest extends TestCase
|
|||
#[TestDox('Non-existent session ID returns error')]
|
||||
public function testNonExistentSessionIdReturnsError(): void
|
||||
{
|
||||
$this->sessionStore->method('exists')->willReturn(false);
|
||||
$this->sessionManager->method('exists')->willReturn(false);
|
||||
|
||||
$this->transport->expects($this->once())
|
||||
->method('send')
|
||||
|
|
@ -265,8 +257,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$sessionId = Uuid::v4();
|
||||
|
|
@ -296,8 +287,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$protocol->processInput(
|
||||
|
|
@ -312,8 +302,8 @@ final class ProtocolTest extends TestCase
|
|||
{
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
// Configure session mock for queue operations
|
||||
$queue = [];
|
||||
|
|
@ -340,8 +330,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$sessionId = Uuid::v4();
|
||||
|
|
@ -365,8 +354,8 @@ final class ProtocolTest extends TestCase
|
|||
{
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
// Configure session mock for queue operations
|
||||
$queue = [];
|
||||
|
|
@ -393,8 +382,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$sessionId = Uuid::v4();
|
||||
|
|
@ -423,8 +411,8 @@ final class ProtocolTest extends TestCase
|
|||
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
// Configure session mock for queue operations
|
||||
$queue = [];
|
||||
|
|
@ -451,8 +439,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [$handler],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$sessionId = Uuid::v4();
|
||||
|
|
@ -481,8 +468,8 @@ final class ProtocolTest extends TestCase
|
|||
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
// Configure session mock for queue operations
|
||||
$queue = [];
|
||||
|
|
@ -509,8 +496,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [$handler],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$sessionId = Uuid::v4();
|
||||
|
|
@ -539,15 +525,14 @@ final class ProtocolTest extends TestCase
|
|||
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [],
|
||||
notificationHandlers: [$handler],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$sessionId = Uuid::v4();
|
||||
|
|
@ -571,8 +556,8 @@ final class ProtocolTest extends TestCase
|
|||
$session = $this->createMock(SessionInterface::class);
|
||||
$session->method('getId')->willReturn($sessionId);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
// Configure session mock for queue operations
|
||||
$queue = [];
|
||||
|
|
@ -599,8 +584,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [$handler],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$protocol->processInput(
|
||||
|
|
@ -650,8 +634,8 @@ final class ProtocolTest extends TestCase
|
|||
}
|
||||
});
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
// The protocol now queues responses instead of sending them directly
|
||||
$session->expects($this->exactly(2))
|
||||
|
|
@ -661,8 +645,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [$handlerA],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$sessionId = Uuid::v4();
|
||||
|
|
@ -687,8 +670,8 @@ final class ProtocolTest extends TestCase
|
|||
{
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$session->expects($this->once())->method('save');
|
||||
|
||||
|
|
@ -696,8 +679,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$sessionId = Uuid::v4();
|
||||
|
|
@ -713,7 +695,7 @@ final class ProtocolTest extends TestCase
|
|||
{
|
||||
$sessionId = Uuid::v4();
|
||||
|
||||
$this->sessionStore->expects($this->once())
|
||||
$this->sessionManager->expects($this->once())
|
||||
->method('destroy')
|
||||
->with($sessionId);
|
||||
|
||||
|
|
@ -721,8 +703,7 @@ final class ProtocolTest extends TestCase
|
|||
requestHandlers: [],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
);
|
||||
|
||||
$protocol->destroySession($sessionId);
|
||||
|
|
@ -750,15 +731,14 @@ final class ProtocolTest extends TestCase
|
|||
$session->method('getId')->willReturn(Uuid::v4());
|
||||
$session->method('get')->willReturn([]);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [$handler],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: $eventDispatcher,
|
||||
);
|
||||
|
||||
|
|
@ -820,15 +800,14 @@ final class ProtocolTest extends TestCase
|
|||
$session->method('getId')->willReturn(Uuid::v4());
|
||||
$session->method('get')->willReturn([]);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [$handler],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: $eventDispatcher,
|
||||
);
|
||||
|
||||
|
|
@ -857,15 +836,14 @@ final class ProtocolTest extends TestCase
|
|||
$session->method('getId')->willReturn(Uuid::v4());
|
||||
$session->method('get')->willReturn([]);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [$handler],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: null,
|
||||
);
|
||||
|
||||
|
|
@ -902,15 +880,14 @@ final class ProtocolTest extends TestCase
|
|||
$session->method('getId')->willReturn(Uuid::v4());
|
||||
$session->method('get')->willReturn([]);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [$handler],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: $eventDispatcher,
|
||||
);
|
||||
|
||||
|
|
@ -969,15 +946,14 @@ final class ProtocolTest extends TestCase
|
|||
}
|
||||
});
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [$handler],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: $eventDispatcher,
|
||||
);
|
||||
|
||||
|
|
@ -1021,15 +997,14 @@ final class ProtocolTest extends TestCase
|
|||
$session->method('getId')->willReturn(Uuid::v4());
|
||||
$session->method('get')->willReturn([]);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [$handler],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: $eventDispatcher,
|
||||
);
|
||||
|
||||
|
|
@ -1074,15 +1049,14 @@ final class ProtocolTest extends TestCase
|
|||
$session->method('getId')->willReturn(Uuid::v4());
|
||||
$session->method('get')->willReturn([]);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [$handler],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: $eventDispatcher,
|
||||
);
|
||||
|
||||
|
|
@ -1126,15 +1100,14 @@ final class ProtocolTest extends TestCase
|
|||
$session->method('getId')->willReturn(Uuid::v4());
|
||||
$session->method('get')->willReturn([]);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [$handler],
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: $eventDispatcher,
|
||||
);
|
||||
|
||||
|
|
@ -1176,15 +1149,14 @@ final class ProtocolTest extends TestCase
|
|||
$session->expects($this->once())->method('save');
|
||||
$session->expects($this->atLeastOnce())->method('set');
|
||||
|
||||
$this->sessionFactory->method('create')->willReturn($session); // create() for initialize
|
||||
$this->sessionStore->method('exists')->willReturn(false); // No existing session
|
||||
$this->sessionManager->method('create')->willReturn($session); // create() for initialize
|
||||
$this->sessionManager->method('exists')->willReturn(false); // No existing session
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [], // No handlers
|
||||
notificationHandlers: [],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: $eventDispatcher,
|
||||
);
|
||||
|
||||
|
|
@ -1227,15 +1199,14 @@ final class ProtocolTest extends TestCase
|
|||
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [],
|
||||
notificationHandlers: [$handler],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: $eventDispatcher,
|
||||
);
|
||||
|
||||
|
|
@ -1289,15 +1260,14 @@ final class ProtocolTest extends TestCase
|
|||
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [],
|
||||
notificationHandlers: [$handler],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: $eventDispatcher,
|
||||
);
|
||||
|
||||
|
|
@ -1324,15 +1294,14 @@ final class ProtocolTest extends TestCase
|
|||
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
|
||||
$this->sessionFactory->method('createWithId')->willReturn($session);
|
||||
$this->sessionStore->method('exists')->willReturn(true);
|
||||
$this->sessionManager->method('createWithId')->willReturn($session);
|
||||
$this->sessionManager->method('exists')->willReturn(true);
|
||||
|
||||
$protocol = new Protocol(
|
||||
requestHandlers: [],
|
||||
notificationHandlers: [$handler],
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $this->sessionFactory,
|
||||
sessionStore: $this->sessionStore,
|
||||
sessionManager: $this->sessionManager,
|
||||
eventDispatcher: null,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,264 @@ namespace Mcp\Tests\Unit\Server\Session;
|
|||
use Mcp\Server\Session\InMemorySessionStore;
|
||||
use Mcp\Server\Session\Session;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Uid\UuidV4;
|
||||
|
||||
class SessionTest extends TestCase
|
||||
{
|
||||
private InMemorySessionStore $store;
|
||||
private Session $session;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->store = new InMemorySessionStore();
|
||||
$this->session = new Session($this->store);
|
||||
}
|
||||
|
||||
public function testGetIdReturnsSessionId(): void
|
||||
{
|
||||
$id = new UuidV4();
|
||||
$session = new Session($this->store, $id);
|
||||
|
||||
$this->assertSame($id, $session->getId());
|
||||
}
|
||||
|
||||
public function testSetAndGetSimpleKey(): void
|
||||
{
|
||||
$this->session->set('foo', 'bar');
|
||||
|
||||
$this->assertSame('bar', $this->session->get('foo'));
|
||||
}
|
||||
|
||||
public function testGetReturnsDefaultWhenKeyDoesNotExist(): void
|
||||
{
|
||||
$this->assertNull($this->session->get('nonexistent'));
|
||||
$this->assertSame('default', $this->session->get('nonexistent', 'default'));
|
||||
}
|
||||
|
||||
public function testSetAndGetNestedKey(): void
|
||||
{
|
||||
$this->session->set('user.name', 'John');
|
||||
$this->session->set('user.email', 'john@example.com');
|
||||
$this->session->set('user.address.city', 'New York');
|
||||
|
||||
$this->assertSame('John', $this->session->get('user.name'));
|
||||
$this->assertSame('john@example.com', $this->session->get('user.email'));
|
||||
$this->assertSame('New York', $this->session->get('user.address.city'));
|
||||
$this->assertSame(['city' => 'New York'], $this->session->get('user.address'));
|
||||
}
|
||||
|
||||
public function testSetDoesNotOverwriteWhenOverwriteIsFalse(): void
|
||||
{
|
||||
$this->session->set('key', 'original');
|
||||
$this->session->set('key', 'new', overwrite: false);
|
||||
|
||||
$this->assertSame('original', $this->session->get('key'));
|
||||
}
|
||||
|
||||
public function testSetOverwritesWhenOverwriteIsTrue(): void
|
||||
{
|
||||
$this->session->set('key', 'original');
|
||||
$this->session->set('key', 'new');
|
||||
|
||||
$this->assertSame('new', $this->session->get('key'));
|
||||
}
|
||||
|
||||
public function testHasReturnsTrueForExistingKey(): void
|
||||
{
|
||||
$this->session->set('foo', 'bar');
|
||||
|
||||
$this->assertTrue($this->session->has('foo'));
|
||||
}
|
||||
|
||||
public function testHasReturnsFalseForNonExistingKey(): void
|
||||
{
|
||||
$this->assertFalse($this->session->has('nonexistent'));
|
||||
}
|
||||
|
||||
public function testHasWorksWithNestedKeys(): void
|
||||
{
|
||||
$this->session->set('user.name', 'John');
|
||||
|
||||
$this->assertTrue($this->session->has('user'));
|
||||
$this->assertTrue($this->session->has('user.name'));
|
||||
$this->assertFalse($this->session->has('user.email'));
|
||||
$this->assertFalse($this->session->has('user.name.first'));
|
||||
}
|
||||
|
||||
public function testForgetRemovesSimpleKey(): void
|
||||
{
|
||||
$this->session->set('foo', 'bar');
|
||||
$this->session->forget('foo');
|
||||
|
||||
$this->assertFalse($this->session->has('foo'));
|
||||
$this->assertNull($this->session->get('foo'));
|
||||
}
|
||||
|
||||
public function testForgetRemovesNestedKey(): void
|
||||
{
|
||||
$this->session->set('user.name', 'John');
|
||||
$this->session->set('user.email', 'john@example.com');
|
||||
$this->session->forget('user.name');
|
||||
|
||||
$this->assertFalse($this->session->has('user.name'));
|
||||
$this->assertTrue($this->session->has('user.email'));
|
||||
}
|
||||
|
||||
public function testClearRemovesAllData(): void
|
||||
{
|
||||
$this->session->set('foo', 'bar');
|
||||
$this->session->set('baz', 'qux');
|
||||
$this->session->clear();
|
||||
|
||||
$this->assertSame([], $this->session->all());
|
||||
$this->assertFalse($this->session->has('foo'));
|
||||
$this->assertFalse($this->session->has('baz'));
|
||||
}
|
||||
|
||||
public function testPullReturnsValueAndRemovesKey(): void
|
||||
{
|
||||
$this->session->set('foo', 'bar');
|
||||
|
||||
$value = $this->session->pull('foo');
|
||||
|
||||
$this->assertSame('bar', $value);
|
||||
$this->assertFalse($this->session->has('foo'));
|
||||
}
|
||||
|
||||
public function testPullReturnsDefaultWhenKeyDoesNotExist(): void
|
||||
{
|
||||
$this->assertNull($this->session->pull('nonexistent'));
|
||||
$this->assertSame('default', $this->session->pull('also_nonexistent', 'default'));
|
||||
}
|
||||
|
||||
public function testAllReturnsAllData(): void
|
||||
{
|
||||
$this->session->set('foo', 'bar');
|
||||
$this->session->set('user.name', 'John');
|
||||
|
||||
$all = $this->session->all();
|
||||
|
||||
$this->assertSame([
|
||||
'foo' => 'bar',
|
||||
'user' => ['name' => 'John'],
|
||||
], $all);
|
||||
}
|
||||
|
||||
public function testHydrateReplacesAllData(): void
|
||||
{
|
||||
$this->session->set('original', 'value');
|
||||
|
||||
$this->session->hydrate(['new' => 'data', 'nested' => ['key' => 'value']]);
|
||||
|
||||
$this->assertSame([
|
||||
'new' => 'data',
|
||||
'nested' => ['key' => 'value'],
|
||||
], $this->session->all());
|
||||
$this->assertFalse($this->session->has('original'));
|
||||
}
|
||||
|
||||
public function testJsonSerializeReturnsAllData(): void
|
||||
{
|
||||
$this->session->set('foo', 'bar');
|
||||
$this->session->set('user.name', 'John');
|
||||
|
||||
$serialized = $this->session->jsonSerialize();
|
||||
|
||||
$this->assertSame([
|
||||
'foo' => 'bar',
|
||||
'user' => ['name' => 'John'],
|
||||
], $serialized);
|
||||
}
|
||||
|
||||
public function testSavePersistsDataToStore(): void
|
||||
{
|
||||
$this->session->set('foo', 'bar');
|
||||
$result = $this->session->save();
|
||||
|
||||
$this->assertTrue($result);
|
||||
|
||||
// Verify data was persisted by creating a new session with the same ID
|
||||
$newSession = new Session($this->store, $this->session->getId());
|
||||
$this->assertSame('bar', $newSession->get('foo'));
|
||||
}
|
||||
|
||||
public function testSessionLoadsDataFromStoreOnConstruction(): void
|
||||
{
|
||||
// Set and save data in one session
|
||||
$this->session->set('persisted', 'value');
|
||||
$this->session->save();
|
||||
$sessionId = $this->session->getId();
|
||||
|
||||
// Create a new session instance with the same ID
|
||||
$newSession = new Session($this->store, $sessionId);
|
||||
|
||||
$this->assertSame('value', $newSession->get('persisted'));
|
||||
}
|
||||
|
||||
public function testSetCreatesNestedStructure(): void
|
||||
{
|
||||
$this->session->set('a.b.c.d', 'value');
|
||||
|
||||
$this->assertSame('value', $this->session->get('a.b.c.d'));
|
||||
$this->assertSame(['d' => 'value'], $this->session->get('a.b.c'));
|
||||
$this->assertSame(['c' => ['d' => 'value']], $this->session->get('a.b'));
|
||||
$this->assertSame(['b' => ['c' => ['d' => 'value']]], $this->session->get('a'));
|
||||
}
|
||||
|
||||
public function testSetOverwritesNonArrayWithNestedStructure(): void
|
||||
{
|
||||
$this->session->set('key', 'string_value');
|
||||
$this->session->set('key.nested', 'nested_value');
|
||||
|
||||
$this->assertSame('nested_value', $this->session->get('key.nested'));
|
||||
$this->assertSame(['nested' => 'nested_value'], $this->session->get('key'));
|
||||
}
|
||||
|
||||
public function testGetReturnsArrayForIntermediateKey(): void
|
||||
{
|
||||
$this->session->set('user.profile.name', 'John');
|
||||
$this->session->set('user.profile.age', 30);
|
||||
|
||||
$profile = $this->session->get('user.profile');
|
||||
|
||||
$this->assertSame(['name' => 'John', 'age' => 30], $profile);
|
||||
}
|
||||
|
||||
public function testForgetDoesNotThrowWhenKeyDoesNotExist(): void
|
||||
{
|
||||
$this->session->forget('nonexistent');
|
||||
$this->session->forget('nested.nonexistent');
|
||||
|
||||
$this->assertFalse($this->session->has('nonexistent'));
|
||||
}
|
||||
|
||||
public function testSessionCanStoreVariousDataTypes(): void
|
||||
{
|
||||
$this->session->set('string', 'value');
|
||||
$this->session->set('int', 42);
|
||||
$this->session->set('float', 3.14);
|
||||
$this->session->set('bool', true);
|
||||
$this->session->set('null', null);
|
||||
$this->session->set('array', ['a', 'b', 'c']);
|
||||
$this->session->set('assoc', ['key' => 'value']);
|
||||
|
||||
$this->assertSame('value', $this->session->get('string'));
|
||||
$this->assertSame(42, $this->session->get('int'));
|
||||
$this->assertSame(3.14, $this->session->get('float'));
|
||||
$this->assertTrue($this->session->get('bool'));
|
||||
$this->assertNull($this->session->get('null'));
|
||||
$this->assertSame(['a', 'b', 'c'], $this->session->get('array'));
|
||||
$this->assertSame(['key' => 'value'], $this->session->get('assoc'));
|
||||
}
|
||||
|
||||
public function testSessionGeneratesUniqueIdIfNotProvided(): void
|
||||
{
|
||||
$session1 = new Session($this->store);
|
||||
$session2 = new Session($this->store);
|
||||
|
||||
$this->assertNotEquals($session1->getId()->toRfc4122(), $session2->getId()->toRfc4122());
|
||||
}
|
||||
|
||||
public function testAll()
|
||||
{
|
||||
$store = $this->getMockBuilder(InMemorySessionStore::class)
|
||||
|
|
|
|||
Loading…
Reference in a new issue