| | |
| | | |
| | | |
| | | CLIPS = [ |
| | | "/english_learning/shadowing/lessons/unit01/01.mp3", |
| | | "/english_learning/shadowing/lessons/unit01/02.mp3", |
| | | "/english_learning/shadowing/lessons/l1/001/en/01.mp3", |
| | | "/english_learning/shadowing/lessons/l1/001/en/02.mp3", |
| | | ] |
| | | |
| | | |
| | | def test_resolve_expected_text_from_transcripts_not_path(): |
| | | task = { |
| | | "transcripts": {"01.mp3": "Hello, how are you today?", "01": "Hello, how are you today?"}, |
| | | "sentences": ["/english_learning/shadowing/lessons/unit01/01.mp3"], |
| | | "sentences": ["/english_learning/shadowing/lessons/l1/001/en/01.mp3"], |
| | | } |
| | | expected = resolve_expected_text(task, CLIPS, 0) |
| | | assert expected == "Hello, how are you today?" |
| | |
| | | |
| | | def test_resolve_expected_text_rejects_path_as_original(): |
| | | task = { |
| | | "transcripts": {"01.mp3": "/english_learning/shadowing/lessons/unit01/01.mp3"}, |
| | | "transcripts": {"01.mp3": "/english_learning/shadowing/lessons/l1/001/en/01.mp3"}, |
| | | "sentences": [CLIPS[0]], |
| | | } |
| | | assert resolve_expected_text(task, CLIPS, 0) is None |
| | |
| | | ) |
| | | ) |
| | | assert switched == [("chat", "conversation", False, False)] |
| | | assert spoken == ["复读结束。我们来聊天吧。"] |
| | | assert spoken == ["泛听结束。我们来聊天吧。"] |
| | | |
| | | |
| | | def test_late_asr_after_listen_window_still_scores_previous_sentence(tmp_path, monkeypatch): |
| | |
| | | |
| | | captured = {} |
| | | |
| | | def fake_save(session_id, step_index, wav_bytes, conn=None): |
| | | def fake_save(session_id, step_index, wav_bytes, conn=None, text=None): |
| | | captured["wav"] = wav_bytes |
| | | captured["text"] = text |
| | | captured["id"] = f"{session_id}_{step_index}.wav" |
| | | return captured["id"] |
| | | |
| | |
| | | turn = conn.task_session.find_turn(0) |
| | | assert turn["metrics"][0]["method"] == "text_similarity_v0" |
| | | assert turn["audio_id"] == captured["id"] |
| | | assert captured["text"] == "Hello, how are you today?" |
| | | wav = pcm_snapshot_to_wav(conn.last_asr_audio_snapshot) |
| | | assert captured["wav"][:4] == b"RIFF" |
| | | assert wav[8:12] == b"WAVE" |
| | | assert captured["wav"] == wav |
| | | |
| | | |
| | | def test_later_asr_updates_sidecar_without_new_wav(tmp_path, monkeypatch): |
| | | from pathlib import Path |
| | | from core.app_runtime.runtime import ( |
| | | apply_deferred_assessment, |
| | | apply_shadowing_user_text, |
| | | save_shadowing_listen_audio, |
| | | ) |
| | | |
| | | conn = _shadowing_conn(tmp_path, monkeypatch) |
| | | conn.shadowing_listen_pcm = [b"\x00\x00" * 80] |
| | | save_shadowing_listen_audio(conn, 0) |
| | | turn = conn.task_session.find_turn(0) |
| | | wav_path = tmp_path / "data" / "recordings" / Path(turn["audio_id"]) |
| | | txt_path = wav_path.with_suffix(".txt") |
| | | assert txt_path.read_text(encoding="utf-8").strip() == "" |
| | | apply_shadowing_user_text(conn, "Hello, how are you today?") |
| | | apply_deferred_assessment(conn, 0, persist_audio=False) |
| | | recordings = list((tmp_path / "data" / "recordings").rglob("*.wav")) |
| | | assert len(recordings) == 1 |
| | | assert txt_path.read_text(encoding="utf-8").strip() == "Hello, how are you today?" |
| | | |
| | | |
| | | def test_shadowing_playlist_end_announces_then_chat(tmp_path, monkeypatch): |
| | |
| | | assert played == [] |
| | | assert switched == [("chat", "conversation", False)] |
| | | assert spoken == ["跟读结束。我们来聊天吧。"] |
| | | |
| | | |
| | | def test_repeater_completed_returns_chat_after_reconnect(monkeypatch): |
| | | """WS 掉线后设备可能在新的 CHAT 连接上补发 audio.completed。""" |
| | | from core.app_runtime.runtime import handle_skill_event |
| | | |
| | | switched = [] |
| | | spoken = [] |
| | | |
| | | async def fake_start(conn, app_id, task_id, params, source="voice", **kwargs): |
| | | switched.append((app_id, task_id, kwargs.get("speak_switch"))) |
| | | conn.interaction_mode = "CHAT" |
| | | return None |
| | | |
| | | monkeypatch.setattr("core.app_runtime.runtime.start_app_task", fake_start) |
| | | monkeypatch.setattr("core.app_runtime.runtime._speak", lambda conn, text: spoken.append(text)) |
| | | conn = SimpleNamespace( |
| | | interaction_mode="CHAT", |
| | | active_app_id="chat", |
| | | current_task_id="conversation", |
| | | task_session=None, |
| | | repeater_speed_queue=[], |
| | | repeater_playlist=[], |
| | | shadowing_awaiting_user=False, |
| | | prompt="", |
| | | config={}, |
| | | device_id="mac", |
| | | tts=None, |
| | | ) |
| | | asyncio.run( |
| | | handle_skill_event( |
| | | conn, "[skill_event]audio.completed|english_learning|repeater|101290" |
| | | ) |
| | | ) |
| | | assert switched == [("chat", "conversation", False)] |
| | | assert spoken == ["泛听结束。我们来聊天吧。"] |
| | | |
| | | |
| | | def test_playlist_completed_returns_chat_after_reconnect(monkeypatch): |
| | |
| | | wav_path = tmp_path / "data" / "recordings" / Path(turn["audio_id"]) |
| | | assert wav_path.is_file() |
| | | assert wav_path.stat().st_size > 44 |
| | | txt_path = wav_path.with_suffix(".txt") |
| | | assert txt_path.is_file() |
| | | assert txt_path.read_text(encoding="utf-8").strip() == "" |
| | | |
| | | |
| | | def test_fifteen_listen_windows_write_fifteen_wavs(tmp_path, monkeypatch): |