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