diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 1d46a0ac5284..b86a91ca7c93 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -264,6 +264,54 @@ public function assertLocation($uri) return $this; } + /** + * Assert that the response offers a file download. + * + * @param string|null $filename + * @return $this + */ + public function assertDownload($filename = null) + { + $contentDisposition = explode(';', $this->headers->get('content-disposition')); + + if (trim($contentDisposition[0]) !== 'attachment') { + PHPUnit::fail( + 'Response does not offer a file download.'.PHP_EOL. + 'Disposition ['.trim($contentDisposition[0]).'] found in header, [attachment] expected.' + ); + } + + if (! is_null($filename)) { + if (isset($contentDisposition[1]) && + trim(explode('=', $contentDisposition[1])[0]) !== 'filename') { + PHPUnit::fail( + 'Unsupported Content-Disposition header provided.'.PHP_EOL. + 'Disposition ['.trim(explode('=', $contentDisposition[1])[0]).'] found in header, [filename] expected.' + ); + } + + $message = "Expected file [{$filename}] is not present in Content-Disposition header."; + + if (! isset($contentDisposition[1])) { + PHPUnit::fail($message); + } else { + PHPUnit::assertSame( + $filename, + isset(explode('=', $contentDisposition[1])[1]) + ? trim(explode('=', $contentDisposition[1])[1]) + : '', + $message + ); + + return $this; + } + } else { + PHPUnit::assertTrue(true); + + return $this; + } + } + /** * Asserts that the response contains the given cookie and equals the optional value. * diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index 4c80d1196c7f..f3bcac9e2f62 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -1190,6 +1190,63 @@ public function testAssertJsonMissingValidationErrorsNestedCustomErrorsName2() $testResponse->assertJsonMissingValidationErrors('bar', 'data.errors'); } + public function testAssertDownloadOffered() + { + $files = new Filesystem; + $tempDir = __DIR__.'/tmp'; + $files->makeDirectory($tempDir, 0755, false, true); + $files->put($tempDir.'/file.txt', 'Hello World'); + $testResponse = TestResponse::fromBaseResponse(new Response( + $files->get($tempDir.'/file.txt'), 200, [ + 'Content-Disposition' => 'attachment; filename=file.txt', + ] + )); + $testResponse->assertDownload(); + $files->deleteDirectory($tempDir); + } + + public function testAssertDownloadOfferedWithAFileName() + { + $files = new Filesystem; + $tempDir = __DIR__.'/tmp'; + $files->makeDirectory($tempDir, 0755, false, true); + $files->put($tempDir.'/file.txt', 'Hello World'); + $testResponse = TestResponse::fromBaseResponse(new Response( + $files->get($tempDir.'/file.txt'), 200, [ + 'Content-Disposition' => 'attachment; filename = file.txt', + ] + )); + $testResponse->assertDownload('file.txt'); + $files->deleteDirectory($tempDir); + } + + public function testAssertDownloadOfferedWorksWithBinaryFileResponse() + { + $files = new Filesystem; + $tempDir = __DIR__.'/tmp'; + $files->makeDirectory($tempDir, 0755, false, true); + $files->put($tempDir.'/file.txt', 'Hello World'); + $testResponse = TestResponse::fromBaseResponse(new BinaryFileResponse( + $tempDir.'/file.txt', 200, [], true, 'attachment' + )); + $testResponse->assertDownload('file.txt'); + $files->deleteDirectory($tempDir); + } + + public function testAssertDownloadOfferedFailsWithInlineContentDisposition() + { + $this->expectException(AssertionFailedError::class); + $files = new Filesystem; + $tempDir = __DIR__.'/tmp'; + $files->makeDirectory($tempDir, 0755, false, true); + $files->put($tempDir.'/file.txt', 'Hello World'); + $testResponse = TestResponse::fromBaseResponse(new BinaryFileResponse( + $tempDir.'/file.txt', 200, [], true, 'inline' + )); + $testResponse->assertDownload(); + $files->deleteDirectory($tempDir); + } + public function testMacroable() { TestResponse::macro('foo', function () {