]> BookStack Code Mirror - bookstack/blob - tests/Api/PagesApiTest.php
Added role permissions for exporting content
[bookstack] / tests / Api / PagesApiTest.php
1 <?php
2
3 namespace Tests\Api;
4
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Chapter;
7 use BookStack\Entities\Models\Page;
8 use Tests\TestCase;
9
10 class PagesApiTest extends TestCase
11 {
12     use TestsApi;
13
14     protected $baseEndpoint = '/api/pages';
15
16     public function test_index_endpoint_returns_expected_page()
17     {
18         $this->actingAsApiEditor();
19         $firstPage = Page::query()->orderBy('id', 'asc')->first();
20
21         $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
22         $resp->assertJson(['data' => [
23             [
24                 'id'       => $firstPage->id,
25                 'name'     => $firstPage->name,
26                 'slug'     => $firstPage->slug,
27                 'book_id'  => $firstPage->book->id,
28                 'priority' => $firstPage->priority,
29             ],
30         ]]);
31     }
32
33     public function test_create_endpoint()
34     {
35         $this->actingAsApiEditor();
36         $book = Book::query()->first();
37         $details = [
38             'name'    => 'My API page',
39             'book_id' => $book->id,
40             'html'    => '<p>My new page content</p>',
41             'tags'    => [
42                 [
43                     'name'  => 'tagname',
44                     'value' => 'tagvalue',
45                 ],
46             ],
47         ];
48
49         $resp = $this->postJson($this->baseEndpoint, $details);
50         unset($details['html']);
51         $resp->assertStatus(200);
52         $newItem = Page::query()->orderByDesc('id')->where('name', '=', $details['name'])->first();
53         $resp->assertJson(array_merge($details, ['id' => $newItem->id, 'slug' => $newItem->slug]));
54         $this->assertDatabaseHas('tags', [
55             'entity_id'   => $newItem->id,
56             'entity_type' => $newItem->getMorphClass(),
57             'name'        => 'tagname',
58             'value'       => 'tagvalue',
59         ]);
60         $resp->assertSeeText('My new page content');
61         $resp->assertJsonMissing(['book' => []]);
62         $this->assertActivityExists('page_create', $newItem);
63     }
64
65     public function test_page_name_needed_to_create()
66     {
67         $this->actingAsApiEditor();
68         $book = Book::query()->first();
69         $details = [
70             'book_id' => $book->id,
71             'html'    => '<p>A page created via the API</p>',
72         ];
73
74         $resp = $this->postJson($this->baseEndpoint, $details);
75         $resp->assertStatus(422);
76         $resp->assertJson($this->validationResponse([
77             'name' => ['The name field is required.'],
78         ]));
79     }
80
81     public function test_book_id_or_chapter_id_needed_to_create()
82     {
83         $this->actingAsApiEditor();
84         $details = [
85             'name' => 'My api page',
86             'html' => '<p>A page created via the API</p>',
87         ];
88
89         $resp = $this->postJson($this->baseEndpoint, $details);
90         $resp->assertStatus(422);
91         $resp->assertJson($this->validationResponse([
92             'book_id'    => ['The book id field is required when chapter id is not present.'],
93             'chapter_id' => ['The chapter id field is required when book id is not present.'],
94         ]));
95
96         $chapter = Chapter::visible()->first();
97         $resp = $this->postJson($this->baseEndpoint, array_merge($details, ['chapter_id' => $chapter->id]));
98         $resp->assertStatus(200);
99
100         $book = Book::visible()->first();
101         $resp = $this->postJson($this->baseEndpoint, array_merge($details, ['book_id' => $book->id]));
102         $resp->assertStatus(200);
103     }
104
105     public function test_markdown_can_be_provided_for_create()
106     {
107         $this->actingAsApiEditor();
108         $book = Book::visible()->first();
109         $details = [
110             'book_id'  => $book->id,
111             'name'     => 'My api page',
112             'markdown' => "# A new API page \n[link](https://example.com)",
113         ];
114
115         $resp = $this->postJson($this->baseEndpoint, $details);
116         $resp->assertJson(['markdown' => $details['markdown']]);
117
118         $respHtml = $resp->json('html');
119         $this->assertStringContainsString('new API page</h1>', $respHtml);
120         $this->assertStringContainsString('link</a>', $respHtml);
121         $this->assertStringContainsString('href="https://example.com"', $respHtml);
122     }
123
124     public function test_read_endpoint()
125     {
126         $this->actingAsApiEditor();
127         $page = Page::visible()->first();
128
129         $resp = $this->getJson($this->baseEndpoint . "/{$page->id}");
130         $resp->assertStatus(200);
131         $resp->assertJson([
132             'id'         => $page->id,
133             'slug'       => $page->slug,
134             'created_by' => [
135                 'name' => $page->createdBy->name,
136             ],
137             'book_id'    => $page->book_id,
138             'updated_by' => [
139                 'name' => $page->createdBy->name,
140             ],
141             'owned_by' => [
142                 'name' => $page->ownedBy->name,
143             ],
144         ]);
145     }
146
147     public function test_read_endpoint_provides_rendered_html()
148     {
149         $this->actingAsApiEditor();
150         $page = Page::visible()->first();
151         $page->html = "<p>testing</p><script>alert('danger')</script><h1>Hello</h1>";
152         $page->save();
153
154         $resp = $this->getJson($this->baseEndpoint . "/{$page->id}");
155         $html = $resp->json('html');
156         $this->assertStringNotContainsString('script', $html);
157         $this->assertStringContainsString('Hello', $html);
158         $this->assertStringContainsString('testing', $html);
159     }
160
161     public function test_update_endpoint()
162     {
163         $this->actingAsApiEditor();
164         $page = Page::visible()->first();
165         $details = [
166             'name' => 'My updated API page',
167             'html' => '<p>A page created via the API</p>',
168             'tags' => [
169                 [
170                     'name'  => 'freshtag',
171                     'value' => 'freshtagval',
172                 ],
173             ],
174         ];
175
176         $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
177         $page->refresh();
178
179         $resp->assertStatus(200);
180         unset($details['html']);
181         $resp->assertJson(array_merge($details, [
182             'id' => $page->id, 'slug' => $page->slug, 'book_id' => $page->book_id,
183         ]));
184         $this->assertActivityExists('page_update', $page);
185     }
186
187     public function test_providing_new_chapter_id_on_update_will_move_page()
188     {
189         $this->actingAsApiEditor();
190         $page = Page::visible()->first();
191         $chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first();
192         $details = [
193             'name'       => 'My updated API page',
194             'chapter_id' => $chapter->id,
195             'html'       => '<p>A page created via the API</p>',
196         ];
197
198         $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
199         $resp->assertStatus(200);
200         $resp->assertJson([
201             'chapter_id' => $chapter->id,
202             'book_id'    => $chapter->book_id,
203         ]);
204     }
205
206     public function test_providing_move_via_update_requires_page_create_permission_on_new_parent()
207     {
208         $this->actingAsApiEditor();
209         $page = Page::visible()->first();
210         $chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first();
211         $this->setEntityRestrictions($chapter, ['view'], [$this->getEditor()->roles()->first()]);
212         $details = [
213             'name'       => 'My updated API page',
214             'chapter_id' => $chapter->id,
215             'html'       => '<p>A page created via the API</p>',
216         ];
217
218         $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
219         $resp->assertStatus(403);
220     }
221
222     public function test_update_endpoint_does_not_wipe_content_if_no_html_or_md_provided()
223     {
224         $this->actingAsApiEditor();
225         $page = Page::visible()->first();
226         $originalContent = $page->html;
227         $details = [
228             'name' => 'My updated API page',
229             'tags' => [
230                 [
231                     'name'  => 'freshtag',
232                     'value' => 'freshtagval',
233                 ],
234             ],
235         ];
236
237         $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
238         $page->refresh();
239
240         $this->assertEquals($originalContent, $page->html);
241     }
242
243     public function test_delete_endpoint()
244     {
245         $this->actingAsApiEditor();
246         $page = Page::visible()->first();
247         $resp = $this->deleteJson($this->baseEndpoint . "/{$page->id}");
248
249         $resp->assertStatus(204);
250         $this->assertActivityExists('page_delete', $page);
251     }
252
253     public function test_export_html_endpoint()
254     {
255         $this->actingAsApiEditor();
256         $page = Page::visible()->first();
257
258         $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/html");
259         $resp->assertStatus(200);
260         $resp->assertSee($page->name);
261         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.html"');
262     }
263
264     public function test_export_plain_text_endpoint()
265     {
266         $this->actingAsApiEditor();
267         $page = Page::visible()->first();
268
269         $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/plaintext");
270         $resp->assertStatus(200);
271         $resp->assertSee($page->name);
272         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.txt"');
273     }
274
275     public function test_export_pdf_endpoint()
276     {
277         $this->actingAsApiEditor();
278         $page = Page::visible()->first();
279
280         $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/pdf");
281         $resp->assertStatus(200);
282         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf"');
283     }
284
285     public function test_export_markdown_endpoint()
286     {
287         $this->actingAsApiEditor();
288         $page = Page::visible()->first();
289
290         $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/markdown");
291         $resp->assertStatus(200);
292         $resp->assertSee('# ' . $page->name);
293         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.md"');
294     }
295
296     public function test_cant_export_when_not_have_permission()
297     {
298         $types = ['html', 'plaintext', 'pdf', 'markdown'];
299         $this->actingAsApiEditor();
300         $this->removePermissionFromUser($this->getEditor(), 'content-export');
301
302         $page = Page::visible()->first();
303         foreach ($types as $type) {
304             $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/{$type}");
305             $this->assertPermissionError($resp);
306         }
307     }
308 }