diff --git a/config/routes.php b/config/routes.php index 7e188818..8637f8f2 100644 --- a/config/routes.php +++ b/config/routes.php @@ -111,24 +111,24 @@ $route->addGroup( $route->addGroup( '/api', function (RouteCollector $route): void { - $route->get('/', 'ApiController@index'); + $route->get('', 'Api\IndexController@index'); $route->addGroup( '/v0-beta', function (RouteCollector $route): void { - $route->addRoute(['OPTIONS'], '[/{resource:.+}]', 'ApiController@options'); - $route->get('/', 'ApiController@indexV0'); + $route->addRoute(['OPTIONS'], '[/{resource:.+}]', 'Api\IndexController@options'); + $route->get('', 'Api\IndexController@indexV0'); - $route->get('/news', 'ApiController@news'); + $route->get('/news', 'Api\NewsController@index'); $route->addRoute( ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'], - '[/{resource:.+}]', - 'ApiController@notImplemented' + '/[{resource:.+}]', + 'Api\IndexController@notImplemented' ); } ); - $route->get('[/{resource:.+}]', 'ApiController@notImplemented'); + $route->get('/[{resource:.+}]', 'Api\IndexController@notImplemented'); } ); diff --git a/src/Controllers/Api/ApiController.php b/src/Controllers/Api/ApiController.php new file mode 100644 index 00000000..7e4097d5 --- /dev/null +++ b/src/Controllers/Api/ApiController.php @@ -0,0 +1,23 @@ +response = $this->response + ->withHeader('content-type', 'application/json') + // Using * here to "skip" all other headers on browser requests + ->withHeader('access-control-allow-origin', '*'); + } +} diff --git a/src/Controllers/ApiController.php b/src/Controllers/Api/IndexController.php similarity index 53% rename from src/Controllers/ApiController.php rename to src/Controllers/Api/IndexController.php index 4ed54910..61a1b7ad 100644 --- a/src/Controllers/ApiController.php +++ b/src/Controllers/Api/IndexController.php @@ -2,24 +2,17 @@ declare(strict_types=1); -namespace Engelsystem\Controllers; +namespace Engelsystem\Controllers\Api; use Engelsystem\Http\Response; -use Engelsystem\Models\News; -class ApiController extends BaseController +class IndexController extends ApiController { public array $permissions = [ - 'api', + 'index' => 'api', + 'indexV0' => 'api', ]; - public function __construct(protected Response $response) - { - $this->response = $this->response - ->withHeader('content-type', 'application/json') - ->withHeader('Access-Control-Allow-Origin', '*'); - } - public function index(): Response { return $this->response @@ -44,10 +37,11 @@ class ApiController extends BaseController public function options(): Response { + // Respond to browser preflight options requests return $this->response ->setStatusCode(204) - ->withHeader('Allow', 'OPTIONS, HEAD, GET') - ->withHeader('Access-Control-Allow-Headers', 'Authorization'); + ->withHeader('allow', 'OPTIONS, HEAD, GET') + ->withHeader('access-control-allow-headers', 'Authorization'); } public function notImplemented(): Response @@ -56,16 +50,4 @@ class ApiController extends BaseController ->setStatusCode(501) ->withContent(json_encode(['error' => 'Not implemented'])); } - - public function news(): Response - { - $news = News::query() - ->orderByDesc('updated_at') - ->orderByDesc('created_at') - ->get(['id', 'title', 'text', 'is_meeting', 'is_pinned', 'is_highlighted', 'created_at', 'updated_at']); - - $data = ['data' => $news]; - return $this->response - ->withContent(json_encode($data)); - } } diff --git a/src/Controllers/Api/NewsController.php b/src/Controllers/Api/NewsController.php new file mode 100644 index 00000000..983d6bea --- /dev/null +++ b/src/Controllers/Api/NewsController.php @@ -0,0 +1,23 @@ +orderByDesc('updated_at') + ->orderByDesc('created_at') + ->get(['id', 'title', 'text', 'is_meeting', 'is_pinned', 'is_highlighted', 'created_at', 'updated_at']); + + $data = ['data' => $news]; + return $this->response + ->withContent(json_encode($data)); + } +} diff --git a/tests/Unit/Controllers/Api/ApiBaseControllerTest.php b/tests/Unit/Controllers/Api/ApiBaseControllerTest.php new file mode 100644 index 00000000..9989e8c7 --- /dev/null +++ b/tests/Unit/Controllers/Api/ApiBaseControllerTest.php @@ -0,0 +1,33 @@ +validator->validate($operation, $response); + } + + public function setUp(): void + { + parent::setUp(); + $this->initDatabase(); + + $openApiDefinition = $this->app->get('path.resources.api') . '/openapi.yml'; + $this->validator = (new OpenApiValidatorBuilder()) + ->fromYamlFile($openApiDefinition) + ->getResponseValidator(); + } +} diff --git a/tests/Unit/Controllers/Api/ApiControllerTest.php b/tests/Unit/Controllers/Api/ApiControllerTest.php new file mode 100644 index 00000000..296d794c --- /dev/null +++ b/tests/Unit/Controllers/Api/ApiControllerTest.php @@ -0,0 +1,32 @@ +response; + } + }; + + $response = $controller->getResponse(); + + $this->assertEquals(['application/json'], $response->getHeader('content-type')); + $this->assertJson($response->getContent()); + + $this->assertEquals(['*'], $response->getHeader('access-control-allow-origin')); + $this->assertJson($response->getContent()); + } +} diff --git a/tests/Unit/Controllers/Api/IndexControllerTest.php b/tests/Unit/Controllers/Api/IndexControllerTest.php new file mode 100644 index 00000000..d4749467 --- /dev/null +++ b/tests/Unit/Controllers/Api/IndexControllerTest.php @@ -0,0 +1,71 @@ +index(); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(['application/json'], $response->getHeader('content-type')); + $this->assertEquals(['*'], $response->getHeader('access-control-allow-origin')); + $this->assertJson($response->getContent()); + + $data = json_decode($response->getContent(), true); + $this->assertArrayHasKey('versions', $data); + } + + /** + * @covers \Engelsystem\Controllers\Api\IndexController::indexV0 + */ + public function testIndexV0(): void + { + $controller = new IndexController(new Response()); + $response = $controller->indexV0(); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertJson($response->getContent()); + + $data = json_decode($response->getContent(), true); + $this->assertArrayHasKey('version', $data); + $this->assertArrayHasKey('paths', $data); + } + + /** + * @covers \Engelsystem\Controllers\Api\IndexController::options + */ + public function testOptions(): void + { + $controller = new IndexController(new Response()); + $response = $controller->options(); + + $this->assertEquals(204, $response->getStatusCode()); + $this->assertNotEmpty($response->getHeader('allow')); + $this->assertNotEmpty($response->getHeader('access-control-allow-headers')); + } + + /** + * @covers \Engelsystem\Controllers\Api\IndexController::notImplemented + */ + public function testNotImplemented(): void + { + $controller = new IndexController(new Response()); + $response = $controller->notImplemented(); + + $this->assertEquals(501, $response->getStatusCode()); + $this->assertEquals(['application/json'], $response->getHeader('content-type')); + $this->assertJson($response->getContent()); + } +} diff --git a/tests/Unit/Controllers/Api/NewsControllerTest.php b/tests/Unit/Controllers/Api/NewsControllerTest.php new file mode 100644 index 00000000..feba4d54 --- /dev/null +++ b/tests/Unit/Controllers/Api/NewsControllerTest.php @@ -0,0 +1,32 @@ +create(); + + $controller = new NewsController(new Response()); + + $response = $controller->index(); + $this->validateApiResponse('/news', 'get', $response); + + $this->assertEquals(['application/json'], $response->getHeader('content-type')); + $this->assertJson($response->getContent()); + + $data = json_decode($response->getContent(), true); + $this->assertArrayHasKey('data', $data); + $this->assertCount(3, $data['data']); + } +} diff --git a/tests/Unit/Controllers/ApiControllerTest.php b/tests/Unit/Controllers/ApiControllerTest.php deleted file mode 100644 index 12dce380..00000000 --- a/tests/Unit/Controllers/ApiControllerTest.php +++ /dev/null @@ -1,114 +0,0 @@ -index(); - - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals(['application/json'], $response->getHeader('content-type')); - $this->assertEquals(['*'], $response->getHeader('Access-Control-Allow-Origin')); - $this->assertJson($response->getContent()); - - $data = json_decode($response->getContent(), true); - $this->assertArrayHasKey('versions', $data); - } - - /** - * @covers \Engelsystem\Controllers\ApiController::indexV0 - */ - public function testIndexV0(): void - { - $controller = new ApiController(new Response()); - - $response = $controller->indexV0(); - - $this->assertEquals(200, $response->getStatusCode()); - $this->assertJson($response->getContent()); - - $data = json_decode($response->getContent(), true); - $this->assertArrayHasKey('version', $data); - $this->assertArrayHasKey('paths', $data); - } - - /** - * @covers \Engelsystem\Controllers\ApiController::options - */ - public function testOptions(): void - { - $controller = new ApiController(new Response()); - - $response = $controller->options(); - - $this->assertEquals(204, $response->getStatusCode()); - $this->assertNotEmpty($response->getHeader('Allow')); - $this->assertNotEmpty($response->getHeader('Access-Control-Allow-Headers')); - } - - /** - * @covers \Engelsystem\Controllers\ApiController::notImplemented - */ - public function testNotImplemented(): void - { - $controller = new ApiController(new Response()); - - $response = $controller->notImplemented(); - - $this->assertEquals(501, $response->getStatusCode()); - $this->assertEquals(['application/json'], $response->getHeader('content-type')); - $this->assertJson($response->getContent()); - } - - /** - * @covers \Engelsystem\Controllers\ApiController::news - */ - public function testNews(): void - { - $this->initDatabase(); - News::factory(3)->create(); - - $controller = new ApiController(new Response()); - - $response = $controller->news(); - - $operation = new OpenApiAddress('/news', 'get'); - $this->validator->validate($operation, $response); - - $this->assertEquals(['application/json'], $response->getHeader('content-type')); - $this->assertJson($response->getContent()); - - $data = json_decode($response->getContent(), true); - $this->assertArrayHasKey('data', $data); - $this->assertCount(3, $data['data']); - } - - public function setUp(): void - { - parent::setUp(); - - $openApiDefinition = $this->app->get('path.resources.api') . '/openapi.yml'; - $this->validator = (new OpenApiValidatorBuilder()) - ->fromYamlFile($openApiDefinition) - ->getResponseValidator(); - } -}