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(); }); }); });