From 9f6fcceee9546ffec00bc1d81df7710a0e32cdd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Tue, 6 Dec 2022 08:51:31 +0100 Subject: [PATCH] IBX-4123: [Backport] Added `/languages` and `/languages/{code}` endpoints (cherry picked from commit 674e612e176bc85556513a716556d643f81d722d) --- src/bundle/Resources/config/routing.yml | 12 ++ src/bundle/Resources/config/services.yml | 4 + .../config/value_object_visitors.yml | 5 + src/lib/Server/Controller/Language.php | 41 ++++++ .../Output/ValueObjectVisitor/Language.php | 13 +- .../ValueObjectVisitor/LanguageList.php | 35 +++++ src/lib/Server/Values/LanguageList.php | 25 ++++ .../Functional/JsonSchema/Language.json | 38 ++++++ .../Functional/JsonSchema/LanguageList.json | 56 ++++++++ tests/bundle/Functional/LanguageTest.php | 64 +++++++++ .../Functional/ResourceAssertionsTrait.php | 123 ++++++++++++++++++ .../bundle/Functional/_snapshot/Language.json | 9 ++ .../bundle/Functional/_snapshot/Language.xml | 6 + .../Functional/_snapshot/LanguageList.json | 21 +++ .../Functional/_snapshot/LanguageList.xml | 13 ++ 15 files changed, 462 insertions(+), 3 deletions(-) create mode 100644 src/lib/Server/Controller/Language.php create mode 100644 src/lib/Server/Output/ValueObjectVisitor/LanguageList.php create mode 100644 src/lib/Server/Values/LanguageList.php create mode 100644 tests/bundle/Functional/JsonSchema/Language.json create mode 100644 tests/bundle/Functional/JsonSchema/LanguageList.json create mode 100644 tests/bundle/Functional/LanguageTest.php create mode 100644 tests/bundle/Functional/ResourceAssertionsTrait.php create mode 100644 tests/bundle/Functional/_snapshot/Language.json create mode 100644 tests/bundle/Functional/_snapshot/Language.xml create mode 100644 tests/bundle/Functional/_snapshot/LanguageList.json create mode 100644 tests/bundle/Functional/_snapshot/LanguageList.xml diff --git a/src/bundle/Resources/config/routing.yml b/src/bundle/Resources/config/routing.yml index ba95b558..2663e920 100644 --- a/src/bundle/Resources/config/routing.yml +++ b/src/bundle/Resources/config/routing.yml @@ -383,6 +383,18 @@ ezpublish_rest_setObjectStatesForContent: contentId: \d+ +# Languages + +ibexa.rest.languages.list: + path: /languages + methods: [GET] + controller: Ibexa\Rest\Server\Controller\Language::listLanguages + +ibexa.rest.languages.view: + path: /languages/{languageCode} + methods: [GET] + controller: Ibexa\Rest\Server\Controller\Language::loadLanguage + # Locations diff --git a/src/bundle/Resources/config/services.yml b/src/bundle/Resources/config/services.yml index 4eede10d..fc814a1d 100644 --- a/src/bundle/Resources/config/services.yml +++ b/src/bundle/Resources/config/services.yml @@ -177,6 +177,10 @@ services: - "@ezpublish.api.service.location" tags: [controller.service_arguments] + Ibexa\Rest\Server\Controller\Language: + autowire: true + tags: [ controller.service_arguments ] + ezpublish_rest.controller.location: class: "%ezpublish_rest.controller.location.class%" parent: ezpublish_rest.controller.base diff --git a/src/bundle/Resources/config/value_object_visitors.yml b/src/bundle/Resources/config/value_object_visitors.yml index 1273d150..4c76b2e0 100644 --- a/src/bundle/Resources/config/value_object_visitors.yml +++ b/src/bundle/Resources/config/value_object_visitors.yml @@ -206,6 +206,11 @@ services: - { name: ezpublish_rest.output.value_object_visitor, type: Symfony\Component\HttpKernel\Exception\HttpException } # Language + Ibexa\Rest\Server\Output\ValueObjectVisitor\LanguageList: + parent: Ibexa\Contracts\Rest\Output\ValueObjectVisitor + tags: + - { name: ibexa.rest.output.value_object.visitor, type: Ibexa\Rest\Server\Values\LanguageList } + ezpublish_rest.output.value_object_visitor.Language: parent: ezpublish_rest.output.value_object_visitor.base class: EzSystems\EzPlatformRest\Server\Output\ValueObjectVisitor\Language diff --git a/src/lib/Server/Controller/Language.php b/src/lib/Server/Controller/Language.php new file mode 100644 index 00000000..9c005c80 --- /dev/null +++ b/src/lib/Server/Controller/Language.php @@ -0,0 +1,41 @@ +languageService = $languageService; + } + + public function listLanguages(): LanguageList + { + $languages = $this->languageService->loadLanguages(); + + if ($languages instanceof Traversable) { + $languages = iterator_to_array($languages); + } + + return new LanguageList($languages); + } + + public function loadLanguage(string $languageCode): ApiLanguage + { + return $this->languageService->loadLanguage($languageCode); + } +} diff --git a/src/lib/Server/Output/ValueObjectVisitor/Language.php b/src/lib/Server/Output/ValueObjectVisitor/Language.php index 8e05eee2..69272aae 100644 --- a/src/lib/Server/Output/ValueObjectVisitor/Language.php +++ b/src/lib/Server/Output/ValueObjectVisitor/Language.php @@ -28,8 +28,15 @@ public function visit(Visitor $visitor, Generator $generator, $data): void private function visitLanguageAttributes(Visitor $visitor, Generator $generator, LanguageValue $language): void { - $generator->valueElement('languageId', $language->id); - $generator->valueElement('languageCode', $language->languageCode); - $generator->valueElement('name', $language->name); + $generator->attribute( + 'href', + $this->router->generate( + 'ibexa.rest.languages.view', + ['languageCode' => $language->getLanguageCode()], + ), + ); + $generator->valueElement('languageId', $language->getId()); + $generator->valueElement('languageCode', $language->getLanguageCode()); + $generator->valueElement('name', $language->getName()); } } diff --git a/src/lib/Server/Output/ValueObjectVisitor/LanguageList.php b/src/lib/Server/Output/ValueObjectVisitor/LanguageList.php new file mode 100644 index 00000000..c9c82d4b --- /dev/null +++ b/src/lib/Server/Output/ValueObjectVisitor/LanguageList.php @@ -0,0 +1,35 @@ +startObjectElement('LanguageList'); + $visitor->setHeader('Content-Type', $generator->getMediaType('LanguageList')); + + $generator->attribute('href', $this->router->generate('ibexa.rest.languages.list')); + + $generator->startList('Language'); + foreach ($data->languages as $language) { + $visitor->visitValueObject($language); + } + $generator->endList('Language'); + + $generator->endObjectElement('LanguageList'); + } +} diff --git a/src/lib/Server/Values/LanguageList.php b/src/lib/Server/Values/LanguageList.php new file mode 100644 index 00000000..b8ff3ac3 --- /dev/null +++ b/src/lib/Server/Values/LanguageList.php @@ -0,0 +1,25 @@ + $languages + */ + public function __construct(array $languages) + { + $this->languages = $languages; + } +} diff --git a/tests/bundle/Functional/JsonSchema/Language.json b/tests/bundle/Functional/JsonSchema/Language.json new file mode 100644 index 00000000..427ed753 --- /dev/null +++ b/tests/bundle/Functional/JsonSchema/Language.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "Language": { + "type": "object", + "properties": { + "_media-type": { + "type": "string" + }, + "_href": { + "type": "string" + }, + "languageId": { + "type": "integer" + }, + "languageCode": { + "type": "string", + "minLength": 1, + "pattern": "^[[:alnum:]_]+" + }, + "name": { + "type": "string" + } + }, + "required": [ + "_media-type", + "_href", + "languageId", + "languageCode", + "name" + ] + } + }, + "required": [ + "Language" + ] +} diff --git a/tests/bundle/Functional/JsonSchema/LanguageList.json b/tests/bundle/Functional/JsonSchema/LanguageList.json new file mode 100644 index 00000000..c26ce25d --- /dev/null +++ b/tests/bundle/Functional/JsonSchema/LanguageList.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "LanguageList": { + "type": "object", + "properties": { + "_media-type": { + "type": "string" + }, + "_href": { + "type": "string" + }, + "Language": { + "type":"array", + "items": { + "properties": { + "_media-type": { + "type": "string" + }, + "_href": { + "type": "string" + }, + "languageId": { + "type": "integer" + }, + "languageCode": { + "type": "string", + "minLength": 1, + "pattern": "^[[:alnum:]_]+" + }, + "name": { + "type": "string" + } + }, + "required": [ + "_media-type", + "_href", + "languageId", + "languageCode", + "name" + ] + } + } + }, + "required": [ + "_media-type", + "_href", + "Language" + ] + } + }, + "required": [ + "LanguageList" + ] +} diff --git a/tests/bundle/Functional/LanguageTest.php b/tests/bundle/Functional/LanguageTest.php new file mode 100644 index 00000000..f56f1e33 --- /dev/null +++ b/tests/bundle/Functional/LanguageTest.php @@ -0,0 +1,64 @@ +createHttpRequest('GET', '/api/ibexa/v2/languages', '', 'LanguageList+json'); + $response = $this->sendHttpRequest($request); + + self::assertHttpResponseCodeEquals($response, 200); + $content = $response->getBody()->getContents(); + self::assertJson($content); + + self::assertJsonResponseIsValid($content, 'LanguageList'); + self::assertResponseMatchesJsonSnapshot($content, self::SNAPSHOT_DIR . '/LanguageList.json'); + } + + public function testLanguageListXml(): void + { + $request = $this->createHttpRequest('GET', '/api/ibexa/v2/languages'); + $response = $this->sendHttpRequest($request); + + self::assertHttpResponseCodeEquals($response, 200); + $content = $response->getBody()->getContents(); + self::assertResponseMatchesXmlSnapshot($content, self::SNAPSHOT_DIR . '/LanguageList.xml'); + } + + public function testLanguageViewJson(): void + { + $request = $this->createHttpRequest('GET', '/api/ibexa/v2/languages/eng-GB', '', 'LanguageList+json'); + $response = $this->sendHttpRequest($request); + + self::assertHttpResponseCodeEquals($response, 200); + $content = $response->getBody()->getContents(); + self::assertJson($content); + + self::assertJsonResponseIsValid($content, 'Language'); + self::assertResponseMatchesJsonSnapshot($content, self::SNAPSHOT_DIR . '/Language.json'); + } + + public function testLanguageViewXml(): void + { + $request = $this->createHttpRequest('GET', '/api/ibexa/v2/languages/eng-GB'); + $response = $this->sendHttpRequest($request); + + self::assertHttpResponseCodeEquals($response, 200); + $content = $response->getBody()->getContents(); + self::assertResponseMatchesXmlSnapshot($content, self::SNAPSHOT_DIR . '/Language.xml'); + } +} diff --git a/tests/bundle/Functional/ResourceAssertionsTrait.php b/tests/bundle/Functional/ResourceAssertionsTrait.php new file mode 100644 index 00000000..8e55183e --- /dev/null +++ b/tests/bundle/Functional/ResourceAssertionsTrait.php @@ -0,0 +1,123 @@ + 'file://' . self::getSchemaFileLocation($resource), + ]; + + $validator->validate($decodedData, $schemaReference); + + self::assertTrue($validator->isValid(), self::convertErrorsToString($validator, $data)); + } + + private static function convertErrorsToString(Validator $validator, string $data): string + { + $errorMessage = ''; + foreach ($validator->getErrors() as $error) { + $errorMessage .= sprintf( + "property: [%s], constraint: %s, error: %s\n", + $error['property'], + $error['constraint'], + $error['message'] + ); + } + + $errorMessage .= "\n\nFor data:\n\n" . $data; + + return $errorMessage; + } + + private static function getSchemaFileLocation(string $resource): string + { + return __DIR__ . '/JsonSchema/' . $resource . '.json'; + } + + private static function checkSnapshotFileExistence(string $file, string $content): void + { + if (file_exists($file)) { + return; + } + + if ($_ENV['IBEXA_REST_GENERATE_SNAPSHOTS'] ?? false) { + file_put_contents($file, rtrim($content, "\n") . "\n"); + + return; + } + + self::fail(sprintf( + 'File %s does not exist. If it\'s a new REST route, add environment variable "%s" to phpunit.xml ' + . '(or environment) set to truthy value to enable snapshot generation.', + $file, + 'IBEXA_REST_GENERATE_SNAPSHOTS', + )); + } + + private static function getDefaultSnapshotFileLocation(?string $type): string + { + $classInfo = new \ReflectionClass(static::class); + $class = substr(static::class, strrpos(static::class, '\\') + 1); + $classFilename = $classInfo->getFileName(); + self::assertNotFalse($classFilename); + + return dirname($classFilename) . '/_snapshot/' . $class . '.' . ($type ?? 'log'); + } +} diff --git a/tests/bundle/Functional/_snapshot/Language.json b/tests/bundle/Functional/_snapshot/Language.json new file mode 100644 index 00000000..e82ba70f --- /dev/null +++ b/tests/bundle/Functional/_snapshot/Language.json @@ -0,0 +1,9 @@ +{ + "Language": { + "_media-type": "application\/vnd.ibexa.api.Language+json", + "_href": "\/api\/ibexa\/v2\/languages\/eng-GB", + "languageId": 2, + "languageCode": "eng-GB", + "name": "English (United Kingdom)" + } +} diff --git a/tests/bundle/Functional/_snapshot/Language.xml b/tests/bundle/Functional/_snapshot/Language.xml new file mode 100644 index 00000000..43519f1c --- /dev/null +++ b/tests/bundle/Functional/_snapshot/Language.xml @@ -0,0 +1,6 @@ + + + 2 + eng-GB + English (United Kingdom) + diff --git a/tests/bundle/Functional/_snapshot/LanguageList.json b/tests/bundle/Functional/_snapshot/LanguageList.json new file mode 100644 index 00000000..b9eb5383 --- /dev/null +++ b/tests/bundle/Functional/_snapshot/LanguageList.json @@ -0,0 +1,21 @@ +{ + "LanguageList": { + "_media-type": "application\/vnd.ibexa.api.LanguageList+json", + "_href": "\/api\/ibexa\/v2\/languages", + "Language": [ + { + "_media-type": "application\/vnd.ibexa.api.Language+json", + "_href": "\/api\/ibexa\/v2\/languages\/eng-GB", + "languageId": 2, + "languageCode": "eng-GB", + "name": "English (United Kingdom)" + }, { + "_href": "/api/ibexa/v2/languages/pol-PL", + "_media-type": "application/vnd.ibexa.api.Language+json", + "languageCode": "pol-PL", + "languageId": 4, + "name": "Polish (polski)" + } + ] + } +} diff --git a/tests/bundle/Functional/_snapshot/LanguageList.xml b/tests/bundle/Functional/_snapshot/LanguageList.xml new file mode 100644 index 00000000..f61cfa72 --- /dev/null +++ b/tests/bundle/Functional/_snapshot/LanguageList.xml @@ -0,0 +1,13 @@ + + + + 2 + eng-GB + English (United Kingdom) + + + 4 + pol-PL + Polish (polski) + +