- 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.
341 lines
12 KiB
PHP
341 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Core\ValueObjects\FileSize;
|
|
use App\Framework\Filesystem\ValueObjects\FileOperationContext;
|
|
use App\Framework\Filesystem\ValueObjects\FileOperation;
|
|
|
|
describe('FileOperationContext Logging', function () {
|
|
// Severity Detection Tests
|
|
describe('Severity Detection', function () {
|
|
it('identifies high severity operations', function () {
|
|
$deleteContext = FileOperationContext::forOperation(
|
|
FileOperation::DELETE,
|
|
'/path/to/file.txt'
|
|
);
|
|
|
|
expect($deleteContext->isHighSeverity())->toBeTrue();
|
|
expect($deleteContext->toArray()['severity'])->toBe('high');
|
|
|
|
$moveContext = FileOperationContext::forOperationWithDestination(
|
|
FileOperation::MOVE,
|
|
'/source/file.txt',
|
|
'/dest/file.txt'
|
|
);
|
|
|
|
expect($moveContext->isHighSeverity())->toBeTrue();
|
|
|
|
$deleteDirContext = FileOperationContext::forOperation(
|
|
FileOperation::DELETE_DIRECTORY,
|
|
'/path/to/dir'
|
|
);
|
|
|
|
expect($deleteDirContext->isHighSeverity())->toBeTrue();
|
|
});
|
|
|
|
it('identifies medium severity operations', function () {
|
|
$writeContext = FileOperationContext::forWrite(
|
|
'/path/to/file.txt',
|
|
FileSize::fromBytes(1024)
|
|
);
|
|
|
|
expect($writeContext->isHighSeverity())->toBeFalse();
|
|
expect($writeContext->toArray()['severity'])->toBe('medium');
|
|
|
|
$copyContext = FileOperationContext::forOperationWithDestination(
|
|
FileOperation::COPY,
|
|
'/source/file.txt',
|
|
'/dest/file.txt'
|
|
);
|
|
|
|
expect($copyContext->toArray()['severity'])->toBe('medium');
|
|
|
|
$createDirContext = FileOperationContext::forOperation(
|
|
FileOperation::CREATE_DIRECTORY,
|
|
'/path/to/newdir'
|
|
);
|
|
|
|
expect($createDirContext->toArray()['severity'])->toBe('medium');
|
|
});
|
|
|
|
it('identifies low severity operations', function () {
|
|
$readContext = FileOperationContext::forRead(
|
|
'/path/to/file.txt',
|
|
FileSize::fromBytes(1024)
|
|
);
|
|
|
|
expect($readContext->isHighSeverity())->toBeFalse();
|
|
expect($readContext->toArray()['severity'])->toBe('low');
|
|
|
|
$listContext = FileOperationContext::forOperation(
|
|
FileOperation::LIST_DIRECTORY,
|
|
'/path/to/dir'
|
|
);
|
|
|
|
expect($listContext->toArray()['severity'])->toBe('low');
|
|
|
|
$metadataContext = FileOperationContext::forOperation(
|
|
FileOperation::GET_METADATA,
|
|
'/path/to/file.txt'
|
|
);
|
|
|
|
expect($metadataContext->toArray()['severity'])->toBe('low');
|
|
});
|
|
});
|
|
|
|
// Large Operation Detection Tests
|
|
describe('Large Operation Detection', function () {
|
|
it('identifies large operations (>10MB)', function () {
|
|
// 15MB operation - should be large
|
|
$largeWrite = FileOperationContext::forWrite(
|
|
'/path/to/large.txt',
|
|
FileSize::fromMegabytes(15)
|
|
);
|
|
|
|
expect($largeWrite->isLargeOperation())->toBeTrue();
|
|
|
|
// 50MB read - should be large
|
|
$largeRead = FileOperationContext::forRead(
|
|
'/path/to/huge.txt',
|
|
FileSize::fromMegabytes(50)
|
|
);
|
|
|
|
expect($largeRead->isLargeOperation())->toBeTrue();
|
|
});
|
|
|
|
it('does not identify small operations as large', function () {
|
|
// 1MB operation - should NOT be large
|
|
$smallWrite = FileOperationContext::forWrite(
|
|
'/path/to/small.txt',
|
|
FileSize::fromMegabytes(1)
|
|
);
|
|
|
|
expect($smallWrite->isLargeOperation())->toBeFalse();
|
|
|
|
// 5KB read - should NOT be large
|
|
$tinyRead = FileOperationContext::forRead(
|
|
'/path/to/tiny.txt',
|
|
FileSize::fromKilobytes(5)
|
|
);
|
|
|
|
expect($tinyRead->isLargeOperation())->toBeFalse();
|
|
});
|
|
|
|
it('returns false for operations without byte information', function () {
|
|
$deleteContext = FileOperationContext::forOperation(
|
|
FileOperation::DELETE,
|
|
'/path/to/file.txt'
|
|
);
|
|
|
|
expect($deleteContext->isLargeOperation())->toBeFalse();
|
|
});
|
|
});
|
|
|
|
// Context Data Completeness Tests
|
|
describe('Context Data Completeness', function () {
|
|
it('includes all relevant data in toArray()', function () {
|
|
$context = FileOperationContext::forWrite(
|
|
'/path/to/file.txt',
|
|
FileSize::fromKilobytes(150),
|
|
'user123'
|
|
);
|
|
|
|
$context = $context->withMetadata([
|
|
'source' => 'upload',
|
|
'mime_type' => 'text/plain'
|
|
]);
|
|
|
|
$array = $context->toArray();
|
|
|
|
expect($array)->toHaveKey('operation');
|
|
expect($array)->toHaveKey('operation_name');
|
|
expect($array)->toHaveKey('path');
|
|
expect($array)->toHaveKey('timestamp');
|
|
expect($array)->toHaveKey('severity');
|
|
expect($array)->toHaveKey('bytes_affected');
|
|
expect($array)->toHaveKey('bytes_affected_human');
|
|
expect($array)->toHaveKey('user_id');
|
|
expect($array)->toHaveKey('metadata');
|
|
|
|
expect($array['operation'])->toBe('write');
|
|
expect($array['operation_name'])->toBe('file.write');
|
|
expect($array['path'])->toBe('/path/to/file.txt');
|
|
expect($array['severity'])->toBe('medium');
|
|
expect($array['bytes_affected'])->toBe(153600); // 150 * 1024
|
|
expect($array['bytes_affected_human'])->toBe('150 KB'); // toHumanReadable() returns without decimals
|
|
expect($array['user_id'])->toBe('user123');
|
|
expect($array['metadata'])->toBe([
|
|
'source' => 'upload',
|
|
'mime_type' => 'text/plain'
|
|
]);
|
|
});
|
|
|
|
it('handles operation with destination', function () {
|
|
$context = FileOperationContext::forOperationWithDestination(
|
|
FileOperation::COPY,
|
|
'/source/file.txt',
|
|
'/dest/file.txt'
|
|
);
|
|
|
|
$array = $context->toArray();
|
|
|
|
expect($array)->toHaveKey('destination_path');
|
|
expect($array['destination_path'])->toBe('/dest/file.txt');
|
|
expect($array['path'])->toBe('/source/file.txt');
|
|
});
|
|
|
|
it('generates readable string representation', function () {
|
|
$context = FileOperationContext::forWrite(
|
|
'/var/www/uploads/document.pdf',
|
|
FileSize::fromKilobytes(250),
|
|
'admin'
|
|
);
|
|
|
|
$string = $context->toString();
|
|
|
|
expect($string)->toContain('Write file contents');
|
|
expect($string)->toContain('path: /var/www/uploads/document.pdf');
|
|
expect($string)->toContain('bytes: 250'); // Flexible - may be "250 KB" or "250.00 KB"
|
|
expect($string)->toContain('user: admin');
|
|
});
|
|
});
|
|
|
|
// Write Operation Detection Tests
|
|
describe('Write Operation Detection', function () {
|
|
it('correctly identifies write operations', function () {
|
|
$writeContext = FileOperationContext::forOperation(
|
|
FileOperation::WRITE,
|
|
'/path/to/file.txt'
|
|
);
|
|
expect($writeContext->isWriteOperation())->toBeTrue();
|
|
|
|
$deleteContext = FileOperationContext::forOperation(
|
|
FileOperation::DELETE,
|
|
'/path/to/file.txt'
|
|
);
|
|
expect($deleteContext->isWriteOperation())->toBeTrue();
|
|
|
|
$moveContext = FileOperationContext::forOperationWithDestination(
|
|
FileOperation::MOVE,
|
|
'/source.txt',
|
|
'/dest.txt'
|
|
);
|
|
expect($moveContext->isWriteOperation())->toBeTrue();
|
|
});
|
|
|
|
it('correctly identifies non-write operations', function () {
|
|
$readContext = FileOperationContext::forRead(
|
|
'/path/to/file.txt',
|
|
FileSize::fromBytes(100)
|
|
);
|
|
expect($readContext->isWriteOperation())->toBeFalse();
|
|
|
|
$copyContext = FileOperationContext::forOperationWithDestination(
|
|
FileOperation::COPY,
|
|
'/source.txt',
|
|
'/dest.txt'
|
|
);
|
|
expect($copyContext->isWriteOperation())->toBeFalse();
|
|
});
|
|
});
|
|
|
|
// Immutability Tests
|
|
describe('Immutability', function () {
|
|
it('withMetadata creates new instance', function () {
|
|
$original = FileOperationContext::forOperation(
|
|
FileOperation::WRITE,
|
|
'/path/to/file.txt'
|
|
);
|
|
|
|
$modified = $original->withMetadata(['key' => 'value']);
|
|
|
|
expect($original->metadata)->toBeNull();
|
|
expect($modified->metadata)->toBe(['key' => 'value']);
|
|
expect($original)->not->toBe($modified);
|
|
});
|
|
|
|
it('withUserId creates new instance', function () {
|
|
$original = FileOperationContext::forOperation(
|
|
FileOperation::DELETE,
|
|
'/path/to/file.txt'
|
|
);
|
|
|
|
$modified = $original->withUserId('user456');
|
|
|
|
expect($original->userId)->toBeNull();
|
|
expect($modified->userId)->toBe('user456');
|
|
expect($original)->not->toBe($modified);
|
|
});
|
|
|
|
it('withMetadata merges with existing metadata', function () {
|
|
$context = FileOperationContext::forOperation(
|
|
FileOperation::WRITE,
|
|
'/file.txt'
|
|
);
|
|
|
|
$context = $context->withMetadata(['a' => 1]);
|
|
$context = $context->withMetadata(['b' => 2]);
|
|
|
|
expect($context->metadata)->toBe(['a' => 1, 'b' => 2]);
|
|
});
|
|
});
|
|
|
|
// Factory Method Tests
|
|
describe('Factory Methods', function () {
|
|
it('creates context for simple operations', function () {
|
|
$context = FileOperationContext::forOperation(
|
|
FileOperation::DELETE,
|
|
'/path/to/file.txt'
|
|
);
|
|
|
|
expect($context->operation)->toBe(FileOperation::DELETE);
|
|
expect($context->path)->toBe('/path/to/file.txt');
|
|
expect($context->timestamp)->not->toBeNull();
|
|
expect($context->destinationPath)->toBeNull();
|
|
expect($context->bytesAffected)->toBeNull();
|
|
});
|
|
|
|
it('creates context for operations with destination', function () {
|
|
$context = FileOperationContext::forOperationWithDestination(
|
|
FileOperation::MOVE,
|
|
'/source/file.txt',
|
|
'/dest/file.txt'
|
|
);
|
|
|
|
expect($context->operation)->toBe(FileOperation::MOVE);
|
|
expect($context->path)->toBe('/source/file.txt');
|
|
expect($context->destinationPath)->toBe('/dest/file.txt');
|
|
expect($context->timestamp)->not->toBeNull();
|
|
});
|
|
|
|
it('creates context for write operations', function () {
|
|
$context = FileOperationContext::forWrite(
|
|
'/path/to/file.txt',
|
|
FileSize::fromKilobytes(100),
|
|
'user789'
|
|
);
|
|
|
|
expect($context->operation)->toBe(FileOperation::WRITE);
|
|
expect($context->path)->toBe('/path/to/file.txt');
|
|
expect($context->bytesAffected)->toBeInstanceOf(FileSize::class);
|
|
expect($context->bytesAffected->toBytes())->toBe(102400);
|
|
expect($context->userId)->toBe('user789');
|
|
expect($context->timestamp)->not->toBeNull();
|
|
});
|
|
|
|
it('creates context for read operations', function () {
|
|
$context = FileOperationContext::forRead(
|
|
'/path/to/file.txt',
|
|
FileSize::fromMegabytes(5)
|
|
);
|
|
|
|
expect($context->operation)->toBe(FileOperation::READ);
|
|
expect($context->path)->toBe('/path/to/file.txt');
|
|
expect($context->bytesAffected)->toBeInstanceOf(FileSize::class);
|
|
expect($context->bytesAffected->toMegabytes())->toBe(5.0);
|
|
expect($context->timestamp)->not->toBeNull();
|
|
});
|
|
});
|
|
});
|