package parser_test import ( "testing" "time" "tangled.org/samatkins.net/dockscope/internal/parser" ) type funcParser func(raw string) (parser.LogEntry, bool) func (f funcParser) Parse(raw string) (parser.LogEntry, bool) { return f(raw) } func TestChain(t *testing.T) { t.Parallel() t.Run("GIVEN a chain of parsers WHEN the first parser handles the input THEN its result is returned", func(t *testing.T) { t.Parallel() fakeTime := time.Date(2026, 5, 9, 14, 32, 1, 0, time.UTC) chain := parser.Chain{ funcParser(func(raw string) (parser.LogEntry, bool) { if raw == "match-first" { return parser.LogEntry{Raw: raw, TraceID: "first-trace", Timestamp: fakeTime}, true } return parser.LogEntry{}, false }), funcParser(func(raw string) (parser.LogEntry, bool) { return parser.LogEntry{Raw: raw, TraceID: "second-trace", Timestamp: fakeTime}, true }), } entry, ok := chain.Parse("match-first") if !ok { t.Fatal("ok = false, want true") } if got, want := entry.TraceID, "first-trace"; got != want { t.Errorf("TraceID = %q, want %q", got, want) } }) t.Run("GIVEN a chain WHERE no parser handles the input WHEN chain parses THEN a raw fallback entry is returned", func(t *testing.T) { t.Parallel() chain := parser.Chain{ funcParser(func(raw string) (parser.LogEntry, bool) { return parser.LogEntry{}, false }), funcParser(func(raw string) (parser.LogEntry, bool) { return parser.LogEntry{}, false }), } entry, ok := chain.Parse("some raw text") if !ok { t.Fatal("ok = false, want true (Chain always returns true via fallback)") } if got, want := entry.Raw, "some raw text"; got != want { t.Errorf("Raw = %q, want %q", got, want) } if got, want := entry.Message, "some raw text"; got != want { t.Errorf("Message = %q, want %q", got, want) } if entry.TraceID != "" { t.Errorf("TraceID = %q, want empty", entry.TraceID) } if entry.Timestamp.IsZero() { t.Error("Timestamp is zero, want non-zero") } }) t.Run("GIVEN a chain of parsers WHEN second parser handles the input THEN its result is returned", func(t *testing.T) { t.Parallel() chain := parser.Chain{ funcParser(func(raw string) (parser.LogEntry, bool) { return parser.LogEntry{}, false }), funcParser(func(raw string) (parser.LogEntry, bool) { if raw == "match-second" { return parser.LogEntry{Raw: raw, TraceID: "second-trace"}, true } return parser.LogEntry{}, false }), } entry, ok := chain.Parse("match-second") if !ok { t.Fatal("ok = false, want true") } if got, want := entry.TraceID, "second-trace"; got != want { t.Errorf("TraceID = %q, want %q", got, want) } }) t.Run("GIVEN a parser that signals success without setting Raw WHEN chain parses THEN that result is preserved", func(t *testing.T) { t.Parallel() chain := parser.Chain{ funcParser(func(raw string) (parser.LogEntry, bool) { return parser.LogEntry{TraceID: "no-raw-trace", Message: "structured"}, true }), } entry, ok := chain.Parse("input") if !ok { t.Fatal("ok = false, want true") } if got, want := entry.TraceID, "no-raw-trace"; got != want { t.Errorf("TraceID = %q, want %q (chain must not require Raw to be set)", got, want) } if entry.Raw != "" { t.Errorf("Raw = %q, want empty (chain should not synthesize Raw when parser succeeded)", entry.Raw) } }) t.Run("GIVEN an empty chain WHEN chain parses THEN a raw fallback entry is returned", func(t *testing.T) { t.Parallel() chain := parser.Chain{} entry, ok := chain.Parse("bare text") if !ok { t.Fatal("ok = false, want true") } if got, want := entry.Raw, "bare text"; got != want { t.Errorf("Raw = %q, want %q", got, want) } if got, want := entry.Message, "bare text"; got != want { t.Errorf("Message = %q, want %q", got, want) } }) } func TestNoopParser(t *testing.T) { t.Parallel() t.Run("GIVEN any input WHEN NoopParser.Parse THEN Raw and Message are set to input", func(t *testing.T) { t.Parallel() p := parser.NoopParser{} entry, ok := p.Parse("hello world") if !ok { t.Fatal("ok = false, want true") } if got, want := entry.Raw, "hello world"; got != want { t.Errorf("Raw = %q, want %q", got, want) } if got, want := entry.Message, "hello world"; got != want { t.Errorf("Message = %q, want %q", got, want) } }) t.Run("GIVEN any input WHEN NoopParser.Parse THEN Timestamp is non-zero", func(t *testing.T) { t.Parallel() p := parser.NoopParser{} entry, _ := p.Parse("hello") if entry.Timestamp.IsZero() { t.Error("Timestamp is zero, want non-zero") } }) t.Run("GIVEN any input WHEN NoopParser.Parse THEN TraceID is empty", func(t *testing.T) { t.Parallel() p := parser.NoopParser{} entry, _ := p.Parse("trace_id=abc123") if entry.TraceID != "" { t.Errorf("TraceID = %q, want empty", entry.TraceID) } }) }