From 65d4505079c8c5616c98c14e795fe33acb95e8cf Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Sun, 19 Jun 2022 17:26:23 +0100
Subject: [PATCH] Added tests and doc updates for shelf/book cover image API
 abilities

---
 .../Controllers/Api/BookApiController.php     |  8 +++-
 .../Api/BookshelfApiController.php            |  8 +++-
 tests/Api/BooksApiTest.php                    | 38 +++++++++++++++++++
 tests/Api/ShelvesApiTest.php                  | 38 +++++++++++++++++++
 4 files changed, 88 insertions(+), 4 deletions(-)

diff --git a/app/Http/Controllers/Api/BookApiController.php b/app/Http/Controllers/Api/BookApiController.php
index 73cac6318..939300697 100644
--- a/app/Http/Controllers/Api/BookApiController.php
+++ b/app/Http/Controllers/Api/BookApiController.php
@@ -30,13 +30,15 @@ class BookApiController extends ApiController
 
     /**
      * Create a new book in the system.
+     * The cover image of a book can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
+     * If the 'image' property is null then the book cover image will be removed.
      *
      * @throws ValidationException
      */
     public function create(Request $request)
     {
         $this->checkPermission('book-create-all');
-        $requestData = $this->validate($request, $this->rules['create']);
+        $requestData = $this->validate($request, $this->rules()['create']);
 
         $book = $this->bookRepo->create($requestData);
 
@@ -55,6 +57,8 @@ class BookApiController extends ApiController
 
     /**
      * Update the details of a single book.
+     * The cover image of a book can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
+     * If the 'image' property is null then the book cover image will be removed.
      *
      * @throws ValidationException
      */
@@ -63,7 +67,7 @@ class BookApiController extends ApiController
         $book = Book::visible()->findOrFail($id);
         $this->checkOwnablePermission('book-update', $book);
 
-        $requestData = $this->validate($request, $this->rules['update']);
+        $requestData = $this->validate($request, $this->rules()['update']);
         $book = $this->bookRepo->update($book, $requestData);
 
         return response()->json($book);
diff --git a/app/Http/Controllers/Api/BookshelfApiController.php b/app/Http/Controllers/Api/BookshelfApiController.php
index 400dff977..620df1638 100644
--- a/app/Http/Controllers/Api/BookshelfApiController.php
+++ b/app/Http/Controllers/Api/BookshelfApiController.php
@@ -37,13 +37,15 @@ class BookshelfApiController extends ApiController
      * Create a new shelf in the system.
      * An array of books IDs can be provided in the request. These
      * will be added to the shelf in the same order as provided.
+     * The cover image of a shelf can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
+     * If the 'image' property is null then the shelf cover image will be removed.
      *
      * @throws ValidationException
      */
     public function create(Request $request)
     {
         $this->checkPermission('bookshelf-create-all');
-        $requestData = $this->validate($request, $this->rules['create']);
+        $requestData = $this->validate($request, $this->rules()['create']);
 
         $bookIds = $request->get('books', []);
         $shelf = $this->bookshelfRepo->create($requestData, $bookIds);
@@ -71,6 +73,8 @@ class BookshelfApiController extends ApiController
      * An array of books IDs can be provided in the request. These
      * will be added to the shelf in the same order as provided and overwrite
      * any existing book assignments.
+     * The cover image of a shelf can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
+     * If the 'image' property is null then the shelf cover image will be removed.
      *
      * @throws ValidationException
      */
@@ -79,7 +83,7 @@ class BookshelfApiController extends ApiController
         $shelf = Bookshelf::visible()->findOrFail($id);
         $this->checkOwnablePermission('bookshelf-update', $shelf);
 
-        $requestData = $this->validate($request, $this->rules['update']);
+        $requestData = $this->validate($request, $this->rules()['update']);
         $bookIds = $request->get('books', null);
 
         $shelf = $this->bookshelfRepo->update($shelf, $requestData, $bookIds);
diff --git a/tests/Api/BooksApiTest.php b/tests/Api/BooksApiTest.php
index 9fe8f8215..fb3244e55 100644
--- a/tests/Api/BooksApiTest.php
+++ b/tests/Api/BooksApiTest.php
@@ -6,10 +6,12 @@ use BookStack\Entities\Models\Book;
 use Carbon\Carbon;
 use Illuminate\Support\Facades\DB;
 use Tests\TestCase;
+use Tests\Uploads\UsesImages;
 
 class BooksApiTest extends TestCase
 {
     use TestsApi;
+    use UsesImages;
 
     protected string $baseEndpoint = '/api/books';
 
@@ -118,6 +120,42 @@ class BooksApiTest extends TestCase
         $this->assertGreaterThan(Carbon::now()->subDay()->unix(), $book->updated_at->unix());
     }
 
+    public function test_update_cover_image_control()
+    {
+        $this->actingAsApiEditor();
+        /** @var Book $book */
+        $book = Book::visible()->first();
+        $this->assertNull($book->cover);
+        $file = $this->getTestImage('image.png');
+
+        // Ensure cover image can be set via API
+        $resp = $this->call('PUT', $this->baseEndpoint . "/{$book->id}", [
+            'name'  => 'My updated API book with image',
+        ], [], ['image' => $file]);
+        $book->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNotNull($book->cover);
+
+        // Ensure further updates without image do not clear cover image
+        $resp = $this->put($this->baseEndpoint . "/{$book->id}", [
+            'name' => 'My updated book again'
+        ]);
+        $book->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNotNull($book->cover);
+
+        // Ensure update with null image property clears image
+        $resp = $this->put($this->baseEndpoint . "/{$book->id}", [
+            'image' => null,
+        ]);
+        $book->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNull($book->cover);
+    }
+
     public function test_delete_endpoint()
     {
         $this->actingAsApiEditor();
diff --git a/tests/Api/ShelvesApiTest.php b/tests/Api/ShelvesApiTest.php
index 034d4bc28..95b165402 100644
--- a/tests/Api/ShelvesApiTest.php
+++ b/tests/Api/ShelvesApiTest.php
@@ -7,10 +7,12 @@ use BookStack\Entities\Models\Bookshelf;
 use Carbon\Carbon;
 use Illuminate\Support\Facades\DB;
 use Tests\TestCase;
+use Tests\Uploads\UsesImages;
 
 class ShelvesApiTest extends TestCase
 {
     use TestsApi;
+    use UsesImages;
 
     protected string $baseEndpoint = '/api/shelves';
 
@@ -146,6 +148,42 @@ class ShelvesApiTest extends TestCase
         $this->assertTrue($shelf->books()->count() === 0);
     }
 
+    public function test_update_cover_image_control()
+    {
+        $this->actingAsApiEditor();
+        /** @var Book $shelf */
+        $shelf = Bookshelf::visible()->first();
+        $this->assertNull($shelf->cover);
+        $file = $this->getTestImage('image.png');
+
+        // Ensure cover image can be set via API
+        $resp = $this->call('PUT', $this->baseEndpoint . "/{$shelf->id}", [
+            'name'  => 'My updated API shelf with image',
+        ], [], ['image' => $file]);
+        $shelf->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNotNull($shelf->cover);
+
+        // Ensure further updates without image do not clear cover image
+        $resp = $this->put($this->baseEndpoint . "/{$shelf->id}", [
+            'name' => 'My updated shelf again'
+        ]);
+        $shelf->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNotNull($shelf->cover);
+
+        // Ensure update with null image property clears image
+        $resp = $this->put($this->baseEndpoint . "/{$shelf->id}", [
+            'image' => null,
+        ]);
+        $shelf->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNull($shelf->cover);
+    }
+
     public function test_delete_endpoint()
     {
         $this->actingAsApiEditor();