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'));
171 $this->get($book->getUrl('/create-page'));
172 /** @var Page $page */
173 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
174 $resp = $this->post($page->getUrl(), [
175 'name' => 'test page',
176 'html' => 'test content',
178 $resp->assertRedirect($book->getUrl('/page/test-page'));
181 ->assertElementContains('.actions', 'New Page')
182 ->assertElementContains('.actions', 'New Chapter');
185 public function test_book_update_restriction()
187 /** @var Book $book */
188 $book = Book::query()->first();
189 $bookPage = $book->pages->first();
190 $bookChapter = $book->chapters->first();
192 $bookUrl = $book->getUrl();
193 $this->actingAs($this->user)
194 ->get($bookUrl . '/edit')
195 ->assertSee('Edit Book');
197 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
199 $this->get($bookUrl . '/edit')->assertRedirect('/');
200 $this->get('/')->assertSee('You do not have permission');
201 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
202 $this->get('/')->assertSee('You do not have permission');
203 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
204 $this->get('/')->assertSee('You do not have permission');
206 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
208 $this->get($bookUrl . '/edit')->assertOk();
209 $this->get($bookPage->getUrl() . '/edit')->assertOk();
210 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
213 public function test_book_delete_restriction()
215 /** @var Book $book */
216 $book = Book::query()->first();
217 $bookPage = $book->pages->first();
218 $bookChapter = $book->chapters->first();
220 $bookUrl = $book->getUrl();
221 $this->actingAs($this->user)->get($bookUrl . '/delete')
222 ->assertSee('Delete Book');
224 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
226 $this->get($bookUrl . '/delete')->assertRedirect('/');
227 $this->get('/')->assertSee('You do not have permission');
228 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
229 $this->get('/')->assertSee('You do not have permission');
230 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
231 $this->get('/')->assertSee('You do not have permission');
233 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
235 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
236 $this->get($bookPage->getUrl('/delete'))->assertOk()->assertSee('Delete Page');
237 $this->get($bookChapter->getUrl('/delete'))->assertSee('Delete Chapter');
240 public function test_chapter_view_restriction()
242 /** @var Chapter $chapter */
243 $chapter = Chapter::query()->first();
244 $chapterPage = $chapter->pages->first();
246 $chapterUrl = $chapter->getUrl();
247 $this->actingAs($this->user)->get($chapterUrl)->assertOk();
249 $this->setRestrictionsForTestRoles($chapter, []);
251 $this->followingRedirects()->get($chapterUrl)->assertSee('Chapter not found');
252 $this->followingRedirects()->get($chapterPage->getUrl())->assertSee('Page not found');
254 $this->setRestrictionsForTestRoles($chapter, ['view']);
256 $this->get($chapterUrl)->assertSee($chapter->name);
257 $this->get($chapterPage->getUrl())->assertSee($chapterPage->name);
260 public function test_chapter_create_restriction()
262 /** @var Chapter $chapter */
263 $chapter = Chapter::query()->first();
265 $chapterUrl = $chapter->getUrl();
266 $this->actingAs($this->user)
268 ->assertElementContains('.actions', 'New Page');
270 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete', 'update']);
272 $this->get($chapterUrl . '/create-page')->assertRedirect('/');
273 $this->get('/')->assertSee('You do not have permission');
274 $this->get($chapterUrl)->assertElementNotContains('.actions', 'New Page');
276 $this->setRestrictionsForTestRoles($chapter, ['view', 'create']);
278 $this->get($chapter->getUrl('/create-page'));
279 /** @var Page $page */
280 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
281 $resp = $this->post($page->getUrl(), [
282 'name' => 'test page',
283 'html' => 'test content',
285 $resp->assertRedirect($chapter->book->getUrl('/page/test-page'));
287 $this->get($chapterUrl)->assertElementContains('.actions', 'New Page');
290 public function test_chapter_update_restriction()
292 /** @var Chapter $chapter */
293 $chapter = Chapter::query()->first();
294 $chapterPage = $chapter->pages->first();
296 $chapterUrl = $chapter->getUrl();
297 $this->actingAs($this->user)->get($chapterUrl . '/edit')
298 ->assertSee('Edit Chapter');
300 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
302 $this->get($chapterUrl . '/edit')->assertRedirect('/');
303 $this->get('/')->assertSee('You do not have permission');
304 $this->get($chapterPage->getUrl() . '/edit')->assertRedirect('/');
305 $this->get('/')->assertSee('You do not have permission');
307 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
309 $this->get($chapterUrl . '/edit')->assertOk()->assertSee('Edit Chapter');
310 $this->get($chapterPage->getUrl() . '/edit')->assertOk();
313 public function test_chapter_delete_restriction()
315 /** @var Chapter $chapter */
316 $chapter = Chapter::query()->first();
317 $chapterPage = $chapter->pages->first();
319 $chapterUrl = $chapter->getUrl();
320 $this->actingAs($this->user)
321 ->get($chapterUrl . '/delete')
322 ->assertSee('Delete Chapter');
324 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
326 $this->get($chapterUrl . '/delete')->assertRedirect('/');
327 $this->get('/')->assertSee('You do not have permission');
328 $this->get($chapterPage->getUrl() . '/delete')->assertRedirect('/');
329 $this->get('/')->assertSee('You do not have permission');
331 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
333 $this->get($chapterUrl . '/delete')->assertOk()->assertSee('Delete Chapter');
334 $this->get($chapterPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
337 public function test_page_view_restriction()
339 /** @var Page $page */
340 $page = Page::query()->first();
342 $pageUrl = $page->getUrl();
343 $this->actingAs($this->user)->get($pageUrl)->assertOk();
345 $this->setRestrictionsForTestRoles($page, ['update', 'delete']);
347 $this->get($pageUrl)->assertSee('Page not found');
349 $this->setRestrictionsForTestRoles($page, ['view']);
351 $this->get($pageUrl)->assertSee($page->name);
354 public function test_page_update_restriction()
356 /** @var Page $page */
357 $page = Page::query()->first();
359 $pageUrl = $page->getUrl();
360 $this->actingAs($this->user)
361 ->get($pageUrl . '/edit')
362 ->assertElementExists('input[name="name"][value="' . $page->name . '"]');
364 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
366 $this->get($pageUrl . '/edit')->assertRedirect('/');
367 $this->get('/')->assertSee('You do not have permission');
369 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
371 $this->get($pageUrl . '/edit')
373 ->assertElementExists('input[name="name"][value="' . $page->name . '"]');
376 public function test_page_delete_restriction()
378 /** @var Page $page */
379 $page = Page::query()->first();
381 $pageUrl = $page->getUrl();
382 $this->actingAs($this->user)
383 ->get($pageUrl . '/delete')
384 ->assertSee('Delete Page');
386 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
388 $this->get($pageUrl . '/delete')->assertRedirect('/');
389 $this->get('/')->assertSee('You do not have permission');
391 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
393 $this->get($pageUrl . '/delete')->assertOk()->assertSee('Delete Page');
396 protected function entityRestrictionFormTest(string $model, string $title, string $permission, string $roleId)
398 /** @var Entity $modelInstance */
399 $modelInstance = $model::query()->first();
400 $this->asAdmin()->get($modelInstance->getUrl('/permissions'))
403 $this->put($modelInstance->getUrl('/permissions'), [
404 'restricted' => 'true',
407 $permission => 'true'
412 $this->assertDatabaseHas($modelInstance->getTable(), ['id' => $modelInstance->id, 'restricted' => true]);
413 $this->assertDatabaseHas('entity_permissions', [
414 'restrictable_id' => $modelInstance->id,
415 'restrictable_type' => $modelInstance->getMorphClass(),
416 'role_id' => $roleId,
417 'action' => $permission,
421 public function test_bookshelf_restriction_form()
423 $this->entityRestrictionFormTest(Bookshelf::class, 'Bookshelf Permissions', 'view', '2');
426 public function test_book_restriction_form()
428 $this->entityRestrictionFormTest(Book::class, 'Book Permissions', 'view', '2');
431 public function test_chapter_restriction_form()
433 $this->entityRestrictionFormTest(Chapter::class, 'Chapter Permissions', 'update', '2');
436 public function test_page_restriction_form()
438 $this->entityRestrictionFormTest(Page::class, 'Page Permissions', 'delete', '2');
441 public function test_restricted_pages_not_visible_in_book_navigation_on_pages()
443 /** @var Chapter $chapter */
444 $chapter = Chapter::query()->first();
445 $page = $chapter->pages->first();
446 $page2 = $chapter->pages[2];
448 $this->setRestrictionsForTestRoles($page, []);
450 $this->actingAs($this->user)
451 ->get($page2->getUrl())
452 ->assertElementNotContains('.sidebar-page-list', $page->name);
455 public function test_restricted_pages_not_visible_in_book_navigation_on_chapters()
457 /** @var Chapter $chapter */
458 $chapter = Chapter::query()->first();
459 $page = $chapter->pages->first();
461 $this->setRestrictionsForTestRoles($page, []);
463 $this->actingAs($this->user)
464 ->get($chapter->getUrl())
465 ->assertElementNotContains('.sidebar-page-list', $page->name);
468 public function test_restricted_pages_not_visible_on_chapter_pages()
470 /** @var Chapter $chapter */
471 $chapter = Chapter::query()->first();
472 $page = $chapter->pages->first();
474 $this->setRestrictionsForTestRoles($page, []);
476 $this->actingAs($this->user)
477 ->get($chapter->getUrl())
478 ->assertDontSee($page->name);
481 public function test_restricted_chapter_pages_not_visible_on_book_page()
483 /** @var Chapter $chapter */
484 $chapter = Chapter::query()->first();
485 $this->actingAs($this->user)
486 ->get($chapter->book->getUrl())
487 ->assertSee($chapter->pages->first()->name);
489 foreach ($chapter->pages as $page) {
490 $this->setRestrictionsForTestRoles($page, []);
493 $this->actingAs($this->user)
494 ->get($chapter->book->getUrl())
495 ->assertDontSee($chapter->pages->first()->name);
498 public function test_bookshelf_update_restriction_override()
500 /** @var Bookshelf $shelf */
501 $shelf = Bookshelf::query()->first();
503 $this->actingAs($this->viewer)
504 ->get($shelf->getUrl('/edit'))
505 ->assertDontSee('Edit Book');
507 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
509 $this->get($shelf->getUrl('/edit'))->assertRedirect('/');
510 $this->get('/')->assertSee('You do not have permission');
512 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
514 $this->get($shelf->getUrl('/edit'))->assertOk();
517 public function test_bookshelf_delete_restriction_override()
519 /** @var Bookshelf $shelf */
520 $shelf = Bookshelf::query()->first();
522 $this->actingAs($this->viewer)
523 ->get($shelf->getUrl('/delete'))
524 ->assertDontSee('Delete Book');
526 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
528 $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
529 $this->get('/')->assertSee('You do not have permission');
531 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
533 $this->get($shelf->getUrl('/delete'))->assertOk()->assertSee('Delete Book');
536 public function test_book_create_restriction_override()
538 /** @var Book $book */
539 $book = Book::query()->first();
541 $bookUrl = $book->getUrl();
542 $this->actingAs($this->viewer)
544 ->assertElementNotContains('.actions', 'New Page')
545 ->assertElementNotContains('.actions', 'New Chapter');
547 $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
549 $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
550 $this->get('/')->assertSee('You do not have permission');
551 $this->get($bookUrl . '/create-page')->assertRedirect('/');
552 $this->get('/')->assertSee('You do not have permission');
553 $this->get($bookUrl)->assertElementNotContains('.actions', 'New Page')
554 ->assertElementNotContains('.actions', 'New Chapter');
556 $this->setRestrictionsForTestRoles($book, ['view', 'create']);
558 $resp = $this->post($book->getUrl('/create-chapter'), [
559 'name' => 'test chapter',
560 'description' => 'test desc',
562 $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
565 $this->get($book->getUrl('/create-page'));
566 /** @var Page $page */
567 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
568 $resp = $this->post($page->getUrl(), [
569 'name' => 'test page',
570 'html' => 'test desc',
572 $resp->assertRedirect($book->getUrl('/page/test-page'));
575 ->assertElementContains('.actions', 'New Page')
576 ->assertElementContains('.actions', 'New Chapter');
579 public function test_book_update_restriction_override()
581 /** @var Book $book */
582 $book = Book::query()->first();
583 $bookPage = $book->pages->first();
584 $bookChapter = $book->chapters->first();
586 $bookUrl = $book->getUrl();
587 $this->actingAs($this->viewer)->get($bookUrl . '/edit')
588 ->assertDontSee('Edit Book');
590 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
592 $this->get($bookUrl . '/edit')->assertRedirect('/');
593 $this->get('/')->assertSee('You do not have permission');
594 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
595 $this->get('/')->assertSee('You do not have permission');
596 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
597 $this->get('/')->assertSee('You do not have permission');
599 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
601 $this->get($bookUrl . '/edit')->assertOk();
602 $this->get($bookPage->getUrl() . '/edit')->assertOk();
603 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
606 public function test_book_delete_restriction_override()
608 /** @var Book $book */
609 $book = Book::query()->first();
610 $bookPage = $book->pages->first();
611 $bookChapter = $book->chapters->first();
613 $bookUrl = $book->getUrl();
614 $this->actingAs($this->viewer)
615 ->get($bookUrl . '/delete')
616 ->assertDontSee('Delete Book');
618 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
620 $this->get($bookUrl . '/delete')->assertRedirect('/');
621 $this->get('/')->assertSee('You do not have permission');
622 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
623 $this->get('/')->assertSee('You do not have permission');
624 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
625 $this->get('/')->assertSee('You do not have permission');
627 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
629 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
630 $this->get($bookPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
631 $this->get($bookChapter->getUrl() . '/delete')->assertSee('Delete Chapter');
634 public function test_page_visible_if_has_permissions_when_book_not_visible()
636 /** @var Book $book */
637 $book = Book::query()->first();
638 $bookChapter = $book->chapters->first();
639 $bookPage = $bookChapter->pages->first();
641 foreach ([$book, $bookChapter, $bookPage] as $entity) {
642 $entity->name = Str::random(24);
646 $this->setRestrictionsForTestRoles($book, []);
647 $this->setRestrictionsForTestRoles($bookPage, ['view']);
649 $this->actingAs($this->viewer);
650 $resp = $this->get($bookPage->getUrl());
652 $resp->assertSee($bookPage->name);
653 $resp->assertDontSee(substr($book->name, 0, 15));
654 $resp->assertDontSee(substr($bookChapter->name, 0, 15));
657 public function test_book_sort_view_permission()
659 /** @var Book $firstBook */
660 $firstBook = Book::query()->first();
661 /** @var Book $secondBook */
662 $secondBook = Book::query()->find(2);
664 $this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
665 $this->setRestrictionsForTestRoles($secondBook, ['view']);
667 // Test sort page visibility
668 $this->actingAs($this->user)->get($secondBook->getUrl('/sort'))->assertRedirect('/');
669 $this->get('/')->assertSee('You do not have permission');
671 // Check sort page on first book
672 $this->actingAs($this->user)->get($firstBook->getUrl('/sort'));
675 public function test_book_sort_permission()
677 /** @var Book $firstBook */
678 $firstBook = Book::query()->first();
679 /** @var Book $secondBook */
680 $secondBook = Book::query()->find(2);
682 $this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
683 $this->setRestrictionsForTestRoles($secondBook, ['view']);
685 $firstBookChapter = $this->newChapter(['name' => 'first book chapter'], $firstBook);
686 $secondBookChapter = $this->newChapter(['name' => 'second book chapter'], $secondBook);
688 // Create request data
691 'id' => $firstBookChapter->id,
693 'parentChapter' => false,
695 'book' => $secondBook->id,
699 // Move chapter from first book to a second book
700 $this->actingAs($this->user)->put($firstBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)])
701 ->assertRedirect('/');
702 $this->get('/')->assertSee('You do not have permission');
706 'id' => $secondBookChapter->id,
708 'parentChapter' => false,
710 'book' => $firstBook->id,
714 // Move chapter from second book to first book
715 $this->actingAs($this->user)->put($firstBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)])
716 ->assertRedirect('/');
717 $this->get('/')->assertSee('You do not have permission');
720 public function test_can_create_page_if_chapter_has_permissions_when_book_not_visible()
722 /** @var Book $book */
723 $book = Book::query()->first();
724 $this->setRestrictionsForTestRoles($book, []);
725 $bookChapter = $book->chapters->first();
726 $this->setRestrictionsForTestRoles($bookChapter, ['view']);
728 $this->actingAs($this->user)->get($bookChapter->getUrl())
729 ->assertDontSee('New Page');
731 $this->setRestrictionsForTestRoles($bookChapter, ['view', 'create']);
734 $this->get($bookChapter->getUrl('/create-page'));
735 /** @var Page $page */
736 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
737 $resp = $this->post($page->getUrl(), [
738 'name' => 'test page',
739 'html' => 'test content',
741 $resp->assertRedirect($book->getUrl('/page/test-page'));