]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - tests/bookmark/LinkDBTest.php
namespacing: \Shaarli\Bookmark\LinkDB
[github/shaarli/Shaarli.git] / tests / bookmark / LinkDBTest.php
1 <?php
2 /**
3 * Link datastore tests
4 */
5
6 namespace Shaarli\Bookmark;
7
8 use DateTime;
9 use LinkNotFoundException;
10 use ReferenceLinkDB;
11 use ReflectionClass;
12 use Shaarli;
13
14 require_once 'application/feed/Cache.php';
15 require_once 'application/Utils.php';
16 require_once 'tests/utils/ReferenceLinkDB.php';
17
18
19 /**
20 * Unitary tests for LinkDB
21 */
22 class LinkDBTest extends \PHPUnit\Framework\TestCase
23 {
24 // datastore to test write operations
25 protected static $testDatastore = 'sandbox/datastore.php';
26
27 /**
28 * @var ReferenceLinkDB instance.
29 */
30 protected static $refDB = null;
31
32 /**
33 * @var LinkDB public LinkDB instance.
34 */
35 protected static $publicLinkDB = null;
36
37 /**
38 * @var LinkDB private LinkDB instance.
39 */
40 protected static $privateLinkDB = null;
41
42 /**
43 * Instantiates public and private LinkDBs with test data
44 *
45 * The reference datastore contains public and private links that
46 * will be used to test LinkDB's methods:
47 * - access filtering (public/private),
48 * - link searches:
49 * - by day,
50 * - by tag,
51 * - by text,
52 * - etc.
53 */
54 public static function setUpBeforeClass()
55 {
56 self::$refDB = new ReferenceLinkDB();
57 self::$refDB->write(self::$testDatastore);
58
59 self::$publicLinkDB = new LinkDB(self::$testDatastore, false, false);
60 self::$privateLinkDB = new LinkDB(self::$testDatastore, true, false);
61 }
62
63 /**
64 * Resets test data for each test
65 */
66 protected function setUp()
67 {
68 if (file_exists(self::$testDatastore)) {
69 unlink(self::$testDatastore);
70 }
71 }
72
73 /**
74 * Allows to test LinkDB's private methods
75 *
76 * @see
77 * https://sebastian-bergmann.de/archives/881-Testing-Your-Privates.html
78 * http://stackoverflow.com/a/2798203
79 */
80 protected static function getMethod($name)
81 {
82 $class = new ReflectionClass('Shaarli\Bookmark\LinkDB');
83 $method = $class->getMethod($name);
84 $method->setAccessible(true);
85 return $method;
86 }
87
88 /**
89 * Instantiate LinkDB objects - logged in user
90 */
91 public function testConstructLoggedIn()
92 {
93 new LinkDB(self::$testDatastore, true, false);
94 $this->assertFileExists(self::$testDatastore);
95 }
96
97 /**
98 * Instantiate LinkDB objects - logged out or public instance
99 */
100 public function testConstructLoggedOut()
101 {
102 new LinkDB(self::$testDatastore, false, false);
103 $this->assertFileExists(self::$testDatastore);
104 }
105
106 /**
107 * Attempt to instantiate a LinkDB whereas the datastore is not writable
108 *
109 * @expectedException Shaarli\Exceptions\IOException
110 * @expectedExceptionMessageRegExp /Error accessing "null"/
111 */
112 public function testConstructDatastoreNotWriteable()
113 {
114 new LinkDB('null/store.db', false, false);
115 }
116
117 /**
118 * The DB doesn't exist, ensure it is created with dummy content
119 */
120 public function testCheckDBNew()
121 {
122 $linkDB = new LinkDB(self::$testDatastore, false, false);
123 unlink(self::$testDatastore);
124 $this->assertFileNotExists(self::$testDatastore);
125
126 $checkDB = self::getMethod('check');
127 $checkDB->invokeArgs($linkDB, array());
128 $this->assertFileExists(self::$testDatastore);
129
130 // ensure the correct data has been written
131 $this->assertGreaterThan(0, filesize(self::$testDatastore));
132 }
133
134 /**
135 * The DB exists, don't do anything
136 */
137 public function testCheckDBLoad()
138 {
139 $linkDB = new LinkDB(self::$testDatastore, false, false);
140 $datastoreSize = filesize(self::$testDatastore);
141 $this->assertGreaterThan(0, $datastoreSize);
142
143 $checkDB = self::getMethod('check');
144 $checkDB->invokeArgs($linkDB, array());
145
146 // ensure the datastore is left unmodified
147 $this->assertEquals(
148 $datastoreSize,
149 filesize(self::$testDatastore)
150 );
151 }
152
153 /**
154 * Load an empty DB
155 */
156 public function testReadEmptyDB()
157 {
158 file_put_contents(self::$testDatastore, '<?php /* S7QysKquBQA= */ ?>');
159 $emptyDB = new LinkDB(self::$testDatastore, false, false);
160 $this->assertEquals(0, sizeof($emptyDB));
161 $this->assertEquals(0, count($emptyDB));
162 }
163
164 /**
165 * Load public links from the DB
166 */
167 public function testReadPublicDB()
168 {
169 $this->assertEquals(
170 self::$refDB->countPublicLinks(),
171 sizeof(self::$publicLinkDB)
172 );
173 }
174
175 /**
176 * Load public and private links from the DB
177 */
178 public function testReadPrivateDB()
179 {
180 $this->assertEquals(
181 self::$refDB->countLinks(),
182 sizeof(self::$privateLinkDB)
183 );
184 }
185
186 /**
187 * Save the links to the DB
188 */
189 public function testSave()
190 {
191 $testDB = new LinkDB(self::$testDatastore, true, false);
192 $dbSize = sizeof($testDB);
193
194 $link = array(
195 'id' => 42,
196 'title' => 'an additional link',
197 'url' => 'http://dum.my',
198 'description' => 'One more',
199 'private' => 0,
200 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150518_190000'),
201 'tags' => 'unit test'
202 );
203 $testDB[$link['id']] = $link;
204 $testDB->save('tests');
205
206 $testDB = new LinkDB(self::$testDatastore, true, false);
207 $this->assertEquals($dbSize + 1, sizeof($testDB));
208 }
209
210 /**
211 * Count existing links
212 */
213 public function testCount()
214 {
215 $this->assertEquals(
216 self::$refDB->countPublicLinks(),
217 self::$publicLinkDB->count()
218 );
219 $this->assertEquals(
220 self::$refDB->countLinks(),
221 self::$privateLinkDB->count()
222 );
223 }
224
225 /**
226 * Count existing links - public links hidden
227 */
228 public function testCountHiddenPublic()
229 {
230 $linkDB = new LinkDB(self::$testDatastore, false, true);
231
232 $this->assertEquals(
233 0,
234 $linkDB->count()
235 );
236 $this->assertEquals(
237 0,
238 $linkDB->count()
239 );
240 }
241
242 /**
243 * List the days for which links have been posted
244 */
245 public function testDays()
246 {
247 $this->assertEquals(
248 array('20100309', '20100310', '20121206', '20121207', '20130614', '20150310'),
249 self::$publicLinkDB->days()
250 );
251
252 $this->assertEquals(
253 array('20100309', '20100310', '20121206', '20121207', '20130614', '20141125', '20150310'),
254 self::$privateLinkDB->days()
255 );
256 }
257
258 /**
259 * The URL corresponds to an existing entry in the DB
260 */
261 public function testGetKnownLinkFromURL()
262 {
263 $link = self::$publicLinkDB->getLinkFromUrl('http://mediagoblin.org/');
264
265 $this->assertNotEquals(false, $link);
266 $this->assertContains(
267 'A free software media publishing platform',
268 $link['description']
269 );
270 }
271
272 /**
273 * The URL is not in the DB
274 */
275 public function testGetUnknownLinkFromURL()
276 {
277 $this->assertEquals(
278 false,
279 self::$publicLinkDB->getLinkFromUrl('http://dev.null')
280 );
281 }
282
283 /**
284 * Lists all tags
285 */
286 public function testAllTags()
287 {
288 $this->assertEquals(
289 array(
290 'web' => 3,
291 'cartoon' => 2,
292 'gnu' => 2,
293 'dev' => 1,
294 'samba' => 1,
295 'media' => 1,
296 'software' => 1,
297 'stallman' => 1,
298 'free' => 1,
299 '-exclude' => 1,
300 'hashtag' => 2,
301 // The DB contains a link with `sTuff` and another one with `stuff` tag.
302 // They need to be grouped with the first case found - order by date DESC: `sTuff`.
303 'sTuff' => 2,
304 'ut' => 1,
305 ),
306 self::$publicLinkDB->linksCountPerTag()
307 );
308
309 $this->assertEquals(
310 array(
311 'web' => 4,
312 'cartoon' => 3,
313 'gnu' => 2,
314 'dev' => 2,
315 'samba' => 1,
316 'media' => 1,
317 'software' => 1,
318 'stallman' => 1,
319 'free' => 1,
320 'html' => 1,
321 'w3c' => 1,
322 'css' => 1,
323 'Mercurial' => 1,
324 'sTuff' => 2,
325 '-exclude' => 1,
326 '.hidden' => 1,
327 'hashtag' => 2,
328 'tag1' => 1,
329 'tag2' => 1,
330 'tag3' => 1,
331 'tag4' => 1,
332 'ut' => 1,
333 ),
334 self::$privateLinkDB->linksCountPerTag()
335 );
336 $this->assertEquals(
337 array(
338 'web' => 4,
339 'cartoon' => 2,
340 'gnu' => 1,
341 'dev' => 1,
342 'samba' => 1,
343 'media' => 1,
344 'html' => 1,
345 'w3c' => 1,
346 'css' => 1,
347 'Mercurial' => 1,
348 '.hidden' => 1,
349 'hashtag' => 1,
350 ),
351 self::$privateLinkDB->linksCountPerTag(['web'])
352 );
353 $this->assertEquals(
354 array(
355 'web' => 1,
356 'html' => 1,
357 'w3c' => 1,
358 'css' => 1,
359 'Mercurial' => 1,
360 ),
361 self::$privateLinkDB->linksCountPerTag(['web'], 'private')
362 );
363 }
364
365 /**
366 * Test real_url without redirector.
367 */
368 public function testLinkRealUrlWithoutRedirector()
369 {
370 $db = new LinkDB(self::$testDatastore, false, false);
371 foreach ($db as $link) {
372 $this->assertEquals($link['url'], $link['real_url']);
373 }
374 }
375
376 /**
377 * Test real_url with redirector.
378 */
379 public function testLinkRealUrlWithRedirector()
380 {
381 $redirector = 'http://redirector.to?';
382 $db = new LinkDB(self::$testDatastore, false, false, $redirector);
383 foreach ($db as $link) {
384 $this->assertStringStartsWith($redirector, $link['real_url']);
385 $this->assertNotFalse(strpos($link['real_url'], urlencode('://')));
386 }
387
388 $db = new LinkDB(self::$testDatastore, false, false, $redirector, false);
389 foreach ($db as $link) {
390 $this->assertStringStartsWith($redirector, $link['real_url']);
391 $this->assertFalse(strpos($link['real_url'], urlencode('://')));
392 }
393 }
394
395 /**
396 * Test filter with string.
397 */
398 public function testFilterString()
399 {
400 $tags = 'dev cartoon';
401 $request = array('searchtags' => $tags);
402 $this->assertEquals(
403 2,
404 count(self::$privateLinkDB->filterSearch($request, true, false))
405 );
406 }
407
408 /**
409 * Test filter with string.
410 */
411 public function testFilterArray()
412 {
413 $tags = array('dev', 'cartoon');
414 $request = array('searchtags' => $tags);
415 $this->assertEquals(
416 2,
417 count(self::$privateLinkDB->filterSearch($request, true, false))
418 );
419 }
420
421 /**
422 * Test hidden tags feature:
423 * tags starting with a dot '.' are only visible when logged in.
424 */
425 public function testHiddenTags()
426 {
427 $tags = '.hidden';
428 $request = array('searchtags' => $tags);
429 $this->assertEquals(
430 1,
431 count(self::$privateLinkDB->filterSearch($request, true, false))
432 );
433
434 $this->assertEquals(
435 0,
436 count(self::$publicLinkDB->filterSearch($request, true, false))
437 );
438 }
439
440 /**
441 * Test filterHash() with a valid smallhash.
442 */
443 public function testFilterHashValid()
444 {
445 $request = smallHash('20150310_114651');
446 $this->assertEquals(
447 1,
448 count(self::$publicLinkDB->filterHash($request))
449 );
450 $request = smallHash('20150310_114633' . 8);
451 $this->assertEquals(
452 1,
453 count(self::$publicLinkDB->filterHash($request))
454 );
455 }
456
457 /**
458 * Test filterHash() with an invalid smallhash.
459 *
460 * @expectedException LinkNotFoundException
461 */
462 public function testFilterHashInValid1()
463 {
464 $request = 'blabla';
465 self::$publicLinkDB->filterHash($request);
466 }
467
468 /**
469 * Test filterHash() with an empty smallhash.
470 *
471 * @expectedException LinkNotFoundException
472 */
473 public function testFilterHashInValid()
474 {
475 self::$publicLinkDB->filterHash('');
476 }
477
478 /**
479 * Test reorder with asc/desc parameter.
480 */
481 public function testReorderLinksDesc()
482 {
483 self::$privateLinkDB->reorder('ASC');
484 $stickyIds = [11, 10];
485 $standardIds = [42, 4, 9, 1, 0, 7, 6, 8, 41];
486 $linkIds = array_merge($stickyIds, $standardIds);
487 $cpt = 0;
488 foreach (self::$privateLinkDB as $key => $value) {
489 $this->assertEquals($linkIds[$cpt++], $key);
490 }
491 self::$privateLinkDB->reorder('DESC');
492 $linkIds = array_merge(array_reverse($stickyIds), array_reverse($standardIds));
493 $cpt = 0;
494 foreach (self::$privateLinkDB as $key => $value) {
495 $this->assertEquals($linkIds[$cpt++], $key);
496 }
497 }
498
499 /**
500 * Test rename tag with a valid value present in multiple links
501 */
502 public function testRenameTagMultiple()
503 {
504 self::$refDB->write(self::$testDatastore);
505 $linkDB = new LinkDB(self::$testDatastore, true, false);
506
507 $res = $linkDB->renameTag('cartoon', 'Taz');
508 $this->assertEquals(3, count($res));
509 $this->assertContains(' Taz ', $linkDB[4]['tags']);
510 $this->assertContains(' Taz ', $linkDB[1]['tags']);
511 $this->assertContains(' Taz ', $linkDB[0]['tags']);
512 }
513
514 /**
515 * Test rename tag with a valid value
516 */
517 public function testRenameTagCaseSensitive()
518 {
519 self::$refDB->write(self::$testDatastore);
520 $linkDB = new LinkDB(self::$testDatastore, true, false, '');
521
522 $res = $linkDB->renameTag('sTuff', 'Taz');
523 $this->assertEquals(1, count($res));
524 $this->assertEquals('Taz', $linkDB[41]['tags']);
525 }
526
527 /**
528 * Test rename tag with invalid values
529 */
530 public function testRenameTagInvalid()
531 {
532 $linkDB = new LinkDB(self::$testDatastore, false, false);
533
534 $this->assertFalse($linkDB->renameTag('', 'test'));
535 $this->assertFalse($linkDB->renameTag('', ''));
536 // tag non existent
537 $this->assertEquals([], $linkDB->renameTag('test', ''));
538 $this->assertEquals([], $linkDB->renameTag('test', 'retest'));
539 }
540
541 /**
542 * Test delete tag with a valid value
543 */
544 public function testDeleteTag()
545 {
546 self::$refDB->write(self::$testDatastore);
547 $linkDB = new LinkDB(self::$testDatastore, true, false);
548
549 $res = $linkDB->renameTag('cartoon', null);
550 $this->assertEquals(3, count($res));
551 $this->assertNotContains('cartoon', $linkDB[4]['tags']);
552 }
553
554 /**
555 * Test linksCountPerTag all tags without filter.
556 * Equal occurrences should be sorted alphabetically.
557 */
558 public function testCountLinkPerTagAllNoFilter()
559 {
560 $expected = [
561 'web' => 4,
562 'cartoon' => 3,
563 'dev' => 2,
564 'gnu' => 2,
565 'hashtag' => 2,
566 'sTuff' => 2,
567 '-exclude' => 1,
568 '.hidden' => 1,
569 'Mercurial' => 1,
570 'css' => 1,
571 'free' => 1,
572 'html' => 1,
573 'media' => 1,
574 'samba' => 1,
575 'software' => 1,
576 'stallman' => 1,
577 'tag1' => 1,
578 'tag2' => 1,
579 'tag3' => 1,
580 'tag4' => 1,
581 'ut' => 1,
582 'w3c' => 1,
583 ];
584 $tags = self::$privateLinkDB->linksCountPerTag();
585
586 $this->assertEquals($expected, $tags, var_export($tags, true));
587 }
588
589 /**
590 * Test linksCountPerTag all tags with filter.
591 * Equal occurrences should be sorted alphabetically.
592 */
593 public function testCountLinkPerTagAllWithFilter()
594 {
595 $expected = [
596 'gnu' => 2,
597 'hashtag' => 2,
598 '-exclude' => 1,
599 '.hidden' => 1,
600 'free' => 1,
601 'media' => 1,
602 'software' => 1,
603 'stallman' => 1,
604 'stuff' => 1,
605 'web' => 1,
606 ];
607 $tags = self::$privateLinkDB->linksCountPerTag(['gnu']);
608
609 $this->assertEquals($expected, $tags, var_export($tags, true));
610 }
611
612 /**
613 * Test linksCountPerTag public tags with filter.
614 * Equal occurrences should be sorted alphabetically.
615 */
616 public function testCountLinkPerTagPublicWithFilter()
617 {
618 $expected = [
619 'gnu' => 2,
620 'hashtag' => 2,
621 '-exclude' => 1,
622 '.hidden' => 1,
623 'free' => 1,
624 'media' => 1,
625 'software' => 1,
626 'stallman' => 1,
627 'stuff' => 1,
628 'web' => 1,
629 ];
630 $tags = self::$privateLinkDB->linksCountPerTag(['gnu'], 'public');
631
632 $this->assertEquals($expected, $tags, var_export($tags, true));
633 }
634
635 /**
636 * Test linksCountPerTag public tags with filter.
637 * Equal occurrences should be sorted alphabetically.
638 */
639 public function testCountLinkPerTagPrivateWithFilter()
640 {
641 $expected = [
642 'cartoon' => 1,
643 'dev' => 1,
644 'tag1' => 1,
645 'tag2' => 1,
646 'tag3' => 1,
647 'tag4' => 1,
648 ];
649 $tags = self::$privateLinkDB->linksCountPerTag(['dev'], 'private');
650
651 $this->assertEquals($expected, $tags, var_export($tags, true));
652 }
653 }