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,
140 'name' => $page->ownedBy->name
145 public function test_read_endpoint_provides_rendered_html()
147 $this->actingAsApiEditor();
148 $page = Page::visible()->first();
149 $page->html = "<p>testing</p><script>alert('danger')</script><h1>Hello</h1>";
152 $resp = $this->getJson($this->baseEndpoint . "/{$page->id}");
153 $html = $resp->json('html');
154 $this->assertStringNotContainsString('script', $html);
155 $this->assertStringContainsString('Hello', $html);
156 $this->assertStringContainsString('testing', $html);
159 public function test_update_endpoint()
161 $this->actingAsApiEditor();
162 $page = Page::visible()->first();
164 'name' => 'My updated API page',
165 'html' => '<p>A page created via the API</p>',
168 'name' => 'freshtag',
169 'value' => 'freshtagval',
174 $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
177 $resp->assertStatus(200);
178 unset($details['html']);
179 $resp->assertJson(array_merge($details, [
180 'id' => $page->id, 'slug' => $page->slug, 'book_id' => $page->book_id
182 $this->assertActivityExists('page_update', $page);
185 public function test_providing_new_chapter_id_on_update_will_move_page()
187 $this->actingAsApiEditor();
188 $page = Page::visible()->first();
189 $chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first();
191 'name' => 'My updated API page',
192 'chapter_id' => $chapter->id,
193 'html' => '<p>A page created via the API</p>',
196 $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
197 $resp->assertStatus(200);
199 'chapter_id' => $chapter->id,
200 'book_id' => $chapter->book_id,
204 public function test_providing_move_via_update_requires_page_create_permission_on_new_parent()
206 $this->actingAsApiEditor();
207 $page = Page::visible()->first();
208 $chapter = Chapter::visible()->where('book_id', '!=', $page->book_id)->first();
209 $this->setEntityRestrictions($chapter, ['view'], [$this->getEditor()->roles()->first()]);
211 'name' => 'My updated API page',
212 'chapter_id' => $chapter->id,
213 'html' => '<p>A page created via the API</p>',
216 $resp = $this->putJson($this->baseEndpoint . "/{$page->id}", $details);
217 $resp->assertStatus(403);
220 public function test_delete_endpoint()
222 $this->actingAsApiEditor();
223 $page = Page::visible()->first();
224 $resp = $this->deleteJson($this->baseEndpoint . "/{$page->id}");
226 $resp->assertStatus(204);
227 $this->assertActivityExists('page_delete', $page);
230 public function test_export_html_endpoint()
232 $this->actingAsApiEditor();
233 $page = Page::visible()->first();
235 $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/html");
236 $resp->assertStatus(200);
237 $resp->assertSee($page->name);
238 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.html"');
241 public function test_export_plain_text_endpoint()
243 $this->actingAsApiEditor();
244 $page = Page::visible()->first();
246 $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/plaintext");
247 $resp->assertStatus(200);
248 $resp->assertSee($page->name);
249 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.txt"');
252 public function test_export_pdf_endpoint()
254 $this->actingAsApiEditor();
255 $page = Page::visible()->first();
257 $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/pdf");
258 $resp->assertStatus(200);
259 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf"');