1 <?php namespace Tests\Api;
3 use BookStack\Entities\Models\Book;
4 use BookStack\Entities\Models\Chapter;
5 use BookStack\Entities\Models\Page;
8 class PagesApiTest extends TestCase
12 protected $baseEndpoint = '/api/pages';
14 public function test_index_endpoint_returns_expected_page()
16 $this->actingAsApiEditor();
17 $firstPage = Page::query()->orderBy('id', 'asc')->first();
19 $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
20 $resp->assertJson(['data' => [
22 'id' => $firstPage->id,
23 'name' => $firstPage->name,
24 'slug' => $firstPage->slug,
25 'book_id' => $firstPage->book->id,
26 'priority' => $firstPage->priority,
31 public function test_create_endpoint()
33 $this->actingAsApiEditor();
34 $book = Book::query()->first();
36 'name' => 'My API page',
37 'book_id' => $book->id,
38 'html' => '<p>My new page content</p>',
42 'value' => 'tagvalue',
47 $resp = $this->postJson($this->baseEndpoint, $details);
48 unset($details['html']);
49 $resp->assertStatus(200);
50 $newItem = Page::query()->orderByDesc('id')->where('name', '=', $details['name'])->first();
51 $resp->assertJson(array_merge($details, ['id' => $newItem->id, 'slug' => $newItem->slug]));
52 $this->assertDatabaseHas('tags', [
53 'entity_id' => $newItem->id,
54 'entity_type' => $newItem->getMorphClass(),
56 'value' => 'tagvalue',
58 $resp->assertSeeText('My new page content');
59 $resp->assertJsonMissing(['book' => []]);
60 $this->assertActivityExists('page_create', $newItem);
63 public function test_page_name_needed_to_create()
65 $this->actingAsApiEditor();
66 $book = Book::query()->first();
68 'book_id' => $book->id,
69 'html' => '<p>A page created via the API</p>',
72 $resp = $this->postJson($this->baseEndpoint, $details);
73 $resp->assertStatus(422);
74 $resp->assertJson($this->validationResponse([
75 "name" => ["The name field is required."]
79 public function test_book_id_or_chapter_id_needed_to_create()
81 $this->actingAsApiEditor();
83 'name' => 'My api page',
84 'html' => '<p>A page created via the API</p>',
87 $resp = $this->postJson($this->baseEndpoint, $details);
88 $resp->assertStatus(422);
89 $resp->assertJson($this->validationResponse([
90 "book_id" => ["The book id field is required when chapter id is not present."],
91 "chapter_id" => ["The chapter id field is required when book id is not present."]
94 $chapter = Chapter::visible()->first();
95 $resp = $this->postJson($this->baseEndpoint, array_merge($details, ['chapter_id' => $chapter->id]));
96 $resp->assertStatus(200);
98 $book = Book::visible()->first();
99 $resp = $this->postJson($this->baseEndpoint, array_merge($details, ['book_id' => $book->id]));
100 $resp->assertStatus(200);
103 public function test_markdown_can_be_provided_for_create()
105 $this->actingAsApiEditor();
106 $book = Book::visible()->first();
108 'book_id' => $book->id,
109 'name' => 'My api page',
110 'markdown' => "# A new API page \n[link](https://example.com)",
113 $resp = $this->postJson($this->baseEndpoint, $details);
114 $resp->assertJson(['markdown' => $details['markdown']]);
116 $respHtml = $resp->json('html');
117 $this->assertStringContainsString('new API page</h1>', $respHtml);
118 $this->assertStringContainsString('link</a>', $respHtml);
119 $this->assertStringContainsString('href="https://example.com"', $respHtml);
122 public function test_read_endpoint()
124 $this->actingAsApiEditor();
125 $page = Page::visible()->first();
127 $resp = $this->getJson($this->baseEndpoint . "/{$page->id}");
128 $resp->assertStatus(200);
131 'slug' => $page->slug,
133 'name' => $page->createdBy->name,
135 'book_id' => $page->book_id,
137 'name' => $page->createdBy->name,
142 public function test_read_endpoint_provides_rendered_html()
144 $this->actingAsApiEditor();
145 $page = Page::visible()->first();
146 $page->html = "<p>testing</p><script>alert('danger')</script><h1>Hello</h1>";
149 $resp = $this->getJson($this->baseEndpoint . "/{$page->id}");
150 $html = $resp->json('html');
151 $this->assertStringNotContainsString('script', $html);
152 $this->assertStringContainsString('Hello', $html);
153 $this->assertStringContainsString('testing', $html);
156 public function test_update_endpoint()
158 $this->actingAsApiEditor();
159 $page = Page::visible()->first();
161 'name' => 'My updated API page',
162 'html' => '<p>A page created via the API</p>',
165 'name' => 'freshtag',
166 'value' => 'freshtagval',
171 $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
174 $resp->assertStatus(200);
175 unset($details['html']);
176 $resp->assertJson(array_merge($details, [
177 'id' => $page->id, 'slug' => $page->slug, 'book_id' => $page->book_id
179 $this->assertActivityExists('page_update', $page);
182 public function test_providing_new_chapter_id_on_update_will_move_page()
184 $this->actingAsApiEditor();
185 $page = Page::visible()->first();
186 $chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first();
188 'name' => 'My updated API page',
189 'chapter_id' => $chapter->id,
190 'html' => '<p>A page created via the API</p>',
193 $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
194 $resp->assertStatus(200);
196 'chapter_id' => $chapter->id,
197 'book_id' => $chapter->book_id,
201 public function test_providing_move_via_update_requires_page_create_permission_on_new_parent()
203 $this->actingAsApiEditor();
204 $page = Page::visible()->first();
205 $chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first();
206 $this->setEntityRestrictions($chapter, ['view'], [$this->getEditor()->roles()->first()]);
208 'name' => 'My updated API page',
209 'chapter_id' => $chapter->id,
210 'html' => '<p>A page created via the API</p>',
213 $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
214 $resp->assertStatus(403);
217 public function test_delete_endpoint()
219 $this->actingAsApiEditor();
220 $page = Page::visible()->first();
221 $resp = $this->deleteJson($this->baseEndpoint . "/{$page->id}");
223 $resp->assertStatus(204);
224 $this->assertActivityExists('page_delete', $page);
227 public function test_export_html_endpoint()
229 $this->actingAsApiEditor();
230 $page = Page::visible()->first();
232 $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/html");
233 $resp->assertStatus(200);
234 $resp->assertSee($page->name);
235 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.html"');
238 public function test_export_plain_text_endpoint()
240 $this->actingAsApiEditor();
241 $page = Page::visible()->first();
243 $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/plaintext");
244 $resp->assertStatus(200);
245 $resp->assertSee($page->name);
246 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.txt"');
249 public function test_export_pdf_endpoint()
251 $this->actingAsApiEditor();
252 $page = Page::visible()->first();
254 $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/pdf");
255 $resp->assertStatus(200);
256 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf"');