5 use BookStack\Entities\Models\Book;
7 use Illuminate\Support\Facades\DB;
9 use Tests\Uploads\UsesImages;
11 class BooksApiTest extends TestCase
16 protected string $baseEndpoint = '/api/books';
18 public function test_index_endpoint_returns_expected_book()
20 $this->actingAsApiEditor();
21 $firstBook = Book::query()->orderBy('id', 'asc')->first();
23 $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
24 $resp->assertJson(['data' => [
26 'id' => $firstBook->id,
27 'name' => $firstBook->name,
28 'slug' => $firstBook->slug,
33 public function test_create_endpoint()
35 $this->actingAsApiEditor();
37 'name' => 'My API book',
38 'description' => 'A book created via the API',
41 $resp = $this->postJson($this->baseEndpoint, $details);
42 $resp->assertStatus(200);
43 $newItem = Book::query()->orderByDesc('id')->where('name', '=', $details['name'])->first();
44 $resp->assertJson(array_merge($details, ['id' => $newItem->id, 'slug' => $newItem->slug]));
45 $this->assertActivityExists('book_create', $newItem);
48 public function test_book_name_needed_to_create()
50 $this->actingAsApiEditor();
52 'description' => 'A book created via the API',
55 $resp = $this->postJson($this->baseEndpoint, $details);
56 $resp->assertStatus(422);
59 'message' => 'The given data was invalid.',
61 'name' => ['The name field is required.'],
68 public function test_read_endpoint()
70 $this->actingAsApiEditor();
71 $book = Book::visible()->first();
73 $resp = $this->getJson($this->baseEndpoint . "/{$book->id}");
75 $resp->assertStatus(200);
78 'slug' => $book->slug,
80 'name' => $book->createdBy->name,
83 'name' => $book->createdBy->name,
86 'name' => $book->ownedBy->name,
91 public function test_read_endpoint_includes_chapter_and_page_contents()
93 $this->actingAsApiEditor();
94 /** @var Book $book */
95 $book = Book::visible()->has('chapters')->has('pages')->first();
96 $chapter = $book->chapters()->first();
97 $chapterPage = $chapter->pages()->first();
99 $resp = $this->getJson($this->baseEndpoint . "/{$book->id}");
101 $directChildCount = $book->directPages()->count() + $book->chapters()->count();
102 $resp->assertStatus(200);
103 $resp->assertJsonCount($directChildCount, 'contents');
108 'id' => $chapter->id,
109 'name' => $chapter->name,
110 'slug' => $chapter->slug,
113 'id' => $chapterPage->id,
114 'name' => $chapterPage->name,
115 'slug' => $chapterPage->slug,
123 public function test_update_endpoint()
125 $this->actingAsApiEditor();
126 $book = Book::visible()->first();
128 'name' => 'My updated API book',
129 'description' => 'A book created via the API',
132 $resp = $this->putJson($this->baseEndpoint . "/{$book->id}", $details);
135 $resp->assertStatus(200);
136 $resp->assertJson(array_merge($details, ['id' => $book->id, 'slug' => $book->slug]));
137 $this->assertActivityExists('book_update', $book);
140 public function test_update_increments_updated_date_if_only_tags_are_sent()
142 $this->actingAsApiEditor();
143 $book = Book::visible()->first();
144 DB::table('books')->where('id', '=', $book->id)->update(['updated_at' => Carbon::now()->subWeek()]);
147 'tags' => [['name' => 'Category', 'value' => 'Testing']],
150 $this->putJson($this->baseEndpoint . "/{$book->id}", $details);
152 $this->assertGreaterThan(Carbon::now()->subDay()->unix(), $book->updated_at->unix());
155 public function test_update_cover_image_control()
157 $this->actingAsApiEditor();
158 /** @var Book $book */
159 $book = Book::visible()->first();
160 $this->assertNull($book->cover);
161 $file = $this->getTestImage('image.png');
163 // Ensure cover image can be set via API
164 $resp = $this->call('PUT', $this->baseEndpoint . "/{$book->id}", [
165 'name' => 'My updated API book with image',
166 ], [], ['image' => $file]);
169 $resp->assertStatus(200);
170 $this->assertNotNull($book->cover);
172 // Ensure further updates without image do not clear cover image
173 $resp = $this->put($this->baseEndpoint . "/{$book->id}", [
174 'name' => 'My updated book again',
178 $resp->assertStatus(200);
179 $this->assertNotNull($book->cover);
181 // Ensure update with null image property clears image
182 $resp = $this->put($this->baseEndpoint . "/{$book->id}", [
187 $resp->assertStatus(200);
188 $this->assertNull($book->cover);
191 public function test_delete_endpoint()
193 $this->actingAsApiEditor();
194 $book = Book::visible()->first();
195 $resp = $this->deleteJson($this->baseEndpoint . "/{$book->id}");
197 $resp->assertStatus(204);
198 $this->assertActivityExists('book_delete');
201 public function test_export_html_endpoint()
203 $this->actingAsApiEditor();
204 $book = Book::visible()->first();
206 $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/html");
207 $resp->assertStatus(200);
208 $resp->assertSee($book->name);
209 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html"');
212 public function test_export_plain_text_endpoint()
214 $this->actingAsApiEditor();
215 $book = Book::visible()->first();
217 $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/plaintext");
218 $resp->assertStatus(200);
219 $resp->assertSee($book->name);
220 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.txt"');
223 public function test_export_pdf_endpoint()
225 $this->actingAsApiEditor();
226 $book = Book::visible()->first();
228 $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/pdf");
229 $resp->assertStatus(200);
230 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.pdf"');
233 public function test_export_markdown_endpoint()
235 $this->actingAsApiEditor();
236 $book = Book::visible()->has('pages')->has('chapters')->first();
238 $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/markdown");
239 $resp->assertStatus(200);
240 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.md"');
241 $resp->assertSee('# ' . $book->name);
242 $resp->assertSee('# ' . $book->pages()->first()->name);
243 $resp->assertSee('# ' . $book->chapters()->first()->name);
246 public function test_cant_export_when_not_have_permission()
248 $types = ['html', 'plaintext', 'pdf', 'markdown'];
249 $this->actingAsApiEditor();
250 $this->removePermissionFromUser($this->getEditor(), 'content-export');
252 $book = Book::visible()->first();
253 foreach ($types as $type) {
254 $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/{$type}");
255 $this->assertPermissionError($resp);