Skip to content

Commit cdc11cb

Browse files
committed
feat: Add test suite for Session classes
1 parent 4d183ec commit cdc11cb

File tree

8 files changed

+1750
-5
lines changed

8 files changed

+1750
-5
lines changed

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -965,15 +965,13 @@ composer test
965965
We follow PSR-12 coding standards:
966966

967967
```bash
968-
composer cs-fix # Fix code style
969-
composer cs-check # Check code style
970-
composer refactor # Run rector
968+
composer cs:fix
971969
```
972970

973971
### Testing
974972

975973
```bash
976-
composer test # Run all tests
974+
composer test
977975
```
978976

979977
## License

context.yaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,14 @@ documents:
7575
- vendor/react/http/src/HttpServer.php
7676
- vendor/react/http/src/Io/MiddlewareRunner.php
7777
- vendor/react/http/src/Middleware
78-
- src/Transports/Middleware
78+
- src/Transports/Middleware
79+
80+
- description: Session
81+
outputPath: src/session.md
82+
sources:
83+
- type: file
84+
sourcePaths:
85+
- src/Session
86+
- src/Contracts/SessionHandlerInterface.php
87+
- src/Contracts/SessionIdGeneratorInterface.php
88+
- src/Contracts/SessionInterface.php
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mcp\Server\Tests\Unit\Session;
6+
7+
use Mcp\Server\Session\ArraySessionHandler;
8+
use Mockery;
9+
use PHPUnit\Framework\TestCase;
10+
use Psr\Clock\ClockInterface;
11+
12+
final class ArraySessionHandlerTest extends TestCase
13+
{
14+
private ClockInterface $clock;
15+
private ArraySessionHandler $handler;
16+
17+
protected function setUp(): void
18+
{
19+
$this->clock = Mockery::mock(ClockInterface::class);
20+
$this->handler = new ArraySessionHandler(ttl: 3600, clock: $this->clock);
21+
}
22+
23+
protected function tearDown(): void
24+
{
25+
Mockery::close();
26+
}
27+
28+
public function test_read_returns_false_when_session_not_exists(): void
29+
{
30+
$result = $this->handler->read('non-existent-id');
31+
32+
$this->assertFalse($result);
33+
}
34+
35+
public function test_write_and_read_session_data(): void
36+
{
37+
$dateTime = new \DateTimeImmutable('2025-01-01 12:00:00');
38+
$this->clock->shouldReceive('now')->andReturn($dateTime);
39+
40+
$sessionId = 'test-session-id';
41+
$sessionData = '{"user_id": 123, "username": "test"}';
42+
43+
$writeResult = $this->handler->write($sessionId, $sessionData);
44+
$this->assertTrue($writeResult);
45+
46+
$readResult = $this->handler->read($sessionId);
47+
$this->assertEquals($sessionData, $readResult);
48+
}
49+
50+
public function test_read_returns_false_when_session_expired(): void
51+
{
52+
$writeTime = new \DateTimeImmutable('2025-01-01 12:00:00');
53+
$readTime = new \DateTimeImmutable('2025-01-01 14:01:00'); // 2 hours 1 minute later
54+
55+
$sessionId = 'expired-session';
56+
$sessionData = '{"data": "test"}';
57+
58+
// Write session
59+
$this->clock->shouldReceive('now')->andReturn($writeTime)->once();
60+
$this->handler->write($sessionId, $sessionData);
61+
62+
// Try to read after expiration (ttl = 3600 seconds = 1 hour)
63+
$this->clock->shouldReceive('now')->andReturn($readTime)->once();
64+
$result = $this->handler->read($sessionId);
65+
66+
$this->assertFalse($result);
67+
}
68+
69+
public function test_read_returns_data_when_session_not_expired(): void
70+
{
71+
$writeTime = new \DateTimeImmutable('2025-01-01 12:00:00');
72+
$readTime = new \DateTimeImmutable('2025-01-01 12:30:00'); // 30 minutes later
73+
74+
$sessionId = 'valid-session';
75+
$sessionData = '{"data": "test"}';
76+
77+
// Write session
78+
$this->clock->shouldReceive('now')->andReturn($writeTime)->once();
79+
$this->handler->write($sessionId, $sessionData);
80+
81+
// Read before expiration
82+
$this->clock->shouldReceive('now')->andReturn($readTime)->once();
83+
$result = $this->handler->read($sessionId);
84+
85+
$this->assertEquals($sessionData, $result);
86+
}
87+
88+
public function test_destroy_removes_session(): void
89+
{
90+
$dateTime = new \DateTimeImmutable('2025-01-01 12:00:00');
91+
$this->clock->shouldReceive('now')->andReturn($dateTime)->times(2);
92+
93+
$sessionId = 'test-session';
94+
$sessionData = '{"data": "test"}';
95+
96+
// Write session
97+
$this->handler->write($sessionId, $sessionData);
98+
$this->assertEquals($sessionData, $this->handler->read($sessionId));
99+
100+
// Destroy session
101+
$result = $this->handler->destroy($sessionId);
102+
$this->assertTrue($result);
103+
104+
// Verify session is removed
105+
$this->assertFalse($this->handler->read($sessionId));
106+
}
107+
108+
public function test_destroy_returns_true_for_non_existent_session(): void
109+
{
110+
$result = $this->handler->destroy('non-existent-session');
111+
112+
$this->assertTrue($result);
113+
}
114+
115+
public function test_gc_removes_expired_sessions(): void
116+
{
117+
$writeTime = new \DateTimeImmutable('2025-01-01 12:00:00');
118+
$gcTime = new \DateTimeImmutable('2025-01-01 14:31:00'); // 2 hours 31 minutes later
119+
120+
// Write multiple sessions
121+
$this->clock->shouldReceive('now')->andReturn($writeTime)->times(3);
122+
$this->handler->write('session1', '{"data": "test1"}');
123+
$this->handler->write('session2', '{"data": "test2"}');
124+
$this->handler->write('session3', '{"data": "test3"}');
125+
126+
// Run garbage collection with maxLifetime = 1800 (30 minutes)
127+
$this->clock->shouldReceive('now')->andReturn($gcTime)->once();
128+
$deletedSessions = $this->handler->gc(1800);
129+
130+
$this->assertCount(3, $deletedSessions);
131+
$this->assertContains('session1', $deletedSessions);
132+
$this->assertContains('session2', $deletedSessions);
133+
$this->assertContains('session3', $deletedSessions);
134+
135+
// Verify sessions are actually removed
136+
$this->assertFalse($this->handler->read('session1'));
137+
$this->assertFalse($this->handler->read('session2'));
138+
$this->assertFalse($this->handler->read('session3'));
139+
}
140+
141+
public function test_gc_keeps_non_expired_sessions(): void
142+
{
143+
$writeTime = new \DateTimeImmutable('2025-01-01 12:00:00');
144+
$gcTime = new \DateTimeImmutable('2025-01-01 12:30:00'); // 30 minutes later
145+
146+
// Write session
147+
$this->clock->shouldReceive('now')->andReturn($writeTime)->once();
148+
$this->handler->write('valid-session', '{"data": "test"}');
149+
150+
// Run garbage collection with maxLifetime = 3600 (1 hour)
151+
$this->clock->shouldReceive('now')->andReturn($gcTime)->times(2); // once for gc, once for read
152+
$deletedSessions = $this->handler->gc(3600);
153+
154+
$this->assertEmpty($deletedSessions);
155+
$this->assertEquals('{"data": "test"}', $this->handler->read('valid-session'));
156+
}
157+
158+
public function test_gc_returns_empty_array_when_no_sessions(): void
159+
{
160+
$this->clock->shouldReceive('now')->andReturn(new \DateTimeImmutable())->once();
161+
162+
$deletedSessions = $this->handler->gc(3600);
163+
164+
$this->assertEmpty($deletedSessions);
165+
}
166+
167+
public function test_constructor_with_default_parameters(): void
168+
{
169+
$handler = new ArraySessionHandler();
170+
171+
$this->assertInstanceOf(ArraySessionHandler::class, $handler);
172+
$this->assertEquals(3600, $handler->ttl);
173+
}
174+
175+
public function test_constructor_with_custom_ttl(): void
176+
{
177+
$handler = new ArraySessionHandler(ttl: 7200);
178+
179+
$this->assertEquals(7200, $handler->ttl);
180+
}
181+
}

0 commit comments

Comments
 (0)