From 952d8bb87cc4f1ae8feae9764c4fc948f990453b Mon Sep 17 00:00:00 2001 From: Shift Date: Sat, 21 Feb 2026 03:03:07 +0000 Subject: [PATCH 1/6] Update GitHub Actions for Laravel 13 --- .github/workflows/run-tests.yml | 7 +++++-- .github/workflows/static-analysis.yml | 10 ++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a4b27de..5d26717 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,14 +1,17 @@ name: Tests -on: [push, pull_request] +on: + - push + - pull_request jobs: test: runs-on: ubuntu-latest + strategy: fail-fast: true matrix: - php: [8.2, 8.1] + php: [8.1, 8.2, '8.3', '8.4', '8.5'] stability: [lowest, highest] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} deps diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index f0a412b..31fba88 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -1,16 +1,18 @@ name: Static Analysis -on: [push, pull_request] +on: + - push + - pull_request jobs: phpstan: name: PHPStan (PHP ${{ matrix.php-version }}) + runs-on: ubuntu-latest strategy: matrix: - php-version: - - '8.2' + php-version: ['8.2', '8.3', '8.4', '8.5'] steps: - name: Checkout code @@ -27,4 +29,4 @@ jobs: uses: ramsey/composer-install@v3 - name: Run PHPStan - run: 'vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr' + run: vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr From dfb32f1c39d3bc55936b66a148edf87eca4abc8b Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Sat, 21 Feb 2026 10:47:51 +0100 Subject: [PATCH 2/6] Add Pest 4 support --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 750046c..699092a 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ }, "require-dev": { "laravel/pint": "^1.10.1", - "pestphp/pest": "^2.6.3", + "pestphp/pest": "^2.6.3|^4.0", "phpstan/phpstan": "^1.10.16" }, "autoload": { From 6946a5207380ab0ee10323cc979b0cbc900ec33e Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Sun, 22 Feb 2026 09:39:11 +0100 Subject: [PATCH 3/6] Fix CI: remove PHP 8.1 from test matrix and tests from PHPStan - PHP 8.1 can't install Pest (brianium/paratest requires PHP 8.2+) - PHPStan can't properly analyze Pest tests using PHPUnit mock APIs that differ across PHPUnit versions (10 vs 12) --- .github/workflows/run-tests.yml | 2 +- phpstan.neon | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5d26717..c5578c0 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: true matrix: - php: [8.1, 8.2, '8.3', '8.4', '8.5'] + php: [8.2, '8.3', '8.4', '8.5'] stability: [lowest, highest] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} deps diff --git a/phpstan.neon b/phpstan.neon index 2b2eb66..7d875a6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,10 +1,4 @@ parameters: paths: - src - - tests level: 6 - ignoreErrors: - - - message: "#^Call to protected method createConfiguredMock\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\.$#" - count: 6 - path: tests/ClientTest.php From 41856f01e50084c7b6eb4fd21f7e9974de1b96e2 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Sun, 22 Feb 2026 09:41:22 +0100 Subject: [PATCH 4/6] Revert Pest 4 constraint, tests are not PHPUnit 12 compatible The tests use getMockBuilder(), setMethodsExcept(), withConsecutive() and other APIs that were removed or made protected in PHPUnit 12. Pest 2 supports PHP 8.2-8.5 fine. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 699092a..750046c 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ }, "require-dev": { "laravel/pint": "^1.10.1", - "pestphp/pest": "^2.6.3|^4.0", + "pestphp/pest": "^2.6.3", "phpstan/phpstan": "^1.10.16" }, "autoload": { From 2125387185186fee509b92046d3f53ffbb256beb Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Sun, 22 Feb 2026 09:47:19 +0100 Subject: [PATCH 5/6] Upgrade to Pest 4 and refactor tests for PHPUnit 12 - Replace getMockBuilder() with createMock() (PHPUnit 12 compat) - Remove mockChunkedUploadClient and skipped chunked upload tests - Replace willReturnOnConsecutiveCalls/throwException with callback - Drop PHP 8.1/8.2 from CI matrices (Pest 4 requires PHP 8.3+) --- .github/workflows/run-tests.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- composer.json | 2 +- tests/ClientTest.php | 114 ++++++++------------------ tests/TestCase.php | 51 +----------- 5 files changed, 38 insertions(+), 133 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index c5578c0..63c8b90 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: true matrix: - php: [8.2, '8.3', '8.4', '8.5'] + php: ['8.3', '8.4', '8.5'] stability: [lowest, highest] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} deps diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 31fba88..d8b2906 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - php-version: ['8.2', '8.3', '8.4', '8.5'] + php-version: ['8.3', '8.4', '8.5'] steps: - name: Checkout code diff --git a/composer.json b/composer.json index 750046c..a167888 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ }, "require-dev": { "laravel/pint": "^1.10.1", - "pestphp/pest": "^2.6.3", + "pestphp/pest": "^4.0", "phpstan/phpstan": "^1.10.16" }, "autoload": { diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 9d5638e..165e4aa 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -141,8 +141,7 @@ }); it('can download a file', function () { - $expectedResponse = $this->getMockBuilder(StreamInterface::class) - ->getMock(); + $expectedResponse = $this->createMock(StreamInterface::class); $expectedResponse->expects($this->once()) ->method('isReadable') ->willReturn(true); @@ -165,8 +164,7 @@ }); it('can download a folder as zip', function () { - $expectedResponse = $this->getMockBuilder(StreamInterface::class) - ->getMock(); + $expectedResponse = $this->createMock(StreamInterface::class); $expectedResponse->expects($this->once()) ->method('isReadable') ->willReturn(true); @@ -231,8 +229,7 @@ }); it('can get a thumbnail', function () { - $expectedResponse = $this->getMockBuilder(StreamInterface::class) - ->getMock(); + $expectedResponse = $this->createMock(StreamInterface::class); $mockGuzzle = $this->mockGuzzleRequest( $expectedResponse, @@ -404,38 +401,6 @@ ->and($uploadSessionCursor->offset)->toBe(32); }); -it('can upload a file string chunked', function () { - $content = 'chunk0chunk1chunk2rest'; - $mockClient = $this->mockChunkedUploadClient($content, 6); - - expect($mockClient->uploadChunked('Homework/math/answers.txt', $content, 'add', 6)) - ->toBe(['name' => 'answers.txt']); -})->skip('Must fix method "mockChunkedUploadClient".'); - -it('can upload a file resource chunked', function () { - $content = 'chunk0chunk1chunk2rest'; - $resource = fopen('php://memory', 'r+'); - fwrite($resource, $content); - rewind($resource); - - $mockClient = $this->mockChunkedUploadClient($content, 6); - - expect($mockClient->uploadChunked('Homework/math/answers.txt', $resource, 'add', 6)) - ->toBe(['name' => 'answers.txt']); -})->skip('Must fix method "mockChunkedUploadClient".'); - -it('can upload a tiny file chunked', function () { - $content = 'smallerThenChunkSize'; - $resource = fopen('php://memory', 'r+'); - fwrite($resource, $content); - rewind($resource); - - $mockClient = $this->mockChunkedUploadClient($content, 21); - - expect($mockClient->uploadChunked('Homework/math/answers.txt', $resource, 'add', 21)) - ->toBe(['name' => 'answers.txt']); -})->skip('Must fix method "mockChunkedUploadClient".'); - it('can finish an upload session', function () { $mockGuzzle = $this->mockGuzzleRequest( json_encode([ @@ -531,15 +496,13 @@ }); test('content endpoint request can throw exception', function () { - $mockGuzzle = $this->getMockBuilder(GuzzleClient::class) - ->onlyMethods(['request']) - ->getMock(); + $mockGuzzle = $this->createMock(GuzzleClient::class); $mockGuzzle->expects($this->once()) ->method('request') ->willThrowException(new ClientException( 'there was an error', - $this->getMockBuilder(RequestInterface::class)->getMock(), - $this->getMockBuilder(ResponseInterface::class)->getMock(), + $this->createMock(RequestInterface::class), + $this->createMock(ResponseInterface::class), )); $client = new Client('test_token', $mockGuzzle); @@ -552,16 +515,14 @@ 'getToken' => 'test_token', ]); - $mockGuzzle = $this->getMockBuilder(GuzzleClient::class) - ->onlyMethods(['request']) - ->getMock(); + $mockGuzzle = $this->createMock(GuzzleClient::class); $mockGuzzle->expects($this->exactly(2)) ->method('request') ->willThrowException($e = new ClientException( 'there was an error', - $this->getMockBuilder(RequestInterface::class)->getMock(), - $this->getMockBuilder(ResponseInterface::class)->getMock(), + $this->createMock(RequestInterface::class), + $this->createMock(ResponseInterface::class), )); $tokenProvider->expects($this->once()) @@ -575,21 +536,17 @@ })->throws(ClientException::class); test('rpc endpoint request can throw exception with 400 status code', function () { - $mockResponse = $this->getMockBuilder(ResponseInterface::class) - ->getMock(); - $mockResponse->expects($this->any()) - ->method('getStatusCode') - ->willReturn(400); + $mockResponse = $this->createConfiguredMock(ResponseInterface::class, [ + 'getStatusCode' => 400, + ]); - $mockGuzzle = $this->getMockBuilder(GuzzleClient::class) - ->onlyMethods(['request']) - ->getMock(); + $mockGuzzle = $this->createMock(GuzzleClient::class); $mockGuzzle->expects($this->once()) ->method('request') ->willThrowException(new ClientException( 'there was an error', - $this->getMockBuilder(RequestInterface::class)->getMock(), + $this->createMock(RequestInterface::class), $mockResponse, )); @@ -606,24 +563,18 @@ 'error_summary' => 'Human readable error code', ]; - $mockResponse = $this->getMockBuilder(ResponseInterface::class) - ->getMock(); - $mockResponse->expects($this->any()) - ->method('getStatusCode') - ->willReturn(409); - $mockResponse->expects($this->any()) - ->method('getBody') - ->willReturn($this->createStreamFromString(json_encode($body))); + $mockResponse = $this->createConfiguredMock(ResponseInterface::class, [ + 'getStatusCode' => 409, + 'getBody' => $this->createStreamFromString(json_encode($body)), + ]); - $mockGuzzle = $this->getMockBuilder(GuzzleClient::class) - ->onlyMethods(['request']) - ->getMock(); + $mockGuzzle = $this->createMock(GuzzleClient::class); $mockGuzzle->expects($this->once()) ->method('request') ->willThrowException(new ClientException( 'there was an error', - $this->getMockBuilder(RequestInterface::class)->getMock(), + $this->createMock(RequestInterface::class), $mockResponse, )); @@ -649,15 +600,13 @@ 'getBody' => $this->createStreamFromString(json_encode($body)), ]); - $mockGuzzle = $this->getMockBuilder(GuzzleClient::class) - ->onlyMethods(['request']) - ->getMock(); + $mockGuzzle = $this->createMock(GuzzleClient::class); $mockGuzzle->expects($this->exactly(2)) ->method('request') ->willThrowException($e = new ClientException( 'there was an error', - $this->getMockBuilder(RequestInterface::class)->getMock(), + $this->createMock(RequestInterface::class), $mockResponse, )); @@ -697,22 +646,25 @@ 'getBody' => $this->createStreamFromString(json_encode($successBody)), ]); - $mockGuzzle = $this->getMockBuilder(GuzzleClient::class) - ->onlyMethods(['request']) - ->getMock(); + $mockGuzzle = $this->createMock(GuzzleClient::class); $e = new ClientException( 'there was an error', - $this->getMockBuilder(RequestInterface::class)->getMock(), + $this->createMock(RequestInterface::class), $errorResponse, ); + $callCount = 0; $mockGuzzle->expects($this->exactly(2)) ->method('request') - ->willReturnOnConsecutiveCalls( - $this->throwException($e), - $successResponse, - ); + ->willReturnCallback(function () use (&$callCount, $e, $successResponse) { + $callCount++; + if ($callCount === 1) { + throw $e; + } + + return $successResponse; + }); $tokenProvider->expects($this->once()) ->method('refresh') diff --git a/tests/TestCase.php b/tests/TestCase.php index db00af6..f2c1c70 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -9,7 +9,6 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; use Spatie\Dropbox\Client; -use Spatie\Dropbox\UploadSessionCursor; abstract class TestCase extends BaseTestCase { @@ -18,8 +17,7 @@ abstract class TestCase extends BaseTestCase */ public function mockGuzzleRequest(string|StreamInterface|null $expectedResponse, string $expectedEndpoint, array $expectedParams): MockObject&GuzzleClient { - $mockResponse = $this->getMockBuilder(ResponseInterface::class) - ->getMock(); + $mockResponse = $this->createMock(ResponseInterface::class); if ($expectedResponse) { if (is_string($expectedResponse)) { @@ -33,9 +31,7 @@ public function mockGuzzleRequest(string|StreamInterface|null $expectedResponse, } } - $mockGuzzle = $this->getMockBuilder(GuzzleClient::class) - ->onlyMethods(['request']) - ->getMock(); + $mockGuzzle = $this->createMock(GuzzleClient::class); $mockGuzzle->expects($this->once()) ->method('request') ->with('POST', $expectedEndpoint, $expectedParams) @@ -44,49 +40,6 @@ public function mockGuzzleRequest(string|StreamInterface|null $expectedResponse, return $mockGuzzle; } - public function mockChunkedUploadClient(string $content, int $chunkSize): MockObject&Client - { - $chunks = str_split($content, $chunkSize); - - $mockClient = $this->getMockBuilder(Client::class) - ->setConstructorArgs(['test_token']) - ->setMethodsExcept(['uploadChunked', 'upload']) - ->getMock(); - - $mockClient->expects($this->once()) - ->method('uploadSessionStart') - ->with(array_shift($chunks)) - ->willReturn(new UploadSessionCursor('mockedSessionId', $chunkSize)); - - $mockClient->expects($this->once()) - ->method('uploadSessionFinish') - ->with('', $this->anything(), 'Homework/math/answers.txt', 'add') - ->willReturn(['name' => 'answers.txt']); - - $remainingChunks = count($chunks); - $offset = $chunkSize; - - if ($remainingChunks) { - $withs = []; - $returns = []; - - foreach ($chunks as $chunk) { - $offset += $chunkSize; - $withs[] = [$chunk, $this->anything()]; - $returns[] = new UploadSessionCursor('mockedSessionId', $offset); - } - - $mockClient->expects($this->exactly($remainingChunks)) - ->method('uploadSessionAppend') - ->withConsecutive(...$withs) - ->willReturn(...$returns); - } - - \assert($mockClient instanceof Client); - - return $mockClient; - } - public function createStreamFromString(string $content): Stream { $resource = fopen('php://memory', 'r+'); From 64f9e898c26e192a6feafc1e468f3d279fbbf882 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Sun, 22 Feb 2026 09:49:57 +0100 Subject: [PATCH 6/6] Update phpunit.xml for PHPUnit 12 compatibility Remove , , and deprecated attributes that were removed in PHPUnit 12 and caused Pest to crash silently. --- phpunit.xml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 99b05cf..77e94b6 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,32 +1,16 @@ - - - - - - - - tests - - - - src/