1<?php 2 3namespace dokuwiki\test\Search\Collection; 4 5use dokuwiki\Search\Collection\PageMetaCollection; 6use dokuwiki\Search\Exception\IndexLockException; 7use dokuwiki\Search\Index\MemoryIndex; 8 9class LookupCollectionTest extends \DokuWikiTest 10{ 11 /** 12 * Add data and directly check the underlying indexes for correctness 13 */ 14 public function testAddEntity() 15 { 16 $index = new MockLookupCollection('a_entity', 'a_token', 'a_freq', 'a_reverse'); 17 $index->lock(); 18 $index->addEntity('wiki:start', ['wiki:logo.png', 'wiki:banner.jpg', 'wiki:icon.svg']); 19 $index->unlock(); 20 21 // check entity index 22 $idxEntity = new MemoryIndex('a_entity'); 23 $this->assertEquals('wiki:start', $idxEntity->retrieveRow(0)); 24 25 // check token index (single file, no suffix) 26 $idxToken = new MemoryIndex('a_token'); 27 $this->assertEquals('wiki:logo.png', $idxToken->retrieveRow(0)); 28 $this->assertEquals('wiki:banner.jpg', $idxToken->retrieveRow(1)); 29 $this->assertEquals('wiki:icon.svg', $idxToken->retrieveRow(2)); 30 31 // check frequency index — all frequencies are 1 (written without *1) 32 $idxFreq = new MemoryIndex('a_freq'); 33 $this->assertEquals('0', $idxFreq->retrieveRow(0)); // entity 0 with implicit freq 1 34 $this->assertEquals('0', $idxFreq->retrieveRow(1)); 35 $this->assertEquals('0', $idxFreq->retrieveRow(2)); 36 37 // check reverse index 38 $idxRev = new MemoryIndex('a_reverse'); 39 $this->assertEquals('0:1:2', $idxRev->retrieveRow(0)); 40 } 41 42 /** 43 * Duplicate tokens should be deduplicated 44 */ 45 public function testAddEntityDedup() 46 { 47 $index = new MockLookupCollection('b_entity', 'b_token', 'b_freq', 'b_reverse'); 48 $index->lock(); 49 $index->addEntity('wiki:start', ['wiki:logo.png', 'wiki:logo.png', 'wiki:banner.jpg']); 50 $index->unlock(); 51 52 $idxToken = new MemoryIndex('b_token'); 53 $this->assertEquals('wiki:logo.png', $idxToken->retrieveRow(0)); 54 $this->assertEquals('wiki:banner.jpg', $idxToken->retrieveRow(1)); 55 56 $idxRev = new MemoryIndex('b_reverse'); 57 $this->assertEquals('0:1', $idxRev->retrieveRow(0)); 58 } 59 60 /** 61 * Updating an entity should remove old tokens and add new ones 62 */ 63 public function testUpdateEntity() 64 { 65 $index = new MockLookupCollection('c_entity', 'c_token', 'c_freq', 'c_reverse'); 66 67 // initial add 68 $index->lock(); 69 $index->addEntity('wiki:start', ['wiki:logo.png', 'wiki:banner.jpg']); 70 $index->unlock(); 71 72 // update: remove logo, keep banner, add icon 73 $index->lock(); 74 $index->addEntity('wiki:start', ['wiki:banner.jpg', 'wiki:icon.svg']); 75 $index->unlock(); 76 77 // logo should be removed from frequency index 78 $idxFreq = new MemoryIndex('c_freq'); 79 $this->assertEquals('', $idxFreq->retrieveRow(0)); // logo removed 80 $this->assertEquals('0', $idxFreq->retrieveRow(1)); // banner still on entity 0 81 $this->assertEquals('0', $idxFreq->retrieveRow(2)); // icon added on entity 0 82 83 // reverse index should only have banner and icon 84 $idxRev = new MemoryIndex('c_reverse'); 85 $this->assertEquals('1:2', $idxRev->retrieveRow(0)); 86 } 87 88 /** 89 * Test reverse assignments returns two-level structure with empty group key 90 */ 91 public function testReverseAssignments() 92 { 93 $index = new MockLookupCollection('d_entity', 'd_token', 'd_freq', 'd_reverse'); 94 $index->lock(); 95 $index->addEntity('wiki:start', ['wiki:logo.png', 'wiki:banner.jpg']); 96 $index->unlock(); 97 98 $result = $index->getReverseAssignments('wiki:start'); 99 $this->assertEquals(['' => [0 => 0, 1 => 0]], $result); 100 } 101 102 /** 103 * Adding entity without lock should throw exception 104 */ 105 public function testAddEntityWithoutLock() 106 { 107 $this->expectException(IndexLockException::class); 108 109 $index = new MockLookupCollection(); 110 $index->addEntity('wiki:start', ['wiki:logo.png']); 111 } 112 113 /** 114 * Adding empty token list should clear entity from indexes 115 */ 116 public function testEmptyTokens() 117 { 118 $index = new MockLookupCollection('f_entity', 'f_token', 'f_freq', 'f_reverse'); 119 120 // add some tokens first 121 $index->lock(); 122 $index->addEntity('wiki:start', ['wiki:logo.png']); 123 $index->unlock(); 124 125 // now clear 126 $index->lock(); 127 $index->addEntity('wiki:start', []); 128 $index->unlock(); 129 130 // frequency index should be empty for this token 131 $idxFreq = new MemoryIndex('f_freq'); 132 $this->assertEquals('', $idxFreq->retrieveRow(0)); 133 134 // reverse index should be empty 135 $idxRev = new MemoryIndex('f_reverse'); 136 $this->assertEquals('', $idxRev->retrieveRow(0)); 137 } 138 139 /** 140 * Test that PageMetaCollection('relation_media') uses correct index names 141 */ 142 public function testMediaCollection() 143 { 144 $index = new PageMetaCollection('relation_media'); 145 $index->lock(); 146 $index->addEntity('wiki:start', ['wiki:logo.png', 'wiki:banner.jpg']); 147 $index->unlock(); 148 149 $idxToken = new MemoryIndex('relation_media_w'); 150 $this->assertEquals('wiki:logo.png', $idxToken->retrieveRow(0)); 151 $this->assertEquals('wiki:banner.jpg', $idxToken->retrieveRow(1)); 152 153 $idxRev = new MemoryIndex('relation_media_p'); 154 $this->assertEquals('0:1', $idxRev->retrieveRow(0)); 155 } 156 157 /** 158 * Test that PageMetaCollection('relation_references') uses correct index names 159 */ 160 public function testReferencesCollection() 161 { 162 $index = new PageMetaCollection('relation_references'); 163 $index->lock(); 164 $index->addEntity('wiki:start', ['wiki:syntax', 'wiki:welcome']); 165 $index->unlock(); 166 167 $idxToken = new MemoryIndex('relation_references_w'); 168 $this->assertEquals('wiki:syntax', $idxToken->retrieveRow(0)); 169 $this->assertEquals('wiki:welcome', $idxToken->retrieveRow(1)); 170 171 $idxRev = new MemoryIndex('relation_references_p'); 172 $this->assertEquals('0:1', $idxRev->retrieveRow(0)); 173 174 $result = $index->getReverseAssignments('wiki:start'); 175 $this->assertEquals(['' => [0 => 0, 1 => 0]], $result); 176 } 177 178 /** 179 * resolveTokens should deduplicate and assign frequency 1 under empty group key 180 */ 181 public function testResolveTokens() 182 { 183 $index = new MockLookupCollection('rt_entity', 'rt_token', 'rt_freq', 'rt_reverse'); 184 $index->lock(); 185 186 $result = $this->callInaccessibleMethod($index, 'resolveTokens', [ 187 ['wiki:logo.png', 'wiki:banner.jpg', 'wiki:logo.png'], 188 ]); 189 190 // all tokens under empty group key 191 $this->assertArrayHasKey('', $result); 192 $this->assertCount(2, $result['']); // deduplicated 193 194 // token IDs are sequential: logo=0, banner=1 195 $this->assertEquals(1, $result[''][0]); // logo freq=1 196 $this->assertEquals(1, $result[''][1]); // banner freq=1 197 } 198 199 /** 200 * resolveTokens with empty input should return empty array 201 */ 202 public function testResolveTokensEmpty() 203 { 204 $index = new MockLookupCollection('rte_entity', 'rte_token', 'rte_freq', 'rte_reverse'); 205 $index->lock(); 206 207 $result = $this->callInaccessibleMethod($index, 'resolveTokens', [[]]); 208 209 $this->assertEmpty($result); 210 } 211 212 /** 213 * countTokens should deduplicate and assign frequency 1 214 */ 215 public function testCountTokens() 216 { 217 $index = new MockLookupCollection(); 218 219 $result = $this->callInaccessibleMethod($index, 'countTokens', [ 220 ['wiki:logo.png', 'wiki:banner.jpg', 'wiki:logo.png'], 221 ]); 222 223 $this->assertEquals([ 224 'wiki:logo.png' => 1, 225 'wiki:banner.jpg' => 1, 226 ], $result); 227 } 228} 229