logName = LogName::fromString('app_error'); $this->logPath = FilePath::create('/tmp/test.log'); $this->entries = [ LogEntry::fromParsedLine( timestamp: '2024-01-15 10:30:45', level: 'ERROR', messageWithContext: 'Database error', raw: '[2024-01-15 10:30:45] local.ERROR: Database error', sourcePath: $this->logPath ), LogEntry::fromParsedLine( timestamp: '2024-01-15 10:31:00', level: 'WARNING', messageWithContext: 'Cache miss', raw: '[2024-01-15 10:31:00] local.WARNING: Cache miss', sourcePath: $this->logPath ), LogEntry::fromParsedLine( timestamp: '2024-01-15 10:31:30', level: 'INFO', messageWithContext: 'User logged in', raw: '[2024-01-15 10:31:30] local.INFO: User logged in', sourcePath: $this->logPath ), ]; }); it('creates from entries', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); expect($result->logName)->toBe($this->logName); expect($result->logPath)->toBe($this->logPath); expect($result->entries)->toHaveCount(3); expect($result->totalEntries)->toBe(3); expect($result->limit)->toBe(100); }); it('checks if empty', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: [], limit: 100 ); expect($result->isEmpty())->toBeTrue(); expect($result->hasEntries())->toBeFalse(); }); it('filters by log level', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); $filtered = $result->filterByLevel(LogLevel::ERROR); expect($filtered->entries)->toHaveCount(1); expect($filtered->entries[0]->level)->toBe(LogLevel::ERROR); }); it('filters by search term', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); $filtered = $result->filterBySearch('database'); expect($filtered->entries)->toHaveCount(1); expect($filtered->entries[0]->message)->toContain('Database'); }); it('filters by minimum log level', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); $filtered = $result->filterByMinimumLevel(LogLevel::WARNING); expect($filtered->entries)->toHaveCount(2); // ERROR and WARNING expect($filtered->first()->level)->toBe(LogLevel::ERROR); expect($filtered->last()->level)->toBe(LogLevel::WARNING); }); it('gets first entry', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); $first = $result->first(); expect($first)->not->toBeNull(); expect($first->level)->toBe(LogLevel::ERROR); }); it('gets last entry', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); $last = $result->last(); expect($last)->not->toBeNull(); expect($last->level)->toBe(LogLevel::INFO); }); it('returns null for first/last on empty result', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: [], limit: 100 ); expect($result->first())->toBeNull(); expect($result->last())->toBeNull(); }); it('takes limited entries', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); $limited = $result->take(2); expect($limited->entries)->toHaveCount(2); expect($limited->totalEntries)->toBe(2); }); it('skips entries', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); $skipped = $result->skip(1); expect($skipped->entries)->toHaveCount(2); expect($skipped->first()->level)->toBe(LogLevel::WARNING); }); it('reverses entry order', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); $reversed = $result->reverse(); expect($reversed->first()->level)->toBe(LogLevel::INFO); expect($reversed->last()->level)->toBe(LogLevel::ERROR); }); it('gets level statistics', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); $stats = $result->getLevelStatistics(); expect($stats)->toBeArray(); expect($stats['ERROR'])->toBe(1); expect($stats['WARNING'])->toBe(1); expect($stats['INFO'])->toBe(1); }); it('gets metadata', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100, search: 'test', levelFilter: LogLevel::ERROR ); $metadata = $result->getMetadata(); expect($metadata)->toBeArray(); expect($metadata['log_name'])->toBe('app_error'); expect($metadata['total_entries'])->toBe(3); expect($metadata['limit'])->toBe(100); expect($metadata['search'])->toBe('test'); expect($metadata['level_filter'])->toBe('ERROR'); }); it('converts to array', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); $array = $result->toArray(); expect($array)->toHaveKey('log_name'); expect($array)->toHaveKey('log_path'); expect($array)->toHaveKey('entries'); expect($array)->toHaveKey('total_entries'); expect($array)->toHaveKey('metadata'); expect($array['entries'])->toBeArray(); expect($array['entries'])->toHaveCount(3); }); it('creates iterator', function () { $result = LogReadResult::fromEntries( logName: $this->logName, logPath: $this->logPath, entries: $this->entries, limit: 100 ); $iterator = $result->getIterator(); expect($iterator)->toBeInstanceOf(\ArrayIterator::class); $count = 0; foreach ($iterator as $entry) { expect($entry)->toBeInstanceOf(LogEntry::class); $count++; } expect($count)->toBe(3); }); });