diff --git a/README.md b/README.md index cc88e87..b8850ad 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Latest Stable Version](https://poser.pugx.org/donatj/mock-webserver/version)](https://packagist.org/packages/donatj/mock-webserver) [![License](https://poser.pugx.org/donatj/mock-webserver/license)](https://packagist.org/packages/donatj/mock-webserver) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/donatj/mock-webserver/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/donatj/mock-webserver) -[![Build Status](https://github.com/donatj/mock-webserver/workflows/CI/badge.svg?)](https://github.com/donatj/mock-webserver/actions?query=workflow%3ACI) +[![CI](https://github.com/donatj/mock-webserver/workflows/CI/badge.svg?)](https://github.com/donatj/mock-webserver/actions?query=workflow%3ACI) [![Build Status](https://travis-ci.org/donatj/mock-webserver.svg?branch=master)](https://travis-ci.org/donatj/mock-webserver) @@ -101,7 +101,7 @@ require __DIR__ . '/../vendor/autoload.php'; $server = new MockWebServer; $server->start(); -// We define the servers response to requests of the /definedPath endpoint +// We define the server's response to requests of the /definedPath endpoint $url = $server->setResponseOfPath( '/definedPath', new Response( @@ -137,6 +137,53 @@ Content-type: text/html; charset=UTF-8 This is our http body response ``` +### Change Default Response + +```php +start(); + +// The default response is donatj\MockWebServer\Responses\DefaultResponse +// which returns an HTTP 200 and a descriptive JSON payload. +// +// Change the default response to donatj\MockWebServer\Responses\NotFoundResponse +// to get a standard 404. +// +// Any other response may be specified as default as well. +$server->setDefaultResponse(new NotFoundResponse); + +$content = file_get_contents($server->getServerRoot() . '/PageDoesNotExist', false, stream_context_create([ + 'http' => [ 'ignore_errors' => true ] // allow reading 404s +])); + +// $http_response_header is a little known variable magically defined +// in the current scope by file_get_contents with the response headers +echo implode("\n", $http_response_header) . "\n\n"; +echo $content . "\n"; + +``` + +Outputs: + +``` +HTTP/1.0 404 Not Found +Host: 127.0.0.1:61355 +Date: Mon, 30 Aug 2021 20:02:58 GMT +Connection: close +X-Powered-By: PHP/7.3.29 +Content-type: text/html; charset=UTF-8 + +VND.DonatStudios.MockWebServer: Resource '/PageDoesNotExist' not found! + +``` + ### PHPUnit ```php diff --git a/docs/docs.md b/docs/docs.md index 3f43441..707371b 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -115,6 +115,24 @@ Set a specified path to provide a specific response --- +### Method: MockWebServer->setDefaultResponse + +```php +function setDefaultResponse(\donatj\MockWebServer\ResponseInterface $response) +``` + +Override the default server response, e.g. Fallback or 404 + +#### Parameters: + +- ***\donatj\MockWebServer\ResponseInterface*** `$response` + +#### Returns: + +- ***void*** + +--- + ### Method: MockWebServer->getLastRequest ```php @@ -273,4 +291,16 @@ Set the Response for the Given Method #### Parameters: - ***string*** `$method` -- ***\donatj\MockWebServer\ResponseInterface*** `$response` \ No newline at end of file +- ***\donatj\MockWebServer\ResponseInterface*** `$response` + +## Built-In Responses + +### Class: \donatj\MockWebServer\Responses\DefaultResponse + +The Built-In Default Response. + +Results in an HTTP 200 with a JSON encoded version of the incoming Request + +### Class: \donatj\MockWebServer\Responses\NotFoundResponse + +Basic Built-In 404 Response \ No newline at end of file diff --git a/example/simple.php b/example/simple.php index 87d9b05..c8c940f 100644 --- a/example/simple.php +++ b/example/simple.php @@ -8,7 +8,7 @@ $server = new MockWebServer; $server->start(); -// We define the servers response to requests of the /definedPath endpoint +// We define the server's response to requests of the /definedPath endpoint $url = $server->setResponseOfPath( '/definedPath', new Response( diff --git a/mddoc.xml b/mddoc.xml index 9fb7755..e75b67f 100644 --- a/mddoc.xml +++ b/mddoc.xml @@ -28,6 +28,10 @@ I would be happy to accept pull requests that correct this. +
+ + +
@@ -49,6 +53,11 @@ I would be happy to accept pull requests that correct this. Outputs: +
+ + Outputs: + +
diff --git a/src/InternalServer.php b/src/InternalServer.php index 10d83e0..0cd1f37 100644 --- a/src/InternalServer.php +++ b/src/InternalServer.php @@ -3,6 +3,8 @@ namespace donatj\MockWebServer; use donatj\MockWebServer\Exceptions\ServerException; +use donatj\MockWebServer\Responses\DefaultResponse; +use donatj\MockWebServer\Responses\NotFoundResponse; /** * Class InternalServer @@ -24,6 +26,8 @@ class InternalServer { */ private $header; + const DEFAULT_REF = 'default'; + /** * InternalServer constructor. * @@ -82,83 +86,121 @@ public static function aliasPath( $tmpPath, $path ) { ); } + /** + * @param string $ref + * @return ResponseInterface|null + */ + private function responseForRef( $ref ) { + $path = $this->tmpPath . DIRECTORY_SEPARATOR . $ref; + if( !is_readable($path) ) { + return null; + } + + $content = file_get_contents($path); + $response = unserialize($content); + if( !$response instanceof ResponseInterface ) { + throw new ServerException('invalid serialized response'); + } + + return $response; + } + public function __invoke() { - $path = $this->getDataPath(); - - if( $path !== false ) { - if( is_readable($path) ) { - $content = file_get_contents($path); - $response = unserialize($content); - if( !$response instanceof ResponseInterface ) { - throw new ServerException('invalid serialized response'); - } - - http_response_code($response->getStatus($this->request)); - - foreach( $response->getHeaders($this->request) as $key => $header ) { - if( is_int($key) ) { - call_user_func($this->header, $header); - } else { - call_user_func($this->header, "{$key}: {$header}"); - } - } - $body = $response->getBody($this->request); - - if( $response instanceof MultiResponseInterface ) { - $response->next(); - self::storeResponse($this->tmpPath, $response); - } - - echo $body; + $ref = $this->getRefForUri($this->request->getParsedUri()['path']); + + if( $ref !== null ) { + $response = $this->responseForRef($ref); + if( $response ) { + $this->sendResponse($response); return; } - http_response_code(404); - echo MockWebServer::VND . ": Resource '{$path}' not found!\n"; + $this->sendResponse(new NotFoundResponse); return; } - header('Content-Type: application/json'); + $response = $this->responseForRef(self::DEFAULT_REF); + if( $response ) { + $this->sendResponse($response); - echo json_encode($this->request, JSON_PRETTY_PRINT); + return; + } + + $this->sendResponse(new DefaultResponse); + } + + protected function sendResponse( ResponseInterface $response ) { + http_response_code($response->getStatus($this->request)); + + foreach( $response->getHeaders($this->request) as $key => $header ) { + if( is_int($key) ) { + call_user_func($this->header, $header); + } else { + call_user_func($this->header, "{$key}: {$header}"); + } + } + + echo $response->getBody($this->request); + + if( $response instanceof MultiResponseInterface ) { + $response->next(); + self::storeResponse($this->tmpPath, $response); + } } /** - * @return false|string + * @return string|null */ - protected function getDataPath() { - $path = false; - - $uriPath = $this->request->getParsedUri()['path']; + protected function getRefForUri( $uriPath ) { $aliasPath = self::aliasPath($this->tmpPath, $uriPath); + if( file_exists($aliasPath) ) { if( $path = file_get_contents($aliasPath) ) { - $path = $this->tmpPath . DIRECTORY_SEPARATOR . $path; + return $path; } - } elseif( preg_match('%^/' . preg_quote(MockWebServer::VND) . '/([0-9a-fA-F]{32})$%', $uriPath, $matches) ) { - $path = $this->tmpPath . DIRECTORY_SEPARATOR . $matches[1]; + } elseif( preg_match('%^/' . preg_quote(MockWebServer::VND, '%') . '/([0-9a-fA-F]{32})$%', $uriPath, $matches) ) { + return $matches[1]; } - return $path; + return null; } /** - * @internal * @param string $tmpPath * @param \donatj\MockWebServer\ResponseInterface $response * @return string + * @internal */ public static function storeResponse( $tmpPath, ResponseInterface $response ) { - $ref = $response->getRef(); + $ref = $response->getRef(); + self::storeRef($response, $tmpPath, $ref); + + return $ref; + } + + /** + * @param string $tmpPath + * @param \donatj\MockWebServer\ResponseInterface $response + * @return void + * @internal + */ + public static function storeDefaultResponse( $tmpPath, ResponseInterface $response ) { + self::storeRef($response, $tmpPath, self::DEFAULT_REF); + } + + /** + * @param \donatj\MockWebServer\ResponseInterface $response + * @param string $tmpPath + * @param string $ref + */ + private static function storeRef( ResponseInterface $response, $tmpPath, $ref ) { $content = serialize($response); if( !file_put_contents($tmpPath . DIRECTORY_SEPARATOR . $ref, $content) ) { throw new Exceptions\RuntimeException('Failed to write temporary content'); } - - return $ref; } } diff --git a/src/MockWebServer.php b/src/MockWebServer.php index 91e4d9b..ca35ceb 100644 --- a/src/MockWebServer.php +++ b/src/MockWebServer.php @@ -178,6 +178,16 @@ public function setResponseOfPath( $path, ResponseInterface $response ) { return $this->getServerRoot() . $path; } + /** + * Override the default server response, e.g. Fallback or 404 + * + * @param \donatj\MockWebServer\ResponseInterface $response + * @return void + */ + public function setDefaultResponse( ResponseInterface $response ) { + InternalServer::storeDefaultResponse($this->tmpDir, $response); + } + /** * @return string * @internal diff --git a/src/Responses/DefaultResponse.php b/src/Responses/DefaultResponse.php new file mode 100644 index 0000000..0687df3 --- /dev/null +++ b/src/Responses/DefaultResponse.php @@ -0,0 +1,30 @@ + 'application/json' ]; + } + + public function getStatus( RequestInfo $request ) { + return 200; + } +} diff --git a/src/Responses/NotFoundResponse.php b/src/Responses/NotFoundResponse.php new file mode 100644 index 0000000..7b6cf40 --- /dev/null +++ b/src/Responses/NotFoundResponse.php @@ -0,0 +1,31 @@ +getParsedUri()['path']; + + return MockWebServer::VND . ": Resource '{$path}' not found!\n"; + } + + public function getHeaders( RequestInfo $request ) { + return []; + } + + public function getStatus( RequestInfo $request ) { + return 404; + } +} diff --git a/test/MockWebServer_ChangedDefault_IntegrationTest.php b/test/MockWebServer_ChangedDefault_IntegrationTest.php new file mode 100644 index 0000000..bde3f55 --- /dev/null +++ b/test/MockWebServer_ChangedDefault_IntegrationTest.php @@ -0,0 +1,45 @@ +start(); + + $server->setResponseOfPath('funk', new Response('fresh')); + $path = $server->getUrlOfResponse(new Response('fries')); + + $content = file_get_contents($server->getServerRoot() . '/PageDoesNotExist'); + $result = json_decode($content, true); + $this->assertNotFalse(stripos($http_response_header[0], '200 OK', true) ); + $this->assertSame('/PageDoesNotExist', $result['PARSED_REQUEST_URI']['path']); + + // try with a 404 + $server->setDefaultResponse(new NotFoundResponse); + + $content = file_get_contents($server->getServerRoot() . '/PageDoesNotExist', false, stream_context_create([ + 'http' => [ 'ignore_errors' => true ] // allow reading 404s + ])); + + $this->assertNotFalse(stripos($http_response_header[0], '404 Not Found', true)); + $this->assertSame("VND.DonatStudios.MockWebServer: Resource '/PageDoesNotExist' not found!\n", $content); + + // try with a custom response + $server->setDefaultResponse(new Response('cool beans')); + $content = file_get_contents($server->getServerRoot() . '/BadUrlBadTime'); + $this->assertSame('cool beans', $content); + + // ensure non-404-ing pages countinue to work as expected + $content = file_get_contents($server->getServerRoot() . '/funk'); + $this->assertSame('fresh', $content); + + $content = file_get_contents($path); + $this->assertSame('fries', $content); + } + +}