]> BookStack Code Mirror - bookstack/blob - tests/Api/PagesApiTest.php
add tests for priority
[bookstack] / tests / Api / PagesApiTest.php
1 <?php
2
3 namespace Tests\Api;
4
5 use BookStack\Entities\Models\Chapter;
6 use BookStack\Entities\Models\Page;
7 use Carbon\Carbon;
8 use Illuminate\Support\Facades\DB;
9 use Tests\TestCase;
10
11 class PagesApiTest extends TestCase
12 {
13     use TestsApi;
14
15     protected string $baseEndpoint = '/api/pages';
16
17     public function test_index_endpoint_returns_expected_page()
18     {
19         $this->actingAsApiEditor();
20         $firstPage = Page::query()->orderBy('id', 'asc')->first();
21
22         $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
23         $resp->assertJson(['data' => [
24             [
25                 'id'       => $firstPage->id,
26                 'name'     => $firstPage->name,
27                 'slug'     => $firstPage->slug,
28                 'book_id'  => $firstPage->book->id,
29                 'priority' => $firstPage->priority,
30             ],
31         ]]);
32     }
33
34     public function test_create_endpoint()
35     {
36         $this->actingAsApiEditor();
37         $book = $this->entities->book();
38         $details = [
39             'name'    => 'My API page',
40             'book_id' => $book->id,
41             'html'    => '<p>My new page content</p>',
42             'tags'    => [
43                 [
44                     'name'  => 'tagname',
45                     'value' => 'tagvalue',
46                 ],
47             ],
48         ];
49
50         $resp = $this->postJson($this->baseEndpoint, $details);
51         unset($details['html']);
52         $resp->assertStatus(200);
53         $newItem = Page::query()->orderByDesc('id')->where('name', '=', $details['name'])->first();
54         $resp->assertJson(array_merge($details, ['id' => $newItem->id, 'slug' => $newItem->slug]));
55         $this->assertDatabaseHas('tags', [
56             'entity_id'   => $newItem->id,
57             'entity_type' => $newItem->getMorphClass(),
58             'name'        => 'tagname',
59             'value'       => 'tagvalue',
60         ]);
61         $resp->assertSeeText('My new page content');
62         $resp->assertJsonMissing(['book' => []]);
63         $this->assertActivityExists('page_create', $newItem);
64     }
65
66     public function test_create_applies_correct_priority()
67     {
68         $this->actingAsApiEditor();
69         $book = $this->entities->book();
70         $details = [
71             'name'    => 'My API page',
72             'book_id' => $book->id,
73             'html'    => '<p>My new page content</p>',
74             'tags'    => [
75                 [
76                     'name'  => 'tagname',
77                     'value' => 'tagvalue',
78                 ],
79             ],
80             'priority' => 15,
81         ];
82
83         $resp = $this->postJson($this->baseEndpoint, $details);
84         unset($details['html']);
85         $resp->assertStatus(200);
86         $newItem = Page::query()->orderByDesc('id')->where('name', '=', $details['name'])->first();
87         $resp->assertJson(array_merge($details, ['id' => $newItem->id, 'slug' => $newItem->slug]));
88         $this->assertDatabaseHas('tags', [
89             'entity_id'   => $newItem->id,
90             'entity_type' => $newItem->getMorphClass(),
91             'name'        => 'tagname',
92             'value'       => 'tagvalue',
93         ]);
94         $resp->assertSeeText('My new page content');
95         $resp->assertJsonMissing(['book' => []]);
96         $this->assertActivityExists('page_create', $newItem);
97     }
98
99     public function test_page_name_needed_to_create()
100     {
101         $this->actingAsApiEditor();
102         $book = $this->entities->book();
103         $details = [
104             'book_id' => $book->id,
105             'html'    => '<p>A page created via the API</p>',
106         ];
107
108         $resp = $this->postJson($this->baseEndpoint, $details);
109         $resp->assertStatus(422);
110         $resp->assertJson($this->validationResponse([
111             'name' => ['The name field is required.'],
112         ]));
113     }
114
115     public function test_book_id_or_chapter_id_needed_to_create()
116     {
117         $this->actingAsApiEditor();
118         $details = [
119             'name' => 'My api page',
120             'html' => '<p>A page created via the API</p>',
121         ];
122
123         $resp = $this->postJson($this->baseEndpoint, $details);
124         $resp->assertStatus(422);
125         $resp->assertJson($this->validationResponse([
126             'book_id'    => ['The book id field is required when chapter id is not present.'],
127             'chapter_id' => ['The chapter id field is required when book id is not present.'],
128         ]));
129
130         $chapter = $this->entities->chapter();
131         $resp = $this->postJson($this->baseEndpoint, array_merge($details, ['chapter_id' => $chapter->id]));
132         $resp->assertStatus(200);
133
134         $book = $this->entities->book();
135         $resp = $this->postJson($this->baseEndpoint, array_merge($details, ['book_id' => $book->id]));
136         $resp->assertStatus(200);
137     }
138
139     public function test_markdown_can_be_provided_for_create()
140     {
141         $this->actingAsApiEditor();
142         $book = $this->entities->book();
143         $details = [
144             'book_id'  => $book->id,
145             'name'     => 'My api page',
146             'markdown' => "# A new API page \n[link](https://example.com)",
147         ];
148
149         $resp = $this->postJson($this->baseEndpoint, $details);
150         $resp->assertJson(['markdown' => $details['markdown']]);
151
152         $respHtml = $resp->json('html');
153         $this->assertStringContainsString('new API page</h1>', $respHtml);
154         $this->assertStringContainsString('link</a>', $respHtml);
155         $this->assertStringContainsString('href="https://example.com"', $respHtml);
156     }
157
158     public function test_read_endpoint()
159     {
160         $this->actingAsApiEditor();
161         $page = $this->entities->page();
162
163         $resp = $this->getJson($this->baseEndpoint . "/{$page->id}");
164         $resp->assertStatus(200);
165         $resp->assertJson([
166             'id'         => $page->id,
167             'slug'       => $page->slug,
168             'created_by' => [
169                 'name' => $page->createdBy->name,
170             ],
171             'book_id'    => $page->book_id,
172             'updated_by' => [
173                 'name' => $page->createdBy->name,
174             ],
175             'owned_by' => [
176                 'name' => $page->ownedBy->name,
177             ],
178         ]);
179     }
180
181     public function test_read_endpoint_provides_rendered_html()
182     {
183         $this->actingAsApiEditor();
184         $page = $this->entities->page();
185         $page->html = "<p>testing</p><script>alert('danger')</script><h1>Hello</h1>";
186         $page->save();
187
188         $resp = $this->getJson($this->baseEndpoint . "/{$page->id}");
189         $html = $resp->json('html');
190         $this->assertStringNotContainsString('script', $html);
191         $this->assertStringContainsString('Hello', $html);
192         $this->assertStringContainsString('testing', $html);
193     }
194
195     public function test_read_endpoint_provides_raw_html()
196     {
197         $html = "<p>testing</p><script>alert('danger')</script><h1>Hello</h1>";
198
199         $this->actingAsApiEditor();
200         $page = $this->entities->page();
201         $page->html = $html;
202         $page->save();
203
204         $resp = $this->getJson($this->baseEndpoint . "/{$page->id}");
205         $this->assertEquals($html, $resp->json('raw_html'));
206         $this->assertNotEquals($html, $resp->json('html'));
207     }
208
209     public function test_read_endpoint_returns_not_found()
210     {
211         $this->actingAsApiEditor();
212         // get an id that is not used
213         $id = Page::orderBy('id', 'desc')->first()->id + 1;
214         $this->assertNull(Page::find($id));
215
216         $resp = $this->getJson($this->baseEndpoint . "/$id");
217
218         $resp->assertNotFound();
219         $this->assertNull($resp->json('id'));
220         $resp->assertJsonIsObject('error');
221         $resp->assertJsonStructure([
222             'error' => [
223                 'code',
224                 'message',
225             ],
226         ]);
227         $this->assertSame(404, $resp->json('error')['code']);
228     }
229
230     public function test_update_endpoint()
231     {
232         $this->actingAsApiEditor();
233         $page = $this->entities->page();
234         $details = [
235             'name' => 'My updated API page',
236             'html' => '<p>A page created via the API</p>',
237             'tags' => [
238                 [
239                     'name'  => 'freshtag',
240                     'value' => 'freshtagval',
241                 ],
242             ],
243             'priority' => 15,
244         ];
245
246         $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
247         $page->refresh();
248
249         $resp->assertStatus(200);
250         unset($details['html']);
251         $resp->assertJson(array_merge($details, [
252             'id' => $page->id, 'slug' => $page->slug, 'book_id' => $page->book_id,
253         ]));
254         $this->assertActivityExists('page_update', $page);
255     }
256
257     public function test_providing_new_chapter_id_on_update_will_move_page()
258     {
259         $this->actingAsApiEditor();
260         $page = $this->entities->page();
261         $chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first();
262         $details = [
263             'name'       => 'My updated API page',
264             'chapter_id' => $chapter->id,
265             'html'       => '<p>A page created via the API</p>',
266         ];
267
268         $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
269         $resp->assertStatus(200);
270         $resp->assertJson([
271             'chapter_id' => $chapter->id,
272             'book_id'    => $chapter->book_id,
273         ]);
274     }
275
276     public function test_providing_move_via_update_requires_page_create_permission_on_new_parent()
277     {
278         $this->actingAsApiEditor();
279         $page = $this->entities->page();
280         $chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first();
281         $this->permissions->setEntityPermissions($chapter, ['view'], [$this->users->editor()->roles()->first()]);
282         $details = [
283             'name'       => 'My updated API page',
284             'chapter_id' => $chapter->id,
285             'html'       => '<p>A page created via the API</p>',
286         ];
287
288         $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
289         $resp->assertStatus(403);
290     }
291
292     public function test_update_endpoint_does_not_wipe_content_if_no_html_or_md_provided()
293     {
294         $this->actingAsApiEditor();
295         $page = $this->entities->page();
296         $originalContent = $page->html;
297         $details = [
298             'name' => 'My updated API page',
299             'tags' => [
300                 [
301                     'name'  => 'freshtag',
302                     'value' => 'freshtagval',
303                 ],
304             ],
305         ];
306
307         $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
308         $page->refresh();
309
310         $this->assertEquals($originalContent, $page->html);
311     }
312
313     public function test_update_increments_updated_date_if_only_tags_are_sent()
314     {
315         $this->actingAsApiEditor();
316         $page = $this->entities->page();
317         DB::table('pages')->where('id', '=', $page->id)->update(['updated_at' => Carbon::now()->subWeek()]);
318
319         $details = [
320             'tags' => [['name' => 'Category', 'value' => 'Testing']],
321         ];
322
323         $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
324         $resp->assertOk();
325
326         $page->refresh();
327         $this->assertGreaterThan(Carbon::now()->subDay()->unix(), $page->updated_at->unix());
328     }
329
330     public function test_delete_endpoint()
331     {
332         $this->actingAsApiEditor();
333         $page = $this->entities->page();
334         $resp = $this->deleteJson($this->baseEndpoint . "/{$page->id}");
335
336         $resp->assertStatus(204);
337         $this->assertActivityExists('page_delete', $page);
338     }
339
340     public function test_export_html_endpoint()
341     {
342         $this->actingAsApiEditor();
343         $page = $this->entities->page();
344
345         $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/html");
346         $resp->assertStatus(200);
347         $resp->assertSee($page->name);
348         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.html"');
349     }
350
351     public function test_export_plain_text_endpoint()
352     {
353         $this->actingAsApiEditor();
354         $page = $this->entities->page();
355
356         $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/plaintext");
357         $resp->assertStatus(200);
358         $resp->assertSee($page->name);
359         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.txt"');
360     }
361
362     public function test_export_pdf_endpoint()
363     {
364         $this->actingAsApiEditor();
365         $page = $this->entities->page();
366
367         $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/pdf");
368         $resp->assertStatus(200);
369         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf"');
370     }
371
372     public function test_export_markdown_endpoint()
373     {
374         $this->actingAsApiEditor();
375         $page = $this->entities->page();
376
377         $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/markdown");
378         $resp->assertStatus(200);
379         $resp->assertSee('# ' . $page->name);
380         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.md"');
381     }
382
383     public function test_cant_export_when_not_have_permission()
384     {
385         $types = ['html', 'plaintext', 'pdf', 'markdown'];
386         $this->actingAsApiEditor();
387         $this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
388
389         $page = $this->entities->page();
390         foreach ($types as $type) {
391             $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/{$type}");
392             $this->assertPermissionError($resp);
393         }
394     }
395 }