diff --git a/composer.json b/composer.json index 1c820f2..9770009 100644 --- a/composer.json +++ b/composer.json @@ -21,13 +21,14 @@ "require-dev": { "phpunit/phpunit": "^9.6.21", "doctrine/cache": "^1.10", - "league/flysystem": "^2.5", + "league/flysystem": "^2.5 || ^3.0", "psr/cache": "^1.0", "cache/array-adapter": "^0.4 || ^0.5 || ^1.0", "illuminate/cache": "^5.0", "cache/simple-cache-bridge": "^0.1 || ^1.0", "symfony/phpunit-bridge": "^7.1.4", - "symfony/cache": "^4.4 || ^5.0" + "symfony/cache": "^4.4 || ^5.0", + "phpspec/prophecy-phpunit": "^2.0" }, "autoload": { "psr-4": { diff --git a/src/Storage/FlysystemStorage.php b/src/Storage/FlysystemStorage.php index b12a2c5..418da01 100644 --- a/src/Storage/FlysystemStorage.php +++ b/src/Storage/FlysystemStorage.php @@ -26,17 +26,22 @@ public function __construct(FilesystemAdapter $adapter) public function fetch($key) { if ($this->filesystem->fileExists($key)) { - // The file exist, read it! - $data = @unserialize( - $this->filesystem->read($key) - ); + try { + // The file exist, read it! + $data = @unserialize( + $this->filesystem->read($key) + ); - if ($data instanceof CacheEntry) { - return $data; + if ($data instanceof CacheEntry) { + return $data; + } + } catch (FilesystemException $e) { + // In case of error, act as if the file didn't exist + return null; } } - return; + return null; } /** @@ -61,7 +66,7 @@ public function delete($key) $this->filesystem->delete($key); return true; } catch (FilesystemException $ex) { - return true; + return false; } } } diff --git a/tests/Storage/FlysystemStorageTest.php b/tests/Storage/FlysystemStorageTest.php new file mode 100644 index 0000000..f8cce21 --- /dev/null +++ b/tests/Storage/FlysystemStorageTest.php @@ -0,0 +1,95 @@ +prophesize(Filesystem::class); + $filesystem->fileExists('testKey')->willReturn(true)->shouldBeCalled(); + $filesystem->read('testKey')->willThrow(new UnableToReadFile('Mocked read failure'))->shouldBeCalled(); + + $storage = new FlysystemStorage($this->prophesize(FilesystemAdapter::class)->reveal()); + // Inject the mocked Filesystem object + $reflection = new \ReflectionClass($storage); + $property = $reflection->getProperty('filesystem'); + $property->setAccessible(true); + $property->setValue($storage, $filesystem->reveal()); + + $this->assertNull($storage->fetch('testKey')); + } + + public function testSaveReturnsFalseOnFilesystemException() + { + // Create a real CacheEntry with a dummy request and response to ensure it can be serialized + $dummyRequest = new \GuzzleHttp\Psr7\Request('GET', 'test-uri'); + $dummyResponse = new \GuzzleHttp\Psr7\Response(200, [], 'test body'); + $cacheEntry = new CacheEntry($dummyRequest, $dummyResponse, new \DateTime('+1 hour')); + + $filesystem = $this->prophesize(Filesystem::class); + // Prophesize with the actual serialized object + $filesystem->write('testKey', \serialize($cacheEntry)) + ->willThrow(new UnableToWriteFile('Mocked write failure')) + ->shouldBeCalled(); + + $storage = new FlysystemStorage($this->prophesize(FilesystemAdapter::class)->reveal()); + // Inject the mocked Filesystem object + $reflection = new \ReflectionClass($storage); + $property = $reflection->getProperty('filesystem'); + $property->setAccessible(true); + $property->setValue($storage, $filesystem->reveal()); + + $this->assertFalse($storage->save('testKey', $cacheEntry)); + } + + public function testDeleteReturnsFalseOnFilesystemException() + { + $filesystem = $this->prophesize(Filesystem::class); + $filesystem->delete('testKey')->willThrow(new UnableToDeleteFile('Mocked delete failure'))->shouldBeCalled(); + + $storage = new FlysystemStorage($this->prophesize(FilesystemAdapter::class)->reveal()); + // Inject the mocked Filesystem object + $reflection = new \ReflectionClass($storage); + $property = $reflection->getProperty('filesystem'); + $property->setAccessible(true); + $property->setValue($storage, $filesystem->reveal()); + + $this->assertFalse($storage->delete('testKey')); + } + + // It might be good to also test the constructor if FilesystemAdapter is directly used + // or if the Filesystem object construction within FlysystemStorage needs specific adapter behavior. + // For now, focusing on the core fetch/save/delete exception handling. + + // Test for fetch when file does not exist - should return null without exception + public function testFetchReturnsNullWhenFileDoesNotExist() + { + $filesystem = $this->prophesize(Filesystem::class); + $filesystem->fileExists('nonExistentKey')->willReturn(false)->shouldBeCalled(); + // read should not be called + $filesystem->read('nonExistentKey')->shouldNotBeCalled(); + + + $storage = new FlysystemStorage($this->prophesize(FilesystemAdapter::class)->reveal()); + $reflection = new \ReflectionClass($storage); + $property = $reflection->getProperty('filesystem'); + $property->setAccessible(true); + $property->setValue($storage, $filesystem->reveal()); + + $this->assertNull($storage->fetch('nonExistentKey')); + } +}