5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Chapter;
8 use Illuminate\Support\Facades\DB;
11 class ChaptersApiTest extends TestCase
15 protected string $baseEndpoint = '/api/chapters';
17 public function test_index_endpoint_returns_expected_chapter()
19 $this->actingAsApiEditor();
20 $firstChapter = Chapter::query()->orderBy('id', 'asc')->first();
22 $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
23 $resp->assertJson(['data' => [
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,
35 public function test_create_endpoint()
37 $this->actingAsApiEditor();
38 $book = $this->entities->book();
40 'name' => 'My API chapter',
41 'description' => 'A chapter created via the API',
42 'book_id' => $book->id,
46 'value' => 'tagvalue',
52 $resp = $this->postJson($this->baseEndpoint, $details);
53 $resp->assertStatus(200);
54 $newItem = Chapter::query()->orderByDesc('id')->where('name', '=', $details['name'])->first();
55 $resp->assertJson(array_merge($details, [
57 'slug' => $newItem->slug,
58 'description_html' => '<p>A chapter created via the API</p>',
60 $this->assertDatabaseHas('tags', [
61 'entity_id' => $newItem->id,
62 'entity_type' => $newItem->getMorphClass(),
64 'value' => 'tagvalue',
66 $resp->assertJsonMissing(['pages' => []]);
67 $this->assertActivityExists('chapter_create', $newItem);
70 public function test_create_endpoint_with_html()
72 $this->actingAsApiEditor();
73 $book = $this->entities->book();
75 'name' => 'My API chapter',
76 'description_html' => '<p>A chapter <strong>created</strong> via the API</p>',
77 'book_id' => $book->id,
80 $resp = $this->postJson($this->baseEndpoint, $details);
81 $resp->assertStatus(200);
82 $newItem = Chapter::query()->orderByDesc('id')->where('name', '=', $details['name'])->first();
84 $expectedDetails = array_merge($details, [
86 'description' => 'A chapter created via the API',
88 $resp->assertJson($expectedDetails);
89 $this->assertDatabaseHas('chapters', $expectedDetails);
92 public function test_chapter_name_needed_to_create()
94 $this->actingAsApiEditor();
95 $book = $this->entities->book();
97 'book_id' => $book->id,
98 'description' => 'A chapter created via the API',
101 $resp = $this->postJson($this->baseEndpoint, $details);
102 $resp->assertStatus(422);
103 $resp->assertJson($this->validationResponse([
104 'name' => ['The name field is required.'],
108 public function test_chapter_book_id_needed_to_create()
110 $this->actingAsApiEditor();
112 'name' => 'My api chapter',
113 'description' => 'A chapter created via the API',
116 $resp = $this->postJson($this->baseEndpoint, $details);
117 $resp->assertStatus(422);
118 $resp->assertJson($this->validationResponse([
119 'book_id' => ['The book id field is required.'],
123 public function test_read_endpoint()
125 $this->actingAsApiEditor();
126 $chapter = $this->entities->chapter();
127 $page = $chapter->pages()->first();
129 $resp = $this->getJson($this->baseEndpoint . "/{$chapter->id}");
130 $resp->assertStatus(200);
132 'id' => $chapter->id,
133 'slug' => $chapter->slug,
134 'book_slug' => $chapter->book->slug,
136 'name' => $chapter->createdBy->name,
138 'book_id' => $chapter->book_id,
140 'name' => $chapter->createdBy->name,
143 'name' => $chapter->ownedBy->name,
148 'slug' => $page->slug,
149 'name' => $page->name,
153 $resp->assertJsonMissingPath('book');
154 $resp->assertJsonCount($chapter->pages()->count(), 'pages');
157 public function test_update_endpoint()
159 $this->actingAsApiEditor();
160 $chapter = $this->entities->chapter();
162 'name' => 'My updated API chapter',
163 'description' => 'A chapter updated via the API',
166 'name' => 'freshtag',
167 'value' => 'freshtagval',
173 $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details);
176 $resp->assertStatus(200);
177 $resp->assertJson(array_merge($details, [
178 'id' => $chapter->id,
179 'slug' => $chapter->slug,
180 'book_id' => $chapter->book_id,
181 'description_html' => '<p>A chapter updated via the API</p>',
183 $this->assertActivityExists('chapter_update', $chapter);
186 public function test_update_endpoint_with_html()
188 $this->actingAsApiEditor();
189 $chapter = $this->entities->chapter();
191 'name' => 'My updated API chapter',
192 'description_html' => '<p>A chapter <em>updated</em> via the API</p>',
195 $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details);
196 $resp->assertStatus(200);
198 $this->assertDatabaseHas('chapters', array_merge($details, [
199 'id' => $chapter->id, 'description' => 'A chapter updated via the API'
203 public function test_update_increments_updated_date_if_only_tags_are_sent()
205 $this->actingAsApiEditor();
206 $chapter = $this->entities->chapter();
207 DB::table('chapters')->where('id', '=', $chapter->id)->update(['updated_at' => Carbon::now()->subWeek()]);
210 'tags' => [['name' => 'Category', 'value' => 'Testing']],
213 $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details);
215 $this->assertGreaterThan(Carbon::now()->subDay()->unix(), $chapter->updated_at->unix());
218 public function test_update_with_book_id_moves_chapter()
220 $this->actingAsApiEditor();
221 $chapter = $this->entities->chapterHasPages();
222 $page = $chapter->pages()->first();
223 $newBook = Book::query()->where('id', '!=', $chapter->book_id)->first();
225 $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", ['book_id' => $newBook->id]);
229 $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'book_id' => $newBook->id]);
230 $this->assertDatabaseHas('pages', ['id' => $page->id, 'book_id' => $newBook->id, 'chapter_id' => $chapter->id]);
233 public function test_update_with_new_book_id_requires_delete_permission()
235 $editor = $this->users->editor();
236 $this->permissions->removeUserRolePermissions($editor, ['chapter-delete-all', 'chapter-delete-own']);
237 $this->actingAs($editor);
238 $chapter = $this->entities->chapterHasPages();
239 $newBook = Book::query()->where('id', '!=', $chapter->book_id)->first();
241 $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", ['book_id' => $newBook->id]);
242 $this->assertPermissionError($resp);
245 public function test_delete_endpoint()
247 $this->actingAsApiEditor();
248 $chapter = $this->entities->chapter();
249 $resp = $this->deleteJson($this->baseEndpoint . "/{$chapter->id}");
251 $resp->assertStatus(204);
252 $this->assertActivityExists('chapter_delete');
255 public function test_export_html_endpoint()
257 $this->actingAsApiEditor();
258 $chapter = $this->entities->chapter();
260 $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/html");
261 $resp->assertStatus(200);
262 $resp->assertSee($chapter->name);
263 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html"');
266 public function test_export_plain_text_endpoint()
268 $this->actingAsApiEditor();
269 $chapter = $this->entities->chapter();
271 $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/plaintext");
272 $resp->assertStatus(200);
273 $resp->assertSee($chapter->name);
274 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.txt"');
277 public function test_export_pdf_endpoint()
279 $this->actingAsApiEditor();
280 $chapter = $this->entities->chapter();
282 $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/pdf");
283 $resp->assertStatus(200);
284 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf"');
287 public function test_export_markdown_endpoint()
289 $this->actingAsApiEditor();
290 $chapter = Chapter::visible()->has('pages')->first();
292 $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/markdown");
293 $resp->assertStatus(200);
294 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.md"');
295 $resp->assertSee('# ' . $chapter->name);
296 $resp->assertSee('# ' . $chapter->pages()->first()->name);
299 public function test_cant_export_when_not_have_permission()
301 $types = ['html', 'plaintext', 'pdf', 'markdown'];
302 $this->actingAsApiEditor();
303 $this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
305 $chapter = Chapter::visible()->has('pages')->first();
306 foreach ($types as $type) {
307 $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/{$type}");
308 $this->assertPermissionError($resp);