diff --git a/.github/workflows/phpunit.yaml b/.github/workflows/phpunit.yaml new file mode 100644 index 00000000..1d850685 --- /dev/null +++ b/.github/workflows/phpunit.yaml @@ -0,0 +1,36 @@ +name: PHPUnit Tests +on: [push] +jobs: + run-phpstan: + name: Run PHPUnit + runs-on: ubuntu-latest + steps: + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: dom, json, mbstring, psr, xdebug + tools: composer:v2 + + - name: Check out code + uses: actions/checkout@v2 + + - name: Get Composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Connect downloaded dependencies with a cache in GitHub + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + # Note: Normally, we'd use the composer.lock to generate a hash, + # but the lock file is currently not versioned. + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --prefer-dist + + - name: Run PHPUnit + run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d678ae60 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# PHP Composer files +composer.lock +vendor/ + +# PHPUnit cache +.phpunit.cache/ diff --git a/Clockwork/Storage/FileStorage.php b/Clockwork/Storage/FileStorage.php index d0b9ba13..f2098e13 100644 --- a/Clockwork/Storage/FileStorage.php +++ b/Clockwork/Storage/FileStorage.php @@ -25,10 +25,10 @@ class FileStorage extends Storage protected $indexHandle; // Return new storage, takes path where to store files as argument - public function __construct($path, $pathPermissions = 0700, $expiration = null, $compress = false) + public function __construct($path, $pathPermissions = null, $expiration = null, $compress = false) { $this->path = $path; - $this->pathPermissions = $pathPermissions; + $this->pathPermissions = ($pathPermissions === null) ? 0700 : $pathPermissions; $this->expiration = $expiration === null ? 60 * 24 * 7 : $expiration; $this->compress = $compress; } diff --git a/composer.json b/composer.json index 80eec482..25a645b4 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,8 @@ }, "autoload": { "psr-4": { - "Clockwork\\": "Clockwork/" + "Clockwork\\": "Clockwork/", + "Tests\\Clockwork\\": "tests/" } }, "extra": { @@ -37,5 +38,8 @@ "Clockwork": "Clockwork\\Support\\Laravel\\Facade" } } + }, + "require-dev": { + "phpunit/phpunit": "^10.0" } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..0071aabc --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,29 @@ + + + + + tests + + + + + ./src/ + + + diff --git a/tests/Storage/FileStorageTest.php b/tests/Storage/FileStorageTest.php new file mode 100644 index 00000000..1e9265f3 --- /dev/null +++ b/tests/Storage/FileStorageTest.php @@ -0,0 +1,199 @@ +storageDir = static::STORAGE_ROOT_DIR . '/' . uniqid(); + + $this->storage = new FileStorage( + $this->storageDir, + static::STORAGE_PERMISSIONS, + static::STORAGE_EXPIRATION + ); + } + + public function tearDown(): void + { + if (file_exists($this->storageDir)) { + static::rmdirRecursive($this->storageDir); + } + + parent::tearDown(); + } + + public static function tearDownAfterClass(): void + { + if (file_exists(static::STORAGE_ROOT_DIR)) { + static::rmdirRecursive(static::STORAGE_ROOT_DIR); + } + + parent::tearDownAfterClass(); + } + + private static function rmdirRecursive(string $dir): void + { + if (!is_dir($dir)) { + if (is_file($dir)) { + unlink($dir); + return; + } + return; + } + $iter = dir($dir); + while (false !== ($entry = $iter->read())) { + if (in_array($entry, ['..', '.'])) { + continue; + } + static::rmdirRecursive($dir . '/' . $entry); + } + rmdir($dir); + } + + public function testInterface(): void + { + static::assertInstanceOf(StorageInterface::class, $this->storage); + } + + public function testStore(): void + { + $request = new Request([ + 'id' => '12345', + 'version' => 1, + 'type' => 'request', + 'responseDuration' => -12345678.9, + 'updateToken' => 'abcd1234', + ]); + + // ensure the request doesn't exist yet + static::assertNull($this->storage->find($request->id)); + + // store request + $this->storage->store($request); + + // ensure it can be loaded by ID + static::assertRequestEquals( + $request, + $this->storage->find($request->id) + ); + + // update request + $request->addEmail('test', 'nobody@example.com'); + $this->storage->update($request); + + // ensure it can be loaded by ID + static::assertRequestEquals( + $request, + $this->storage->find($request->id) + ); + } + + public function testCleanup(): void + { + $now = microtime(true); + + $request1 = new Request([ + 'id' => '12345', + 'time' => $now - static::STORAGE_EXPIRATION * 60 - 1, + ]); + $request2 = new Request([ + 'id' => '67890', + 'time' => $now, + ]); + + // store requests + // Note: This randomly triggers a cleanup already, so it might be + // that request 1 is gone even before we trigger one ourselves. + $this->storage->store($request1); + $this->storage->store($request2); + + // trigger cleanup + $this->storage->cleanup(true); + + // ensure request 1 was purged + static::assertNull($this->storage->find($request1->id)); + // ensure request 2 was kept + static::assertNotNull($this->storage->find($request2->id)); + } + + public function testStore2(): void + { + $request1 = new Request([ + 'id' => '12345', + ]); + $request2 = new Request([ + 'id' => '67890', + ]); + + // store requests + $this->storage->store($request1); + $this->storage->store($request2); + + // ensure both can be loaded + $allRequests = $this->storage->all(); + static::assertContainsOnlyInstancesOf(Request::class, $allRequests); + static::assertCount(2, $allRequests); + + // ensure both can be loaded by ID + static::assertRequestEquals( + $request1, + $this->storage->find($request1->id) + ); + static::assertRequestEquals( + $request2, + $this->storage->find($request2->id) + ); + + // ensure the next after the first is the second + $next = $this->storage->next($request1->id); + static::assertContainsOnlyInstancesOf(Request::class, $next); + static::assertCount(1, $next); + static::assertRequestEquals( + $request2, + $next[0] + ); + + // ensure the previous before the second is the first + $previous = $this->storage->previous($request2->id); + static::assertContainsOnlyInstancesOf(Request::class, $previous); + static::assertCount(1, $previous); + static::assertRequestEquals( + $request1, + $previous[0] + ); + } + + /** + * compare requests + * + * This ensures that a result is actually a request instance and with + * the expected properties. + */ + private static function assertRequestEquals(Request $expected, $actual): void + { + static::assertInstanceOf(Request::class, $actual); + + $expectedData = array_filter($expected->toArray()); + $actualData = array_filter($actual->toArray()); + + static::assertEquals($expectedData, $actualData); + } +}