From 071f8cbc7e267ba1ce16d586805c32a34b82f2e3 Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 14:06:53 -0500 Subject: [PATCH 01/13] Clean up getDataPath with early returns --- src/InternalServer.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/InternalServer.php b/src/InternalServer.php index 10d83e0..b4c583d 100644 --- a/src/InternalServer.php +++ b/src/InternalServer.php @@ -129,19 +129,18 @@ public function __invoke() { * @return false|string */ protected function getDataPath() { - $path = false; - $uriPath = $this->request->getParsedUri()['path']; $aliasPath = self::aliasPath($this->tmpPath, $uriPath); + if( file_exists($aliasPath) ) { if( $path = file_get_contents($aliasPath) ) { - $path = $this->tmpPath . DIRECTORY_SEPARATOR . $path; + return $this->tmpPath . DIRECTORY_SEPARATOR . $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 $this->tmpPath . DIRECTORY_SEPARATOR . $matches[1]; } - return $path; + return false; } /** From 5819445f032694617ea16d5c95f66b6144a893e0 Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 14:13:05 -0500 Subject: [PATCH 02/13] NULL is a better sentinel --- src/InternalServer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/InternalServer.php b/src/InternalServer.php index b4c583d..c236c8d 100644 --- a/src/InternalServer.php +++ b/src/InternalServer.php @@ -85,7 +85,7 @@ public static function aliasPath( $tmpPath, $path ) { public function __invoke() { $path = $this->getDataPath(); - if( $path !== false ) { + if( $path !== null ) { if( is_readable($path) ) { $content = file_get_contents($path); $response = unserialize($content); @@ -126,7 +126,7 @@ public function __invoke() { } /** - * @return false|string + * @return string|null */ protected function getDataPath() { $uriPath = $this->request->getParsedUri()['path']; @@ -140,7 +140,7 @@ protected function getDataPath() { return $this->tmpPath . DIRECTORY_SEPARATOR . $matches[1]; } - return false; + return null; } /** From c6cb0b3380fe9a7d372c06f6218e5d8a0d119ec7 Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 14:14:03 -0500 Subject: [PATCH 03/13] Centeralize response sending --- src/InternalServer.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/InternalServer.php b/src/InternalServer.php index c236c8d..770330d 100644 --- a/src/InternalServer.php +++ b/src/InternalServer.php @@ -93,24 +93,13 @@ public function __invoke() { 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); + $this->sendResponse($response, $this->request); if( $response instanceof MultiResponseInterface ) { $response->next(); self::storeResponse($this->tmpPath, $response); } - echo $body; - return; } @@ -125,6 +114,20 @@ public function __invoke() { echo json_encode($this->request, JSON_PRETTY_PRINT); } + protected function sendResponse( ResponseInterface $response, RequestInfo $request ) { + http_response_code($response->getStatus($request)); + + foreach( $response->getHeaders($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($request); + } + /** * @return string|null */ From cf23561f60b13fc14d29f81ba1aa79366e478940 Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 14:34:50 -0500 Subject: [PATCH 04/13] Adds built in responses --- src/Responses/DefaultResponse.php | 26 ++++++++++++++++++++++++++ src/Responses/NotFoundResponse.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/Responses/DefaultResponse.php create mode 100644 src/Responses/NotFoundResponse.php diff --git a/src/Responses/DefaultResponse.php b/src/Responses/DefaultResponse.php new file mode 100644 index 0000000..ff57654 --- /dev/null +++ b/src/Responses/DefaultResponse.php @@ -0,0 +1,26 @@ + '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..73fdbee --- /dev/null +++ b/src/Responses/NotFoundResponse.php @@ -0,0 +1,28 @@ +getParsedUri()['path']; + + return MockWebServer::VND . ": Resource '{$path}' not found!\n"; + } + + public function getHeaders( RequestInfo $request ) { + return []; + } + + public function getStatus( RequestInfo $request ) { + return 404; + } +} From c394b89b8dd526eb606d006dcbf94ee4de697fd4 Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 14:36:32 -0500 Subject: [PATCH 05/13] Refactor request handling --- src/InternalServer.php | 66 +++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/src/InternalServer.php b/src/InternalServer.php index 770330d..c33ae4c 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 @@ -82,42 +84,48 @@ public static function aliasPath( $tmpPath, $path ) { ); } - public function __invoke() { - $path = $this->getDataPath(); + /** + * @param string $ref + * @return ResponseInterface|null + */ + private function responseForRef( $ref ) { + $path = $this->tmpPath . DIRECTORY_SEPARATOR . $ref; + if( !is_readable($path) ) { + return null; + } - if( $path !== null ) { - if( is_readable($path) ) { - $content = file_get_contents($path); - $response = unserialize($content); - if( !$response instanceof ResponseInterface ) { - throw new ServerException('invalid serialized response'); - } + $content = file_get_contents($path); + $response = unserialize($content); + if( !$response instanceof ResponseInterface ) { + throw new ServerException('invalid serialized response'); + } - $this->sendResponse($response, $this->request); + return $response; + } - if( $response instanceof MultiResponseInterface ) { - $response->next(); - self::storeResponse($this->tmpPath, $response); - } + public function __invoke() { + $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'); - - echo json_encode($this->request, JSON_PRETTY_PRINT); + $this->sendResponse(new DefaultResponse); } - protected function sendResponse( ResponseInterface $response, RequestInfo $request ) { - http_response_code($response->getStatus($request)); + protected function sendResponse( ResponseInterface $response ) { + http_response_code($response->getStatus($this->request)); - foreach( $response->getHeaders($request) as $key => $header ) { + foreach( $response->getHeaders($this->request) as $key => $header ) { if( is_int($key) ) { call_user_func($this->header, $header); } else { @@ -125,22 +133,26 @@ protected function sendResponse( ResponseInterface $response, RequestInfo $reque } } - echo $response->getBody($request); + if( $response instanceof MultiResponseInterface ) { + $response->next(); + self::storeResponse($this->tmpPath, $response); + } + + echo $response->getBody($this->request); } /** * @return string|null */ - protected function getDataPath() { - $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) ) { - return $this->tmpPath . DIRECTORY_SEPARATOR . $path; + return $path; } } elseif( preg_match('%^/' . preg_quote(MockWebServer::VND, '%') . '/([0-9a-fA-F]{32})$%', $uriPath, $matches) ) { - return $this->tmpPath . DIRECTORY_SEPARATOR . $matches[1]; + return $matches[1]; } return null; From 6d7538c658bf69950b3fdf2b2efb41e494c0853f Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 14:44:14 -0500 Subject: [PATCH 06/13] Corrects typo --- example/simple.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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( From 7e1c751099688242fe264d9ba93e915193909ddd Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 14:45:15 -0500 Subject: [PATCH 07/13] Adds support for a default ref --- src/InternalServer.php | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/InternalServer.php b/src/InternalServer.php index c33ae4c..ab7dabd 100644 --- a/src/InternalServer.php +++ b/src/InternalServer.php @@ -26,6 +26,8 @@ class InternalServer { */ private $header; + const DEFAULT_REF = 'default'; + /** * InternalServer constructor. * @@ -119,6 +121,13 @@ public function __invoke() { return; } + $response = $this->responseForRef(self::DEFAULT_REF); + if( $response ) { + $this->sendResponse($response); + + return; + } + $this->sendResponse(new DefaultResponse); } @@ -159,20 +168,39 @@ protected function getRefForUri( $uriPath ) { } /** - * @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; } } From 7320420a15185f5bc6f29c6aa87cb4fe15c1163f Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 14:45:38 -0500 Subject: [PATCH 08/13] Adds setDefaultResponse to MockWebServer --- src/MockWebServer.php | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 From 5714893ce1b12a3383f744fb2967dfb43048588d Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 15:04:52 -0500 Subject: [PATCH 09/13] Fix increment order of operations --- src/InternalServer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/InternalServer.php b/src/InternalServer.php index ab7dabd..0cd1f37 100644 --- a/src/InternalServer.php +++ b/src/InternalServer.php @@ -142,12 +142,12 @@ protected function sendResponse( ResponseInterface $response ) { } } + echo $response->getBody($this->request); + if( $response instanceof MultiResponseInterface ) { $response->next(); self::storeResponse($this->tmpPath, $response); } - - echo $response->getBody($this->request); } /** From a40ab973e466b04092304ae9986396d109bf7f48 Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 15:24:47 -0500 Subject: [PATCH 10/13] Updates docs --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- docs/docs.md | 18 ++++++++++++++++++ mddoc.xml | 5 +++++ 3 files changed, 72 insertions(+), 2 deletions(-) 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..b7282ac 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 diff --git a/mddoc.xml b/mddoc.xml index 9fb7755..2584d7d 100644 --- a/mddoc.xml +++ b/mddoc.xml @@ -49,6 +49,11 @@ I would be happy to accept pull requests that correct this. Outputs: +
+ + Outputs: + +
From c9d806db9d3366be2807dcfd2e43a3e0e128a9ee Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 15:30:59 -0500 Subject: [PATCH 11/13] Very slight docs improvments --- docs/docs.md | 14 +++++++++++++- mddoc.xml | 4 ++++ src/Responses/DefaultResponse.php | 4 ++++ src/Responses/NotFoundResponse.php | 3 +++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/docs.md b/docs/docs.md index b7282ac..707371b 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -291,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/mddoc.xml b/mddoc.xml index 2584d7d..e75b67f 100644 --- a/mddoc.xml +++ b/mddoc.xml @@ -28,6 +28,10 @@ I would be happy to accept pull requests that correct this. +
+ + +
diff --git a/src/Responses/DefaultResponse.php b/src/Responses/DefaultResponse.php index ff57654..0687df3 100644 --- a/src/Responses/DefaultResponse.php +++ b/src/Responses/DefaultResponse.php @@ -6,6 +6,10 @@ use donatj\MockWebServer\RequestInfo; use donatj\MockWebServer\ResponseInterface; +/** + * The Built-In Default Response. + * Results in an HTTP 200 with a JSON encoded version of the incoming Request + */ class DefaultResponse implements ResponseInterface { public function getRef() { diff --git a/src/Responses/NotFoundResponse.php b/src/Responses/NotFoundResponse.php index 73fdbee..7b6cf40 100644 --- a/src/Responses/NotFoundResponse.php +++ b/src/Responses/NotFoundResponse.php @@ -6,6 +6,9 @@ use donatj\MockWebServer\RequestInfo; use donatj\MockWebServer\ResponseInterface; +/** + * Basic Built-In 404 Response + */ class NotFoundResponse implements ResponseInterface { public function getRef() { From 17139e2e5db887019fdc4d263610ad6e1836fae9 Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 15:52:56 -0500 Subject: [PATCH 12/13] Adds super basic integration test --- ...bServer_ChangedDefault_IntegrationTest.php | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/MockWebServer_ChangedDefault_IntegrationTest.php diff --git a/test/MockWebServer_ChangedDefault_IntegrationTest.php b/test/MockWebServer_ChangedDefault_IntegrationTest.php new file mode 100644 index 0000000..85edafc --- /dev/null +++ b/test/MockWebServer_ChangedDefault_IntegrationTest.php @@ -0,0 +1,46 @@ +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->assertStringContainsString('200 OK', $http_response_header[0]); + $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->assertStringContainsString('404 Not Found', $http_response_header[0]); + $this->assertContains('HTTP/1.0 404 Not Found', $http_response_header); + $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); + } + +} From fb0230b9d15183e3cf1a025a5d5d9d77225965e3 Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Mon, 30 Aug 2021 16:00:09 -0500 Subject: [PATCH 13/13] Fixup phpunit --- test/MockWebServer_ChangedDefault_IntegrationTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/MockWebServer_ChangedDefault_IntegrationTest.php b/test/MockWebServer_ChangedDefault_IntegrationTest.php index 85edafc..bde3f55 100644 --- a/test/MockWebServer_ChangedDefault_IntegrationTest.php +++ b/test/MockWebServer_ChangedDefault_IntegrationTest.php @@ -16,7 +16,7 @@ public function testChangingDefaultResponse() { $content = file_get_contents($server->getServerRoot() . '/PageDoesNotExist'); $result = json_decode($content, true); - $this->assertStringContainsString('200 OK', $http_response_header[0]); + $this->assertNotFalse(stripos($http_response_header[0], '200 OK', true) ); $this->assertSame('/PageDoesNotExist', $result['PARSED_REQUEST_URI']['path']); // try with a 404 @@ -26,8 +26,7 @@ public function testChangingDefaultResponse() { 'http' => [ 'ignore_errors' => true ] // allow reading 404s ])); - $this->assertStringContainsString('404 Not Found', $http_response_header[0]); - $this->assertContains('HTTP/1.0 404 Not Found', $http_response_header); + $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