From da1a66abd384911085d595e4ef06d36430a38a27 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 8 Feb 2023 14:39:13 +0000 Subject: [PATCH] Extracted test file handling to its own class Closes #3995 --- app/Uploads/Attachment.php | 2 + .../factories/Uploads/AttachmentFactory.php | 39 +++++ tests/Api/BooksApiTest.php | 4 +- tests/Api/ShelvesApiTest.php | 4 +- tests/Entity/BookShelfTest.php | 5 +- tests/Entity/BookTest.php | 5 +- tests/Entity/PageContentTest.php | 11 +- tests/Helpers/FileProvider.php | 153 ++++++++++++++++++ tests/OpenGraphTest.php | 7 +- tests/Settings/SettingsTest.php | 5 +- tests/TestCase.php | 3 + tests/Uploads/AttachmentTest.php | 107 +++--------- tests/Uploads/AvatarTest.php | 6 +- tests/Uploads/DrawioTest.php | 9 +- tests/Uploads/ImageTest.php | 115 +++++++------ tests/Uploads/UsesImages.php | 122 -------------- tests/User/UserManagementTest.php | 5 +- 17 files changed, 293 insertions(+), 309 deletions(-) create mode 100644 database/factories/Uploads/AttachmentFactory.php create mode 100644 tests/Helpers/FileProvider.php delete mode 100644 tests/Uploads/UsesImages.php diff --git a/app/Uploads/Attachment.php b/app/Uploads/Attachment.php index fc86d36ea..01c927382 100644 --- a/app/Uploads/Attachment.php +++ b/app/Uploads/Attachment.php @@ -10,6 +10,7 @@ use BookStack\Entities\Models\Page; use BookStack\Model; use BookStack\Traits\HasCreatorAndUpdater; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -29,6 +30,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; class Attachment extends Model { use HasCreatorAndUpdater; + use HasFactory; protected $fillable = ['name', 'order']; protected $hidden = ['path', 'page']; diff --git a/database/factories/Uploads/AttachmentFactory.php b/database/factories/Uploads/AttachmentFactory.php new file mode 100644 index 000000000..30722265d --- /dev/null +++ b/database/factories/Uploads/AttachmentFactory.php @@ -0,0 +1,39 @@ + + */ +class AttachmentFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = \BookStack\Uploads\Attachment::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition() + { + return [ + 'name' => $this->faker->words(2, true), + 'path' => $this->faker->url(), + 'extension' => '', + 'external' => true, + 'uploaded_to' => Page::factory(), + 'created_by' => User::factory(), + 'updated_by' => User::factory(), + 'order' => 0, + ]; + } +} diff --git a/tests/Api/BooksApiTest.php b/tests/Api/BooksApiTest.php index dd187672e..326304d6f 100644 --- a/tests/Api/BooksApiTest.php +++ b/tests/Api/BooksApiTest.php @@ -6,12 +6,10 @@ 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'; @@ -157,7 +155,7 @@ class BooksApiTest extends TestCase /** @var Book $book */ $book = $this->entities->book(); $this->assertNull($book->cover); - $file = $this->getTestImage('image.png'); + $file = $this->files->uploadedImage('image.png'); // Ensure cover image can be set via API $resp = $this->call('PUT', $this->baseEndpoint . "/{$book->id}", [ diff --git a/tests/Api/ShelvesApiTest.php b/tests/Api/ShelvesApiTest.php index bc7b6f164..fbfc17cb4 100644 --- a/tests/Api/ShelvesApiTest.php +++ b/tests/Api/ShelvesApiTest.php @@ -7,12 +7,10 @@ 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'; @@ -154,7 +152,7 @@ class ShelvesApiTest extends TestCase /** @var Book $shelf */ $shelf = Bookshelf::visible()->first(); $this->assertNull($shelf->cover); - $file = $this->getTestImage('image.png'); + $file = $this->files->uploadedImage('image.png'); // Ensure cover image can be set via API $resp = $this->call('PUT', $this->baseEndpoint . "/{$shelf->id}", [ diff --git a/tests/Entity/BookShelfTest.php b/tests/Entity/BookShelfTest.php index d953f3692..2e7d41d64 100644 --- a/tests/Entity/BookShelfTest.php +++ b/tests/Entity/BookShelfTest.php @@ -8,12 +8,9 @@ use BookStack\Entities\Models\Bookshelf; use BookStack\Uploads\Image; use Illuminate\Support\Str; use Tests\TestCase; -use Tests\Uploads\UsesImages; class BookShelfTest extends TestCase { - use UsesImages; - public function test_shelves_shows_in_header_if_have_view_permissions() { $viewer = $this->users->viewer(); @@ -114,7 +111,7 @@ class BookShelfTest extends TestCase 'description' => 'Test book description ' . Str::random(10), ]; - $imageFile = $this->getTestImage('shelf-test.png'); + $imageFile = $this->files->uploadedImage('shelf-test.png'); $resp = $this->asEditor()->call('POST', '/shelves', $shelfInfo, [], ['image' => $imageFile]); $resp->assertRedirect(); diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index f124a1690..833cabaae 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -7,12 +7,9 @@ use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Repos\BookRepo; use Tests\TestCase; -use Tests\Uploads\UsesImages; class BookTest extends TestCase { - use UsesImages; - public function test_create() { $book = Book::factory()->make([ @@ -333,7 +330,7 @@ class BookTest extends TestCase { $book = $this->entities->book(); $bookRepo = $this->app->make(BookRepo::class); - $coverImageFile = $this->getTestImage('cover.png'); + $coverImageFile = $this->files->uploadedImage('cover.png'); $bookRepo->updateCoverImage($book, $coverImageFile); $this->asEditor()->post($book->getUrl('/copy'), ['name' => 'My copy book']); diff --git a/tests/Entity/PageContentTest.php b/tests/Entity/PageContentTest.php index e8838ae0b..f62d09b1f 100644 --- a/tests/Entity/PageContentTest.php +++ b/tests/Entity/PageContentTest.php @@ -5,12 +5,9 @@ namespace Tests\Entity; use BookStack\Entities\Models\Page; use BookStack\Entities\Tools\PageContent; use Tests\TestCase; -use Tests\Uploads\UsesImages; class PageContentTest extends TestCase { - use UsesImages; - protected $base64Jpeg = '/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k='; public function test_page_includes() @@ -591,7 +588,7 @@ class PageContentTest extends TestCase $imageFile = public_path($imagePath); $this->assertEquals(base64_decode($this->base64Jpeg), file_get_contents($imageFile)); - $this->deleteImage($imagePath); + $this->files->deleteAtRelativePath($imagePath); } public function test_base64_images_get_extracted_when_containing_whitespace() @@ -615,7 +612,7 @@ class PageContentTest extends TestCase $imageFile = public_path($imagePath); $this->assertEquals(base64_decode($base64PngWithoutWhitespace), file_get_contents($imageFile)); - $this->deleteImage($imagePath); + $this->files->deleteAtRelativePath($imagePath); } public function test_base64_images_within_html_blanked_if_not_supported_extension_for_extract() @@ -659,7 +656,7 @@ class PageContentTest extends TestCase $imageFile = public_path($imagePath); $this->assertEquals(base64_decode($this->base64Jpeg), file_get_contents($imageFile)); - $this->deleteImage($imagePath); + $this->files->deleteAtRelativePath($imagePath); } public function test_markdown_base64_extract_not_limited_by_pcre_limits() @@ -690,7 +687,7 @@ class PageContentTest extends TestCase $imageFile = public_path($imagePath); $this->assertEquals($content, file_get_contents($imageFile)); - $this->deleteImage($imagePath); + $this->files->deleteAtRelativePath($imagePath); ini_set('pcre.backtrack_limit', $pcreBacktrackLimit); ini_set('pcre.recursion_limit', $pcreRecursionLimit); } diff --git a/tests/Helpers/FileProvider.php b/tests/Helpers/FileProvider.php new file mode 100644 index 000000000..9e44697c7 --- /dev/null +++ b/tests/Helpers/FileProvider.php @@ -0,0 +1,153 @@ +testFilePath($base64FileName); + $data = file_get_contents($base64FilePath); + $decoded = base64_decode($data); + file_put_contents($imagePath, $decoded); + + return new UploadedFile($imagePath, $imageFileName, 'image/png', null, true); + } + + /** + * Get a test image UploadedFile instance, that can be uploaded via test requests. + */ + public function uploadedImage(string $fileName, string $testDataFileName = ''): UploadedFile + { + return new UploadedFile($this->testFilePath($testDataFileName ?: 'test-image.png'), $fileName, 'image/png', null, true); + } + + /** + * Get a test txt UploadedFile instance, that can be uploaded via test requests. + */ + public function uploadedTextFile(string $fileName): UploadedFile + { + return new UploadedFile($this->testFilePath('test-file.txt'), $fileName, 'text/plain', null, true); + } + + /** + * Get raw data for a PNG image test file. + */ + public function pngImageData(): string + { + return file_get_contents($this->testFilePath('test-image.png')); + } + + /** + * Get the expected relative path for an uploaded image of the given type and filename. + */ + public function expectedImagePath(string $imageType, string $fileName): string + { + return '/uploads/images/' . $imageType . '/' . date('Y-m') . '/' . $fileName; + } + + /** + * Performs an image gallery upload request with the given name. + */ + public function uploadGalleryImage(TestCase $case, string $name, int $uploadedTo = 0, string $contentType = 'image/png', string $testDataFileName = ''): TestResponse + { + $file = $this->uploadedImage($name, $testDataFileName); + + return $case->call('POST', '/images/gallery', ['uploaded_to' => $uploadedTo], [], ['file' => $file], ['CONTENT_TYPE' => $contentType]); + } + + /** + * Upload a new gallery image and return a set of details about the image, + * including the json decoded response of the upload. + * Ensures the upload succeeds. + * + * @return array{name: string, path: string, page: Page, response: stdClass} + */ + public function uploadGalleryImageToPage(TestCase $case, Page $page, string $testDataFileName = ''): array + { + $imageName = $testDataFileName ?: 'first-image.png'; + $relPath = $this->expectedImagePath('gallery', $imageName); + $this->deleteAtRelativePath($relPath); + + $upload = $this->uploadGalleryImage($case, $imageName, $page->id, 'image/png', $testDataFileName); + $upload->assertStatus(200); + + return [ + 'name' => $imageName, + 'path' => $relPath, + 'page' => $page, + 'response' => json_decode($upload->getContent()), + ]; + } + + /** + * Uploads an attachment file with the given name. + */ + public function uploadAttachmentFile(TestCase $case, string $name, int $uploadedTo = 0): TestResponse + { + $file = $this->uploadedTextFile($name); + + return $case->call('POST', '/attachments/upload', ['uploaded_to' => $uploadedTo], [], ['file' => $file], []); + } + + /** + * Upload a new attachment from the given raw data of the given type, to the given page. + * Returns the attachment + */ + public function uploadAttachmentDataToPage(TestCase $case, Page $page, string $filename, string $content, string $mimeType): Attachment + { + $file = tmpfile(); + $filePath = stream_get_meta_data($file)['uri']; + file_put_contents($filePath, $content); + $upload = new UploadedFile($filePath, $filename, $mimeType, null, true); + + $case->call('POST', '/attachments/upload', ['uploaded_to' => $page->id], [], ['file' => $upload], []); + + return $page->attachments()->where('uploaded_to', '=', $page->id)->latest()->firstOrFail(); + } + + /** + * Delete an uploaded image. + */ + public function deleteAtRelativePath(string $path): void + { + $fullPath = public_path($path); + if (file_exists($fullPath)) { + unlink($fullPath); + } + } + + /** + * Delete all uploaded files. + * To assist with cleanup. + */ + public function deleteAllAttachmentFiles(): void + { + $fileService = app()->make(AttachmentService::class); + foreach (Attachment::all() as $file) { + $fileService->deleteFile($file); + } + } +} diff --git a/tests/OpenGraphTest.php b/tests/OpenGraphTest.php index d6f535718..545fa4c86 100644 --- a/tests/OpenGraphTest.php +++ b/tests/OpenGraphTest.php @@ -6,12 +6,9 @@ use BookStack\Entities\Repos\BaseRepo; use BookStack\Entities\Repos\BookRepo; use Illuminate\Support\Str; use Illuminate\Testing\TestResponse; -use Tests\Uploads\UsesImages; class OpenGraphTest extends TestCase { - use UsesImages; - public function test_page_tags() { $page = $this->entities->page(); @@ -47,7 +44,7 @@ class OpenGraphTest extends TestCase // Test image set if image has cover image $bookRepo = app(BookRepo::class); - $bookRepo->updateCoverImage($book, $this->getTestImage('image.png')); + $bookRepo->updateCoverImage($book, $this->files->uploadedImage('image.png')); $resp = $this->asEditor()->get($book->getUrl()); $tags = $this->getOpenGraphTags($resp); @@ -67,7 +64,7 @@ class OpenGraphTest extends TestCase // Test image set if image has cover image $baseRepo = app(BaseRepo::class); - $baseRepo->updateCoverImage($shelf, $this->getTestImage('image.png')); + $baseRepo->updateCoverImage($shelf, $this->files->uploadedImage('image.png')); $resp = $this->asEditor()->get($shelf->getUrl()); $tags = $this->getOpenGraphTags($resp); diff --git a/tests/Settings/SettingsTest.php b/tests/Settings/SettingsTest.php index f4db6f1c8..30bb50f7c 100644 --- a/tests/Settings/SettingsTest.php +++ b/tests/Settings/SettingsTest.php @@ -3,12 +3,9 @@ namespace Tests\Settings; use Tests\TestCase; -use Tests\Uploads\UsesImages; class SettingsTest extends TestCase { - use UsesImages; - public function test_settings_endpoint_redirects_to_settings_view() { $resp = $this->asAdmin()->get('/settings'); @@ -47,7 +44,7 @@ class SettingsTest extends TestCase public function test_updating_and_removing_app_icon() { $this->asAdmin(); - $galleryFile = $this->getTestImage('my-app-icon.png'); + $galleryFile = $this->files->uploadedImage('my-app-icon.png'); $expectedPath = public_path('uploads/images/system/' . date('Y-m') . '/my-app-icon.png'); $this->assertFalse(setting()->get('app-icon')); diff --git a/tests/TestCase.php b/tests/TestCase.php index 9e4b1df41..abee1d3b3 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,6 +23,7 @@ use Monolog\Logger; use Psr\Http\Client\ClientInterface; use Ssddanbrown\AssertHtml\TestsHtml; use Tests\Helpers\EntityProvider; +use Tests\Helpers\FileProvider; use Tests\Helpers\PermissionsProvider; use Tests\Helpers\TestServiceProvider; use Tests\Helpers\UserRoleProvider; @@ -36,12 +37,14 @@ abstract class TestCase extends BaseTestCase protected EntityProvider $entities; protected UserRoleProvider $users; protected PermissionsProvider $permissions; + protected FileProvider $files; protected function setUp(): void { $this->entities = new EntityProvider(); $this->users = new UserRoleProvider(); $this->permissions = new PermissionsProvider($this->users); + $this->files = new FileProvider(); parent::setUp(); diff --git a/tests/Uploads/AttachmentTest.php b/tests/Uploads/AttachmentTest.php index f2f30ff2e..1da12cd1c 100644 --- a/tests/Uploads/AttachmentTest.php +++ b/tests/Uploads/AttachmentTest.php @@ -6,71 +6,10 @@ use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\PageRepo; use BookStack\Entities\Tools\TrashCan; use BookStack\Uploads\Attachment; -use BookStack\Uploads\AttachmentService; -use Illuminate\Http\UploadedFile; use Tests\TestCase; class AttachmentTest extends TestCase { - /** - * Get a test file that can be uploaded. - */ - protected function getTestFile(string $fileName): UploadedFile - { - return new UploadedFile(base_path('tests/test-data/test-file.txt'), $fileName, 'text/plain', null, true); - } - - /** - * Uploads a file with the given name. - */ - protected function uploadFile(string $name, int $uploadedTo = 0): \Illuminate\Testing\TestResponse - { - $file = $this->getTestFile($name); - - return $this->call('POST', '/attachments/upload', ['uploaded_to' => $uploadedTo], [], ['file' => $file], []); - } - - /** - * Create a new attachment. - */ - protected function createAttachment(Page $page): Attachment - { - $this->post('attachments/link', [ - 'attachment_link_url' => 'https://example.com', - 'attachment_link_name' => 'Example Attachment Link', - 'attachment_link_uploaded_to' => $page->id, - ]); - - return Attachment::query()->latest()->first(); - } - - /** - * Create a new upload attachment from the given data. - */ - protected function createUploadAttachment(Page $page, string $filename, string $content, string $mimeType): Attachment - { - $file = tmpfile(); - $filePath = stream_get_meta_data($file)['uri']; - file_put_contents($filePath, $content); - $upload = new UploadedFile($filePath, $filename, $mimeType, null, true); - - $this->call('POST', '/attachments/upload', ['uploaded_to' => $page->id], [], ['file' => $upload], []); - - return $page->attachments()->latest()->firstOrFail(); - } - - /** - * Delete all uploaded files. - * To assist with cleanup. - */ - protected function deleteUploads() - { - $fileService = $this->app->make(AttachmentService::class); - foreach (Attachment::all() as $file) { - $fileService->deleteFile($file); - } - } - public function test_file_upload() { $page = $this->entities->page(); @@ -87,7 +26,7 @@ class AttachmentTest extends TestCase 'updated_by' => $admin->id, ]; - $upload = $this->uploadFile($fileName, $page->id); + $upload = $this->files->uploadAttachmentFile($this, $fileName, $page->id); $upload->assertStatus(200); $attachment = Attachment::query()->orderBy('id', 'desc')->first(); @@ -96,7 +35,7 @@ class AttachmentTest extends TestCase $expectedResp['path'] = $attachment->path; $this->assertDatabaseHas('attachments', $expectedResp); - $this->deleteUploads(); + $this->files->deleteAllAttachmentFiles(); } public function test_file_upload_does_not_use_filename() @@ -104,13 +43,14 @@ class AttachmentTest extends TestCase $page = $this->entities->page(); $fileName = 'upload_test_file.txt'; - $upload = $this->asAdmin()->uploadFile($fileName, $page->id); + $this->asAdmin(); + $upload = $this->files->uploadAttachmentFile($this, $fileName, $page->id); $upload->assertStatus(200); $attachment = Attachment::query()->orderBy('id', 'desc')->first(); $this->assertStringNotContainsString($fileName, $attachment->path); $this->assertStringEndsWith('-txt', $attachment->path); - $this->deleteUploads(); + $this->files->deleteAllAttachmentFiles(); } public function test_file_display_and_access() @@ -119,7 +59,7 @@ class AttachmentTest extends TestCase $this->asAdmin(); $fileName = 'upload_test_file.txt'; - $upload = $this->uploadFile($fileName, $page->id); + $upload = $this->files->uploadAttachmentFile($this, $fileName, $page->id); $upload->assertStatus(200); $attachment = Attachment::orderBy('id', 'desc')->take(1)->first(); @@ -131,7 +71,7 @@ class AttachmentTest extends TestCase $content = $attachmentGet->streamedContent(); $this->assertStringContainsString('Hi, This is a test file for testing the upload process.', $content); - $this->deleteUploads(); + $this->files->deleteAllAttachmentFiles(); } public function test_attaching_link_to_page() @@ -168,7 +108,7 @@ class AttachmentTest extends TestCase $attachmentGet = $this->get($attachment->getUrl()); $attachmentGet->assertRedirect('https://example.com'); - $this->deleteUploads(); + $this->files->deleteAllAttachmentFiles(); } public function test_attachment_updating() @@ -176,7 +116,7 @@ class AttachmentTest extends TestCase $page = $this->entities->page(); $this->asAdmin(); - $attachment = $this->createAttachment($page); + $attachment = Attachment::factory()->create(['uploaded_to' => $page->id]); $update = $this->call('PUT', 'attachments/' . $attachment->id, [ 'attachment_edit_name' => 'My new attachment name', 'attachment_edit_url' => 'https://test.example.com', @@ -192,7 +132,7 @@ class AttachmentTest extends TestCase $update->assertStatus(200); $this->assertDatabaseHas('attachments', $expectedData); - $this->deleteUploads(); + $this->files->deleteAllAttachmentFiles(); } public function test_file_deletion() @@ -200,7 +140,7 @@ class AttachmentTest extends TestCase $page = $this->entities->page(); $this->asAdmin(); $fileName = 'deletion_test.txt'; - $this->uploadFile($fileName, $page->id); + $this->files->uploadAttachmentFile($this, $fileName, $page->id); $attachment = Attachment::query()->orderBy('id', 'desc')->first(); $filePath = storage_path($attachment->path); @@ -214,7 +154,7 @@ class AttachmentTest extends TestCase ]); $this->assertFalse(file_exists($filePath), 'File at path ' . $filePath . ' was not deleted as expected'); - $this->deleteUploads(); + $this->files->deleteAllAttachmentFiles(); } public function test_attachment_deletion_on_page_deletion() @@ -222,7 +162,7 @@ class AttachmentTest extends TestCase $page = $this->entities->page(); $this->asAdmin(); $fileName = 'deletion_test.txt'; - $this->uploadFile($fileName, $page->id); + $this->files->uploadAttachmentFile($this, $fileName, $page->id); $attachment = Attachment::query()->orderBy('id', 'desc')->first(); $filePath = storage_path($attachment->path); @@ -240,7 +180,7 @@ class AttachmentTest extends TestCase ]); $this->assertFalse(file_exists($filePath), 'File at path ' . $filePath . ' was not deleted as expected'); - $this->deleteUploads(); + $this->files->deleteAllAttachmentFiles(); } public function test_attachment_access_without_permission_shows_404() @@ -250,7 +190,7 @@ class AttachmentTest extends TestCase $page = $this->entities->page(); /** @var Page $page */ $this->actingAs($admin); $fileName = 'permission_test.txt'; - $this->uploadFile($fileName, $page->id); + $this->files->uploadAttachmentFile($this, $fileName, $page->id); $attachment = Attachment::orderBy('id', 'desc')->take(1)->first(); $this->permissions->setEntityPermissions($page, [], []); @@ -260,7 +200,7 @@ class AttachmentTest extends TestCase $attachmentGet->assertStatus(404); $attachmentGet->assertSee('Attachment not found'); - $this->deleteUploads(); + $this->files->deleteAllAttachmentFiles(); } public function test_data_and_js_links_cannot_be_attached_to_a_page() @@ -290,7 +230,7 @@ class AttachmentTest extends TestCase ]); } - $attachment = $this->createAttachment($page); + $attachment = Attachment::factory()->create(['uploaded_to' => $page->id]); foreach ($badLinks as $badLink) { $linkReq = $this->put('attachments/' . $attachment->id, [ @@ -310,7 +250,7 @@ class AttachmentTest extends TestCase $this->asAdmin(); $fileName = 'upload_test_file.txt'; - $upload = $this->uploadFile($fileName, $page->id); + $upload = $this->files->uploadAttachmentFile($this, $fileName, $page->id); $upload->assertStatus(200); $attachment = Attachment::query()->orderBy('id', 'desc')->take(1)->first(); @@ -320,7 +260,7 @@ class AttachmentTest extends TestCase $attachmentGet->assertHeader('Content-Disposition', 'inline; filename="upload_test_file.txt"'); $attachmentGet->assertHeader('X-Content-Type-Options', 'nosniff'); - $this->deleteUploads(); + $this->files->deleteAllAttachmentFiles(); } public function test_html_file_access_with_open_forces_plain_content_type() @@ -328,14 +268,14 @@ class AttachmentTest extends TestCase $page = $this->entities->page(); $this->asAdmin(); - $attachment = $this->createUploadAttachment($page, 'test_file.html', '

testing

', 'text/html'); + $attachment = $this->files->uploadAttachmentDataToPage($this, $page, 'test_file.html', '

testing

', 'text/html'); $attachmentGet = $this->get($attachment->getUrl(true)); // http-foundation/Response does some 'fixing' of responses to add charsets to text responses. $attachmentGet->assertHeader('Content-Type', 'text/plain; charset=UTF-8'); $attachmentGet->assertHeader('Content-Disposition', 'inline; filename="test_file.html"'); - $this->deleteUploads(); + $this->files->deleteAllAttachmentFiles(); } public function test_file_upload_works_when_local_secure_restricted_is_in_use() @@ -345,11 +285,12 @@ class AttachmentTest extends TestCase $page = $this->entities->page(); $fileName = 'upload_test_file.txt'; - $upload = $this->asAdmin()->uploadFile($fileName, $page->id); + $this->asAdmin(); + $upload = $this->files->uploadAttachmentFile($this, $fileName, $page->id); $upload->assertStatus(200); $attachment = Attachment::query()->orderBy('id', 'desc')->where('uploaded_to', '=', $page->id)->first(); $this->assertFileExists(storage_path($attachment->path)); - $this->deleteUploads(); + $this->files->deleteAllAttachmentFiles(); } } diff --git a/tests/Uploads/AvatarTest.php b/tests/Uploads/AvatarTest.php index eeaae09dc..57f28db42 100644 --- a/tests/Uploads/AvatarTest.php +++ b/tests/Uploads/AvatarTest.php @@ -9,8 +9,6 @@ use Tests\TestCase; class AvatarTest extends TestCase { - use UsesImages; - protected function createUserRequest($user): User { $this->asAdmin()->post('/settings/users/create', [ @@ -29,12 +27,12 @@ class AvatarTest extends TestCase $http->shouldReceive('fetch') ->once()->with($url) - ->andReturn($this->getTestImageContent()); + ->andReturn($this->files->pngImageData()); } protected function deleteUserImage(User $user) { - $this->deleteImage($user->avatar->path); + $this->files->deleteAtRelativePath($user->avatar->path); } public function test_gravatar_fetched_on_user_create() diff --git a/tests/Uploads/DrawioTest.php b/tests/Uploads/DrawioTest.php index 3c208f54d..d5b3f6088 100644 --- a/tests/Uploads/DrawioTest.php +++ b/tests/Uploads/DrawioTest.php @@ -2,21 +2,18 @@ namespace Tests\Uploads; -use BookStack\Entities\Models\Page; use BookStack\Uploads\Image; use Tests\TestCase; class DrawioTest extends TestCase { - use UsesImages; - public function test_get_image_as_base64() { $page = $this->entities->page(); $this->asAdmin(); $imageName = 'first-image.png'; - $this->uploadImage($imageName, $page->id); + $this->files->uploadGalleryImage($this, $imageName, $page->id); /** @var Image $image */ $image = Image::query()->first(); $image->type = 'drawio'; @@ -34,7 +31,7 @@ class DrawioTest extends TestCase $this->asEditor(); $imageName = 'non-accessible-image.png'; - $this->uploadImage($imageName, $page->id); + $this->files->uploadGalleryImage($this, $imageName, $page->id); /** @var Image $image */ $image = Image::query()->first(); $image->type = 'drawio'; @@ -70,7 +67,7 @@ class DrawioTest extends TestCase $image = Image::where('type', '=', 'drawio')->first(); $this->assertTrue(file_exists(public_path($image->path)), 'Uploaded image not found at path: ' . public_path($image->path)); - $testImageData = file_get_contents($this->getTestImageFilePath()); + $testImageData = $this->files->pngImageData(); $uploadedImageData = file_get_contents(public_path($image->path)); $this->assertTrue($testImageData === $uploadedImageData, 'Uploaded image file data does not match our test image as expected'); } diff --git a/tests/Uploads/ImageTest.php b/tests/Uploads/ImageTest.php index c6e678ff2..fb98565fc 100644 --- a/tests/Uploads/ImageTest.php +++ b/tests/Uploads/ImageTest.php @@ -2,7 +2,6 @@ namespace Tests\Uploads; -use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\PageRepo; use BookStack\Uploads\Image; use BookStack\Uploads\ImageService; @@ -11,20 +10,18 @@ use Tests\TestCase; class ImageTest extends TestCase { - use UsesImages; - public function test_image_upload() { $page = $this->entities->page(); $admin = $this->users->admin(); $this->actingAs($admin); - $imgDetails = $this->uploadGalleryImage($page); + $imgDetails = $this->files->uploadGalleryImageToPage($this, $page); $relPath = $imgDetails['path']; $this->assertTrue(file_exists(public_path($relPath)), 'Uploaded image found at path: ' . public_path($relPath)); - $this->deleteImage($relPath); + $this->files->deleteAtRelativePath($relPath); $this->assertDatabaseHas('images', [ 'url' => $this->baseUrl . $relPath, @@ -43,9 +40,9 @@ class ImageTest extends TestCase $admin = $this->users->admin(); $this->actingAs($admin); - $originalFile = $this->getTestImageFilePath('compressed.png'); + $originalFile = $this->files->testFilePath('compressed.png'); $originalFileSize = filesize($originalFile); - $imgDetails = $this->uploadGalleryImage($page, 'compressed.png'); + $imgDetails = $this->files->uploadGalleryImageToPage($this, $page, 'compressed.png'); $relPath = $imgDetails['path']; $this->assertTrue(file_exists(public_path($relPath)), 'Uploaded image found at path: ' . public_path($relPath)); @@ -55,8 +52,8 @@ class ImageTest extends TestCase $displayImagePath = public_path($displayImageRelPath); $displayFileSize = filesize($displayImagePath); - $this->deleteImage($relPath); - $this->deleteImage($displayImageRelPath); + $this->files->deleteAtRelativePath($relPath); + $this->files->deleteAtRelativePath($displayImageRelPath); $this->assertEquals($originalFileSize, $displayFileSize, 'Display thumbnail generation should not increase image size'); } @@ -67,8 +64,8 @@ class ImageTest extends TestCase $admin = $this->users->admin(); $this->actingAs($admin); - $imgDetails = $this->uploadGalleryImage($page, 'animated.png'); - $this->deleteImage($imgDetails['path']); + $imgDetails = $this->files->uploadGalleryImageToPage($this, $page, 'animated.png'); + $this->files->deleteAtRelativePath($imgDetails['path']); $this->assertStringContainsString('thumbs-', $imgDetails['response']->thumbs->gallery); $this->assertStringNotContainsString('thumbs-', $imgDetails['response']->thumbs->display); @@ -79,7 +76,7 @@ class ImageTest extends TestCase $editor = $this->users->editor(); $this->actingAs($editor); - $imgDetails = $this->uploadGalleryImage(); + $imgDetails = $this->files->uploadGalleryImageToPage($this, $this->entities->page()); $image = Image::query()->first(); $newName = Str::random(); @@ -87,7 +84,7 @@ class ImageTest extends TestCase $update->assertSuccessful(); $update->assertSee($newName); - $this->deleteImage($imgDetails['path']); + $this->files->deleteAtRelativePath($imgDetails['path']); $this->assertDatabaseHas('images', [ 'type' => 'gallery', @@ -99,7 +96,7 @@ class ImageTest extends TestCase { $this->asEditor(); - $imgDetails = $this->uploadGalleryImage(); + $imgDetails = $this->files->uploadGalleryImageToPage($this, $this->entities->page()); $image = Image::query()->first(); $pageId = $imgDetails['page']->id; @@ -129,7 +126,7 @@ class ImageTest extends TestCase $editor = $this->users->editor(); $this->actingAs($editor); - $imgDetails = $this->uploadGalleryImage($page); + $imgDetails = $this->files->uploadGalleryImageToPage($this, $page); $image = Image::query()->first(); $page->html = ''; @@ -140,7 +137,7 @@ class ImageTest extends TestCase $usage->assertSeeText($page->name); $usage->assertSee($page->getUrl()); - $this->deleteImage($imgDetails['path']); + $this->files->deleteAtRelativePath($imgDetails['path']); } public function test_php_files_cannot_be_uploaded() @@ -150,10 +147,10 @@ class ImageTest extends TestCase $this->actingAs($admin); $fileName = 'bad.php'; - $relPath = $this->getTestImagePath('gallery', $fileName); - $this->deleteImage($relPath); + $relPath = $this->files->expectedImagePath('gallery', $fileName); + $this->files->deleteAtRelativePath($relPath); - $file = $this->newTestImageFromBase64('bad-php.base64', $fileName); + $file = $this->files->imageFromBase64File('bad-php.base64', $fileName); $upload = $this->withHeader('Content-Type', 'image/jpeg')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []); $upload->assertStatus(302); @@ -172,10 +169,10 @@ class ImageTest extends TestCase $this->actingAs($admin); $fileName = 'bad.phtml'; - $relPath = $this->getTestImagePath('gallery', $fileName); - $this->deleteImage($relPath); + $relPath = $this->files->expectedImagePath('gallery', $fileName); + $this->files->deleteAtRelativePath($relPath); - $file = $this->newTestImageFromBase64('bad-phtml.base64', $fileName); + $file = $this->files->imageFromBase64File('bad-phtml.base64', $fileName); $upload = $this->withHeader('Content-Type', 'image/jpeg')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []); $upload->assertStatus(302); @@ -189,11 +186,11 @@ class ImageTest extends TestCase $this->actingAs($admin); $fileName = 'bad.phtml.png'; - $relPath = $this->getTestImagePath('gallery', $fileName); + $relPath = $this->files->expectedImagePath('gallery', $fileName); $expectedRelPath = dirname($relPath) . '/bad-phtml.png'; - $this->deleteImage($expectedRelPath); + $this->files->deleteAtRelativePath($expectedRelPath); - $file = $this->newTestImageFromBase64('bad-phtml-png.base64', $fileName); + $file = $this->files->imageFromBase64File('bad-phtml-png.base64', $fileName); $upload = $this->withHeader('Content-Type', 'image/png')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []); $upload->assertStatus(200); @@ -204,7 +201,7 @@ class ImageTest extends TestCase $this->assertFileDoesNotExist(public_path($relPath), 'Uploaded image file name was not stripped of dots'); $this->assertFileExists(public_path($expectedRelPath)); - $this->deleteImage($lastImage->path); + $this->files->deleteAtRelativePath($lastImage->path); } public function test_url_entities_removed_from_filenames() @@ -218,10 +215,10 @@ class ImageTest extends TestCase '#.png', ]; foreach ($badNames as $name) { - $galleryFile = $this->getTestImage($name); + $galleryFile = $this->files->uploadedImage($name); $page = $this->entities->page(); - $badPath = $this->getTestImagePath('gallery', $name); - $this->deleteImage($badPath); + $badPath = $this->files->expectedImagePath('gallery', $name); + $this->files->deleteAtRelativePath($badPath); $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []); $upload->assertStatus(200); @@ -235,7 +232,7 @@ class ImageTest extends TestCase $this->assertTrue(strlen($newFileName) > 0, 'File name was reduced to nothing'); - $this->deleteImage($lastImage->path); + $this->files->deleteAtRelativePath($lastImage->path); } } @@ -243,7 +240,7 @@ class ImageTest extends TestCase { config()->set('filesystems.images', 'local_secure'); $this->asEditor(); - $galleryFile = $this->getTestImage('my-secure-test-upload.png'); + $galleryFile = $this->files->uploadedImage('my-secure-test-upload.png'); $page = $this->entities->page(); $expectedPath = storage_path('uploads/images/gallery/' . date('Y-m') . '/my-secure-test-upload.png'); @@ -291,7 +288,7 @@ class ImageTest extends TestCase { config()->set('filesystems.images', 'local_secure'); $this->asEditor(); - $galleryFile = $this->getTestImage('my-secure-test-upload.png'); + $galleryFile = $this->files->uploadedImage('my-secure-test-upload.png'); $page = $this->entities->page(); $expectedPath = storage_path('uploads/images/gallery/' . date('Y-m') . '/my-secure-test-upload.png'); @@ -314,7 +311,7 @@ class ImageTest extends TestCase { config()->set('filesystems.images', 'local_secure'); $this->asAdmin(); - $galleryFile = $this->getTestImage('my-system-test-upload.png'); + $galleryFile = $this->files->uploadedImage('my-system-test-upload.png'); $expectedPath = public_path('uploads/images/system/' . date('Y-m') . '/my-system-test-upload.png'); $upload = $this->call('POST', '/settings/customization', [], [], ['app_logo' => $galleryFile], []); @@ -331,7 +328,7 @@ class ImageTest extends TestCase { config()->set('filesystems.images', 'local_secure_restricted'); $this->asAdmin(); - $galleryFile = $this->getTestImage('my-system-test-restricted-upload.png'); + $galleryFile = $this->files->uploadedImage('my-system-test-restricted-upload.png'); $expectedPath = public_path('uploads/images/system/' . date('Y-m') . '/my-system-test-restricted-upload.png'); $upload = $this->call('POST', '/settings/customization', [], [], ['app_logo' => $galleryFile], []); @@ -348,7 +345,7 @@ class ImageTest extends TestCase { config()->set('filesystems.images', 'local_secure_restricted'); $this->asEditor(); - $galleryFile = $this->getTestImage('my-secure-restricted-test-upload.png'); + $galleryFile = $this->files->uploadedImage('my-secure-restricted-test-upload.png'); $page = $this->entities->page(); $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []); @@ -372,7 +369,7 @@ class ImageTest extends TestCase { config()->set('filesystems.images', 'local_secure_restricted'); $this->asEditor(); - $galleryFile = $this->getTestImage('my-secure-restricted-thumb-test-test.png'); + $galleryFile = $this->files->uploadedImage('my-secure-restricted-thumb-test-test.png'); $page = $this->entities->page(); $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []); @@ -396,12 +393,10 @@ class ImageTest extends TestCase { config()->set('filesystems.images', 'local_secure_restricted'); $this->asEditor(); - $galleryFile = $this->getTestImage('my-secure-restricted-export-test.png'); + $galleryFile = $this->files->uploadedImage('my-secure-restricted-export-test.png'); - /** @var Page $pageA */ - /** @var Page $pageB */ - $pageA = Page::query()->first(); - $pageB = Page::query()->where('id', '!=', $pageA->id)->first(); + $pageA = $this->entities->page(); + $pageB = $this->entities->page(); $expectedPath = storage_path('uploads/images/gallery/' . date('Y-m') . '/my-secure-restricted-export-test.png'); $upload = $this->asEditor()->call('POST', '/images/gallery', ['uploaded_to' => $pageA->id], [], ['file' => $galleryFile], []); @@ -430,10 +425,10 @@ class ImageTest extends TestCase $page = $this->entities->page(); $this->asAdmin(); $imageName = 'first-image.png'; - $relPath = $this->getTestImagePath('gallery', $imageName); - $this->deleteImage($relPath); + $relPath = $this->files->expectedImagePath('gallery', $imageName); + $this->files->deleteAtRelativePath($relPath); - $this->uploadImage($imageName, $page->id); + $this->files->uploadGalleryImage($this, $imageName, $page->id); $image = Image::first(); $delete = $this->delete('/images/' . $image->id); @@ -453,12 +448,12 @@ class ImageTest extends TestCase $this->asAdmin(); $imageName = 'first-image.png'; - $relPath = $this->getTestImagePath('gallery', $imageName); - $this->deleteImage($relPath); + $relPath = $this->files->expectedImagePath('gallery', $imageName); + $this->files->deleteAtRelativePath($relPath); - $this->uploadImage($imageName, $page->id); - $this->uploadImage($imageName, $page->id); - $this->uploadImage($imageName, $page->id); + $this->files->uploadGalleryImage($this, $imageName, $page->id); + $this->files->uploadGalleryImage($this, $imageName, $page->id); + $this->files->uploadGalleryImage($this, $imageName, $page->id); $image = Image::first(); $folder = public_path(dirname($relPath)); @@ -477,11 +472,11 @@ class ImageTest extends TestCase $page = $this->entities->page(); $this->asAdmin(); $imageName = 'first-image.png'; - $relPath = $this->getTestImagePath('gallery', $imageName); - $this->deleteImage($relPath); + $relPath = $this->files->expectedImagePath('gallery', $imageName); + $this->files->deleteAtRelativePath($relPath); $viewer = $this->users->viewer(); - $this->uploadImage($imageName, $page->id); + $this->files->uploadGalleryImage($this, $imageName, $page->id); $image = Image::first(); $resp = $this->get("/images/edit/{$image->id}"); @@ -495,16 +490,16 @@ class ImageTest extends TestCase $resp = $this->actingAs($viewer)->get("/images/edit/{$image->id}"); $this->withHtml($resp)->assertElementExists('button#image-manager-delete[title="Delete"]'); - $this->deleteImage($relPath); + $this->files->deleteAtRelativePath($relPath); } protected function getTestProfileImage() { $imageName = 'profile.png'; - $relPath = $this->getTestImagePath('user', $imageName); - $this->deleteImage($relPath); + $relPath = $this->files->expectedImagePath('user', $imageName); + $this->files->deleteAtRelativePath($relPath); - return $this->getTestImage($imageName); + return $this->files->uploadedImage($imageName); } public function test_user_image_upload() @@ -559,10 +554,10 @@ class ImageTest extends TestCase $this->actingAs($admin); $imageName = 'unused-image.png'; - $relPath = $this->getTestImagePath('gallery', $imageName); - $this->deleteImage($relPath); + $relPath = $this->files->expectedImagePath('gallery', $imageName); + $this->files->deleteAtRelativePath($relPath); - $upload = $this->uploadImage($imageName, $page->id); + $upload = $this->files->uploadGalleryImage($this, $imageName, $page->id); $upload->assertStatus(200); $image = Image::where('type', '=', 'gallery')->first(); @@ -604,6 +599,6 @@ class ImageTest extends TestCase $this->assertCount(1, $toDelete); $this->assertFalse(file_exists($absPath)); - $this->deleteImage($relPath); + $this->files->deleteAtRelativePath($relPath); } } diff --git a/tests/Uploads/UsesImages.php b/tests/Uploads/UsesImages.php deleted file mode 100644 index e2c16c37c..000000000 --- a/tests/Uploads/UsesImages.php +++ /dev/null @@ -1,122 +0,0 @@ -getTestImageFilePath($base64FileName); - $data = file_get_contents($base64FilePath); - $decoded = base64_decode($data); - file_put_contents($imagePath, $decoded); - - return new UploadedFile($imagePath, $imageFileName, 'image/png', null, true); - } - - /** - * Get a test image that can be uploaded. - */ - protected function getTestImage(string $fileName, ?string $testDataFileName = null): UploadedFile - { - return new UploadedFile($this->getTestImageFilePath($testDataFileName), $fileName, 'image/png', null, true); - } - - /** - * Get the raw file data for the test image. - * - * @return false|string - */ - protected function getTestImageContent() - { - return file_get_contents($this->getTestImageFilePath()); - } - - /** - * Get the path for a test image. - */ - protected function getTestImagePath(string $type, string $fileName): string - { - return '/uploads/images/' . $type . '/' . date('Y-m') . '/' . $fileName; - } - - /** - * Uploads an image with the given name. - * - * @param $name - * @param int $uploadedTo - * @param string $contentType - * - * @return \Illuminate\Foundation\Testing\TestResponse - */ - protected function uploadImage($name, $uploadedTo = 0, $contentType = 'image/png', ?string $testDataFileName = null) - { - $file = $this->getTestImage($name, $testDataFileName); - - return $this->withHeader('Content-Type', $contentType) - ->call('POST', '/images/gallery', ['uploaded_to' => $uploadedTo], [], ['file' => $file], []); - } - - /** - * Upload a new gallery image. - * Returns the image name. - * Can provide a page to relate the image to. - * - * @param Page|null $page - * - * @return array{name: string, path: string, page: Page, response: stdClass} - */ - protected function uploadGalleryImage(Page $page = null, ?string $testDataFileName = null) - { - if ($page === null) { - $page = $this->entities->page(); - } - - $imageName = $testDataFileName ?? 'first-image.png'; - $relPath = $this->getTestImagePath('gallery', $imageName); - $this->deleteImage($relPath); - - $upload = $this->uploadImage($imageName, $page->id, 'image/png', $testDataFileName); - $upload->assertStatus(200); - - return [ - 'name' => $imageName, - 'path' => $relPath, - 'page' => $page, - 'response' => json_decode($upload->getContent()), - ]; - } - - /** - * Delete an uploaded image. - */ - protected function deleteImage(string $relPath) - { - $path = public_path($relPath); - if (file_exists($path)) { - unlink($path); - } - } -} diff --git a/tests/User/UserManagementTest.php b/tests/User/UserManagementTest.php index 38447d293..af17db52b 100644 --- a/tests/User/UserManagementTest.php +++ b/tests/User/UserManagementTest.php @@ -12,12 +12,9 @@ use Illuminate\Support\Str; use Mockery\MockInterface; use RuntimeException; use Tests\TestCase; -use Tests\Uploads\UsesImages; class UserManagementTest extends TestCase { - use UsesImages; - public function test_user_creation() { /** @var User $user */ @@ -282,7 +279,7 @@ class UserManagementTest extends TestCase public function test_user_avatar_update_and_reset() { $user = $this->users->viewer(); - $avatarFile = $this->getTestImage('avatar-icon.png'); + $avatarFile = $this->files->uploadedImage('avatar-icon.png'); $this->assertEquals(0, $user->image_id);