"""The monitor must record step-by-step turns (heard / thought / answered, per-step timing, ok vs error) and stream them to subscribers — that data is exactly what the status dashboard renders.""" import asyncio import json from wsai.backends.mock import MockBrain, MockSTT, MockTTS from wsai.monitor import Monitor from wsai.pipeline import Pipeline def test_monitor_records_turn_with_timed_steps(): async def go(): mon = Monitor() pipe = Pipeline( brain=MockBrain(), stt=MockSTT(script=["안녕"], interval=0.01), tts=MockTTS(), monitor=mon, ) await asyncio.wait_for(pipe.run(), timeout=5) return mon mon = asyncio.run(go()) snap = mon.snapshot() assert snap["status"]["turns_total"] == 1 assert snap["status"]["running"] is False # cleaned up after run assert len(snap["turns"]) == 1 turn = snap["turns"][0] assert turn["heard"] == "안녕" # what it heard assert turn["reply"] # what it answered assert turn["status"] == "ok" # it worked assert turn["total_ms"] >= 0 # step-by-step: every stage is named and timed names = [s["name"] for s in turn["steps"]] assert names == ["화면 맥락", "두뇌(생각)", "응답(TTS/전송)"] assert all(s["ok"] is True for s in turn["steps"]) assert all(s["ms"] >= 0 for s in turn["steps"]) def test_monitor_marks_errors(): class BoomBrain(MockBrain): async def respond(self, user_text, screen, history): raise RuntimeError("boom") async def go(): mon = Monitor() pipe = Pipeline( brain=BoomBrain(), stt=MockSTT(script=["안녕"], interval=0.01), tts=MockTTS(), monitor=mon, ) try: await asyncio.wait_for(pipe.run(), timeout=5) except BaseException: pass # TaskGroup re-raises; we only care about recorded telemetry return mon mon = asyncio.run(go()) snap = mon.snapshot() turn = snap["turns"][0] assert turn["status"] == "error" brain_step = next(s for s in turn["steps"] if s["name"] == "두뇌(생각)") assert brain_step["ok"] is False assert "boom" in brain_step["error"] assert snap["status"]["errors_total"] >= 1 def test_subscriber_receives_live_turn_events(): async def go(): mon = Monitor() q = mon.subscribe() pipe = Pipeline( brain=MockBrain(), stt=MockSTT(script=["안녕"], interval=0.01), tts=MockTTS(), monitor=mon, ) await asyncio.wait_for(pipe.run(), timeout=5) return q q = asyncio.run(go()) events = [] while not q.empty(): events.append(json.loads(q.get_nowait())) types = {e["type"] for e in events} assert "turn" in types # live turn updates were pushed assert "status" in types # listening/running status changes were pushed