3 namespace Tests\Permissions;
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Bookshelf;
7 use BookStack\Entities\Models\Chapter;
8 use BookStack\Entities\Models\Entity;
9 use BookStack\Entities\Models\Page;
10 use BookStack\Users\Models\Role;
11 use BookStack\Users\Models\User;
13 use Illuminate\Support\Str;
16 class EntityPermissionsTest extends TestCase
19 protected User $viewer;
21 protected function setUp(): void
24 $this->user = $this->users->editor();
25 $this->viewer = $this->users->viewer();
28 protected function setRestrictionsForTestRoles(Entity $entity, array $actions = [])
31 $this->user->roles->first(),
32 $this->viewer->roles->first(),
34 $this->permissions->setEntityPermissions($entity, $actions, $roles);
37 public function test_bookshelf_view_restriction()
39 $shelf = $this->entities->shelf();
41 $this->actingAs($this->user)
42 ->get($shelf->getUrl())
45 $this->setRestrictionsForTestRoles($shelf, []);
47 $this->followingRedirects()->get($shelf->getUrl())
48 ->assertSee('Shelf not found');
50 $this->setRestrictionsForTestRoles($shelf, ['view']);
52 $this->get($shelf->getUrl())
53 ->assertSee($shelf->name);
56 public function test_bookshelf_update_restriction()
58 $shelf = $this->entities->shelf();
60 $this->actingAs($this->user)
61 ->get($shelf->getUrl('/edit'))
62 ->assertSee('Edit Shelf');
64 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
66 $resp = $this->get($shelf->getUrl('/edit'))
67 ->assertRedirect('/');
68 $this->followRedirects($resp)->assertSee('You do not have permission');
70 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
72 $this->get($shelf->getUrl('/edit'))
76 public function test_bookshelf_delete_restriction()
78 $shelf = $this->entities->shelf();
80 $this->actingAs($this->user)
81 ->get($shelf->getUrl('/delete'))
82 ->assertSee('Delete Shelf');
84 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
86 $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
87 $this->get('/')->assertSee('You do not have permission');
89 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
91 $this->get($shelf->getUrl('/delete'))
93 ->assertSee('Delete Shelf');
96 public function test_book_view_restriction()
98 $book = $this->entities->book();
99 $bookPage = $book->pages->first();
100 $bookChapter = $book->chapters->first();
102 $bookUrl = $book->getUrl();
103 $this->actingAs($this->user)
107 $this->setRestrictionsForTestRoles($book, []);
109 $this->followingRedirects()->get($bookUrl)
110 ->assertSee('Book not found');
111 $this->followingRedirects()->get($bookPage->getUrl())
112 ->assertSee('Page not found');
113 $this->followingRedirects()->get($bookChapter->getUrl())
114 ->assertSee('Chapter not found');
116 $this->setRestrictionsForTestRoles($book, ['view']);
119 ->assertSee($book->name);
120 $this->get($bookPage->getUrl())
121 ->assertSee($bookPage->name);
122 $this->get($bookChapter->getUrl())
123 ->assertSee($bookChapter->name);
126 public function test_book_create_restriction()
128 $book = $this->entities->book();
130 $bookUrl = $book->getUrl();
131 $resp = $this->actingAs($this->viewer)->get($bookUrl);
132 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
133 ->assertElementNotContains('.actions', 'New Chapter');
134 $resp = $this->actingAs($this->user)->get($bookUrl);
135 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
136 ->assertElementContains('.actions', 'New Chapter');
138 $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
140 $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
141 $this->get('/')->assertSee('You do not have permission');
143 $this->get($bookUrl . '/create-page')->assertRedirect('/');
144 $this->get('/')->assertSee('You do not have permission');
146 $resp = $this->get($bookUrl);
147 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
148 ->assertElementNotContains('.actions', 'New Chapter');
150 $this->setRestrictionsForTestRoles($book, ['view', 'create']);
152 $resp = $this->post($book->getUrl('/create-chapter'), [
153 'name' => 'test chapter',
154 'description' => 'desc',
156 $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
158 $this->get($book->getUrl('/create-page'));
159 /** @var Page $page */
160 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
161 $resp = $this->post($page->getUrl(), [
162 'name' => 'test page',
163 'html' => 'test content',
165 $resp->assertRedirect($book->getUrl('/page/test-page'));
167 $resp = $this->get($bookUrl);
168 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
169 ->assertElementContains('.actions', 'New Chapter');
172 public function test_book_update_restriction()
174 $book = $this->entities->book();
175 $bookPage = $book->pages->first();
176 $bookChapter = $book->chapters->first();
178 $bookUrl = $book->getUrl();
179 $this->actingAs($this->user)
180 ->get($bookUrl . '/edit')
181 ->assertSee('Edit Book');
183 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
185 $this->get($bookUrl . '/edit')->assertRedirect('/');
186 $this->get('/')->assertSee('You do not have permission');
187 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
188 $this->get('/')->assertSee('You do not have permission');
189 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
190 $this->get('/')->assertSee('You do not have permission');
192 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
194 $this->get($bookUrl . '/edit')->assertOk();
195 $this->get($bookPage->getUrl() . '/edit')->assertOk();
196 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
199 public function test_book_delete_restriction()
201 $book = $this->entities->book();
202 $bookPage = $book->pages->first();
203 $bookChapter = $book->chapters->first();
205 $bookUrl = $book->getUrl();
206 $this->actingAs($this->user)->get($bookUrl . '/delete')
207 ->assertSee('Delete Book');
209 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
211 $this->get($bookUrl . '/delete')->assertRedirect('/');
212 $this->get('/')->assertSee('You do not have permission');
213 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
214 $this->get('/')->assertSee('You do not have permission');
215 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
216 $this->get('/')->assertSee('You do not have permission');
218 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
220 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
221 $this->get($bookPage->getUrl('/delete'))->assertOk()->assertSee('Delete Page');
222 $this->get($bookChapter->getUrl('/delete'))->assertSee('Delete Chapter');
225 public function test_chapter_view_restriction()
227 $chapter = $this->entities->chapter();
228 $chapterPage = $chapter->pages->first();
230 $chapterUrl = $chapter->getUrl();
231 $this->actingAs($this->user)->get($chapterUrl)->assertOk();
233 $this->setRestrictionsForTestRoles($chapter, []);
235 $this->followingRedirects()->get($chapterUrl)->assertSee('Chapter not found');
236 $this->followingRedirects()->get($chapterPage->getUrl())->assertSee('Page not found');
238 $this->setRestrictionsForTestRoles($chapter, ['view']);
240 $this->get($chapterUrl)->assertSee($chapter->name);
241 $this->get($chapterPage->getUrl())->assertSee($chapterPage->name);
244 public function test_chapter_create_restriction()
246 $chapter = $this->entities->chapter();
248 $chapterUrl = $chapter->getUrl();
249 $resp = $this->actingAs($this->user)->get($chapterUrl);
250 $this->withHtml($resp)->assertElementContains('.actions', 'New Page');
252 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete', 'update']);
254 $this->get($chapterUrl . '/create-page')->assertRedirect('/');
255 $this->get('/')->assertSee('You do not have permission');
256 $this->withHtml($this->get($chapterUrl))->assertElementNotContains('.actions', 'New Page');
258 $this->setRestrictionsForTestRoles($chapter, ['view', 'create']);
260 $this->get($chapter->getUrl('/create-page'));
261 /** @var Page $page */
262 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
263 $resp = $this->post($page->getUrl(), [
264 'name' => 'test page',
265 'html' => 'test content',
267 $resp->assertRedirect($chapter->book->getUrl('/page/test-page'));
269 $this->withHtml($this->get($chapterUrl))->assertElementContains('.actions', 'New Page');
272 public function test_chapter_update_restriction()
274 $chapter = $this->entities->chapter();
275 $chapterPage = $chapter->pages->first();
277 $chapterUrl = $chapter->getUrl();
278 $this->actingAs($this->user)->get($chapterUrl . '/edit')
279 ->assertSee('Edit Chapter');
281 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
283 $this->get($chapterUrl . '/edit')->assertRedirect('/');
284 $this->get('/')->assertSee('You do not have permission');
285 $this->get($chapterPage->getUrl() . '/edit')->assertRedirect('/');
286 $this->get('/')->assertSee('You do not have permission');
288 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
290 $this->get($chapterUrl . '/edit')->assertOk()->assertSee('Edit Chapter');
291 $this->get($chapterPage->getUrl() . '/edit')->assertOk();
294 public function test_chapter_delete_restriction()
296 $chapter = $this->entities->chapter();
297 $chapterPage = $chapter->pages->first();
299 $chapterUrl = $chapter->getUrl();
300 $this->actingAs($this->user)
301 ->get($chapterUrl . '/delete')
302 ->assertSee('Delete Chapter');
304 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
306 $this->get($chapterUrl . '/delete')->assertRedirect('/');
307 $this->get('/')->assertSee('You do not have permission');
308 $this->get($chapterPage->getUrl() . '/delete')->assertRedirect('/');
309 $this->get('/')->assertSee('You do not have permission');
311 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
313 $this->get($chapterUrl . '/delete')->assertOk()->assertSee('Delete Chapter');
314 $this->get($chapterPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
317 public function test_page_view_restriction()
319 $page = $this->entities->page();
321 $pageUrl = $page->getUrl();
322 $this->actingAs($this->user)->get($pageUrl)->assertOk();
324 $this->setRestrictionsForTestRoles($page, ['update', 'delete']);
326 $this->get($pageUrl)->assertSee('Page not found');
328 $this->setRestrictionsForTestRoles($page, ['view']);
330 $this->get($pageUrl)->assertSee($page->name);
333 public function test_page_update_restriction()
335 $page = $this->entities->page();
337 $pageUrl = $page->getUrl();
338 $resp = $this->actingAs($this->user)
339 ->get($pageUrl . '/edit');
340 $this->withHtml($resp)->assertElementExists('input[name="name"][value="' . $page->name . '"]');
342 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
344 $this->get($pageUrl . '/edit')->assertRedirect('/');
345 $this->get('/')->assertSee('You do not have permission');
347 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
349 $resp = $this->get($pageUrl . '/edit')
351 $this->withHtml($resp)->assertElementExists('input[name="name"][value="' . $page->name . '"]');
354 public function test_page_delete_restriction()
356 $page = $this->entities->page();
358 $pageUrl = $page->getUrl();
359 $this->actingAs($this->user)
360 ->get($pageUrl . '/delete')
361 ->assertSee('Delete Page');
363 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
365 $this->get($pageUrl . '/delete')->assertRedirect('/');
366 $this->get('/')->assertSee('You do not have permission');
368 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
370 $this->get($pageUrl . '/delete')->assertOk()->assertSee('Delete Page');
373 protected function entityRestrictionFormTest(string $model, string $title, string $permission, string $roleId)
375 /** @var Entity $modelInstance */
376 $modelInstance = $model::query()->first();
377 $this->asAdmin()->get($modelInstance->getUrl('/permissions'))
380 $this->put($modelInstance->getUrl('/permissions'), [
383 $permission => 'true',
388 $this->assertDatabaseHas('entity_permissions', [
389 'entity_id' => $modelInstance->id,
390 'entity_type' => $modelInstance->getMorphClass(),
391 'role_id' => $roleId,
396 public function test_bookshelf_restriction_form()
398 $this->entityRestrictionFormTest(Bookshelf::class, 'Shelf Permissions', 'view', '2');
401 public function test_book_restriction_form()
403 $this->entityRestrictionFormTest(Book::class, 'Book Permissions', 'view', '2');
406 public function test_chapter_restriction_form()
408 $this->entityRestrictionFormTest(Chapter::class, 'Chapter Permissions', 'update', '2');
411 public function test_page_restriction_form()
413 $this->entityRestrictionFormTest(Page::class, 'Page Permissions', 'delete', '2');
416 public function test_shelf_create_permission_visible_with_notice()
418 $shelf = $this->entities->shelf();
420 $resp = $this->asAdmin()->get($shelf->getUrl('/permissions'));
421 $html = $this->withHtml($resp);
422 $html->assertElementExists('input[name$="[create]"]');
423 $resp->assertSee('Shelf create permissions are only used for copying permissions to child books using the action below.');
426 public function test_restricted_pages_not_visible_in_book_navigation_on_pages()
428 $chapter = $this->entities->chapter();
429 $page = $chapter->pages->first();
430 $page2 = $chapter->pages[2];
432 $this->setRestrictionsForTestRoles($page, []);
434 $resp = $this->actingAs($this->user)->get($page2->getUrl());
435 $this->withHtml($resp)->assertElementNotContains('.sidebar-page-list', $page->name);
438 public function test_restricted_pages_not_visible_in_book_navigation_on_chapters()
440 $chapter = $this->entities->chapter();
441 $page = $chapter->pages->first();
443 $this->setRestrictionsForTestRoles($page, []);
445 $resp = $this->actingAs($this->user)->get($chapter->getUrl());
446 $this->withHtml($resp)->assertElementNotContains('.sidebar-page-list', $page->name);
449 public function test_restricted_pages_not_visible_on_chapter_pages()
451 $chapter = $this->entities->chapter();
452 $page = $chapter->pages->first();
454 $this->setRestrictionsForTestRoles($page, []);
456 $this->actingAs($this->user)
457 ->get($chapter->getUrl())
458 ->assertDontSee($page->name);
461 public function test_restricted_chapter_pages_not_visible_on_book_page()
463 $chapter = $this->entities->chapter();
464 $this->actingAs($this->user)
465 ->get($chapter->book->getUrl())
466 ->assertSee($chapter->pages->first()->name);
468 foreach ($chapter->pages as $page) {
469 $this->setRestrictionsForTestRoles($page, []);
472 $this->actingAs($this->user)
473 ->get($chapter->book->getUrl())
474 ->assertDontSee($chapter->pages->first()->name);
477 public function test_bookshelf_update_restriction_override()
479 $shelf = $this->entities->shelf();
481 $this->actingAs($this->viewer)
482 ->get($shelf->getUrl('/edit'))
483 ->assertDontSee('Edit Book');
485 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
487 $this->get($shelf->getUrl('/edit'))->assertRedirect('/');
488 $this->get('/')->assertSee('You do not have permission');
490 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
492 $this->get($shelf->getUrl('/edit'))->assertOk();
495 public function test_bookshelf_delete_restriction_override()
497 $shelf = $this->entities->shelf();
499 $this->actingAs($this->viewer)
500 ->get($shelf->getUrl('/delete'))
501 ->assertDontSee('Delete Book');
503 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
505 $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
506 $this->get('/')->assertSee('You do not have permission');
508 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
510 $this->get($shelf->getUrl('/delete'))->assertOk()->assertSee('Delete Shelf');
513 public function test_book_create_restriction_override()
515 $book = $this->entities->book();
517 $bookUrl = $book->getUrl();
518 $resp = $this->actingAs($this->viewer)->get($bookUrl);
519 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
520 ->assertElementNotContains('.actions', 'New Chapter');
522 $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
524 $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
525 $this->get('/')->assertSee('You do not have permission');
526 $this->get($bookUrl . '/create-page')->assertRedirect('/');
527 $this->get('/')->assertSee('You do not have permission');
528 $resp = $this->get($bookUrl);
529 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
530 ->assertElementNotContains('.actions', 'New Chapter');
532 $this->setRestrictionsForTestRoles($book, ['view', 'create']);
534 $resp = $this->post($book->getUrl('/create-chapter'), [
535 'name' => 'test chapter',
536 'description' => 'test desc',
538 $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
540 $this->get($book->getUrl('/create-page'));
541 /** @var Page $page */
542 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
543 $resp = $this->post($page->getUrl(), [
544 'name' => 'test page',
545 'html' => 'test desc',
547 $resp->assertRedirect($book->getUrl('/page/test-page'));
549 $resp = $this->get($bookUrl);
550 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
551 ->assertElementContains('.actions', 'New Chapter');
554 public function test_book_update_restriction_override()
556 $book = $this->entities->book();
557 $bookPage = $book->pages->first();
558 $bookChapter = $book->chapters->first();
560 $bookUrl = $book->getUrl();
561 $this->actingAs($this->viewer)->get($bookUrl . '/edit')
562 ->assertDontSee('Edit Book');
564 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
566 $this->get($bookUrl . '/edit')->assertRedirect('/');
567 $this->get('/')->assertSee('You do not have permission');
568 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
569 $this->get('/')->assertSee('You do not have permission');
570 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
571 $this->get('/')->assertSee('You do not have permission');
573 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
575 $this->get($bookUrl . '/edit')->assertOk();
576 $this->get($bookPage->getUrl() . '/edit')->assertOk();
577 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
580 public function test_book_delete_restriction_override()
582 $book = $this->entities->book();
583 $bookPage = $book->pages->first();
584 $bookChapter = $book->chapters->first();
586 $bookUrl = $book->getUrl();
587 $this->actingAs($this->viewer)
588 ->get($bookUrl . '/delete')
589 ->assertDontSee('Delete Book');
591 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
593 $this->get($bookUrl . '/delete')->assertRedirect('/');
594 $this->get('/')->assertSee('You do not have permission');
595 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
596 $this->get('/')->assertSee('You do not have permission');
597 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
598 $this->get('/')->assertSee('You do not have permission');
600 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
602 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
603 $this->get($bookPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
604 $this->get($bookChapter->getUrl() . '/delete')->assertSee('Delete Chapter');
607 public function test_page_visible_if_has_permissions_when_book_not_visible()
609 $book = $this->entities->book();
610 $bookChapter = $book->chapters->first();
611 $bookPage = $bookChapter->pages->first();
613 foreach ([$book, $bookChapter, $bookPage] as $entity) {
614 $entity->name = Str::random(24);
618 $this->setRestrictionsForTestRoles($book, []);
619 $this->setRestrictionsForTestRoles($bookPage, ['view']);
621 $this->actingAs($this->viewer);
622 $resp = $this->get($bookPage->getUrl());
624 $resp->assertSee($bookPage->name);
625 $resp->assertDontSee(substr($book->name, 0, 15));
626 $resp->assertDontSee(substr($bookChapter->name, 0, 15));
629 public function test_book_sort_view_permission()
631 /** @var Book $firstBook */
632 $firstBook = Book::query()->first();
633 /** @var Book $secondBook */
634 $secondBook = Book::query()->find(2);
636 $this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
637 $this->setRestrictionsForTestRoles($secondBook, ['view']);
639 // Test sort page visibility
640 $this->actingAs($this->user)->get($secondBook->getUrl('/sort'))->assertRedirect('/');
641 $this->get('/')->assertSee('You do not have permission');
643 // Check sort page on first book
644 $this->actingAs($this->user)->get($firstBook->getUrl('/sort'));
647 public function test_can_create_page_if_chapter_has_permissions_when_book_not_visible()
649 $book = $this->entities->book();
650 $this->setRestrictionsForTestRoles($book, []);
651 $bookChapter = $book->chapters->first();
652 $this->setRestrictionsForTestRoles($bookChapter, ['view']);
654 $this->actingAs($this->user)->get($bookChapter->getUrl())
655 ->assertDontSee('New Page');
657 $this->setRestrictionsForTestRoles($bookChapter, ['view', 'create']);
659 $this->get($bookChapter->getUrl('/create-page'));
660 /** @var Page $page */
661 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
662 $resp = $this->post($page->getUrl(), [
663 'name' => 'test page',
664 'html' => 'test content',
666 $resp->assertRedirect($book->getUrl('/page/test-page'));
669 public function test_access_to_item_prevented_if_inheritance_active_but_permission_prevented_via_role()
671 $user = $this->users->viewer();
672 $viewerRole = $user->roles->first();
673 $chapter = $this->entities->chapter();
674 $book = $chapter->book;
676 $this->permissions->setEntityPermissions($book, ['update'], [$viewerRole], false);
677 $this->permissions->setEntityPermissions($chapter, [], [$viewerRole], true);
679 $this->assertFalse(userCan('chapter-update', $chapter));
682 public function test_access_to_item_allowed_if_inheritance_active_and_permission_prevented_via_role_but_allowed_via_parent()
684 $user = $this->users->viewer();
685 $viewerRole = $user->roles->first();
686 $editorRole = Role::getRole('Editor');
687 $user->attachRole($editorRole);
688 $chapter = $this->entities->chapter();
689 $book = $chapter->book;
691 $this->permissions->setEntityPermissions($book, ['update'], [$editorRole], false);
692 $this->permissions->setEntityPermissions($chapter, [], [$viewerRole], true);
694 $this->actingAs($user);
695 $this->assertTrue(userCan('chapter-update', $chapter));
698 public function test_book_permissions_can_be_generated_without_error_if_child_chapter_is_in_recycle_bin()
700 $book = $this->entities->bookHasChaptersAndPages();
701 /** @var Chapter $chapter */
702 $chapter = $book->chapters()->first();
704 $this->asAdmin()->delete($chapter->getUrl());
708 $this->permissions->setEntityPermissions($book, ['view'], []);
709 } catch (Exception $e) {
713 $this->assertNull($error);