REST: Testable EntryPoint
authorTim Starling <tstarling@wikimedia.org>
Tue, 4 Jun 2019 23:59:57 +0000 (09:59 +1000)
committerTim Starling <tstarling@wikimedia.org>
Fri, 14 Jun 2019 07:01:15 +0000 (17:01 +1000)
* Split EntryPoint into a static main() and a non-static execute()
* Add tests for execute()

Change-Id: I025356b04ddc5a16494f98c446d785d6bb05ab10

includes/Rest/EntryPoint.php
tests/phpunit/includes/Rest/EntryPointTest.php [new file with mode: 0644]
tests/phpunit/includes/Rest/Handler/HelloHandlerTest.php
tests/phpunit/includes/Rest/Handler/testRoutes.json [deleted file]
tests/phpunit/includes/Rest/testRoutes.json [new file with mode: 0644]

index d5924f0..795999a 100644 (file)
@@ -6,8 +6,16 @@ use ExtensionRegistry;
 use MediaWiki\MediaWikiServices;
 use RequestContext;
 use Title;
+use WebResponse;
 
 class EntryPoint {
+       /** @var RequestInterface */
+       private $request;
+       /** @var WebResponse */
+       private $webResponse;
+       /** @var Router */
+       private $router;
+
        public static function main() {
                // URL safety checks
                global $wgRequest;
@@ -21,8 +29,8 @@ class EntryPoint {
                RequestContext::getMain()->setTitle( $wgTitle );
 
                $services = MediaWikiServices::getInstance();
-
                $conf = $services->getMainConfig();
+
                $request = new RequestFromGlobals( [
                        'cookiePrefix' => $conf->get( 'CookiePrefix' )
                ] );
@@ -36,20 +44,35 @@ class EntryPoint {
                        new ResponseFactory
                );
 
-               $response = $router->execute( $request );
+               $entryPoint = new self(
+                       $request,
+                       $wgRequest->response(),
+                       $router );
+               $entryPoint->execute();
+       }
+
+       public function __construct( RequestInterface $request, WebResponse $webResponse,
+               Router $router
+       ) {
+               $this->request = $request;
+               $this->webResponse = $webResponse;
+               $this->router = $router;
+       }
+
+       public function execute() {
+               $response = $this->router->execute( $this->request );
 
-               $webResponse = $wgRequest->response();
-               $webResponse->header(
+               $this->webResponse->header(
                        'HTTP/' . $response->getProtocolVersion() . ' ' .
                        $response->getStatusCode() . ' ' .
                        $response->getReasonPhrase() );
 
                foreach ( $response->getRawHeaderLines() as $line ) {
-                       $webResponse->header( $line );
+                       $this->webResponse->header( $line );
                }
 
                foreach ( $response->getCookies() as $cookie ) {
-                       $webResponse->setCookie(
+                       $this->webResponse->setCookie(
                                $cookie['name'],
                                $cookie['value'],
                                $cookie['expiry'],
diff --git a/tests/phpunit/includes/Rest/EntryPointTest.php b/tests/phpunit/includes/Rest/EntryPointTest.php
new file mode 100644 (file)
index 0000000..4f87a70
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use EmptyBagOStuff;
+use GuzzleHttp\Psr7\Uri;
+use GuzzleHttp\Psr7\Stream;
+use MediaWiki\Rest\Handler;
+use MediaWikiTestCase;
+use MediaWiki\Rest\EntryPoint;
+use MediaWiki\Rest\RequestData;
+use MediaWiki\Rest\ResponseFactory;
+use MediaWiki\Rest\Router;
+use WebResponse;
+
+/**
+ * @covers \MediaWiki\Rest\EntryPoint
+ * @covers \MediaWiki\Rest\Router
+ */
+class EntryPointTest extends MediaWikiTestCase {
+       private static $mockHandler;
+
+       private function createRouter() {
+               return new Router(
+                       [ __DIR__ . '/testRoutes.json' ],
+                       [],
+                       '/rest',
+                       new EmptyBagOStuff(),
+                       new ResponseFactory() );
+       }
+
+       private function createWebResponse() {
+               return $this->getMockBuilder( WebResponse::class )
+                       ->setMethods( [ 'header' ] )
+                       ->getMock();
+       }
+
+       public static function mockHandlerHeader() {
+               return new class extends Handler {
+                       public function execute() {
+                               $response = $this->getResponseFactory()->create();
+                               $response->setHeader( 'Foo', 'Bar' );
+                               return $response;
+                       }
+               };
+       }
+
+       public function testHeader() {
+               $webResponse = $this->createWebResponse();
+               $webResponse->expects( $this->any() )
+                       ->method( 'header' )
+                       ->withConsecutive(
+                               [ 'HTTP/1.1 200 OK', true, null ],
+                               [ 'Foo: Bar', true, null ]
+                       );
+
+               $entryPoint = new EntryPoint(
+                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] ),
+                       $webResponse,
+                       $this->createRouter() );
+               $entryPoint->execute();
+               $this->assertTrue( true );
+       }
+
+       public static function mockHandlerBodyRewind() {
+               return new class extends Handler {
+                       public function execute() {
+                               $response = $this->getResponseFactory()->create();
+                               $stream = new Stream( fopen( 'php://memory', 'w+' ) );
+                               $stream->write( 'hello' );
+                               $response->setBody( $stream );
+                               return $response;
+                       }
+               };
+       }
+
+       /**
+        * Make sure EntryPoint rewinds a seekable body stream before reading.
+        */
+       public function testBodyRewind() {
+               $entryPoint = new EntryPoint(
+                       new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] ),
+                       $this->createWebResponse(),
+                       $this->createRouter() );
+               ob_start();
+               $entryPoint->execute();
+               $this->assertSame( 'hello', ob_get_clean() );
+       }
+
+}
index 9719f9e..afbaafb 100644 (file)
@@ -49,7 +49,7 @@ class HelloHandlerTest extends MediaWikiTestCase {
        /** @dataProvider provideTestViaRouter */
        public function testViaRouter( $requestInfo, $responseInfo ) {
                $router = new Router(
-                       [ __DIR__ . '/testRoutes.json' ],
+                       [ __DIR__ . '/../testRoutes.json' ],
                        [],
                        '/rest',
                        new EmptyBagOStuff(),
diff --git a/tests/phpunit/includes/Rest/Handler/testRoutes.json b/tests/phpunit/includes/Rest/Handler/testRoutes.json
deleted file mode 100644 (file)
index 6b440f7..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-[
-       {
-               "path": "/user/{name}/hello",
-               "class": "MediaWiki\\Rest\\Handler\\HelloHandler"
-       }
-]
diff --git a/tests/phpunit/includes/Rest/testRoutes.json b/tests/phpunit/includes/Rest/testRoutes.json
new file mode 100644 (file)
index 0000000..7e43bb0
--- /dev/null
@@ -0,0 +1,14 @@
+[
+       {
+               "path": "/user/{name}/hello",
+               "class": "MediaWiki\\Rest\\Handler\\HelloHandler"
+       },
+       {
+               "path": "/mock/EntryPoint/header",
+               "factory": "MediaWiki\\Tests\\Rest\\EntryPointTest::mockHandlerHeader"
+       },
+       {
+               "path": "/mock/EntryPoint/bodyRewind",
+               "factory": "MediaWiki\\Tests\\Rest\\EntryPointTest::mockHandlerBodyRewind"
+       }
+]