1ede46466SAndreas Gohr<?php 2ede46466SAndreas Gohr 3ede46466SAndreas Gohrnamespace dokuwiki\test\Search; 4ede46466SAndreas Gohr 5ede46466SAndreas Gohruse dokuwiki\Search\Indexer; 6ede46466SAndreas Gohruse dokuwiki\Search\Index\FileIndex; 7*2ff7e61cSAndreas Gohruse dokuwiki\Search\MetadataSearch; 8ede46466SAndreas Gohr 9ede46466SAndreas Gohr/** 10ede46466SAndreas Gohr * Tests the Indexer class 11ede46466SAndreas Gohr */ 12ede46466SAndreas Gohrclass IndexerTest extends \DokuWikiTest 13ede46466SAndreas Gohr{ 14ede46466SAndreas Gohr /** 15ede46466SAndreas Gohr * Test basic page indexing via addPage 16ede46466SAndreas Gohr */ 17ede46466SAndreas Gohr public function testAddPage() 18ede46466SAndreas Gohr { 19ede46466SAndreas Gohr $indexer = new Indexer(); 20ede46466SAndreas Gohr 21ede46466SAndreas Gohr saveWikiText('testpage', 'Foo bar baz.', 'Test initialization'); 22ede46466SAndreas Gohr $indexer->addPage('testpage'); 23ede46466SAndreas Gohr 24ede46466SAndreas Gohr // page should be in the entity index 25ede46466SAndreas Gohr $pageIndex = new FileIndex('page'); 26ede46466SAndreas Gohr $result = $pageIndex->search('/^testpage$/'); 27ede46466SAndreas Gohr $this->assertNotEmpty($result, 'testpage not found in page.idx'); 28ede46466SAndreas Gohr } 29ede46466SAndreas Gohr 30ede46466SAndreas Gohr /** 31ede46466SAndreas Gohr * Test that deletePage clears data 32ede46466SAndreas Gohr */ 33ede46466SAndreas Gohr public function testDeletePage() 34ede46466SAndreas Gohr { 35ede46466SAndreas Gohr $indexer = new Indexer(); 36ede46466SAndreas Gohr 37ede46466SAndreas Gohr saveWikiText('delpage', 'Delete me content.', 'Test initialization'); 38ede46466SAndreas Gohr $indexer->addPage('delpage'); 39ede46466SAndreas Gohr $indexer->deletePage('delpage', true); 40ede46466SAndreas Gohr 41ede46466SAndreas Gohr // page entity persists in page.idx but data is cleared 42ede46466SAndreas Gohr $pageIndex = new FileIndex('page'); 43ede46466SAndreas Gohr $result = $pageIndex->search('/^delpage$/'); 44ede46466SAndreas Gohr $this->assertNotEmpty($result, 'delpage should persist in page.idx'); 45ede46466SAndreas Gohr } 46ede46466SAndreas Gohr 47ede46466SAndreas Gohr /** 48ede46466SAndreas Gohr * Test renamePage clears old and indexes new 49ede46466SAndreas Gohr */ 50ede46466SAndreas Gohr public function testRenamePage() 51ede46466SAndreas Gohr { 52ede46466SAndreas Gohr $indexer = new Indexer(); 53ede46466SAndreas Gohr 54ede46466SAndreas Gohr saveWikiText('old_name', 'Old page content words.', 'Test initialization'); 55ede46466SAndreas Gohr $indexer->addPage('old_name'); 56ede46466SAndreas Gohr 57ede46466SAndreas Gohr // move the page on disk 58ede46466SAndreas Gohr io_rename(wikiFN('old_name'), wikiFN('new_name')); 59ede46466SAndreas Gohr saveWikiText('new_name', 'Old page content words.', 'Renamed'); 60ede46466SAndreas Gohr 61ede46466SAndreas Gohr $indexer->renamePage('old_name', 'new_name'); 62ede46466SAndreas Gohr 63ede46466SAndreas Gohr // new page should be indexed 64ede46466SAndreas Gohr $pageIndex = new FileIndex('page'); 65ede46466SAndreas Gohr $result = $pageIndex->search('/^new_name$/'); 66ede46466SAndreas Gohr $this->assertNotEmpty($result, 'new_name not found in page.idx after rename'); 67ede46466SAndreas Gohr } 68ede46466SAndreas Gohr 69ede46466SAndreas Gohr /** 70*2ff7e61cSAndreas Gohr * renamePage must preserve the renamed page's outgoing references 71*2ff7e61cSAndreas Gohr * 72*2ff7e61cSAndreas Gohr * The rename only changes the page's name in the index, not its content, so all of 73*2ff7e61cSAndreas Gohr * its index associations - including the pages it links to (relation_references) - 74*2ff7e61cSAndreas Gohr * must survive under the new name. This is what allows a page renamed early during a 75*2ff7e61cSAndreas Gohr * namespace move to still be found as a backlink source for pages moved afterwards. 76*2ff7e61cSAndreas Gohr * It must work even though the destination page is not on disk yet at rename time 77*2ff7e61cSAndreas Gohr * (the move operation writes it only later), so re-indexing from disk cannot be relied 78*2ff7e61cSAndreas Gohr * upon here. 79*2ff7e61cSAndreas Gohr * 80*2ff7e61cSAndreas Gohr * @see https://github.com/dokuwiki/dokuwiki - regression after the indexer rewrite 81*2ff7e61cSAndreas Gohr */ 82*2ff7e61cSAndreas Gohr public function testRenamePagePreservesOutgoingReferences() 83*2ff7e61cSAndreas Gohr { 84*2ff7e61cSAndreas Gohr $indexer = new Indexer(); 85*2ff7e61cSAndreas Gohr 86*2ff7e61cSAndreas Gohr saveWikiText('refsource', '[[target:page]]', 'Test initialization'); 87*2ff7e61cSAndreas Gohr $indexer->addPage('refsource'); 88*2ff7e61cSAndreas Gohr 89*2ff7e61cSAndreas Gohr $search = new MetadataSearch(); 90*2ff7e61cSAndreas Gohr 91*2ff7e61cSAndreas Gohr // sanity: the source page references target:page 92*2ff7e61cSAndreas Gohr $value = 'target:page'; 93*2ff7e61cSAndreas Gohr $this->assertEquals(['refsource'], $search->lookupKey('relation_references', $value)); 94*2ff7e61cSAndreas Gohr 95*2ff7e61cSAndreas Gohr // rename the source page WITHOUT writing the destination to disk first, 96*2ff7e61cSAndreas Gohr // mimicking how the move plugin calls renamePage before saving the new page 97*2ff7e61cSAndreas Gohr $indexer->renamePage('refsource', 'moved:newsource'); 98*2ff7e61cSAndreas Gohr 99*2ff7e61cSAndreas Gohr // the outgoing reference must now belong to the renamed page 100*2ff7e61cSAndreas Gohr $value = 'target:page'; 101*2ff7e61cSAndreas Gohr $this->assertEquals( 102*2ff7e61cSAndreas Gohr ['moved:newsource'], 103*2ff7e61cSAndreas Gohr $search->lookupKey('relation_references', $value), 104*2ff7e61cSAndreas Gohr 'rename lost the outgoing reference of the renamed page' 105*2ff7e61cSAndreas Gohr ); 106*2ff7e61cSAndreas Gohr } 107*2ff7e61cSAndreas Gohr 108*2ff7e61cSAndreas Gohr /** 109*2ff7e61cSAndreas Gohr * renamePage onto a name that already has its own index entry 110*2ff7e61cSAndreas Gohr * 111*2ff7e61cSAndreas Gohr * The renamed page must take over the destination name (keeping its own data) while the 112*2ff7e61cSAndreas Gohr * destination's previous data is dropped. The stale destination row must be vacated so the 113*2ff7e61cSAndreas Gohr * name resolves only to the renamed entity and does not leak as a phantom page. 114*2ff7e61cSAndreas Gohr */ 115*2ff7e61cSAndreas Gohr public function testRenamePageOntoExistingPage() 116*2ff7e61cSAndreas Gohr { 117*2ff7e61cSAndreas Gohr $indexer = new Indexer(); 118*2ff7e61cSAndreas Gohr 119*2ff7e61cSAndreas Gohr saveWikiText('src', '[[target:fromsrc]]', 'Test initialization'); 120*2ff7e61cSAndreas Gohr $indexer->addPage('src'); 121*2ff7e61cSAndreas Gohr saveWikiText('dst', '[[target:fromdst]]', 'Test initialization'); 122*2ff7e61cSAndreas Gohr $indexer->addPage('dst'); 123*2ff7e61cSAndreas Gohr 124*2ff7e61cSAndreas Gohr $indexer->renamePage('src', 'dst'); 125*2ff7e61cSAndreas Gohr 126*2ff7e61cSAndreas Gohr $search = new MetadataSearch(); 127*2ff7e61cSAndreas Gohr 128*2ff7e61cSAndreas Gohr // dst now carries src's outgoing reference ... 129*2ff7e61cSAndreas Gohr $value = 'target:fromsrc'; 130*2ff7e61cSAndreas Gohr $this->assertEquals(['dst'], $search->lookupKey('relation_references', $value)); 131*2ff7e61cSAndreas Gohr // ... and the destination's previous reference is gone 132*2ff7e61cSAndreas Gohr $value = 'target:fromdst'; 133*2ff7e61cSAndreas Gohr $this->assertEquals([], $search->lookupKey('relation_references', $value)); 134*2ff7e61cSAndreas Gohr 135*2ff7e61cSAndreas Gohr // exactly one entity named 'dst', the old name and any phantom entry are gone 136*2ff7e61cSAndreas Gohr $allPages = $indexer->getAllPages(); 137*2ff7e61cSAndreas Gohr $this->assertSame(['dst'], array_values(array_filter($allPages, fn($p) => $p === 'dst' || $p === 'src'))); 138*2ff7e61cSAndreas Gohr } 139*2ff7e61cSAndreas Gohr 140*2ff7e61cSAndreas Gohr /** 141ede46466SAndreas Gohr * Test that clear removes all index files 142ede46466SAndreas Gohr */ 143ede46466SAndreas Gohr public function testClear() 144ede46466SAndreas Gohr { 145ede46466SAndreas Gohr global $conf; 146ede46466SAndreas Gohr $indexer = new Indexer(); 147ede46466SAndreas Gohr 148ede46466SAndreas Gohr saveWikiText('clearpage', 'Some words to index.', 'Test initialization'); 149ede46466SAndreas Gohr $indexer->addPage('clearpage'); 150ede46466SAndreas Gohr 151ede46466SAndreas Gohr $this->assertFileExists($conf['indexdir'] . '/page.idx'); 152ede46466SAndreas Gohr 153ede46466SAndreas Gohr $indexer->clear(); 154ede46466SAndreas Gohr 155ede46466SAndreas Gohr $this->assertFileDoesNotExist($conf['indexdir'] . '/page.idx'); 156ede46466SAndreas Gohr } 157ede46466SAndreas Gohr 158ede46466SAndreas Gohr /** 159ede46466SAndreas Gohr * Test that getVersion returns a version string 160ede46466SAndreas Gohr */ 161ede46466SAndreas Gohr public function testGetVersion() 162ede46466SAndreas Gohr { 163ede46466SAndreas Gohr $indexer = new Indexer(); 164ede46466SAndreas Gohr $version = $indexer->getVersion(); 165ede46466SAndreas Gohr $this->assertNotEmpty($version); 166ede46466SAndreas Gohr $this->assertIsString((string)$version); 167ede46466SAndreas Gohr } 168ede46466SAndreas Gohr 169ede46466SAndreas Gohr /** 170ede46466SAndreas Gohr * Test needsIndexing returns true for new pages 171ede46466SAndreas Gohr */ 172ede46466SAndreas Gohr public function testNeedsIndexing() 173ede46466SAndreas Gohr { 174ede46466SAndreas Gohr $indexer = new Indexer(); 175ede46466SAndreas Gohr 176ede46466SAndreas Gohr saveWikiText('needsidx', 'Some content.', 'Test initialization'); 177ede46466SAndreas Gohr $this->assertTrue($indexer->needsIndexing('needsidx')); 178ede46466SAndreas Gohr 179ede46466SAndreas Gohr $indexer->addPage('needsidx'); 180ede46466SAndreas Gohr // ensure the .indexed tag is strictly newer than the wiki file 181ede46466SAndreas Gohr touch(metaFN('needsidx', '.indexed'), time() + 1); 182ede46466SAndreas Gohr $this->assertFalse($indexer->needsIndexing('needsidx')); 183ede46466SAndreas Gohr $this->assertTrue($indexer->needsIndexing('needsidx', true)); // force 184ede46466SAndreas Gohr } 185ede46466SAndreas Gohr 186ede46466SAndreas Gohr /** 187ede46466SAndreas Gohr * Test the logger callback 188ede46466SAndreas Gohr */ 189ede46466SAndreas Gohr public function testLogger() 190ede46466SAndreas Gohr { 191ede46466SAndreas Gohr $messages = []; 192ede46466SAndreas Gohr $indexer = (new Indexer())->setLogger(function ($msg) use (&$messages) { 193ede46466SAndreas Gohr $messages[] = $msg; 194ede46466SAndreas Gohr }); 195ede46466SAndreas Gohr 196ede46466SAndreas Gohr saveWikiText('logpage', 'Log test content.', 'Test initialization'); 197ede46466SAndreas Gohr $indexer->addPage('logpage'); 198ede46466SAndreas Gohr 199ede46466SAndreas Gohr // ensure the .indexed tag is strictly newer so second call detects "up to date" 200ede46466SAndreas Gohr touch(metaFN('logpage', '.indexed'), time() + 1); 201ede46466SAndreas Gohr 202ede46466SAndreas Gohr $indexer->addPage('logpage'); 203ede46466SAndreas Gohr $this->assertNotEmpty($messages); 204ede46466SAndreas Gohr $this->assertStringContainsString('up to date', end($messages)); 205ede46466SAndreas Gohr } 206ede46466SAndreas Gohr} 207