]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - tests/bookmark/BookmarkFilterTest.php
New plugin hook: ability to add custom filters to Shaarli search engine
[github/shaarli/Shaarli.git] / tests / bookmark / BookmarkFilterTest.php
1 <?php
2
3 namespace Shaarli\Bookmark;
4
5 use malkusch\lock\mutex\NoMutex;
6 use ReferenceLinkDB;
7 use Shaarli\Config\ConfigManager;
8 use Shaarli\History;
9 use Shaarli\Plugin\PluginManager;
10 use Shaarli\TestCase;
11
12 /**
13 * Class BookmarkFilterTest.
14 */
15 class BookmarkFilterTest extends TestCase
16 {
17 /**
18 * @var string Test datastore path.
19 */
20 protected static $testDatastore = 'sandbox/datastore.php';
21 /**
22 * @var BookmarkFilter instance.
23 */
24 protected static $linkFilter;
25
26 /**
27 * @var ReferenceLinkDB instance
28 */
29 protected static $refDB;
30
31 /**
32 * @var BookmarkFileService instance
33 */
34 protected static $bookmarkService;
35
36 /** @var PluginManager */
37 protected static $pluginManager;
38
39 /**
40 * Instantiate linkFilter with ReferenceLinkDB data.
41 */
42 public static function setUpBeforeClass(): void
43 {
44
45 $mutex = new NoMutex();
46 $conf = new ConfigManager('tests/utils/config/configJson');
47 $conf->set('resource.datastore', self::$testDatastore);
48 static::$pluginManager = new PluginManager($conf);
49 self::$refDB = new \ReferenceLinkDB();
50 self::$refDB->write(self::$testDatastore);
51 $history = new History('sandbox/history.php');
52 self::$bookmarkService = new \FakeBookmarkService($conf, static::$pluginManager, $history, $mutex, true);
53 self::$linkFilter = new BookmarkFilter(self::$bookmarkService->getBookmarks(), $conf, static::$pluginManager);
54 }
55
56 /**
57 * Blank filter.
58 */
59 public function testFilter()
60 {
61 $this->assertEquals(
62 self::$refDB->countLinks(),
63 count(self::$linkFilter->filter('', ''))
64 );
65
66 $this->assertEquals(
67 self::$refDB->countLinks(),
68 count(self::$linkFilter->filter('', '', 'all'))
69 );
70
71 $this->assertEquals(
72 self::$refDB->countLinks(),
73 count(self::$linkFilter->filter('', '', 'randomstr'))
74 );
75
76 // Private only.
77 $this->assertEquals(
78 self::$refDB->countPrivateLinks(),
79 count(self::$linkFilter->filter('', '', false, 'private'))
80 );
81
82 // Public only.
83 $this->assertEquals(
84 self::$refDB->countPublicLinks(),
85 count(self::$linkFilter->filter('', '', false, 'public'))
86 );
87
88 $this->assertEquals(
89 ReferenceLinkDB::$NB_LINKS_TOTAL,
90 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, ''))
91 );
92
93 $this->assertEquals(
94 self::$refDB->countUntaggedLinks(),
95 count(
96 self::$linkFilter->filter(
97 BookmarkFilter::$FILTER_TAG,
98 /*$request=*/
99 '',
100 /*$casesensitive=*/
101 false,
102 /*$visibility=*/
103 'all',
104 /*$untaggedonly=*/
105 true
106 )
107 )
108 );
109
110 $this->assertEquals(
111 ReferenceLinkDB::$NB_LINKS_TOTAL,
112 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, ''))
113 );
114 }
115
116 /**
117 * Filter bookmarks using a tag
118 */
119 public function testFilterOneTag()
120 {
121 $this->assertEquals(
122 4,
123 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'web', false))
124 );
125
126 $this->assertEquals(
127 4,
128 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'web', false, 'all'))
129 );
130
131 $this->assertEquals(
132 4,
133 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'web', false, 'default-blabla'))
134 );
135
136 // Private only.
137 $this->assertEquals(
138 1,
139 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'web', false, 'private'))
140 );
141
142 // Public only.
143 $this->assertEquals(
144 3,
145 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'web', false, 'public'))
146 );
147 }
148
149 /**
150 * Filter bookmarks using a tag - case-sensitive
151 */
152 public function testFilterCaseSensitiveTag()
153 {
154 $this->assertEquals(
155 0,
156 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'mercurial', true))
157 );
158
159 $this->assertEquals(
160 1,
161 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'Mercurial', true))
162 );
163 }
164
165 /**
166 * Filter bookmarks using a tag combination
167 */
168 public function testFilterMultipleTags()
169 {
170 $this->assertEquals(
171 2,
172 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'dev cartoon', false))
173 );
174 }
175
176 /**
177 * Filter bookmarks using a non-existent tag
178 */
179 public function testFilterUnknownTag()
180 {
181 $this->assertEquals(
182 0,
183 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'null', false))
184 );
185 }
186
187 /**
188 * Retrieve a link entry with its hash
189 */
190 public function testFilterSmallHash()
191 {
192 $links = self::$linkFilter->filter(BookmarkFilter::$FILTER_HASH, 'IuWvgA');
193
194 $this->assertEquals(
195 1,
196 count($links)
197 );
198
199 $this->assertEquals(
200 'MediaGoblin',
201 $links[7]->getTitle()
202 );
203 }
204
205 /**
206 * No link for this hash
207 */
208 public function testFilterUnknownSmallHash()
209 {
210 $this->expectException(\Shaarli\Bookmark\Exception\BookmarkNotFoundException::class);
211
212 self::$linkFilter->filter(BookmarkFilter::$FILTER_HASH, 'Iblaah');
213 }
214
215 /**
216 * Full-text search - no result found.
217 */
218 public function testFilterFullTextNoResult()
219 {
220 $this->assertEquals(
221 0,
222 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'azertyuiop'))
223 );
224 }
225
226 /**
227 * Full-text search - result from a link's URL
228 */
229 public function testFilterFullTextURL()
230 {
231 $this->assertEquals(
232 2,
233 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'ars.userfriendly.org'))
234 );
235
236 $this->assertEquals(
237 2,
238 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'ars org'))
239 );
240 }
241
242 /**
243 * Full-text search - result from a link's title only
244 */
245 public function testFilterFullTextTitle()
246 {
247 // use miscellaneous cases
248 $this->assertEquals(
249 2,
250 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'userfriendly -'))
251 );
252 $this->assertEquals(
253 2,
254 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'UserFriendly -'))
255 );
256 $this->assertEquals(
257 2,
258 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'uSeRFrIendlY -'))
259 );
260
261 // use miscellaneous case and offset
262 $this->assertEquals(
263 2,
264 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'RFrIendL'))
265 );
266 }
267
268 /**
269 * Full-text search - result from the link's description only
270 */
271 public function testFilterFullTextDescription()
272 {
273 $this->assertEquals(
274 1,
275 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'publishing media'))
276 );
277
278 $this->assertEquals(
279 1,
280 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'mercurial w3c'))
281 );
282
283 $this->assertEquals(
284 3,
285 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, '"free software"'))
286 );
287 }
288
289 /**
290 * Full-text search - result from the link's tags only
291 */
292 public function testFilterFullTextTags()
293 {
294 $this->assertEquals(
295 6,
296 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'web'))
297 );
298
299 $this->assertEquals(
300 6,
301 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'web', 'all'))
302 );
303
304 $this->assertEquals(
305 6,
306 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'web', 'bla'))
307 );
308
309 // Private only.
310 $this->assertEquals(
311 1,
312 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'web', false, 'private'))
313 );
314
315 // Public only.
316 $this->assertEquals(
317 5,
318 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'web', false, 'public'))
319 );
320 }
321
322 /**
323 * Full-text search - result set from mixed sources
324 */
325 public function testFilterFullTextMixed()
326 {
327 $this->assertEquals(
328 3,
329 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'free software'))
330 );
331 }
332
333 /**
334 * Full-text search - test exclusion with '-'.
335 */
336 public function testExcludeSearch()
337 {
338 $this->assertEquals(
339 1,
340 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, 'free -gnu'))
341 );
342
343 $this->assertEquals(
344 ReferenceLinkDB::$NB_LINKS_TOTAL - 1,
345 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TEXT, '-revolution'))
346 );
347 }
348
349 /**
350 * Full-text search - test AND, exact terms and exclusion combined, across fields.
351 */
352 public function testMultiSearch()
353 {
354 $this->assertEquals(
355 2,
356 count(self::$linkFilter->filter(
357 BookmarkFilter::$FILTER_TEXT,
358 '"Free Software " stallman "read this" @website stuff'
359 ))
360 );
361
362 $this->assertEquals(
363 1,
364 count(self::$linkFilter->filter(
365 BookmarkFilter::$FILTER_TEXT,
366 '"free software " stallman "read this" -beard @website stuff'
367 ))
368 );
369 }
370
371 /**
372 * Full-text search - make sure that exact search won't work across fields.
373 */
374 public function testSearchExactTermMultiFieldsKo()
375 {
376 $this->assertEquals(
377 0,
378 count(self::$linkFilter->filter(
379 BookmarkFilter::$FILTER_TEXT,
380 '"designer naming"'
381 ))
382 );
383
384 $this->assertEquals(
385 0,
386 count(self::$linkFilter->filter(
387 BookmarkFilter::$FILTER_TEXT,
388 '"designernaming"'
389 ))
390 );
391 }
392
393 /**
394 * Tag search with exclusion.
395 */
396 public function testTagFilterWithExclusion()
397 {
398 $this->assertEquals(
399 1,
400 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, 'gnu -free'))
401 );
402
403 $this->assertEquals(
404 ReferenceLinkDB::$NB_LINKS_TOTAL - 1,
405 count(self::$linkFilter->filter(BookmarkFilter::$FILTER_TAG, '-free'))
406 );
407 }
408
409 /**
410 * Test crossed search (terms + tags).
411 */
412 public function testFilterCrossedSearch()
413 {
414 $terms = '"Free Software " stallman "read this" @website stuff';
415 $tags = 'free';
416 $this->assertEquals(
417 1,
418 count(self::$linkFilter->filter(
419 BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
420 array($tags, $terms)
421 ))
422 );
423 $this->assertEquals(
424 2,
425 count(self::$linkFilter->filter(
426 BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
427 array('', $terms)
428 ))
429 );
430 $this->assertEquals(
431 1,
432 count(self::$linkFilter->filter(
433 BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
434 array(false, 'PSR-2')
435 ))
436 );
437 $this->assertEquals(
438 1,
439 count(self::$linkFilter->filter(
440 BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
441 array($tags, '')
442 ))
443 );
444 $this->assertEquals(
445 ReferenceLinkDB::$NB_LINKS_TOTAL,
446 count(self::$linkFilter->filter(
447 BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
448 ''
449 ))
450 );
451 }
452
453 /**
454 * Filter bookmarks by #hashtag.
455 */
456 public function testFilterByHashtag()
457 {
458 $hashtag = 'hashtag';
459 $this->assertEquals(
460 3,
461 count(self::$linkFilter->filter(
462 BookmarkFilter::$FILTER_TAG,
463 $hashtag
464 ))
465 );
466
467 $hashtag = 'private';
468 $this->assertEquals(
469 1,
470 count(self::$linkFilter->filter(
471 BookmarkFilter::$FILTER_TAG,
472 $hashtag,
473 false,
474 'private'
475 ))
476 );
477 }
478
479 /**
480 * Test search result highlights in every field of bookmark reference #9.
481 */
482 public function testFullTextSearchHighlight(): void
483 {
484 $bookmarks = self::$linkFilter->filter(
485 BookmarkFilter::$FILTER_TEXT,
486 '"psr-2" coding guide http fig "psr-2/" "This guide" basic standard. coding-style quality assurance'
487 );
488
489 static::assertCount(1, $bookmarks);
490 static::assertArrayHasKey(9, $bookmarks);
491
492 $bookmark = $bookmarks[9];
493 $expectedHighlights = [
494 'title' => [
495 ['start' => 0, 'end' => 5], // "psr-2"
496 ['start' => 7, 'end' => 13], // coding
497 ['start' => 20, 'end' => 25], // guide
498 ],
499 'description' => [
500 ['start' => 0, 'end' => 10], // "This guide"
501 ['start' => 45, 'end' => 50], // basic
502 ['start' => 58, 'end' => 67], // standard.
503 ],
504 'url' => [
505 ['start' => 0, 'end' => 4], // http
506 ['start' => 15, 'end' => 18], // fig
507 ['start' => 27, 'end' => 33], // "psr-2/"
508 ],
509 'tags' => [
510 ['start' => 0, 'end' => 12], // coding-style
511 ['start' => 23, 'end' => 30], // quality
512 ['start' => 31, 'end' => 40], // assurance
513 ],
514 ];
515 static::assertSame($expectedHighlights, $bookmark->getAdditionalContentEntry('search_highlight'));
516 }
517 }