isEmpty())->toBeTrue(); expect($details->count())->toBe(0); expect($details->toArray())->toBe([]); }); it('can be created from array', function () { $data = ['host' => 'localhost', 'port' => 3306]; $details = HealthDetails::fromArray($data); expect($details->isEmpty())->toBeFalse(); expect($details->count())->toBe(2); expect($details->get('host'))->toBe('localhost'); expect($details->get('port'))->toBe(3306); }); it('can add and remove details immutably', function () { $details = HealthDetails::empty(); $newDetails = $details->with('key', 'value'); expect($details->has('key'))->toBeFalse(); // Original unchanged expect($newDetails->has('key'))->toBeTrue(); expect($newDetails->get('key'))->toBe('value'); $withoutKey = $newDetails->without('key'); expect($withoutKey->has('key'))->toBeFalse(); expect($newDetails->has('key'))->toBeTrue(); // Original unchanged }); it('gets values with defaults', function () { $details = HealthDetails::fromArray(['existing' => 'value']); expect($details->get('existing'))->toBe('value'); expect($details->get('missing'))->toBeNull(); expect($details->get('missing', 'default'))->toBe('default'); }); it('creates database details correctly', function () { $details = HealthDetails::forDatabase('localhost', 3306, 'testdb', 25.5, 10); expect($details->get('host'))->toBe('localhost'); expect($details->get('port'))->toBe(3306); expect($details->get('database'))->toBe('testdb'); expect($details->get('connection_time_ms'))->toBe(25.5); expect($details->get('active_connections'))->toBe(10); }); it('creates cache details correctly', function () { $details = HealthDetails::forCache('redis', 95, 1000, '128MB'); expect($details->get('driver'))->toBe('redis'); expect($details->get('hit_rate_percent'))->toBe(95); expect($details->get('total_keys'))->toBe(1000); expect($details->get('memory_usage'))->toBe('128MB'); }); it('creates disk details correctly', function () { $details = HealthDetails::forDisk('/var/log', '100GB', '25GB', 75.0); expect($details->get('path'))->toBe('/var/log'); expect($details->get('total_space'))->toBe('100GB'); expect($details->get('free_space'))->toBe('25GB'); expect($details->get('usage_percent'))->toBe(75.0); }); it('creates system details correctly', function () { $details = HealthDetails::forSystem(45.2, '8GB/16GB', 1.5, '5 days'); expect($details->get('cpu_usage_percent'))->toBe(45.2); expect($details->get('memory_usage'))->toBe('8GB/16GB'); expect($details->get('load_average'))->toBe(1.5); expect($details->get('uptime'))->toBe('5 days'); }); it('creates API details correctly', function () { $details = HealthDetails::forApi('/health', 200, 150.5, 'v1.2.3'); expect($details->get('endpoint'))->toBe('/health'); expect($details->get('status_code'))->toBe(200); expect($details->get('response_time_ms'))->toBe(150.5); expect($details->get('version'))->toBe('v1.2.3'); }); it('creates error details correctly', function () { $exception = new \RuntimeException('Test error', 500); $details = HealthDetails::forError($exception); expect($details->get('error_type'))->toBe('RuntimeException'); expect($details->get('error_message'))->toBe('Test error'); expect($details->get('error_code'))->toBe(500); expect($details->get('error_file'))->toBeString(); expect($details->get('error_line'))->toBeInt(); }); it('can merge details', function () { $details1 = HealthDetails::fromArray(['key1' => 'value1']); $details2 = HealthDetails::fromArray(['key2' => 'value2']); $merged = $details1->merge($details2); expect($merged->get('key1'))->toBe('value1'); expect($merged->get('key2'))->toBe('value2'); expect($merged->count())->toBe(2); }); it('identifies sensitive keys', function () { $details = HealthDetails::fromArray([ 'username' => 'john', 'password' => 'secret123', 'api_key' => 'abc123', 'database_token' => 'xyz789', 'normal_field' => 'value', ]); $sensitiveKeys = $details->getSensitiveKeys(); expect($sensitiveKeys)->toContain('password'); expect($sensitiveKeys)->toContain('api_key'); expect($sensitiveKeys)->toContain('database_token'); expect($sensitiveKeys)->not->toContain('username'); expect($sensitiveKeys)->not->toContain('normal_field'); }); it('sanitizes sensitive data', function () { $details = HealthDetails::fromArray([ 'username' => 'john', 'password' => 'secret123', 'normal_field' => 'value', ]); $sanitized = $details->toSanitizedArray(); expect($sanitized['username'])->toBe('john'); expect($sanitized['password'])->toBe('[REDACTED]'); expect($sanitized['normal_field'])->toBe('value'); }); it('throws exception for non-string keys', function () { expect(fn () => HealthDetails::fromArray([123 => 'value']))->toThrow(InvalidArgumentException::class); }); it('throws exception for empty keys', function () { expect(fn () => HealthDetails::fromArray(['' => 'value']))->toThrow(InvalidArgumentException::class); expect(fn () => HealthDetails::fromArray([' ' => 'value']))->toThrow(InvalidArgumentException::class); }); it('throws exception for resource values', function () { $resource = fopen('php://memory', 'r'); expect(fn () => HealthDetails::fromArray(['resource' => $resource]))->toThrow(InvalidArgumentException::class); fclose($resource); }); it('allows scalar and null values', function () { $details = HealthDetails::fromArray([ 'string' => 'text', 'int' => 123, 'float' => 12.34, 'bool' => true, 'null' => null, ]); expect($details->get('string'))->toBe('text'); expect($details->get('int'))->toBe(123); expect($details->get('float'))->toBe(12.34); expect($details->get('bool'))->toBeTrue(); expect($details->get('null'))->toBeNull(); }); });