Skip to content

Commit 31b448b

Browse files
test(speak): cover empty-labels path; pin voice fallback, speaker count, multi duration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c4730c3 commit 31b448b

1 file changed

Lines changed: 18 additions & 6 deletions

File tree

tests/test_speak.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ def test_plays_audio_by_default(monkeypatch, fake_synthesize):
4949
assert result.exit_code == 0
5050
assert played == {"pcm": b"\x01\x02\x03\x04", "rate": 24000}
5151
assert fake_synthesize["cfg"].text == "Hello there"
52+
# No --voice given -> single-voice path falls back to the default "jane".
53+
assert fake_synthesize["cfg"].voice == "jane"
5254
# Human summary (stderr) reports the default "played" disposition.
5355
assert "played" in result.stderr
5456
assert "saved to" not in result.stderr
@@ -131,21 +133,18 @@ def test_human_mode_keeps_stdout_clean(monkeypatch, fake_synthesize):
131133
assert result.stdout.strip() == ""
132134

133135

134-
from aai_cli.tts import session as tts_session
135-
136-
137136
@pytest.fixture
138137
def fake_dialogue(monkeypatch: pytest.MonkeyPatch):
139138
calls: dict[str, object] = {}
140139

141140
def _fake(api_key, segments, *, language=None, sample_rate=None, connect=None, on_warning=None):
142141
calls["segments"] = segments
143142
calls["language"] = language
144-
return tts_session.SpeakResult(
145-
pcm=b"\x01\x02", sample_rate=24000, audio_duration_seconds=1.5
143+
return session.SpeakResult(
144+
pcm=b"\x01\x02", sample_rate=24000, audio_duration_seconds=1.23456
146145
)
147146

148-
monkeypatch.setattr(tts_session, "synthesize_dialogue", _fake)
147+
monkeypatch.setattr(session, "synthesize_dialogue", _fake)
149148
monkeypatch.setattr("aai_cli.commands.speak.audio.play_pcm", lambda *a, **k: None)
150149
return calls
151150

@@ -179,6 +178,8 @@ def test_bare_voice_in_dialogue_mode_is_ignored_with_a_note(fake_dialogue):
179178
assert fake_dialogue["segments"] == [("jane", "One."), ("michael", "Two.")]
180179
# ...and the user is told why, pointed at the per-speaker form.
181180
assert "A=NAME" in result.stderr
181+
# The human note reports the speaker count, pinning len(speakers) in _emit_multi.
182+
assert "2 voices" in result.stderr
182183

183184

184185
def test_dialogue_json_reports_speaker_voice_map(fake_dialogue):
@@ -190,6 +191,17 @@ def test_dialogue_json_reports_speaker_voice_map(fake_dialogue):
190191
assert payload["speakers"] == {"A": "jane", "B": "michael"}
191192
assert payload["segments"] == 2
192193
assert payload["sample_rate"] == 24000
194+
# 1.23456 rounded to 3 decimals -> pins the round(...) precision in _emit_multi.
195+
assert payload["audio_duration_seconds"] == 1.235
196+
197+
198+
def test_empty_speaker_labels_raises_usage_error():
199+
# Speaker-labeled input with no spoken text: detected as labeled, parses to zero
200+
# segments, and raises the usage error before any synthesis.
201+
result = runner.invoke(app, ["--sandbox", "speak"], input="Speaker A:\nSpeaker B:")
202+
assert result.exit_code == 2
203+
assert "No text to speak" in result.output
204+
assert "speaker labels" in result.output
193205

194206

195207
def test_unlabeled_text_still_uses_single_voice_path(fake_synthesize, monkeypatch):

0 commit comments

Comments
 (0)