storage = new InMemorySessionStorage(); $this->sessionId = SessionId::fromString('testsessionid1234567890abcdefgh12'); }); test('reads empty array for non-existent session', function () { $data = $this->storage->read($this->sessionId); expect($data)->toBe([]); }); test('writes and reads session data correctly', function () { $testData = [ 'user_id' => 123, 'username' => 'testuser', 'preferences' => ['theme' => 'dark', 'language' => 'en'], ]; $this->storage->write($this->sessionId, $testData); $retrievedData = $this->storage->read($this->sessionId); expect($retrievedData)->toBe($testData); }); test('removes session data correctly', function () { $testData = ['key' => 'value']; $this->storage->write($this->sessionId, $testData); expect($this->storage->read($this->sessionId))->toBe($testData); $this->storage->remove($this->sessionId); expect($this->storage->read($this->sessionId))->toBe([]); }); test('migrates session data correctly', function () { $oldId = SessionId::fromString('oldsessionid1234567890abcdefghij1'); $newId = SessionId::fromString('newsessionid1234567890abcdefghij1'); $testData = ['migration_test' => 'data']; $this->storage->write($oldId, $testData); $this->storage->migrate($oldId, $newId); expect($this->storage->read($oldId))->toBe([]); expect($this->storage->read($newId))->toBe($testData); }); test('handles complex data structures', function () { $complexData = [ 'nested' => [ 'deep' => [ 'array' => [1, 2, 3], 'object' => (object)['property' => 'value'], ], ], 'null_value' => null, 'boolean_true' => true, 'boolean_false' => false, 'empty_string' => '', 'zero' => 0, ]; $this->storage->write($this->sessionId, $complexData); $retrieved = $this->storage->read($this->sessionId); expect($retrieved)->toBe($complexData); }); test('multiple sessions work independently', function () { $session1Id = SessionId::fromString('session1id1234567890abcdefghijk1'); $session2Id = SessionId::fromString('session2id1234567890abcdefghijk2'); $data1 = ['session' => '1', 'data' => 'first']; $data2 = ['session' => '2', 'data' => 'second']; $this->storage->write($session1Id, $data1); $this->storage->write($session2Id, $data2); expect($this->storage->read($session1Id))->toBe($data1); expect($this->storage->read($session2Id))->toBe($data2); $this->storage->remove($session1Id); expect($this->storage->read($session1Id))->toBe([]); expect($this->storage->read($session2Id))->toBe($data2); }); }); describe('FileSessionStorage', function () { beforeEach(function () { $this->tempDir = sys_get_temp_dir() . '/session_test_' . uniqid(); $this->storage = new FileSessionStorage($this->tempDir); $this->sessionId = SessionId::fromString('testfilesessionid1234567890abcde'); }); afterEach(function () { // Cleanup: Remove test directory and files if (is_dir($this->tempDir)) { $files = glob($this->tempDir . '/*'); foreach ($files as $file) { if (is_file($file)) { unlink($file); } } rmdir($this->tempDir); } }); test('creates storage directory if it does not exist', function () { expect(is_dir($this->tempDir))->toBeTrue(); }); test('reads empty array for non-existent session file', function () { $data = $this->storage->read($this->sessionId); expect($data)->toBe([]); }); test('writes and reads session data to/from file correctly', function () { $testData = [ 'user_id' => 456, 'username' => 'fileuser', 'complex' => [ 'nested' => ['deep' => 'value'], 'array' => [1, 2, 3, 4], ], ]; $this->storage->write($this->sessionId, $testData); $retrievedData = $this->storage->read($this->sessionId); expect($retrievedData)->toBe($testData); }); test('file exists after writing', function () { $testData = ['file_test' => 'exists']; $this->storage->write($this->sessionId, $testData); $expectedFile = $this->tempDir . '/sess_' . $this->sessionId->toString(); expect(file_exists($expectedFile))->toBeTrue(); }); test('removes session file correctly', function () { $testData = ['to_be_removed' => 'data']; $this->storage->write($this->sessionId, $testData); $expectedFile = $this->tempDir . '/sess_' . $this->sessionId->toString(); expect(file_exists($expectedFile))->toBeTrue(); $this->storage->remove($this->sessionId); expect(file_exists($expectedFile))->toBeFalse(); expect($this->storage->read($this->sessionId))->toBe([]); }); test('migrates session file correctly', function () { $oldId = SessionId::fromString('oldfilesessionid1234567890abcdef'); $newId = SessionId::fromString('newfilesessionid1234567890abcdef'); $testData = ['migration_file_test' => 'data']; $this->storage->write($oldId, $testData); $oldFile = $this->tempDir . '/sess_' . $oldId->toString(); $newFile = $this->tempDir . '/sess_' . $newId->toString(); expect(file_exists($oldFile))->toBeTrue(); expect(file_exists($newFile))->toBeFalse(); $this->storage->migrate($oldId, $newId); expect(file_exists($oldFile))->toBeFalse(); expect(file_exists($newFile))->toBeTrue(); expect($this->storage->read($newId))->toBe($testData); }); test('handles JSON encoding/decoding correctly', function () { $testData = [ 'string' => 'test string with üñíçødé', 'integer' => 42, 'float' => 3.14159, 'boolean_true' => true, 'boolean_false' => false, 'null' => null, 'array' => ['a', 'b', 'c'], 'object' => ['key' => 'value'], ]; $this->storage->write($this->sessionId, $testData); $retrieved = $this->storage->read($this->sessionId); expect($retrieved)->toBe($testData); }); test('handles corrupted JSON file gracefully', function () { // Schreibe invalides JSON in die Session-Datei $sessionFile = $this->tempDir . '/sess_' . $this->sessionId->toString(); file_put_contents($sessionFile, 'invalid json content {'); $data = $this->storage->read($this->sessionId); expect($data)->toBe([]); // Sollte leeres Array zurückgeben }); test('handles file system errors gracefully', function () { // Versuche in nicht-existierendes Verzeichnis zu schreiben (das Framework sollte das abfangen) expect(fn () => new FileSessionStorage('/root/nonexistent/directory/path')) ->toThrow(RuntimeException::class); }); test('garbage collection removes old files', function () { // Erstelle mehrere Session-Dateien mit verschiedenen Timestamps $oldSessionId = SessionId::fromString('oldsessionid1234567890abcdefghij1'); $newSessionId = SessionId::fromString('newsessionid1234567890abcdefghij2'); $this->storage->write($oldSessionId, ['old' => 'data']); $this->storage->write($newSessionId, ['new' => 'data']); // Da wir die neue FileSessionStorage verwenden, können wir nicht direkt touch() verwenden // Stattdessen testen wir nur dass GC nicht crashed und beide Files noch existieren // (da sie frisch erstellt wurden) $this->storage->gc(3600); // Beide Sessions sollten noch existieren da sie gerade erstellt wurden expect($this->storage->read($oldSessionId))->toBe(['old' => 'data']); expect($this->storage->read($newSessionId))->toBe(['new' => 'data']); }); }); describe('SessionStorage Interface Compliance', function () { /** * Teste dass alle Storage-Implementierungen das gleiche Interface-Verhalten haben */ $storageProviders = [ 'InMemorySessionStorage' => fn () => new InMemorySessionStorage(), 'FileSessionStorage' => fn () => new FileSessionStorage(sys_get_temp_dir() . '/pest_session_test_' . uniqid()), ]; foreach ($storageProviders as $storageName => $storageFactory) { describe($storageName . ' Interface Compliance', function () use ($storageName, $storageFactory) { beforeEach(function () use ($storageFactory) { $this->storage = $storageFactory(); $this->sessionId = SessionId::fromString('interfacetestsessionid1234567890'); }); afterEach(function () use ($storageName) { // Cleanup für FileStorage if ($storageName === 'FileSessionStorage' && $this->storage instanceof FileSessionStorage) { $basePath = $this->storage->getBasePath(); $pathString = $basePath->toString(); if (is_dir($pathString)) { $files = glob($pathString . '/*'); foreach ($files as $file) { if (is_file($file)) { unlink($file); } } rmdir($pathString); } } }); test('implements SessionStorage interface', function () { expect($this->storage)->toBeInstanceOf(SessionStorage::class); }); test('read() returns array', function () { $result = $this->storage->read($this->sessionId); expect($result)->toBeArray(); }); test('write() and read() cycle works', function () { $testData = ['interface_test' => 'value', 'number' => 42]; $this->storage->write($this->sessionId, $testData); $result = $this->storage->read($this->sessionId); expect($result)->toBe($testData); }); test('remove() clears session data', function () { $this->storage->write($this->sessionId, ['to_remove' => 'data']); $this->storage->remove($this->sessionId); expect($this->storage->read($this->sessionId))->toBe([]); }); test('migrate() transfers data correctly', function () { $oldId = SessionId::fromString('oldinterfacetest1234567890abcdefg'); $newId = SessionId::fromString('newinterfacetest1234567890abcdefg'); $testData = ['migrate_interface_test' => 'data']; $this->storage->write($oldId, $testData); $this->storage->migrate($oldId, $newId); expect($this->storage->read($oldId))->toBe([]); expect($this->storage->read($newId))->toBe($testData); }); test('handles empty data correctly', function () { $this->storage->write($this->sessionId, []); expect($this->storage->read($this->sessionId))->toBe([]); }); test('handles null values in data', function () { $testData = ['null_value' => null, 'string' => 'value']; $this->storage->write($this->sessionId, $testData); $result = $this->storage->read($this->sessionId); expect($result)->toBe($testData); expect($result['null_value'])->toBeNull(); }); }); } });