assertEquals($expected, $uploadedFile); } /** * @param array $input The input array to parse. * * @dataProvider providerCreateFromGlobals */ public function testCreateFromGlobalsFromUserData(array $input) { //If slim.files provided - it will return what was provided $userData['slim.files'] = $input; $uploadedFile = UploadedFile::createFromGlobals(Environment::mock($userData)); $this->assertEquals($input, $uploadedFile); } public function testCreateFromGlobalsWithoutFile() { unset($_FILES); $uploadedFile = UploadedFile::createFromGlobals(Environment::mock()); $this->assertEquals([], $uploadedFile); } public function testConstructor(): UploadedFile { $attr = [ 'tmp_name' => self::$filename, 'name' => 'my-avatar.txt', 'size' => 8, 'type' => 'text/plain', 'error' => 0, ]; $uploadedFile = new UploadedFile( $attr['tmp_name'], $attr['name'], $attr['type'], $attr['size'], $attr['error'], false ); $this->assertEquals($attr['name'], $uploadedFile->getClientFilename()); $this->assertEquals($attr['type'], $uploadedFile->getClientMediaType()); $this->assertEquals($attr['size'], $uploadedFile->getSize()); $this->assertEquals($attr['error'], $uploadedFile->getError()); $this->assertEquals($attr['tmp_name'], $uploadedFile->getFilePath()); return $uploadedFile; } public function testConstructorSapi(): UploadedFile { $attr = [ 'tmp_name' => self::$filename, 'name' => 'my-avatar.txt', 'size' => 8, 'type' => 'text/plain', 'error' => 0, ]; $uploadedFile = new UploadedFile( $attr['tmp_name'], $attr['name'], $attr['type'], $attr['size'], $attr['error'], true ); $this->assertEquals($attr['name'], $uploadedFile->getClientFilename()); $this->assertEquals($attr['type'], $uploadedFile->getClientMediaType()); $this->assertEquals($attr['size'], $uploadedFile->getSize()); $this->assertEquals($attr['error'], $uploadedFile->getError()); $this->assertEquals($attr['tmp_name'], $uploadedFile->getFilePath()); return $uploadedFile; } /** * @depends testConstructor * * @param UploadedFile $uploadedFile * * @return UploadedFile */ public function testGetStream(UploadedFile $uploadedFile): UploadedFile { $stream = $uploadedFile->getStream(); $this->assertEquals(true, $uploadedFile->getStream() instanceof Stream); $stream->close(); return $uploadedFile; } /** * @depends testConstructor * * @param UploadedFile $uploadedFile * */ public function testMoveToNotWritable(UploadedFile $uploadedFile) { $this->expectException(InvalidArgumentException::class); $tempName = uniqid('file-'); $path = 'some_random_dir' . DIRECTORY_SEPARATOR . $tempName; $uploadedFile->moveTo($path); } /** * @depends testConstructor * * @param UploadedFile $uploadedFile * * @return UploadedFile */ public function testMoveTo(UploadedFile $uploadedFile): UploadedFile { $tempName = uniqid('file-'); $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $tempName; $uploadedFile->moveTo($path); $this->assertFileExists($path); unlink($path); return $uploadedFile; } public function testMoveToRenameFailure() { $this->expectException(RuntimeException::class); $this->expectExceptionMessageMatches('/^Error moving uploaded file .* to .*$/'); $uploadedFile = $this->generateNewTmpFile(); $tempName = uniqid('file-'); $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $tempName; $GLOBALS['rename_return'] = false; $uploadedFile->moveTo($path); } /** * @depends testConstructorSapi * * @param UploadedFile $uploadedFile * */ public function testMoveToSapiNonUploadedFile(UploadedFile $uploadedFile) { $this->expectException(RuntimeException::class); $tempName = uniqid('file-'); $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $tempName; $uploadedFile->moveTo($path); } /** * @depends testConstructorSapi * * @param UploadedFile $uploadedFile * */ public function testMoveToSapiMoveUploadedFileFails(UploadedFile $uploadedFile) { $this->expectException(RuntimeException::class); $this->expectExceptionMessageMatches('~Error moving uploaded file.*~'); $GLOBALS['is_uploaded_file_return'] = true; $tempName = uniqid('file-'); $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $tempName; $uploadedFile->moveTo($path); } /** * @depends testMoveTo * * @param UploadedFile $uploadedFile * */ public function testMoveToCannotBeDoneTwice(UploadedFile $uploadedFile) { $this->expectException(RuntimeException::class); $tempName = uniqid('file-'); $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $tempName; $uploadedFile->moveTo($path); $this->assertFileExists($path); unlink($path); $uploadedFile->moveTo($path); } /** * This test must run after testMoveTo * * @depends testConstructor * * @param UploadedFile $uploadedFile * */ public function testMoveToAgain(UploadedFile $uploadedFile) { $this->expectException(RuntimeException::class); $tempName = uniqid('file-'); $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $tempName; $uploadedFile->moveTo($path); } /** * This test must run after testMoveTo * * @depends testConstructor * * @param UploadedFile $uploadedFile * */ public function testMovedStream(UploadedFile $uploadedFile) { $this->expectException(RuntimeException::class); $uploadedFile->getStream(); } public function testMoveToStream() { $uploadedFile = $this->generateNewTmpFile(); $fileProperty = new ReflectionProperty($uploadedFile, 'file'); $fileProperty->setAccessible(true); $fileName = $fileProperty->getValue($uploadedFile); $contents = file_get_contents($fileName); ob_start(); $uploadedFile->moveTo('php://output'); $movedFileContents = ob_get_clean(); $this->assertEquals($contents, $movedFileContents); $this->assertFileDoesNotExist($fileName); } public function testMoveToStreamCopyFailure() { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Error moving uploaded file to php://output'); $uploadedFile = $this->generateNewTmpFile(); $GLOBALS['copy_return'] = false; $uploadedFile->moveTo('php://output'); } public function testFileUploadWithTempStream() { $streamFactory = function (...$args) { return (new StreamFactory())->createStream(...$args); }; $uploadedFileFactory = function (...$args) { return (new UploadedFileFactory())->createUploadedFile(...$args); }; $this->runFileUploadWithTempStreamTest($streamFactory, $uploadedFileFactory); } /** * This test sequence has been inspired by UploadedFileFactoryTestCase from http-interop/http-factory-tests package. * * @param callable $streamFactory * @param callable $uploadedFileFactory */ private function runFileUploadWithTempStreamTest(callable $streamFactory, callable $uploadedFileFactory) { $content = 'this is your capitan speaking'; $error = UPLOAD_ERR_OK; $clientFilename = 'test.txt'; $clientMediaType = 'text/plain'; $stream = call_user_func($streamFactory, $content); $file = call_user_func( $uploadedFileFactory, $stream, strlen($content), $error, $clientFilename, $clientMediaType ); $this->assertInstanceOf(UploadedFileInterface::class, $file); $this->assertSame($content, (string)$file->getStream()); $this->assertSame(strlen($content), $file->getSize()); $this->assertSame($error, $file->getError()); $this->assertSame($clientFilename, $file->getClientFilename()); $this->assertSame($clientMediaType, $file->getClientMediaType()); $this->assertSame($stream->getMetadata('uri'), $file->getFilePath()); } public function testCreateUploadedFileWithInvalidArguments() { $this->expectException(InvalidArgumentException::class); new UploadedFile(42); // a random value that is neither a string nor an instance of StreamInterface } public function testCreateUploadedFileWithInvalidUri() { $this->expectException(InvalidArgumentException::class); $streamProphecy = $this->prophesize(StreamInterface::class); /** @noinspection PhpUndefinedMethodInspection */ $streamProphecy ->getMetadata('uri') ->willReturn(null) ->shouldBeCalled(); $stream = $streamProphecy->reveal(); // Test with a StreamInterface that returns `null` // when `$stream->getMetadata('uri')` is called (which is an invalid case). new UploadedFile($stream); } public function providerCreateFromGlobals(): array { return [ // no nest: [ // $_FILES array [ 'avatar' => [ 'tmp_name' => 'phpUxcOty', 'name' => 'my-avatar.png', 'size' => 90996, 'type' => 'image/png', 'error' => 0, ], ], // expected format of array [ 'avatar' => new UploadedFile('phpUxcOty', 'my-avatar.png', 'image/png', 90996, UPLOAD_ERR_OK, true) ] ], // no nest, with error: [ // $_FILES array [ 'avatar' => [ 'tmp_name' => 'phpUxcOty', 'name' => 'my-avatar.png', 'size' => 90996, 'type' => 'image/png', 'error' => 7, ], ], // expected format of array [ 'avatar' => new UploadedFile( 'phpUxcOty', 'my-avatar.png', 'image/png', 90996, UPLOAD_ERR_CANT_WRITE, true ) ] ], // array of files: [ // $_FILES array [ 'avatars' => [ 'tmp_name' => [ 0 => __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', 1 => __DIR__ . DIRECTORY_SEPARATOR . 'file1.html', ], 'name' => [ 0 => 'file0.txt', 1 => 'file1.html', ], 'type' => [ 0 => 'text/plain', 1 => 'text/html', ], 'error' => [ 0 => 0, 1 => 0 ], 'size' => [ 0 => 0, 1 => 0 ] ], ], // expected format of array [ 'avatars' => [ 0 => new UploadedFile( __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', 'file0.txt', 'text/plain', null, UPLOAD_ERR_OK, true ), 1 => new UploadedFile( __DIR__ . DIRECTORY_SEPARATOR . 'file1.html', 'file1.html', 'text/html', null, UPLOAD_ERR_OK, true ), ], ] ], // array of files as multidimensional array: [ // $_FILES array [ [ 'avatars' => [ 'tmp_name' => [ 0 => __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', 1 => __DIR__ . DIRECTORY_SEPARATOR . 'file1.html', ], 'name' => [ 0 => 'file0.txt', 1 => 'file1.html', ], 'type' => [ 0 => 'text/plain', 1 => 'text/html', ], 'size' => [ 0 => 0, 1 => 0, ], ], ], ], // expected format of array [ 0 => [ 'avatars' => [ 'tmp_name' => [], 'name' => [], 'type' => [], 'size' => [], ], ], ], ], // single nested file: [ // $_FILES array [ 'details' => [ 'tmp_name' => [ 'avatar' => __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', ], 'name' => [ 'avatar' => 'file0.txt', ], 'type' => [ 'avatar' => 'text/plain', ], 'error' => [ 'avatar' => 0, ], 'size' => [ 'avatar' => 0, ], ], ], // expected format of array [ 'details' => [ 'avatar' => new UploadedFile( __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', 'file0.txt', 'text/plain', null, UPLOAD_ERR_OK, true ), ], ] ], // nested array of files: [ [ 'files' => [ 'tmp_name' => [ 'details' => [ 'avatar' => [ 0 => __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', 1 => __DIR__ . DIRECTORY_SEPARATOR . 'file1.html', ], ], ], 'name' => [ 'details' => [ 'avatar' => [ 0 => 'file0.txt', 1 => 'file1.html', ], ], ], 'type' => [ 'details' => [ 'avatar' => [ 0 => 'text/plain', 1 => 'text/html', ], ], ], 'error' => [ 'details' => [ 'avatar' => [ 0 => 0, 1 => 0 ], ], ], 'size' => [ 'details' => [ 'avatar' => [ 0 => 0, 1 => 0 ], ], ], ], ], // expected format of array [ 'files' => [ 'details' => [ 'avatar' => [ 0 => new UploadedFile( __DIR__ . DIRECTORY_SEPARATOR . 'file0.txt', 'file0.txt', 'text/plain', null, UPLOAD_ERR_OK, true ), 1 => new UploadedFile( __DIR__ . DIRECTORY_SEPARATOR . 'file1.html', 'file1.html', 'text/html', null, UPLOAD_ERR_OK, true ), ], ], ], ] ], ]; } }