diff options
-rw-r--r-- | docs/hooks.txt | 15 | ||||
-rw-r--r-- | includes/Rest/Entity/SearchResultPageIdentity.php | 38 | ||||
-rw-r--r-- | includes/Rest/Entity/SearchResultPageIdentityValue.php | 46 | ||||
-rw-r--r-- | includes/Rest/Handler/SearchHandler.php | 163 | ||||
-rw-r--r-- | includes/Rest/Hook/SearchResultProvideDescriptionHook.php | 25 | ||||
-rw-r--r-- | includes/Rest/Hook/SearchResultProvideThumbnailHook.php | 25 | ||||
-rw-r--r-- | includes/search/Entity/SearchResultThumbnail.php | 136 | ||||
-rw-r--r-- | tests/api-testing/REST/Search.js | 8 | ||||
-rw-r--r-- | tests/phpunit/unit/includes/Rest/Handler/SearchHandlerTest.php | 114 |
9 files changed, 534 insertions, 36 deletions
diff --git a/docs/hooks.txt b/docs/hooks.txt index 42cb793a7eb3..d745e79a79f1 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -4057,5 +4057,20 @@ $row: The database row for the revision being dumped. DEPRECATED, use $rev inste $text: The revision text to be dumped. DEPRECATED, use $rev instead. $rev: The RevisionRecord that is being dumped to XML +'SearchResultProvideDescription': Called by REST SearchHandler in order to allow +extensions to fill the 'description' field in search results. Warning: this +hook as well as SearchResultPageIdentity interface is being under development and still unstable. +$pageIdentities: an array (string=>SearchResultPageIdentity) where key is pageId. +&$descriptions: an output array (string=>string|null) where key is pageId and value is either +a desciption for given page or null + +'SearchResultProvideThumbnail': Called by REST SearchHandler in order to allow +extensions to fill the 'thumbnail' field in rest search results. Warning: this +hook as well as SearchResultPageIdentity interface is being under development and still unstable. +$pageIdentities: an array (string=>SearchResultPageIdentity) where key is pageId. +&$thumbnails: an output array (string=>SearchResultThumbnail|null) where key is pageId and +value is either a valid SearchResultThumbnail for given page or null + + More hooks might be available but undocumented, you can execute "php maintenance/findHooks.php" to find hidden ones. diff --git a/includes/Rest/Entity/SearchResultPageIdentity.php b/includes/Rest/Entity/SearchResultPageIdentity.php new file mode 100644 index 000000000000..a811d20d9e4b --- /dev/null +++ b/includes/Rest/Entity/SearchResultPageIdentity.php @@ -0,0 +1,38 @@ +<?php +namespace MediaWiki\Rest\Entity; + +/** + * Lightweight interface representing a page identity + * + * @unstable + * @note This interface is temorary solution. It will be replaced by the one from: + * https://phabricator.wikimedia.org/T208776 + */ +interface SearchResultPageIdentity { + /** + * The numerical page ID. + * At the moment it is equivalent to Title::getArticleID() + * @see Title::getArticleID() + * + * @return int + */ + function getId(): int; + + /** + * Returns the page's namespace number. + * At the moment it is equivalent to Title::getNamespace() + * @see Title::getNamespace() + * + * @return int + */ + function getNamespace(): int; + + /** + * Get the page title in DB key form. + * At the moment it is equivalent to Title::getDBkey() + * @see Title::getDBkey() + * + * @return string + */ + function getDBkey(): string; +} diff --git a/includes/Rest/Entity/SearchResultPageIdentityValue.php b/includes/Rest/Entity/SearchResultPageIdentityValue.php new file mode 100644 index 000000000000..def4ae5a569d --- /dev/null +++ b/includes/Rest/Entity/SearchResultPageIdentityValue.php @@ -0,0 +1,46 @@ +<?php +namespace MediaWiki\Rest\Entity; + +/** + * Lightweight value class representing a page identity + * + * @unstable + * @note This class is temorary solution. It will be replaced by the one from: + * https://phabricator.wikimedia.org/T208776 + */ +class SearchResultPageIdentityValue implements SearchResultPageIdentity { + + /** + * @var int + */ + private $id = 0; + + /** + * @var string + */ + private $dbKey = ''; + + /** + * @var int + */ + private $namespace = null; + + public function __construct( int $id, int $namespace, string $dbKey ) { + $this->id = $id; + $this->namespace = $namespace; + $this->dbKey = $dbKey; + } + + public function getId(): int { + return $this->id; + } + + public function getNamespace(): int { + return $this->namespace; + } + + public function getDBkey(): string { + return $this->dbKey; + } + +} diff --git a/includes/Rest/Handler/SearchHandler.php b/includes/Rest/Handler/SearchHandler.php index 7a335ecb5e09..c9b73de823a5 100644 --- a/includes/Rest/Handler/SearchHandler.php +++ b/includes/Rest/Handler/SearchHandler.php @@ -3,15 +3,18 @@ namespace MediaWiki\Rest\Handler; use Config; +use Hooks; use InvalidArgumentException; use ISearchResultSet; use MediaWiki\Permissions\PermissionManager; +use MediaWiki\Rest\Entity\SearchResultPageIdentityValue; use MediaWiki\Rest\Handler; use MediaWiki\Rest\LocalizedHttpException; use MediaWiki\Rest\RequestInterface; use MediaWiki\Rest\Response; use MediaWiki\Rest\ResponseFactory; use MediaWiki\Rest\Router; +use MediaWiki\Search\Entity\SearchResultThumbnail; use RequestContext; use SearchEngine; use SearchEngineConfig; @@ -171,10 +174,10 @@ class SearchHandler extends Handler { } /** - * Execute search and return results. + * Execute search and return info about pages for further processing. * * @param SearchEngine $searchEngine - * @return SearchResult[] + * @return array[] * @throws LocalizedHttpException */ private function doSearch( $searchEngine ) { @@ -182,7 +185,7 @@ class SearchHandler extends Handler { if ( $this->mode == self::COMPLETION_MODE ) { $completionSearch = $searchEngine->completionSearchWithVariants( $query ); - return $this->buildOutputFromSuggestions( $completionSearch->getSuggestions() ); + return $this->buildPageInfosFromSuggestions( $completionSearch->getSuggestions() ); } else { $titleSearch = $searchEngine->searchTitle( $query ); $textSearch = $searchEngine->searchText( $query ); @@ -191,70 +194,143 @@ class SearchHandler extends Handler { $textSearchResults = $this->getSearchResultsOrThrow( $textSearch ); $mergedResults = array_merge( $titleSearchResults, $textSearchResults ); - return $this->buildOutputFromSearchResults( $mergedResults ); + return $this->buildPageInfosFromSearchResults( $mergedResults ); } } /** - * Remove duplicate pages and turn results into response json objects + * Remove duplicate pages and turn suggestions into array with + * information needed for further processing: + * pageId => [ $title, $suggestion, null ] * * @param SearchSuggestion[] $suggestions * - * @return array page objects + * @return array[] of pageId => [ $title, $suggestion, null ] */ - private function buildOutputFromSuggestions( array $suggestions ) { - $pages = []; - $foundPageIds = []; + private function buildPageInfosFromSuggestions( array $suggestions ): array { + $pageInfos = []; + foreach ( $suggestions as $sugg ) { $title = $sugg->getSuggestedTitle(); if ( $title && $title->exists() ) { $pageID = $title->getArticleID(); - if ( !isset( $foundPageIds[$pageID] ) && + if ( !isset( $pageInfos[$pageID] ) && $this->permissionManager->quickUserCan( 'read', $this->user, $title ) ) { - $page = [ - 'id' => $pageID, - 'key' => $title->getPrefixedDBkey(), - 'title' => $title->getPrefixedText(), - 'excerpt' => $sugg->getText() ?: null, - ]; - $pages[] = $page; - $foundPageIds[$pageID] = true; + $pageInfos[ $pageID ] = [ $title, $sugg, null ]; } } } - return $pages; + return $pageInfos; } /** - * Remove duplicate pages and turn results into response json objects + * Remove duplicate pages and turn search results into array with + * information needed for further processing: + * pageId => [ $title, null, $result ] * * @param SearchResult[] $searchResults * - * @return array page objects + * @return array[] of pageId => [ $title, null, $result ] */ - private function buildOutputFromSearchResults( array $searchResults ) { - $pages = []; - $foundPageIds = []; + private function buildPageInfosFromSearchResults( array $searchResults ): array { + $pageInfos = []; + foreach ( $searchResults as $result ) { if ( !$result->isBrokenTitle() && !$result->isMissingRevision() ) { $title = $result->getTitle(); $pageID = $title->getArticleID(); - if ( !isset( $foundPageIds[$pageID] ) && + if ( !isset( $pageInfos[$pageID] ) && $this->permissionManager->quickUserCan( 'read', $this->user, $title ) ) { - $page = [ - 'id' => $pageID, - 'key' => $title->getPrefixedDBkey(), - 'title' => $title->getPrefixedText(), - 'excerpt' => $result->getTextSnippet() ?: null, - ]; - $pages[] = $page; - $foundPageIds[$pageID] = true; + $pageInfos[$pageID] = [ $title, null, $result ]; } } } - return $pages; + return $pageInfos; + } + + /** + * Turn array of page info into serializable array with common information about the page + * + * @param array[] $pageInfos + * + * @return array[] of pageId => [ $title, null, $result ] + */ + private function buildResultFromPageInfos( array $pageInfos ): array { + return array_map( function ( $pageInfo ) { + list( $title, $sugg, $result ) = $pageInfo; + return [ + 'id' => $title->getArticleID(), + 'key' => $title->getPrefixedDBkey(), + 'title' => $title->getPrefixedText(), + 'excerpt' => ( $sugg ? $sugg->getText() : $result->getTextSnippet() ) ?: null, + ]; + }, + $pageInfos ); + } + + /** + * Converts SearchResultThumbnail object into serializable array + * + * @param SearchResultThumbnail|null $thumbnail + * + * @return array|null + */ + private function serializeThumbnail( ?SearchResultThumbnail $thumbnail ) : ?array { + if ( $thumbnail == null ) { + return null; + } + + return [ + 'mimetype' => $thumbnail->getMimeType(), + 'size' => $thumbnail->getSize(), + 'width' => $thumbnail->getWidth(), + 'height' => $thumbnail->getHeight(), + 'duration' => $thumbnail->getDuration(), + 'url' => $thumbnail->getUrl(), + ]; + } + + /** + * Turn page info into serializable array with desciption field for the page. + * + * The information about desciption should be provided by extension by implementing + * 'SearchResultProvideDescription' hook. Desciption is set to null if no extensions implement + * the hook. + * @param array $pageIdentities + * + * @return array + */ + private function buildDescriptionsFromPageIdentities( array $pageIdentities ) { + $descriptions = array_fill_keys( array_keys( $pageIdentities ), null ); + + Hooks::run( 'SearchResultProvideDescription', [ $pageIdentities, &$descriptions ] ); + + return array_map( function ( $description ) { + return [ 'description' => $description ]; + }, $descriptions ); + } + + /** + * Turn page info into serializable array with thumbnail information for the page. + * + * The information about thumbnail should be provided by extension by implementing + * 'SearchResultProvideThumbnail' hook. Thumbnail is set to null if no extensions implement + * the hook. + * + * @param array $pageIdentities + * + * @return array + */ + private function buildThumbnailsFromPageIdentities( array $pageIdentities ) { + $thumbnails = array_fill_keys( array_keys( $pageIdentities ), null ); + + Hooks::run( 'SearchResultProvideThumbnail', [ $pageIdentities, &$thumbnails ] ); + + return array_map( function ( $thumbnail ) { + return [ 'thumbnail' => $this->serializeThumbnail( $thumbnail ) ]; + }, $thumbnails ); } /** @@ -263,8 +339,23 @@ class SearchHandler extends Handler { */ public function execute() { $searchEngine = $this->createSearchEngine(); - $results = $this->doSearch( $searchEngine ); - $response = $this->getResponseFactory()->createJson( [ 'pages' => $results ] ); + $pageInfos = $this->doSearch( $searchEngine ); + $pageIdentities = array_map( function ( $pageInfo ) { + list( $title ) = $pageInfo; + return new SearchResultPageIdentityValue( + $title->getArticleID(), + $title->getNamespace(), + $title->getDBkey() + ); + }, $pageInfos ); + + $result = array_map( "array_merge", + $this->buildResultFromPageInfos( $pageInfos ), + $this->buildDescriptionsFromPageIdentities( $pageIdentities ), + $this->buildThumbnailsFromPageIdentities( $pageIdentities ) + ); + + $response = $this->getResponseFactory()->createJson( [ 'pages' => $result ] ); if ( $this->mode === self::COMPLETION_MODE && $this->completionCacheExpiry ) { // Type-ahead completion matches should be cached by the client and diff --git a/includes/Rest/Hook/SearchResultProvideDescriptionHook.php b/includes/Rest/Hook/SearchResultProvideDescriptionHook.php new file mode 100644 index 000000000000..82832e54077a --- /dev/null +++ b/includes/Rest/Hook/SearchResultProvideDescriptionHook.php @@ -0,0 +1,25 @@ +<?php + +namespace MediaWiki\Rest\Hook; + +/** + * Called by REST SearchHandler in order to allow extensions to fill the 'description' + * field in search results. Warning: this hook as well as SearchResultPageIdentity interface + * is being under development and still unstable. + * + * @unstable + * @ingroup Hooks + */ +interface SearchResultProvideDescriptionHook { + /** + * This hook is called when generating search results in order to fill the 'description' + * field in an extension. + * + * @since 1.35 + * + * @param array $pageIdentities an array (string=>SearchResultPageIdentity) where key is pageId. + * @param array &$descriptions an output array (string=>string|null) where key + * is pageId and value is either a desciption for given page or null + */ + public function onSearchResultProvideDescription( array $pageIdentities, &$descriptions ); +} diff --git a/includes/Rest/Hook/SearchResultProvideThumbnailHook.php b/includes/Rest/Hook/SearchResultProvideThumbnailHook.php new file mode 100644 index 000000000000..ffa2f182e9a9 --- /dev/null +++ b/includes/Rest/Hook/SearchResultProvideThumbnailHook.php @@ -0,0 +1,25 @@ +<?php + +namespace MediaWiki\Rest\Hook; + +/** + * Called by REST SearchHandler in order to allow extensions to fill the 'thumbnail' + * field in rest search results. Warning: this hook as well as SearchResultPageIdentity + * interface is being under development and still unstable. + * + * @unstable + * @ingroup Hooks + */ +interface SearchResultProvideThumbnailHook { + /** + * This hook is called when generating search results in order to fill the 'thumbnail' + * field in an extension. + * + * @since 1.35 + * + * @param array $pageIdentities an array (string=>SearchResultPageIdentity) where key is pageId. + * @param array &$thumbnails an output array (string=>SearchResultThumbnail|null) where key + * is pageId and value is either a valid SearchResultThumbnail for given page or null + */ + public function onSearchResultProvideThumbnail( array $pageIdentities, &$thumbnails ); +} diff --git a/includes/search/Entity/SearchResultThumbnail.php b/includes/search/Entity/SearchResultThumbnail.php new file mode 100644 index 000000000000..82a30432bd2d --- /dev/null +++ b/includes/search/Entity/SearchResultThumbnail.php @@ -0,0 +1,136 @@ +<?php + +namespace MediaWiki\Search\Entity; + +/** + * Class that stores information about thumbnail, e. g. url, width and height + * @newable + */ +class SearchResultThumbnail { + /** + * Internet mime type for the representation, like "image/png" or "audio/mp3" + * @var string + */ + private $mimeType; + + /** + * Size of the representation in bytes or null if not applicable + * @var int|null + */ + private $size; + + /** + * Duration of the representation in seconds or null if not applicable + * @var int|null + */ + private $duration; + + /** + * Full URL to the contents of the file + * @var string + */ + private $url; + + /** + * Width of the representation in pixels or null if not applicable + * @var int|null + */ + private $width; + + /** + * Height of the representation in pixels or null if not applicable + * @var int|null + */ + private $height; + + /** + * String that represent file indentity in storage or null + * @var string|null + */ + private $name; + + /** + * @param string $mimeType Internet mime type for the representation, + * like "image/png" or "audio/mp3" + * @param int|null $size Size of the representation in bytes + * @param int|null $width Width of the representation in pixels or null if not applicable + * @param int|null $height Height of the representation in pixels or null if not applicable + * @param int|null $duration Duration of the representation in seconds or + * null if not applicable + * @param string $url full URL to the contents of the file + * @param string|null $name full URL to the contents of the file + */ + public function __construct( + string $mimeType, + ?int $size, + ?int $width, + ?int $height, + ?int $duration, + string $url, + ?string $name + ) { + $this->mimeType = $mimeType; + $this->size = $size; + $this->width = $width; + $this->height = $height; + $this->duration = $duration; + $this->url = $url; + $this->name = $name; + } + + /** + * Full URL to the contents of the file + * @return string + */ + public function getUrl(): string { + return $this->url; + } + + /** + * Width of the representation in pixels or null if not applicable + * @return int|null + */ + public function getWidth(): ?int { + return $this->width; + } + + /** + * Height of the representation in pixels or null if not applicable + * @return int|null + */ + public function getHeight(): ?int { + return $this->height; + } + + /** + * Internet mime type for the representation, like "image/png" or "audio/mp3" + * @return string + */ + public function getMimeType(): string { + return $this->mimeType; + } + + /** + * Size of the representation in bytes or null if not applicable + * @return int|null + */ + public function getSize(): ?int { + return $this->size; + } + + /** + * Duration of the representation in seconds or null if not applicable + * @return int|null + */ + public function getDuration(): ?int { + return $this->duration; + } + + /** + * String that represent file indentity in storage or null + * @return string|null + */ + public function getName(): ?string { + return $this->name; + } +} diff --git a/tests/api-testing/REST/Search.js b/tests/api-testing/REST/Search.js index 0b798a940ec4..6249e70e1834 100644 --- a/tests/api-testing/REST/Search.js +++ b/tests/api-testing/REST/Search.js @@ -34,6 +34,8 @@ describe( 'Search', () => { assert.nestedProperty( returnPage, 'id' ); assert.nestedProperty( returnPage, 'key' ); assert.nestedProperty( returnPage, 'excerpt' ); + assert.nestedPropertyVal( returnPage, 'thumbnail', null ); + assert.nestedPropertyVal( returnPage, 'description', null ); assert.include( returnPage.excerpt, `<span class='searchmatch'>${searchTerm}</span>` ); // full-text search should not have cache-control @@ -51,6 +53,8 @@ describe( 'Search', () => { assert.nestedProperty( returnPage, 'id' ); assert.nestedProperty( returnPage, 'key' ); assert.nestedPropertyVal( returnPage, 'excerpt', null ); + assert.nestedPropertyVal( returnPage, 'thumbnail', null ); + assert.nestedPropertyVal( returnPage, 'description', null ); } ); it( 'should return a single page when there is a title and text match on the same page', async () => { const { body } = await client.get( `/search/page?q=${pageWithOwnTitle}` ); @@ -60,6 +64,8 @@ describe( 'Search', () => { assert.nestedProperty( returnPage, 'id' ); assert.nestedProperty( returnPage, 'key' ); assert.nestedPropertyVal( returnPage, 'title', pageWithOwnTitle ); + assert.nestedPropertyVal( returnPage, 'thumbnail', null ); + assert.nestedPropertyVal( returnPage, 'description', null ); } ); it( 'should return two pages when both pages match', async () => { const { body } = await client.get( `/search/page?q=${searchTerm2}` ); @@ -104,6 +110,8 @@ describe( 'Search', () => { assert.nestedProperty( returnPage, 'id' ); assert.nestedProperty( returnPage, 'key' ); assert.nestedProperty( returnPage, 'excerpt' ); + assert.nestedPropertyVal( returnPage, 'thumbnail', null ); + assert.nestedPropertyVal( returnPage, 'description', null ); // completion search should encourage caching assert.nestedProperty( headers, 'cache-control' ); diff --git a/tests/phpunit/unit/includes/Rest/Handler/SearchHandlerTest.php b/tests/phpunit/unit/includes/Rest/Handler/SearchHandlerTest.php index 865e7b63bda5..d88bd861504f 100644 --- a/tests/phpunit/unit/includes/Rest/Handler/SearchHandlerTest.php +++ b/tests/phpunit/unit/includes/Rest/Handler/SearchHandlerTest.php @@ -3,12 +3,14 @@ namespace MediaWiki\Tests\Rest\Handler; use HashConfig; +use InvalidArgumentException; use Language; use MediaWiki\Linker\LinkTarget; use MediaWiki\Permissions\PermissionManager; use MediaWiki\Rest\Handler\SearchHandler; use MediaWiki\Rest\LocalizedHttpException; use MediaWiki\Rest\RequestData; +use MediaWiki\Search\Entity\SearchResultThumbnail; use MockSearchResultSet; use PHPUnit\Framework\MockObject\MockObject; use SearchEngine; @@ -345,4 +347,116 @@ class SearchHandlerTest extends \MediaWikiUnitTestCase { $this->assertFalse( $handler->needsWriteAccess() ); } + public function testExecute_augmentedFields() { + $titleResults = Status::newGood( new MockSearchResultSet( [ + $this->makeMockSearchResult( 'Foo', 'one' ), + $this->makeMockSearchResult( 'FooBar', 'three' ), + ] ) ); + $textResults = Status::newGood( new MockSearchResultSet( [ + $this->makeMockSearchResult( 'Quux', 'one' ), + $this->makeMockSearchResult( 'Xyzzy', 'three' ), + ] ) ); + + $query = 'foo'; + $request = new RequestData( [ 'queryParams' => [ 'q' => $query ] ] ); + + $handler = $this->newHandler( $query, $titleResults, $textResults ); + $data = $this->executeHandlerAndGetBodyData( $handler, $request ); + + $this->assertArrayHasKey( 'pages', $data ); + $this->assertCount( 4, $data['pages'] ); + $this->assertArrayHasKey( 'thumbnail', $data['pages'][0] ); + $this->assertNull( $data['pages'][0][ 'thumbnail' ] ); + + $this->assertArrayHasKey( 'description', $data['pages'][0] ); + $this->assertNull( $data['pages'][0][ 'description' ] ); + } + + public function testExecute_augmentedFieldsDescriptionAndThumbnailProvided() { + $titleResults = Status::newGood( new MockSearchResultSet( [ + $this->makeMockSearchResult( 'Foo', 'one' ), + ] ) ); + $textResults = Status::newGood( new MockSearchResultSet( [ + $this->makeMockSearchResult( 'Quux', 'one' ), + ] ) ); + + $query = 'foo'; + $request = new RequestData( [ 'queryParams' => [ 'q' => $query ] ] ); + + $handler = $this->newHandler( $query, $titleResults, $textResults ); + $this->setTemporaryHook( 'SearchResultProvideDescription', + function ( array $pageIdentities, array &$result ) { + foreach ( $pageIdentities as $pageId => $pageIdentity ) { + $result[ $pageId ] = 'Description_' . $pageIdentity->getId(); + } + } ); + + $this->setTemporaryHook( 'SearchResultProvideThumbnail', + function ( array $pageIdentities, array &$result ) { + foreach ( $pageIdentities as $pageId => $pageIdentity ) { + $result[ $pageId ] = new SearchResultThumbnail( + 'image/png', + 2250, + 100, + 125, + 500, + 'http:/example.org/url_' . $pageIdentity->getId(), + null + ); + } + } ); + + $data = $this->executeHandlerAndGetBodyData( $handler, $request ); + + $this->assertArrayHasKey( 'pages', $data ); + $this->assertCount( 2, $data['pages'] ); + $this->assertArrayHasKey( 'thumbnail', $data['pages'][0] ); + + $this->assertSame( 'http:/example.org/url_1', $data['pages'][0][ 'thumbnail' ]['url'] ); + $this->assertSame( 125, $data['pages'][0][ 'thumbnail' ]['height'] ); + $this->assertSame( 100, $data['pages'][0][ 'thumbnail' ]['width'] ); + $this->assertSame( 'image/png', $data['pages'][0][ 'thumbnail' ]['mimetype'] ); + $this->assertSame( 2250, $data['pages'][0][ 'thumbnail' ]['size'] ); + $this->assertSame( 500, $data['pages'][0][ 'thumbnail' ]['duration'] ); + $this->assertArrayHasKey( 'description', $data['pages'][0] ); + $this->assertSame( 'Description_1', $data['pages'][0][ 'description' ] ); + } + + public function testExecute_NullResults() { + $query = 'foo'; + $request = new RequestData( [ 'queryParams' => [ 'q' => $query ] ] ); + + $handler = $this->newHandler( $query, null, null ); + $data = $this->executeHandlerAndGetBodyData( $handler, $request ); + + $this->assertArrayHasKey( 'pages', $data ); + $this->assertCount( 0, $data['pages'] ); + } + + public function testInitWrongConfig() { + $titleResults = Status::newGood( new MockSearchResultSet( [ + $this->makeMockSearchResult( 'Foo', 'one' ), + $this->makeMockSearchResult( 'FooBar', 'three' ), + ] ) ); + $textResults = Status::newGood( new MockSearchResultSet( [ + $this->makeMockSearchResult( 'Quux', 'one' ), + $this->makeMockSearchResult( 'Xyzzy', 'three' ), + ] ) ); + + $query = 'foo'; + $request = new RequestData( [ 'queryParams' => [ 'q' => $query ] ] ); + + $this->expectException( InvalidArgumentException::class ); + + $handler = $this->newHandler( $query, $titleResults, $textResults ); + $data = $this->executeHandlerAndGetBodyData( + $handler, + $request, + [ + 'mode' => 'SomethingWrong' + ] ); + + $this->assertArrayHasKey( 'pages', $data ); + $this->assertCount( 0, $data['pages'] ); + } } |