Skip to content

Commit

Permalink
test: Add initial tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jesse-c committed Nov 28, 2024
1 parent 51778f9 commit b41853d
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ wheels/
.venv
/audios/
/transcripts/
/.coverage
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,5 @@ uv sync --dev --all-extras

Tests can be run using pytest:
```bash
uv run pytest
uv run pytest --cov=src tests/
```
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ dependencies = [
"rich>=13.9.4",
"python-ffmpeg>=2.0.12",
]

[project.optional-dependencies]
dev = [
"pytest>=8.3.3",
"pytest-cov>=6.0.0",
]
Empty file added src/__init__.py
Empty file.
4 changes: 3 additions & 1 deletion src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ def transcribe_audio(speech_file: Path) -> str:
with Live(spinner, refresh_per_second=10):
result = mlx_whisper.transcribe(
str(speech_file),
# FIX: `ValueError: [load_npz] Input must be a zip file or a file-like object that can be opened with zipfile.ZipFile`
# FIX: `ValueError: [load_npz] Input must be a zip file or
# a file-like object that can be opened with
# zipfile.ZipFile`
# path_or_hf_repo="mlx-community/whisper-large-v3-turbo",
path_or_hf_repo="mlx-community/whisper-turbo",
)
Expand Down
Empty file added tests/__init__.py
Empty file.
Empty file added tests/conftest.py
Empty file.
144 changes: 144 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
from unittest.mock import Mock, patch

import pytest
from typer.testing import CliRunner

from src.main import (
is_youtube_url,
record_audio,
summarise_audio,
transcribe_audio,
validate_local_file,
)

runner = CliRunner()


def test_is_youtube_url():
assert is_youtube_url("https://www.youtube.com/watch?v=dQw4w9WgXcQ") is True
assert is_youtube_url("https://youtube.com/watch?v=dQw4w9WgXcQ") is True
assert is_youtube_url("https://youtu.be/dQw4w9WgXcQ") is True
assert is_youtube_url("https://example.com") is False
assert is_youtube_url("not_a_url") is False


def test_validate_local_file(tmp_path):
# Create a temporary MP3 file
mp3_file = tmp_path / "test.mp3"
mp3_file.touch()

# Create a non-MP3 file
txt_file = tmp_path / "test.txt"
txt_file.touch()

# Test valid MP3 file
assert validate_local_file(mp3_file) == mp3_file

# Test non-existent file
with pytest.raises(FileNotFoundError):
validate_local_file(tmp_path / "nonexistent.mp3")

# Test non-MP3 file
with pytest.raises(ValueError):
validate_local_file(txt_file)


def test_record_audio(tmp_path):
timestamp = 1234567890
audios_path = tmp_path / "audios"
audios_path.mkdir()

mock_recorder = Mock()
mock_recorder.sample_rate = 16000
mock_recorder.frame_length = 128

# Make read() raise KeyboardInterrupt after 3 frames
read_count = 0

def mock_read():
nonlocal read_count
if read_count >= 3:
raise KeyboardInterrupt
read_count += 1
return [0] * 128

mock_recorder.read = mock_read

with patch("src.main.PvRecorder", return_value=mock_recorder):
with patch("src.main.FFmpeg") as mock_ffmpeg:
mock_ffmpeg.return_value.option.return_value.input.return_value.output.return_value.execute.return_value = None

result = record_audio(audios_path, timestamp, device_index=1)

assert result == audios_path / f"recording_{timestamp}.mp3"
assert mock_recorder.start.called
assert mock_recorder.delete.called


def test_transcribe_audio(tmp_path):
test_file = tmp_path / "test.mp3"
test_file.touch()

expected_text = "This is a test transcription"
mock_result = {"text": expected_text}

with patch("src.main.mlx_whisper.transcribe", return_value=mock_result):
result = transcribe_audio(test_file)
assert result == expected_text


def test_summarise_audio():
test_text = "This is a test text to summarize"
mock_response = Mock()
mock_response.choices = [Mock(message=Mock(content="Summary of the text"))]

with patch("src.main.OpenAI") as mock_openai:
mock_client = Mock()
mock_client.chat.completions.create.return_value = mock_response
mock_openai.return_value = mock_client

summarise_audio(test_text, profile="test profile")

mock_client.chat.completions.create.assert_called_once()


@pytest.mark.parametrize(
"source,expected_output",
[
("record", "Audio recorded"),
("https://youtube.com/watch?v=test", "Audio downloaded from YouTube"),
("test.mp3", "Valid MP3 file"),
],
)
def test_summarise_command(tmp_path, source, expected_output):
with (
patch("src.main.record_audio") as mock_record,
patch("src.main.download_from_youtube") as mock_download,
patch("src.main.validate_local_file") as mock_validate,
patch("src.main.transcribe_audio") as mock_transcribe,
):
# Setup mocks
mock_record.return_value = tmp_path / "recording.mp3"
mock_download.return_value = tmp_path / "youtube.mp3"
mock_validate.return_value = tmp_path / "test.mp3"
mock_transcribe.return_value = "Test transcription"

from src.main import app

result = runner.invoke(app, ["summarise", source])

assert result.exit_code == 0
assert expected_output in result.stdout


def test_list_devices():
mock_devices = ["Device 1", "Device 2"]

with patch("src.main.PvRecorder.get_available_devices", return_value=mock_devices):
from src.main import app

result = runner.invoke(app, ["list-devices"])

assert result.exit_code == 0
assert "Device 1" in result.stdout
assert "Device 2" in result.stdout
Loading

0 comments on commit b41853d

Please sign in to comment.