@@ -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
138137def 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
184185def 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:\n Speaker 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
195207def test_unlabeled_text_still_uses_single_voice_path (fake_synthesize , monkeypatch ):
0 commit comments