"""Tests for sermon_clean.transcribe. These tests don't actually invoke Whisper (that takes ~10-30s per test). They validate the data shapes and helper functions in pure Python. """ import re import pytest from sermon_clean.transcribe import ( find_bad_words, suggest_replacements, export_to_segments_json, ) class TestRegexPattern: """Verify the bad-word regex used by find_bad_words matches substring words correctly.""" def test_substring_match(self): pattern = re.compile("|".join(re.escape(w) for w in ["fuck", "shit"]), re.IGNORECASE) # "fucking" should match "fuck" as substring assert pattern.search("fucking") # "shitty" should match "shit" as substring assert pattern.search("shitty") # "fudge" should NOT match assert not pattern.search("fudge") # find_bad_words() guards against empty bad_words set (would match zero-width) from sermon_clean.transcribe import find_bad_words # Mock find_bad_words path: should return [] for empty bad_words # (real test would require whisper; we test the guard logic by reading source) import inspect src = inspect.getsource(find_bad_words) assert "if not bad_set:" in src and "return []" in src class TestSuggestReplacements: def test_no_context(self): hits = [{"start": 5.0, "end": 6.0, "word": "fuck", "probability": 0.9, "bad_word": "fuck"}] result = suggest_replacements(hits) assert result[0]["suggested_replacement"] == "" def test_with_context(self): hits = [{"start": 5.0, "end": 6.0, "word": "fucking", "probability": 0.9, "bad_word": "fuck"}] context = [ {"word": "you", "start": 4.5, "end": 4.9}, {"word": "are", "start": 7.0, "end": 7.4}, {"word": "great", "start": 7.5, "end": 7.9}, ] result = suggest_replacements(hits, transcript_context=context) assert "you" in result[0]["suggested_replacement"] assert "are" in result[0]["suggested_replacement"] def test_filters_far_words(self): hits = [{"start": 5.0, "end": 6.0, "word": "damn", "probability": 0.9, "bad_word": "damn"}] context = [ {"word": "long", "start": 0.0, "end": 0.5}, # too far {"word": "ago", "start": 9.0, "end": 9.5}, # too far ] result = suggest_replacements(hits, transcript_context=context, max_window=2.0) assert result[0]["suggested_replacement"] == "" class TestExportToSegmentsJson: def test_basic_export(self): hits = [{ "start": 5.0, "end": 6.0, "word": "fucking", "probability": 0.9, "bad_word": "fuck", "suggested_replacement": "the actual sentence", }] segs = export_to_segments_json(hits) assert len(segs) == 1 assert segs[0]["start"] == 5.0 assert segs[0]["end"] == 6.0 assert segs[0]["reason"] == "bad_word:fuck" assert segs[0]["replacement_text"] == "the actual sentence" def test_skip_empty_replacements_when_flagged(self): hits = [ {"start": 5.0, "end": 6.0, "word": "x", "probability": 0.9, "bad_word": "x", "suggested_replacement": ""}, {"start": 7.0, "end": 8.0, "word": "y", "probability": 0.9, "bad_word": "y", "suggested_replacement": "good"}, ] # Default: skip empty replacements segs = export_to_segments_json(hits, include_suggested_only=True) assert len(segs) == 1 # With include_suggested_only=False, both kept segs = export_to_segments_json(hits, include_suggested_only=False) assert len(segs) == 2 def test_rounds_timestamps(self): hits = [{"start": 5.123456, "end": 6.987654, "word": "x", "probability": 0.9, "bad_word": "x", "suggested_replacement": "r"}] segs = export_to_segments_json(hits) assert segs[0]["start"] == 5.123 assert segs[0]["end"] == 6.988