BookStack/tests/Exports/ZipImportRunnerTest.php
Dan Brown 59cfc087e1
ZIP Imports: Added image type validation/handling
Images were missing their extension after import since it was
(potentially) not part of the import data.
This adds validation via mime sniffing (to match normal image upload
checks) and also uses the same logic to sniff out a correct extension.

Added tests to cover.
Also fixed some existing tests around zip functionality.
2024-11-18 17:42:49 +00:00

397 lines
16 KiB
PHP

<?php
namespace Tests\Exports;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use BookStack\Exports\ZipExports\ZipImportRunner;
use BookStack\Uploads\Image;
use Tests\TestCase;
class ZipImportRunnerTest extends TestCase
{
protected ZipImportRunner $runner;
protected function setUp(): void
{
parent::setUp();
$this->runner = app()->make(ZipImportRunner::class);
}
public function test_book_import()
{
$testImagePath = $this->files->testFilePath('test-image.png');
$testFilePath = $this->files->testFilePath('test-file.txt');
$import = ZipTestHelper::importFromData([], [
'book' => [
'id' => 5,
'name' => 'Import test',
'cover' => 'book_cover_image',
'description_html' => '<p><a href="[[bsexport:page:3]]">Link to chapter page</a></p>',
'tags' => [
['name' => 'Animal', 'value' => 'Cat'],
['name' => 'Category', 'value' => 'Test'],
],
'chapters' => [
[
'id' => 6,
'name' => 'Chapter A',
'description_html' => '<p><a href="[[bsexport:book:5]]">Link to book</a></p>',
'priority' => 1,
'tags' => [
['name' => 'Reviewed'],
['name' => 'Category', 'value' => 'Test Chapter'],
],
'pages' => [
[
'id' => 3,
'name' => 'Page A',
'priority' => 6,
'html' => '
<p><a href="[[bsexport:page:3]]">Link to self</a></p>
<p><a href="[[bsexport:image:1]]">Link to cat image</a></p>
<p><a href="[[bsexport:attachment:4]]">Link to text attachment</a></p>',
'tags' => [
['name' => 'Unreviewed'],
],
'attachments' => [
[
'id' => 4,
'name' => 'Text attachment',
'file' => 'file_attachment'
],
[
'name' => 'Cats',
'link' => 'https://example.com/cats',
]
],
'images' => [
[
'id' => 1,
'name' => 'Cat',
'type' => 'gallery',
'file' => 'cat_image'
],
[
'id' => 2,
'name' => 'Dog Drawing',
'type' => 'drawio',
'file' => 'dog_image'
]
],
],
],
],
[
'name' => 'Chapter child B',
'priority' => 5,
]
],
'pages' => [
[
'name' => 'Page C',
'markdown' => '[Link to text]([[bsexport:attachment:4]]?scale=big)',
'priority' => 3,
]
],
],
], [
'book_cover_image' => $testImagePath,
'file_attachment' => $testFilePath,
'cat_image' => $testImagePath,
'dog_image' => $testImagePath,
]);
$this->asAdmin();
/** @var Book $book */
$book = $this->runner->run($import);
// Book checks
$this->assertEquals('Import test', $book->name);
$this->assertFileExists(public_path($book->cover->path));
$this->assertCount(2, $book->tags);
$this->assertEquals('Cat', $book->tags()->first()->value);
$this->assertCount(2, $book->chapters);
$this->assertEquals(1, $book->directPages()->count());
// Chapter checks
$chapterA = $book->chapters()->where('name', 'Chapter A')->first();
$this->assertCount(2, $chapterA->tags);
$firstChapterTag = $chapterA->tags()->first();
$this->assertEquals('Reviewed', $firstChapterTag->name);
$this->assertEquals('', $firstChapterTag->value);
$this->assertCount(1, $chapterA->pages);
// Page checks
/** @var Page $pageA */
$pageA = $chapterA->pages->first();
$this->assertEquals('Page A', $pageA->name);
$this->assertCount(1, $pageA->tags);
$firstPageTag = $pageA->tags()->first();
$this->assertEquals('Unreviewed', $firstPageTag->name);
$this->assertCount(2, $pageA->attachments);
$firstAttachment = $pageA->attachments->first();
$this->assertEquals('Text attachment', $firstAttachment->name);
$this->assertFileEquals($testFilePath, storage_path($firstAttachment->path));
$this->assertFalse($firstAttachment->external);
$secondAttachment = $pageA->attachments->last();
$this->assertEquals('Cats', $secondAttachment->name);
$this->assertEquals('https://example.com/cats', $secondAttachment->path);
$this->assertTrue($secondAttachment->external);
$pageAImages = Image::where('uploaded_to', '=', $pageA->id)->whereIn('type', ['gallery', 'drawio'])->get();
$this->assertCount(2, $pageAImages);
$this->assertEquals('Cat', $pageAImages[0]->name);
$this->assertEquals('gallery', $pageAImages[0]->type);
$this->assertFileEquals($testImagePath, public_path($pageAImages[0]->path));
$this->assertEquals('Dog Drawing', $pageAImages[1]->name);
$this->assertEquals('drawio', $pageAImages[1]->type);
// Book order check
$children = $book->getDirectVisibleChildren()->values()->all();
$this->assertEquals($children[0]->name, 'Chapter A');
$this->assertEquals($children[1]->name, 'Page C');
$this->assertEquals($children[2]->name, 'Chapter child B');
// Reference checks
$textAttachmentUrl = $firstAttachment->getUrl();
$this->assertStringContainsString($pageA->getUrl(), $book->description_html);
$this->assertStringContainsString($book->getUrl(), $chapterA->description_html);
$this->assertStringContainsString($pageA->getUrl(), $pageA->html);
$this->assertStringContainsString($pageAImages[0]->getThumb(1680, null, true), $pageA->html);
$this->assertStringContainsString($firstAttachment->getUrl(), $pageA->html);
// Reference in converted markdown
$pageC = $children[1];
$this->assertStringContainsString("href=\"{$textAttachmentUrl}?scale=big\"", $pageC->html);
ZipTestHelper::deleteZipForImport($import);
}
public function test_chapter_import()
{
$testImagePath = $this->files->testFilePath('test-image.png');
$testFilePath = $this->files->testFilePath('test-file.txt');
$parent = $this->entities->book();
$import = ZipTestHelper::importFromData([], [
'chapter' => [
'id' => 6,
'name' => 'Chapter A',
'description_html' => '<p><a href="[[bsexport:page:3]]">Link to page</a></p>',
'priority' => 1,
'tags' => [
['name' => 'Reviewed', 'value' => '2024'],
],
'pages' => [
[
'id' => 3,
'name' => 'Page A',
'priority' => 6,
'html' => '<p><a href="[[bsexport:chapter:6]]">Link to chapter</a></p>
<p><a href="[[bsexport:image:2]]">Link to dog drawing</a></p>
<p><a href="[[bsexport:attachment:4]]">Link to text attachment</a></p>',
'tags' => [
['name' => 'Unreviewed'],
],
'attachments' => [
[
'id' => 4,
'name' => 'Text attachment',
'file' => 'file_attachment'
]
],
'images' => [
[
'id' => 2,
'name' => 'Dog Drawing',
'type' => 'drawio',
'file' => 'dog_image'
]
],
],
[
'name' => 'Page B',
'markdown' => '[Link to page A]([[bsexport:page:3]])',
'priority' => 9,
],
],
],
], [
'file_attachment' => $testFilePath,
'dog_image' => $testImagePath,
]);
$this->asAdmin();
/** @var Chapter $chapter */
$chapter = $this->runner->run($import, $parent);
// Chapter checks
$this->assertEquals('Chapter A', $chapter->name);
$this->assertEquals($parent->id, $chapter->book_id);
$this->assertCount(1, $chapter->tags);
$firstChapterTag = $chapter->tags()->first();
$this->assertEquals('Reviewed', $firstChapterTag->name);
$this->assertEquals('2024', $firstChapterTag->value);
$this->assertCount(2, $chapter->pages);
// Page checks
/** @var Page $pageA */
$pageA = $chapter->pages->first();
$this->assertEquals('Page A', $pageA->name);
$this->assertCount(1, $pageA->tags);
$this->assertCount(1, $pageA->attachments);
$pageAImages = Image::where('uploaded_to', '=', $pageA->id)->whereIn('type', ['gallery', 'drawio'])->get();
$this->assertCount(1, $pageAImages);
// Reference checks
$attachment = $pageA->attachments->first();
$this->assertStringContainsString($pageA->getUrl(), $chapter->description_html);
$this->assertStringContainsString($chapter->getUrl(), $pageA->html);
$this->assertStringContainsString($pageAImages[0]->url, $pageA->html);
$this->assertStringContainsString($attachment->getUrl(), $pageA->html);
ZipTestHelper::deleteZipForImport($import);
}
public function test_page_import()
{
$testImagePath = $this->files->testFilePath('test-image.png');
$testFilePath = $this->files->testFilePath('test-file.txt');
$parent = $this->entities->chapter();
$import = ZipTestHelper::importFromData([], [
'page' => [
'id' => 3,
'name' => 'Page A',
'priority' => 6,
'html' => '<p><a href="[[bsexport:page:3]]">Link to self</a></p>
<p><a href="[[bsexport:image:2]]">Link to dog drawing</a></p>
<p><a href="[[bsexport:attachment:4]]">Link to text attachment</a></p>',
'tags' => [
['name' => 'Unreviewed'],
],
'attachments' => [
[
'id' => 4,
'name' => 'Text attachment',
'file' => 'file_attachment'
]
],
'images' => [
[
'id' => 2,
'name' => 'Dog Drawing',
'type' => 'drawio',
'file' => 'dog_image'
]
],
],
], [
'file_attachment' => $testFilePath,
'dog_image' => $testImagePath,
]);
$this->asAdmin();
/** @var Page $page */
$page = $this->runner->run($import, $parent);
// Page checks
$this->assertEquals('Page A', $page->name);
$this->assertCount(1, $page->tags);
$this->assertCount(1, $page->attachments);
$pageImages = Image::where('uploaded_to', '=', $page->id)->whereIn('type', ['gallery', 'drawio'])->get();
$this->assertCount(1, $pageImages);
$this->assertFileEquals($testImagePath, public_path($pageImages[0]->path));
// Reference checks
$this->assertStringContainsString($page->getUrl(), $page->html);
$this->assertStringContainsString($pageImages[0]->url, $page->html);
$this->assertStringContainsString($page->attachments->first()->getUrl(), $page->html);
ZipTestHelper::deleteZipForImport($import);
}
public function test_revert_cleans_up_uploaded_files()
{
$testImagePath = $this->files->testFilePath('test-image.png');
$testFilePath = $this->files->testFilePath('test-file.txt');
$parent = $this->entities->chapter();
$import = ZipTestHelper::importFromData([], [
'page' => [
'name' => 'Page A',
'html' => '<p>Hello</p>',
'attachments' => [
[
'name' => 'Text attachment',
'file' => 'file_attachment'
]
],
'images' => [
[
'name' => 'Dog Image',
'type' => 'gallery',
'file' => 'dog_image'
]
],
],
], [
'file_attachment' => $testFilePath,
'dog_image' => $testImagePath,
]);
$this->asAdmin();
/** @var Page $page */
$page = $this->runner->run($import, $parent);
$attachment = $page->attachments->first();
$image = Image::query()->where('uploaded_to', '=', $page->id)->where('type', '=', 'gallery')->first();
$this->assertFileExists(public_path($image->path));
$this->assertFileExists(storage_path($attachment->path));
$this->runner->revertStoredFiles();
$this->assertFileDoesNotExist(public_path($image->path));
$this->assertFileDoesNotExist(storage_path($attachment->path));
ZipTestHelper::deleteZipForImport($import);
}
public function test_imported_images_have_their_detected_extension_added()
{
$testImagePath = $this->files->testFilePath('test-image.png');
$parent = $this->entities->chapter();
$import = ZipTestHelper::importFromData([], [
'page' => [
'name' => 'Page A',
'html' => '<p>hello</p>',
'images' => [
[
'id' => 2,
'name' => 'Cat',
'type' => 'gallery',
'file' => 'cat_image'
]
],
],
], [
'cat_image' => $testImagePath,
]);
$this->asAdmin();
/** @var Page $page */
$page = $this->runner->run($import, $parent);
$pageImages = Image::where('uploaded_to', '=', $page->id)->whereIn('type', ['gallery', 'drawio'])->get();
$this->assertCount(1, $pageImages);
$this->assertStringEndsWith('.png', $pageImages[0]->url);
$this->assertStringEndsWith('.png', $pageImages[0]->path);
ZipTestHelper::deleteZipForImport($import);
}
}