]> BookStack Code Mirror - bookstack/blob - tests/Helpers/EntityProvider.php
Lexical: Added tests to cover recent changes
[bookstack] / tests / Helpers / EntityProvider.php
1 <?php
2
3 namespace Tests\Helpers;
4
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;
18
19 /**
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.
23  */
24 class EntityProvider
25 {
26     /**
27      * @var array<string, int[]>
28      */
29     protected array $fetchCache = [
30         'book' => [],
31         'page' => [],
32         'bookshelf' => [],
33         'chapter' => [],
34     ];
35
36     /**
37      * Get an un-fetched page from the system.
38      */
39     public function page(callable $queryFilter = null): Page
40     {
41         /** @var Page $page */
42         $page = Page::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['page'])->first();
43         $this->addToCache($page);
44         return $page;
45     }
46
47     public function pageWithinChapter(): Page
48     {
49         return $this->page(fn(Builder $query) => $query->whereHas('chapter')->with('chapter'));
50     }
51
52     public function pageNotWithinChapter(): Page
53     {
54         return $this->page(fn(Builder $query) => $query->where('chapter_id', '=', 0));
55     }
56
57     public function templatePage(): Page
58     {
59         $page = $this->page();
60         $page->template = true;
61         $page->save();
62
63         return $page;
64     }
65
66     /**
67      * Get an un-fetched chapter from the system.
68      */
69     public function chapter(callable $queryFilter = null): Chapter
70     {
71         /** @var Chapter $chapter */
72         $chapter = Chapter::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['chapter'])->first();
73         $this->addToCache($chapter);
74         return $chapter;
75     }
76
77     public function chapterHasPages(): Chapter
78     {
79         return $this->chapter(fn(Builder $query) => $query->whereHas('pages'));
80     }
81
82     /**
83      * Get an un-fetched book from the system.
84      */
85     public function book(callable $queryFilter = null): Book
86     {
87         /** @var Book $book */
88         $book = Book::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['book'])->first();
89         $this->addToCache($book);
90         return $book;
91     }
92
93     /**
94      * Get a book that has chapters and pages assigned.
95      */
96     public function bookHasChaptersAndPages(): Book
97     {
98         return $this->book(function (Builder $query) {
99             $query->has('chapters')->has('pages')->with(['chapters', 'pages']);
100         });
101     }
102
103     /**
104      * Get an un-fetched shelf from the system.
105      */
106     public function shelf(callable $queryFilter = null): Bookshelf
107     {
108         /** @var Bookshelf $shelf */
109         $shelf = Bookshelf::query()->when($queryFilter, $queryFilter)->whereNotIn('id', $this->fetchCache['bookshelf'])->first();
110         $this->addToCache($shelf);
111         return $shelf;
112     }
113
114     /**
115      * Get all entity types from the system.
116      * @return array{page: Page, chapter: Chapter, book: Book, bookshelf: Bookshelf}
117      */
118     public function all(): array
119     {
120         return [
121             'page'      => $this->page(),
122             'chapter'   => $this->chapter(),
123             'book'      => $this->book(),
124             'bookshelf' => $this->shelf(),
125         ];
126     }
127
128     public function updatePage(Page $page, array $data): Page
129     {
130         $this->addToCache($page);
131         return app()->make(PageRepo::class)->update($page, $data);
132     }
133
134     /**
135      * Create a book to page chain of entities that belong to a specific user.
136      * @return array{book: Book, chapter: Chapter, page: Page}
137      */
138     public function createChainBelongingToUser(User $creatorUser, ?User $updaterUser = null): array
139     {
140         if (empty($updaterUser)) {
141             $updaterUser = $creatorUser;
142         }
143
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));
149
150         $book->rebuildPermissions();
151         $this->addToCache([$page, $chapter, $book]);
152
153         return compact('book', 'chapter', 'page');
154     }
155
156     /**
157      * Create and return a new bookshelf.
158      */
159     public function newShelf(array $input = ['name' => 'test shelf', 'description' => 'My new test shelf']): Bookshelf
160     {
161         $shelf = app(BookshelfRepo::class)->create($input, []);
162         $this->addToCache($shelf);
163         return $shelf;
164     }
165
166     /**
167      * Create and return a new book.
168      */
169     public function newBook(array $input = ['name' => 'test book', 'description' => 'My new test book']): Book
170     {
171         $book = app(BookRepo::class)->create($input);
172         $this->addToCache($book);
173         return $book;
174     }
175
176     /**
177      * Create and return a new test chapter.
178      */
179     public function newChapter(array $input, Book $book): Chapter
180     {
181         $chapter = app(ChapterRepo::class)->create($input, $book);
182         $this->addToCache($chapter);
183         return $chapter;
184     }
185
186     /**
187      * Create and return a new test page.
188      */
189     public function newPage(array $input = ['name' => 'test page', 'html' => 'My new test page']): Page
190     {
191         $book = $this->book();
192         $pageRepo = app(PageRepo::class);
193         $draftPage = $pageRepo->getNewDraftPage($book);
194         $this->addToCache($draftPage);
195         return $pageRepo->publishDraft($draftPage, $input);
196     }
197
198     /**
199      * Create and return a new test draft page.
200      */
201     public function newDraftPage(array $input = ['name' => 'test page', 'html' => 'My new test page']): Page
202     {
203         $book = $this->book();
204         $pageRepo = app(PageRepo::class);
205         $draftPage = $pageRepo->getNewDraftPage($book);
206         $pageRepo->updatePageDraft($draftPage, $input);
207         $this->addToCache($draftPage);
208         return $draftPage;
209     }
210
211     /**
212      * Send an entity to the recycle bin.
213      */
214     public function sendToRecycleBin(Entity $entity)
215     {
216         $trash = app()->make(TrashCan::class);
217
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);
226         }
227
228         $entity->refresh();
229         if (is_null($entity->deleted_at)) {
230             throw new \Exception("Could not send entity type [{$entity->getMorphClass()}] to the recycle bin");
231         }
232     }
233
234     /**
235      * Fully destroy the given entity from the system, bypassing the recycle bin
236      * stage. Still runs through main app deletion logic.
237      */
238     public function destroy(Entity $entity)
239     {
240         $trash = app()->make(TrashCan::class);
241         $trash->destroyEntity($entity);
242     }
243
244     /**
245      * @param Entity|Entity[] $entities
246      */
247     protected function addToCache($entities): void
248     {
249         if (!is_array($entities)) {
250             $entities = [$entities];
251         }
252
253         foreach ($entities as $entity) {
254             $this->fetchCache[$entity->getType()][] = $entity->id;
255         }
256     }
257 }