diff --git a/src/Laravel/Storage/FallbackSession.php b/src/Laravel/Storage/FallbackSession.php new file mode 100644 index 00000000..38407112 --- /dev/null +++ b/src/Laravel/Storage/FallbackSession.php @@ -0,0 +1,26 @@ + */ + private static array $storage = []; + + public function get(string $name, mixed $default = null): mixed + { + return \array_key_exists($name, self::$storage) ? self::$storage[$name] : $default; + } + + public function set(string $name, mixed $value): void + { + self::$storage[$name] = $value; + } + + public static function reset(): void + { + self::$storage = []; + } +} diff --git a/src/Laravel/Storage/FallbackSessionInterface.php b/src/Laravel/Storage/FallbackSessionInterface.php new file mode 100644 index 00000000..d38be797 --- /dev/null +++ b/src/Laravel/Storage/FallbackSessionInterface.php @@ -0,0 +1,12 @@ +fallbackSession = $fallbackSession ?? new FallbackSession(); } public function get(): array { + $session = $this->getSession(); + /** @var Envelope[] $envelopes */ - $envelopes = $this->session->get(self::ENVELOPES_NAMESPACE, []); + $envelopes = $session->get(self::ENVELOPES_NAMESPACE, []); return $envelopes; } public function set(array $envelopes): void { - $this->session->put(self::ENVELOPES_NAMESPACE, $envelopes); + $session = $this->getSession(); + + $session->set(self::ENVELOPES_NAMESPACE, $envelopes); + } + + private function getSession(): Session|FallbackSessionInterface + { + $session = $this->sessionManager->driver(); + + if ($session->isStarted()) { + return $session; + } + + return $this->fallbackSession; } } diff --git a/tests/Laravel/Storage/FallbackSessionTest.php b/tests/Laravel/Storage/FallbackSessionTest.php new file mode 100644 index 00000000..2eabed54 --- /dev/null +++ b/tests/Laravel/Storage/FallbackSessionTest.php @@ -0,0 +1,131 @@ +session = new FallbackSession(); + } + + protected function tearDown(): void + { + FallbackSession::reset(); + parent::tearDown(); + } + + public function testImplementsInterface(): void + { + $this->assertInstanceOf(FallbackSessionInterface::class, $this->session); + } + + public function testGetReturnsDefaultWhenKeyNotFound(): void + { + $this->assertNull($this->session->get('nonexistent')); + $this->assertSame('default', $this->session->get('nonexistent', 'default')); + $this->assertSame([], $this->session->get('nonexistent', [])); + } + + public function testSetAndGet(): void + { + $this->session->set('key', 'value'); + + $this->assertSame('value', $this->session->get('key')); + } + + public function testSetOverwritesExistingValue(): void + { + $this->session->set('key', 'value1'); + $this->session->set('key', 'value2'); + + $this->assertSame('value2', $this->session->get('key')); + } + + public function testStoresComplexData(): void + { + $data = [ + 'nested' => [ + 'array' => ['with', 'values'], + ], + 'number' => 42, + 'null' => null, + ]; + + $this->session->set('complex', $data); + + $this->assertSame($data, $this->session->get('complex')); + } + + public function testStaticStoragePersistsAcrossInstances(): void + { + $session1 = new FallbackSession(); + $session1->set('shared', 'data'); + + $session2 = new FallbackSession(); + + $this->assertSame('data', $session2->get('shared')); + } + + public function testResetClearsAllData(): void + { + $this->session->set('key1', 'value1'); + $this->session->set('key2', 'value2'); + + FallbackSession::reset(); + + $this->assertNull($this->session->get('key1')); + $this->assertNull($this->session->get('key2')); + } + + public function testHandlesNullValue(): void + { + $this->session->set('null_key', null); + + $this->assertNull($this->session->get('null_key')); + $this->assertNull($this->session->get('null_key', 'default')); + } + + public function testHandlesEmptyString(): void + { + $this->session->set('empty', ''); + + $this->assertSame('', $this->session->get('empty')); + $this->assertSame('', $this->session->get('empty', 'default')); + } + + public function testHandlesFalseValue(): void + { + $this->session->set('false', false); + + $this->assertFalse($this->session->get('false')); + $this->assertFalse($this->session->get('false', 'default')); + } + + public function testHandlesZeroValue(): void + { + $this->session->set('zero', 0); + + $this->assertSame(0, $this->session->get('zero')); + $this->assertSame(0, $this->session->get('zero', 'default')); + } + + public function testKeyExistsWithNullValue(): void + { + $this->session->set('exists_null', null); + + $this->assertNull($this->session->get('exists_null')); + $this->assertNull($this->session->get('exists_null', 'should_not_return')); + } +} diff --git a/tests/Laravel/Storage/SessionBagTest.php b/tests/Laravel/Storage/SessionBagTest.php index ee218043..5d0d92e0 100644 --- a/tests/Laravel/Storage/SessionBagTest.php +++ b/tests/Laravel/Storage/SessionBagTest.php @@ -4,12 +4,15 @@ declare(strict_types=1); namespace Flasher\Tests\Laravel\Storage; +use Flasher\Laravel\Storage\FallbackSession; +use Flasher\Laravel\Storage\FallbackSessionInterface; use Flasher\Laravel\Storage\SessionBag; use Flasher\Prime\Notification\Envelope; use Flasher\Prime\Notification\Notification; use Flasher\Prime\Stamp\IdStamp; use Flasher\Tests\Laravel\TestCase; use Illuminate\Session\SessionManager; +use Illuminate\Session\Store; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Mockery\MockInterface; @@ -25,10 +28,17 @@ final class SessionBagTest extends TestCase { parent::setUp(); + FallbackSession::reset(); $this->sessionManagerMock = \Mockery::mock(SessionManager::class); $this->sessionBag = new SessionBag($this->sessionManagerMock); } + protected function tearDown(): void + { + FallbackSession::reset(); + parent::tearDown(); + } + public function testGet(): void { $envelopes = [ @@ -36,9 +46,11 @@ final class SessionBagTest extends TestCase new Envelope(new Notification(), new IdStamp('2222')), ]; - $this->sessionManagerMock->expects() - ->get(SessionBag::ENVELOPES_NAMESPACE, []) - ->andReturns($envelopes); + $sessionMock = \Mockery::mock(Store::class); + $sessionMock->expects()->isStarted()->andReturns(true); + $sessionMock->expects()->get(SessionBag::ENVELOPES_NAMESPACE, [])->andReturns($envelopes); + + $this->sessionManagerMock->expects()->driver()->andReturns($sessionMock); $this->assertEquals($envelopes, $this->sessionBag->get()); } @@ -50,15 +62,134 @@ final class SessionBagTest extends TestCase new Envelope(new Notification(), new IdStamp('2222')), ]; - $this->sessionManagerMock->allows() - ->get(SessionBag::ENVELOPES_NAMESPACE, []) - ->andReturns($envelopes); + $sessionMock = \Mockery::mock(Store::class); + $sessionMock->allows()->isStarted()->andReturns(true); + $sessionMock->allows()->get(SessionBag::ENVELOPES_NAMESPACE, [])->andReturns($envelopes); + $sessionMock->expects()->set(SessionBag::ENVELOPES_NAMESPACE, $envelopes); - $this->sessionManagerMock->expects() - ->put(SessionBag::ENVELOPES_NAMESPACE, $envelopes); + $this->sessionManagerMock->allows()->driver()->andReturns($sessionMock); $this->sessionBag->set($envelopes); $this->assertSame($envelopes, $this->sessionBag->get()); } + + public function testUsesFallbackSessionWhenSessionNotStarted(): void + { + $envelopes = [ + new Envelope(new Notification(), new IdStamp('fallback-1')), + ]; + + $sessionMock = \Mockery::mock(Store::class); + $sessionMock->allows()->isStarted()->andReturns(false); + + $this->sessionManagerMock->allows()->driver()->andReturns($sessionMock); + + $this->sessionBag->set($envelopes); + + $this->assertSame($envelopes, $this->sessionBag->get()); + } + + public function testCustomFallbackSession(): void + { + $customFallback = new class() implements FallbackSessionInterface { + private array $data = []; + + public function get(string $name, mixed $default = null): mixed + { + return $this->data[$name] ?? $default; + } + + public function set(string $name, mixed $value): void + { + $this->data[$name] = $value; + } + }; + + $sessionMock = \Mockery::mock(Store::class); + $sessionMock->allows()->isStarted()->andReturns(false); + + $this->sessionManagerMock->allows()->driver()->andReturns($sessionMock); + + $sessionBag = new SessionBag($this->sessionManagerMock, $customFallback); + + $envelopes = [ + new Envelope(new Notification(), new IdStamp('custom-fallback')), + ]; + + $sessionBag->set($envelopes); + + $this->assertSame($envelopes, $sessionBag->get()); + } + + public function testFallbackSessionIsStatic(): void + { + $envelopes = [ + new Envelope(new Notification(), new IdStamp('static-test')), + ]; + + $sessionMock = \Mockery::mock(Store::class); + $sessionMock->allows()->isStarted()->andReturns(false); + + $this->sessionManagerMock->allows()->driver()->andReturns($sessionMock); + + $this->sessionBag->set($envelopes); + + $sessionBag2 = new SessionBag($this->sessionManagerMock); + + $this->assertSame($envelopes, $sessionBag2->get()); + } + + public function testFallbackSessionResetClearsData(): void + { + $envelopes = [ + new Envelope(new Notification(), new IdStamp('reset-test')), + ]; + + $sessionMock = \Mockery::mock(Store::class); + $sessionMock->allows()->isStarted()->andReturns(false); + + $this->sessionManagerMock->allows()->driver()->andReturns($sessionMock); + + $this->sessionBag->set($envelopes); + $this->assertSame($envelopes, $this->sessionBag->get()); + + FallbackSession::reset(); + + $this->assertSame([], $this->sessionBag->get()); + } + + public function testEmptyEnvelopesReturnsEmptyArray(): void + { + $sessionMock = \Mockery::mock(Store::class); + $sessionMock->expects()->isStarted()->andReturns(true); + $sessionMock->expects()->get(SessionBag::ENVELOPES_NAMESPACE, [])->andReturns([]); + + $this->sessionManagerMock->expects()->driver()->andReturns($sessionMock); + + $this->assertSame([], $this->sessionBag->get()); + } + + public function testOverwritesExistingEnvelopes(): void + { + $envelopes1 = [ + new Envelope(new Notification(), new IdStamp('first')), + ]; + + $envelopes2 = [ + new Envelope(new Notification(), new IdStamp('second')), + ]; + + $sessionMock = \Mockery::mock(Store::class); + $sessionMock->allows()->isStarted()->andReturns(true); + $sessionMock->allows()->get(SessionBag::ENVELOPES_NAMESPACE, [])->andReturns($envelopes2); + $sessionMock->allows()->set(SessionBag::ENVELOPES_NAMESPACE, \Mockery::any())->twice(); + + $this->sessionManagerMock->allows()->driver()->andReturns($sessionMock); + + $this->sessionBag->set($envelopes1); + $this->sessionBag->set($envelopes2); + + $this->assertSame($envelopes2, $this->sessionBag->get()); + } }