"""Unit tests for the SRT parser used by /dub/import-srt.""" import sys import os import pytest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend")) from services.srt_parser import parse_srt # noqa: E402 def test_parses_a_well_formed_srt_file(): srt = """1 00:00:01,000 --> 00:00:04,500 Hello world. 2 00:00:05,250 --> 00:00:08,000 Second line here. """ result = parse_srt(srt) assert result.skipped_cues == 0 assert result.dropped_overlaps == 0 assert len(result.segments) == 2 assert result.segments[0]["start"] == 1.0 assert result.segments[0]["end"] == 4.5 assert result.segments[0]["text"] == "Hello world." assert result.segments[1]["start"] == 5.25 assert result.segments[1]["text"] == "Second line here." def test_joins_multi_line_cues_with_newline(): srt = """1 00:00:01,000 --> 00:00:04,000 Line one Line two """ result = parse_srt(srt) assert result.segments[0]["text"] == "Line one\nLine two" def test_accepts_dot_as_milliseconds_separator(): # WebVTT-style timestamps inside an SRT file are common in the wild. srt = """1 00:00:01.500 --> 00:00:03.250 Dotty. """ result = parse_srt(srt) assert result.segments[0]["start"] == 1.5 assert result.segments[0]["end"] == 3.25 def test_handles_utf8_bom_at_start_of_file(): srt = "1\n00:00:01,000 --> 00:00:02,000\nBOM cue.\n" result = parse_srt(srt) assert len(result.segments) == 1 assert result.segments[0]["text"] == "BOM cue." def test_handles_crlf_line_endings(): srt = "1\r\n00:00:01,000 --> 00:00:02,000\r\nWindows.\r\n\r\n" result = parse_srt(srt) assert len(result.segments) == 1 def test_skips_cue_with_non_positive_duration(): srt = """1 00:00:05,000 --> 00:00:05,000 Zero duration. 2 00:00:06,000 --> 00:00:05,500 End before start. 3 00:00:07,000 --> 00:00:08,000 Good cue. """ result = parse_srt(srt) assert result.skipped_cues == 2 assert len(result.segments) == 1 assert result.segments[0]["text"] == "Good cue." def test_skips_cue_with_only_whitespace_body(): srt = """1 00:00:01,000 --> 00:00:02,000 2 00:00:03,000 --> 00:00:04,000 Real text. """ result = parse_srt(srt) assert result.skipped_cues == 1 assert len(result.segments) == 1 def test_shifts_overlapping_cue_to_keep_both(): # Cue 2 starts inside cue 1; we expect cue 2's start to be pushed # forward to cue 1's end so both still play, in order. srt = """1 00:00:01,000 --> 00:00:05,000 First. 2 00:00:03,000 --> 00:00:07,000 Second. """ result = parse_srt(srt) assert len(result.segments) == 2 assert result.segments[1]["start"] == 5.0 assert result.segments[1]["end"] == 7.0 assert result.dropped_overlaps == 0 def test_drops_overlap_that_would_have_negative_duration(): srt = """1 00:00:01,000 --> 00:00:10,000 First. 2 00:00:03,000 --> 00:00:08,000 Second entirely inside first. """ result = parse_srt(srt) assert len(result.segments) == 1 assert result.dropped_overlaps == 1 def test_parses_missing_index_numbers(): # No "1", "2" lines — just timings + text. This happens with some # subtitle editors that strip indices. srt = """00:00:01,000 --> 00:00:02,000 First. 00:00:03,000 --> 00:00:04,000 Second. """ result = parse_srt(srt) assert len(result.segments) == 2 def test_returns_empty_result_for_empty_input(): assert parse_srt("").segments == [] assert parse_srt("").skipped_cues == 0 def test_segments_get_sequential_ids_and_required_fields(): srt = """1 00:00:01,000 --> 00:00:02,000 A 2 00:00:03,000 --> 00:00:04,000 B """ result = parse_srt(srt) for i, seg in enumerate(result.segments): assert seg["id"] == i assert seg["text"] == seg["text_original"] assert seg["speaker_id"] == "Speaker 1"