package normalize import ( "strings" "testing" ) func TestParseReaderObjectWithSegmentsParses(t *testing.T) { input := `{ "segments": [ {"start": 1.0, "end": 2.0, "speaker": " Alice ", "text": "hello", "id": 100} ] }` parsed, err := ParseReader(strings.NewReader(input)) if err != nil { t.Fatalf("parse failed: %v", err) } if parsed.Shape != ShapeObjectWithSegments { t.Fatalf("shape = %q, want %q", parsed.Shape, ShapeObjectWithSegments) } if len(parsed.Segments) != 1 { t.Fatalf("segment count = %d, want 1", len(parsed.Segments)) } segment := parsed.Segments[0] if segment.Speaker != "Alice" { t.Fatalf("speaker = %q, want %q", segment.Speaker, "Alice") } if segment.OriginalID == nil || *segment.OriginalID != 100 { t.Fatalf("original id = %v, want 100", segment.OriginalID) } } func TestParseReaderBareSegmentArrayParses(t *testing.T) { input := `[ {"start": 1.0, "end": 2.0, "speaker": "Alice", "text": "hello"}, {"start": 3.0, "end": 4.0, "speaker": "Bob", "text": "world"} ]` parsed, err := ParseReader(strings.NewReader(input)) if err != nil { t.Fatalf("parse failed: %v", err) } if parsed.Shape != ShapeBareSegmentsArray { t.Fatalf("shape = %q, want %q", parsed.Shape, ShapeBareSegmentsArray) } if len(parsed.Segments) != 2 { t.Fatalf("segment count = %d, want 2", len(parsed.Segments)) } } func TestParseReaderInvalidJSONFails(t *testing.T) { _, err := ParseReader(strings.NewReader(`{"segments":`)) if err == nil { t.Fatal("expected parse error") } if !strings.Contains(err.Error(), "decode normalize input JSON") { t.Fatalf("unexpected error: %v", err) } } func TestParseReaderObjectMissingSegmentsFails(t *testing.T) { _, err := ParseReader(strings.NewReader(`{"items":[]}`)) if err == nil { t.Fatal("expected missing segments error") } if !strings.Contains(err.Error(), "must contain a \"segments\" field") { t.Fatalf("unexpected error: %v", err) } } func TestParseReaderSegmentsNotArrayFails(t *testing.T) { _, err := ParseReader(strings.NewReader(`{"segments": {}}`)) if err == nil { t.Fatal("expected segments not array error") } if !strings.Contains(err.Error(), "\"segments\" must be an array") { t.Fatalf("unexpected error: %v", err) } } func TestParseReaderTopLevelScalarShapesFail(t *testing.T) { tests := []string{`"text"`, `42`, `null`, `true`} for _, input := range tests { _, err := ParseReader(strings.NewReader(input)) if err == nil { t.Fatalf("expected top-level shape error for %s", input) } if !strings.Contains(err.Error(), "top-level object") { t.Fatalf("unexpected error for %s: %v", input, err) } } } func TestParseReaderMissingStartUsesEndValue(t *testing.T) { parsed, err := ParseReader(strings.NewReader(`[{"end":2,"speaker":"A","text":"t"}]`)) if err != nil { t.Fatalf("parse failed: %v", err) } if len(parsed.Segments) != 1 { t.Fatalf("segment count = %d, want 1", len(parsed.Segments)) } if parsed.Segments[0].Start != 2 || parsed.Segments[0].End != 2 { t.Fatalf("segment timing = %v..%v, want 2..2", parsed.Segments[0].Start, parsed.Segments[0].End) } } func TestParseReaderMissingEndUsesStartValue(t *testing.T) { parsed, err := ParseReader(strings.NewReader(`[{"start":1,"speaker":"A","text":"t"}]`)) if err != nil { t.Fatalf("parse failed: %v", err) } if len(parsed.Segments) != 1 { t.Fatalf("segment count = %d, want 1", len(parsed.Segments)) } if parsed.Segments[0].Start != 1 || parsed.Segments[0].End != 1 { t.Fatalf("segment timing = %v..%v, want 1..1", parsed.Segments[0].Start, parsed.Segments[0].End) } } func TestParseReaderMissingSpeakerUsesUnknownPlaceholder(t *testing.T) { parsed, err := ParseReader(strings.NewReader(`[{"start":1,"end":2,"text":"t"}]`)) if err != nil { t.Fatalf("parse failed: %v", err) } if parsed.Segments[0].Speaker != "Unknown_Speaker" { t.Fatalf("speaker = %q, want Unknown_Speaker", parsed.Segments[0].Speaker) } } func TestParseReaderEmptySpeakerUsesUnknownPlaceholder(t *testing.T) { parsed, err := ParseReader(strings.NewReader(`[{"start":1,"end":2,"speaker":" ","text":"t"}]`)) if err != nil { t.Fatalf("parse failed: %v", err) } if parsed.Segments[0].Speaker != "Unknown_Speaker" { t.Fatalf("speaker = %q, want Unknown_Speaker", parsed.Segments[0].Speaker) } } func TestParseReaderMissingTextDropsSegment(t *testing.T) { parsed, err := ParseReader(strings.NewReader(`[{"start":1,"end":2,"speaker":"A"}]`)) if err != nil { t.Fatalf("parse failed: %v", err) } if len(parsed.Segments) != 0 { t.Fatalf("segment count = %d, want 0", len(parsed.Segments)) } } func TestParseReaderEndBeforeStartSwapsValues(t *testing.T) { parsed, err := ParseReader(strings.NewReader(`[{"start":3,"end":2,"speaker":"A","text":"t"}]`)) if err != nil { t.Fatalf("parse failed: %v", err) } if parsed.Segments[0].Start != 2 || parsed.Segments[0].End != 3 { t.Fatalf("segment timing = %v..%v, want 2..3", parsed.Segments[0].Start, parsed.Segments[0].End) } } func TestParseReaderNegativeStartFails(t *testing.T) { _, err := ParseReader(strings.NewReader(`[{"start":-1,"end":2,"speaker":"A","text":"t"}]`)) assertContains(t, err, "start must be >= 0") } func TestParseReaderEmptySegmentsArrayAccepted(t *testing.T) { parsed, err := ParseReader(strings.NewReader(`{"segments":[]}`)) if err != nil { t.Fatalf("parse failed: %v", err) } if len(parsed.Segments) != 0 { t.Fatalf("segment count = %d, want 0", len(parsed.Segments)) } } func TestParseReaderCategoriesPreservedWhenValid(t *testing.T) { parsed, err := ParseReader(strings.NewReader(`[{"start":1,"end":2,"speaker":"A","text":"t","categories":["filler","backchannel"]}]`)) if err != nil { t.Fatalf("parse failed: %v", err) } if len(parsed.Segments) != 1 { t.Fatalf("segment count = %d, want 1", len(parsed.Segments)) } if len(parsed.Segments[0].Categories) != 2 { t.Fatalf("categories length = %d, want 2", len(parsed.Segments[0].Categories)) } if parsed.Segments[0].Categories[0] != "filler" || parsed.Segments[0].Categories[1] != "backchannel" { t.Fatalf("categories = %v", parsed.Segments[0].Categories) } } func TestParseReaderMissingBothTimesMiddleUsesNeighborMidpoint(t *testing.T) { parsed, err := ParseReader(strings.NewReader(`[ {"start":1,"end":2,"speaker":"A","text":"left"}, {"speaker":"B","text":"middle"}, {"start":6,"end":7,"speaker":"C","text":"right"} ]`)) if err != nil { t.Fatalf("parse failed: %v", err) } if parsed.Segments[1].Start != 4 || parsed.Segments[1].End != 4 { t.Fatalf("middle timing = %v..%v, want 4..4", parsed.Segments[1].Start, parsed.Segments[1].End) } } func TestParseReaderMissingBothTimesEdgeFallbacks(t *testing.T) { parsedFirst, err := ParseReader(strings.NewReader(`[ {"speaker":"A","text":"first"}, {"start":5,"end":6,"speaker":"B","text":"second"} ]`)) if err != nil { t.Fatalf("parse first failed: %v", err) } if parsedFirst.Segments[0].Start != 5 || parsedFirst.Segments[0].End != 5 { t.Fatalf("first timing = %v..%v, want 5..5", parsedFirst.Segments[0].Start, parsedFirst.Segments[0].End) } parsedLast, err := ParseReader(strings.NewReader(`[ {"start":1,"end":2,"speaker":"A","text":"first"}, {"speaker":"B","text":"last"} ]`)) if err != nil { t.Fatalf("parse last failed: %v", err) } if parsedLast.Segments[1].Start != 2 || parsedLast.Segments[1].End != 2 { t.Fatalf("last timing = %v..%v, want 2..2", parsedLast.Segments[1].Start, parsedLast.Segments[1].End) } } func TestParseReaderMissingBothTimesSingleSegmentUsesZero(t *testing.T) { parsed, err := ParseReader(strings.NewReader(`[{"speaker":"A","text":"only"}]`)) if err != nil { t.Fatalf("parse failed: %v", err) } if parsed.Segments[0].Start != 0 || parsed.Segments[0].End != 0 { t.Fatalf("timing = %v..%v, want 0..0", parsed.Segments[0].Start, parsed.Segments[0].End) } } func TestParseReaderEmptyOrWhitespaceTextDropped(t *testing.T) { parsed, err := ParseReader(strings.NewReader(`[ {"start":1,"end":2,"speaker":"A","text":"ok"}, {"start":2,"end":3,"speaker":"A","text":""}, {"start":3,"end":4,"speaker":"A","text":" "} ]`)) if err != nil { t.Fatalf("parse failed: %v", err) } if len(parsed.Segments) != 1 { t.Fatalf("segment count = %d, want 1", len(parsed.Segments)) } } func TestParseReaderOriginalInputIndexPreserved(t *testing.T) { input := `[ {"start":1,"end":2,"speaker":"A","text":"one"}, {"start":2,"end":3,"speaker":"B","text":"two"}, {"start":3,"end":4,"speaker":"C","text":"three"} ]` parsed, err := ParseReader(strings.NewReader(input)) if err != nil { t.Fatalf("parse failed: %v", err) } for index, segment := range parsed.Segments { if segment.InputIndex != index { t.Fatalf("segment %d input index = %d, want %d", index, segment.InputIndex, index) } } } func assertContains(t *testing.T, err error, fragment string) { t.Helper() if err == nil { t.Fatalf("expected error containing %q", fragment) } if !strings.Contains(err.Error(), fragment) { t.Fatalf("error = %q, want substring %q", err.Error(), fragment) } }