diff --git a/src/Symfony/Component/BrowserKit/HttpBrowser.php b/src/Symfony/Component/BrowserKit/HttpBrowser.php index b2331ea492..a1e6dd9af0 100644 --- a/src/Symfony/Component/BrowserKit/HttpBrowser.php +++ b/src/Symfony/Component/BrowserKit/HttpBrowser.php @@ -73,18 +73,9 @@ class HttpBrowser extends AbstractBrowser } $fields = $request->getParameters(); - $hasFile = false; - foreach ($request->getFiles() as $name => $file) { - if (!isset($file['tmp_name'])) { - continue; - } - $hasFile = true; - $fields[$name] = DataPart::fromPath($file['tmp_name'], $file['name']); - } - - if ($hasFile) { - $part = new FormDataPart($fields); + if ($uploadedFiles = $this->getUploadedFiles($request->getFiles())) { + $part = new FormDataPart($uploadedFiles); return [$part->bodyToIterable(), $part->getPreparedHeaders()->toArray()]; } @@ -119,4 +110,26 @@ class HttpBrowser extends AbstractBrowser return $headers; } + + /** + * Recursively go through the list. If the file has a tmp_name, convert it to a DataPart. + * Keep the original hierarchy. + */ + private function getUploadedFiles(array $files): array + { + $uploadedFiles = []; + foreach ($files as $name => $file) { + if (!\is_array($file)) { + return $uploadedFiles; + } + if (!isset($file['tmp_name'])) { + $uploadedFiles[$name] = $this->getUploadedFiles($file); + } + if (isset($file['tmp_name'])) { + $uploadedFiles[$name] = DataPart::fromPath($file['tmp_name'], $file['name']); + } + } + + return $uploadedFiles; + } } diff --git a/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php b/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php index 44eed997bd..fa3d531aa9 100644 --- a/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php @@ -27,17 +27,17 @@ class HttpBrowserTest extends AbstractBrowserTest /** * @dataProvider validContentTypes */ - public function testRequestHeaders(array $request, array $exepectedCall) + public function testRequestHeaders(array $requestArguments, array $expectedArguments) { $client = $this->createMock(HttpClientInterface::class); $client ->expects($this->once()) ->method('request') - ->with(...$exepectedCall) + ->with(...$expectedArguments) ->willReturn($this->createMock(ResponseInterface::class)); $browser = new HttpBrowser($client); - $browser->request(...$request); + $browser->request(...$requestArguments); } public function validContentTypes() @@ -61,7 +61,7 @@ class HttpBrowserTest extends AbstractBrowserTest ]; } - public function testMultiPartRequest() + public function testMultiPartRequestWithSingleFile() { $client = $this->createMock(HttpClientInterface::class); $client @@ -81,4 +81,90 @@ class HttpBrowserTest extends AbstractBrowserTest file_put_contents($path, 'my_file'); $browser->request('POST', 'http://example.com/', [], ['file' => ['tmp_name' => $path, 'name' => 'foo']]); } + + public function testMultiPartRequestWithNormalFlatArray() + { + $client = $this->createMock(HttpClientInterface::class); + $this->expectClientToSendRequestWithFiles($client, ['file1_content', 'file2_content']); + + $browser = new HttpBrowser($client); + $browser->request('POST', 'http://example.com/', [], [ + 'file1' => $this->getUploadedFile('file1'), + 'file2' => $this->getUploadedFile('file2'), + ]); + } + + public function testMultiPartRequestWithNormalNestedArray() + { + $client = $this->createMock(HttpClientInterface::class); + $this->expectClientToSendRequestWithFiles($client, ['file1_content', 'file2_content']); + + $browser = new HttpBrowser($client); + $browser->request('POST', 'http://example.com/', [], [ + 'level1' => [ + 'level2' => [ + 'file1' => $this->getUploadedFile('file1'), + 'file2' => $this->getUploadedFile('file2'), + ], + ], + ]); + } + + public function testMultiPartRequestWithBracketedArray() + { + $client = $this->createMock(HttpClientInterface::class); + $this->expectClientToSendRequestWithFiles($client, ['file1_content', 'file2_content']); + + $browser = new HttpBrowser($client); + $browser->request('POST', 'http://example.com/', [], [ + 'form[file1]' => $this->getUploadedFile('file1'), + 'form[file2]' => $this->getUploadedFile('file2'), + ]); + } + + public function testMultiPartRequestWithInvalidItem() + { + $client = $this->createMock(HttpClientInterface::class); + $this->expectClientToSendRequestWithFiles($client, ['file1_content']); + + $browser = new HttpBrowser($client); + $browser->request('POST', 'http://example.com/', [], [ + 'file1' => $this->getUploadedFile('file1'), + 'file2' => 'INVALID', + ]); + } + + private function uploadFile(string $data): string + { + $path = tempnam(sys_get_temp_dir(), 'http'); + file_put_contents($path, $data); + + return $path; + } + + private function getUploadedFile(string $name): array + { + return [ + 'tmp_name' => $this->uploadFile($name.'_content'), + 'name' => $name.'_name', + ]; + } + + protected function expectClientToSendRequestWithFiles(HttpClientInterface $client, $fileContents) + { + $client + ->expects($this->once()) + ->method('request') + ->with('POST', 'http://example.com/', $this->callback(function ($options) use ($fileContents) { + $this->assertStringContainsString('Content-Type: multipart/form-data', implode('', $options['headers'])); + $this->assertInstanceOf('\Generator', $options['body']); + $body = implode('', iterator_to_array($options['body'], false)); + foreach ($fileContents as $content) { + $this->assertStringContainsString($content, $body); + } + + return true; + })) + ->willReturn($this->createMock(ResponseInterface::class)); + } }