level)->toBe(LogLevel::ERROR); expect($entry->message)->toBe('Test error message'); expect($entry->context)->toBe('{"user_id":123}'); expect($entry->parsed)->toBeTrue(); expect($entry->sourcePath)->toBe($path); }); it('creates from raw unparsed line', function () { $path = FilePath::create('/tmp/test.log'); $entry = LogEntry::fromRawLine('Some random log line', $path); expect($entry->level)->toBe(LogLevel::INFO); expect($entry->message)->toBe('Some random log line'); expect($entry->context)->toBe(''); expect($entry->parsed)->toBeFalse(); }); it('extracts message without context', function () { $path = FilePath::create('/tmp/test.log'); $entry = LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: 'INFO', messageWithContext: 'Simple message without context', raw: 'Simple message without context', sourcePath: $path ); expect($entry->message)->toBe('Simple message without context'); expect($entry->context)->toBe(''); }); it('matches search term in message', function () { $path = FilePath::create('/tmp/test.log'); $entry = LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: 'ERROR', messageWithContext: 'Database connection failed', raw: 'Database connection failed', sourcePath: $path ); expect($entry->matchesSearch('database'))->toBeTrue(); expect($entry->matchesSearch('Database'))->toBeTrue(); // Case insensitive expect($entry->matchesSearch('connection'))->toBeTrue(); expect($entry->matchesSearch('redis'))->toBeFalse(); }); it('matches search term in context', function () { $path = FilePath::create('/tmp/test.log'); $entry = LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: 'ERROR', messageWithContext: 'Error occurred {"database":"mysql"}', raw: 'Error occurred {"database":"mysql"}', sourcePath: $path ); expect($entry->matchesSearch('mysql'))->toBeTrue(); }); it('checks log level match', function () { $path = FilePath::create('/tmp/test.log'); $entry = LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: 'ERROR', messageWithContext: 'Test message', raw: 'Test message', sourcePath: $path ); expect($entry->matchesLevel(LogLevel::ERROR))->toBeTrue(); expect($entry->matchesLevel(LogLevel::INFO))->toBeFalse(); }); it('checks minimum log level', function () { $path = FilePath::create('/tmp/test.log'); $entry = LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: 'ERROR', messageWithContext: 'Test message', raw: 'Test message', sourcePath: $path ); expect($entry->isAtLeastLevel(LogLevel::ERROR))->toBeTrue(); expect($entry->isAtLeastLevel(LogLevel::WARNING))->toBeTrue(); expect($entry->isAtLeastLevel(LogLevel::CRITICAL))->toBeFalse(); }); it('formats timestamp', function () { $path = FilePath::create('/tmp/test.log'); $entry = LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: 'INFO', messageWithContext: 'Test', raw: 'Test', sourcePath: $path ); expect($entry->getFormattedTimestamp())->toBe('2024-01-15 10:30:45'); expect($entry->getFormattedTimestamp('Y-m-d'))->toBe('2024-01-15'); }); it('parses context as array', function () { $path = FilePath::create('/tmp/test.log'); $entry = LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: 'ERROR', messageWithContext: 'Error {"user_id":123,"action":"login"}', raw: 'Error', sourcePath: $path ); $contextArray = $entry->getContextArray(); expect($contextArray)->toBeArray(); expect($contextArray['user_id'])->toBe(123); expect($contextArray['action'])->toBe('login'); }); it('returns empty array for no context', function () { $path = FilePath::create('/tmp/test.log'); $entry = LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: 'INFO', messageWithContext: 'Simple message', raw: 'Simple message', sourcePath: $path ); expect($entry->getContextArray())->toBe([]); expect($entry->hasContext())->toBeFalse(); }); it('converts to array', function () { $path = FilePath::create('/tmp/test.log'); $entry = LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: 'ERROR', messageWithContext: 'Test error', raw: '[2024-01-15 10:30:45] ERROR: Test error', sourcePath: $path ); $array = $entry->toArray(); expect($array)->toHaveKey('timestamp'); expect($array)->toHaveKey('level'); expect($array)->toHaveKey('message'); expect($array)->toHaveKey('context'); expect($array)->toHaveKey('raw'); expect($array)->toHaveKey('parsed'); expect($array)->toHaveKey('source_path'); expect($array['level'])->toBe('ERROR'); expect($array['message'])->toBe('Test error'); }); it('parses all log levels correctly', function () { $path = FilePath::create('/tmp/test.log'); $levels = ['DEBUG', 'INFO', 'NOTICE', 'WARNING', 'ERROR', 'CRITICAL', 'ALERT', 'EMERGENCY']; foreach ($levels as $levelName) { $entry = LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: $levelName, messageWithContext: 'Test', raw: 'Test', sourcePath: $path ); expect($entry->level->getName())->toBe($levelName); } }); it('handles unknown log level gracefully', function () { $path = FilePath::create('/tmp/test.log'); $entry = LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: 'UNKNOWN', messageWithContext: 'Test', raw: 'Test', sourcePath: $path ); // Should fallback to INFO expect($entry->level)->toBe(LogLevel::INFO); }); });