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