]> BookStack Code Mirror - bookstack/blob - tests/Exports/ZipImportTest.php
ZIP Imports: Added full contents view to import display
[bookstack] / tests / Exports / ZipImportTest.php
1 <?php
2
3 namespace Tests\Exports;
4
5 use BookStack\Activity\ActivityType;
6 use BookStack\Exports\Import;
7 use BookStack\Exports\ZipExports\Models\ZipExportBook;
8 use BookStack\Exports\ZipExports\Models\ZipExportChapter;
9 use BookStack\Exports\ZipExports\Models\ZipExportPage;
10 use Illuminate\Http\UploadedFile;
11 use Illuminate\Testing\TestResponse;
12 use Tests\TestCase;
13 use ZipArchive;
14
15 class ZipImportTest extends TestCase
16 {
17     public function test_import_page_view()
18     {
19         $resp = $this->asAdmin()->get('/import');
20         $resp->assertSee('Import');
21         $this->withHtml($resp)->assertElementExists('form input[type="file"][name="file"]');
22     }
23
24     public function test_permissions_needed_for_import_page()
25     {
26         $user = $this->users->viewer();
27         $this->actingAs($user);
28
29         $resp = $this->get('/books');
30         $this->withHtml($resp)->assertLinkNotExists(url('/import'));
31         $resp = $this->get('/import');
32         $resp->assertRedirect('/');
33
34         $this->permissions->grantUserRolePermissions($user, ['content-import']);
35
36         $resp = $this->get('/books');
37         $this->withHtml($resp)->assertLinkExists(url('/import'));
38         $resp = $this->get('/import');
39         $resp->assertOk();
40         $resp->assertSeeText('Select ZIP file to upload');
41     }
42
43     public function test_import_page_pending_import_visibility_limited()
44     {
45         $user = $this->users->viewer();
46         $admin = $this->users->admin();
47         $userImport = Import::factory()->create(['name' => 'MySuperUserImport', 'created_by' => $user->id]);
48         $adminImport = Import::factory()->create(['name' => 'MySuperAdminImport', 'created_by' => $admin->id]);
49         $this->permissions->grantUserRolePermissions($user, ['content-import']);
50
51         $resp = $this->actingAs($user)->get('/import');
52         $resp->assertSeeText('MySuperUserImport');
53         $resp->assertDontSeeText('MySuperAdminImport');
54
55         $this->permissions->grantUserRolePermissions($user, ['settings-manage']);
56
57         $resp = $this->actingAs($user)->get('/import');
58         $resp->assertSeeText('MySuperUserImport');
59         $resp->assertSeeText('MySuperAdminImport');
60     }
61
62     public function test_zip_read_errors_are_shown_on_validation()
63     {
64         $invalidUpload = $this->files->uploadedImage('image.zip');
65
66         $this->asAdmin();
67         $resp = $this->runImportFromFile($invalidUpload);
68         $resp->assertRedirect('/import');
69
70         $resp = $this->followRedirects($resp);
71         $resp->assertSeeText('Could not read ZIP file');
72     }
73
74     public function test_error_shown_if_missing_data()
75     {
76         $zipFile = tempnam(sys_get_temp_dir(), 'bstest-');
77         $zip = new ZipArchive();
78         $zip->open($zipFile, ZipArchive::CREATE);
79         $zip->addFromString('beans', 'cat');
80         $zip->close();
81
82         $this->asAdmin();
83         $upload = new UploadedFile($zipFile, 'upload.zip', 'application/zip', null, true);
84         $resp = $this->runImportFromFile($upload);
85         $resp->assertRedirect('/import');
86
87         $resp = $this->followRedirects($resp);
88         $resp->assertSeeText('Could not find and decode ZIP data.json content.');
89     }
90
91     public function test_error_shown_if_no_importable_key()
92     {
93         $this->asAdmin();
94         $resp = $this->runImportFromFile($this->zipUploadFromData([
95             'instance' => []
96         ]));
97
98         $resp->assertRedirect('/import');
99         $resp = $this->followRedirects($resp);
100         $resp->assertSeeText('ZIP file data has no expected book, chapter or page content.');
101     }
102
103     public function test_zip_data_validation_messages_shown()
104     {
105         $this->asAdmin();
106         $resp = $this->runImportFromFile($this->zipUploadFromData([
107             'book' => [
108                 'id' => 4,
109                 'pages' => [
110                     'cat',
111                     [
112                         'name' => 'My inner page',
113                         'tags' => [
114                             [
115                                 'value' => 5
116                             ]
117                         ],
118                     ]
119                 ],
120             ]
121         ]));
122
123         $resp->assertRedirect('/import');
124         $resp = $this->followRedirects($resp);
125
126         $resp->assertSeeText('[book.name]: The name field is required.');
127         $resp->assertSeeText('[book.pages.0.0]: Data object expected but "string" found.');
128         $resp->assertSeeText('[book.pages.1.tags.0.name]: The name field is required.');
129         $resp->assertSeeText('[book.pages.1.tags.0.value]: The value must be a string.');
130     }
131
132     public function test_import_upload_success()
133     {
134         $admin = $this->users->admin();
135         $this->actingAs($admin);
136         $data = [
137             'book' => [
138                 'name' => 'My great book name',
139                 'chapters' => [
140                     [
141                         'name' => 'my chapter',
142                         'pages' => [
143                             [
144                                 'name' => 'my chapter page',
145                             ]
146                         ]
147                     ]
148                 ],
149                 'pages' => [
150                     [
151                         'name' => 'My page',
152                     ]
153                 ],
154             ],
155         ];
156
157         $resp = $this->runImportFromFile($this->zipUploadFromData($data));
158
159         $this->assertDatabaseHas('imports', [
160             'name' => 'My great book name',
161             'type' => 'book',
162             'created_by' => $admin->id,
163         ]);
164
165         /** @var Import $import */
166         $import = Import::query()->latest()->first();
167         $resp->assertRedirect("/import/{$import->id}");
168         $this->assertFileExists(storage_path($import->path));
169         $this->assertActivityExists(ActivityType::IMPORT_CREATE);
170     }
171
172     public function test_import_show_page()
173     {
174         $exportBook = new ZipExportBook();
175         $exportBook->name = 'My exported book';
176         $exportChapter = new ZipExportChapter();
177         $exportChapter->name = 'My exported chapter';
178         $exportPage = new ZipExportPage();
179         $exportPage->name = 'My exported page';
180         $exportBook->chapters = [$exportChapter];
181         $exportChapter->pages = [$exportPage];
182
183         $import = Import::factory()->create([
184             'name' => 'MySuperAdminImport',
185             'metadata' => json_encode($exportBook)
186         ]);
187
188         $resp = $this->asAdmin()->get("/import/{$import->id}");
189         $resp->assertOk();
190         $resp->assertSeeText('My exported book');
191         $resp->assertSeeText('My exported chapter');
192         $resp->assertSeeText('My exported page');
193     }
194
195     public function test_import_show_page_access_limited()
196     {
197         $user = $this->users->viewer();
198         $admin = $this->users->admin();
199         $userImport = Import::factory()->create(['name' => 'MySuperUserImport', 'created_by' => $user->id]);
200         $adminImport = Import::factory()->create(['name' => 'MySuperAdminImport', 'created_by' => $admin->id]);
201         $this->actingAs($user);
202
203         $this->get("/import/{$userImport->id}")->assertRedirect('/');
204         $this->get("/import/{$adminImport->id}")->assertRedirect('/');
205
206         $this->permissions->grantUserRolePermissions($user, ['content-import']);
207
208         $this->get("/import/{$userImport->id}")->assertOk();
209         $this->get("/import/{$adminImport->id}")->assertStatus(404);
210
211         $this->permissions->grantUserRolePermissions($user, ['settings-manage']);
212
213         $this->get("/import/{$userImport->id}")->assertOk();
214         $this->get("/import/{$adminImport->id}")->assertOk();
215     }
216
217     public function test_import_delete()
218     {
219         $this->asAdmin();
220         $this->runImportFromFile($this->zipUploadFromData([
221             'book' => [
222                 'name' => 'My great book name'
223             ],
224         ]));
225
226         /** @var Import $import */
227         $import = Import::query()->latest()->first();
228         $this->assertDatabaseHas('imports', [
229             'id' => $import->id,
230             'name' => 'My great book name'
231         ]);
232         $this->assertFileExists(storage_path($import->path));
233
234         $resp = $this->delete("/import/{$import->id}");
235
236         $resp->assertRedirect('/import');
237         $this->assertActivityExists(ActivityType::IMPORT_DELETE);
238         $this->assertDatabaseMissing('imports', [
239             'id' => $import->id,
240         ]);
241         $this->assertFileDoesNotExist(storage_path($import->path));
242     }
243
244     public function test_import_delete_access_limited()
245     {
246         $user = $this->users->viewer();
247         $admin = $this->users->admin();
248         $userImport = Import::factory()->create(['name' => 'MySuperUserImport', 'created_by' => $user->id]);
249         $adminImport = Import::factory()->create(['name' => 'MySuperAdminImport', 'created_by' => $admin->id]);
250         $this->actingAs($user);
251
252         $this->delete("/import/{$userImport->id}")->assertRedirect('/');
253         $this->delete("/import/{$adminImport->id}")->assertRedirect('/');
254
255         $this->permissions->grantUserRolePermissions($user, ['content-import']);
256
257         $this->delete("/import/{$userImport->id}")->assertRedirect('/import');
258         $this->delete("/import/{$adminImport->id}")->assertStatus(404);
259
260         $this->permissions->grantUserRolePermissions($user, ['settings-manage']);
261
262         $this->delete("/import/{$adminImport->id}")->assertRedirect('/import');
263     }
264
265     protected function runImportFromFile(UploadedFile $file): TestResponse
266     {
267         return $this->call('POST', '/import', [], [], ['file' => $file]);
268     }
269
270     protected function zipUploadFromData(array $data): UploadedFile
271     {
272         $zipFile = tempnam(sys_get_temp_dir(), 'bstest-');
273
274         $zip = new ZipArchive();
275         $zip->open($zipFile, ZipArchive::CREATE);
276         $zip->addFromString('data.json', json_encode($data));
277         $zip->close();
278
279         return new UploadedFile($zipFile, 'upload.zip', 'application/zip', null, true);
280     }
281 }