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