Server responds with a default 404 response when request is unhandled

This commit is contained in:
PJ Dietz 2018-06-20 16:12:49 -04:00
parent 6f247bccfa
commit f016b74c38
2 changed files with 203 additions and 109 deletions

View File

@ -37,13 +37,14 @@ class Server
* stored with the name. The value will be an array containing all of the * stored with the name. The value will be an array containing all of the
* path variables. * path variables.
* *
* @param array $attributes key-value pairs to register as attributes * @param array $attributes
* with the server request. * Key-value pairs to register as attributes with the server request.
* @param DispatcherInterface $dispatcher Dispatches middleware. If no * @param DispatcherInterface $dispatcher
* object is passed, the Server will create a * Dispatches handlers and middleware. If no object is passed, the
* WellRESTed\Dispatching\Dispatcher * Server will create a WellRESTed\Dispatching\Dispatcher.
* @param string|null $pathVariablesAttributeName Attribute name for * @param string|null $pathVariablesAttributeName
* matched path variables. A null value sets attributes directly. * Attribute name for matched path variables. A null value sets
* attributes directly.
*/ */
public function __construct( public function __construct(
array $attributes = null, array $attributes = null,
@ -66,7 +67,7 @@ class Server
* Push a new middleware onto the stack. * Push a new middleware onto the stack.
* *
* @param mixed $middleware Middleware to dispatch in sequence * @param mixed $middleware Middleware to dispatch in sequence
* @return self * @return static
*/ */
public function add($middleware) public function add($middleware)
{ {
@ -150,8 +151,8 @@ class Server
$transmitter = $this->getTransmitter(); $transmitter = $this->getTransmitter();
} }
$next = function ($request, $response) { $next = function () {
return $response; return $this->getUnhandledResponse();
}; };
$response = $this->dispatch($request, $response, $next); $response = $this->dispatch($request, $response, $next);
$transmitter->transmit($request, $response); $transmitter->transmit($request, $response);
@ -196,8 +197,7 @@ class Server
/** /**
* Return a "blank" response instance to populate. * Return a "blank" response instance to populate.
* *
* The response will be dispatched through the middleware and eventually * The response will be dispatched through the middleware.
* output to the client.
* *
* @return ResponseInterface * @return ResponseInterface
*/ */
@ -206,5 +206,16 @@ class Server
return new Response(); return new Response();
} }
/**
* Return a Response to indicate the server was unable to find any handlers
* that handled the request. By default, this is a 404 error.
*
* @return ResponseInterface
*/
protected function getUnhandledResponse(): ResponseInterface
{
return new Response(404);
}
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }

View File

@ -3,121 +3,108 @@
namespace WellRESTed\Test\Unit; namespace WellRESTed\Test\Unit;
use Prophecy\Argument; use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use WellRESTed\Dispatching\DispatcherInterface;
use WellRESTed\Message\Response; use WellRESTed\Message\Response;
use WellRESTed\Message\ServerRequest; use WellRESTed\Message\ServerRequest;
use WellRESTed\Server; use WellRESTed\Server;
use WellRESTed\Test\Doubles\NextMock;
use WellRESTed\Test\TestCase; use WellRESTed\Test\TestCase;
use WellRESTed\Transmission\TransmitterInterface;
class ServerTest extends TestCase class ServerTest extends TestCase
{ {
private $dispatcher;
private $next;
private $request;
private $response;
private $transmitter; private $transmitter;
private $server; private $server;
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
$this->request = new ServerRequest();
$this->response = new Response();
$this->next = new NextMock();
$this->transmitter = $this->prophesize('WellRESTed\Transmission\TransmitterInterface'); $this->transmitter = $this->prophesize(TransmitterInterface::class);
$this->transmitter->transmit(Argument::cetera())->willReturn(); $this->transmitter->transmit(Argument::cetera())->willReturn();
$this->dispatcher = $this->prophesize('WellRESTed\Dispatching\DispatcherInterface');
$this->dispatcher->dispatch(Argument::cetera())->will( $this->server = new Server();
function ($args) { }
list($middleware, $request, $response, $next) = $args;
return $next($request, $response); private function respond()
} {
$this->server->respond(
new ServerRequest(),
new Response(),
$this->transmitter->reveal()
); );
$this->server = $this->getMockBuilder('WellRESTed\Server')
->setMethods(["getDefaultDispatcher", "getRequest", "getResponse", "getTransmitter"])
->disableOriginalConstructor()
->getMock();
$this->server->expects($this->any())
->method("getDefaultDispatcher")
->will($this->returnValue($this->dispatcher->reveal()));
$this->server->expects($this->any())
->method("getRequest")
->will($this->returnValue($this->request));
$this->server->expects($this->any())
->method("getResponse")
->will($this->returnValue($this->response));
$this->server->expects($this->any())
->method("getTransmitter")
->will($this->returnValue($this->transmitter->reveal()));
$this->server->__construct();
} }
public function testCreatesInstances() // ------------------------------------------------------------------------
{
$server = new Server();
$this->assertNotNull($server);
}
public function testAddIsFluid()
{
$server = new Server();
$this->assertSame($server, $server->add("middleware"));
}
public function testReturnsDispatcher() public function testReturnsDispatcher()
{ {
$this->assertSame($this->dispatcher->reveal(), $this->server->getDispatcher()); $this->assertNotNull($this->server->getDispatcher());
} }
public function testDispatchesMiddlewareStack() public function testDispatchesMiddlewareStack()
{ {
$this->server->add("first"); // This test will add a string to this array from each middleware.
$this->server->add("second");
$this->server->add("third");
$this->server->dispatch($this->request, $this->response, $this->next); $steps = [];
$this->dispatcher->dispatch( $this->server->add(
["first", "second", "third"], function ($rqst, $resp, $next) use (&$steps) {
$this->request, $steps[] = 'first';
$this->response, return $next($rqst, $resp);
$this->next }
)->shouldHaveBeenCalled(); );
$this->server->add(
function ($rqst, $resp, $next) use (&$steps) {
$steps[] = 'second';
return $next($rqst, $resp);
}
);
$this->server->add(
function ($rqst, $resp, $next) use (&$steps) {
$steps[] = 'third';
return $next($rqst, $resp);
}
);
$this->respond();
$this->assertEquals(['first', 'second', 'third'], $steps);
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Respond // Respond
public function testRespondDispatchesRequest() public function testRespondSendsResponseToTransmitter()
{ {
$this->server->respond(); $expectedResponse = new Response(200);
$this->dispatcher->dispatch(
Argument::any(),
$this->request,
Argument::any(),
Argument::any()
)->shouldHaveBeenCalled();
}
public function testRespondDispatchesResponse() $this->server->add(
{ function ($rqst, $resp, $next) {
$this->server->respond(); return $next($rqst, $resp);
$this->dispatcher->dispatch( }
Argument::any(), );
Argument::any(),
$this->response, $this->server->add(
Argument::any() function ($rqst, $resp, $next) {
)->shouldHaveBeenCalled(); return $next($rqst, $resp);
} }
);
$this->server->add(
function () use ($expectedResponse) {
return $expectedResponse;
}
);
$this->respond();
public function testRespondSendsResponseToResponder()
{
$this->server->respond();
$this->transmitter->transmit( $this->transmitter->transmit(
$this->request, Argument::any(),
$this->response $expectedResponse
)->shouldHaveBeenCalled(); )->shouldHaveBeenCalled();
} }
@ -126,20 +113,30 @@ class ServerTest extends TestCase
public function testCreatesRouterWithDispatcher() public function testCreatesRouterWithDispatcher()
{ {
$this->request = $this->request $dispatcher = $this->prophesize(DispatcherInterface::class);
$dispatcher->dispatch(Argument::cetera())->will(
function ($args) {
list($middleware, $request, $response, $next) = $args;
return $next($request, $response);
}
);
$server = new Server(null, $dispatcher->reveal());
$request = (new ServerRequest())
->withMethod("GET") ->withMethod("GET")
->withRequestTarget("/"); ->withRequestTarget("/");
$response = new Response();
$next = function ($rqst, $resp) {
return $resp;
};
$router = $this->server->createRouter(); $router = $server->createRouter();
$router->register("GET", "/", "middleware"); $router->register("GET", "/", "middleware");
$router($this->request, $this->response, $this->next); $router($request, $response, $next);
$this->dispatcher->dispatch( $dispatcher->dispatch(Argument::cetera())
"middleware", ->shouldHaveBeenCalled();
$this->request,
$this->response,
$this->next
)->shouldHaveBeenCalled();
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -148,21 +145,107 @@ class ServerTest extends TestCase
public function testAddsAttributesToRequest() public function testAddsAttributesToRequest()
{ {
$attributes = [ $attributes = [
"name" => "value" 'name' => 'value'
]; ];
$this->server->__construct($attributes); $server = new Server($attributes);
$this->server->respond();
$isRequestWithExpectedAttribute = function ($request) { $spyMiddleware = function ($rqst, $resp) use (&$capturedRequest) {
return $request->getAttribute("name") === "value"; $capturedRequest = $rqst;
return $resp;
}; };
$this->dispatcher->dispatch( $server->add($spyMiddleware);
$server->respond(
new ServerRequest(),
new Response(),
$this->transmitter->reveal()
);
$this->assertEquals('value', $capturedRequest->getAttribute('name'));
}
// ------------------------------------------------------------------------
// End of Stack
public function testRespondsWithDefaultHandlerWhenReachingEndOfStack()
{
$this->respond();
$has404StatusCode = function ($response) {
return $response->getStatusCode() === 404;
};
$this->transmitter->transmit(
Argument::any(), Argument::any(),
Argument::that($isRequestWithExpectedAttribute), Argument::that($has404StatusCode)
Argument::any(),
Argument::any()
)->shouldHaveBeenCalled(); )->shouldHaveBeenCalled();
} }
// ------------------------------------------------------------------------
// Defaults
public function testUsesDefaultRequestResponseAndTransmitter()
{
$request = new ServerRequest();
$response = new Response();
$server = new TestServer(
$request,
$response,
$this->transmitter->reveal()
);
$server->add(function ($rqst, $resp) {
return $resp;
});
$server->respond();
$this->transmitter->transmit($request, $response)
->shouldHaveBeenCalled();
}
}
// ----------------------------------------------------------------------------
class TestServer extends Server
{
/** @var ServerRequestInterface */
private $request;
/** @var ResponseInterface */
private $response;
/** @var TransmitterInterface */
private $transmitter;
/**
* TestServer constructor.
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @param TransmitterInterface $transmitter
*/
public function __construct(
ServerRequestInterface $request,
ResponseInterface $response,
TransmitterInterface $transmitter
) {
parent::__construct();
$this->request = $request;
$this->response = $response;
$this->transmitter = $transmitter;
}
protected function getRequest()
{
return $this->request;
}
protected function getResponse()
{
return $this->response;
}
protected function getTransmitter()
{
return $this->transmitter;
}
} }