3 use BookStack\Entities\Book;
4 use BookStack\Entities\Deletion;
5 use BookStack\Entities\Page;
7 use Illuminate\Support\Carbon;
9 class RecycleBinTest extends TestCase
11 public function test_recycle_bin_routes_permissions()
13 $page = Page::query()->first();
14 $editor = $this->getEditor();
15 $this->actingAs($editor)->delete($page->getUrl());
16 $deletion = Deletion::query()->firstOrFail();
19 'GET:/settings/recycle-bin',
20 'POST:/settings/recycle-bin/empty',
21 "GET:/settings/recycle-bin/{$deletion->id}/destroy",
22 "GET:/settings/recycle-bin/{$deletion->id}/restore",
23 "POST:/settings/recycle-bin/{$deletion->id}/restore",
24 "DELETE:/settings/recycle-bin/{$deletion->id}",
27 foreach($routes as $route) {
28 [$method, $url] = explode(':', $route);
29 $resp = $this->call($method, $url);
30 $this->assertPermissionError($resp);
33 $this->giveUserPermissions($editor, ['restrictions-manage-all']);
35 foreach($routes as $route) {
36 [$method, $url] = explode(':', $route);
37 $resp = $this->call($method, $url);
38 $this->assertPermissionError($resp);
41 $this->giveUserPermissions($editor, ['settings-manage']);
43 foreach($routes as $route) {
44 DB::beginTransaction();
45 [$method, $url] = explode(':', $route);
46 $resp = $this->call($method, $url);
47 $this->assertNotPermissionError($resp);
53 public function test_recycle_bin_view()
55 $page = Page::query()->first();
56 $book = Book::query()->whereHas('pages')->whereHas('chapters')->withCount(['pages', 'chapters'])->first();
57 $editor = $this->getEditor();
58 $this->actingAs($editor)->delete($page->getUrl());
59 $this->actingAs($editor)->delete($book->getUrl());
61 $viewReq = $this->asAdmin()->get('/settings/recycle-bin');
62 $viewReq->assertElementContains('table.table', $page->name);
63 $viewReq->assertElementContains('table.table', $editor->name);
64 $viewReq->assertElementContains('table.table', $book->name);
65 $viewReq->assertElementContains('table.table', $book->pages_count . ' Pages');
66 $viewReq->assertElementContains('table.table', $book->chapters_count . ' Chapters');
69 public function test_recycle_bin_empty()
71 $page = Page::query()->first();
72 $book = Book::query()->where('id' , '!=', $page->book_id)->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
73 $editor = $this->getEditor();
74 $this->actingAs($editor)->delete($page->getUrl());
75 $this->actingAs($editor)->delete($book->getUrl());
77 $this->assertTrue(Deletion::query()->count() === 2);
78 $emptyReq = $this->asAdmin()->post('/settings/recycle-bin/empty');
79 $emptyReq->assertRedirect('/settings/recycle-bin');
81 $this->assertTrue(Deletion::query()->count() === 0);
82 $this->assertDatabaseMissing('books', ['id' => $book->id]);
83 $this->assertDatabaseMissing('pages', ['id' => $page->id]);
84 $this->assertDatabaseMissing('pages', ['id' => $book->pages->first()->id]);
85 $this->assertDatabaseMissing('chapters', ['id' => $book->chapters->first()->id]);
87 $itemCount = 2 + $book->pages->count() + $book->chapters->count();
88 $redirectReq = $this->get('/settings/recycle-bin');
89 $redirectReq->assertNotificationContains('Deleted '.$itemCount.' total items from the recycle bin');
92 public function test_entity_restore()
94 $book = Book::query()->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
95 $this->asEditor()->delete($book->getUrl());
96 $deletion = Deletion::query()->firstOrFail();
98 $this->assertEquals($book->pages->count(), DB::table('pages')->where('book_id', '=', $book->id)->whereNotNull('deleted_at')->count());
99 $this->assertEquals($book->chapters->count(), DB::table('chapters')->where('book_id', '=', $book->id)->whereNotNull('deleted_at')->count());
101 $restoreReq = $this->asAdmin()->post("/settings/recycle-bin/{$deletion->id}/restore");
102 $restoreReq->assertRedirect('/settings/recycle-bin');
103 $this->assertTrue(Deletion::query()->count() === 0);
105 $this->assertEquals($book->pages->count(), DB::table('pages')->where('book_id', '=', $book->id)->whereNull('deleted_at')->count());
106 $this->assertEquals($book->chapters->count(), DB::table('chapters')->where('book_id', '=', $book->id)->whereNull('deleted_at')->count());
108 $itemCount = 1 + $book->pages->count() + $book->chapters->count();
109 $redirectReq = $this->get('/settings/recycle-bin');
110 $redirectReq->assertNotificationContains('Restored '.$itemCount.' total items from the recycle bin');
113 public function test_permanent_delete()
115 $book = Book::query()->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
116 $this->asEditor()->delete($book->getUrl());
117 $deletion = Deletion::query()->firstOrFail();
119 $deleteReq = $this->asAdmin()->delete("/settings/recycle-bin/{$deletion->id}");
120 $deleteReq->assertRedirect('/settings/recycle-bin');
121 $this->assertTrue(Deletion::query()->count() === 0);
123 $this->assertDatabaseMissing('books', ['id' => $book->id]);
124 $this->assertDatabaseMissing('pages', ['id' => $book->pages->first()->id]);
125 $this->assertDatabaseMissing('chapters', ['id' => $book->chapters->first()->id]);
127 $itemCount = 1 + $book->pages->count() + $book->chapters->count();
128 $redirectReq = $this->get('/settings/recycle-bin');
129 $redirectReq->assertNotificationContains('Deleted '.$itemCount.' total items from the recycle bin');
132 public function test_permanent_entity_delete_updates_existing_activity_with_entity_name()
134 $page = Page::query()->firstOrFail();
135 $this->asEditor()->delete($page->getUrl());
136 $deletion = $page->deletions()->firstOrFail();
138 $this->assertDatabaseHas('activities', [
139 'key' => 'page_delete',
140 'entity_id' => $page->id,
141 'entity_type' => $page->getMorphClass(),
144 $this->asAdmin()->delete("/settings/recycle-bin/{$deletion->id}");
146 $this->assertDatabaseMissing('activities', [
147 'key' => 'page_delete',
148 'entity_id' => $page->id,
149 'entity_type' => $page->getMorphClass(),
152 $this->assertDatabaseHas('activities', [
153 'key' => 'page_delete',
156 'extra' => $page->name,
160 public function test_auto_clear_functionality_works()
162 config()->set('app.recycle_bin_lifetime', 5);
163 $page = Page::query()->firstOrFail();
164 $otherPage = Page::query()->where('id', '!=', $page->id)->firstOrFail();
166 $this->asEditor()->delete($page->getUrl());
167 $this->assertDatabaseHas('pages', ['id' => $page->id]);
168 $this->assertEquals(1, Deletion::query()->count());
170 Carbon::setTestNow(Carbon::now()->addDays(6));
171 $this->asEditor()->delete($otherPage->getUrl());
172 $this->assertEquals(1, Deletion::query()->count());
174 $this->assertDatabaseMissing('pages', ['id' => $page->id]);
177 public function test_auto_clear_functionality_with_negative_time_keeps_forever()
179 config()->set('app.recycle_bin_lifetime', -1);
180 $page = Page::query()->firstOrFail();
181 $otherPage = Page::query()->where('id', '!=', $page->id)->firstOrFail();
183 $this->asEditor()->delete($page->getUrl());
184 $this->assertEquals(1, Deletion::query()->count());
186 Carbon::setTestNow(Carbon::now()->addDays(6000));
187 $this->asEditor()->delete($otherPage->getUrl());
188 $this->assertEquals(2, Deletion::query()->count());
190 $this->assertDatabaseHas('pages', ['id' => $page->id]);
193 public function test_auto_clear_functionality_with_zero_time_deletes_instantly()
195 config()->set('app.recycle_bin_lifetime', 0);
196 $page = Page::query()->firstOrFail();
198 $this->asEditor()->delete($page->getUrl());
199 $this->assertDatabaseMissing('pages', ['id' => $page->id]);
200 $this->assertEquals(0, Deletion::query()->count());
203 public function test_restore_flow_when_restoring_nested_delete_first()
205 $book = Book::query()->whereHas('pages')->whereHas('chapters')->with(['pages', 'chapters'])->firstOrFail();
206 $chapter = $book->chapters->first();
207 $this->asEditor()->delete($chapter->getUrl());
208 $this->asEditor()->delete($book->getUrl());
210 $bookDeletion = $book->deletions()->first();
211 $chapterDeletion = $chapter->deletions()->first();
213 $chapterRestoreView = $this->asAdmin()->get("/settings/recycle-bin/{$chapterDeletion->id}/restore");
214 $chapterRestoreView->assertStatus(200);
215 $chapterRestoreView->assertSeeText($chapter->name);
217 $chapterRestore = $this->post("/settings/recycle-bin/{$chapterDeletion->id}/restore");
218 $chapterRestore->assertRedirect("/settings/recycle-bin");
219 $this->assertDatabaseMissing("deletions", ["id" => $chapterDeletion->id]);
222 $this->assertNotNull($chapter->deleted_at);
224 $bookRestoreView = $this->asAdmin()->get("/settings/recycle-bin/{$bookDeletion->id}/restore");
225 $bookRestoreView->assertStatus(200);
226 $bookRestoreView->assertSeeText($chapter->name);
228 $this->post("/settings/recycle-bin/{$bookDeletion->id}/restore");
230 $this->assertNull($chapter->deleted_at);