3 declare(strict_types
=1);
5 namespace Shaarli\Front\Controller\Visitor
;
7 use Shaarli\Bookmark\Bookmark
;
8 use Shaarli\Bookmark\Exception\BookmarkNotFoundException
;
9 use Shaarli\Bookmark\SearchResult
;
10 use Shaarli\Config\ConfigManager
;
11 use Shaarli\Security\LoginManager
;
13 use Shaarli\Thumbnailer
;
14 use Slim\Http\Request
;
15 use Slim\Http\Response
;
17 class BookmarkListControllerTest
extends TestCase
19 use FrontControllerMockHelper
;
21 /** @var BookmarkListController */
22 protected $controller;
24 public function setUp(): void
26 $this->createContainer();
28 $this->controller
= new BookmarkListController($this->container
);
32 * Test rendering list of bookmarks with default parameters (first page).
34 public function testIndexDefaultFirstPage(): void
36 $assignedVariables = [];
37 $this->assignTemplateVars($assignedVariables);
39 $request = $this->createMock(Request
::class);
40 $response = new Response();
42 $this->container
->bookmarkService
43 ->expects(static::once())
46 ['searchtags' => '', 'searchterm' => ''],
51 ['offset' => 0, 'limit' => 2]
53 ->willReturn(SearchResult
::getSearchResult([
54 (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
55 (new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
56 (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
60 $this->container
->sessionManager
61 ->method('getSessionParameter')
62 ->willReturnCallback(function (string $parameter, $default = null) {
63 if ('LINKS_PER_PAGE' === $parameter) {
71 $result = $this->controller
->index($request, $response);
73 static::assertSame(200, $result->getStatusCode());
74 static::assertSame('linklist', (string) $result->getBody());
76 static::assertSame('Shaarli', $assignedVariables['pagetitle']);
77 static::assertSame('?page=2', $assignedVariables['previous_page_url']);
78 static::assertSame('', $assignedVariables['next_page_url']);
79 static::assertSame(2, $assignedVariables['page_max']);
80 static::assertSame('', $assignedVariables['search_tags']);
81 static::assertSame(3, $assignedVariables['result_count']);
82 static::assertSame(1, $assignedVariables['page_current']);
83 static::assertSame('', $assignedVariables['search_term']);
84 static::assertNull($assignedVariables['visibility']);
85 static::assertCount(2, $assignedVariables['links']);
87 $link = $assignedVariables['links'][0];
89 static::assertSame(1, $link['id']);
90 static::assertSame('http://url1.tld', $link['url']);
91 static::assertSame('Title 1', $link['title']);
93 $link = $assignedVariables['links'][1];
95 static::assertSame(2, $link['id']);
96 static::assertSame('http://url2.tld', $link['url']);
97 static::assertSame('Title 2', $link['title']);
101 * Test rendering list of bookmarks with default parameters (second page).
103 public function testIndexDefaultSecondPage(): void
105 $assignedVariables = [];
106 $this->assignTemplateVars($assignedVariables);
108 $request = $this->createMock(Request
::class);
109 $request->method('getParam')->willReturnCallback(function (string $key) {
110 if ('page' === $key) {
116 $response = new Response();
118 $this->container
->bookmarkService
119 ->expects(static::once())
122 ['searchtags' => '', 'searchterm' => ''],
127 ['offset' => 2, 'limit' => 2]
129 ->willReturn(SearchResult
::getSearchResult([
130 (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
131 (new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
132 (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
136 $this->container
->sessionManager
137 ->method('getSessionParameter')
138 ->willReturnCallback(function (string $parameter, $default = null) {
139 if ('LINKS_PER_PAGE' === $parameter) {
147 $result = $this->controller
->index($request, $response);
149 static::assertSame(200, $result->getStatusCode());
150 static::assertSame('linklist', (string) $result->getBody());
152 static::assertSame('Shaarli', $assignedVariables['pagetitle']);
153 static::assertSame('', $assignedVariables['previous_page_url']);
154 static::assertSame('?page=1', $assignedVariables['next_page_url']);
155 static::assertSame(2, $assignedVariables['page_max']);
156 static::assertSame('', $assignedVariables['search_tags']);
157 static::assertSame(3, $assignedVariables['result_count']);
158 static::assertSame(2, $assignedVariables['page_current']);
159 static::assertSame('', $assignedVariables['search_term']);
160 static::assertNull($assignedVariables['visibility']);
161 static::assertCount(1, $assignedVariables['links']);
163 $link = $assignedVariables['links'][2];
165 static::assertSame(3, $link['id']);
166 static::assertSame('http://url3.tld', $link['url']);
167 static::assertSame('Title 3', $link['title']);
171 * Test rendering list of bookmarks with filters.
173 public function testIndexDefaultWithFilters(): void
175 $assignedVariables = [];
176 $this->assignTemplateVars($assignedVariables);
178 $request = $this->createMock(Request
::class);
179 $request->method('getParam')->willReturnCallback(function (string $key) {
180 if ('searchtags' === $key) {
183 if ('searchterm' === $key) {
189 $response = new Response();
191 $this->container
->sessionManager
192 ->method('getSessionParameter')
193 ->willReturnCallback(function (string $key, $default) {
194 if ('LINKS_PER_PAGE' === $key) {
197 if ('visibility' === $key) {
200 if ('untaggedonly' === $key) {
208 $this->container
->bookmarkService
209 ->expects(static::once())
212 ['searchtags' => 'abc@def', 'searchterm' => 'ghi jkl'],
217 ['offset' => 0, 'limit' => 2]
219 ->willReturn(SearchResult
::getSearchResult([
220 (new Bookmark())->setId(1)->setUrl('http://url1.tld')->setTitle('Title 1'),
221 (new Bookmark())->setId(2)->setUrl('http://url2.tld')->setTitle('Title 2'),
222 (new Bookmark())->setId(3)->setUrl('http://url3.tld')->setTitle('Title 3'),
226 $result = $this->controller
->index($request, $response);
228 static::assertSame(200, $result->getStatusCode());
229 static::assertSame('linklist', (string) $result->getBody());
231 static::assertSame('Search: ghi jkl [abc] [def] - Shaarli', $assignedVariables['pagetitle']);
232 static::assertSame('?page=2&searchterm=ghi+jkl&searchtags=abc%40def', $assignedVariables['previous_page_url']);
236 * Test displaying a permalink with valid parameters
238 public function testPermalinkValid(): void
242 $assignedVariables = [];
243 $this->assignTemplateVars($assignedVariables);
245 $request = $this->createMock(Request
::class);
246 $response = new Response();
248 $this->container
->bookmarkService
249 ->expects(static::once())
250 ->method('findByHash')
252 ->willReturn((new Bookmark())->setId(123)->setTitle('Title 1')->setUrl('http://url1.tld'))
255 $result = $this->controller
->permalink($request, $response, ['hash' => $hash]);
257 static::assertSame(200, $result->getStatusCode());
258 static::assertSame('linklist', (string) $result->getBody());
260 static::assertSame('Title 1 - Shaarli', $assignedVariables['pagetitle']);
261 static::assertCount(1, $assignedVariables['links']);
263 $link = $assignedVariables['links'][0];
265 static::assertSame(123, $link['id']);
266 static::assertSame('http://url1.tld', $link['url']);
267 static::assertSame('Title 1', $link['title']);
271 * Test displaying a permalink with an unknown small hash : renders a 404 template error
273 public function testPermalinkNotFound(): void
277 $assignedVariables = [];
278 $this->assignTemplateVars($assignedVariables);
280 $request = $this->createMock(Request
::class);
281 $response = new Response();
283 $this->container
->bookmarkService
284 ->expects(static::once())
285 ->method('findByHash')
287 ->willThrowException(new BookmarkNotFoundException())
290 $result = $this->controller
->permalink($request, $response, ['hash' => $hash]);
292 static::assertSame(200, $result->getStatusCode());
293 static::assertSame('404', (string) $result->getBody());
296 'The link you are trying to reach does not exist or has been deleted.',
297 $assignedVariables['error_message']
302 * Test GET /shaare/{hash}?key={key} - Find a link by hash using a private link.
304 public function testPermalinkWithPrivateKey(): void
307 $privateKey = 'this is a private key';
309 $assignedVariables = [];
310 $this->assignTemplateVars($assignedVariables);
312 $request = $this->createMock(Request
::class);
313 $request->method('getParam')->willReturnCallback(function (string $key, $default = null) use ($privateKey) {
314 return $key === 'key' ? $privateKey : $default;
316 $response = new Response();
318 $this->container
->bookmarkService
319 ->expects(static::once())
320 ->method('findByHash')
321 ->with($hash, $privateKey)
322 ->willReturn((new Bookmark())->setId(123)->setTitle('Title 1')->setUrl('http://url1.tld'))
325 $result = $this->controller
->permalink($request, $response, ['hash' => $hash]);
327 static::assertSame(200, $result->getStatusCode());
328 static::assertSame('linklist', (string) $result->getBody());
329 static::assertCount(1, $assignedVariables['links']);
333 * Test getting link list with thumbnail updates.
334 * -> 2 thumbnails update, only 1 datastore write
336 public function testThumbnailUpdateFromLinkList(): void
338 $request = $this->createMock(Request
::class);
339 $response = new Response();
341 $this->container
->loginManager
= $this->createMock(LoginManager
::class);
342 $this->container
->loginManager
->method('isLoggedIn')->willReturn(true);
344 $this->container
->conf
= $this->createMock(ConfigManager
::class);
345 $this->container
->conf
347 ->willReturnCallback(function (string $key, $default) {
348 if ($key === 'thumbnails.mode') {
349 return Thumbnailer
::MODE_ALL
;
350 } elseif ($key === 'general.enable_async_metadata') {
358 $this->container
->thumbnailer
= $this->createMock(Thumbnailer
::class);
359 $this->container
->thumbnailer
360 ->expects(static::exactly(2))
362 ->withConsecutive(['https://url2.tld'], ['https://url4.tld'])
365 $this->container
->bookmarkService
366 ->expects(static::once())
368 ->willReturn(SearchResult
::getSearchResult([
369 (new Bookmark())->setId(1)->setUrl('https://url1.tld')->setTitle('Title 1')->setThumbnail(false),
370 $b1 = (new Bookmark())->setId(2)->setUrl('https://url2.tld')->setTitle('Title 2'),
371 (new Bookmark())->setId(3)->setUrl('https://url3.tld')->setTitle('Title 3')->setThumbnail(false),
372 $b2 = (new Bookmark())->setId(2)->setUrl('https://url4.tld')->setTitle('Title 4'),
373 (new Bookmark())->setId(2)->setUrl('ftp://url5.tld', ['ftp'])->setTitle('Title 5'),
376 $this->container
->bookmarkService
377 ->expects(static::exactly(2))
379 ->withConsecutive([$b1, false], [$b2, false])
381 $this->container
->bookmarkService
->expects(static::once())->method('save');
383 $result = $this->controller
->index($request, $response);
385 static::assertSame(200, $result->getStatusCode());
386 static::assertSame('linklist', (string) $result->getBody());
390 * Test getting a permalink with thumbnail update.
392 public function testThumbnailUpdateFromPermalink(): void
394 $request = $this->createMock(Request
::class);
395 $response = new Response();
397 $this->container
->loginManager
= $this->createMock(LoginManager
::class);
398 $this->container
->loginManager
->method('isLoggedIn')->willReturn(true);
400 $this->container
->conf
= $this->createMock(ConfigManager
::class);
401 $this->container
->conf
403 ->willReturnCallback(function (string $key, $default) {
404 if ($key === 'thumbnails.mode') {
405 return Thumbnailer
::MODE_ALL
;
406 } elseif ($key === 'general.enable_async_metadata') {
414 $this->container
->thumbnailer
= $this->createMock(Thumbnailer
::class);
415 $this->container
->thumbnailer
->expects(static::once())->method('get')->withConsecutive(['https://url.tld']);
417 $this->container
->bookmarkService
418 ->expects(static::once())
419 ->method('findByHash')
420 ->willReturn($bookmark = (new Bookmark())->setId(2)->setUrl('https://url.tld')->setTitle('Title 1'))
422 $this->container
->bookmarkService
->expects(static::once())->method('set')->with($bookmark, true);
423 $this->container
->bookmarkService
->expects(static::never())->method('save');
425 $result = $this->controller
->permalink($request, $response, ['hash' => 'abc']);
427 static::assertSame(200, $result->getStatusCode());
428 static::assertSame('linklist', (string) $result->getBody());
432 * Test getting a permalink with thumbnail update with async setting: no update should run.
434 public function testThumbnailUpdateFromPermalinkAsync(): void
436 $request = $this->createMock(Request
::class);
437 $response = new Response();
439 $this->container
->loginManager
= $this->createMock(LoginManager
::class);
440 $this->container
->loginManager
->method('isLoggedIn')->willReturn(true);
442 $this->container
->conf
= $this->createMock(ConfigManager
::class);
443 $this->container
->conf
445 ->willReturnCallback(function (string $key, $default) {
446 if ($key === 'thumbnails.mode') {
447 return Thumbnailer
::MODE_ALL
;
448 } elseif ($key === 'general.enable_async_metadata') {
456 $this->container
->thumbnailer
= $this->createMock(Thumbnailer
::class);
457 $this->container
->thumbnailer
->expects(static::never())->method('get');
459 $this->container
->bookmarkService
460 ->expects(static::once())
461 ->method('findByHash')
462 ->willReturn((new Bookmark())->setId(2)->setUrl('https://url.tld')->setTitle('Title 1'))
464 $this->container
->bookmarkService
->expects(static::never())->method('set');
465 $this->container
->bookmarkService
->expects(static::never())->method('save');
467 $result = $this->controller
->permalink($request, $response, ['hash' => 'abc']);
469 static::assertSame(200, $result->getStatusCode());
473 * Trigger legacy controller in link list controller: permalink
475 public function testLegacyControllerPermalink(): void
478 $this->container
->environment
['QUERY_STRING'] = $hash;
480 $request = $this->createMock(Request
::class);
481 $response = new Response();
483 $result = $this->controller
->index($request, $response);
485 static::assertSame(302, $result->getStatusCode());
486 static::assertSame('/subfolder/shaare/' . $hash, $result->getHeader('location')[0]);
490 * Trigger legacy controller in link list controller: ?do= query parameter
492 public function testLegacyControllerDoPage(): void
494 $request = $this->createMock(Request
::class);
495 $request->method('getQueryParam')->with('do')->willReturn('picwall');
496 $response = new Response();
498 $result = $this->controller
->index($request, $response);
500 static::assertSame(302, $result->getStatusCode());
501 static::assertSame('/subfolder/picture-wall', $result->getHeader('location')[0]);
505 * Trigger legacy controller in link list controller: ?do= query parameter with unknown legacy route
507 public function testLegacyControllerUnknownDoPage(): void
509 $request = $this->createMock(Request
::class);
510 $request->method('getQueryParam')->with('do')->willReturn('nope');
511 $response = new Response();
513 $result = $this->controller
->index($request, $response);
515 static::assertSame(200, $result->getStatusCode());
516 static::assertSame('linklist', (string) $result->getBody());
520 * Trigger legacy controller in link list controller: other GET route (e.g. ?post)
522 public function testLegacyControllerGetParameter(): void
524 $request = $this->createMock(Request
::class);
525 $request->method('getQueryParams')->willReturn(['post' => $url = 'http://url.tld']);
526 $response = new Response();
528 $this->container
->loginManager
= $this->createMock(LoginManager
::class);
529 $this->container
->loginManager
->method('isLoggedIn')->willReturn(true);
531 $result = $this->controller
->index($request, $response);
533 static::assertSame(302, $result->getStatusCode());
535 '/subfolder/admin/shaare?post=' . urlencode($url),
536 $result->getHeader('location')[0]