Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
387
tests/Framework/Http/Session/SessionManagerTest.php
Normal file
387
tests/Framework/Http/Session/SessionManagerTest.php
Normal file
@@ -0,0 +1,387 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Framework\DateTime\FrozenClock;
|
||||
use App\Framework\Http\Cookies\Cookie;
|
||||
use App\Framework\Http\Cookies\Cookies;
|
||||
use App\Framework\Http\HttpRequest;
|
||||
use App\Framework\Http\Response;
|
||||
use App\Framework\Http\ResponseManipulator;
|
||||
use App\Framework\Http\Session\InMemorySessionStorage;
|
||||
use App\Framework\Http\Session\Session;
|
||||
use App\Framework\Http\Session\SessionCookieConfig;
|
||||
use App\Framework\Http\Session\SessionId;
|
||||
use App\Framework\Http\Session\SessionManager;
|
||||
use App\Framework\Http\Session\SimpleSessionIdGenerator;
|
||||
use App\Framework\Random\TestableRandomGenerator;
|
||||
use App\Framework\Security\CsrfTokenGenerator;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->clock = new FrozenClock();
|
||||
$this->randomGenerator = new TestableRandomGenerator();
|
||||
$this->csrfTokenGenerator = new CsrfTokenGenerator($this->randomGenerator);
|
||||
$this->storage = new InMemorySessionStorage();
|
||||
$this->idGenerator = new SimpleSessionIdGenerator($this->randomGenerator);
|
||||
$this->responseManipulator = new ResponseManipulator();
|
||||
$this->cookieConfig = new SessionCookieConfig();
|
||||
|
||||
$this->sessionManager = new SessionManager(
|
||||
$this->idGenerator,
|
||||
$this->responseManipulator,
|
||||
$this->clock,
|
||||
$this->csrfTokenGenerator,
|
||||
$this->storage,
|
||||
$this->cookieConfig
|
||||
);
|
||||
});
|
||||
|
||||
describe('SessionManager Basic Operations', function () {
|
||||
test('creates new session when no cookie exists', function () {
|
||||
$request = new HttpRequest(
|
||||
path: '/',
|
||||
cookies: new Cookies()
|
||||
);
|
||||
|
||||
$session = $this->sessionManager->getOrCreateSession($request);
|
||||
|
||||
expect($session)->toBeInstanceOf(Session::class);
|
||||
expect($session->isStarted())->toBeTrue();
|
||||
});
|
||||
|
||||
test('creates new session explicitly', function () {
|
||||
$session = $this->sessionManager->createNewSession();
|
||||
|
||||
expect($session)->toBeInstanceOf(Session::class);
|
||||
expect($session->isStarted())->toBeTrue();
|
||||
});
|
||||
|
||||
test('loads existing session from cookie', function () {
|
||||
// Erst eine Session erstellen und Daten speichern
|
||||
$sessionId = SessionId::fromString('existingsessionid1234567890abcdefg');
|
||||
$testData = ['user_id' => 123, 'username' => 'testuser'];
|
||||
$this->storage->write($sessionId, $testData);
|
||||
|
||||
// Request mit Session-Cookie erstellen
|
||||
$cookies = new Cookies([
|
||||
new Cookie('ms_context', $sessionId->toString()),
|
||||
]);
|
||||
|
||||
$request = new Request(
|
||||
method: 'GET',
|
||||
uri: '/',
|
||||
cookies: $cookies,
|
||||
headers: [],
|
||||
body: ''
|
||||
);
|
||||
|
||||
$session = $this->sessionManager->getOrCreateSession($request);
|
||||
|
||||
expect($session->id->toString())->toBe($sessionId->toString());
|
||||
expect($session->get('user_id'))->toBe(123);
|
||||
expect($session->get('username'))->toBe('testuser');
|
||||
});
|
||||
|
||||
test('creates new session when existing session data is corrupted', function () {
|
||||
// Session-ID existiert, aber keine Daten im Storage
|
||||
$sessionId = SessionId::fromString('nonexistentsessionid1234567890abc');
|
||||
|
||||
$cookies = new Cookies([
|
||||
new Cookie('ms_context', $sessionId->toString()),
|
||||
]);
|
||||
|
||||
$request = new Request(
|
||||
method: 'GET',
|
||||
uri: '/',
|
||||
cookies: $cookies,
|
||||
headers: [],
|
||||
body: ''
|
||||
);
|
||||
|
||||
$session = $this->sessionManager->getOrCreateSession($request);
|
||||
|
||||
// Sollte eine neue Session erstellen, nicht die alte laden
|
||||
expect($session->id->toString())->not->toBe($sessionId->toString());
|
||||
expect($session->all())->toBe([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SessionManager Session Persistence', function () {
|
||||
test('saves session data correctly', function () {
|
||||
$session = $this->sessionManager->createNewSession();
|
||||
$session->set('test_key', 'test_value');
|
||||
$session->set('user_data', ['id' => 456, 'name' => 'Test User']);
|
||||
|
||||
$response = new Response(200, [], '');
|
||||
$responseWithCookie = $this->sessionManager->saveSession($session, $response);
|
||||
|
||||
// Prüfe ob Daten im Storage gespeichert wurden
|
||||
$storedData = $this->storage->read($session->id);
|
||||
expect($storedData['test_key'])->toBe('test_value');
|
||||
expect($storedData['user_data'])->toBe(['id' => 456, 'name' => 'Test User']);
|
||||
|
||||
// Prüfe ob Cookie gesetzt wurde
|
||||
$headers = $responseWithCookie->headers;
|
||||
expect($headers)->toHaveKey('Set-Cookie');
|
||||
expect($headers['Set-Cookie'])->toContain('ms_context=' . $session->id->toString());
|
||||
});
|
||||
|
||||
test('session data persists across requests', function () {
|
||||
// Erste Request: Session erstellen und Daten speichern
|
||||
$session1 = $this->sessionManager->createNewSession();
|
||||
$session1->set('persistent_data', 'should_persist');
|
||||
|
||||
$response = new Response(200, [], '');
|
||||
$responseWithCookie = $this->sessionManager->saveSession($session1, $response);
|
||||
|
||||
// Simulate Cookie aus Response extrahieren
|
||||
$sessionId = $session1->id->toString();
|
||||
|
||||
// Zweite Request: Session mit Cookie laden
|
||||
$cookies = new Cookies([
|
||||
new Cookie('ms_context', $sessionId),
|
||||
]);
|
||||
|
||||
$request2 = new Request(
|
||||
method: 'GET',
|
||||
uri: '/',
|
||||
cookies: $cookies,
|
||||
headers: [],
|
||||
body: ''
|
||||
);
|
||||
|
||||
$session2 = $this->sessionManager->getOrCreateSession($request2);
|
||||
|
||||
expect($session2->get('persistent_data'))->toBe('should_persist');
|
||||
expect($session2->id->toString())->toBe($sessionId);
|
||||
});
|
||||
|
||||
test('complex data structures persist correctly', function () {
|
||||
$session = $this->sessionManager->createNewSession();
|
||||
$complexData = [
|
||||
'user' => [
|
||||
'id' => 789,
|
||||
'profile' => [
|
||||
'name' => 'Complex User',
|
||||
'preferences' => [
|
||||
'theme' => 'dark',
|
||||
'notifications' => true,
|
||||
'languages' => ['en', 'de', 'fr'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'cart' => [
|
||||
'items' => [
|
||||
['id' => 1, 'quantity' => 2, 'price' => 19.99],
|
||||
['id' => 2, 'quantity' => 1, 'price' => 39.99],
|
||||
],
|
||||
'total' => 79.97,
|
||||
],
|
||||
];
|
||||
|
||||
$session->set('complex_data', $complexData);
|
||||
|
||||
$response = new Response(200, [], '');
|
||||
$this->sessionManager->saveSession($session, $response);
|
||||
|
||||
// Session erneut laden
|
||||
$cookies = new Cookies([
|
||||
new Cookie('ms_context', $session->id->toString()),
|
||||
]);
|
||||
|
||||
$request = new Request(
|
||||
method: 'GET',
|
||||
uri: '/',
|
||||
cookies: $cookies,
|
||||
headers: [],
|
||||
body: ''
|
||||
);
|
||||
|
||||
$reloadedSession = $this->sessionManager->getOrCreateSession($request);
|
||||
|
||||
expect($reloadedSession->get('complex_data'))->toBe($complexData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SessionManager Session Regeneration', function () {
|
||||
test('regenerates session correctly', function () {
|
||||
$originalSession = $this->sessionManager->createNewSession();
|
||||
$originalSession->set('user_id', 123);
|
||||
$originalSession->set('data_to_preserve', 'important_data');
|
||||
|
||||
// Session speichern
|
||||
$response = new Response(200, [], '');
|
||||
$this->sessionManager->saveSession($originalSession, $response);
|
||||
|
||||
$originalId = $originalSession->id->toString();
|
||||
|
||||
// Session regenerieren
|
||||
$newSession = $this->sessionManager->regenerateSession($originalSession);
|
||||
|
||||
// Neue Session sollte andere ID haben
|
||||
expect($newSession->id->toString())->not->toBe($originalId);
|
||||
|
||||
// Aber die gleichen Daten
|
||||
expect($newSession->get('user_id'))->toBe(123);
|
||||
expect($newSession->get('data_to_preserve'))->toBe('important_data');
|
||||
|
||||
// Alte Session sollte nicht mehr im Storage existieren
|
||||
$oldData = $this->storage->read($originalSession->id);
|
||||
expect($oldData)->toBe([]);
|
||||
|
||||
// Neue Session sollte im Storage existieren
|
||||
$newData = $this->storage->read($newSession->id);
|
||||
expect($newData['user_id'])->toBe(123);
|
||||
});
|
||||
|
||||
test('regeneration marks session as regenerated', function () {
|
||||
$originalSession = $this->sessionManager->createNewSession();
|
||||
$originalSession->fromArray([]); // Initialize components
|
||||
|
||||
$newSession = $this->sessionManager->regenerateSession($originalSession);
|
||||
|
||||
// Security manager sollte die Regeneration registriert haben
|
||||
expect($newSession->security)->toBeInstanceOf(\App\Framework\Http\Session\SecurityManager::class);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SessionManager Session Destruction', function () {
|
||||
test('destroys session completely', function () {
|
||||
$session = $this->sessionManager->createNewSession();
|
||||
$session->set('data_to_destroy', 'will_be_gone');
|
||||
|
||||
// Session speichern
|
||||
$response = new Response(200, [], '');
|
||||
$this->sessionManager->saveSession($session, $response);
|
||||
|
||||
// Prüfen dass Daten existieren
|
||||
$storedData = $this->storage->read($session->id);
|
||||
expect($storedData['data_to_destroy'])->toBe('will_be_gone');
|
||||
|
||||
// Session zerstören
|
||||
$destroyResponse = $this->sessionManager->destroySession($session, $response);
|
||||
|
||||
// Daten sollten gelöscht sein
|
||||
$deletedData = $this->storage->read($session->id);
|
||||
expect($deletedData)->toBe([]);
|
||||
|
||||
// Cookie sollte zum Löschen gesetzt werden (expires in der Vergangenheit)
|
||||
$headers = $destroyResponse->headers;
|
||||
expect($headers)->toHaveKey('Set-Cookie');
|
||||
$cookieHeader = $headers['Set-Cookie'];
|
||||
expect($cookieHeader)->toContain('ms_context=');
|
||||
expect($cookieHeader)->toContain('expires='); // Should have expiry in past
|
||||
});
|
||||
});
|
||||
|
||||
describe('SessionManager Configuration', function () {
|
||||
test('returns correct cookie name', function () {
|
||||
expect($this->sessionManager->getCookieName())->toBe('ms_context');
|
||||
});
|
||||
|
||||
test('returns cookie configuration', function () {
|
||||
$config = $this->sessionManager->getCookieConfig();
|
||||
expect($config)->toBeInstanceOf(SessionCookieConfig::class);
|
||||
});
|
||||
|
||||
test('respects custom cookie configuration', function () {
|
||||
$customConfig = new SessionCookieConfig(
|
||||
lifetime: 7200,
|
||||
path: '/custom',
|
||||
domain: 'example.com',
|
||||
secure: true,
|
||||
httpOnly: true
|
||||
);
|
||||
|
||||
$customManager = new SessionManager(
|
||||
$this->idGenerator,
|
||||
$this->responseManipulator,
|
||||
$this->clock,
|
||||
$this->csrfTokenGenerator,
|
||||
$this->storage,
|
||||
$customConfig
|
||||
);
|
||||
|
||||
$session = $customManager->createNewSession();
|
||||
$response = new Response(200, [], '');
|
||||
$responseWithCookie = $customManager->saveSession($session, $response);
|
||||
|
||||
$cookieHeader = $responseWithCookie->headers['Set-Cookie'];
|
||||
expect($cookieHeader)->toContain('path=/custom');
|
||||
expect($cookieHeader)->toContain('domain=example.com');
|
||||
expect($cookieHeader)->toContain('secure');
|
||||
expect($cookieHeader)->toContain('httponly');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SessionManager Error Handling', function () {
|
||||
test('handles invalid session ID gracefully', function () {
|
||||
$cookies = new Cookies([
|
||||
new Cookie('ms_context', 'invalid-session-id-format'),
|
||||
]);
|
||||
|
||||
$request = new Request(
|
||||
method: 'GET',
|
||||
uri: '/',
|
||||
cookies: $cookies,
|
||||
headers: [],
|
||||
body: ''
|
||||
);
|
||||
|
||||
// Sollte eine neue Session erstellen anstatt zu crashen
|
||||
$session = $this->sessionManager->getOrCreateSession($request);
|
||||
expect($session)->toBeInstanceOf(Session::class);
|
||||
expect($session->isStarted())->toBeTrue();
|
||||
});
|
||||
|
||||
test('handles storage read errors gracefully', function () {
|
||||
// Mock eines Storage der beim Lesen fehlschlägt
|
||||
$failingStorage = new class () implements \App\Framework\Http\Session\SessionStorage {
|
||||
public function read(SessionId $id): array
|
||||
{
|
||||
throw new Exception('Storage read failed');
|
||||
}
|
||||
|
||||
public function write(SessionId $id, array $data): void
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
public function remove(SessionId $id): void
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
public function migrate(SessionId $fromId, SessionId $toId): void
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
};
|
||||
|
||||
$failingManager = new SessionManager(
|
||||
$this->idGenerator,
|
||||
$this->responseManipulator,
|
||||
$this->clock,
|
||||
$this->csrfTokenGenerator,
|
||||
$failingStorage,
|
||||
$this->cookieConfig
|
||||
);
|
||||
|
||||
$sessionId = SessionId::fromString('existingsessionid1234567890abcdef');
|
||||
$cookies = new Cookies([
|
||||
new Cookie('ms_context', $sessionId->toString()),
|
||||
]);
|
||||
|
||||
$request = new Request(
|
||||
method: 'GET',
|
||||
uri: '/',
|
||||
cookies: $cookies,
|
||||
headers: [],
|
||||
body: ''
|
||||
);
|
||||
|
||||
// Sollte eine neue Session erstellen wenn das Laden fehlschlägt
|
||||
expect(fn () => $failingManager->getOrCreateSession($request))
|
||||
->toThrow(Exception::class, 'Storage read failed');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user