summaryrefslogtreecommitdiff
path: root/www/wiki/tests/phpunit/includes/PrefixSearchTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/tests/phpunit/includes/PrefixSearchTest.php')
-rw-r--r--www/wiki/tests/phpunit/includes/PrefixSearchTest.php386
1 files changed, 386 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/includes/PrefixSearchTest.php b/www/wiki/tests/phpunit/includes/PrefixSearchTest.php
new file mode 100644
index 00000000..ed34a8ab
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/PrefixSearchTest.php
@@ -0,0 +1,386 @@
+<?php
+
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Search
+ * @group Database
+ * @covers PrefixSearch
+ */
+class PrefixSearchTest extends MediaWikiLangTestCase {
+ const NS_NONCAP = 12346;
+
+ private $originalHandlers;
+
+ public function addDBDataOnce() {
+ if ( !$this->isWikitextNS( NS_MAIN ) ) {
+ // tests are skipped if NS_MAIN is not wikitext
+ return;
+ }
+
+ $this->insertPage( 'Sandbox' );
+ $this->insertPage( 'Bar' );
+ $this->insertPage( 'Example' );
+ $this->insertPage( 'Example Bar' );
+ $this->insertPage( 'Example Foo' );
+ $this->insertPage( 'Example Foo/Bar' );
+ $this->insertPage( 'Example/Baz' );
+ $this->insertPage( 'Redirect test', '#REDIRECT [[Redirect Test]]' );
+ $this->insertPage( 'Redirect Test' );
+ $this->insertPage( 'Redirect Test Worse Result' );
+ $this->insertPage( 'Redirect test2', '#REDIRECT [[Redirect Test2]]' );
+ $this->insertPage( 'Redirect TEST2', '#REDIRECT [[Redirect Test2]]' );
+ $this->insertPage( 'Redirect Test2' );
+ $this->insertPage( 'Redirect Test2 Worse Result' );
+
+ $this->insertPage( 'Talk:Sandbox' );
+ $this->insertPage( 'Talk:Example' );
+
+ $this->insertPage( 'User:Example' );
+
+ $this->insertPage( Title::makeTitle( self::NS_NONCAP, 'Bar' ) );
+ $this->insertPage( Title::makeTitle( self::NS_NONCAP, 'Upper' ) );
+ $this->insertPage( Title::makeTitle( self::NS_NONCAP, 'sandbox' ) );
+ }
+
+ protected function setUp() {
+ parent::setUp();
+
+ if ( !$this->isWikitextNS( NS_MAIN ) ) {
+ $this->markTestSkipped( 'Main namespace does not support wikitext.' );
+ }
+
+ // Avoid special pages from extensions interferring with the tests
+ $this->setMwGlobals( [
+ 'wgSpecialPages' => [],
+ 'wgHooks' => [],
+ 'wgExtraNamespaces' => [ self::NS_NONCAP => 'NonCap' ],
+ 'wgCapitalLinkOverrides' => [ self::NS_NONCAP => false ],
+ ] );
+
+ $this->originalHandlers = TestingAccessWrapper::newFromClass( Hooks::class )->handlers;
+ TestingAccessWrapper::newFromClass( Hooks::class )->handlers = [];
+
+ // Clear caches so that our new namespace appears
+ MWNamespace::clearCaches();
+ Language::factory( 'en' )->resetNamespaces();
+
+ SpecialPageFactory::resetList();
+ }
+
+ public function tearDown() {
+ MWNamespace::clearCaches();
+ Language::factory( 'en' )->resetNamespaces();
+
+ parent::tearDown();
+
+ TestingAccessWrapper::newFromClass( Hooks::class )->handlers = $this->originalHandlers;
+
+ SpecialPageFactory::resetList();
+ }
+
+ protected function searchProvision( array $results = null ) {
+ if ( $results === null ) {
+ $this->setMwGlobals( 'wgHooks', [] );
+ } else {
+ $this->setMwGlobals( 'wgHooks', [
+ 'PrefixSearchBackend' => [
+ function ( $namespaces, $search, $limit, &$srchres ) use ( $results ) {
+ $srchres = $results;
+ return false;
+ }
+ ],
+ ] );
+ }
+ }
+
+ public static function provideSearch() {
+ return [
+ [ [
+ 'Empty string',
+ 'query' => '',
+ 'results' => [],
+ ] ],
+ [ [
+ 'Main namespace with title prefix',
+ 'query' => 'Ex',
+ 'results' => [
+ 'Example',
+ 'Example/Baz',
+ 'Example Bar',
+ ],
+ // Third result when testing offset
+ 'offsetresult' => [
+ 'Example Foo',
+ ],
+ ] ],
+ [ [
+ 'Talk namespace prefix',
+ 'query' => 'Talk:',
+ 'results' => [
+ 'Talk:Example',
+ 'Talk:Sandbox',
+ ],
+ ] ],
+ [ [
+ 'User namespace prefix',
+ 'query' => 'User:',
+ 'results' => [
+ 'User:Example',
+ ],
+ ] ],
+ [ [
+ 'Special namespace prefix',
+ 'query' => 'Special:',
+ 'results' => [
+ 'Special:ActiveUsers',
+ 'Special:AllMessages',
+ 'Special:AllMyUploads',
+ ],
+ // Third result when testing offset
+ 'offsetresult' => [
+ 'Special:AllPages',
+ ],
+ ] ],
+ [ [
+ 'Special namespace with prefix',
+ 'query' => 'Special:Un',
+ 'results' => [
+ 'Special:Unblock',
+ 'Special:UncategorizedCategories',
+ 'Special:UncategorizedFiles',
+ ],
+ // Third result when testing offset
+ 'offsetresult' => [
+ 'Special:UncategorizedPages',
+ ],
+ ] ],
+ [ [
+ 'Special page name',
+ 'query' => 'Special:EditWatchlist',
+ 'results' => [
+ 'Special:EditWatchlist',
+ ],
+ ] ],
+ [ [
+ 'Special page subpages',
+ 'query' => 'Special:EditWatchlist/',
+ 'results' => [
+ 'Special:EditWatchlist/clear',
+ 'Special:EditWatchlist/raw',
+ ],
+ ] ],
+ [ [
+ 'Special page subpages with prefix',
+ 'query' => 'Special:EditWatchlist/cl',
+ 'results' => [
+ 'Special:EditWatchlist/clear',
+ ],
+ ] ],
+ [ [
+ 'Namespace with case sensitive first letter',
+ 'query' => 'NonCap:upper',
+ 'results' => []
+ ] ],
+ [ [
+ 'Multinamespace search',
+ 'query' => 'B',
+ 'results' => [
+ 'Bar',
+ 'NonCap:Bar',
+ ],
+ 'namespaces' => [ NS_MAIN, self::NS_NONCAP ],
+ ] ],
+ [ [
+ 'Multinamespace search with lowercase first letter',
+ 'query' => 'sand',
+ 'results' => [
+ 'Sandbox',
+ 'NonCap:sandbox',
+ ],
+ 'namespaces' => [ NS_MAIN, self::NS_NONCAP ],
+ ] ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideSearch
+ * @covers PrefixSearch::search
+ * @covers PrefixSearch::searchBackend
+ */
+ public function testSearch( array $case ) {
+ $this->searchProvision( null );
+
+ $namespaces = isset( $case['namespaces'] ) ? $case['namespaces'] : [];
+
+ if ( wfGetDB( DB_REPLICA )->getType() === 'postgres' ) {
+ // Postgres will sort lexicographically on utf8 code units (" " before "/")
+ sort( $case['results'], SORT_STRING );
+ }
+
+ $searcher = new StringPrefixSearch;
+ $results = $searcher->search( $case['query'], 3, $namespaces );
+ $this->assertEquals(
+ $case['results'],
+ $results,
+ $case[0]
+ );
+ }
+
+ /**
+ * @dataProvider provideSearch
+ * @covers PrefixSearch::search
+ * @covers PrefixSearch::searchBackend
+ */
+ public function testSearchWithOffset( array $case ) {
+ $this->searchProvision( null );
+
+ $namespaces = isset( $case['namespaces'] ) ? $case['namespaces'] : [];
+
+ $searcher = new StringPrefixSearch;
+ $results = $searcher->search( $case['query'], 3, $namespaces, 1 );
+
+ if ( wfGetDB( DB_REPLICA )->getType() === 'postgres' ) {
+ // Postgres will sort lexicographically on utf8 code units (" " before "/")
+ sort( $case['results'], SORT_STRING );
+ }
+
+ // We don't expect the first result when offsetting
+ array_shift( $case['results'] );
+ // And sometimes we expect a different last result
+ $expected = isset( $case['offsetresult'] ) ?
+ array_merge( $case['results'], $case['offsetresult'] ) :
+ $case['results'];
+
+ $this->assertEquals(
+ $expected,
+ $results,
+ $case[0]
+ );
+ }
+
+ public static function provideSearchBackend() {
+ return [
+ [ [
+ 'Simple case',
+ 'provision' => [
+ 'Bar',
+ 'Barcelona',
+ 'Barbara',
+ ],
+ 'query' => 'Bar',
+ 'results' => [
+ 'Bar',
+ 'Barcelona',
+ 'Barbara',
+ ],
+ ] ],
+ [ [
+ 'Exact match not on top (T72958)',
+ 'provision' => [
+ 'Barcelona',
+ 'Bar',
+ 'Barbara',
+ ],
+ 'query' => 'Bar',
+ 'results' => [
+ 'Bar',
+ 'Barcelona',
+ 'Barbara',
+ ],
+ ] ],
+ [ [
+ 'Exact match missing (T72958)',
+ 'provision' => [
+ 'Barcelona',
+ 'Barbara',
+ 'Bart',
+ ],
+ 'query' => 'Bar',
+ 'results' => [
+ 'Bar',
+ 'Barcelona',
+ 'Barbara',
+ ],
+ ] ],
+ [ [
+ 'Exact match missing and not existing',
+ 'provision' => [
+ 'Exile',
+ 'Exist',
+ 'External',
+ ],
+ 'query' => 'Ex',
+ 'results' => [
+ 'Exile',
+ 'Exist',
+ 'External',
+ ],
+ ] ],
+ [ [
+ "Exact match shouldn't override already found match if " .
+ "exact is redirect and found isn't",
+ 'provision' => [
+ // Target of the exact match is low in the list
+ 'Redirect Test Worse Result',
+ 'Redirect Test',
+ ],
+ 'query' => 'redirect test',
+ 'results' => [
+ // Redirect target is pulled up and exact match isn't added
+ 'Redirect Test',
+ 'Redirect Test Worse Result',
+ ],
+ ] ],
+ [ [
+ "Exact match shouldn't override already found match if " .
+ "both exact match and found match are redirect",
+ 'provision' => [
+ // Another redirect to the same target as the exact match
+ // is low in the list
+ 'Redirect Test2 Worse Result',
+ 'Redirect test2',
+ ],
+ 'query' => 'redirect TEST2',
+ 'results' => [
+ // Found redirect is pulled to the top and exact match isn't
+ // added
+ 'Redirect test2',
+ 'Redirect Test2 Worse Result',
+ ],
+ ] ],
+ [ [
+ "Exact match should override any already found matches that " .
+ "are redirects to it",
+ 'provision' => [
+ // Another redirect to the same target as the exact match
+ // is low in the list
+ 'Redirect Test Worse Result',
+ 'Redirect test',
+ ],
+ 'query' => 'Redirect Test',
+ 'results' => [
+ // Found redirect is pulled to the top and exact match isn't
+ // added
+ 'Redirect Test',
+ 'Redirect Test Worse Result',
+ ],
+ ] ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideSearchBackend
+ * @covers PrefixSearch::searchBackend
+ */
+ public function testSearchBackend( array $case ) {
+ $this->searchProvision( $case['provision'] );
+ $searcher = new StringPrefixSearch;
+ $results = $searcher->search( $case['query'], 3 );
+ $this->assertEquals(
+ $case['results'],
+ $results,
+ $case[0]
+ );
+ }
+}