3 namespace Tests\Helpers;
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\HasCoverImage;
10 use BookStack\Entities\Models\Page;
11 use BookStack\Entities\Repos\BookRepo;
12 use BookStack\Entities\Repos\BookshelfRepo;
13 use BookStack\Entities\Repos\ChapterRepo;
14 use BookStack\Entities\Repos\PageRepo;
15 use BookStack\Entities\Tools\TrashCan;
16 use BookStack\Users\Models\User;
17 use Illuminate\Database\Eloquent\Builder;
20 * Class to provider and action entity models for common test case
21 * operations. Tracks handled models and only returns fresh models.
22 * Does not dedupe against nested/child/parent models.
27 * @var array<string, int[]>
29 protected array $fetchCache = [
37 * Get an un-fetched page from the system.
39 public function page(callable $queryFilter = null): Page
41 /** @var Page $page */
42 $page = Page::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['page'])->first();
43 $this->addToCache($page);
47 public function pageWithinChapter(): Page
49 return $this->page(fn(Builder $query) => $query->whereHas('chapter')->with('chapter'));
52 public function pageNotWithinChapter(): Page
54 return $this->page(fn(Builder $query) => $query->where('chapter_id', '=', 0));
57 public function templatePage(): Page
59 $page = $this->page();
60 $page->template = true;
67 * Get an un-fetched chapter from the system.
69 public function chapter(callable $queryFilter = null): Chapter
71 /** @var Chapter $chapter */
72 $chapter = Chapter::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['chapter'])->first();
73 $this->addToCache($chapter);
77 public function chapterHasPages(): Chapter
79 return $this->chapter(fn(Builder $query) => $query->whereHas('pages'));
83 * Get an un-fetched book from the system.
85 public function book(callable $queryFilter = null): Book
87 /** @var Book $book */
88 $book = Book::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['book'])->first();
89 $this->addToCache($book);
94 * Get a book that has chapters and pages assigned.
96 public function bookHasChaptersAndPages(): Book
98 return $this->book(function (Builder $query) {
99 $query->has('chapters')->has('pages')->with(['chapters', 'pages']);
104 * Get an un-fetched shelf from the system.
106 public function shelf(callable $queryFilter = null): Bookshelf
108 /** @var Bookshelf $shelf */
109 $shelf = Bookshelf::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['bookshelf'])->first();
110 $this->addToCache($shelf);
115 * Get all entity types from the system.
116 * @return array{page: Page, chapter: Chapter, book: Book, bookshelf: Bookshelf}
118 public function all(): array
121 'page' => $this->page(),
122 'chapter' => $this->chapter(),
123 'book' => $this->book(),
124 'bookshelf' => $this->shelf(),
128 public function updatePage(Page $page, array $data): Page
130 $this->addToCache($page);
131 return app()->make(PageRepo::class)->update($page, $data);
135 * Create a book to page chain of entities that belong to a specific user.
136 * @return array{book: Book, chapter: Chapter, page: Page}
138 public function createChainBelongingToUser(User $creatorUser, ?User $updaterUser = null): array
140 if (empty($updaterUser)) {
141 $updaterUser = $creatorUser;
144 $userAttrs = ['created_by' => $creatorUser->id, 'owned_by' => $creatorUser->id, 'updated_by' => $updaterUser->id];
145 /** @var Book $book */
146 $book = Book::factory()->create($userAttrs);
147 $chapter = Chapter::factory()->create(array_merge(['book_id' => $book->id], $userAttrs));
148 $page = Page::factory()->create(array_merge(['book_id' => $book->id, 'chapter_id' => $chapter->id], $userAttrs));
150 $book->rebuildPermissions();
151 $this->addToCache([$page, $chapter, $book]);
153 return compact('book', 'chapter', 'page');
157 * Create and return a new bookshelf.
159 public function newShelf(array $input = ['name' => 'test shelf', 'description' => 'My new test shelf']): Bookshelf
161 $shelf = app(BookshelfRepo::class)->create($input, []);
162 $this->addToCache($shelf);
167 * Create and return a new book.
169 public function newBook(array $input = ['name' => 'test book', 'description' => 'My new test book']): Book
171 $book = app(BookRepo::class)->create($input);
172 $this->addToCache($book);
177 * Create and return a new test chapter.
179 public function newChapter(array $input, Book $book): Chapter
181 $chapter = app(ChapterRepo::class)->create($input, $book);
182 $this->addToCache($chapter);
187 * Create and return a new test page.
189 public function newPage(array $input = ['name' => 'test page', 'html' => 'My new test page']): Page
191 $book = $this->book();
192 $pageRepo = app(PageRepo::class);
193 $draftPage = $pageRepo->getNewDraftPage($book);
194 $this->addToCache($draftPage);
195 return $pageRepo->publishDraft($draftPage, $input);
199 * Create and return a new test draft page.
201 public function newDraftPage(array $input = ['name' => 'test page', 'html' => 'My new test page']): Page
203 $book = $this->book();
204 $pageRepo = app(PageRepo::class);
205 $draftPage = $pageRepo->getNewDraftPage($book);
206 $pageRepo->updatePageDraft($draftPage, $input);
207 $this->addToCache($draftPage);
212 * Send an entity to the recycle bin.
214 public function sendToRecycleBin(Entity $entity)
216 $trash = app()->make(TrashCan::class);
218 if ($entity instanceof Page) {
219 $trash->softDestroyPage($entity);
220 } elseif ($entity instanceof Chapter) {
221 $trash->softDestroyChapter($entity);
222 } elseif ($entity instanceof Book) {
223 $trash->softDestroyBook($entity);
224 } elseif ($entity instanceof Bookshelf) {
225 $trash->softDestroyBookshelf($entity);
229 if (is_null($entity->deleted_at)) {
230 throw new \Exception("Could not send entity type [{$entity->getMorphClass()}] to the recycle bin");
235 * Fully destroy the given entity from the system, bypassing the recycle bin
236 * stage. Still runs through main app deletion logic.
238 public function destroy(Entity $entity)
240 $trash = app()->make(TrashCan::class);
241 $trash->destroyEntity($entity);
245 * @param Entity|Entity[] $entities
247 protected function addToCache($entities): void
249 if (!is_array($entities)) {
250 $entities = [$entities];
253 foreach ($entities as $entity) {
254 $this->fetchCache[$entity->getType()][] = $entity->id;