Files
michaelschiemer/tests/Unit/Framework/Filesystem/FileStorageIntegrationTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

284 lines
10 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\FileSize;
use App\Framework\Filesystem\FileStorage;
use App\Framework\Filesystem\FileValidator;
use App\Framework\Filesystem\Exceptions\FileValidationException;
use App\Framework\Filesystem\Exceptions\FileNotFoundException;
describe('FileStorage Integration', function () {
beforeEach(function () {
// Create temp directory for tests
$this->testDir = sys_get_temp_dir() . '/filesystem_test_' . uniqid();
mkdir($this->testDir, 0777, true);
});
afterEach(function () {
// Clean up temp directory
if (is_dir($this->testDir)) {
$files = glob($this->testDir . '/*');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
}
rmdir($this->testDir);
}
});
// Storage without Validator or Logger
it('works without validator or logger', function () {
$storage = new FileStorage($this->testDir);
$storage->put('test.txt', 'Hello World');
$content = $storage->get('test.txt');
expect($content)->toBe('Hello World');
expect($storage->exists('test.txt'))->toBeTrue();
});
// Storage with FileValidator integration
it('validates path traversal attempts with validator', function () {
$validator = FileValidator::createDefault();
$storage = new FileStorage($this->testDir, validator: $validator);
try {
$storage->get('../../../etc/passwd');
expect(true)->toBeFalse('Should have thrown exception');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('Path traversal');
}
});
it('validates file extensions with validator on write', function () {
$validator = FileValidator::createStrict(['txt', 'json']);
$storage = new FileStorage($this->testDir, validator: $validator);
// Allowed extension should work
$storage->put('allowed.txt', 'content');
expect($storage->exists('allowed.txt'))->toBeTrue();
// Disallowed extension should fail
try {
$storage->put('blocked.exe', 'malicious');
expect(true)->toBeFalse('Should have thrown exception');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('not allowed');
}
});
it('validates file size with validator on write', function () {
$validator = FileValidator::forUploads(FileSize::fromKilobytes(1)); // 1KB limit
$storage = new FileStorage($this->testDir, validator: $validator);
// Small file should work
$storage->put('small.txt', 'Small content');
expect($storage->exists('small.txt'))->toBeTrue();
// Large file should fail
$largeContent = str_repeat('X', 2048); // 2KB
try {
$storage->put('large.txt', $largeContent);
expect(true)->toBeFalse('Should have thrown exception');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('exceeds maximum');
}
});
// Note: validateRead() does not validate extensions by design
// Extensions are validated on write operations only
// This is the correct behavior - when reading files, extension doesn't matter
// Edge cases
it('handles nonexistent files with validator', function () {
$validator = FileValidator::createDefault();
$storage = new FileStorage($this->testDir, validator: $validator);
try {
$storage->get('nonexistent.txt');
expect(true)->toBeFalse('Should have thrown exception');
} catch (FileNotFoundException $e) {
expect($e)->toBeInstanceOf(FileNotFoundException::class);
}
});
it('validates before attempting file operations', function () {
$validator = FileValidator::forUploads();
$storage = new FileStorage($this->testDir, validator: $validator);
// Path traversal should fail validation before attempting filesystem operation
try {
$storage->put('../../../tmp/malicious.txt', 'content');
expect(true)->toBeFalse('Should have thrown exception');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('Path traversal');
}
// Verify file was NOT created (path traversal prevented)
expect(file_exists($this->testDir . '/../../../tmp/malicious.txt'))->toBeFalse();
});
// Performance: Large file handling
it('handles large files efficiently', function () {
$validator = FileValidator::forUploads(FileSize::fromMegabytes(10));
$storage = new FileStorage($this->testDir, validator: $validator);
// Create 1MB file
$largeContent = str_repeat('X', 1024 * 1024);
$storage->put('large.txt', $largeContent);
$content = $storage->get('large.txt');
expect(strlen($content))->toBe(1024 * 1024);
});
// Multiple operations workflow
it('handles complete file lifecycle with validation', function () {
$validator = FileValidator::createStrict(['txt', 'json']);
$storage = new FileStorage($this->testDir, validator: $validator);
// Create
$storage->put('lifecycle.txt', 'initial content');
expect($storage->exists('lifecycle.txt'))->toBeTrue();
// Read
$content = $storage->get('lifecycle.txt');
expect($content)->toBe('initial content');
// Update
$storage->put('lifecycle.txt', 'updated content');
$updatedContent = $storage->get('lifecycle.txt');
expect($updatedContent)->toBe('updated content');
// Copy
$storage->copy('lifecycle.txt', 'lifecycle-copy.txt');
expect($storage->exists('lifecycle-copy.txt'))->toBeTrue();
// Delete
$storage->delete('lifecycle.txt');
expect($storage->exists('lifecycle.txt'))->toBeFalse();
// Copy still exists
expect($storage->exists('lifecycle-copy.txt'))->toBeTrue();
});
// Validator prevents invalid operations
it('prevents path traversal on read operations', function () {
$validator = FileValidator::createDefault();
$storage = new FileStorage($this->testDir, validator: $validator);
try {
$storage->get('../../sensitive/file.txt');
expect(true)->toBeFalse('Should have thrown exception');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('Path traversal');
}
});
it('prevents path traversal on copy operations', function () {
$validator = FileValidator::createDefault();
$storage = new FileStorage($this->testDir, validator: $validator);
$storage->put('source.txt', 'content');
try {
$storage->copy('source.txt', '../../../etc/target.txt');
expect(true)->toBeFalse('Should have thrown exception');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('Path traversal');
}
});
it('prevents path traversal on delete operations', function () {
$validator = FileValidator::createDefault();
$storage = new FileStorage($this->testDir, validator: $validator);
try {
$storage->delete('../../../important/file.txt');
expect(true)->toBeFalse('Should have thrown exception');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('Path traversal');
}
});
// URL-encoded path traversal prevention
it('prevents URL-encoded path traversal attacks', function () {
$validator = FileValidator::createDefault();
$storage = new FileStorage($this->testDir, validator: $validator);
try {
$storage->put('%2e%2e/%2e%2e/malicious.txt', 'content');
expect(true)->toBeFalse('Should have thrown exception');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('Path traversal');
}
});
// Multiple validator configurations
it('enforces strict whitelist validation', function () {
$validator = FileValidator::createStrict(['txt']);
$storage = new FileStorage($this->testDir, validator: $validator);
// Only .txt should work
$storage->put('allowed.txt', 'content');
expect($storage->exists('allowed.txt'))->toBeTrue();
// Everything else should fail
$blocked = ['file.json', 'file.pdf', 'file.exe', 'file.jpg'];
foreach ($blocked as $filename) {
try {
$storage->put($filename, 'content');
expect(true)->toBeFalse("Should have blocked {$filename}");
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('not allowed');
}
}
});
// Image validator integration
it('works with image validator', function () {
$validator = FileValidator::forImages();
$storage = new FileStorage($this->testDir, validator: $validator);
// Image extensions should work
$allowedImages = ['photo.jpg', 'image.png', 'graphic.gif', 'icon.webp'];
foreach ($allowedImages as $filename) {
$storage->put($filename, 'fake image data');
expect($storage->exists($filename))->toBeTrue();
}
// Non-image should fail
try {
$storage->put('document.pdf', 'content');
expect(true)->toBeFalse('Should have thrown exception');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('not allowed');
}
});
// Upload validator integration
it('works with upload validator', function () {
$validator = FileValidator::forUploads();
$storage = new FileStorage($this->testDir, validator: $validator);
// Common upload types should work
$allowedFiles = ['data.json', 'report.pdf', 'document.txt', 'data.csv'];
foreach ($allowedFiles as $filename) {
$storage->put($filename, 'content');
expect($storage->exists($filename))->toBeTrue();
}
// Executable should fail
try {
$storage->put('malware.exe', 'malicious');
expect(true)->toBeFalse('Should have thrown exception');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('not allowed');
}
});
});