diff options
author | Yaco <franco@reevo.org> | 2020-06-04 11:01:00 -0300 |
---|---|---|
committer | Yaco <franco@reevo.org> | 2020-06-04 11:01:00 -0300 |
commit | fc7369835258467bf97eb64f184b93691f9a9fd5 (patch) | |
tree | daabd60089d2dd76d9f5fb416b005fbe159c799d /www/wiki/tests/phpunit/includes/search/SearchEngineTest.php |
first commit
Diffstat (limited to 'www/wiki/tests/phpunit/includes/search/SearchEngineTest.php')
-rw-r--r-- | www/wiki/tests/phpunit/includes/search/SearchEngineTest.php | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/includes/search/SearchEngineTest.php b/www/wiki/tests/phpunit/includes/search/SearchEngineTest.php new file mode 100644 index 00000000..b7bc1530 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/search/SearchEngineTest.php @@ -0,0 +1,368 @@ +<?php + +/** + * @group Search + * @group Database + * + * @covers SearchEngine<extended> + * @note Coverage will only ever show one of on of the Search* classes + */ +class SearchEngineTest extends MediaWikiLangTestCase { + + /** + * @var SearchEngine + */ + protected $search; + + /** + * Checks for database type & version. + * Will skip current test if DB does not support search. + */ + protected function setUp() { + parent::setUp(); + + // Search tests require MySQL or SQLite with FTS + $dbType = $this->db->getType(); + $dbSupported = ( $dbType === 'mysql' ) + || ( $dbType === 'sqlite' && $this->db->getFulltextSearchModule() == 'FTS3' ); + + if ( !$dbSupported ) { + $this->markTestSkipped( "MySQL or SQLite with FTS3 only" ); + } + + $searchType = SearchEngineFactory::getSearchEngineClass( $this->db ); + $this->setMwGlobals( [ + 'wgSearchType' => $searchType, + 'wgCapitalLinks' => true, + 'wgCapitalLinkOverrides' => [ + NS_CATEGORY => false // for testCompletionSearchMustRespectCapitalLinkOverrides + ] + ] ); + + $this->search = new $searchType( $this->db ); + } + + protected function tearDown() { + unset( $this->search ); + + parent::tearDown(); + } + + public function addDBDataOnce() { + if ( !$this->isWikitextNS( NS_MAIN ) ) { + // @todo cover the case of non-wikitext content in the main namespace + return; + } + + // Reset the search type back to default - some extensions may have + // overridden it. + $this->setMwGlobals( [ + 'wgSearchType' => null, + 'wgCapitalLinks' => true, + 'wgCapitalLinkOverrides' => [ + NS_CATEGORY => false // for testCompletionSearchMustRespectCapitalLinkOverrides + ] + ] ); + + $this->insertPage( 'Not_Main_Page', 'This is not a main page' ); + $this->insertPage( + 'Talk:Not_Main_Page', + 'This is not a talk page to the main page, see [[smithee]]' + ); + $this->insertPage( 'Smithee', 'A smithee is one who smiths. See also [[Alan Smithee]]' ); + $this->insertPage( 'Talk:Smithee', 'This article sucks.' ); + $this->insertPage( 'Unrelated_page', 'Nothing in this page is about the S word.' ); + $this->insertPage( 'Another_page', 'This page also is unrelated.' ); + $this->insertPage( 'Help:Help', 'Help me!' ); + $this->insertPage( 'Thppt', 'Blah blah' ); + $this->insertPage( 'Alan_Smithee', 'yum' ); + $this->insertPage( 'Pages', 'are\'food' ); + $this->insertPage( 'HalfOneUp', 'AZ' ); + $this->insertPage( 'FullOneUp', 'AZ' ); + $this->insertPage( 'HalfTwoLow', 'az' ); + $this->insertPage( 'FullTwoLow', 'az' ); + $this->insertPage( 'HalfNumbers', '1234567890' ); + $this->insertPage( 'FullNumbers', '1234567890' ); + $this->insertPage( 'DomainName', 'example.com' ); + $this->insertPage( 'DomainName', 'example.com' ); + $this->insertPage( 'Category:search is not Search', '' ); + $this->insertPage( 'Category:Search is not search', '' ); + } + + protected function fetchIds( $results ) { + if ( !$this->isWikitextNS( NS_MAIN ) ) { + $this->markTestIncomplete( __CLASS__ . " does no yet support non-wikitext content " + . "in the main namespace" ); + } + $this->assertTrue( is_object( $results ) ); + + $matches = []; + $row = $results->next(); + while ( $row ) { + $matches[] = $row->getTitle()->getPrefixedText(); + $row = $results->next(); + } + $results->free(); + # Search is not guaranteed to return results in a certain order; + # sort them numerically so we will compare simply that we received + # the expected matches. + sort( $matches ); + + return $matches; + } + + public function testFullWidth() { + $this->assertEquals( + [ 'FullOneUp', 'FullTwoLow', 'HalfOneUp', 'HalfTwoLow' ], + $this->fetchIds( $this->search->searchText( 'AZ' ) ), + "Search for normalized from Half-width Upper" ); + $this->assertEquals( + [ 'FullOneUp', 'FullTwoLow', 'HalfOneUp', 'HalfTwoLow' ], + $this->fetchIds( $this->search->searchText( 'az' ) ), + "Search for normalized from Half-width Lower" ); + $this->assertEquals( + [ 'FullOneUp', 'FullTwoLow', 'HalfOneUp', 'HalfTwoLow' ], + $this->fetchIds( $this->search->searchText( 'AZ' ) ), + "Search for normalized from Full-width Upper" ); + $this->assertEquals( + [ 'FullOneUp', 'FullTwoLow', 'HalfOneUp', 'HalfTwoLow' ], + $this->fetchIds( $this->search->searchText( 'az' ) ), + "Search for normalized from Full-width Lower" ); + } + + public function testTextSearch() { + $this->assertEquals( + [ 'Smithee' ], + $this->fetchIds( $this->search->searchText( 'smithee' ) ), + "Plain search" ); + } + + public function testWildcardSearch() { + $res = $this->search->searchText( 'smith*' ); + $this->assertEquals( + [ 'Smithee' ], + $this->fetchIds( $res ), + "Search with wildcards" ); + + $res = $this->search->searchText( 'smithson*' ); + $this->assertEquals( + [], + $this->fetchIds( $res ), + "Search with wildcards must not find unrelated articles" ); + + $res = $this->search->searchText( 'smith* smithee' ); + $this->assertEquals( + [ 'Smithee' ], + $this->fetchIds( $res ), + "Search with wildcards can be combined with simple terms" ); + + $res = $this->search->searchText( 'smith* "one who smiths"' ); + $this->assertEquals( + [ 'Smithee' ], + $this->fetchIds( $res ), + "Search with wildcards can be combined with phrase search" ); + } + + public function testPhraseSearch() { + $res = $this->search->searchText( '"smithee is one who smiths"' ); + $this->assertEquals( + [ 'Smithee' ], + $this->fetchIds( $res ), + "Search a phrase" ); + + $res = $this->search->searchText( '"smithee is who smiths"' ); + $this->assertEquals( + [], + $this->fetchIds( $res ), + "Phrase search is not sloppy, search terms must be adjacent" ); + + $res = $this->search->searchText( '"is smithee one who smiths"' ); + $this->assertEquals( + [], + $this->fetchIds( $res ), + "Phrase search is ordered" ); + } + + public function testPhraseSearchHighlight() { + $phrase = "smithee is one who smiths"; + $res = $this->search->searchText( "\"$phrase\"" ); + $match = $res->next(); + $snippet = "A <span class='searchmatch'>" . $phrase . "</span>"; + $this->assertStringStartsWith( $snippet, + $match->getTextSnippet( $res->termMatches() ), + "Highlight a phrase search" ); + } + + public function testTextPowerSearch() { + $this->search->setNamespaces( [ 0, 1, 4 ] ); + $this->assertEquals( + [ + 'Smithee', + 'Talk:Not Main Page', + ], + $this->fetchIds( $this->search->searchText( 'smithee' ) ), + "Power search" ); + } + + public function testTitleSearch() { + $this->assertEquals( + [ + 'Alan Smithee', + 'Smithee', + ], + $this->fetchIds( $this->search->searchTitle( 'smithee' ) ), + "Title search" ); + } + + public function testTextTitlePowerSearch() { + $this->search->setNamespaces( [ 0, 1, 4 ] ); + $this->assertEquals( + [ + 'Alan Smithee', + 'Smithee', + 'Talk:Smithee', + ], + $this->fetchIds( $this->search->searchTitle( 'smithee' ) ), + "Title power search" ); + } + + public function provideCompletionSearchMustRespectCapitalLinkOverrides() { + return [ + 'Searching for "smithee" finds Smithee on NS_MAIN' => [ + 'smithee', + 'Smithee', + [ NS_MAIN ], + ], + 'Searching for "search is" will finds "search is not Search" on NS_CATEGORY' => [ + 'search is', + 'Category:search is not Search', + [ NS_CATEGORY ], + ], + 'Searching for "Search is" will finds "search is not Search" on NS_CATEGORY' => [ + 'Search is', + 'Category:Search is not search', + [ NS_CATEGORY ], + ], + ]; + } + + /** + * Test that the search query is not munged using wrong CapitalLinks setup + * (in other test that the default search backend can benefit from wgCapitalLinksOverride) + * Guard against regressions like T208255 + * @dataProvider provideCompletionSearchMustRespectCapitalLinkOverrides + * @covers SearchEngine::completionSearch + * @covers PrefixSearch::defaultSearchBackend + * @param string $search + * @param string $expectedSuggestion + * @param int[] $namespaces + */ + public function testCompletionSearchMustRespectCapitalLinkOverrides( + $search, + $expectedSuggestion, + array $namespaces + ) { + $this->search->setNamespaces( $namespaces ); + $results = $this->search->completionSearch( $search ); + $this->assertEquals( 1, $results->getSize() ); + $this->assertEquals( $expectedSuggestion, $results->getSuggestions()[0]->getText() ); + } + + /** + * @covers SearchEngine::getSearchIndexFields + */ + public function testSearchIndexFields() { + /** + * @var $mockEngine SearchEngine + */ + $mockEngine = $this->getMockBuilder( SearchEngine::class ) + ->setMethods( [ 'makeSearchFieldMapping' ] )->getMock(); + + $mockFieldBuilder = function ( $name, $type ) { + $mockField = + $this->getMockBuilder( SearchIndexFieldDefinition::class )->setConstructorArgs( [ + $name, + $type + ] )->getMock(); + + $mockField->expects( $this->any() )->method( 'getMapping' )->willReturn( [ + 'testData' => 'test', + 'name' => $name, + 'type' => $type, + ] ); + + $mockField->expects( $this->any() ) + ->method( 'merge' ) + ->willReturn( $mockField ); + + return $mockField; + }; + + $mockEngine->expects( $this->atLeastOnce() ) + ->method( 'makeSearchFieldMapping' ) + ->willReturnCallback( $mockFieldBuilder ); + + // Not using mock since PHPUnit mocks do not work properly with references in params + $this->setTemporaryHook( 'SearchIndexFields', + function ( &$fields, SearchEngine $engine ) use ( $mockFieldBuilder ) { + $fields['testField'] = + $mockFieldBuilder( "testField", SearchIndexField::INDEX_TYPE_TEXT ); + return true; + } ); + + $fields = $mockEngine->getSearchIndexFields(); + $this->assertArrayHasKey( 'language', $fields ); + $this->assertArrayHasKey( 'category', $fields ); + $this->assertInstanceOf( SearchIndexField::class, $fields['testField'] ); + + $mapping = $fields['testField']->getMapping( $mockEngine ); + $this->assertArrayHasKey( 'testData', $mapping ); + $this->assertEquals( 'test', $mapping['testData'] ); + } + + public function hookSearchIndexFields( $mockFieldBuilder, &$fields, SearchEngine $engine ) { + $fields['testField'] = $mockFieldBuilder( "testField", SearchIndexField::INDEX_TYPE_TEXT ); + return true; + } + + public function testAugmentorSearch() { + $this->search->setNamespaces( [ 0, 1, 4 ] ); + $resultSet = $this->search->searchText( 'smithee' ); + // Not using mock since PHPUnit mocks do not work properly with references in params + $this->mergeMwGlobalArrayValue( 'wgHooks', + [ 'SearchResultsAugment' => [ [ $this, 'addAugmentors' ] ] ] ); + $this->search->augmentSearchResults( $resultSet ); + for ( $result = $resultSet->next(); $result; $result = $resultSet->next() ) { + $id = $result->getTitle()->getArticleID(); + $augmentData = "Result:$id:" . $result->getTitle()->getText(); + $augmentData2 = "Result2:$id:" . $result->getTitle()->getText(); + $this->assertEquals( [ 'testSet' => $augmentData, 'testRow' => $augmentData2 ], + $result->getExtensionData() ); + } + } + + public function addAugmentors( &$setAugmentors, &$rowAugmentors ) { + $setAugmentor = $this->createMock( ResultSetAugmentor::class ); + $setAugmentor->expects( $this->once() ) + ->method( 'augmentAll' ) + ->willReturnCallback( function ( SearchResultSet $resultSet ) { + $data = []; + for ( $result = $resultSet->next(); $result; $result = $resultSet->next() ) { + $id = $result->getTitle()->getArticleID(); + $data[$id] = "Result:$id:" . $result->getTitle()->getText(); + } + $resultSet->rewind(); + return $data; + } ); + $setAugmentors['testSet'] = $setAugmentor; + + $rowAugmentor = $this->createMock( ResultAugmentor::class ); + $rowAugmentor->expects( $this->exactly( 2 ) ) + ->method( 'augment' ) + ->willReturnCallback( function ( SearchResult $result ) { + $id = $result->getTitle()->getArticleID(); + return "Result2:$id:" . $result->getTitle()->getText(); + } ); + $rowAugmentors['testRow'] = $rowAugmentor; + } +} |