'Keep uploaded test files in storage (default: delete after success).', '--pdf' => 'Use this PDF file path for policy_pdf upload instead of a generated sample.', ]; /** @var list */ private array $uploadedObjects = []; private string $tempDir = ''; public function run(array $params) { helper('storage_helper'); $config = config('Storage'); $this->printEnvironment($config); $this->tempDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'nhance_storage_upload_test_' . uniqid('', true); if (! mkdir($this->tempDir, 0777, true) && ! is_dir($this->tempDir)) { CLI::error('Unable to create temp directory: ' . $this->tempDir); return EXIT_ERROR; } $keepFiles = (bool) CLI::getOption('keep'); $failed = 0; $passed = 0; CLI::newLine(); CLI::write('Starting live storage upload scenarios...', 'yellow'); CLI::newLine(); $scenarios = [ 'policy_pdf' => fn () => $this->scenarioPolicyPdf(), 'policy_payment_receipt' => fn () => $this->scenarioPolicyReceipt(), 'policy_md' => fn () => $this->scenarioPolicyMarkdown(), 'endorsement_original' => fn () => $this->scenarioEndorsementOriginal(), 'endorsement_completion' => fn () => $this->scenarioEndorsementCompletion(), 'agent_certificate' => fn () => $this->scenarioGeneric('agent', 'certificate_file', 'certificate.pdf'), 'agent_incentive' => fn () => $this->scenarioGeneric('agent', 'incentive_file', 'incentive.xlsx'), 'enquiry_id_proof' => fn () => $this->scenarioGeneric('enquiry', 'id_proof', 'id_proof.pdf'), 'enquiry_rc' => fn () => $this->scenarioGeneric('enquiry', 'rc', 'rc.pdf'), 'enquiry_previous_policy' => fn () => $this->scenarioGeneric('enquiry', 'previous_policy', 'previous_policy.pdf'), 'enquiry_root' => fn () => $this->scenarioGeneric('enquiry', '', 'enquiry_extra.pdf'), 'quotation' => fn () => $this->scenarioGeneric('quotation', '', 'quotation_extra.pdf'), 'claims' => fn () => $this->scenarioGeneric('claims', '', 'claim_file.pdf'), ]; foreach ($scenarios as $label => $callback) { try { $callback(); CLI::write('[PASS] ' . $label, 'green'); $passed++; } catch (\Throwable $e) { CLI::error('[FAIL] ' . $label . ' -> ' . $e->getMessage()); $failed++; } CLI::newLine(); } if (! $keepFiles && $failed === 0) { $this->cleanupUploadedObjects(); CLI::write('Cleaned up uploaded test files from storage.', 'dark_gray'); } elseif ($keepFiles) { CLI::write('Uploaded test files were kept (--keep).', 'yellow'); foreach ($this->uploadedObjects as $object) { CLI::write( ' - ' . storage()->resolveKey($object['module'], $object['subFolder'], $object['fileName']), 'white' ); } } $this->cleanupTempDir(); CLI::newLine(); CLI::write("Summary: {$passed} passed, {$failed} failed", $failed === 0 ? 'green' : 'red'); return $failed === 0 ? EXIT_SUCCESS : EXIT_ERROR; } private function printEnvironment(Storage $config): void { CLI::write('Storage upload test', 'green'); CLI::write('Driver: ' . ($config->driver === 's3' ? 's3' : 'local'), 'cyan'); if ($config->driver === 's3') { CLI::write('Bucket: ' . ($config->s3Bucket ?: '(empty)'), 'cyan'); CLI::write('Region: ' . $config->s3Region, 'cyan'); CLI::write('Prefix: ' . ($config->s3Prefix !== '' ? $config->s3Prefix : '(none)'), 'cyan'); } else { CLI::write('Local root: ' . $config->localRoot, 'cyan'); } } private function scenarioPolicyPdf(): void { $customPdf = CLI::getOption('pdf'); $source = is_string($customPdf) && $customPdf !== '' && is_file($customPdf) ? $customPdf : $this->createSamplePdf(); $fileName = time() . '_spark_test_policy.pdf'; $content = file_get_contents($source); if ($content === false || $content === '') { throw new FileStorageException('Sample PDF is empty or unreadable'); } $this->uploadFile('policy', 'policy_pdf', $fileName, $source, $content); $this->assertReadable('policy', 'policy_pdf', $fileName, $content); $this->assertLocalProcessingPath('policy', 'policy_pdf', $fileName); $this->assertTemporaryUrl('policy', 'policy_pdf', $fileName); } private function scenarioPolicyReceipt(): void { $source = $this->createTempFile('sample_receipt.jpg', 'JPEG receipt test content for spark upload-test'); $fileName = time() . '_spark_test_receipt.jpg'; $content = file_get_contents($source); $this->uploadFile('policy', 'policy_payment_receipt', $fileName, $source, $content); $this->assertReadable('policy', 'policy_payment_receipt', $fileName, $content); $this->assertTemporaryUrl('policy', 'policy_payment_receipt', $fileName); } private function scenarioPolicyMarkdown(): void { $fileName = time() . '_spark_test_policy.md'; $content = "# Spark Storage Test\n\n" . str_repeat('Policy markdown body. ', 40); storage_put('policy', 'policy_md', $fileName, $content, ['content_type' => 'text/markdown']); $this->trackUploaded('policy', 'policy_md', $fileName); if (! policy_md_exists($fileName)) { throw new FileStorageException('policy_md file not found after upload'); } if (policy_read_md($fileName) !== $content) { throw new FileStorageException('policy_md content mismatch after read'); } } private function scenarioEndorsementOriginal(): void { $source = $this->createTempFile('endorsement_original.pdf', '%PDF-1.4 endorsement original test'); $fileName = time() . '_spark_test_endorsement.pdf'; $content = file_get_contents($source); $this->uploadFile('endorsement', '', $fileName, $source, $content); $this->assertReadable('endorsement', '', $fileName, $content); $this->assertTemporaryUrl('endorsement', '', $fileName); } private function scenarioEndorsementCompletion(): void { $source = $this->createTempFile('endorsement_completion.pdf', '%PDF-1.4 endorsement completion test'); $fileName = time() . '_spark_test_completion.pdf'; $content = file_get_contents($source); $this->uploadFile('endorsement', 'endorsement_pdf', $fileName, $source, $content); $this->assertReadable('endorsement', 'endorsement_pdf', $fileName, $content); $this->assertTemporaryUrl('endorsement', 'endorsement_pdf', $fileName); } private function scenarioGeneric(string $module, string $subFolder, string $sampleName): void { $source = $this->createTempFile($sampleName, '%PDF-1.4 storage module test for ' . $module); $fileName = time() . '_spark_test_' . preg_replace('/[^a-z0-9]+/i', '_', $sampleName); $content = file_get_contents($source); $this->uploadFile($module, $subFolder, $fileName, $source, $content); $this->assertReadable($module, $subFolder, $fileName, $content); $this->assertTemporaryUrl($module, $subFolder, $fileName); } /** * @param string|false $content */ private function uploadFile( string $module, string $subFolder, string $fileName, string $sourcePath, $content ): void { storage()->upload($sourcePath, storage()->resolveKey($module, $subFolder, $fileName)); $this->trackUploaded($module, $subFolder, $fileName); if (! storage_exists($module, $subFolder, $fileName)) { throw new FileStorageException('File not found immediately after upload'); } if ($content !== false && storage_read($module, $subFolder, $fileName) !== $content) { throw new FileStorageException('Uploaded file content mismatch on read-back'); } CLI::write(' uploaded: ' . storage()->resolveKey($module, $subFolder, $fileName), 'white'); } /** * @param string|false $expectedContent */ private function assertReadable(string $module, string $subFolder, string $fileName, $expectedContent): void { if (! storage_exists($module, $subFolder, $fileName)) { throw new FileStorageException('exists() returned false'); } $readBack = storage_read($module, $subFolder, $fileName); if ($expectedContent !== false && $readBack !== $expectedContent) { throw new FileStorageException('read() content mismatch'); } CLI::write(' read-back: OK (' . strlen($readBack) . ' bytes)', 'white'); } private function assertLocalProcessingPath(string $module, string $subFolder, string $fileName): void { $path = storage_local_path($module, $subFolder, $fileName); try { if (! is_file($path)) { throw new FileStorageException('Local processing path not readable: ' . $path); } CLI::write(' local path: ' . $path, 'white'); } finally { storage_cleanup_temp($path); } } private function assertTemporaryUrl(string $module, string $subFolder, string $fileName): void { $url = storage_temporary_url($module, $subFolder, $fileName); if ($url === '') { throw new FileStorageException('temporary URL is empty'); } CLI::write(' url: ' . $url, 'white'); } private function trackUploaded(string $module, string $subFolder, string $fileName): void { $this->uploadedObjects[] = [ 'module' => $module, 'subFolder' => $subFolder, 'fileName' => $fileName, ]; } private function cleanupUploadedObjects(): void { foreach ($this->uploadedObjects as $object) { try { storage_delete($object['module'], $object['subFolder'], $object['fileName']); } catch (\Throwable $e) { CLI::write( ' cleanup warning: ' . $object['fileName'] . ' -> ' . $e->getMessage(), 'yellow' ); } } } private function cleanupTempDir(): void { if ($this->tempDir === '' || ! is_dir($this->tempDir)) { return; } foreach (scandir($this->tempDir) ?: [] as $item) { if ($item === '.' || $item === '..') { continue; } @unlink($this->tempDir . DIRECTORY_SEPARATOR . $item); } @rmdir($this->tempDir); } private function createSamplePdf(): string { return $this->createTempFile( 'sample_policy.pdf', "%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\nxref\n0 3\n0000000000 65535 f \n0000000009 00000 n \n0000000058 00000 n \ntrailer\n<< /Size 3 /Root 1 0 R >>\nstartxref\n110\n%%EOF\n" ); } private function createTempFile(string $name, string $contents): string { $path = $this->tempDir . DIRECTORY_SEPARATOR . $name; if (file_put_contents($path, $contents) === false) { throw new FileStorageException('Failed to create temp file: ' . $path); } return $path; } }