From 7e6100c275313f831a7cbacf81e95a42145eb4ab Mon Sep 17 00:00:00 2001 From: Muhammad Yasser Jazirahly Date: Sun, 29 Sep 2024 22:27:07 +0400 Subject: [PATCH] REST: Add a new GetPropertyLabelWithFallback route This patch copies the existing GetPropertyLabel use case functionality. Adding language fallback will be in a separate step (patch). Bug: T375879 Change-Id: I25f1092177bdb42069663eff289b36822d22b858 --- repo/rest-api/routes.dev.json | 5 + repo/rest-api/specs/resources/index.json | 3 + .../label-with-fallback-for-property.json | 25 ++++ .../DeserializedRequestAdapter.php | 2 + ...zedGetPropertyLabelWithFallbackRequest.php | 12 ++ .../GetPropertyLabelWithFallback.php | 46 +++++++ .../GetPropertyLabelWithFallbackRequest.php | 30 +++++ .../GetPropertyLabelWithFallbackResponse.php | 34 +++++ .../GetPropertyLabelWithFallbackValidator.php | 17 +++ .../ValidatingRequestDeserializer.php | 2 + ...tPropertyLabelWithFallbackRouteHandler.php | 116 ++++++++++++++++++ repo/rest-api/src/RouteHandlers/openapi.json | 53 ++++++++ repo/rest-api/src/WbRestApi.ServiceWiring.php | 9 ++ repo/rest-api/src/WbRestApi.php | 6 + .../GetPropertyLabelWithFallbackTest.js | 80 ++++++++++++ .../mocha/helpers/RequestBuilderFactory.js | 10 ++ .../GetPropertyLabelWithFallbackTest.js | 65 ++++++++++ .../GetPropertyLabelWithFallbackTest.php | 108 ++++++++++++++++ 18 files changed, 623 insertions(+) create mode 100644 repo/rest-api/specs/resources/labels/label-with-fallback-for-property.json create mode 100644 repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/DeserializedGetPropertyLabelWithFallbackRequest.php create mode 100644 repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallback.php create mode 100644 repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackRequest.php create mode 100644 repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackResponse.php create mode 100644 repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackValidator.php create mode 100644 repo/rest-api/src/RouteHandlers/GetPropertyLabelWithFallbackRouteHandler.php create mode 100644 repo/rest-api/tests/mocha/api-testing/GetPropertyLabelWithFallbackTest.js create mode 100644 repo/rest-api/tests/mocha/openapi-validation/GetPropertyLabelWithFallbackTest.js create mode 100644 repo/rest-api/tests/phpunit/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackTest.php diff --git a/repo/rest-api/routes.dev.json b/repo/rest-api/routes.dev.json index afb6a024d0..0498a47bc1 100644 --- a/repo/rest-api/routes.dev.json +++ b/repo/rest-api/routes.dev.json @@ -3,5 +3,10 @@ "path": "/wikibase/v0/entities/items/{item_id}/labels_with_language_fallback/{language_code}", "method": "GET", "factory": "Wikibase\\Repo\\RestApi\\RouteHandlers\\GetItemLabelWithFallbackRouteHandler::factory" + }, + { + "path": "/wikibase/v0/entities/properties/{property_id}/labels_with_language_fallback/{language_code}", + "method": "GET", + "factory": "Wikibase\\Repo\\RestApi\\RouteHandlers\\GetPropertyLabelWithFallbackRouteHandler::factory" } ] diff --git a/repo/rest-api/specs/resources/index.json b/repo/rest-api/specs/resources/index.json index c149a22a51..80369fb8b7 100644 --- a/repo/rest-api/specs/resources/index.json +++ b/repo/rest-api/specs/resources/index.json @@ -100,6 +100,9 @@ "/entities/properties/{property_id}/labels/{language_code}": { "$ref": "./labels/label-in-language-for-property.json" }, + "/entities/properties/{property_id}/labels_with_language_fallback/{language_code}": { + "$ref": "./labels/label-with-fallback-for-property.json" + }, "/entities/items/{item_id}/aliases": { "$ref": "./aliases/list-for-item.json" }, diff --git a/repo/rest-api/specs/resources/labels/label-with-fallback-for-property.json b/repo/rest-api/specs/resources/labels/label-with-fallback-for-property.json new file mode 100644 index 0000000000..80f24ccbca --- /dev/null +++ b/repo/rest-api/specs/resources/labels/label-with-fallback-for-property.json @@ -0,0 +1,25 @@ +{ + "get": { + "operationId": "getPropertyLabelWithFallback", + "tags": [ "labels" ], + "summary": "[WIP] Retrieve a Property's label in a specific language, with language fallback", + "description": "This endpoint is currently in development and is not recommended for production use", + "parameters": [ + { "$ref": "../../global/parameters.json#/PropertyId" }, + { "$ref": "../../global/parameters.json#/LanguageCode" }, + { "$ref": "../../global/parameters.json#/IfNoneMatch" }, + { "$ref": "../../global/parameters.json#/IfModifiedSince" }, + { "$ref": "../../global/parameters.json#/IfMatch" }, + { "$ref": "../../global/parameters.json#/IfUnmodifiedSince" }, + { "$ref": "../../global/parameters.json#/Authorization" } + ], + "responses": { + "200": { "$ref": "../../global/responses.json#/Label" }, + "304": { "$ref": "../../global/responses.json#/NotModified" }, + "400": { "$ref": "../../global/responses.json#/InvalidTermByLanguageInput" }, + "404": { "$ref": "../../global/responses.json#/ResourceNotFound" }, + "412": { "$ref": "../../global/responses.json#/PreconditionFailedError" }, + "500": { "$ref": "../../global/responses.json#/UnexpectedError" } + } + } +} diff --git a/repo/rest-api/src/Application/UseCaseRequestValidation/DeserializedRequestAdapter.php b/repo/rest-api/src/Application/UseCaseRequestValidation/DeserializedRequestAdapter.php index bef016cae9..4eea0dfdd5 100644 --- a/repo/rest-api/src/Application/UseCaseRequestValidation/DeserializedRequestAdapter.php +++ b/repo/rest-api/src/Application/UseCaseRequestValidation/DeserializedRequestAdapter.php @@ -33,6 +33,7 @@ use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyDescriptions\DeserializedGetPropertyDescriptionsRequest; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyLabel\DeserializedGetPropertyLabelRequest; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyLabels\DeserializedGetPropertyLabelsRequest; +use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyLabelWithFallback\DeserializedGetPropertyLabelWithFallbackRequest; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyStatement\DeserializedGetPropertyStatementRequest; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyStatements\DeserializedGetPropertyStatementsRequest; use Wikibase\Repo\RestApi\Application\UseCases\GetSitelink\DeserializedGetSitelinkRequest; @@ -106,6 +107,7 @@ class DeserializedRequestAdapter implements DeserializedSetItemLabelRequest, DeserializedSetItemDescriptionRequest, DeserializedGetPropertyLabelRequest, + DeserializedGetPropertyLabelWithFallbackRequest, DeserializedGetPropertyDescriptionRequest, DeserializedSetPropertyDescriptionRequest, DeserializedPatchPropertyRequest, diff --git a/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/DeserializedGetPropertyLabelWithFallbackRequest.php b/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/DeserializedGetPropertyLabelWithFallbackRequest.php new file mode 100644 index 0000000000..88e9c2ec2d --- /dev/null +++ b/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/DeserializedGetPropertyLabelWithFallbackRequest.php @@ -0,0 +1,12 @@ +labelRetriever = $labelRetriever; + $this->getRevisionMetadata = $getRevisionMetadata; + $this->validator = $validator; + } + + /** + * @throws UseCaseError + */ + public function execute( GetPropertyLabelWithFallbackRequest $request ): GetPropertyLabelWithFallbackResponse { + $deserializedRequest = $this->validator->validateAndDeserialize( $request ); + $propertyId = $deserializedRequest->getPropertyId(); + $languageCode = $deserializedRequest->getLanguageCode(); + + [ $revisionId, $lastModified ] = $this->getRevisionMetadata->execute( $propertyId ); + + $label = $this->labelRetriever->getLabel( $propertyId, $languageCode ); + if ( !$label ) { + throw UseCaseError::newResourceNotFound( 'label' ); + } + + return new GetPropertyLabelWithFallbackResponse( $label, $lastModified, $revisionId ); + } + +} diff --git a/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackRequest.php b/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackRequest.php new file mode 100644 index 0000000000..d0d6f9f711 --- /dev/null +++ b/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackRequest.php @@ -0,0 +1,30 @@ +propertyId = $propertyId; + $this->languageCode = $languageCode; + } + + public function getPropertyId(): string { + return $this->propertyId; + } + + public function getLanguageCode(): string { + return $this->languageCode; + } + +} diff --git a/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackResponse.php b/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackResponse.php new file mode 100644 index 0000000000..17a55d4194 --- /dev/null +++ b/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackResponse.php @@ -0,0 +1,34 @@ +label = $label; + $this->lastModified = $lastModified; + $this->revisionId = $revisionId; + } + + public function getLabel(): Label { + return $this->label; + } + + public function getLastModified(): string { + return $this->lastModified; + } + + public function getRevisionId(): int { + return $this->revisionId; + } + +} diff --git a/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackValidator.php b/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackValidator.php new file mode 100644 index 0000000000..4a886d1f0a --- /dev/null +++ b/repo/rest-api/src/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackValidator.php @@ -0,0 +1,17 @@ +useCase = $useCase; + $this->middlewareHandler = $middlewareHandler; + $this->responseFactory = $responseFactory; + } + + public static function factory(): self { + $responseFactory = new ResponseFactory(); + return new self( + WbRestApi::getGetPropertyLabelWithFallback(), + new MiddlewareHandler( [ + WbRestApi::getUnexpectedErrorHandlerMiddleware(), + new UserAgentCheckMiddleware(), + new AuthenticationMiddleware(), + WbRestApi::getPreconditionMiddlewareFactory()->newPreconditionMiddleware( + fn( RequestInterface $request ): string => $request->getPathParam( self::PROPERTY_ID_PATH_PARAM ) + ), + ] ), + $responseFactory + ); + } + + /** + * @param mixed ...$args + */ + public function run( ...$args ): Response { + return $this->middlewareHandler->run( $this, [ $this, 'runUseCase' ], $args ); + } + + public function runUseCase( string $propertyId, string $languageCode ): Response { + try { + $useCaseResponse = $this->useCase->execute( new GetPropertyLabelWithFallbackRequest( $propertyId, $languageCode ) ); + return $this->newSuccessResponse( $useCaseResponse ); + } catch ( UseCaseError $e ) { + return $this->responseFactory->newErrorResponseFromException( $e ); + } + } + + public function getParamSettings(): array { + return [ + self::PROPERTY_ID_PATH_PARAM => [ + self::PARAM_SOURCE => 'path', + ParamValidator::PARAM_TYPE => 'string', + ParamValidator::PARAM_REQUIRED => true, + ], + self::LANGUAGE_CODE_PATH_PARAM => [ + self::PARAM_SOURCE => 'path', + ParamValidator::PARAM_TYPE => 'string', + ParamValidator::PARAM_REQUIRED => true, + ], + ]; + } + + private function newSuccessResponse( GetPropertyLabelWithFallbackResponse $useCaseResponse ): Response { + $httpResponse = $this->getResponseFactory()->create(); + $httpResponse->setHeader( 'Content-Type', 'application/json' ); + $httpResponse->setHeader( 'Last-Modified', wfTimestamp( TS_RFC2822, $useCaseResponse->getLastModified() ) ); + $httpResponse->setHeader( 'ETag', "\"{$useCaseResponse->getRevisionId()}\"" ); + $httpResponse->setBody( + new StringStream( json_encode( $useCaseResponse->getLabel()->getText() ) ) + ); + + return $httpResponse; + } + + /** + * @inheritDoc + */ + public function needsWriteAccess(): bool { + return false; + } + + /** + * Preconditions are checked via {@link PreconditionMiddleware} + */ + public function checkPreconditions(): ?ResponseInterface { + return null; + } + +} diff --git a/repo/rest-api/src/RouteHandlers/openapi.json b/repo/rest-api/src/RouteHandlers/openapi.json index f34671b026..5070924422 100644 --- a/repo/rest-api/src/RouteHandlers/openapi.json +++ b/repo/rest-api/src/RouteHandlers/openapi.json @@ -2555,6 +2555,59 @@ } } }, + "/entities/properties/{property_id}/labels_with_language_fallback/{language_code}": { + "get": { + "operationId": "getPropertyLabelWithFallback", + "tags": [ + "labels" + ], + "summary": "[WIP] Retrieve a Property's label in a specific language, with language fallback", + "description": "This endpoint is currently in development and is not recommended for production use", + "parameters": [ + { + "$ref": "#/components/parameters/PropertyId" + }, + { + "$ref": "#/components/parameters/LanguageCode" + }, + { + "$ref": "#/components/parameters/IfNoneMatch" + }, + { + "$ref": "#/components/parameters/IfModifiedSince" + }, + { + "$ref": "#/components/parameters/IfMatch" + }, + { + "$ref": "#/components/parameters/IfUnmodifiedSince" + }, + { + "$ref": "#/components/parameters/Authorization" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/Label" + }, + "304": { + "$ref": "#/components/responses/NotModified" + }, + "400": { + "$ref": "#/components/responses/InvalidTermByLanguageInput" + }, + "404": { + "$ref": "#/components/responses/ResourceNotFound" + }, + "412": { + "$ref": "#/components/responses/PreconditionFailedError" + }, + "500": { + "$ref": "#/components/responses/UnexpectedError" + } + } + } + }, "/entities/items/{item_id}/aliases": { "get": { "operationId": "getItemAliases", diff --git a/repo/rest-api/src/WbRestApi.ServiceWiring.php b/repo/rest-api/src/WbRestApi.ServiceWiring.php index d40ebd3e84..8dd206ee7a 100644 --- a/repo/rest-api/src/WbRestApi.ServiceWiring.php +++ b/repo/rest-api/src/WbRestApi.ServiceWiring.php @@ -83,6 +83,7 @@ use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyDescriptions\GetPropertyDescriptions; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyLabel\GetPropertyLabel; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyLabels\GetPropertyLabels; +use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyLabelWithFallback\GetPropertyLabelWithFallback; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyStatement\GetPropertyStatement; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyStatements\GetPropertyStatements; use Wikibase\Repo\RestApi\Application\UseCases\GetSitelink\GetSitelink; @@ -686,6 +687,14 @@ function( MediaWikiServices $services ): ItemSerializationRequestValidatingDeser ); }, + 'WbRestApi.GetPropertyLabelWithFallback' => function( MediaWikiServices $services ): GetPropertyLabelWithFallback { + return new GetPropertyLabelWithFallback( + WbRestApi::getValidatingRequestDeserializer( $services ), + WbRestApi::getGetLatestPropertyRevisionMetadata( $services ), + WbRestApi::getTermLookupEntityTermsRetriever( $services ) + ); + }, + 'WbRestApi.GetPropertyStatement' => function( MediaWikiServices $services ): GetPropertyStatement { return new GetPropertyStatement( WbRestApi::getValidatingRequestDeserializer( $services ), diff --git a/repo/rest-api/src/WbRestApi.php b/repo/rest-api/src/WbRestApi.php index b0c0a7cd81..43fe7ce765 100644 --- a/repo/rest-api/src/WbRestApi.php +++ b/repo/rest-api/src/WbRestApi.php @@ -36,6 +36,7 @@ use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyDescriptions\GetPropertyDescriptions; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyLabel\GetPropertyLabel; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyLabels\GetPropertyLabels; +use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyLabelWithFallback\GetPropertyLabelWithFallback; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyStatement\GetPropertyStatement; use Wikibase\Repo\RestApi\Application\UseCases\GetPropertyStatements\GetPropertyStatements; use Wikibase\Repo\RestApi\Application\UseCases\GetSitelink\GetSitelink; @@ -425,6 +426,11 @@ public static function getGetPropertyLabel( ContainerInterface $services = null ->get( 'WbRestApi.GetPropertyLabel' ); } + public static function getGetPropertyLabelWithFallback( ContainerInterface $services = null ): GetPropertyLabelWithFallback { + return ( $services ?: MediaWikiServices::getInstance() ) + ->get( 'WbRestApi.GetPropertyLabelWithFallback' ); + } + public static function getGetPropertyLabels( ContainerInterface $services = null ): GetPropertyLabels { return ( $services ?: MediaWikiServices::getInstance() ) ->get( 'WbRestApi.GetPropertyLabels' ); diff --git a/repo/rest-api/tests/mocha/api-testing/GetPropertyLabelWithFallbackTest.js b/repo/rest-api/tests/mocha/api-testing/GetPropertyLabelWithFallbackTest.js new file mode 100644 index 0000000000..e73cf884c9 --- /dev/null +++ b/repo/rest-api/tests/mocha/api-testing/GetPropertyLabelWithFallbackTest.js @@ -0,0 +1,80 @@ +'use strict'; + +const { assert, utils } = require( 'api-testing' ); +const { expect } = require( '../helpers/chaiHelper' ); +const { createEntity, getLatestEditMetadata } = require( '../helpers/entityHelper' ); +const { newGetPropertyLabelWithFallbackRequestBuilder } = require( '../helpers/RequestBuilderFactory' ); +const { assertValidError } = require( '../helpers/responseValidator' ); + +describe( newGetPropertyLabelWithFallbackRequestBuilder().getRouteDescription(), () => { + let propertyId; + const propertyEnLabel = `en-label-${utils.uniq()}`; + + before( async () => { + const testProperty = await createEntity( 'property', { + labels: [ { language: 'en', value: propertyEnLabel } ], + datatype: 'string' + } ); + propertyId = testProperty.entity.id; + } ); + + it( 'can get a label of a property', async () => { + const response = await newGetPropertyLabelWithFallbackRequestBuilder( propertyId, 'en' ) + .assertValidRequest() + .makeRequest(); + + expect( response ).to.have.status( 200 ); + assert.strictEqual( response.body, propertyEnLabel ); + + const testPropertyCreationMetadata = await getLatestEditMetadata( propertyId ); + assert.strictEqual( response.header.etag, `"${testPropertyCreationMetadata.revid}"` ); + assert.strictEqual( response.header[ 'last-modified' ], testPropertyCreationMetadata.timestamp ); + } ); + + it( 'responds 404 if the property does not exist', async () => { + const nonExistentProperty = 'P99999999'; + const response = await newGetPropertyLabelWithFallbackRequestBuilder( nonExistentProperty, 'en' ) + .assertValidRequest() + .makeRequest(); + + assertValidError( response, 404, 'resource-not-found', { resource_type: 'property' } ); + assert.strictEqual( response.body.message, 'The requested resource does not exist' ); + } ); + + it( 'responds 404 if the label does not exist', async () => { + const languageCodeWithNoDefinedLabel = 'ko'; + const response = await newGetPropertyLabelWithFallbackRequestBuilder( propertyId, languageCodeWithNoDefinedLabel ) + .assertValidRequest() + .makeRequest(); + + assertValidError( response, 404, 'resource-not-found', { resource_type: 'label' } ); + assert.strictEqual( response.body.message, 'The requested resource does not exist' ); + } ); + + it( '400 - invalid property ID', async () => { + const response = await newGetPropertyLabelWithFallbackRequestBuilder( 'X123', 'en' ) + .assertInvalidRequest() + .makeRequest(); + + assertValidError( + response, + 400, + 'invalid-path-parameter', + { parameter: 'property_id' } + ); + } ); + + it( '400 - invalid language code', async () => { + const response = await newGetPropertyLabelWithFallbackRequestBuilder( propertyId, '1e' ) + .assertInvalidRequest() + .makeRequest(); + + assertValidError( + response, + 400, + 'invalid-path-parameter', + { parameter: 'language_code' } + ); + } ); + +} ); diff --git a/repo/rest-api/tests/mocha/helpers/RequestBuilderFactory.js b/repo/rest-api/tests/mocha/helpers/RequestBuilderFactory.js index 88129bcda9..1a18f88a31 100644 --- a/repo/rest-api/tests/mocha/helpers/RequestBuilderFactory.js +++ b/repo/rest-api/tests/mocha/helpers/RequestBuilderFactory.js @@ -185,6 +185,16 @@ module.exports = { .withPathParam( 'language_code', languageCode ); }, + newGetPropertyLabelWithFallbackRequestBuilder( propertyId, languageCode ) { + return new RequestBuilder() + .withRoute( + 'GET', + '/entities/properties/{property_id}/labels_with_language_fallback/{language_code}' + ) + .withPathParam( 'property_id', propertyId ) + .withPathParam( 'language_code', languageCode ); + }, + newSetSitelinkRequestBuilder( itemId, siteId, sitelink ) { return new RequestBuilder() .withRoute( 'PUT', '/entities/items/{item_id}/sitelinks/{site_id}' ) diff --git a/repo/rest-api/tests/mocha/openapi-validation/GetPropertyLabelWithFallbackTest.js b/repo/rest-api/tests/mocha/openapi-validation/GetPropertyLabelWithFallbackTest.js new file mode 100644 index 0000000000..b575c089de --- /dev/null +++ b/repo/rest-api/tests/mocha/openapi-validation/GetPropertyLabelWithFallbackTest.js @@ -0,0 +1,65 @@ +'use strict'; + +const { utils } = require( 'api-testing' ); +const { expect } = require( '../helpers/chaiHelper' ); +const { createEntity } = require( '../helpers/entityHelper' ); +const { newGetPropertyLabelWithFallbackRequestBuilder } = require( '../helpers/RequestBuilderFactory' ); + +describe( newGetPropertyLabelWithFallbackRequestBuilder().getRouteDescription(), () => { + + let propertyId; + let lastRevisionId; + const languageCode = 'en'; + + before( async () => { + const createPropertyResponse = await createEntity( 'property', { + labels: [ { language: languageCode, value: 'an-English-label-' + utils.uniq() } ], + datatype: 'string' + } ); + + propertyId = createPropertyResponse.entity.id; + lastRevisionId = createPropertyResponse.entity.lastrevid; + } ); + + it( '200 OK response is valid', async () => { + const response = await newGetPropertyLabelWithFallbackRequestBuilder( propertyId, languageCode ) + .makeRequest(); + + expect( response ).to.have.status( 200 ); + expect( response ).to.satisfyApiSpec; + } ); + + it( '304 Not Modified response is valid', async () => { + const response = await newGetPropertyLabelWithFallbackRequestBuilder( propertyId, languageCode ) + .withHeader( 'If-None-Match', `"${lastRevisionId}"` ) + .makeRequest(); + + expect( response ).to.have.status( 304 ); + expect( response ).to.satisfyApiSpec; + } ); + + it( '400 Bad Request response is valid for an invalid property ID', async () => { + const response = await newGetPropertyLabelWithFallbackRequestBuilder( 'X123', languageCode ) + .makeRequest(); + + expect( response ).to.have.status( 400 ); + expect( response ).to.satisfyApiSpec; + } ); + + it( '404 Not Found response is valid for a non-existing property', async () => { + const response = await newGetPropertyLabelWithFallbackRequestBuilder( 'P99999', languageCode ) + .makeRequest(); + + expect( response ).to.have.status( 404 ); + expect( response ).to.satisfyApiSpec; + } ); + + it( '404 Not Found response is valid if there is no label in the requested language', async () => { + const response = await newGetPropertyLabelWithFallbackRequestBuilder( propertyId, 'ko' ) + .makeRequest(); + + expect( response ).to.have.status( 404 ); + expect( response ).to.satisfyApiSpec; + } ); + +} ); diff --git a/repo/rest-api/tests/phpunit/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackTest.php b/repo/rest-api/tests/phpunit/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackTest.php new file mode 100644 index 0000000000..91bfde6545 --- /dev/null +++ b/repo/rest-api/tests/phpunit/Application/UseCases/GetPropertyLabelWithFallback/GetPropertyLabelWithFallbackTest.php @@ -0,0 +1,108 @@ +getRevisionMetadata = $this->createStub( GetLatestPropertyRevisionMetadata::class ); + $this->labelRetriever = $this->createStub( PropertyLabelRetriever::class ); + } + + public function testSuccess(): void { + $label = new Label( 'en', 'instance of' ); + $propertyId = new NumericPropertyId( 'P31' ); + $lastModified = '20230922070707'; + $revisionId = 432; + + $this->labelRetriever = $this->createMock( PropertyLabelRetriever::class ); + $this->labelRetriever->expects( $this->once() ) + ->method( 'getLabel' ) + ->with( $propertyId, 'en' ) + ->willReturn( $label ); + + $this->getRevisionMetadata = $this->createStub( GetLatestPropertyRevisionMetadata::class ); + $this->getRevisionMetadata->method( 'execute' )->willReturn( [ $revisionId, $lastModified ] ); + + $response = $this->newUseCase()->execute( + new GetPropertyLabelWithFallbackRequest( "$propertyId", 'en' ) + ); + + $this->assertEquals( + new GetPropertyLabelWithFallbackResponse( $label, $lastModified, $revisionId ), + $response + ); + } + + public function testGivenPropertyDoesNotExist_throws(): void { + $expectedException = $this->createStub( UseCaseError::class ); + $this->getRevisionMetadata = $this->createStub( GetLatestPropertyRevisionMetadata::class ); + $this->getRevisionMetadata->method( 'execute' )->willThrowException( $expectedException ); + + try { + $this->newUseCase()->execute( new GetPropertyLabelWithFallbackRequest( 'P999999', 'en' ) ); + $this->fail( 'expected exception was not thrown' ); + } catch ( UseCaseError $e ) { + $this->assertSame( $expectedException, $e ); + } + } + + public function testGivenLabelDoesNotExist_throws(): void { + $propertyId = 'P123'; + $languageCode = 'en'; + + $this->getRevisionMetadata = $this->createStub( GetLatestPropertyRevisionMetadata::class ); + $this->getRevisionMetadata->method( 'execute' )->willReturn( [ 123, '20230922070707' ] ); + + try { + $this->newUseCase()->execute( new GetPropertyLabelWithFallbackRequest( $propertyId, $languageCode ) ); + $this->fail( 'expected exception was not thrown' ); + } catch ( UseCaseError $e ) { + $this->assertEquals( + UseCaseError::newResourceNotFound( 'label' ), + $e + ); + } + } + + public function testGivenInvalidRequest_throws(): void { + try { + $this->newUseCase()->execute( new GetPropertyLabelWithFallbackRequest( 'X123', 'en' ) ); + $this->fail( 'expected exception was not thrown' ); + } catch ( UseCaseError $e ) { + $this->assertSame( UseCaseError::INVALID_PATH_PARAMETER, $e->getErrorCode() ); + } + } + + private function newUseCase(): GetPropertyLabelWithFallback { + return new GetPropertyLabelWithFallback( + new TestValidatingRequestDeserializer(), + $this->getRevisionMetadata, + $this->labelRetriever + ); + } + +}