Testing covers main UI access, and main non-successfull import actions.
Started planning stored import model.
Extracted some text to language files.
{
// TODO - Show existing imports for user (or for all users if admin-level user)
- return view('exports.import');
+ return view('exports.import', [
+ 'zipErrors' => session()->pull('validation_errors') ?? [],
+ ]);
}
public function upload(Request $request)
$errors = (new ZipExportValidator($zipPath))->validate();
if ($errors) {
- dd($errors);
+ session()->flash('validation_errors', $errors);
+ return redirect('/import');
}
+
dd('passed');
- // TODO - Read existing ZIP upload and send through validator
- // TODO - If invalid, return user with errors
// TODO - Upload to storage
- // TODO - Store info/results from validator
+ // TODO - Store info/results for display:
+ // - zip_path
+ // - name (From name of thing being imported)
+ // - size
+ // - book_count
+ // - chapter_count
+ // - page_count
+ // - created_by
+ // - created_at/updated_at
// TODO - Send user to next import stage
}
}
{
// Validate file exists
if (!file_exists($this->zipPath) || !is_readable($this->zipPath)) {
- return ['format' => "Could not read ZIP file"];
+ return ['format' => trans('errors.import_zip_cant_read')];
}
// Validate file is valid zip
$zip = new \ZipArchive();
$opened = $zip->open($this->zipPath, ZipArchive::RDONLY);
if ($opened !== true) {
- return ['format' => "Could not read ZIP file"];
+ return ['format' => trans('errors.import_zip_cant_read')];
}
// Validate json data exists, including metadata
$jsonData = $zip->getFromName('data.json') ?: '';
$importData = json_decode($jsonData, true);
if (!$importData) {
- return ['format' => "Could not find and decode ZIP data.json content"];
+ return ['format' => trans('errors.import_zip_cant_decode_data')];
}
$helper = new ZipValidationHelper($zip);
$modelErrors = ZipExportPage::validate($helper, $importData['page']);
$keyPrefix = 'page';
} else {
- return ['format' => "ZIP file has no book, chapter or page data"];
+ return ['format' => trans('errors.import_zip_no_data')];
}
+
return $this->flattenModelErrors($modelErrors, $keyPrefix);
}
'default_template_select' => 'Select a template page',
'import' => 'Import',
'import_validate' => 'Validate Import',
+ 'import_desc' => 'Import books, chapters & pages using a portable zip export from the same, or a different, instance. Select a ZIP file to import then press "Validate Import" to proceed. After the file has been uploaded and validated you\'ll be able to configure & confirm the import in the next view.',
+ 'import_zip_select' => 'Select ZIP file to upload',
+ 'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:',
// Permissions and restrictions
'permissions' => 'Permissions',
'app_down' => ':appName is down right now',
'back_soon' => 'It will be back up soon.',
+ // Import
+ 'import_zip_cant_read' => 'Could not read ZIP file.',
+ 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
+ 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
+
// API errors
'api_no_authorization_found' => 'No authorization token found on the request',
'api_bad_authorization_format' => 'An authorization token was found on the request but the format appeared incorrect',
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
- 'zip_model_expected' => 'Data object expected but ":type" found',
+ 'zip_model_expected' => 'Data object expected but ":type" found.',
// Custom validation lines
'custom' => [
<form action="{{ url('/import') }}" enctype="multipart/form-data" method="POST">
{{ csrf_field() }}
<div class="flex-container-row justify-space-between wrap gap-x-xl gap-y-s">
- <p class="flex min-width-l text-muted mb-s">
- Import books, chapters & pages using a portable zip export from the same, or a different, instance.
- Select a ZIP file to import then press "Validate Import" to proceed.
- After the file has been uploaded and validated you'll be able to configure & confirm the import in the next view.
- </p>
+ <p class="flex min-width-l text-muted mb-s">{{ trans('entities.import_desc') }}</p>
<div class="flex-none min-width-l flex-container-row justify-flex-end">
<div class="mb-m">
- <label for="file">Select ZIP file to upload</label>
+ <label for="file">{{ trans('entities.import_zip_select') }}</label>
<input type="file"
accept=".zip,application/zip,application/x-zip-compressed"
name="file"
</div>
</div>
+ @if(count($zipErrors) > 0)
+ <p class="mb-xs"><strong class="text-neg">{{ trans('entities.import_zip_validation_errors') }}</strong></p>
+ <ul class="mb-m">
+ @foreach($zipErrors as $key => $error)
+ <li><strong class="text-neg">[{{ $key }}]</strong>: {{ $error }}</li>
+ @endforeach
+ </ul>
+ @endif
+
<div class="text-right">
<a href="{{ url('/books') }}" class="button outline">{{ trans('common.cancel') }}</a>
<button type="submit" class="button">{{ trans('entities.import_validate') }}</button>
--- /dev/null
+<?php
+
+namespace Tests\Exports;
+
+use Illuminate\Http\UploadedFile;
+use Illuminate\Testing\TestResponse;
+use Tests\TestCase;
+use ZipArchive;
+
+class ZipImportTest extends TestCase
+{
+ public function test_import_page_view()
+ {
+ $resp = $this->asAdmin()->get('/import');
+ $resp->assertSee('Import');
+ $this->withHtml($resp)->assertElementExists('form input[type="file"][name="file"]');
+ }
+
+ public function test_permissions_needed_for_import_page()
+ {
+ $user = $this->users->viewer();
+ $this->actingAs($user);
+
+ $resp = $this->get('/books');
+ $this->withHtml($resp)->assertLinkNotExists(url('/import'));
+ $resp = $this->get('/import');
+ $resp->assertRedirect('/');
+
+ $this->permissions->grantUserRolePermissions($user, ['content-import']);
+
+ $resp = $this->get('/books');
+ $this->withHtml($resp)->assertLinkExists(url('/import'));
+ $resp = $this->get('/import');
+ $resp->assertOk();
+ $resp->assertSeeText('Select ZIP file to upload');
+ }
+
+ public function test_zip_read_errors_are_shown_on_validation()
+ {
+ $invalidUpload = $this->files->uploadedImage('image.zip');
+
+ $this->asAdmin();
+ $resp = $this->runImportFromFile($invalidUpload);
+ $resp->assertRedirect('/import');
+
+ $resp = $this->followRedirects($resp);
+ $resp->assertSeeText('Could not read ZIP file');
+ }
+
+ public function test_error_shown_if_missing_data()
+ {
+ $zipFile = tempnam(sys_get_temp_dir(), 'bstest-');
+ $zip = new ZipArchive();
+ $zip->open($zipFile, ZipArchive::CREATE);
+ $zip->addFromString('beans', 'cat');
+ $zip->close();
+
+ $this->asAdmin();
+ $upload = new UploadedFile($zipFile, 'upload.zip', 'application/zip', null, true);
+ $resp = $this->runImportFromFile($upload);
+ $resp->assertRedirect('/import');
+
+ $resp = $this->followRedirects($resp);
+ $resp->assertSeeText('Could not find and decode ZIP data.json content.');
+ }
+
+ public function test_error_shown_if_no_importable_key()
+ {
+ $this->asAdmin();
+ $resp = $this->runImportFromFile($this->zipUploadFromData([
+ 'instance' => []
+ ]));
+
+ $resp->assertRedirect('/import');
+ $resp = $this->followRedirects($resp);
+ $resp->assertSeeText('ZIP file data has no expected book, chapter or page content.');
+ }
+
+ public function test_zip_data_validation_messages_shown()
+ {
+ $this->asAdmin();
+ $resp = $this->runImportFromFile($this->zipUploadFromData([
+ 'book' => [
+ 'id' => 4,
+ 'pages' => [
+ 'cat',
+ [
+ 'name' => 'My inner page',
+ 'tags' => [
+ [
+ 'value' => 5
+ ]
+ ],
+ ]
+ ],
+ ]
+ ]));
+
+ $resp->assertRedirect('/import');
+ $resp = $this->followRedirects($resp);
+
+ $resp->assertSeeText('[book.name]: The name field is required.');
+ $resp->assertSeeText('[book.pages.0.0]: Data object expected but "string" found.');
+ $resp->assertSeeText('[book.pages.1.tags.0.name]: The name field is required.');
+ $resp->assertSeeText('[book.pages.1.tags.0.value]: The value must be a string.');
+ }
+
+ protected function runImportFromFile(UploadedFile $file): TestResponse
+ {
+ return $this->call('POST', '/import', [], [], ['file' => $file]);
+ }
+
+ protected function zipUploadFromData(array $data): UploadedFile
+ {
+ $zipFile = tempnam(sys_get_temp_dir(), 'bstest-');
+
+ $zip = new ZipArchive();
+ $zip->open($zipFile, ZipArchive::CREATE);
+ $zip->addFromString('data.json', json_encode($data));
+ $zip->close();
+
+ return new UploadedFile($zipFile, 'upload.zip', 'application/zip', null, true);
+ }
+}