]> BookStack Code Mirror - bookstack/blob - tests/Settings/RecycleBinTest.php
Cleaned testing service provider usage
[bookstack] / tests / Settings / RecycleBinTest.php
1 <?php
2
3 namespace Tests\Settings;
4
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Bookshelf;
7 use BookStack\Entities\Models\Chapter;
8 use BookStack\Entities\Models\Deletion;
9 use BookStack\Entities\Models\Entity;
10 use BookStack\Entities\Models\Page;
11 use Illuminate\Support\Carbon;
12 use Illuminate\Support\Facades\DB;
13 use Tests\TestCase;
14
15 class RecycleBinTest extends TestCase
16 {
17     public function test_recycle_bin_routes_permissions()
18     {
19         $page = Page::query()->first();
20         $editor = $this->getEditor();
21         $this->actingAs($editor)->delete($page->getUrl());
22         $deletion = Deletion::query()->firstOrFail();
23
24         $routes = [
25             'GET:/settings/recycle-bin',
26             'POST:/settings/recycle-bin/empty',
27             "GET:/settings/recycle-bin/{$deletion->id}/destroy",
28             "GET:/settings/recycle-bin/{$deletion->id}/restore",
29             "POST:/settings/recycle-bin/{$deletion->id}/restore",
30             "DELETE:/settings/recycle-bin/{$deletion->id}",
31         ];
32
33         foreach ($routes as $route) {
34             [$method, $url] = explode(':', $route);
35             $resp = $this->call($method, $url);
36             $this->assertPermissionError($resp);
37         }
38
39         $this->giveUserPermissions($editor, ['restrictions-manage-all']);
40
41         foreach ($routes as $route) {
42             [$method, $url] = explode(':', $route);
43             $resp = $this->call($method, $url);
44             $this->assertPermissionError($resp);
45         }
46
47         $this->giveUserPermissions($editor, ['settings-manage']);
48
49         foreach ($routes as $route) {
50             DB::beginTransaction();
51             [$method, $url] = explode(':', $route);
52             $resp = $this->call($method, $url);
53             $this->assertNotPermissionError($resp);
54             DB::rollBack();
55         }
56     }
57
58     public function test_recycle_bin_view()
59     {
60         $page = Page::query()->first();
61         $book = Book::query()->whereHas('pages')->whereHas('chapters')->withCount(['pages', 'chapters'])->first();
62         $editor = $this->getEditor();
63         $this->actingAs($editor)->delete($page->getUrl());
64         $this->actingAs($editor)->delete($book->getUrl());
65
66         $viewReq = $this->asAdmin()->get('/settings/recycle-bin');
67         $html = $this->withHtml($viewReq);
68         $html->assertElementContains('table.table', $page->name);
69         $html->assertElementContains('table.table', $editor->name);
70         $html->assertElementContains('table.table', $book->name);
71         $html->assertElementContains('table.table', $book->pages_count . ' Pages');
72         $html->assertElementContains('table.table', $book->chapters_count . ' Chapters');
73     }
74
75     public function test_recycle_bin_empty()
76     {
77         $page = Page::query()->first();
78         $book = Book::query()->where('id', '!=', $page->book_id)->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
79         $editor = $this->getEditor();
80         $this->actingAs($editor)->delete($page->getUrl());
81         $this->actingAs($editor)->delete($book->getUrl());
82
83         $this->assertTrue(Deletion::query()->count() === 2);
84         $emptyReq = $this->asAdmin()->post('/settings/recycle-bin/empty');
85         $emptyReq->assertRedirect('/settings/recycle-bin');
86
87         $this->assertTrue(Deletion::query()->count() === 0);
88         $this->assertDatabaseMissing('books', ['id' => $book->id]);
89         $this->assertDatabaseMissing('pages', ['id' => $page->id]);
90         $this->assertDatabaseMissing('pages', ['id' => $book->pages->first()->id]);
91         $this->assertDatabaseMissing('chapters', ['id' => $book->chapters->first()->id]);
92
93         $itemCount = 2 + $book->pages->count() + $book->chapters->count();
94         $redirectReq = $this->get('/settings/recycle-bin');
95         $this->assertNotificationContains($redirectReq, 'Deleted ' . $itemCount . ' total items from the recycle bin');
96     }
97
98     public function test_entity_restore()
99     {
100         $book = Book::query()->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
101         $this->asEditor()->delete($book->getUrl());
102         $deletion = Deletion::query()->firstOrFail();
103
104         $this->assertEquals($book->pages->count(), DB::table('pages')->where('book_id', '=', $book->id)->whereNotNull('deleted_at')->count());
105         $this->assertEquals($book->chapters->count(), DB::table('chapters')->where('book_id', '=', $book->id)->whereNotNull('deleted_at')->count());
106
107         $restoreReq = $this->asAdmin()->post("/settings/recycle-bin/{$deletion->id}/restore");
108         $restoreReq->assertRedirect('/settings/recycle-bin');
109         $this->assertTrue(Deletion::query()->count() === 0);
110
111         $this->assertEquals($book->pages->count(), DB::table('pages')->where('book_id', '=', $book->id)->whereNull('deleted_at')->count());
112         $this->assertEquals($book->chapters->count(), DB::table('chapters')->where('book_id', '=', $book->id)->whereNull('deleted_at')->count());
113
114         $itemCount = 1 + $book->pages->count() + $book->chapters->count();
115         $redirectReq = $this->get('/settings/recycle-bin');
116         $this->assertNotificationContains($redirectReq, 'Restored ' . $itemCount . ' total items from the recycle bin');
117     }
118
119     public function test_permanent_delete()
120     {
121         $book = Book::query()->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
122         $this->asEditor()->delete($book->getUrl());
123         $deletion = Deletion::query()->firstOrFail();
124
125         $deleteReq = $this->asAdmin()->delete("/settings/recycle-bin/{$deletion->id}");
126         $deleteReq->assertRedirect('/settings/recycle-bin');
127         $this->assertTrue(Deletion::query()->count() === 0);
128
129         $this->assertDatabaseMissing('books', ['id' => $book->id]);
130         $this->assertDatabaseMissing('pages', ['id' => $book->pages->first()->id]);
131         $this->assertDatabaseMissing('chapters', ['id' => $book->chapters->first()->id]);
132
133         $itemCount = 1 + $book->pages->count() + $book->chapters->count();
134         $redirectReq = $this->get('/settings/recycle-bin');
135         $this->assertNotificationContains($redirectReq, 'Deleted ' . $itemCount . ' total items from the recycle bin');
136     }
137
138     public function test_permanent_delete_for_each_type()
139     {
140         /** @var Entity $entity */
141         foreach ([new Bookshelf(), new Book(), new Chapter(), new Page()] as $entity) {
142             $entity = $entity->newQuery()->first();
143             $this->asEditor()->delete($entity->getUrl());
144             $deletion = Deletion::query()->orderBy('id', 'desc')->firstOrFail();
145
146             $deleteReq = $this->asAdmin()->delete("/settings/recycle-bin/{$deletion->id}");
147             $deleteReq->assertRedirect('/settings/recycle-bin');
148             $this->assertDatabaseMissing('deletions', ['id' => $deletion->id]);
149             $this->assertDatabaseMissing($entity->getTable(), ['id' => $entity->id]);
150         }
151     }
152
153     public function test_permanent_entity_delete_updates_existing_activity_with_entity_name()
154     {
155         $page = Page::query()->firstOrFail();
156         $this->asEditor()->delete($page->getUrl());
157         $deletion = $page->deletions()->firstOrFail();
158
159         $this->assertDatabaseHas('activities', [
160             'type'        => 'page_delete',
161             'entity_id'   => $page->id,
162             'entity_type' => $page->getMorphClass(),
163         ]);
164
165         $this->asAdmin()->delete("/settings/recycle-bin/{$deletion->id}");
166
167         $this->assertDatabaseMissing('activities', [
168             'type'        => 'page_delete',
169             'entity_id'   => $page->id,
170             'entity_type' => $page->getMorphClass(),
171         ]);
172
173         $this->assertDatabaseHas('activities', [
174             'type'        => 'page_delete',
175             'entity_id'   => null,
176             'entity_type' => null,
177             'detail'      => $page->name,
178         ]);
179     }
180
181     public function test_auto_clear_functionality_works()
182     {
183         config()->set('app.recycle_bin_lifetime', 5);
184         $page = Page::query()->firstOrFail();
185         $otherPage = Page::query()->where('id', '!=', $page->id)->firstOrFail();
186
187         $this->asEditor()->delete($page->getUrl());
188         $this->assertDatabaseHas('pages', ['id' => $page->id]);
189         $this->assertEquals(1, Deletion::query()->count());
190
191         Carbon::setTestNow(Carbon::now()->addDays(6));
192         $this->asEditor()->delete($otherPage->getUrl());
193         $this->assertEquals(1, Deletion::query()->count());
194
195         $this->assertDatabaseMissing('pages', ['id' => $page->id]);
196     }
197
198     public function test_auto_clear_functionality_with_negative_time_keeps_forever()
199     {
200         config()->set('app.recycle_bin_lifetime', -1);
201         $page = Page::query()->firstOrFail();
202         $otherPage = Page::query()->where('id', '!=', $page->id)->firstOrFail();
203
204         $this->asEditor()->delete($page->getUrl());
205         $this->assertEquals(1, Deletion::query()->count());
206
207         Carbon::setTestNow(Carbon::now()->addDays(6000));
208         $this->asEditor()->delete($otherPage->getUrl());
209         $this->assertEquals(2, Deletion::query()->count());
210
211         $this->assertDatabaseHas('pages', ['id' => $page->id]);
212     }
213
214     public function test_auto_clear_functionality_with_zero_time_deletes_instantly()
215     {
216         config()->set('app.recycle_bin_lifetime', 0);
217         $page = Page::query()->firstOrFail();
218
219         $this->asEditor()->delete($page->getUrl());
220         $this->assertDatabaseMissing('pages', ['id' => $page->id]);
221         $this->assertEquals(0, Deletion::query()->count());
222     }
223
224     public function test_restore_flow_when_restoring_nested_delete_first()
225     {
226         $book = Book::query()->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
227         $chapter = $book->chapters->first();
228         $this->asEditor()->delete($chapter->getUrl());
229         $this->asEditor()->delete($book->getUrl());
230
231         $bookDeletion = $book->deletions()->first();
232         $chapterDeletion = $chapter->deletions()->first();
233
234         $chapterRestoreView = $this->asAdmin()->get("/settings/recycle-bin/{$chapterDeletion->id}/restore");
235         $chapterRestoreView->assertStatus(200);
236         $chapterRestoreView->assertSeeText($chapter->name);
237
238         $chapterRestore = $this->post("/settings/recycle-bin/{$chapterDeletion->id}/restore");
239         $chapterRestore->assertRedirect('/settings/recycle-bin');
240         $this->assertDatabaseMissing('deletions', ['id' => $chapterDeletion->id]);
241
242         $chapter->refresh();
243         $this->assertNotNull($chapter->deleted_at);
244
245         $bookRestoreView = $this->asAdmin()->get("/settings/recycle-bin/{$bookDeletion->id}/restore");
246         $bookRestoreView->assertStatus(200);
247         $bookRestoreView->assertSeeText($chapter->name);
248
249         $this->post("/settings/recycle-bin/{$bookDeletion->id}/restore");
250         $chapter->refresh();
251         $this->assertNull($chapter->deleted_at);
252     }
253
254     public function test_restore_page_shows_link_to_parent_restore_if_parent_also_deleted()
255     {
256         /** @var Book $book */
257         $book = Book::query()->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
258         $chapter = $book->chapters->first();
259         /** @var Page $page */
260         $page = $chapter->pages->first();
261         $this->asEditor()->delete($page->getUrl());
262         $this->asEditor()->delete($book->getUrl());
263
264         $bookDeletion = $book->deletions()->first();
265         $pageDeletion = $page->deletions()->first();
266
267         $pageRestoreView = $this->asAdmin()->get("/settings/recycle-bin/{$pageDeletion->id}/restore");
268         $pageRestoreView->assertSee('The parent of this item has also been deleted.');
269         $this->withHtml($pageRestoreView)->assertElementContains('a[href$="/settings/recycle-bin/' . $bookDeletion->id . '/restore"]', 'Restore Parent');
270     }
271 }