3 namespace Tests\Permissions;
5 use BookStack\Auth\User;
6 use BookStack\Entities\Models\Book;
7 use BookStack\Entities\Models\Bookshelf;
8 use BookStack\Entities\Models\Chapter;
9 use BookStack\Entities\Models\Entity;
10 use BookStack\Entities\Models\Page;
11 use Illuminate\Support\Str;
14 class EntityPermissionsTest extends TestCase
26 public function setUp(): void
29 $this->user = $this->getEditor();
30 $this->viewer = $this->getViewer();
33 protected function setRestrictionsForTestRoles(Entity $entity, array $actions = [])
36 $this->user->roles->first(),
37 $this->viewer->roles->first(),
39 $this->setEntityRestrictions($entity, $actions, $roles);
42 public function test_bookshelf_view_restriction()
44 /** @var Bookshelf $shelf */
45 $shelf = Bookshelf::query()->first();
47 $this->actingAs($this->user)
48 ->get($shelf->getUrl())
51 $this->setRestrictionsForTestRoles($shelf, []);
53 $this->followingRedirects()->get($shelf->getUrl())
54 ->assertSee('Bookshelf not found');
56 $this->setRestrictionsForTestRoles($shelf, ['view']);
58 $this->get($shelf->getUrl())
59 ->assertSee($shelf->name);
62 public function test_bookshelf_update_restriction()
64 /** @var Bookshelf $shelf */
65 $shelf = Bookshelf::query()->first();
67 $this->actingAs($this->user)
68 ->get($shelf->getUrl('/edit'))
69 ->assertSee('Edit Book');
71 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
73 $resp = $this->get($shelf->getUrl('/edit'))
74 ->assertRedirect('/');
75 $this->followRedirects($resp)->assertSee('You do not have permission');
77 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
79 $this->get($shelf->getUrl('/edit'))
83 public function test_bookshelf_delete_restriction()
85 /** @var Bookshelf $shelf */
86 $shelf = Bookshelf::query()->first();
88 $this->actingAs($this->user)
89 ->get($shelf->getUrl('/delete'))
90 ->assertSee('Delete Book');
92 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
94 $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
95 $this->get('/')->assertSee('You do not have permission');
97 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
99 $this->get($shelf->getUrl('/delete'))
101 ->assertSee('Delete Book');
104 public function test_book_view_restriction()
106 /** @var Book $book */
107 $book = Book::query()->first();
108 $bookPage = $book->pages->first();
109 $bookChapter = $book->chapters->first();
111 $bookUrl = $book->getUrl();
112 $this->actingAs($this->user)
116 $this->setRestrictionsForTestRoles($book, []);
118 $this->followingRedirects()->get($bookUrl)
119 ->assertSee('Book not found');
120 $this->followingRedirects()->get($bookPage->getUrl())
121 ->assertSee('Page not found');
122 $this->followingRedirects()->get($bookChapter->getUrl())
123 ->assertSee('Chapter not found');
125 $this->setRestrictionsForTestRoles($book, ['view']);
128 ->assertSee($book->name);
129 $this->get($bookPage->getUrl())
130 ->assertSee($bookPage->name);
131 $this->get($bookChapter->getUrl())
132 ->assertSee($bookChapter->name);
135 public function test_book_create_restriction()
137 /** @var Book $book */
138 $book = Book::query()->first();
140 $bookUrl = $book->getUrl();
141 $this->actingAs($this->viewer)
143 ->assertElementNotContains('.actions', 'New Page')
144 ->assertElementNotContains('.actions', 'New Chapter');
145 $this->actingAs($this->user)
147 ->assertElementContains('.actions', 'New Page')
148 ->assertElementContains('.actions', 'New Chapter');
150 $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
152 $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
153 $this->get('/')->assertSee('You do not have permission');
155 $this->get($bookUrl . '/create-page')->assertRedirect('/');
156 $this->get('/')->assertSee('You do not have permission');
159 ->assertElementNotContains('.actions', 'New Page')
160 ->assertElementNotContains('.actions', 'New Chapter');
162 $this->setRestrictionsForTestRoles($book, ['view', 'create']);
164 $resp = $this->post($book->getUrl('/create-chapter'), [
165 'name' => 'test chapter',
166 'description' => 'desc',
168 $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
170 $this->get($book->getUrl('/create-page'));
171 /** @var Page $page */
172 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
173 $resp = $this->post($page->getUrl(), [
174 'name' => 'test page',
175 'html' => 'test content',
177 $resp->assertRedirect($book->getUrl('/page/test-page'));
180 ->assertElementContains('.actions', 'New Page')
181 ->assertElementContains('.actions', 'New Chapter');
184 public function test_book_update_restriction()
186 /** @var Book $book */
187 $book = Book::query()->first();
188 $bookPage = $book->pages->first();
189 $bookChapter = $book->chapters->first();
191 $bookUrl = $book->getUrl();
192 $this->actingAs($this->user)
193 ->get($bookUrl . '/edit')
194 ->assertSee('Edit Book');
196 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
198 $this->get($bookUrl . '/edit')->assertRedirect('/');
199 $this->get('/')->assertSee('You do not have permission');
200 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
201 $this->get('/')->assertSee('You do not have permission');
202 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
203 $this->get('/')->assertSee('You do not have permission');
205 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
207 $this->get($bookUrl . '/edit')->assertOk();
208 $this->get($bookPage->getUrl() . '/edit')->assertOk();
209 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
212 public function test_book_delete_restriction()
214 /** @var Book $book */
215 $book = Book::query()->first();
216 $bookPage = $book->pages->first();
217 $bookChapter = $book->chapters->first();
219 $bookUrl = $book->getUrl();
220 $this->actingAs($this->user)->get($bookUrl . '/delete')
221 ->assertSee('Delete Book');
223 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
225 $this->get($bookUrl . '/delete')->assertRedirect('/');
226 $this->get('/')->assertSee('You do not have permission');
227 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
228 $this->get('/')->assertSee('You do not have permission');
229 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
230 $this->get('/')->assertSee('You do not have permission');
232 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
234 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
235 $this->get($bookPage->getUrl('/delete'))->assertOk()->assertSee('Delete Page');
236 $this->get($bookChapter->getUrl('/delete'))->assertSee('Delete Chapter');
239 public function test_chapter_view_restriction()
241 /** @var Chapter $chapter */
242 $chapter = Chapter::query()->first();
243 $chapterPage = $chapter->pages->first();
245 $chapterUrl = $chapter->getUrl();
246 $this->actingAs($this->user)->get($chapterUrl)->assertOk();
248 $this->setRestrictionsForTestRoles($chapter, []);
250 $this->followingRedirects()->get($chapterUrl)->assertSee('Chapter not found');
251 $this->followingRedirects()->get($chapterPage->getUrl())->assertSee('Page not found');
253 $this->setRestrictionsForTestRoles($chapter, ['view']);
255 $this->get($chapterUrl)->assertSee($chapter->name);
256 $this->get($chapterPage->getUrl())->assertSee($chapterPage->name);
259 public function test_chapter_create_restriction()
261 /** @var Chapter $chapter */
262 $chapter = Chapter::query()->first();
264 $chapterUrl = $chapter->getUrl();
265 $this->actingAs($this->user)
267 ->assertElementContains('.actions', 'New Page');
269 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete', 'update']);
271 $this->get($chapterUrl . '/create-page')->assertRedirect('/');
272 $this->get('/')->assertSee('You do not have permission');
273 $this->get($chapterUrl)->assertElementNotContains('.actions', 'New Page');
275 $this->setRestrictionsForTestRoles($chapter, ['view', 'create']);
277 $this->get($chapter->getUrl('/create-page'));
278 /** @var Page $page */
279 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
280 $resp = $this->post($page->getUrl(), [
281 'name' => 'test page',
282 'html' => 'test content',
284 $resp->assertRedirect($chapter->book->getUrl('/page/test-page'));
286 $this->get($chapterUrl)->assertElementContains('.actions', 'New Page');
289 public function test_chapter_update_restriction()
291 /** @var Chapter $chapter */
292 $chapter = Chapter::query()->first();
293 $chapterPage = $chapter->pages->first();
295 $chapterUrl = $chapter->getUrl();
296 $this->actingAs($this->user)->get($chapterUrl . '/edit')
297 ->assertSee('Edit Chapter');
299 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
301 $this->get($chapterUrl . '/edit')->assertRedirect('/');
302 $this->get('/')->assertSee('You do not have permission');
303 $this->get($chapterPage->getUrl() . '/edit')->assertRedirect('/');
304 $this->get('/')->assertSee('You do not have permission');
306 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
308 $this->get($chapterUrl . '/edit')->assertOk()->assertSee('Edit Chapter');
309 $this->get($chapterPage->getUrl() . '/edit')->assertOk();
312 public function test_chapter_delete_restriction()
314 /** @var Chapter $chapter */
315 $chapter = Chapter::query()->first();
316 $chapterPage = $chapter->pages->first();
318 $chapterUrl = $chapter->getUrl();
319 $this->actingAs($this->user)
320 ->get($chapterUrl . '/delete')
321 ->assertSee('Delete Chapter');
323 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
325 $this->get($chapterUrl . '/delete')->assertRedirect('/');
326 $this->get('/')->assertSee('You do not have permission');
327 $this->get($chapterPage->getUrl() . '/delete')->assertRedirect('/');
328 $this->get('/')->assertSee('You do not have permission');
330 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
332 $this->get($chapterUrl . '/delete')->assertOk()->assertSee('Delete Chapter');
333 $this->get($chapterPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
336 public function test_page_view_restriction()
338 /** @var Page $page */
339 $page = Page::query()->first();
341 $pageUrl = $page->getUrl();
342 $this->actingAs($this->user)->get($pageUrl)->assertOk();
344 $this->setRestrictionsForTestRoles($page, ['update', 'delete']);
346 $this->get($pageUrl)->assertSee('Page not found');
348 $this->setRestrictionsForTestRoles($page, ['view']);
350 $this->get($pageUrl)->assertSee($page->name);
353 public function test_page_update_restriction()
355 /** @var Page $page */
356 $page = Page::query()->first();
358 $pageUrl = $page->getUrl();
359 $this->actingAs($this->user)
360 ->get($pageUrl . '/edit')
361 ->assertElementExists('input[name="name"][value="' . $page->name . '"]');
363 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
365 $this->get($pageUrl . '/edit')->assertRedirect('/');
366 $this->get('/')->assertSee('You do not have permission');
368 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
370 $this->get($pageUrl . '/edit')
372 ->assertElementExists('input[name="name"][value="' . $page->name . '"]');
375 public function test_page_delete_restriction()
377 /** @var Page $page */
378 $page = Page::query()->first();
380 $pageUrl = $page->getUrl();
381 $this->actingAs($this->user)
382 ->get($pageUrl . '/delete')
383 ->assertSee('Delete Page');
385 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
387 $this->get($pageUrl . '/delete')->assertRedirect('/');
388 $this->get('/')->assertSee('You do not have permission');
390 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
392 $this->get($pageUrl . '/delete')->assertOk()->assertSee('Delete Page');
395 protected function entityRestrictionFormTest(string $model, string $title, string $permission, string $roleId)
397 /** @var Entity $modelInstance */
398 $modelInstance = $model::query()->first();
399 $this->asAdmin()->get($modelInstance->getUrl('/permissions'))
402 $this->put($modelInstance->getUrl('/permissions'), [
403 'restricted' => 'true',
406 $permission => 'true',
411 $this->assertDatabaseHas($modelInstance->getTable(), ['id' => $modelInstance->id, 'restricted' => true]);
412 $this->assertDatabaseHas('entity_permissions', [
413 'restrictable_id' => $modelInstance->id,
414 'restrictable_type' => $modelInstance->getMorphClass(),
415 'role_id' => $roleId,
416 'action' => $permission,
420 public function test_bookshelf_restriction_form()
422 $this->entityRestrictionFormTest(Bookshelf::class, 'Bookshelf Permissions', 'view', '2');
425 public function test_book_restriction_form()
427 $this->entityRestrictionFormTest(Book::class, 'Book Permissions', 'view', '2');
430 public function test_chapter_restriction_form()
432 $this->entityRestrictionFormTest(Chapter::class, 'Chapter Permissions', 'update', '2');
435 public function test_page_restriction_form()
437 $this->entityRestrictionFormTest(Page::class, 'Page Permissions', 'delete', '2');
440 public function test_restricted_pages_not_visible_in_book_navigation_on_pages()
442 /** @var Chapter $chapter */
443 $chapter = Chapter::query()->first();
444 $page = $chapter->pages->first();
445 $page2 = $chapter->pages[2];
447 $this->setRestrictionsForTestRoles($page, []);
449 $this->actingAs($this->user)
450 ->get($page2->getUrl())
451 ->assertElementNotContains('.sidebar-page-list', $page->name);
454 public function test_restricted_pages_not_visible_in_book_navigation_on_chapters()
456 /** @var Chapter $chapter */
457 $chapter = Chapter::query()->first();
458 $page = $chapter->pages->first();
460 $this->setRestrictionsForTestRoles($page, []);
462 $this->actingAs($this->user)
463 ->get($chapter->getUrl())
464 ->assertElementNotContains('.sidebar-page-list', $page->name);
467 public function test_restricted_pages_not_visible_on_chapter_pages()
469 /** @var Chapter $chapter */
470 $chapter = Chapter::query()->first();
471 $page = $chapter->pages->first();
473 $this->setRestrictionsForTestRoles($page, []);
475 $this->actingAs($this->user)
476 ->get($chapter->getUrl())
477 ->assertDontSee($page->name);
480 public function test_restricted_chapter_pages_not_visible_on_book_page()
482 /** @var Chapter $chapter */
483 $chapter = Chapter::query()->first();
484 $this->actingAs($this->user)
485 ->get($chapter->book->getUrl())
486 ->assertSee($chapter->pages->first()->name);
488 foreach ($chapter->pages as $page) {
489 $this->setRestrictionsForTestRoles($page, []);
492 $this->actingAs($this->user)
493 ->get($chapter->book->getUrl())
494 ->assertDontSee($chapter->pages->first()->name);
497 public function test_bookshelf_update_restriction_override()
499 /** @var Bookshelf $shelf */
500 $shelf = Bookshelf::query()->first();
502 $this->actingAs($this->viewer)
503 ->get($shelf->getUrl('/edit'))
504 ->assertDontSee('Edit Book');
506 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
508 $this->get($shelf->getUrl('/edit'))->assertRedirect('/');
509 $this->get('/')->assertSee('You do not have permission');
511 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
513 $this->get($shelf->getUrl('/edit'))->assertOk();
516 public function test_bookshelf_delete_restriction_override()
518 /** @var Bookshelf $shelf */
519 $shelf = Bookshelf::query()->first();
521 $this->actingAs($this->viewer)
522 ->get($shelf->getUrl('/delete'))
523 ->assertDontSee('Delete Book');
525 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
527 $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
528 $this->get('/')->assertSee('You do not have permission');
530 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
532 $this->get($shelf->getUrl('/delete'))->assertOk()->assertSee('Delete Book');
535 public function test_book_create_restriction_override()
537 /** @var Book $book */
538 $book = Book::query()->first();
540 $bookUrl = $book->getUrl();
541 $this->actingAs($this->viewer)
543 ->assertElementNotContains('.actions', 'New Page')
544 ->assertElementNotContains('.actions', 'New Chapter');
546 $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
548 $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
549 $this->get('/')->assertSee('You do not have permission');
550 $this->get($bookUrl . '/create-page')->assertRedirect('/');
551 $this->get('/')->assertSee('You do not have permission');
552 $this->get($bookUrl)->assertElementNotContains('.actions', 'New Page')
553 ->assertElementNotContains('.actions', 'New Chapter');
555 $this->setRestrictionsForTestRoles($book, ['view', 'create']);
557 $resp = $this->post($book->getUrl('/create-chapter'), [
558 'name' => 'test chapter',
559 'description' => 'test desc',
561 $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
563 $this->get($book->getUrl('/create-page'));
564 /** @var Page $page */
565 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
566 $resp = $this->post($page->getUrl(), [
567 'name' => 'test page',
568 'html' => 'test desc',
570 $resp->assertRedirect($book->getUrl('/page/test-page'));
573 ->assertElementContains('.actions', 'New Page')
574 ->assertElementContains('.actions', 'New Chapter');
577 public function test_book_update_restriction_override()
579 /** @var Book $book */
580 $book = Book::query()->first();
581 $bookPage = $book->pages->first();
582 $bookChapter = $book->chapters->first();
584 $bookUrl = $book->getUrl();
585 $this->actingAs($this->viewer)->get($bookUrl . '/edit')
586 ->assertDontSee('Edit Book');
588 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
590 $this->get($bookUrl . '/edit')->assertRedirect('/');
591 $this->get('/')->assertSee('You do not have permission');
592 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
593 $this->get('/')->assertSee('You do not have permission');
594 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
595 $this->get('/')->assertSee('You do not have permission');
597 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
599 $this->get($bookUrl . '/edit')->assertOk();
600 $this->get($bookPage->getUrl() . '/edit')->assertOk();
601 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
604 public function test_book_delete_restriction_override()
606 /** @var Book $book */
607 $book = Book::query()->first();
608 $bookPage = $book->pages->first();
609 $bookChapter = $book->chapters->first();
611 $bookUrl = $book->getUrl();
612 $this->actingAs($this->viewer)
613 ->get($bookUrl . '/delete')
614 ->assertDontSee('Delete Book');
616 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
618 $this->get($bookUrl . '/delete')->assertRedirect('/');
619 $this->get('/')->assertSee('You do not have permission');
620 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
621 $this->get('/')->assertSee('You do not have permission');
622 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
623 $this->get('/')->assertSee('You do not have permission');
625 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
627 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
628 $this->get($bookPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
629 $this->get($bookChapter->getUrl() . '/delete')->assertSee('Delete Chapter');
632 public function test_page_visible_if_has_permissions_when_book_not_visible()
634 /** @var Book $book */
635 $book = Book::query()->first();
636 $bookChapter = $book->chapters->first();
637 $bookPage = $bookChapter->pages->first();
639 foreach ([$book, $bookChapter, $bookPage] as $entity) {
640 $entity->name = Str::random(24);
644 $this->setRestrictionsForTestRoles($book, []);
645 $this->setRestrictionsForTestRoles($bookPage, ['view']);
647 $this->actingAs($this->viewer);
648 $resp = $this->get($bookPage->getUrl());
650 $resp->assertSee($bookPage->name);
651 $resp->assertDontSee(substr($book->name, 0, 15));
652 $resp->assertDontSee(substr($bookChapter->name, 0, 15));
655 public function test_book_sort_view_permission()
657 /** @var Book $firstBook */
658 $firstBook = Book::query()->first();
659 /** @var Book $secondBook */
660 $secondBook = Book::query()->find(2);
662 $this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
663 $this->setRestrictionsForTestRoles($secondBook, ['view']);
665 // Test sort page visibility
666 $this->actingAs($this->user)->get($secondBook->getUrl('/sort'))->assertRedirect('/');
667 $this->get('/')->assertSee('You do not have permission');
669 // Check sort page on first book
670 $this->actingAs($this->user)->get($firstBook->getUrl('/sort'));
673 public function test_book_sort_permission()
675 /** @var Book $firstBook */
676 $firstBook = Book::query()->first();
677 /** @var Book $secondBook */
678 $secondBook = Book::query()->find(2);
680 $this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
681 $this->setRestrictionsForTestRoles($secondBook, ['view']);
683 $firstBookChapter = $this->newChapter(['name' => 'first book chapter'], $firstBook);
684 $secondBookChapter = $this->newChapter(['name' => 'second book chapter'], $secondBook);
686 // Create request data
689 'id' => $firstBookChapter->id,
691 'parentChapter' => false,
693 'book' => $secondBook->id,
697 // Move chapter from first book to a second book
698 $this->actingAs($this->user)->put($firstBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)])
699 ->assertRedirect('/');
700 $this->get('/')->assertSee('You do not have permission');
704 'id' => $secondBookChapter->id,
706 'parentChapter' => false,
708 'book' => $firstBook->id,
712 // Move chapter from second book to first book
713 $this->actingAs($this->user)->put($firstBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)])
714 ->assertRedirect('/');
715 $this->get('/')->assertSee('You do not have permission');
718 public function test_can_create_page_if_chapter_has_permissions_when_book_not_visible()
720 /** @var Book $book */
721 $book = Book::query()->first();
722 $this->setRestrictionsForTestRoles($book, []);
723 $bookChapter = $book->chapters->first();
724 $this->setRestrictionsForTestRoles($bookChapter, ['view']);
726 $this->actingAs($this->user)->get($bookChapter->getUrl())
727 ->assertDontSee('New Page');
729 $this->setRestrictionsForTestRoles($bookChapter, ['view', 'create']);
731 $this->get($bookChapter->getUrl('/create-page'));
732 /** @var Page $page */
733 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
734 $resp = $this->post($page->getUrl(), [
735 'name' => 'test page',
736 'html' => 'test content',
738 $resp->assertRedirect($book->getUrl('/page/test-page'));