5 use BookStack\Entities\Models\Book;
7 use Illuminate\Support\Facades\DB;
10 class BooksApiTest extends TestCase
14 protected string $baseEndpoint = '/api/books';
16 public function test_index_endpoint_returns_expected_book()
18 $this->actingAsApiEditor();
19 $firstBook = Book::query()->orderBy('id', 'asc')->first();
21 $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
22 $resp->assertJson(['data' => [
24 'id' => $firstBook->id,
25 'name' => $firstBook->name,
26 'slug' => $firstBook->slug,
31 public function test_create_endpoint()
33 $this->actingAsApiEditor();
34 $templatePage = $this->entities->templatePage();
36 'name' => 'My API book',
37 'description' => 'A book created via the API',
38 'default_template_id' => $templatePage->id,
41 $resp = $this->postJson($this->baseEndpoint, $details);
42 $resp->assertStatus(200);
44 $newItem = Book::query()->orderByDesc('id')->where('name', '=', $details['name'])->first();
45 $resp->assertJson(array_merge($details, ['id' => $newItem->id, 'slug' => $newItem->slug]));
46 $this->assertActivityExists('book_create', $newItem);
49 public function test_book_name_needed_to_create()
51 $this->actingAsApiEditor();
53 'description' => 'A book created via the API',
56 $resp = $this->postJson($this->baseEndpoint, $details);
57 $resp->assertStatus(422);
60 'message' => 'The given data was invalid.',
62 'name' => ['The name field is required.'],
69 public function test_read_endpoint()
71 $this->actingAsApiEditor();
72 $book = $this->entities->book();
74 $resp = $this->getJson($this->baseEndpoint . "/{$book->id}");
76 $resp->assertStatus(200);
79 'slug' => $book->slug,
81 'name' => $book->createdBy->name,
84 'name' => $book->createdBy->name,
87 'name' => $book->ownedBy->name,
89 'default_template_id' => null,
93 public function test_read_endpoint_includes_chapter_and_page_contents()
95 $this->actingAsApiEditor();
96 $book = $this->entities->bookHasChaptersAndPages();
97 $chapter = $book->chapters()->first();
98 $chapterPage = $chapter->pages()->first();
100 $resp = $this->getJson($this->baseEndpoint . "/{$book->id}");
102 $directChildCount = $book->directPages()->count() + $book->chapters()->count();
103 $resp->assertStatus(200);
104 $resp->assertJsonCount($directChildCount, 'contents');
109 'id' => $chapter->id,
110 'name' => $chapter->name,
111 'slug' => $chapter->slug,
114 'id' => $chapterPage->id,
115 'name' => $chapterPage->name,
116 'slug' => $chapterPage->slug,
124 public function test_update_endpoint()
126 $this->actingAsApiEditor();
127 $book = $this->entities->book();
128 $templatePage = $this->entities->templatePage();
130 'name' => 'My updated API book',
131 'description' => 'A book created via the API',
132 'default_template_id' => $templatePage->id,
135 $resp = $this->putJson($this->baseEndpoint . "/{$book->id}", $details);
138 $resp->assertStatus(200);
139 $resp->assertJson(array_merge($details, ['id' => $book->id, 'slug' => $book->slug]));
140 $this->assertActivityExists('book_update', $book);
143 public function test_update_increments_updated_date_if_only_tags_are_sent()
145 $this->actingAsApiEditor();
146 $book = $this->entities->book();
147 DB::table('books')->where('id', '=', $book->id)->update(['updated_at' => Carbon::now()->subWeek()]);
150 'tags' => [['name' => 'Category', 'value' => 'Testing']],
153 $this->putJson($this->baseEndpoint . "/{$book->id}", $details);
155 $this->assertGreaterThan(Carbon::now()->subDay()->unix(), $book->updated_at->unix());
158 public function test_update_cover_image_control()
160 $this->actingAsApiEditor();
161 /** @var Book $book */
162 $book = $this->entities->book();
163 $this->assertNull($book->cover);
164 $file = $this->files->uploadedImage('image.png');
166 // Ensure cover image can be set via API
167 $resp = $this->call('PUT', $this->baseEndpoint . "/{$book->id}", [
168 'name' => 'My updated API book with image',
169 ], [], ['image' => $file]);
172 $resp->assertStatus(200);
173 $this->assertNotNull($book->cover);
175 // Ensure further updates without image do not clear cover image
176 $resp = $this->put($this->baseEndpoint . "/{$book->id}", [
177 'name' => 'My updated book again',
181 $resp->assertStatus(200);
182 $this->assertNotNull($book->cover);
184 // Ensure update with null image property clears image
185 $resp = $this->put($this->baseEndpoint . "/{$book->id}", [
190 $resp->assertStatus(200);
191 $this->assertNull($book->cover);
194 public function test_delete_endpoint()
196 $this->actingAsApiEditor();
197 $book = $this->entities->book();
198 $resp = $this->deleteJson($this->baseEndpoint . "/{$book->id}");
200 $resp->assertStatus(204);
201 $this->assertActivityExists('book_delete');
204 public function test_export_html_endpoint()
206 $this->actingAsApiEditor();
207 $book = $this->entities->book();
209 $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/html");
210 $resp->assertStatus(200);
211 $resp->assertSee($book->name);
212 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html"');
215 public function test_export_plain_text_endpoint()
217 $this->actingAsApiEditor();
218 $book = $this->entities->book();
220 $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/plaintext");
221 $resp->assertStatus(200);
222 $resp->assertSee($book->name);
223 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.txt"');
226 public function test_export_pdf_endpoint()
228 $this->actingAsApiEditor();
229 $book = $this->entities->book();
231 $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/pdf");
232 $resp->assertStatus(200);
233 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.pdf"');
236 public function test_export_markdown_endpoint()
238 $this->actingAsApiEditor();
239 $book = Book::visible()->has('pages')->has('chapters')->first();
241 $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/markdown");
242 $resp->assertStatus(200);
243 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.md"');
244 $resp->assertSee('# ' . $book->name);
245 $resp->assertSee('# ' . $book->pages()->first()->name);
246 $resp->assertSee('# ' . $book->chapters()->first()->name);
249 public function test_cant_export_when_not_have_permission()
251 $types = ['html', 'plaintext', 'pdf', 'markdown'];
252 $this->actingAsApiEditor();
253 $this->permissions->removeUserRolePermissions($this->users->editor(), ['content-export']);
255 $book = $this->entities->book();
256 foreach ($types as $type) {
257 $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/{$type}");
258 $this->assertPermissionError($resp);