diff --git a/src/JsonLdEmbeddingPagedCollectionFactory.php b/src/JsonLdEmbeddingPagedCollectionFactory.php new file mode 100644 index 0000000..b96de92 --- /dev/null +++ b/src/JsonLdEmbeddingPagedCollectionFactory.php @@ -0,0 +1,145 @@ +pagedCollectionFactory = $pagedCollectionFactory; + $this->httpClient = $httpClient; + $this->logger = $logger; + } + + /** + * @param PagedResultSet $pagedResultSet + * @param int $start + * @param int $limit + * @return PagedCollection + */ + public function fromPagedResultSet( + PagedResultSet $pagedResultSet, + $start, + $limit + ) { + $pagedCollection = $this->pagedCollectionFactory->fromPagedResultSet($pagedResultSet, $start, $limit); + $members = $pagedCollection->getMembers(); + + $promises = $this->getJsonLdRequestPromises($members); + + $jsonLdBodies = []; + + $results = \GuzzleHttp\Promise\settle($promises)->wait(); + + foreach ($results as $key => $result) { + switch ($result['state']) { + case Promise::FULFILLED: + /** @var ResponseInterface $response */ + $response = $result['value']; + $jsonLdBodies[] = json_decode($response->getBody()); + break; + + default: + $this->logClientException($result['reason']); + break; + } + } + + $merged = $this->mergeResults($members, $jsonLdBodies); + + return $pagedCollection->withMembers($merged); + } + + /** + * @param \stdClass[] $members + * @return PromiseInterface[] + */ + private function getJsonLdRequestPromises(array $members) + { + $promises = []; + + foreach ($members as $body) { + if (isset($body->{'@id'})) { + $promises[$body->{'@id'}] = $this->httpClient->requestAsync( + 'GET', + $body->{'@id'} + ); + } + } + + return $promises; + } + + /** + * @param array $originalResults + * @param array $jsonLdResults + * @return array + */ + private function mergeResults(array $originalResults, array $jsonLdResults) + { + $mergedResults = $originalResults; + + foreach ($jsonLdResults as $jsonLd) { + if (!isset($jsonLd->{'@id'})) { + continue; + } + + $mergedResults = array_map( + function ($result) use ($jsonLd) { + if (isset($result->{'@id'}) && $result->{'@id'} == $jsonLd->{'@id'}) { + return $jsonLd; + } else { + return $result; + } + }, + $mergedResults + ); + } + + return $mergedResults; + } + + /** + * @param ClientException $exception + */ + private function logClientException(ClientException $exception) + { + $url = (string) $exception->getRequest()->getUri(); + $code = (string) $exception->getResponse()->getStatusCode(); + $message = "Could not embed document from url {$url}, received error code {$code}."; + + $this->logger->error($message); + } +} diff --git a/src/OfferSearchController.php b/src/OfferSearchController.php index 219c67f..7421d74 100644 --- a/src/OfferSearchController.php +++ b/src/OfferSearchController.php @@ -7,6 +7,7 @@ use CultuurNet\UDB3\Label\ValueObjects\LabelName; use CultuurNet\UDB3\Language; use CultuurNet\UDB3\PriceInfo\Price; +use CultuurNet\UDB3\Search\Creator; use CultuurNet\UDB3\Search\DistanceFactoryInterface; use CultuurNet\UDB3\Search\GeoDistanceParameters; use CultuurNet\UDB3\Search\Offer\AudienceType; @@ -15,11 +16,14 @@ use CultuurNet\UDB3\Search\Offer\FacetName; use CultuurNet\UDB3\Search\Offer\OfferSearchParameters; use CultuurNet\UDB3\Search\Offer\OfferSearchServiceInterface; +use CultuurNet\UDB3\Search\Offer\SortBy; +use CultuurNet\UDB3\Search\Offer\Sorting; use CultuurNet\UDB3\Search\Offer\WorkflowStatus; use CultuurNet\UDB3\Search\Offer\TermId; use CultuurNet\UDB3\Search\Offer\TermLabel; use CultuurNet\UDB3\Search\QueryStringFactoryInterface; use CultuurNet\UDB3\Search\Region\RegionId; +use CultuurNet\UDB3\Search\SortOrder; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -91,7 +95,7 @@ public function __construct( PagedCollectionFactoryInterface $pagedCollectionFactory = null ) { if (is_null($pagedCollectionFactory)) { - $pagedCollectionFactory = new PagedCollectionFactory(); + $pagedCollectionFactory = new ResultSetMappingPagedCollectionFactory(); } $this->searchService = $searchService; @@ -112,11 +116,6 @@ public function search(Request $request) $start = (int) $request->query->get('start', 0); $limit = (int) $request->query->get('limit', 30); - // The embed option is returned as a string, and casting "false" to a - // boolean returns true, so we have to do some extra conversion. - $embedParameter = $request->query->get('embed', false); - $embed = $this->getStringAsBoolean($embedParameter); - if ($limit == 0) { $limit = 30; } @@ -177,11 +176,12 @@ public function search(Request $request) ); } - if (!empty($request->query->get('regionId'))) { - $parameters = $parameters->withRegion( - new RegionId($request->query->get('regionId')), + $regionIds = $this->getRegionIdsFromQuery($request, 'regions'); + if (!empty($regionIds)) { + $parameters = $parameters->withRegions( $this->regionIndexName, - $this->regionDocumentType + $this->regionDocumentType, + ...$regionIds ); } @@ -264,11 +264,14 @@ public function search(Request $request) ); } - $mediaObjectsToggle = $request->query->get('hasMediaObjects', null); + $mediaObjectsToggle = $this->castMixedToBool($request->query->get('hasMediaObjects', null)); if (!is_null($mediaObjectsToggle)) { - $parameters = $parameters->withMediaObjectsToggle( - $this->getStringAsBoolean($mediaObjectsToggle) - ); + $parameters = $parameters->withMediaObjectsToggle($mediaObjectsToggle); + } + + $uitpasToggle = $this->castMixedToBool($request->query->get('uitpas', null)); + if (!is_null($uitpasToggle)) { + $parameters = $parameters->withUitpasToggle($uitpasToggle); } if ($request->query->get('calendarType')) { @@ -347,13 +350,23 @@ public function search(Request $request) $parameters = $parameters->withFacets(...$facets); } + if ($request->query->get('creator')) { + $parameters = $parameters->withCreator( + new Creator($request->query->get('creator')) + ); + } + + $sorting = $this->getSortingFromQuery($request, 'sort'); + if (!empty($sorting)) { + $parameters = $parameters->withSorting(...$sorting); + } + $resultSet = $this->searchService->search($parameters); $pagedCollection = $this->pagedCollectionFactory->fromPagedResultSet( $resultSet, $start, - $limit, - $embed + $limit ); $jsonArray = $pagedCollection->jsonSerialize(); @@ -371,12 +384,16 @@ public function search(Request $request) } /** - * @param string $string - * @return bool + * @param mixed $mixed + * @return bool|null */ - private function getStringAsBoolean($string) + private function castMixedToBool($mixed) { - return filter_var($string, FILTER_VALIDATE_BOOLEAN); + if (is_null($mixed) || (is_string($mixed) && empty($mixed))) { + return null; + } + + return filter_var($mixed, FILTER_VALIDATE_BOOLEAN); } /** @@ -513,6 +530,63 @@ function ($value) { ); } + /** + * @param Request $request + * @param string $queryParameter + * @return RegionIds[] + */ + private function getRegionIdsFromQuery(Request $request, $queryParameter) + { + return $this->getArrayFromQueryParameters( + $request, + $queryParameter, + function ($value) { + return new RegionId($value); + } + ); + } + + /** + * @param Request $request + * @param string $queryParameter + * @return Sorting[] + * @throws \InvalidArgumentException + */ + private function getSortingFromQuery(Request $request, $queryParameter) + { + $sorting = $request->query->get($queryParameter, []); + + if (!is_array($sorting)) { + throw new \InvalidArgumentException('Invalid sorting syntax given.'); + } + + foreach ($sorting as $field => $order) { + if (is_int($field)) { + throw new \InvalidArgumentException('Sort field missing.'); + } + + if (empty($order)) { + throw new \InvalidArgumentException('Sort order missing.'); + } + + try { + $sortBy = SortBy::get($field); + } catch (\InvalidArgumentException $e) { + throw new \InvalidArgumentException("Invalid sort field '{$field}' given."); + } + + try { + $sortOrder = SortOrder::get($order); + } catch (\InvalidArgumentException $e) { + throw new \InvalidArgumentException("Invalid sort order '{$order}' given."); + } + + $sorting[$field] = new Sorting($sortBy, $sortOrder); + } + + return array_values($sorting); + } + /** * @param Request $request * @param string $queryParameter diff --git a/src/OrganizerSearchController.php b/src/OrganizerSearchController.php index 72f8785..0dbf686 100644 --- a/src/OrganizerSearchController.php +++ b/src/OrganizerSearchController.php @@ -32,7 +32,7 @@ public function __construct( PagedCollectionFactoryInterface $pagedCollectionFactory = null ) { if (is_null($pagedCollectionFactory)) { - $pagedCollectionFactory = new PagedCollectionFactory(); + $pagedCollectionFactory = new ResultSetMappingPagedCollectionFactory(); } $this->searchService = $searchService; @@ -78,8 +78,7 @@ public function search(Request $request) $pagedCollection = $this->pagedCollectionFactory->fromPagedResultSet( $resultSet, $start, - $limit, - $embed + $limit ); return (new JsonResponse($pagedCollection, 200, ['Content-Type' => 'application/ld+json'])) diff --git a/src/PagedCollectionFactory.php b/src/PagedCollectionFactory.php deleted file mode 100644 index 36e0988..0000000 --- a/src/PagedCollectionFactory.php +++ /dev/null @@ -1,63 +0,0 @@ -embeddingJsonDocumentTransformer = $embeddingJsonDocumentTransformer; - } - - /** - * @param PagedResultSet $pagedResultSet - * @param int $start - * @param int $limit - * @param bool $embed - * @return PagedCollection - */ - public function fromPagedResultSet( - PagedResultSet $pagedResultSet, - $start, - $limit, - $embed = false - ) { - $results = array_map( - function (JsonDocument $document) use ($embed) { - if ($embed) { - $document = $this->embeddingJsonDocumentTransformer->transform($document); - } - - return $document->getBody(); - }, - $pagedResultSet->getResults() - ); - - $pageNumber = (int) floor($start / $limit) + 1; - - return new PagedCollection( - $pageNumber, - $limit, - $results, - $pagedResultSet->getTotal()->toNative() - ); - } -} diff --git a/src/PagedCollectionFactoryInterface.php b/src/PagedCollectionFactoryInterface.php index e505bbc..8633da2 100644 --- a/src/PagedCollectionFactoryInterface.php +++ b/src/PagedCollectionFactoryInterface.php @@ -9,15 +9,13 @@ interface PagedCollectionFactoryInterface { /** * @param PagedResultSet $pagedResultSet - * @param int $pageNumber + * @param int $start * @param int $limit - * @param bool $embed * @return PagedCollection */ public function fromPagedResultSet( PagedResultSet $pagedResultSet, - $pageNumber, - $limit, - $embed = false + $start, + $limit ); } diff --git a/src/ResultSetMappingPagedCollectionFactory.php b/src/ResultSetMappingPagedCollectionFactory.php new file mode 100644 index 0000000..23bcc7f --- /dev/null +++ b/src/ResultSetMappingPagedCollectionFactory.php @@ -0,0 +1,38 @@ +getBody(); + }, + $pagedResultSet->getResults() + ); + + $pageNumber = (int) floor($start / $limit) + 1; + + return new PagedCollection( + $pageNumber, + $limit, + $results, + $pagedResultSet->getTotal()->toNative() + ); + } +} diff --git a/tests/JsonLdEmbeddingPagedCollectionFactoryTest.php b/tests/JsonLdEmbeddingPagedCollectionFactoryTest.php new file mode 100644 index 0000000..4af2231 --- /dev/null +++ b/tests/JsonLdEmbeddingPagedCollectionFactoryTest.php @@ -0,0 +1,209 @@ +jsonDocuments = array_map( + function ($id) { + return new JsonDocument($id, json_encode(['@id' => "http://mock/event/{$id}"])); + }, + $documentIds + ); + + $jsonDocumentWithoutUrl = new JsonDocument( + '6da8c68a-6b19-4692-abfa-092fde6eb7da', + json_encode(['has_no_@id' => 'should still be in the final paged collection']) + ); + + // Put the document without @id in the middle of the jsonDocuments. + array_splice($this->jsonDocuments, 2, 0, [$jsonDocumentWithoutUrl]); + + $this->start = 0; + $this->limit = 5; + $this->total = 20; + + $this->pagedResultSet = new PagedResultSet( + new Natural($this->total), + new Natural($this->limit), + $this->jsonDocuments + ); + + $this->logger = $this->createMock(LoggerInterface::class); + } + + /** + * @test + */ + public function it_embeds_all_documents_that_have_an_id_url_in_both_the_original_and_returned_document() + { + // Guzzle should return the responses in the correct order, but let's + // test with an incorrect order anyway. Also make one response not + // contain an @id. + $responseDocumentIds = [ + 'f127909c-9e2b-46b6-a4aa-0c19270437f8', + '4c8b6b38-d94d-495a-b4fe-6ae2f701a935', + null, + 'feaae8dc-6eaa-4076-ab3f-5bbbb735494b', + ]; + + $expectedResponses = array_map( + function ($id) { + $json = new \stdClass(); + + if ($id) { + $json->{'@id'} = "http://mock/event/{$id}"; + } + + // Random data that could be returned as part of the complete + // json-ld document. + $json->foo = 'bar'; + + return new Response(200, [], json_encode($json)); + }, + $responseDocumentIds + ); + + $expectedPagedCollection = new PagedCollection( + 1, + $this->limit, + [ + // Response did not contain an @id. + (object) [ + '@id' => 'http://mock/event/fe775848-7552-4450-8479-419f47ad0a9f', + ], + // Valid response. + (object) [ + '@id' => 'http://mock/event/4c8b6b38-d94d-495a-b4fe-6ae2f701a935', + 'foo' => 'bar', + ], + // Original document did not contain an @id. + (object) [ + 'has_no_@id' => 'should still be in the final paged collection' + ], + // Valid response. + (object) [ + '@id' => 'http://mock/event/feaae8dc-6eaa-4076-ab3f-5bbbb735494b', + 'foo' => 'bar', + ], + // Valid response. + (object) [ + '@id' => 'http://mock/event/f127909c-9e2b-46b6-a4aa-0c19270437f8', + 'foo' => 'bar', + ], + ], + $this->total + ); + + $mockClient = $this->createMockClient(new MockHandler($expectedResponses)); + + $factory = new JsonLdEmbeddingPagedCollectionFactory( + new ResultSetMappingPagedCollectionFactory(), + $mockClient, + $this->logger + ); + + $this->logger->expects($this->never()) + ->method('error'); + + $actualPagedCollection = $factory->fromPagedResultSet( + $this->pagedResultSet, + $this->start, + $this->limit + ); + + $this->assertEquals($expectedPagedCollection, $actualPagedCollection); + } + + /** + * @test + */ + public function it_throws_an_exception_when_at_least_one_request_failed() + { + $expectedResponses = [ + new Response(200, [], json_encode('{"@id":"http://mock/event/fe775848-7552-4450-8479-419f47ad0a9f"}')), + new Response(200, [], json_encode('{"@id":"http://mock/event/4c8b6b38-d94d-495a-b4fe-6ae2f701a935"}')), + new Response(404), + new Response(200, [], json_encode('{"@id":"http://mock/event/f127909c-9e2b-46b6-a4aa-0c19270437f8"}')), + ]; + + $mockClient = $this->createMockClient(new MockHandler($expectedResponses)); + + $failedUrl = "http://mock/event/feaae8dc-6eaa-4076-ab3f-5bbbb735494b"; + + $this->logger->expects($this->once()) + ->method('error') + ->with("Could not embed document from url {$failedUrl}, received error code 404."); + + $factory = new JsonLdEmbeddingPagedCollectionFactory( + new ResultSetMappingPagedCollectionFactory(), + $mockClient, + $this->logger + ); + + $factory->fromPagedResultSet( + $this->pagedResultSet, + $this->start, + $this->limit + ); + } + + /** + * @param MockHandler $mockHandler + * @return Client + */ + private function createMockClient(MockHandler $mockHandler) + { + $handler = HandlerStack::create($mockHandler); + return new Client(['handler' => $handler]); + } +} diff --git a/tests/OfferSearchControllerTest.php b/tests/OfferSearchControllerTest.php index 418cff0..15b2003 100644 --- a/tests/OfferSearchControllerTest.php +++ b/tests/OfferSearchControllerTest.php @@ -11,6 +11,7 @@ use CultuurNet\UDB3\Language; use CultuurNet\UDB3\PriceInfo\Price; use CultuurNet\UDB3\ReadModel\JsonDocument; +use CultuurNet\UDB3\Search\Creator; use CultuurNet\UDB3\Search\Facet\FacetFilter; use CultuurNet\UDB3\Search\Facet\FacetNode; use CultuurNet\UDB3\Search\GeoDistanceParameters; @@ -20,11 +21,14 @@ use CultuurNet\UDB3\Search\Offer\FacetName; use CultuurNet\UDB3\Search\Offer\OfferSearchParameters; use CultuurNet\UDB3\Search\Offer\OfferSearchServiceInterface; +use CultuurNet\UDB3\Search\Offer\SortBy; +use CultuurNet\UDB3\Search\Offer\Sorting; use CultuurNet\UDB3\Search\Offer\WorkflowStatus; use CultuurNet\UDB3\Search\Offer\TermId; use CultuurNet\UDB3\Search\Offer\TermLabel; use CultuurNet\UDB3\Search\PagedResultSet; use CultuurNet\UDB3\Search\Region\RegionId; +use CultuurNet\UDB3\Search\SortOrder; use CultuurNet\UDB3\ValueObject\MultilingualString; use Symfony\Component\HttpFoundation\Request; use ValueObjects\Geography\Country; @@ -109,7 +113,7 @@ public function it_returns_a_paged_collection_of_search_results_based_on_request 'availableFrom' => '2017-04-26T00:00:00+01:00', 'availableTo' => '2017-04-28T15:30:23+01:00', 'workflowStatus' => 'DRAFT', - 'regionId' => 'gem-leuven', + 'regions' => ['gem-leuven', 'prv-limburg'], 'coordinates' => '-40,70', 'distance' => '30km', 'postalCode' => 3000, @@ -136,10 +140,16 @@ public function it_returns_a_paged_collection_of_search_results_based_on_request 'termIds' => ['1.45.678.95', 'azYBznHY'], 'termLabels' => ['Jeugdhuis', 'Cultureel centrum'], 'locationTermIds' => ['1234', '5678'], + 'uitpas' => 'true', 'locationTermLabels' => ['foo1', 'bar1'], 'organizerTermIds' => ['9012', '3456'], 'organizerTermLabels' => ['foo2', 'bar2'], 'facets' => ['regions'], + 'creator' => 'Jane Doe', + 'sort' => [ + 'availableTo' => 'asc', + 'score' => 'desc', + ], ] ); @@ -177,10 +187,11 @@ public function it_returns_a_paged_collection_of_search_results_based_on_request ->withWorkflowStatus( new WorkflowStatus('DRAFT') ) - ->withRegion( - new RegionId('gem-leuven'), + ->withRegions( $this->regionIndexName, - $this->regionDocumentType + $this->regionDocumentType, + new RegionId('gem-leuven'), + new RegionId('prv-limburg') ) ->withGeoDistanceParameters( new GeoDistanceParameters( @@ -234,6 +245,9 @@ public function it_returns_a_paged_collection_of_search_results_based_on_request new TermLabel('foo1'), new TermLabel('bar1') ) + ->withUitpasToggle( + true + ) ->withLabels( new LabelName('foo'), new LabelName('bar') @@ -247,6 +261,19 @@ public function it_returns_a_paged_collection_of_search_results_based_on_request ->withFacets( FacetName::REGIONS() ) + ->withCreator( + new Creator('Jane Doe') + ) + ->withSorting( + new Sorting( + SortBy::AVAILABLE_TO(), + SortOrder::ASC() + ), + new Sorting( + SortBy::SCORE(), + SortOrder::DESC() + ) + ) ->withStart(new Natural(30)) ->withLimit(new Natural(10)); @@ -475,40 +502,29 @@ public function it_works_with_a_min_age_of_zero_and_or_a_max_age_of_zero() * @test * @dataProvider booleanStringDataProvider * - * @param mixed $embedParameter - * @param bool $expectedEmbedParameter + * @param string $stringValue + * @param bool|null $booleanValue */ - public function it_converts_the_embed_parameter_to_a_correct_boolean_and_passes_it_to_the_paged_collection_factory( - $embedParameter, - $expectedEmbedParameter + public function it_converts_the_media_objects_toggle_parameter_to_a_correct_boolean( + $stringValue, + $booleanValue ) { - $pagedCollectionFactory = $this->createMock(PagedCollectionFactory::class); - - $controller = new OfferSearchController( - $this->searchService, - $this->regionIndexName, - $this->regionDocumentType, - $this->queryStringFactory, - $this->distanceFactory, - $this->facetTreeNormalizer, - $pagedCollectionFactory - ); - $request = Request::create( 'http://search.uitdatabank.be/offers/', 'GET', [ - 'start' => 0, - 'limit' => 30, + 'hasMediaObjects' => $stringValue, 'availableFrom' => OfferSearchController::QUERY_PARAMETER_RESET_VALUE, 'availableTo' => OfferSearchController::QUERY_PARAMETER_RESET_VALUE, - 'embed' => $embedParameter, ] ); - $expectedSearchParameters = (new OfferSearchParameters()) - ->withStart(new Natural(0)) - ->withLimit(new Natural(30)); + $expectedSearchParameters = (new OfferSearchParameters()); + + if (!is_null($booleanValue)) { + $expectedSearchParameters = $expectedSearchParameters + ->withMediaObjectsToggle($booleanValue); + } $expectedResultSet = new PagedResultSet(new Natural(30), new Natural(0), []); @@ -517,17 +533,7 @@ public function it_converts_the_embed_parameter_to_a_correct_boolean_and_passes_ ->with($expectedSearchParameters) ->willReturn($expectedResultSet); - $pagedCollectionFactory->expects($this->once()) - ->method('fromPagedResultSet') - ->with( - $expectedResultSet, - 0, - 30, - $expectedEmbedParameter - ) - ->willReturn($this->createMock(PagedCollection::class)); - - $controller->search($request); + $this->controller->search($request); } /** @@ -535,9 +541,9 @@ public function it_converts_the_embed_parameter_to_a_correct_boolean_and_passes_ * @dataProvider booleanStringDataProvider * * @param string $stringValue - * @param bool $booleanValue + * @param bool|null $booleanValue */ - public function it_converts_the_media_objects_toggle_parameter_to_a_correct_boolean( + public function it_converts_the_uitpas_toggle_parameter_to_a_correct_boolean( $stringValue, $booleanValue ) { @@ -545,14 +551,18 @@ public function it_converts_the_media_objects_toggle_parameter_to_a_correct_bool 'http://search.uitdatabank.be/offers/', 'GET', [ - 'hasMediaObjects' => $stringValue, + 'uitpas' => $stringValue, 'availableFrom' => OfferSearchController::QUERY_PARAMETER_RESET_VALUE, 'availableTo' => OfferSearchController::QUERY_PARAMETER_RESET_VALUE, ] ); - $expectedSearchParameters = (new OfferSearchParameters()) - ->withMediaObjectsToggle($booleanValue); + $expectedSearchParameters = (new OfferSearchParameters()); + + if (!is_null($booleanValue)) { + $expectedSearchParameters = $expectedSearchParameters + ->withUitpasToggle($booleanValue); + } $expectedResultSet = new PagedResultSet(new Natural(30), new Natural(0), []); @@ -570,6 +580,14 @@ public function it_converts_the_media_objects_toggle_parameter_to_a_correct_bool public function booleanStringDataProvider() { return [ + [ + false, + false, + ], + [ + true, + true, + ], [ 'false', false, @@ -582,6 +600,10 @@ public function booleanStringDataProvider() '0', false, ], + [ + 0, + false, + ], [ 'true', true, @@ -592,7 +614,19 @@ public function booleanStringDataProvider() ], [ '1', - true + true, + ], + [ + 1, + true, + ], + [ + '', + null, + ], + [ + null, + null, ], ]; } @@ -729,4 +763,110 @@ public function malformedDateTimeProvider() ['1493726880'], ]; } + + /** + * @test + */ + public function it_throws_an_exception_when_sort_is_not_an_array() + { + $request = Request::create( + 'http://search.uitdatabank.be/offers/', + 'GET', + [ + 'sort' => 'availableTo asc' + ] + ); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid sorting syntax given.'); + + $this->controller->search($request); + } + + /** + * @test + */ + public function it_throws_an_exception_when_a_sort_field_is_invalid() + { + $request = Request::create( + 'http://search.uitdatabank.be/offers/', + 'GET', + [ + 'sort' => [ + 'availableTo' => 'asc', + 'name.nl' => 'asc', + 'score' => 'desc', + ] + ] + ); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Invalid sort field 'name.nl' given."); + + $this->controller->search($request); + } + + /** + * @test + */ + public function it_throws_an_exception_when_a_sort_order_is_invalid() + { + $request = Request::create( + 'http://search.uitdatabank.be/offers/', + 'GET', + [ + 'sort' => [ + 'availableTo' => 'ascending', + 'score' => 'descending', + ] + ] + ); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Invalid sort order 'ascending' given."); + + $this->controller->search($request); + } + + /** + * @test + */ + public function it_throws_an_exception_when_a_sort_field_is_missing() + { + $request = Request::create( + 'http://search.uitdatabank.be/offers/', + 'GET', + [ + 'sort' => [ + 'asc', + ] + ] + ); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Sort field missing.'); + + $this->controller->search($request); + } + + /** + * @test + */ + public function it_throws_an_exception_when_a_sort_order_is_missing() + { + $request = Request::create( + 'http://search.uitdatabank.be/offers/', + 'GET', + [ + 'sort' => [ + 'availableTo' => '', + ] + ] + ); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Sort order missing.'); + + $this->controller->search($request); + } } diff --git a/tests/OrganizerSearchControllerTest.php b/tests/OrganizerSearchControllerTest.php index 395568e..31b4aca 100644 --- a/tests/OrganizerSearchControllerTest.php +++ b/tests/OrganizerSearchControllerTest.php @@ -108,87 +108,4 @@ public function it_uses_the_default_limit_of_30_if_a_limit_of_0_is_given() $this->controller->search($request); } - - /** - * @test - * @dataProvider embedParameterDataProvider - * - * @param mixed $embedParameter - * @param bool $expectedEmbedParameter - */ - public function it_converts_the_embed_parameter_to_a_correct_boolean_and_passes_it_to_the_paged_collection_factory( - $embedParameter, - $expectedEmbedParameter - ) { - $pagedCollectionFactory = $this->createMock(PagedCollectionFactory::class); - - $controller = new OrganizerSearchController( - $this->searchService, - $pagedCollectionFactory - ); - - $request = new Request( - [ - 'start' => 0, - 'limit' => 30, - 'embed' => $embedParameter, - ] - ); - - $expectedSearchParameters = (new OrganizerSearchParameters()) - ->withStart(new Natural(0)) - ->withLimit(new Natural(30)); - - $expectedResultSet = new PagedResultSet(new Natural(30), new Natural(0), []); - - $this->searchService->expects($this->once()) - ->method('search') - ->with($expectedSearchParameters) - ->willReturn($expectedResultSet); - - $pagedCollectionFactory->expects($this->once()) - ->method('fromPagedResultSet') - ->with( - $expectedResultSet, - 0, - 30, - $expectedEmbedParameter - ) - ->willReturn($this->createMock(PagedCollection::class)); - - $controller->search($request); - } - - /** - * @return Request[] - */ - public function embedParameterDataProvider() - { - return [ - [ - 'false', - false, - ], - [ - 'FALSE', - false, - ], - [ - '0', - false, - ], - [ - 'true', - true, - ], - [ - 'TRUE', - true, - ], - [ - '1', - true - ], - ]; - } } diff --git a/tests/PagedCollectionFactoryTest.php b/tests/PagedCollectionFactoryTest.php index 51f6f02..f53b03b 100644 --- a/tests/PagedCollectionFactoryTest.php +++ b/tests/PagedCollectionFactoryTest.php @@ -11,13 +11,13 @@ class PagedCollectionFactoryTest extends \PHPUnit_Framework_TestCase { /** - * @var PagedCollectionFactory + * @var ResultSetMappingPagedCollectionFactory */ private $factory; public function setUp() { - $this->factory = new PagedCollectionFactory(); + $this->factory = new ResultSetMappingPagedCollectionFactory(); } /** @@ -64,66 +64,4 @@ public function it_creates_a_paged_collection_from_a_paged_result_set() $this->assertEquals($expectedCollection, $actualCollection); } - - /** - * @test - */ - public function it_uses_the_inject_transform_to_embed_results_if_the_embed_flag_is_set() - { - $jsonTransformer = $this->createMock(JsonDocumentTransformerInterface::class); - - $jsonTransformer->expects($this->exactly(2)) - ->method('transform') - ->willReturnCallback( - function (JsonDocument $jsonDocument) { - $body = $jsonDocument->getBody(); - $body->foo = 'bar'; - return $jsonDocument->withBody($body); - } - ); - - $factory = new PagedCollectionFactory($jsonTransformer); - - $start = 10; - $limit = 10; - $total = 12; - $embed = true; - - $pagedResultSet = new PagedResultSet( - new Natural($total), - new Natural(10), - [ - new JsonDocument( - '3d3ecf5c-2c21-4c6c-9faf-cd8e5fbf0464', - '{"@id": "events/3d3ecf5c-2c21-4c6c-9faf-cd8e5fbf0464"}' - ), - new JsonDocument( - 'cd205d41-6534-4519-a38b-50937742d7ac', - '{"@id": "events/9f50a221-c6b3-486d-bede-603c75091dbe"}' - ), - ] - ); - - $expectedPageNumber = 2; - - $expectedCollection = new PagedCollection( - $expectedPageNumber, - $limit, - [ - (object) [ - '@id' => 'events/3d3ecf5c-2c21-4c6c-9faf-cd8e5fbf0464', - 'foo' => 'bar', - ], - (object) [ - '@id' => 'events/9f50a221-c6b3-486d-bede-603c75091dbe', - 'foo' => 'bar', - ], - ], - $total - ); - - $actualCollection = $factory->fromPagedResultSet($pagedResultSet, $start, $limit, $embed); - - $this->assertEquals($expectedCollection, $actualCollection); - } }