aboutsummaryrefslogtreecommitdiffhomepage
path: root/tests/bookmark
diff options
context:
space:
mode:
Diffstat (limited to 'tests/bookmark')
-rw-r--r--tests/bookmark/LinkDBTest.php652
-rw-r--r--tests/bookmark/LinkFilterTest.php507
-rw-r--r--tests/bookmark/LinkUtilsTest.php333
3 files changed, 1492 insertions, 0 deletions
diff --git a/tests/bookmark/LinkDBTest.php b/tests/bookmark/LinkDBTest.php
new file mode 100644
index 00000000..ff5c0b97
--- /dev/null
+++ b/tests/bookmark/LinkDBTest.php
@@ -0,0 +1,652 @@
1<?php
2/**
3 * Link datastore tests
4 */
5
6namespace Shaarli\Bookmark;
7
8use DateTime;
9use ReferenceLinkDB;
10use ReflectionClass;
11use Shaarli;
12
13require_once 'application/feed/Cache.php';
14require_once 'application/Utils.php';
15require_once 'tests/utils/ReferenceLinkDB.php';
16
17
18/**
19 * Unitary tests for LinkDB
20 */
21class LinkDBTest extends \PHPUnit\Framework\TestCase
22{
23 // datastore to test write operations
24 protected static $testDatastore = 'sandbox/datastore.php';
25
26 /**
27 * @var ReferenceLinkDB instance.
28 */
29 protected static $refDB = null;
30
31 /**
32 * @var LinkDB public LinkDB instance.
33 */
34 protected static $publicLinkDB = null;
35
36 /**
37 * @var LinkDB private LinkDB instance.
38 */
39 protected static $privateLinkDB = null;
40
41 /**
42 * Instantiates public and private LinkDBs with test data
43 *
44 * The reference datastore contains public and private links that
45 * will be used to test LinkDB's methods:
46 * - access filtering (public/private),
47 * - link searches:
48 * - by day,
49 * - by tag,
50 * - by text,
51 * - etc.
52 */
53 public static function setUpBeforeClass()
54 {
55 self::$refDB = new ReferenceLinkDB();
56 self::$refDB->write(self::$testDatastore);
57
58 self::$publicLinkDB = new LinkDB(self::$testDatastore, false, false);
59 self::$privateLinkDB = new LinkDB(self::$testDatastore, true, false);
60 }
61
62 /**
63 * Resets test data for each test
64 */
65 protected function setUp()
66 {
67 if (file_exists(self::$testDatastore)) {
68 unlink(self::$testDatastore);
69 }
70 }
71
72 /**
73 * Allows to test LinkDB's private methods
74 *
75 * @see
76 * https://sebastian-bergmann.de/archives/881-Testing-Your-Privates.html
77 * http://stackoverflow.com/a/2798203
78 */
79 protected static function getMethod($name)
80 {
81 $class = new ReflectionClass('Shaarli\Bookmark\LinkDB');
82 $method = $class->getMethod($name);
83 $method->setAccessible(true);
84 return $method;
85 }
86
87 /**
88 * Instantiate LinkDB objects - logged in user
89 */
90 public function testConstructLoggedIn()
91 {
92 new LinkDB(self::$testDatastore, true, false);
93 $this->assertFileExists(self::$testDatastore);
94 }
95
96 /**
97 * Instantiate LinkDB objects - logged out or public instance
98 */
99 public function testConstructLoggedOut()
100 {
101 new LinkDB(self::$testDatastore, false, false);
102 $this->assertFileExists(self::$testDatastore);
103 }
104
105 /**
106 * Attempt to instantiate a LinkDB whereas the datastore is not writable
107 *
108 * @expectedException Shaarli\Exceptions\IOException
109 * @expectedExceptionMessageRegExp /Error accessing "null"/
110 */
111 public function testConstructDatastoreNotWriteable()
112 {
113 new LinkDB('null/store.db', false, false);
114 }
115
116 /**
117 * The DB doesn't exist, ensure it is created with dummy content
118 */
119 public function testCheckDBNew()
120 {
121 $linkDB = new LinkDB(self::$testDatastore, false, false);
122 unlink(self::$testDatastore);
123 $this->assertFileNotExists(self::$testDatastore);
124
125 $checkDB = self::getMethod('check');
126 $checkDB->invokeArgs($linkDB, array());
127 $this->assertFileExists(self::$testDatastore);
128
129 // ensure the correct data has been written
130 $this->assertGreaterThan(0, filesize(self::$testDatastore));
131 }
132
133 /**
134 * The DB exists, don't do anything
135 */
136 public function testCheckDBLoad()
137 {
138 $linkDB = new LinkDB(self::$testDatastore, false, false);
139 $datastoreSize = filesize(self::$testDatastore);
140 $this->assertGreaterThan(0, $datastoreSize);
141
142 $checkDB = self::getMethod('check');
143 $checkDB->invokeArgs($linkDB, array());
144
145 // ensure the datastore is left unmodified
146 $this->assertEquals(
147 $datastoreSize,
148 filesize(self::$testDatastore)
149 );
150 }
151
152 /**
153 * Load an empty DB
154 */
155 public function testReadEmptyDB()
156 {
157 file_put_contents(self::$testDatastore, '<?php /* S7QysKquBQA= */ ?>');
158 $emptyDB = new LinkDB(self::$testDatastore, false, false);
159 $this->assertEquals(0, sizeof($emptyDB));
160 $this->assertEquals(0, count($emptyDB));
161 }
162
163 /**
164 * Load public links from the DB
165 */
166 public function testReadPublicDB()
167 {
168 $this->assertEquals(
169 self::$refDB->countPublicLinks(),
170 sizeof(self::$publicLinkDB)
171 );
172 }
173
174 /**
175 * Load public and private links from the DB
176 */
177 public function testReadPrivateDB()
178 {
179 $this->assertEquals(
180 self::$refDB->countLinks(),
181 sizeof(self::$privateLinkDB)
182 );
183 }
184
185 /**
186 * Save the links to the DB
187 */
188 public function testSave()
189 {
190 $testDB = new LinkDB(self::$testDatastore, true, false);
191 $dbSize = sizeof($testDB);
192
193 $link = array(
194 'id' => 42,
195 'title' => 'an additional link',
196 'url' => 'http://dum.my',
197 'description' => 'One more',
198 'private' => 0,
199 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150518_190000'),
200 'tags' => 'unit test'
201 );
202 $testDB[$link['id']] = $link;
203 $testDB->save('tests');
204
205 $testDB = new LinkDB(self::$testDatastore, true, false);
206 $this->assertEquals($dbSize + 1, sizeof($testDB));
207 }
208
209 /**
210 * Count existing links
211 */
212 public function testCount()
213 {
214 $this->assertEquals(
215 self::$refDB->countPublicLinks(),
216 self::$publicLinkDB->count()
217 );
218 $this->assertEquals(
219 self::$refDB->countLinks(),
220 self::$privateLinkDB->count()
221 );
222 }
223
224 /**
225 * Count existing links - public links hidden
226 */
227 public function testCountHiddenPublic()
228 {
229 $linkDB = new LinkDB(self::$testDatastore, false, true);
230
231 $this->assertEquals(
232 0,
233 $linkDB->count()
234 );
235 $this->assertEquals(
236 0,
237 $linkDB->count()
238 );
239 }
240
241 /**
242 * List the days for which links have been posted
243 */
244 public function testDays()
245 {
246 $this->assertEquals(
247 array('20100309', '20100310', '20121206', '20121207', '20130614', '20150310'),
248 self::$publicLinkDB->days()
249 );
250
251 $this->assertEquals(
252 array('20100309', '20100310', '20121206', '20121207', '20130614', '20141125', '20150310'),
253 self::$privateLinkDB->days()
254 );
255 }
256
257 /**
258 * The URL corresponds to an existing entry in the DB
259 */
260 public function testGetKnownLinkFromURL()
261 {
262 $link = self::$publicLinkDB->getLinkFromUrl('http://mediagoblin.org/');
263
264 $this->assertNotEquals(false, $link);
265 $this->assertContains(
266 'A free software media publishing platform',
267 $link['description']
268 );
269 }
270
271 /**
272 * The URL is not in the DB
273 */
274 public function testGetUnknownLinkFromURL()
275 {
276 $this->assertEquals(
277 false,
278 self::$publicLinkDB->getLinkFromUrl('http://dev.null')
279 );
280 }
281
282 /**
283 * Lists all tags
284 */
285 public function testAllTags()
286 {
287 $this->assertEquals(
288 array(
289 'web' => 3,
290 'cartoon' => 2,
291 'gnu' => 2,
292 'dev' => 1,
293 'samba' => 1,
294 'media' => 1,
295 'software' => 1,
296 'stallman' => 1,
297 'free' => 1,
298 '-exclude' => 1,
299 'hashtag' => 2,
300 // The DB contains a link with `sTuff` and another one with `stuff` tag.
301 // They need to be grouped with the first case found - order by date DESC: `sTuff`.
302 'sTuff' => 2,
303 'ut' => 1,
304 ),
305 self::$publicLinkDB->linksCountPerTag()
306 );
307
308 $this->assertEquals(
309 array(
310 'web' => 4,
311 'cartoon' => 3,
312 'gnu' => 2,
313 'dev' => 2,
314 'samba' => 1,
315 'media' => 1,
316 'software' => 1,
317 'stallman' => 1,
318 'free' => 1,
319 'html' => 1,
320 'w3c' => 1,
321 'css' => 1,
322 'Mercurial' => 1,
323 'sTuff' => 2,
324 '-exclude' => 1,
325 '.hidden' => 1,
326 'hashtag' => 2,
327 'tag1' => 1,
328 'tag2' => 1,
329 'tag3' => 1,
330 'tag4' => 1,
331 'ut' => 1,
332 ),
333 self::$privateLinkDB->linksCountPerTag()
334 );
335 $this->assertEquals(
336 array(
337 'web' => 4,
338 'cartoon' => 2,
339 'gnu' => 1,
340 'dev' => 1,
341 'samba' => 1,
342 'media' => 1,
343 'html' => 1,
344 'w3c' => 1,
345 'css' => 1,
346 'Mercurial' => 1,
347 '.hidden' => 1,
348 'hashtag' => 1,
349 ),
350 self::$privateLinkDB->linksCountPerTag(['web'])
351 );
352 $this->assertEquals(
353 array(
354 'web' => 1,
355 'html' => 1,
356 'w3c' => 1,
357 'css' => 1,
358 'Mercurial' => 1,
359 ),
360 self::$privateLinkDB->linksCountPerTag(['web'], 'private')
361 );
362 }
363
364 /**
365 * Test real_url without redirector.
366 */
367 public function testLinkRealUrlWithoutRedirector()
368 {
369 $db = new LinkDB(self::$testDatastore, false, false);
370 foreach ($db as $link) {
371 $this->assertEquals($link['url'], $link['real_url']);
372 }
373 }
374
375 /**
376 * Test real_url with redirector.
377 */
378 public function testLinkRealUrlWithRedirector()
379 {
380 $redirector = 'http://redirector.to?';
381 $db = new LinkDB(self::$testDatastore, false, false, $redirector);
382 foreach ($db as $link) {
383 $this->assertStringStartsWith($redirector, $link['real_url']);
384 $this->assertNotFalse(strpos($link['real_url'], urlencode('://')));
385 }
386
387 $db = new LinkDB(self::$testDatastore, false, false, $redirector, false);
388 foreach ($db as $link) {
389 $this->assertStringStartsWith($redirector, $link['real_url']);
390 $this->assertFalse(strpos($link['real_url'], urlencode('://')));
391 }
392 }
393
394 /**
395 * Test filter with string.
396 */
397 public function testFilterString()
398 {
399 $tags = 'dev cartoon';
400 $request = array('searchtags' => $tags);
401 $this->assertEquals(
402 2,
403 count(self::$privateLinkDB->filterSearch($request, true, false))
404 );
405 }
406
407 /**
408 * Test filter with string.
409 */
410 public function testFilterArray()
411 {
412 $tags = array('dev', 'cartoon');
413 $request = array('searchtags' => $tags);
414 $this->assertEquals(
415 2,
416 count(self::$privateLinkDB->filterSearch($request, true, false))
417 );
418 }
419
420 /**
421 * Test hidden tags feature:
422 * tags starting with a dot '.' are only visible when logged in.
423 */
424 public function testHiddenTags()
425 {
426 $tags = '.hidden';
427 $request = array('searchtags' => $tags);
428 $this->assertEquals(
429 1,
430 count(self::$privateLinkDB->filterSearch($request, true, false))
431 );
432
433 $this->assertEquals(
434 0,
435 count(self::$publicLinkDB->filterSearch($request, true, false))
436 );
437 }
438
439 /**
440 * Test filterHash() with a valid smallhash.
441 */
442 public function testFilterHashValid()
443 {
444 $request = smallHash('20150310_114651');
445 $this->assertEquals(
446 1,
447 count(self::$publicLinkDB->filterHash($request))
448 );
449 $request = smallHash('20150310_114633' . 8);
450 $this->assertEquals(
451 1,
452 count(self::$publicLinkDB->filterHash($request))
453 );
454 }
455
456 /**
457 * Test filterHash() with an invalid smallhash.
458 *
459 * @expectedException \Shaarli\Bookmark\Exception\LinkNotFoundException
460 */
461 public function testFilterHashInValid1()
462 {
463 $request = 'blabla';
464 self::$publicLinkDB->filterHash($request);
465 }
466
467 /**
468 * Test filterHash() with an empty smallhash.
469 *
470 * @expectedException \Shaarli\Bookmark\Exception\LinkNotFoundException
471 */
472 public function testFilterHashInValid()
473 {
474 self::$publicLinkDB->filterHash('');
475 }
476
477 /**
478 * Test reorder with asc/desc parameter.
479 */
480 public function testReorderLinksDesc()
481 {
482 self::$privateLinkDB->reorder('ASC');
483 $stickyIds = [11, 10];
484 $standardIds = [42, 4, 9, 1, 0, 7, 6, 8, 41];
485 $linkIds = array_merge($stickyIds, $standardIds);
486 $cpt = 0;
487 foreach (self::$privateLinkDB as $key => $value) {
488 $this->assertEquals($linkIds[$cpt++], $key);
489 }
490 self::$privateLinkDB->reorder('DESC');
491 $linkIds = array_merge(array_reverse($stickyIds), array_reverse($standardIds));
492 $cpt = 0;
493 foreach (self::$privateLinkDB as $key => $value) {
494 $this->assertEquals($linkIds[$cpt++], $key);
495 }
496 }
497
498 /**
499 * Test rename tag with a valid value present in multiple links
500 */
501 public function testRenameTagMultiple()
502 {
503 self::$refDB->write(self::$testDatastore);
504 $linkDB = new LinkDB(self::$testDatastore, true, false);
505
506 $res = $linkDB->renameTag('cartoon', 'Taz');
507 $this->assertEquals(3, count($res));
508 $this->assertContains(' Taz ', $linkDB[4]['tags']);
509 $this->assertContains(' Taz ', $linkDB[1]['tags']);
510 $this->assertContains(' Taz ', $linkDB[0]['tags']);
511 }
512
513 /**
514 * Test rename tag with a valid value
515 */
516 public function testRenameTagCaseSensitive()
517 {
518 self::$refDB->write(self::$testDatastore);
519 $linkDB = new LinkDB(self::$testDatastore, true, false, '');
520
521 $res = $linkDB->renameTag('sTuff', 'Taz');
522 $this->assertEquals(1, count($res));
523 $this->assertEquals('Taz', $linkDB[41]['tags']);
524 }
525
526 /**
527 * Test rename tag with invalid values
528 */
529 public function testRenameTagInvalid()
530 {
531 $linkDB = new LinkDB(self::$testDatastore, false, false);
532
533 $this->assertFalse($linkDB->renameTag('', 'test'));
534 $this->assertFalse($linkDB->renameTag('', ''));
535 // tag non existent
536 $this->assertEquals([], $linkDB->renameTag('test', ''));
537 $this->assertEquals([], $linkDB->renameTag('test', 'retest'));
538 }
539
540 /**
541 * Test delete tag with a valid value
542 */
543 public function testDeleteTag()
544 {
545 self::$refDB->write(self::$testDatastore);
546 $linkDB = new LinkDB(self::$testDatastore, true, false);
547
548 $res = $linkDB->renameTag('cartoon', null);
549 $this->assertEquals(3, count($res));
550 $this->assertNotContains('cartoon', $linkDB[4]['tags']);
551 }
552
553 /**
554 * Test linksCountPerTag all tags without filter.
555 * Equal occurrences should be sorted alphabetically.
556 */
557 public function testCountLinkPerTagAllNoFilter()
558 {
559 $expected = [
560 'web' => 4,
561 'cartoon' => 3,
562 'dev' => 2,
563 'gnu' => 2,
564 'hashtag' => 2,
565 'sTuff' => 2,
566 '-exclude' => 1,
567 '.hidden' => 1,
568 'Mercurial' => 1,
569 'css' => 1,
570 'free' => 1,
571 'html' => 1,
572 'media' => 1,
573 'samba' => 1,
574 'software' => 1,
575 'stallman' => 1,
576 'tag1' => 1,
577 'tag2' => 1,
578 'tag3' => 1,
579 'tag4' => 1,
580 'ut' => 1,
581 'w3c' => 1,
582 ];
583 $tags = self::$privateLinkDB->linksCountPerTag();
584
585 $this->assertEquals($expected, $tags, var_export($tags, true));
586 }
587
588 /**
589 * Test linksCountPerTag all tags with filter.
590 * Equal occurrences should be sorted alphabetically.
591 */
592 public function testCountLinkPerTagAllWithFilter()
593 {
594 $expected = [
595 'gnu' => 2,
596 'hashtag' => 2,
597 '-exclude' => 1,
598 '.hidden' => 1,
599 'free' => 1,
600 'media' => 1,
601 'software' => 1,
602 'stallman' => 1,
603 'stuff' => 1,
604 'web' => 1,
605 ];
606 $tags = self::$privateLinkDB->linksCountPerTag(['gnu']);
607
608 $this->assertEquals($expected, $tags, var_export($tags, true));
609 }
610
611 /**
612 * Test linksCountPerTag public tags with filter.
613 * Equal occurrences should be sorted alphabetically.
614 */
615 public function testCountLinkPerTagPublicWithFilter()
616 {
617 $expected = [
618 'gnu' => 2,
619 'hashtag' => 2,
620 '-exclude' => 1,
621 '.hidden' => 1,
622 'free' => 1,
623 'media' => 1,
624 'software' => 1,
625 'stallman' => 1,
626 'stuff' => 1,
627 'web' => 1,
628 ];
629 $tags = self::$privateLinkDB->linksCountPerTag(['gnu'], 'public');
630
631 $this->assertEquals($expected, $tags, var_export($tags, true));
632 }
633
634 /**
635 * Test linksCountPerTag public tags with filter.
636 * Equal occurrences should be sorted alphabetically.
637 */
638 public function testCountLinkPerTagPrivateWithFilter()
639 {
640 $expected = [
641 'cartoon' => 1,
642 'dev' => 1,
643 'tag1' => 1,
644 'tag2' => 1,
645 'tag3' => 1,
646 'tag4' => 1,
647 ];
648 $tags = self::$privateLinkDB->linksCountPerTag(['dev'], 'private');
649
650 $this->assertEquals($expected, $tags, var_export($tags, true));
651 }
652}
diff --git a/tests/bookmark/LinkFilterTest.php b/tests/bookmark/LinkFilterTest.php
new file mode 100644
index 00000000..808f8122
--- /dev/null
+++ b/tests/bookmark/LinkFilterTest.php
@@ -0,0 +1,507 @@
1<?php
2
3namespace Shaarli\Bookmark;
4
5use Exception;
6use ReferenceLinkDB;
7
8/**
9 * Class LinkFilterTest.
10 */
11class LinkFilterTest extends \PHPUnit\Framework\TestCase
12{
13 /**
14 * @var string Test datastore path.
15 */
16 protected static $testDatastore = 'sandbox/datastore.php';
17 /**
18 * @var LinkFilter instance.
19 */
20 protected static $linkFilter;
21
22 /**
23 * @var ReferenceLinkDB instance
24 */
25 protected static $refDB;
26
27 /**
28 * @var LinkDB instance
29 */
30 protected static $linkDB;
31
32 /**
33 * Instantiate linkFilter with ReferenceLinkDB data.
34 */
35 public static function setUpBeforeClass()
36 {
37 self::$refDB = new ReferenceLinkDB();
38 self::$refDB->write(self::$testDatastore);
39 self::$linkDB = new LinkDB(self::$testDatastore, true, false);
40 self::$linkFilter = new LinkFilter(self::$linkDB);
41 }
42
43 /**
44 * Blank filter.
45 */
46 public function testFilter()
47 {
48 $this->assertEquals(
49 self::$refDB->countLinks(),
50 count(self::$linkFilter->filter('', ''))
51 );
52
53 $this->assertEquals(
54 self::$refDB->countLinks(),
55 count(self::$linkFilter->filter('', '', 'all'))
56 );
57
58 $this->assertEquals(
59 self::$refDB->countLinks(),
60 count(self::$linkFilter->filter('', '', 'randomstr'))
61 );
62
63 // Private only.
64 $this->assertEquals(
65 self::$refDB->countPrivateLinks(),
66 count(self::$linkFilter->filter('', '', false, 'private'))
67 );
68
69 // Public only.
70 $this->assertEquals(
71 self::$refDB->countPublicLinks(),
72 count(self::$linkFilter->filter('', '', false, 'public'))
73 );
74
75 $this->assertEquals(
76 ReferenceLinkDB::$NB_LINKS_TOTAL,
77 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, ''))
78 );
79
80 $this->assertEquals(
81 self::$refDB->countUntaggedLinks(),
82 count(
83 self::$linkFilter->filter(
84 LinkFilter::$FILTER_TAG,
85 /*$request=*/
86 '',
87 /*$casesensitive=*/
88 false,
89 /*$visibility=*/
90 'all',
91 /*$untaggedonly=*/
92 true
93 )
94 )
95 );
96
97 $this->assertEquals(
98 ReferenceLinkDB::$NB_LINKS_TOTAL,
99 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, ''))
100 );
101 }
102
103 /**
104 * Filter links using a tag
105 */
106 public function testFilterOneTag()
107 {
108 $this->assertEquals(
109 4,
110 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false))
111 );
112
113 $this->assertEquals(
114 4,
115 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false, 'all'))
116 );
117
118 $this->assertEquals(
119 4,
120 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false, 'default-blabla'))
121 );
122
123 // Private only.
124 $this->assertEquals(
125 1,
126 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false, 'private'))
127 );
128
129 // Public only.
130 $this->assertEquals(
131 3,
132 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false, 'public'))
133 );
134 }
135
136 /**
137 * Filter links using a tag - case-sensitive
138 */
139 public function testFilterCaseSensitiveTag()
140 {
141 $this->assertEquals(
142 0,
143 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'mercurial', true))
144 );
145
146 $this->assertEquals(
147 1,
148 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'Mercurial', true))
149 );
150 }
151
152 /**
153 * Filter links using a tag combination
154 */
155 public function testFilterMultipleTags()
156 {
157 $this->assertEquals(
158 2,
159 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'dev cartoon', false))
160 );
161 }
162
163 /**
164 * Filter links using a non-existent tag
165 */
166 public function testFilterUnknownTag()
167 {
168 $this->assertEquals(
169 0,
170 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'null', false))
171 );
172 }
173
174 /**
175 * Return links for a given day
176 */
177 public function testFilterDay()
178 {
179 $this->assertEquals(
180 4,
181 count(self::$linkFilter->filter(LinkFilter::$FILTER_DAY, '20121206'))
182 );
183 }
184
185 /**
186 * 404 - day not found
187 */
188 public function testFilterUnknownDay()
189 {
190 $this->assertEquals(
191 0,
192 count(self::$linkFilter->filter(LinkFilter::$FILTER_DAY, '19700101'))
193 );
194 }
195
196 /**
197 * Use an invalid date format
198 * @expectedException Exception
199 * @expectedExceptionMessageRegExp /Invalid date format/
200 */
201 public function testFilterInvalidDayWithChars()
202 {
203 self::$linkFilter->filter(LinkFilter::$FILTER_DAY, 'Rainy day, dream away');
204 }
205
206 /**
207 * Use an invalid date format
208 * @expectedException Exception
209 * @expectedExceptionMessageRegExp /Invalid date format/
210 */
211 public function testFilterInvalidDayDigits()
212 {
213 self::$linkFilter->filter(LinkFilter::$FILTER_DAY, '20');
214 }
215
216 /**
217 * Retrieve a link entry with its hash
218 */
219 public function testFilterSmallHash()
220 {
221 $links = self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'IuWvgA');
222
223 $this->assertEquals(
224 1,
225 count($links)
226 );
227
228 $this->assertEquals(
229 'MediaGoblin',
230 $links[7]['title']
231 );
232 }
233
234 /**
235 * No link for this hash
236 *
237 * @expectedException \Shaarli\Bookmark\Exception\LinkNotFoundException
238 */
239 public function testFilterUnknownSmallHash()
240 {
241 self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'Iblaah');
242 }
243
244 /**
245 * Full-text search - no result found.
246 */
247 public function testFilterFullTextNoResult()
248 {
249 $this->assertEquals(
250 0,
251 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'azertyuiop'))
252 );
253 }
254
255 /**
256 * Full-text search - result from a link's URL
257 */
258 public function testFilterFullTextURL()
259 {
260 $this->assertEquals(
261 2,
262 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'ars.userfriendly.org'))
263 );
264
265 $this->assertEquals(
266 2,
267 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'ars org'))
268 );
269 }
270
271 /**
272 * Full-text search - result from a link's title only
273 */
274 public function testFilterFullTextTitle()
275 {
276 // use miscellaneous cases
277 $this->assertEquals(
278 2,
279 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'userfriendly -'))
280 );
281 $this->assertEquals(
282 2,
283 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'UserFriendly -'))
284 );
285 $this->assertEquals(
286 2,
287 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'uSeRFrIendlY -'))
288 );
289
290 // use miscellaneous case and offset
291 $this->assertEquals(
292 2,
293 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'RFrIendL'))
294 );
295 }
296
297 /**
298 * Full-text search - result from the link's description only
299 */
300 public function testFilterFullTextDescription()
301 {
302 $this->assertEquals(
303 1,
304 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'publishing media'))
305 );
306
307 $this->assertEquals(
308 1,
309 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'mercurial w3c'))
310 );
311
312 $this->assertEquals(
313 3,
314 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '"free software"'))
315 );
316 }
317
318 /**
319 * Full-text search - result from the link's tags only
320 */
321 public function testFilterFullTextTags()
322 {
323 $this->assertEquals(
324 6,
325 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'web'))
326 );
327
328 $this->assertEquals(
329 6,
330 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'web', 'all'))
331 );
332
333 $this->assertEquals(
334 6,
335 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'web', 'bla'))
336 );
337
338 // Private only.
339 $this->assertEquals(
340 1,
341 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'web', false, 'private'))
342 );
343
344 // Public only.
345 $this->assertEquals(
346 5,
347 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'web', false, 'public'))
348 );
349 }
350
351 /**
352 * Full-text search - result set from mixed sources
353 */
354 public function testFilterFullTextMixed()
355 {
356 $this->assertEquals(
357 3,
358 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'free software'))
359 );
360 }
361
362 /**
363 * Full-text search - test exclusion with '-'.
364 */
365 public function testExcludeSearch()
366 {
367 $this->assertEquals(
368 1,
369 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'free -gnu'))
370 );
371
372 $this->assertEquals(
373 ReferenceLinkDB::$NB_LINKS_TOTAL - 1,
374 count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution'))
375 );
376 }
377
378 /**
379 * Full-text search - test AND, exact terms and exclusion combined, across fields.
380 */
381 public function testMultiSearch()
382 {
383 $this->assertEquals(
384 2,
385 count(self::$linkFilter->filter(
386 LinkFilter::$FILTER_TEXT,
387 '"Free Software " stallman "read this" @website stuff'
388 ))
389 );
390
391 $this->assertEquals(
392 1,
393 count(self::$linkFilter->filter(
394 LinkFilter::$FILTER_TEXT,
395 '"free software " stallman "read this" -beard @website stuff'
396 ))
397 );
398 }
399
400 /**
401 * Full-text search - make sure that exact search won't work across fields.
402 */
403 public function testSearchExactTermMultiFieldsKo()
404 {
405 $this->assertEquals(
406 0,
407 count(self::$linkFilter->filter(
408 LinkFilter::$FILTER_TEXT,
409 '"designer naming"'
410 ))
411 );
412
413 $this->assertEquals(
414 0,
415 count(self::$linkFilter->filter(
416 LinkFilter::$FILTER_TEXT,
417 '"designernaming"'
418 ))
419 );
420 }
421
422 /**
423 * Tag search with exclusion.
424 */
425 public function testTagFilterWithExclusion()
426 {
427 $this->assertEquals(
428 1,
429 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'gnu -free'))
430 );
431
432 $this->assertEquals(
433 ReferenceLinkDB::$NB_LINKS_TOTAL - 1,
434 count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
435 );
436 }
437
438 /**
439 * Test crossed search (terms + tags).
440 */
441 public function testFilterCrossedSearch()
442 {
443 $terms = '"Free Software " stallman "read this" @website stuff';
444 $tags = 'free';
445 $this->assertEquals(
446 1,
447 count(self::$linkFilter->filter(
448 LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
449 array($tags, $terms)
450 ))
451 );
452 $this->assertEquals(
453 2,
454 count(self::$linkFilter->filter(
455 LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
456 array('', $terms)
457 ))
458 );
459 $this->assertEquals(
460 1,
461 count(self::$linkFilter->filter(
462 LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
463 array(false, 'PSR-2')
464 ))
465 );
466 $this->assertEquals(
467 1,
468 count(self::$linkFilter->filter(
469 LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
470 array($tags, '')
471 ))
472 );
473 $this->assertEquals(
474 ReferenceLinkDB::$NB_LINKS_TOTAL,
475 count(self::$linkFilter->filter(
476 LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
477 ''
478 ))
479 );
480 }
481
482 /**
483 * Filter links by #hashtag.
484 */
485 public function testFilterByHashtag()
486 {
487 $hashtag = 'hashtag';
488 $this->assertEquals(
489 3,
490 count(self::$linkFilter->filter(
491 LinkFilter::$FILTER_TAG,
492 $hashtag
493 ))
494 );
495
496 $hashtag = 'private';
497 $this->assertEquals(
498 1,
499 count(self::$linkFilter->filter(
500 LinkFilter::$FILTER_TAG,
501 $hashtag,
502 false,
503 'private'
504 ))
505 );
506 }
507}
diff --git a/tests/bookmark/LinkUtilsTest.php b/tests/bookmark/LinkUtilsTest.php
new file mode 100644
index 00000000..1b8688e6
--- /dev/null
+++ b/tests/bookmark/LinkUtilsTest.php
@@ -0,0 +1,333 @@
1<?php
2
3namespace Shaarli\Bookmark;
4
5use ReferenceLinkDB;
6
7require_once 'tests/utils/CurlUtils.php';
8
9/**
10 * Class LinkUtilsTest.
11 */
12class LinkUtilsTest extends \PHPUnit\Framework\TestCase
13{
14 /**
15 * Test html_extract_title() when the title is found.
16 */
17 public function testHtmlExtractExistentTitle()
18 {
19 $title = 'Read me please.';
20 $html = '<html><meta>stuff</meta><title>' . $title . '</title></html>';
21 $this->assertEquals($title, html_extract_title($html));
22 $html = '<html><title>' . $title . '</title>blabla<title>another</title></html>';
23 $this->assertEquals($title, html_extract_title($html));
24 }
25
26 /**
27 * Test html_extract_title() when the title is not found.
28 */
29 public function testHtmlExtractNonExistentTitle()
30 {
31 $html = '<html><meta>stuff</meta></html>';
32 $this->assertFalse(html_extract_title($html));
33 }
34
35 /**
36 * Test headers_extract_charset() when the charset is found.
37 */
38 public function testHeadersExtractExistentCharset()
39 {
40 $charset = 'x-MacCroatian';
41 $headers = 'text/html; charset=' . $charset;
42 $this->assertEquals(strtolower($charset), header_extract_charset($headers));
43 }
44
45 /**
46 * Test headers_extract_charset() when the charset is not found.
47 */
48 public function testHeadersExtractNonExistentCharset()
49 {
50 $headers = '';
51 $this->assertFalse(header_extract_charset($headers));
52
53 $headers = 'text/html';
54 $this->assertFalse(header_extract_charset($headers));
55 }
56
57 /**
58 * Test html_extract_charset() when the charset is found.
59 */
60 public function testHtmlExtractExistentCharset()
61 {
62 $charset = 'x-MacCroatian';
63 $html = '<html><meta>stuff2</meta><meta charset="' . $charset . '"/></html>';
64 $this->assertEquals(strtolower($charset), html_extract_charset($html));
65 }
66
67 /**
68 * Test html_extract_charset() when the charset is not found.
69 */
70 public function testHtmlExtractNonExistentCharset()
71 {
72 $html = '<html><meta>stuff</meta></html>';
73 $this->assertFalse(html_extract_charset($html));
74 $html = '<html><meta>stuff</meta><meta charset=""/></html>';
75 $this->assertFalse(html_extract_charset($html));
76 }
77
78 /**
79 * Test the download callback with valid value
80 */
81 public function testCurlDownloadCallbackOk()
82 {
83 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ok');
84 $data = [
85 'HTTP/1.1 200 OK',
86 'Server: GitHub.com',
87 'Date: Sat, 28 Oct 2017 12:01:33 GMT',
88 'Content-Type: text/html; charset=utf-8',
89 'Status: 200 OK',
90 'end' => 'th=device-width">'
91 . '<title>Refactoring · GitHub</title>'
92 . '<link rel="search" type="application/opensea',
93 '<title>ignored</title>',
94 ];
95 foreach ($data as $key => $line) {
96 $ignore = null;
97 $expected = $key !== 'end' ? strlen($line) : false;
98 $this->assertEquals($expected, $callback($ignore, $line));
99 if ($expected === false) {
100 break;
101 }
102 }
103 $this->assertEquals('utf-8', $charset);
104 $this->assertEquals('Refactoring · GitHub', $title);
105 }
106
107 /**
108 * Test the download callback with valid values and no charset
109 */
110 public function testCurlDownloadCallbackOkNoCharset()
111 {
112 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset');
113 $data = [
114 'HTTP/1.1 200 OK',
115 'end' => 'th=device-width">'
116 . '<title>Refactoring · GitHub</title>'
117 . '<link rel="search" type="application/opensea',
118 '<title>ignored</title>',
119 ];
120 foreach ($data as $key => $line) {
121 $ignore = null;
122 $this->assertEquals(strlen($line), $callback($ignore, $line));
123 }
124 $this->assertEmpty($charset);
125 $this->assertEquals('Refactoring · GitHub', $title);
126 }
127
128 /**
129 * Test the download callback with valid values and no charset
130 */
131 public function testCurlDownloadCallbackOkHtmlCharset()
132 {
133 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_no_charset');
134 $data = [
135 'HTTP/1.1 200 OK',
136 '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />',
137 'end' => 'th=device-width">'
138 . '<title>Refactoring · GitHub</title>'
139 . '<link rel="search" type="application/opensea',
140 '<title>ignored</title>',
141 ];
142 foreach ($data as $key => $line) {
143 $ignore = null;
144 $expected = $key !== 'end' ? strlen($line) : false;
145 $this->assertEquals($expected, $callback($ignore, $line));
146 if ($expected === false) {
147 break;
148 }
149 }
150 $this->assertEquals('utf-8', $charset);
151 $this->assertEquals('Refactoring · GitHub', $title);
152 }
153
154 /**
155 * Test the download callback with valid values and no title
156 */
157 public function testCurlDownloadCallbackOkNoTitle()
158 {
159 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ok');
160 $data = [
161 'HTTP/1.1 200 OK',
162 'end' => 'th=device-width">Refactoring · GitHub<link rel="search" type="application/opensea',
163 'ignored',
164 ];
165 foreach ($data as $key => $line) {
166 $ignore = null;
167 $this->assertEquals(strlen($line), $callback($ignore, $line));
168 }
169 $this->assertEquals('utf-8', $charset);
170 $this->assertEmpty($title);
171 }
172
173 /**
174 * Test the download callback with an invalid content type.
175 */
176 public function testCurlDownloadCallbackInvalidContentType()
177 {
178 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_ct_ko');
179 $ignore = null;
180 $this->assertFalse($callback($ignore, ''));
181 $this->assertEmpty($charset);
182 $this->assertEmpty($title);
183 }
184
185 /**
186 * Test the download callback with an invalid response code.
187 */
188 public function testCurlDownloadCallbackInvalidResponseCode()
189 {
190 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_rc_ko');
191 $ignore = null;
192 $this->assertFalse($callback($ignore, ''));
193 $this->assertEmpty($charset);
194 $this->assertEmpty($title);
195 }
196
197 /**
198 * Test the download callback with an invalid content type and response code.
199 */
200 public function testCurlDownloadCallbackInvalidContentTypeAndResponseCode()
201 {
202 $callback = get_curl_download_callback($charset, $title, 'ut_curl_getinfo_rs_ct_ko');
203 $ignore = null;
204 $this->assertFalse($callback($ignore, ''));
205 $this->assertEmpty($charset);
206 $this->assertEmpty($title);
207 }
208
209 /**
210 * Test count_private.
211 */
212 public function testCountPrivateLinks()
213 {
214 $refDB = new ReferenceLinkDB();
215 $this->assertEquals($refDB->countPrivateLinks(), count_private($refDB->getLinks()));
216 }
217
218 /**
219 * Test text2clickable without a redirector being set.
220 */
221 public function testText2clickableWithoutRedirector()
222 {
223 $text = 'stuff http://hello.there/is=someone#here otherstuff';
224 $expectedText = 'stuff <a href="http://hello.there/is=someone#here">'
225 . 'http://hello.there/is=someone#here</a> otherstuff';
226 $processedText = text2clickable($text, '');
227 $this->assertEquals($expectedText, $processedText);
228
229 $text = 'stuff http://hello.there/is=someone#here(please) otherstuff';
230 $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)">'
231 . 'http://hello.there/is=someone#here(please)</a> otherstuff';
232 $processedText = text2clickable($text, '');
233 $this->assertEquals($expectedText, $processedText);
234
235 $text = 'stuff http://hello.there/is=someone#here(please)&no otherstuff';
236 $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)&no">'
237 . 'http://hello.there/is=someone#here(please)&no</a> otherstuff';
238 $processedText = text2clickable($text, '');
239 $this->assertEquals($expectedText, $processedText);
240 }
241
242 /**
243 * Test text2clickable with a redirector set.
244 */
245 public function testText2clickableWithRedirector()
246 {
247 $text = 'stuff http://hello.there/is=someone#here otherstuff';
248 $redirector = 'http://redirector.to';
249 $expectedText = 'stuff <a href="' .
250 $redirector .
251 urlencode('http://hello.there/is=someone#here') .
252 '">http://hello.there/is=someone#here</a> otherstuff';
253 $processedText = text2clickable($text, $redirector);
254 $this->assertEquals($expectedText, $processedText);
255 }
256
257 /**
258 * Test text2clickable a redirector set and without URL encode.
259 */
260 public function testText2clickableWithRedirectorDontEncode()
261 {
262 $text = 'stuff http://hello.there/?is=someone&or=something#here otherstuff';
263 $redirector = 'http://redirector.to';
264 $expectedText = 'stuff <a href="' .
265 $redirector .
266 'http://hello.there/?is=someone&or=something#here' .
267 '">http://hello.there/?is=someone&or=something#here</a> otherstuff';
268 $processedText = text2clickable($text, $redirector, false);
269 $this->assertEquals($expectedText, $processedText);
270 }
271
272 /**
273 * Test testSpace2nbsp.
274 */
275 public function testSpace2nbsp()
276 {
277 $text = ' Are you thrilled by flags ?' . PHP_EOL . ' Really?';
278 $expectedText = '&nbsp; Are you &nbsp; thrilled &nbsp;by flags &nbsp; ?' . PHP_EOL . '&nbsp;Really?';
279 $processedText = space2nbsp($text);
280 $this->assertEquals($expectedText, $processedText);
281 }
282
283 /**
284 * Test hashtags auto-link.
285 */
286 public function testHashtagAutolink()
287 {
288 $index = 'http://domain.tld/';
289 $rawDescription = '#hashtag\n
290 # nothashtag\n
291 test#nothashtag #hashtag \#nothashtag\n
292 test #hashtag #hashtag test #hashtag.test\n
293 #hashtag #hashtag-nothashtag #hashtag_hashtag\n
294 What is #ашок anyway?\n
295 カタカナ #カタカナ」カタカナ\n';
296 $autolinkedDescription = hashtag_autolink($rawDescription, $index);
297
298 $this->assertContains($this->getHashtagLink('hashtag', $index), $autolinkedDescription);
299 $this->assertNotContains(' #hashtag', $autolinkedDescription);
300 $this->assertNotContains('>#nothashtag', $autolinkedDescription);
301 $this->assertContains($this->getHashtagLink('ашок', $index), $autolinkedDescription);
302 $this->assertContains($this->getHashtagLink('カタカナ', $index), $autolinkedDescription);
303 $this->assertContains($this->getHashtagLink('hashtag_hashtag', $index), $autolinkedDescription);
304 $this->assertNotContains($this->getHashtagLink('hashtag-nothashtag', $index), $autolinkedDescription);
305 }
306
307 /**
308 * Test hashtags auto-link without index URL.
309 */
310 public function testHashtagAutolinkNoIndex()
311 {
312 $rawDescription = 'blabla #hashtag x#nothashtag';
313 $autolinkedDescription = hashtag_autolink($rawDescription);
314
315 $this->assertContains($this->getHashtagLink('hashtag'), $autolinkedDescription);
316 $this->assertNotContains(' #hashtag', $autolinkedDescription);
317 $this->assertNotContains('>#nothashtag', $autolinkedDescription);
318 }
319
320 /**
321 * Util function to build an hashtag link.
322 *
323 * @param string $hashtag Hashtag name.
324 * @param string $index Index URL.
325 *
326 * @return string HTML hashtag link.
327 */
328 private function getHashtagLink($hashtag, $index = '')
329 {
330 $hashtagLink = '<a href="' . $index . '?addtag=$1" title="Hashtag $1">#$1</a>';
331 return str_replace('$1', $hashtag, $hashtagLink);
332 }
333}