summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2026-07-24 12:11:54 +0200
committerYuval Adam <_@yuv.al>2026-07-24 12:11:54 +0200
commitdf594d42ad1206fcb246bedb1945949527d7e039 (patch)
treea8fe0e95404d1712331c90cb5095f1df4844c513 /tests
Implement initial terminal-independent transcription core
Diffstat (limited to 'tests')
-rw-r--r--tests/test_persistence.py39
-rw-r--r--tests/test_recording.py65
-rw-r--r--tests/test_transcription.py29
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()