Files
michaelschiemer/tests/Framework/LiveComponents/QuarantineServiceTest.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

305 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Framework\LiveComponents;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Filesystem\InMemoryStorage;
use App\Framework\LiveComponents\Services\QuarantineService;
use App\Framework\LiveComponents\ValueObjects\QuarantineStatus;
use App\Framework\LiveComponents\ValueObjects\ScanResult;
use App\Framework\LiveComponents\ValueObjects\ScanStatus;
use DateTimeImmutable;
beforeEach(function () {
$this->fileStorage = new InMemoryStorage();
$this->service = new QuarantineService(
fileStorage: $this->fileStorage,
quarantinePath: '/tmp/quarantine',
defaultRetentionPeriod: Duration::fromHours(24)
);
});
test('quarantines file successfully', function () {
// Create source file
$sourcePath = '/tmp/source/test-file.txt';
$this->fileStorage->put($sourcePath, 'test content');
// Quarantine file
$quarantineId = 'test-quarantine-123';
$quarantinePath = $this->service->quarantine($sourcePath, $quarantineId);
// Verify file moved to quarantine
expect($this->fileStorage->exists($quarantinePath))->toBeTrue();
expect($this->fileStorage->exists($sourcePath))->toBeFalse();
expect($quarantinePath)->toBe('/tmp/quarantine/' . $quarantineId);
});
test('throws exception when quarantining non-existent file', function () {
$nonExistentPath = '/tmp/does-not-exist.txt';
$quarantineId = 'test-quarantine-123';
expect(fn() => $this->service->quarantine($nonExistentPath, $quarantineId))
->toThrow(\InvalidArgumentException::class, 'Source file not found');
});
test('releases quarantined file to target location', function () {
// Setup: quarantine a file
$sourcePath = '/tmp/source/file.txt';
$this->fileStorage->put($sourcePath, 'quarantined content');
$quarantineId = 'release-test-456';
$this->service->quarantine($sourcePath, $quarantineId);
// Release file
$targetPath = '/tmp/target/released-file.txt';
$this->service->release($quarantineId, $targetPath);
// Verify file moved to target
expect($this->fileStorage->exists($targetPath))->toBeTrue();
expect($this->service->exists($quarantineId))->toBeFalse();
expect($this->fileStorage->get($targetPath))->toBe('quarantined content');
});
test('throws exception when releasing non-existent quarantined file', function () {
$nonExistentQuarantineId = 'does-not-exist';
$targetPath = '/tmp/target/file.txt';
expect(fn() => $this->service->release($nonExistentQuarantineId, $targetPath))
->toThrow(\InvalidArgumentException::class, 'Quarantined file not found');
});
test('deletes quarantined file', function () {
// Setup: quarantine a file
$sourcePath = '/tmp/source/to-delete.txt';
$this->fileStorage->put($sourcePath, 'delete me');
$quarantineId = 'delete-test-789';
$this->service->quarantine($sourcePath, $quarantineId);
// Delete quarantined file
$this->service->delete($quarantineId);
// Verify file deleted
expect($this->service->exists($quarantineId))->toBeFalse();
});
test('delete handles non-existent quarantine gracefully', function () {
$nonExistentId = 'never-existed';
// Should not throw exception
$this->service->delete($nonExistentId);
expect($this->service->exists($nonExistentId))->toBeFalse();
});
test('checks quarantine existence correctly', function () {
$sourcePath = '/tmp/source/exist-check.txt';
$this->fileStorage->put($sourcePath, 'existence test');
$quarantineId = 'exist-test-101';
// Before quarantine
expect($this->service->exists($quarantineId))->toBeFalse();
// After quarantine
$this->service->quarantine($sourcePath, $quarantineId);
expect($this->service->exists($quarantineId))->toBeTrue();
// After deletion
$this->service->delete($quarantineId);
expect($this->service->exists($quarantineId))->toBeFalse();
});
test('gets correct quarantine path', function () {
$quarantineId = 'path-test-202';
$expectedPath = '/tmp/quarantine/' . $quarantineId;
$actualPath = $this->service->getQuarantinePath($quarantineId);
expect($actualPath)->toBe($expectedPath);
});
test('scans file with clean result', function () {
// Setup: quarantine a file
$sourcePath = '/tmp/source/clean-file.txt';
$this->fileStorage->put($sourcePath, 'clean content');
$quarantineId = 'scan-clean-303';
$this->service->quarantine($sourcePath, $quarantineId);
// Mock scanner that returns clean
$scannerHook = function (string $filePath): ScanResult {
return ScanResult::clean('No threats detected');
};
// Scan file
$result = $this->service->scan($quarantineId, $scannerHook);
expect($result->isClean())->toBeTrue();
expect($result->status)->toBe(ScanStatus::CLEAN);
});
test('scans file with infected result', function () {
// Setup: quarantine a file
$sourcePath = '/tmp/source/infected-file.txt';
$this->fileStorage->put($sourcePath, 'infected content');
$quarantineId = 'scan-infected-404';
$this->service->quarantine($sourcePath, $quarantineId);
// Mock scanner that detects threat
$scannerHook = function (string $filePath): ScanResult {
return ScanResult::infected(
threatName: 'Trojan.Generic',
confidenceScore: 0.95,
details: 'Malicious payload detected'
);
};
// Scan file
$result = $this->service->scan($quarantineId, $scannerHook);
expect($result->isInfected())->toBeTrue();
expect($result->threatName)->toBe('Trojan.Generic');
expect($result->confidenceScore)->toBe(0.95);
});
test('scans file with suspicious result', function () {
// Setup: quarantine a file
$sourcePath = '/tmp/source/suspicious-file.txt';
$this->fileStorage->put($sourcePath, 'suspicious content');
$quarantineId = 'scan-suspicious-505';
$this->service->quarantine($sourcePath, $quarantineId);
// Mock scanner that finds suspicious patterns
$scannerHook = function (string $filePath): ScanResult {
return ScanResult::suspicious(
details: 'Potentially unwanted program',
confidenceScore: 0.65
);
};
// Scan file
$result = $this->service->scan($quarantineId, $scannerHook);
expect($result->isSuspicious())->toBeTrue();
expect($result->shouldQuarantine())->toBeTrue();
});
test('throws exception when scanning non-existent quarantined file', function () {
$nonExistentId = 'never-scanned';
$scannerHook = fn($path) => ScanResult::clean();
expect(fn() => $this->service->scan($nonExistentId, $scannerHook))
->toThrow(\InvalidArgumentException::class, 'Quarantined file not found');
});
test('throws exception when scanner hook returns invalid type', function () {
// Setup: quarantine a file
$sourcePath = '/tmp/source/invalid-scanner.txt';
$this->fileStorage->put($sourcePath, 'content');
$quarantineId = 'invalid-scanner-606';
$this->service->quarantine($sourcePath, $quarantineId);
// Invalid scanner hook that doesn't return ScanResult
$invalidHook = function (string $filePath): array {
return ['status' => 'clean'];
};
expect(fn() => $this->service->scan($quarantineId, $invalidHook))
->toThrow(\InvalidArgumentException::class, 'Scanner hook must return ScanResult instance');
});
test('validates status transitions correctly', function () {
// Valid transitions
expect($this->service->canTransition(
QuarantineStatus::PENDING,
QuarantineStatus::SCANNING
))->toBeTrue();
expect($this->service->canTransition(
QuarantineStatus::SCANNING,
QuarantineStatus::APPROVED
))->toBeTrue();
expect($this->service->canTransition(
QuarantineStatus::SCANNING,
QuarantineStatus::REJECTED
))->toBeTrue();
// Invalid transitions
expect($this->service->canTransition(
QuarantineStatus::APPROVED,
QuarantineStatus::PENDING
))->toBeFalse();
expect($this->service->canTransition(
QuarantineStatus::REJECTED,
QuarantineStatus::APPROVED
))->toBeFalse();
});
test('cleans up expired quarantined files', function () {
// Create multiple files with different ages
$oldFile1 = '/tmp/source/old-file-1.txt';
$oldFile2 = '/tmp/source/old-file-2.txt';
$recentFile = '/tmp/source/recent-file.txt';
$this->fileStorage->put($oldFile1, 'old content 1');
$this->fileStorage->put($oldFile2, 'old content 2');
$this->fileStorage->put($recentFile, 'recent content');
$this->service->quarantine($oldFile1, 'old-1');
$this->service->quarantine($oldFile2, 'old-2');
$this->service->quarantine($recentFile, 'recent-1');
// Simulate old files by using expiry time in future
// (files older than 48 hours should be deleted)
$expiryTime = (new DateTimeImmutable())->modify('+48 hours');
$deletedCount = $this->service->cleanupExpired($expiryTime);
// All files should be deleted as they're "older" than expiry time
expect($deletedCount)->toBe(3);
expect($this->service->exists('old-1'))->toBeFalse();
expect($this->service->exists('old-2'))->toBeFalse();
expect($this->service->exists('recent-1'))->toBeFalse();
});
test('cleanup handles empty quarantine directory gracefully', function () {
// No quarantined files
$deletedCount = $this->service->cleanupExpired();
expect($deletedCount)->toBe(0);
});
test('creates quarantine directory if not exists', function () {
$sourcePath = '/tmp/source/first-file.txt';
$this->fileStorage->put($sourcePath, 'first quarantine');
// Quarantine directory doesn't exist yet
expect($this->fileStorage->exists('/tmp/quarantine'))->toBeFalse();
// Quarantine file - should create directory
$this->service->quarantine($sourcePath, 'first-quarantine');
// Directory should now exist
expect($this->fileStorage->exists('/tmp/quarantine'))->toBeTrue();
});
test('creates target directory if not exists during release', function () {
// Setup: quarantine a file
$sourcePath = '/tmp/source/release-target-test.txt';
$this->fileStorage->put($sourcePath, 'release content');
$quarantineId = 'release-dir-test';
$this->service->quarantine($sourcePath, $quarantineId);
// Target directory doesn't exist
$targetPath = '/tmp/new-target-dir/released-file.txt';
expect($this->fileStorage->exists('/tmp/new-target-dir'))->toBeFalse();
// Release - should create target directory
$this->service->release($quarantineId, $targetPath);
// Directory and file should exist
expect($this->fileStorage->exists('/tmp/new-target-dir'))->toBeTrue();
expect($this->fileStorage->exists($targetPath))->toBeTrue();
});