diff options
-rw-r--r-- | src/Controller/Output.php | 92 | ||||
-rw-r--r-- | tests/Controller/OutputTest.php | 646 |
2 files changed, 381 insertions, 357 deletions
diff --git a/src/Controller/Output.php b/src/Controller/Output.php index a6160f1..698b27f 100644 --- a/src/Controller/Output.php +++ b/src/Controller/Output.php @@ -38,17 +38,6 @@ trait Output /** - * If a non scalar value is passed without an format, use this format - * - * @param string $format Format by extention or MIME - */ - public function byDefaultSerializeTo($format) - { - $this->defaultFormat = $format; - } - - - /** * Set a response header * * @param string $header @@ -76,23 +65,24 @@ trait Output * $this->respondWith('json'); * </code> * - * @param int $code HTTP status code (may be omitted) - * @param string|array $format Mime or content format + * @param int|string $status HTTP status (may be omitted) + * @param string|array $format Mime or content format */ - public function respondWith($code, $format = null) + public function respondWith($status, $format = null) { $response = $this->getResponse(); // Shift arguments if $code is omitted - if (!is_int($code) && !preg_match('/^\d{3}\b/', $code)) { - list($code, $format) = array_merge([null], func_get_args()); + if (isset($status) && !is_int($status) && (!is_string($status) || !preg_match('/^\d{3}\b/', $status))) { + list($status, $format) = array_merge([null], func_get_args()); } - if ($code) { - $response = $response->withStatus((int)$code); + if (!empty($status)) { + list($code, $phrase) = explode(' ', $status, 2) + [1 => null]; + $response = $response->withStatus((int)$code, $phrase); } - if ($format) { + if (!empty($format)) { $contentType = $this->getContentType($format); $response = $response->withHeader('Content-Type', $contentType); } @@ -305,15 +295,26 @@ trait Output } $repository = new ApacheMimeTypes(); - $mime = $repository->findType('html'); + $mime = $repository->findType($format); if (!isset($mime)) { - throw new \UnexpectedValueException("Format $format doesn't correspond with a MIME type"); + throw new \UnexpectedValueException("Format '$format' doesn't correspond with a MIME type"); } return $mime; } + + /** + * If a non scalar value is passed without an format, use this format + * + * @param string $format Format by extention or MIME + */ + public function byDefaultSerializeTo($format) + { + $this->defaultFormat = $format; + } + /** * Serialize data * @@ -323,26 +324,26 @@ trait Output */ protected function serializeData($data, $contentType) { - if (is_scalar($data)) { - return (string)$data; + if (is_string($data)) { + return $data; } - $format = preg_replace('~^.+/~', '', $contentType); + $repository = new ApacheMimeTypes(); + list($format) = $repository->findExtensions($contentType) + [null]; + $method = 'serializeDataTo' . $format; if (method_exists($this, $method)) { return $this->$method($data); } - - if (is_object($data) && method_exists($data, '__toString')) { - return (string)$data; - } - throw new \Exception("Unable to serialize data to $format"); + $type = (is_object($data) ? get_class($data) . ' ' : '') . gettype($data); + throw new \UnexpectedValueException("Unable to serialize $type to '$contentType'"); } /** * Serialize data to JSON + * @internal made private because this will likely move to a library * * @param mixed $data * @return string @@ -353,7 +354,8 @@ trait Output } /** - * Serialize data to XML + * Serialize data to XML. + * @internal made private because this will likely move to a library * * @param mixed $data * @return string @@ -364,12 +366,13 @@ trait Output return $data->asXML(); } - if ($data instanceof \DOMNode) { - return $data->ownerDocument->saveXML($data); + if (($data instanceof \DOMNode && isset($data->ownerDocument)) || $data instanceof \DOMDocument) { + $dom = $data instanceof \DOMDocument ? $data : $data->ownerDocument; + return $dom->saveXML($data); } $type = (is_object($data) ? get_class($data) . ' ' : '') . gettype($data); - throw new \Exception("Unable to serialize $type to XML"); + throw new \UnexpectedValueException("Unable to serialize $type to XML"); } /** @@ -381,13 +384,28 @@ trait Output public function output($data, $format = null) { if (!isset($format)) { - $contentType = $this->getResponse()->getHeaderLine('Content-Type') ?: 'text/html'; - } else { + $contentType = $this->getResponse()->getHeaderLine('Content-Type'); + + if (empty($contentType)) { + $format = $this->defaultFormat ?: 'text/html'; + } + } + + if (empty($contentType)) { $contentType = $this->getContentType($format); $this->setResponseHeader('Content-Type', $contentType); } - - $content = $this->serializeData($data, $format); + + try { + $content = $this->serializeData($data, $contentType); + } catch (\UnexpectedValueException $e) { + if (!isset($format) && isset($this->defaultFormat) && $this->defaultFormat !== $contentType) { + $this->output($data, $this->defaultFormat); // Try default format instead + return; + } + + throw $e; + } $this->getResponse()->getBody()->write($content); } diff --git a/tests/Controller/OutputTest.php b/tests/Controller/OutputTest.php index 9952d6f..2793a14 100644 --- a/tests/Controller/OutputTest.php +++ b/tests/Controller/OutputTest.php @@ -17,40 +17,104 @@ class OutputTest extends \PHPUnit_Framework_TestCase use TestHelper; /** - * Provide data for testing error messages functions + * Test function respondWith * * @return array */ - public function statusProvider() + public function respondWithProvider() + { + return [ + [[200, 'application/json'], 200, 'application/json'], + [[200, 'json'], 200, 'application/json'], + [[204], 204, null], + [[204, null], 204, null], + [['400 Foo Bar'], [400, 'Foo Bar'], null], + [[null, 'application/json'], null, 'application/json'], + [['application/json'], null, 'application/json'], + [['json'], null, 'application/json'], + [['html'], null, 'text/html'], + [['text'], null, 'text/plain'] + ]; + } + + /** + * Test respondWith function + * @dataProvider respondWithProvider + * + * @param array $args + * @param int|array $status Expected status code or [code, phrase] + * @param string $contentType + */ + public function testRespondWith($args, $status, $contentType) + { + $response = $this->createMock(ResponseInterface::class); + $statusResponse = isset($status) ? $this->createMock(ResponseInterface::class) : $response; + $finalResponse = isset($contentType) ? $this->createMock(ResponseInterface::class) : $statusResponse; + + $response->expects(isset($status) ? $this->once() : $this->never())->method('withStatus') + ->with(...(array)$status) + ->willReturn($statusResponse); + + $statusResponse->expects(isset($contentType) ? $this->once() : $this->never())->method('withHeader') + ->with('Content-Type', $contentType) + ->willReturn($finalResponse); + + $controller = $this->getController(['getResponse', 'setResponse']); + + $controller->expects($this->once())->method('getResponse')->willReturn($response); + $controller->expects($this->once())->method('setResponse')->with($finalResponse); + + $controller->respondWith(...$args); + } + + /** + * @expectedException \UnexpectedValueException + * @expectedExceptionMessage Format 'foo-bar-zoo' doesn't correspond with a MIME type + */ + public function testRespondWithFormat() + { + $response = $this->createMock(ResponseInterface::class); + + $controller = $this->getController(['getResponse', 'setResponse']); + $controller->expects($this->once())->method('getResponse')->willReturn($response); + $controller->expects($this->never())->method('setResponse'); + + $controller->respondWith(null, 'foo-bar-zoo'); + } + + + public function implicitStatusCodeProvider() { return [ ['ok', 200], ['created', 201], ['accepted', 202], ['noContent', 204], - ['partialContent', 206], - + ['partialContent', 206, [1, 2, 100]], + ['redirect', 303, ['example.com']], ['back', 303], ['notModified', 304], - - ['badRequest', 400], + + ['badRequest', 400, ['']], ['requireAuth', 401], ['requireLogin', 401], ['paymentRequired', 402], ['forbidden', 403], ['notFound', 404], - ['conflict', 409], - ['tooManyRequests', 429] + ['conflict', 409, ['']], + ['tooManyRequests', 429], + + ['error', 500] ]; } - + /** - * Test functions that deal with error messages - * @dataProvider statusProvider - * + * Test the default status code of different response methods + * @dataProvider implicitStatusCodeProvider + * * @param string $function - * @param int $code Status code + * @param int $code Expected status code * @param array $args */ public function testImplicitStatus($function, $code, $args = []) @@ -59,441 +123,383 @@ class OutputTest extends \PHPUnit_Framework_TestCase $response->expects($this->once())->method('withStatus')->with($code)->willReturnSelf(); $response->expects($this->any())->method('withHeader')->willReturnSelf(); - $controller = $this->getController(['getResponse']); + $controller = $this->getController(['getResponse', 'output', 'getLocalReferer']); $controller->method('getResponse')->willReturn($response); $controller->$function(...$args); } - + + public function explicitStatusCodeProvider() + { + return [ + ['noContent', 205], + ['redirect', 301, ['example.com']], + ['redirect', 307, ['example.com']], + ['badRequest', 412, ['']], + ['notFound', 405, ['']], + ['error', 500, ['']] + ]; + } + /** - * Test functions that deal with error messages - * @dataProvider statusProvider - * + * Test setting the status code of different response methods + * @dataProvider explicitStatusCodeProvider + * * @param string $function - * @param int $code Status code + * @param int $code Expected status code + * @param array $args */ - public function testImplicitStatusMessage($function, $code, $args) + public function testExlicitStatus($function, $code, $args = []) { - $message = 'Test message'; - - $stream = $this->createMock(StreamInterface::class); - $stream->expects($this->once())->method('write')->with($message); - $response = $this->createMock(ResponseInterface::class); $response->expects($this->once())->method('withStatus')->with($code)->willReturnSelf(); - $response->expects($this->once())->method('getBody')->willReturn($stream); + $response->expects($this->any())->method('withHeader')->willReturnSelf(); - $controller = $this->getController(['getResponse']); + $controller = $this->getController(['getResponse', 'output', 'getLocalReferer']); $controller->method('getResponse')->willReturn($response); - - - $this->assertEquals($result, $response, "Response object should be returned"); - } - - /** - * Test setting flash - * - * @dataProvider flashProvider - * @param object $data - */ - public function testFlash($data) - { - $controller = $this->getMockBuilder(Controller::class)->disableOriginalConstructor()->getMockForAbstractClass(); - - $flash = $controller->flash(); - $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); - $this->assertEmpty($flash->get(), "Flash data should be empty"); - - $flash = $controller->flash($data->type, $data->message); - $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); - $this->assertEquals($data, $flash->get(), "Flash data is incorrect"); - - $flash = $controller->flash(); - $this->assertInstanceOf(Flash::class, $flash, "Flash is not set"); - $this->assertEquals($data, $flash->get(), "Flash data is incorrect"); + $args[] = $code; - $flash->clear(); + $controller->$function(...$args); } - /** - * Test setting flash - * - * @return array - */ - public function flashProvider() + + public function implicitMessageProvider() { return [ - [(object)['type' => 'test_type', 'message' => 'Test message']] - ]; + ['paymentRequired', 'Payment required'], + ['forbidden', 'Access denied'], + ['notFound', 'Not found'], + ['tooManyRequests', 'Too many requests'], + + ['error', 'An unexpected error occured'] + ]; } /** - * Test respondWith function + * Test the default messages of different response methods + * @dataProvider implicitMessageProvider * - * @dataProvider respondWithProvider - * @param int|string $code - * @param string $format - * @param int $setCode Actual code that will be set in response - * @param string $contentType + * @param string $function + * @param string $message Expected message + * @param array $args */ - public function testRespondWith($code, $format, $setCode, $contentType) + public function testImplicitMessage($function, $message, $args = []) { - $controller = $this->getController(['getResponse']); - list(, $response) = $this->getRequests(); + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('write')->with($message); - $this->expectResponseWith($response, $setCode, $contentType); - $controller->method('getResponse')->will($this->returnValue($response)); + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->any())->method('getHeaderLine')->with('Content-Type') + ->willReturn('text/plain'); + $response->expects($this->once())->method('withStatus')->willReturnSelf(); + $response->expects($this->once())->method('getBody')->willReturn($stream); - $result = $controller->respondWith($code, $format); + $controller = $this->getController(['getResponse']); + $controller->method('getResponse')->willReturn($response); - $this->assertEquals($result, $response, "Response object should be returned"); + $controller->$function(...$args); } - /** - * Test function respondWith - * - * @return array - */ - public function respondWithProvider() + + public function explicitMessageProvider() { return [ - [200, 'json', 200, 'application/json'], - [200, 'application/json', 200, 'application/json'], - [204, null, 204, null], - ['204 Created', null, 204, null], - ['json', null, null, 'application/json'] - ]; + ['badRequest'], + ['paymentRequired'], + ['forbidden'], + ['notFound'], + ['conflict'], + ['tooManyRequests'], + ['error'] + ]; } /** - * Test functions that are simple wrappers around respondWith function + * Test the default messages of different response methods + * @dataProvider explicitMessageProvider * - * @dataProvider respondWithWrappersProvider * @param string $function - * @param int $code */ - public function testResponseWithWrappers($function, $code) + public function testExplicitMessage($function) { - $controller = $this->getController(['getResponse']); - list(, $response) = $this->getRequests(); + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('write')->with("Hello world"); - $this->expectResponseWith($response, $code); - $controller->method('getResponse')->will($this->returnValue($response)); + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->any())->method('getHeaderLine')->with('Content-Type') + ->willReturn('text/plain'); + $response->expects($this->once())->method('withStatus')->willReturnSelf(); + $response->expects($this->once())->method('getBody')->willReturn($stream); - $result = $controller->{$function}(); + $controller = $this->getController(['getResponse']); + $controller->method('getResponse')->willReturn($response); - $this->assertEquals($result, $response, "Response object should be returned"); + $controller->$function("Hello world"); } + /** - * Provide data for testing respondWith wrappers - * - * @return array + * Test 'created' function with a location */ - public function respondWithWrappersProvider() + public function testCreated() { - return [ - ['ok', 200], - ['noContent', 204] - ]; + $controller = $this->getController(['respondWith', 'setResponseHeader']); + $controller->expects($this->once())->method('respondWith')->with(201); + $controller->expects($this->once())->method('setResponseHeader')->with('Location', '/foo/bar'); + + $controller->created('/foo/bar'); } /** - * Test 'created' function - * - * @dataProvider createdProvider - * @param string $location + * Test 'partialContent' function with Range header */ - public function testCreated($location) + public function testPartialContent() { - $controller = $this->getController(['getResponse']); - list(, $response) = $this->getRequests(); - - $response->expects($this->once())->method('withStatus')->with($this->equalTo(201))->will($this->returnSelf()); - if ($location) { - $response->expects($this->once())->method('withHeader')->with($this->equalTo('Location'), $this->equalTo($location))->will($this->returnSelf()); - } - - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $controller->created($location); + $controller = $this->getController(['respondWith', 'setResponseHeader']); + $controller->expects($this->once())->method('respondWith')->with(206); + $controller->expects($this->exactly(2))->method('setResponseHeader')->withConsecutive( + ['Content-Range', 'bytes 100-200/500'], + ['Content-Length', 100] + ); - $this->assertEquals($result, $response, "Response object should be returned"); + $controller->partialContent(100, 200, 500); } - - /** - * Provide data for testing 'created' function - * - * @return array - */ - public function createdProvider() + + + public function redirectStatusProvider() { return [ - [''], ['/some-path/test'] + [301], + [302], + ['307 Temporary Redirect'] ]; } - + /** * Test 'redirect' function - * - * @dataProvider redirectProvider - * @param string $url - * @param int $code - * @param boolean $default + * @dataProvider redirectStatusProvider + * + * @param int|string $status */ - public function testRedirect($url, $code, $default) + public function testRedirect($status) { - $controller = $this->getController(['getResponse']); - list(, $response) = $this->getRequests(); - - $this->expectRedirect($response, $url, $code); - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $default ? - $controller->redirect($url) : - $controller->redirect($url, $code); - - $this->assertEquals($result, $response, "Response object should be returned"); + $controller = $this->getController(['respondWith', 'setResponseHeader', 'output']); + + $controller->expects($this->once())->method('respondWith')->with($status); + $controller->expects($this->once())->method('setResponseHeader')->with('Location', '/foo'); + $controller->expects($this->once())->method('output') + ->with('You are being redirected to <a href="/foo">/foo</a>', 'text/html'); + + $controller->redirect('/foo', $status); } /** - * Provide data for testing 'redirect' function + * Provide data fot testing 'getLocalReferer' function * * @return array */ - public function redirectProvider() + public function localRefererProvider() { return [ - ['/test-url', 303, true], - ['/test-url', 301, false] + [null, '/'], + ['/', '/'], + ['/some/path', '/some/path'] ]; } /** - * Test 'requireLogin' function - * - * @dataProvider requireLoginProvider - * @param string $function + * Test 'back' function + * @dataProvider localRefererProvider + * + * @param string $referer + * @param string $location */ - public function testRequireLogin($function) + public function testBack($referer, $location) { - $controller = $this->getController(['getResponse']); - list(, $response) = $this->getRequests(); - - $this->expectRedirect($response, '/401', 303); - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $controller->{$function}(); - - $this->assertEquals($result, $response, "Response object should be returned"); + $controller = $this->getController(['getLocalReferer', 'respondWith', 'setResponseHeader', 'output']); + + $controller->expects($this->once())->method('getLocalReferer')->willReturn($referer); + $controller->expects($this->once())->method('respondWith')->with(303); + $controller->expects($this->once())->method('setResponseHeader')->with('Location', $location); + $controller->expects($this->once())->method('output') + ->with('You are being redirected to <a href="' . $location . '">' . $location . '</a>', 'text/html'); + + $controller->back(); } + /** - * Provide data for testing 'requireLogon' function + * Provide data for testing output * * @return array */ - public function requireLoginProvider() + public function outputProvider() { + $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document><tag>Test</tag></document>\n"; + + $dom = new \DOMDocument(); + $dom->loadXML($xml); + return [ - ['requireLogin'], ['requireAuth'] + ['green beans', null, 'green beans', 'text/html'], + ['hello world', 'text', 'hello world', 'text/plain'], + ['abc();', 'js', 'abc();', 'application/javascript'], + ['h1 { color: blue; }', 'css', 'h1 { color: blue; }', 'text/css'], + ['{ "testKey": "testValue" }', 'json', '{ "testKey": "testValue" }', 'application/json'], + [['testKey' => 'testValue'], 'json', '{"testKey":"testValue"}', 'application/json'], + [simplexml_load_string($xml), 'xml', $xml, 'application/xml'], + [$dom, 'xml', $xml, 'application/xml'], + [$dom->firstChild->firstChild, 'xml', '<tag>Test</tag>', 'application/xml'] ]; } /** - * Test 'getLocalReferer' funtion - * - * @dataProvider localRefererProvider - * @param string $referer - * @param string $host - * @param boolean $local + * Test output + * @dataProvider outputProvider + * + * @param mixed $data + * @param string $format + * @param string $content Expected content + * @param string $contentType Expected Content-Type */ - public function testLocalReferer($referer, $host, $local) + public function testOutputWithFormat($data, $format, $content, $contentType) { - $controller = $this->getController(['getRequest']); - list($request) = $this->getRequests(); - - $this->expectLocalReferer($request, $referer, $host); - $controller->method('getRequest')->will($this->returnValue($request)); + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('write')->with($content); - $result = $controller->getLocalReferer(); + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('getBody')->willReturn($stream); - $local ? - $this->assertEquals($referer, $result, "Local referer should be returned") : - $this->assertEquals('', $result, "Local referer should not be returned"); + $controller = $this->getController(['getResponse', 'setResponseHeader']); + $controller->method('getResponse')->willReturn($response); + + $controller->expects($this->once())->method('setResponseHeader')->with('Content-Type', $contentType); + + $controller->output($data, $format); } /** - * Test 'back' function - * - * @dataProvider localRefererProvider - * @param string $referer - * @param string $host - * @param boolean $local + * Test output, getting the format from the Content-Type response header + * @dataProvider outputProvider + * + * @param mixed $data + * @param string $format + * @param string $content Expected content + * @param string $contentType Expected Content-Type */ - public function testBack($referer, $host, $local) + public function testOutputWithoutFormat($data, $format, $content, $contentType) { - $controller = $this->getController(['getRequest', 'getResponse']); - list($request, $response) = $this->getRequests(); - - $this->expectLocalReferer($request, $referer, $host); - $this->expectRedirect($response, $local ? $referer : '/', 303); - - $controller->method('getRequest')->will($this->returnValue($request)); - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $controller->back(); - - $this->assertEquals($result, $response, "Response object should be returned"); - } + $stream = $this->createMock(StreamInterface::class); + $stream->expects($this->once())->method('write')->with($content); - /** - * Provide data fot testing 'getLocalReferer' function - * - * @return array - */ - public function localRefererProvider() - { - return [ - ['http://not-local-host.com/path', 'local-host.com', false], - ['http://local-host.com/path', 'local-host.com', true] - ]; - } + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('getHeaderline')->with('Content-Type') + ->willReturn($format ? $contentType : ''); + $response->expects($this->once())->method('getBody')->willReturn($stream); - /** - * Expect for 'getLocalReferer' function to work correctly - * - * @param ServerRequestInterface $request - * @param string $referer - * @param string $host - */ - public function expectLocalReferer($request, $referer, $host) - { - $request->expects($this->exactly(2))->method('getHeaderLine')->withConsecutive( - [$this->equalTo('HTTP_REFERER')], - [$this->equalTo('HTTP_HOST')] - )->will($this->returnCallback(function($header) use ($referer, $host) { - return $header === 'HTTP_REFERER' ? $referer : $host; - })); + $controller = $this->getController(['getResponse', 'setResponseHeader']); + $controller->method('getResponse')->willReturn($response); + + $controller->expects($format ? $this->never() : $this->once())->method('setResponseHeader') + ->with('Content-Type', $contentType); + + $controller->output($data); } - + /** - * Expect for redirect - * - * @param ResponseInterface $response - * @param string $url - * @param int $code + * Test output when using byDefaultSerializeTo + * @dataProvider outputProvider + * + * @param mixed $data + * @param string $format + * @param string $content Expected content + * @param string $contentType Expected Content-Type */ - public function expectRedirect($response, $url, $code) + public function testByDefaultSerializeTo($data, $format, $content, $contentType) { $stream = $this->createMock(StreamInterface::class); - $stream->expects($this->once())->method('write')->with($this->equalTo('You are being redirected to <a href="' . $url . '">' . $url . '</a>')); - - $response->expects($this->once())->method('getBody')->will($this->returnValue($stream)); - $response->expects($this->once())->method('withStatus')->with($this->equalTo($code))->will($this->returnSelf()); - $response->expects($this->exactly(2))->method('withHeader')->withConsecutive( - [$this->equalTo('Content-Type'), $this->equalTo('text/html')], - [$this->equalTo('Location'), $this->equalTo($url)] - )->will($this->returnSelf()); - } + $stream->expects($this->once())->method('write')->with($content); - /** - * Expect correct work of respondWith function - * - * @param ResponseInterface $response - * @param int $code - * @param string $contentType - */ - public function expectResponseWith($response, $code, $contentType = null) - { - $code ? - $response->expects($this->once())->method('withStatus')->with($this->equalTo($code))->will($this->returnSelf()) : - $response->expects($this->never())->method('withStatus')->with($this->equalTo($code)); + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('getBody')->willReturn($stream); - $contentType ? - $response->expects($this->once())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType))->will($this->returnSelf()) : - $response->expects($this->never())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType)); + $controller = $this->getController(['getResponse', 'setResponseHeader']); + $controller->method('getResponse')->willReturn($response); + + $controller->expects($this->once())->method('setResponseHeader')->with('Content-Type', $contentType); + + $controller->byDefaultSerializeTo($format); + $controller->output($data); } - + /** - * Expects that output will be set to content - * - * @param ResponseInterface $response - * @param string $content - * @param string $contentType + * Test output when using byDefaultSerializeTo as fallback + * @dataProvider outputProvider + * + * @param mixed $data + * @param string $format + * @param string $content Expected content + * @param string $contentType Expected Content-Type */ - public function expectOutput($response, $content, $contentType) + public function testByDefaultSerializeToFallback($data, $format, $content, $contentType) { $stream = $this->createMock(StreamInterface::class); - $stream->expects($this->once())->method('write')->with($this->equalTo($content)); + $stream->expects($this->once())->method('write')->with($content); - $response->expects($this->once())->method('withHeader')->with($this->equalTo('Content-Type'), $this->equalTo($contentType))->will($this->returnSelf()); - $response->expects($this->once())->method('getBody')->will($this->returnValue($stream)); - } + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('getHeaderline')->with('Content-Type')->willReturn('text/plain'); + $response->expects($this->once())->method('getBody')->willReturn($stream); + $controller = $this->getController(['getResponse', 'setResponseHeader']); + $controller->method('getResponse')->willReturn($response); + + $controller->expects(is_string($data) ? $this->never() : $this->once())->method('setResponseHeader') + ->with('Content-Type', $contentType); + + $controller->byDefaultSerializeTo($format); + $controller->output($data); + } + /** * Provide data for testing output * * @return array */ - public function outputProvider() + public function unserializableProvider() { - $xml = simplexml_load_string( - "<?xml version='1.0'?> - <document> - <tag1>Test tag</tag1> - <tag2>Test</tag2> - </document>" - ); - + $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document><tag>Test</tag></document>\n"; + + $stringable = $this->createPartialMock('stdClass', ['__toString']); + $stringable->method('__toString')->willReturn('I was an object'); + return [ - ['test_string', 'text', 'text/plain'], - ['javascript:test_call();', 'js', 'application/javascript'], - ['test {}', 'css', 'text/css'], - ['{ "testKey": "testValue" }', 'json', 'application/json'], - [['testKey' => 'testValue'], 'json', 'application/json'], - [$xml, 'xml', 'application/xml'] + [['abc', 'def'], 'text/plain', "Unable to serialize array to 'text/plain'"], + [['abc', 'def'], null, "Unable to serialize array to 'text/html'"], + [new \stdClass(), 'text', "Unable to serialize stdClass object to 'text/plain'"], + [new \stdClass(), 'xml', "Unable to serialize stdClass object to XML"], ]; } /** - * Test output - * - * @dataProvider outputProvider - * @param mixed $data + * Test that serializeData throws an UnexpectedValueException + * @dataProvider unserializableProvider + * + * @param mixed $data * @param string $format - * @param string $contentType + * @param string $message Excpected exception message */ - public function testOutput($data, $format, $contentType) + public function testSerializeDataException($data, $format, $message) { - $response = $this->createMock(ResponseInterface::class); - - $controller = $this->getController(['getResponse']); - $controller->method('getResponse')->willReturn($response); - - if (is_scalar($data)) { - $content = $data; - } elseif ($format === 'json') { - $content = json_encode($data); + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage($message); - if ($callback) $content = "$callback($content)"; - } elseif ($format === 'xml') { - $content = $data->asXML(); - } - - $this->expectOutput($response, $content, $contentType); - - if ($callback) { - $request->method('getQueryParams')->will($this->returnValue(['callback' => $callback])); - } - - $controller->method('getRequest')->will($this->returnValue($request)); - $controller->method('getResponse')->will($this->returnValue($response)); - - $result = $controller->output($data, $format); + $response = $this->createMock(ResponseInterface::class); - $this->assertEquals($result, $response, "Output should return response instance"); + $controller = $this->getController(['getResponse', 'setResponseHeader']); + $controller->method('getResponse')->willReturn($response); + + $controller->output($data, $format); } } |