]> BookStack Code Mirror - bookstack/blob - tests/Api/ChaptersApiTest.php
Drawings: Added class to extract drawio data from png files
[bookstack] / tests / Api / ChaptersApiTest.php
1 <?php
2
3 namespace Tests\Api;
4
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Chapter;
7 use Carbon\Carbon;
8 use Illuminate\Support\Facades\DB;
9 use Tests\TestCase;
10
11 class ChaptersApiTest extends TestCase
12 {
13     use TestsApi;
14
15     protected string $baseEndpoint = '/api/chapters';
16
17     public function test_index_endpoint_returns_expected_chapter()
18     {
19         $this->actingAsApiEditor();
20         $firstChapter = Chapter::query()->orderBy('id', 'asc')->first();
21
22         $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
23         $resp->assertJson(['data' => [
24             [
25                 'id'        => $firstChapter->id,
26                 'name'      => $firstChapter->name,
27                 'slug'      => $firstChapter->slug,
28                 'book_id'   => $firstChapter->book->id,
29                 'priority'  => $firstChapter->priority,
30                 'book_slug' => $firstChapter->book->slug,
31                 'owned_by'   => $firstChapter->owned_by,
32                 'created_by' => $firstChapter->created_by,
33                 'updated_by' => $firstChapter->updated_by,
34             ],
35         ]]);
36     }
37
38     public function test_create_endpoint()
39     {
40         $this->actingAsApiEditor();
41         $book = $this->entities->book();
42         $templatePage = $this->entities->templatePage();
43         $details = [
44             'name'        => 'My API chapter',
45             'description' => 'A chapter created via the API',
46             'book_id'     => $book->id,
47             'tags'        => [
48                 [
49                     'name'  => 'tagname',
50                     'value' => 'tagvalue',
51                 ],
52             ],
53             'priority' => 15,
54             'default_template_id' => $templatePage->id,
55         ];
56
57         $resp = $this->postJson($this->baseEndpoint, $details);
58         $resp->assertStatus(200);
59         $newItem = Chapter::query()->orderByDesc('id')->where('name', '=', $details['name'])->first();
60         $resp->assertJson(array_merge($details, [
61             'id' => $newItem->id,
62             'slug' => $newItem->slug,
63             'description_html' => '<p>A chapter created via the API</p>',
64         ]));
65         $this->assertDatabaseHas('tags', [
66             'entity_id'   => $newItem->id,
67             'entity_type' => $newItem->getMorphClass(),
68             'name'        => 'tagname',
69             'value'       => 'tagvalue',
70         ]);
71         $resp->assertJsonMissing(['pages' => []]);
72         $this->assertActivityExists('chapter_create', $newItem);
73     }
74
75     public function test_create_endpoint_with_html()
76     {
77         $this->actingAsApiEditor();
78         $book = $this->entities->book();
79         $details = [
80             'name'             => 'My API chapter',
81             'description_html' => '<p>A chapter <strong>created</strong> via the API</p>',
82             'book_id'          => $book->id,
83         ];
84
85         $resp = $this->postJson($this->baseEndpoint, $details);
86         $resp->assertStatus(200);
87         $newItem = Chapter::query()->orderByDesc('id')->where('name', '=', $details['name'])->first();
88
89         $expectedDetails = array_merge($details, [
90             'id'          => $newItem->id,
91             'description' => 'A chapter created via the API',
92         ]);
93         $resp->assertJson($expectedDetails);
94         $this->assertDatabaseHas('chapters', $expectedDetails);
95     }
96
97     public function test_chapter_name_needed_to_create()
98     {
99         $this->actingAsApiEditor();
100         $book = $this->entities->book();
101         $details = [
102             'book_id'     => $book->id,
103             'description' => 'A chapter created via the API',
104         ];
105
106         $resp = $this->postJson($this->baseEndpoint, $details);
107         $resp->assertStatus(422);
108         $resp->assertJson($this->validationResponse([
109             'name' => ['The name field is required.'],
110         ]));
111     }
112
113     public function test_chapter_book_id_needed_to_create()
114     {
115         $this->actingAsApiEditor();
116         $details = [
117             'name'        => 'My api chapter',
118             'description' => 'A chapter created via the API',
119         ];
120
121         $resp = $this->postJson($this->baseEndpoint, $details);
122         $resp->assertStatus(422);
123         $resp->assertJson($this->validationResponse([
124             'book_id' => ['The book id field is required.'],
125         ]));
126     }
127
128     public function test_read_endpoint()
129     {
130         $this->actingAsApiEditor();
131         $chapter = $this->entities->chapter();
132         $page = $chapter->pages()->first();
133
134         $resp = $this->getJson($this->baseEndpoint . "/{$chapter->id}");
135         $resp->assertStatus(200);
136         $resp->assertJson([
137             'id'         => $chapter->id,
138             'slug'       => $chapter->slug,
139             'book_slug'  => $chapter->book->slug,
140             'created_by' => [
141                 'name' => $chapter->createdBy->name,
142             ],
143             'book_id'    => $chapter->book_id,
144             'updated_by' => [
145                 'name' => $chapter->createdBy->name,
146             ],
147             'owned_by' => [
148                 'name' => $chapter->ownedBy->name,
149             ],
150             'pages' => [
151                 [
152                     'id'   => $page->id,
153                     'slug' => $page->slug,
154                     'name' => $page->name,
155                     'owned_by' => $page->owned_by,
156                     'created_by' => $page->created_by,
157                     'updated_by' => $page->updated_by,
158                     'book_id' => $page->id,
159                     'chapter_id' => $chapter->id,
160                     'priority' => $page->priority,
161                     'book_slug' => $chapter->book->slug,
162                     'draft' => $page->draft,
163                     'template' => $page->template,
164                     'editor' => $page->editor,
165                 ],
166             ],
167             'default_template_id' => null,
168         ]);
169         $resp->assertJsonMissingPath('book');
170         $resp->assertJsonCount($chapter->pages()->count(), 'pages');
171     }
172
173     public function test_update_endpoint()
174     {
175         $this->actingAsApiEditor();
176         $chapter = $this->entities->chapter();
177         $templatePage = $this->entities->templatePage();
178         $details = [
179             'name'        => 'My updated API chapter',
180             'description' => 'A chapter updated via the API',
181             'tags'        => [
182                 [
183                     'name'  => 'freshtag',
184                     'value' => 'freshtagval',
185                 ],
186             ],
187             'priority'    => 15,
188             'default_template_id' => $templatePage->id,
189         ];
190
191         $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details);
192         $chapter->refresh();
193
194         $resp->assertStatus(200);
195         $resp->assertJson(array_merge($details, [
196             'id' => $chapter->id,
197             'slug' => $chapter->slug,
198             'book_id' => $chapter->book_id,
199             'description_html' => '<p>A chapter updated via the API</p>',
200         ]));
201         $this->assertActivityExists('chapter_update', $chapter);
202     }
203
204     public function test_update_endpoint_with_html()
205     {
206         $this->actingAsApiEditor();
207         $chapter = $this->entities->chapter();
208         $details = [
209             'name'             => 'My updated API chapter',
210             'description_html' => '<p>A chapter <em>updated</em> via the API</p>',
211         ];
212
213         $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details);
214         $resp->assertStatus(200);
215
216         $this->assertDatabaseHas('chapters', array_merge($details, [
217             'id' => $chapter->id, 'description' => 'A chapter updated via the API'
218         ]));
219     }
220
221     public function test_update_increments_updated_date_if_only_tags_are_sent()
222     {
223         $this->actingAsApiEditor();
224         $chapter = $this->entities->chapter();
225         DB::table('chapters')->where('id', '=', $chapter->id)->update(['updated_at' => Carbon::now()->subWeek()]);
226
227         $details = [
228             'tags' => [['name' => 'Category', 'value' => 'Testing']],
229         ];
230
231         $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details);
232         $chapter->refresh();
233         $this->assertGreaterThan(Carbon::now()->subDay()->unix(), $chapter->updated_at->unix());
234     }
235
236     public function test_update_with_book_id_moves_chapter()
237     {
238         $this->actingAsApiEditor();
239         $chapter = $this->entities->chapterHasPages();
240         $page = $chapter->pages()->first();
241         $newBook = Book::query()->where('id', '!=', $chapter->book_id)->first();
242
243         $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", ['book_id' => $newBook->id]);
244         $resp->assertOk();
245         $chapter->refresh();
246
247         $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'book_id' => $newBook->id]);
248         $this->assertDatabaseHas('pages', ['id' => $page->id, 'book_id' => $newBook->id, 'chapter_id' => $chapter->id]);
249     }
250
251     public function test_update_with_new_book_id_requires_delete_permission()
252     {
253         $editor = $this->users->editor();
254         $this->permissions->removeUserRolePermissions($editor, ['chapter-delete-all', 'chapter-delete-own']);
255         $this->actingAs($editor);
256         $chapter = $this->entities->chapterHasPages();
257         $newBook = Book::query()->where('id', '!=', $chapter->book_id)->first();
258
259         $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", ['book_id' => $newBook->id]);
260         $this->assertPermissionError($resp);
261     }
262
263     public function test_delete_endpoint()
264     {
265         $this->actingAsApiEditor();
266         $chapter = $this->entities->chapter();
267         $resp = $this->deleteJson($this->baseEndpoint . "/{$chapter->id}");
268
269         $resp->assertStatus(204);
270         $this->assertActivityExists('chapter_delete');
271     }
272
273     public function test_export_html_endpoint()
274     {
275         $this->actingAsApiEditor();
276         $chapter = $this->entities->chapter();
277
278         $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/html");
279         $resp->assertStatus(200);
280         $resp->assertSee($chapter->name);
281         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html"');
282     }
283
284     public function test_export_plain_text_endpoint()
285     {
286         $this->actingAsApiEditor();
287         $chapter = $this->entities->chapter();
288
289         $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/plaintext");
290         $resp->assertStatus(200);
291         $resp->assertSee($chapter->name);
292         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.txt"');
293     }
294
295     public function test_export_pdf_endpoint()
296     {
297         $this->actingAsApiEditor();
298         $chapter = $this->entities->chapter();
299
300         $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/pdf");
301         $resp->assertStatus(200);
302         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf"');
303     }
304
305     public function test_export_markdown_endpoint()
306     {
307         $this->actingAsApiEditor();
308         $chapter = Chapter::visible()->has('pages')->first();
309
310         $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/markdown");
311         $resp->assertStatus(200);
312         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.md"');
313         $resp->assertSee('# ' . $chapter->name);
314         $resp->assertSee('# ' . $chapter->pages()->first()->name);
315     }
316
317     public function test_cant_export_when_not_have_permission()
318     {
319         $types = ['html', 'plaintext', 'pdf', 'markdown'];
320         $this->actingAsApiEditor();
321         $this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
322
323         $chapter = Chapter::visible()->has('pages')->first();
324         foreach ($types as $type) {
325             $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/{$type}");
326             $this->assertPermissionError($resp);
327         }
328     }
329 }