package schema import ( "os" "testing" ) func TestParseSourceTranscriptJSON_BareArray(t *testing.T) { raw, err := os.ReadFile("testdata/transcript_bare_array.json") if err != nil { t.Fatalf("failed to read test fixture: %v", err) } transcript, err := ParseSourceTranscriptJSON(raw) if err != nil { t.Fatalf("ParseSourceTranscriptJSON failed: %v", err) } if len(transcript.Segments) != 2 { t.Fatalf("expected 2 segments, got %d", len(transcript.Segments)) } s1 := transcript.Segments[0] if *s1.ID != 1 { t.Errorf("expected segment[0].id = 1, got %d", *s1.ID) } if s1.Speaker != "Alice" { t.Errorf("expected segment[0].speaker = Alice, got %q", s1.Speaker) } if s1.Start != 0.0 { t.Errorf("expected segment[0].start = 0.0, got %g", s1.Start) } if s1.End != 1.5 { t.Errorf("expected segment[0].end = 1.5, got %g", s1.End) } if s1.Text != "Hello world." { t.Errorf("expected segment[0].text = Hello world., got %q", s1.Text) } if s1.Categories != nil { t.Errorf("expected segment[0].categories = nil, got %v", s1.Categories) } s2 := transcript.Segments[1] if *s2.ID != 2 { t.Errorf("expected segment[1].id = 2, got %d", *s2.ID) } if len(s2.Categories) != 1 || s2.Categories[0] != "greeting" { t.Errorf("expected segment[1].categories = [greeting], got %v", s2.Categories) } } func TestParseSourceTranscriptJSON_ObjectWithSegments(t *testing.T) { raw, err := os.ReadFile("testdata/transcript_object.json") if err != nil { t.Fatalf("failed to read test fixture: %v", err) } transcript, err := ParseSourceTranscriptJSON(raw) if err != nil { t.Fatalf("ParseSourceTranscriptJSON failed: %v", err) } if len(transcript.Segments) != 2 { t.Fatalf("expected 2 segments, got %d", len(transcript.Segments)) } if *transcript.Segments[0].ID != 1 { t.Errorf("expected segment[0].id = 1, got %d", *transcript.Segments[0].ID) } } func TestParseSourceTranscriptJSON_MalformedJSON(t *testing.T) { raw, err := os.ReadFile("testdata/transcript_malformed.json") if err != nil { t.Fatalf("failed to read test fixture: %v", err) } _, err = ParseSourceTranscriptJSON(raw) if err == nil { t.Fatal("expected error for malformed JSON, got nil") } parseErr, ok := err.(*ParseError) if !ok { t.Errorf("expected *ParseError, got %T", err) } else if parseErr.Message != "transcript is not valid JSON" { t.Errorf("expected 'transcript is not valid JSON', got %q", parseErr.Message) } } func TestParseSourceTranscriptJSON_EmptySpeaker(t *testing.T) { raw, err := os.ReadFile("testdata/transcript_empty_speaker.json") if err != nil { t.Fatalf("failed to read test fixture: %v", err) } _, err = ParseSourceTranscriptJSON(raw) if err == nil { t.Fatal("expected error for empty speaker, got nil") } assertValidationError(t, err, "speaker", "must not be empty") } func TestParseSourceTranscriptJSON_EmptyText(t *testing.T) { raw, err := os.ReadFile("testdata/transcript_empty_text.json") if err != nil { t.Fatalf("failed to read test fixture: %v", err) } _, err = ParseSourceTranscriptJSON(raw) if err == nil { t.Fatal("expected error for empty text, got nil") } assertValidationError(t, err, "text", "must not be empty") } func TestParseSourceTranscriptJSON_InvalidTimes(t *testing.T) { raw, err := os.ReadFile("testdata/transcript_invalid_times.json") if err != nil { t.Fatalf("failed to read test fixture: %v", err) } _, err = ParseSourceTranscriptJSON(raw) if err == nil { t.Fatal("expected error for invalid times, got nil") } assertValidationErrorContains(t, err, "end", "must be greater than or equal to start") } func TestParseSourceTranscriptJSON_DuplicateIDs(t *testing.T) { raw, err := os.ReadFile("testdata/transcript_duplicate_ids.json") if err != nil { t.Fatalf("failed to read test fixture: %v", err) } _, err = ParseSourceTranscriptJSON(raw) if err == nil { t.Fatal("expected error for duplicate IDs, got nil") } assertValidationErrorContains(t, err, "id", "duplicate id") } func TestParseSourceTranscriptJSON_EmptyArray(t *testing.T) { raw, err := os.ReadFile("testdata/transcript_empty_array.json") if err != nil { t.Fatalf("failed to read test fixture: %v", err) } _, err = ParseSourceTranscriptJSON(raw) if err == nil { t.Fatal("expected error for empty array, got nil") } parseErr, ok := err.(*ParseError) if !ok { t.Errorf("expected *ParseError, got %T", err) } else if parseErr.Message != "transcript must contain at least one segment" { t.Errorf("expected 'transcript must contain at least one segment', got %q", parseErr.Message) } } func TestParseTranscriptJSON_NonSequentialID(t *testing.T) { raw, err := os.ReadFile("testdata/transcript_non_sequential_id.json") if err != nil { t.Fatalf("failed to read test fixture: %v", err) } _, err = ParseTranscriptJSON(raw) if err == nil { t.Fatal("expected error for non-sequential ID, got nil") } assertValidationErrorContains(t, err, "id", "must be sequential") } func TestParseTranscriptJSONLenient_AssignsSequentialIDs(t *testing.T) { raw, err := os.ReadFile("testdata/transcript_no_ids.json") if err != nil { t.Fatalf("failed to read test fixture: %v", err) } transcript, err := ParseTranscriptJSONLenient(raw) if err != nil { t.Fatalf("ParseTranscriptJSONLenient failed: %v", err) } if len(transcript.Segments) != 2 { t.Fatalf("expected 2 segments, got %d", len(transcript.Segments)) } if transcript.Segments[0].ID != 1 { t.Errorf("expected segment[0].id = 1, got %d", transcript.Segments[0].ID) } if transcript.Segments[1].ID != 2 { t.Errorf("expected segment[1].id = 2, got %d", transcript.Segments[1].ID) } } func TestParseSourceTranscriptJSON_UnsupportedTopLevelShape(t *testing.T) { raw := []byte(`"just a string"`) _, err := ParseSourceTranscriptJSON(raw) if err == nil { t.Fatal("expected error for unsupported top-level shape, got nil") } parseErr, ok := err.(*ParseError) if !ok { t.Errorf("expected *ParseError, got %T", err) } else if parseErr.Message != "transcript must be a JSON array or an object with a segments array" { t.Errorf("expected unsupported shape message, got %q", parseErr.Message) } } func TestParseSourceTranscriptJSON_ObjectWithoutSegments(t *testing.T) { raw := []byte(`{"metadata": {"version": "1.0"}}`) _, err := ParseSourceTranscriptJSON(raw) if err == nil { t.Fatal("expected error for object without segments, got nil") } parseErr, ok := err.(*ParseError) if !ok { t.Errorf("expected *ParseError, got %T", err) } else if parseErr.Message != "transcript object must contain a segments array" { t.Errorf("expected 'transcript object must contain a segments array', got %q", parseErr.Message) } } func TestTranscriptToJSON(t *testing.T) { transcript := &Transcript{ Segments: []Segment{ {ID: 1, Speaker: "Alice", Start: 0.0, End: 1.5, Text: "Hello world."}, {ID: 2, Speaker: "Bob", Start: 2.0, End: 3.5, Text: "Hi there.", Categories: []string{"greeting"}}, }, } raw, err := TranscriptToJSON(transcript) if err != nil { t.Fatalf("TranscriptToJSON failed: %v", err) } parsed, err := ParseTranscriptJSON(raw) if err != nil { t.Fatalf("failed to parse round-trip JSON: %v", err) } if len(parsed.Segments) != 2 { t.Fatalf("expected 2 segments after round-trip, got %d", len(parsed.Segments)) } if parsed.Segments[0].Speaker != "Alice" { t.Errorf("expected segment[0].speaker = Alice, got %q", parsed.Segments[0].Speaker) } if len(parsed.Segments[1].Categories) != 1 || parsed.Segments[1].Categories[0] != "greeting" { t.Errorf("expected segment[1].categories = [greeting], got %v", parsed.Segments[1].Categories) } } func assertValidationError(t *testing.T, err error, fieldContains, messageContains string) { t.Helper() valErr, ok := err.(*ValidationError) if !ok { t.Errorf("expected *ValidationError, got %T", err) return } if fieldContains != "" && valErr.Field == "" { t.Errorf("expected field to contain %q, got empty field", fieldContains) } if messageContains != "" && valErr.Message == "" { t.Errorf("expected message to contain %q, got empty message", messageContains) } } func assertValidationErrorContains(t *testing.T, err error, fieldContains, messageContains string) { t.Helper() valErr, ok := err.(*ValidationError) if !ok { t.Errorf("expected *ValidationError, got %T", err) return } if fieldContains != "" && valErr.Field == "" { t.Errorf("expected field to contain %q, got empty field", fieldContains) } if messageContains != "" && valErr.Message == "" { t.Errorf("expected message to contain %q, got empty message", messageContains) } }