Skip to content

Commit

Permalink
Extracted test file handling to its own class
Browse files Browse the repository at this point in the history
Closes #3995
  • Loading branch information
ssddanbrown committed Feb 8, 2023
1 parent 5d18e7d commit da1a66a
Show file tree
Hide file tree
Showing 17 changed files with 293 additions and 309 deletions.
2 changes: 2 additions & 0 deletions app/Uploads/Attachment.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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;

Expand All @@ -29,6 +30,7 @@
class Attachment extends Model
{
use HasCreatorAndUpdater;
use HasFactory;

protected $fillable = ['name', 'order'];
protected $hidden = ['path', 'page'];
Expand Down
39 changes: 39 additions & 0 deletions database/factories/Uploads/AttachmentFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Database\Factories\Uploads;

use BookStack\Auth\User;
use BookStack\Entities\Models\Page;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Uploads\Attachment>
*/
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<string, mixed>
*/
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,
];
}
}
4 changes: 1 addition & 3 deletions tests/Api/BooksApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
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';

Expand Down Expand Up @@ -157,7 +155,7 @@ public function test_update_cover_image_control()
/** @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}", [
Expand Down
4 changes: 1 addition & 3 deletions tests/Api/ShelvesApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
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';

Expand Down Expand Up @@ -154,7 +152,7 @@ public function test_update_cover_image_control()
/** @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}", [
Expand Down
5 changes: 1 addition & 4 deletions tests/Entity/BookShelfTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@
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();
Expand Down Expand Up @@ -114,7 +111,7 @@ public function test_shelves_create_sets_cover_image()
'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();

Expand Down
5 changes: 1 addition & 4 deletions tests/Entity/BookTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@
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([
Expand Down Expand Up @@ -333,7 +330,7 @@ public function test_copy_clones_cover_image_if_existing()
{
$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']);
Expand Down
11 changes: 4 additions & 7 deletions tests/Entity/PageContentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@
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()
Expand Down Expand Up @@ -591,7 +588,7 @@ public function test_base64_images_get_extracted_from_page_content()
$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()
Expand All @@ -615,7 +612,7 @@ public function test_base64_images_get_extracted_when_containing_whitespace()
$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()
Expand Down Expand Up @@ -659,7 +656,7 @@ public function test_base64_images_get_extracted_from_markdown_page_content()
$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()
Expand Down Expand Up @@ -690,7 +687,7 @@ public function test_markdown_base64_extract_not_limited_by_pcre_limits()
$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);
}
Expand Down
153 changes: 153 additions & 0 deletions tests/Helpers/FileProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

namespace Tests\Helpers;

use BookStack\Entities\Models\Page;
use BookStack\Uploads\Attachment;
use BookStack\Uploads\AttachmentService;
use Illuminate\Http\UploadedFile;
use Illuminate\Testing\TestResponse;
use stdClass;
use Tests\TestCase;

class FileProvider
{
/**
* Get the path to a file in the test-data-directory.
*/
public function testFilePath(string $fileName): string
{
return base_path('tests/test-data/' . $fileName);
}

/**
* Creates a new temporary image file using the given name,
* with the content decoded from the given bas64 file name.
* Is generally used for testing sketchy files that could trip AV.
*/
public function imageFromBase64File(string $base64FileName, string $imageFileName): UploadedFile
{
$imagePath = implode(DIRECTORY_SEPARATOR, [sys_get_temp_dir(), $imageFileName]);
$base64FilePath = $this->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);
}
}
}
7 changes: 2 additions & 5 deletions tests/OpenGraphTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@
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();
Expand Down Expand Up @@ -47,7 +44,7 @@ public function test_book_tags()

// 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);

Expand All @@ -67,7 +64,7 @@ public function test_shelf_tags()

// 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);

Expand Down
5 changes: 1 addition & 4 deletions tests/Settings/SettingsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -47,7 +44,7 @@ public function test_not_found_setting_category_throws_404()
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'));
Expand Down
Loading

0 comments on commit da1a66a

Please sign in to comment.