diff options
| author | Yuval Adam <_@yuv.al> | 2026-07-24 12:11:54 +0200 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2026-07-24 12:11:54 +0200 |
| commit | df594d42ad1206fcb246bedb1945949527d7e039 (patch) | |
| tree | a8fe0e95404d1712331c90cb5095f1df4844c513 /tests | |
Implement initial terminal-independent transcription core
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_persistence.py | 39 | ||||
| -rw-r--r-- | tests/test_recording.py | 65 | ||||
| -rw-r--r-- | tests/test_transcription.py | 29 |
3 files changed, 133 insertions, 0 deletions
diff --git a/tests/test_persistence.py b/tests/test_persistence.py new file mode 100644 index 0000000..eecb378 --- /dev/null +++ b/tests/test_persistence.py @@ -0,0 +1,39 @@ +from datetime import datetime, timezone +from pathlib import Path +import tempfile +import unittest + +from parley.persistence import save_transcript + + +class PersistenceTests(unittest.TestCase): + def setUp(self) -> None: + self.temporary = tempfile.TemporaryDirectory() + self.directory = Path(self.temporary.name) / "transcripts" + self.now = datetime(2026, 7, 24, 12, 34, 56, 123456, timezone.utc) + + def tearDown(self) -> None: + self.temporary.cleanup() + + def test_writes_utf8_with_one_final_newline(self) -> None: + path = save_transcript("héllo\n\n", self.directory, clock=lambda: self.now) + self.assertEqual(path.read_text(encoding="utf-8"), "héllo\n") + self.assertEqual( + path.name, "transcript_2026-07-24_12-34-56_123456+0000.txt" + ) + + def test_uses_counter_on_collision(self) -> None: + first = save_transcript("one", self.directory, clock=lambda: self.now) + second = save_transcript("two", self.directory, clock=lambda: self.now) + self.assertNotEqual(first, second) + self.assertEqual(second.stem[-2:], "_1") + self.assertEqual(first.read_text(), "one\n") + self.assertEqual(second.read_text(), "two\n") + + def test_leaves_no_temporary_file(self) -> None: + save_transcript("text", self.directory, clock=lambda: self.now) + self.assertFalse(any(path.suffix == ".tmp" for path in self.directory.iterdir())) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_recording.py b/tests/test_recording.py new file mode 100644 index 0000000..8183c0b --- /dev/null +++ b/tests/test_recording.py @@ -0,0 +1,65 @@ +from pathlib import Path +import tempfile +import unittest +from unittest.mock import MagicMock, patch + +from parley.config import Config +from parley.errors import RecordingError +from parley.recording import Recorder + + +class RecorderTests(unittest.TestCase): + def setUp(self) -> None: + self.temporary = tempfile.TemporaryDirectory() + root = Path(self.temporary.name) + self.output = root / "work" / "audio.wav" + self.config = Config( + ffmpeg=Path("/usr/bin/ffmpeg"), + transcribe_cli=root / "transcribe-cli", + model=root / "model.gguf", + transcript_dir=root / "transcripts", + ) + + def tearDown(self) -> None: + self.temporary.cleanup() + + @patch("parley.recording.subprocess.Popen") + def test_start_and_stop(self, popen: MagicMock) -> None: + process = popen.return_value + process.poll.return_value = None + process.communicate.return_value = (b"", b"") + process.returncode = 0 + recorder = Recorder(self.config) + + recorder.start(self.output) + self.output.write_bytes(b"RIFF") + result = recorder.stop() + + self.assertEqual(result, self.output) + self.assertFalse(recorder.is_recording) + process.communicate.assert_called_once_with(input=b"q\n", timeout=5.0) + command = popen.call_args.args[0] + self.assertIn("pulse", command) + self.assertIn("16000", command) + + @patch("parley.recording.subprocess.Popen") + def test_cancel_discards_output(self, popen: MagicMock) -> None: + process = popen.return_value + process.communicate.return_value = (b"", b"") + process.returncode = 0 + recorder = Recorder(self.config) + recorder.start(self.output) + self.output.write_bytes(b"partial") + + recorder.cancel() + + self.assertFalse(self.output.exists()) + self.assertFalse(recorder.is_recording) + + def test_stop_without_start_is_an_error(self) -> None: + with self.assertRaisesRegex(RecordingError, "no recording"): + Recorder(self.config).stop() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_transcription.py b/tests/test_transcription.py new file mode 100644 index 0000000..916aac2 --- /dev/null +++ b/tests/test_transcription.py @@ -0,0 +1,29 @@ +import unittest + +from parley.errors import TranscriptionError +from parley.transcription import parse_jsonl + + +class ParseJsonlTests(unittest.TestCase): + def test_skips_header_and_malformed_lines(self) -> None: + output = '\n'.join([ + 'runtime diagnostic', + '{"type":"batch_header","count":1}', + '{"file":"recording.wav","text":"hello world"}', + ]) + self.assertEqual(parse_jsonl(output), "hello world") + + def test_reports_error_row(self) -> None: + with self.assertRaisesRegex(TranscriptionError, "decoder failed"): + parse_jsonl('{"file":"x.wav","error":"decoder failed","text":""}') + + def test_rejects_output_without_result(self) -> None: + with self.assertRaisesRegex(TranscriptionError, "no transcript"): + parse_jsonl('{"type":"batch_header"}\nnot json') + + def test_accepts_empty_transcript(self) -> None: + self.assertEqual(parse_jsonl('{"text":""}'), "") + + +if __name__ == "__main__": + unittest.main() |
