feat: add voice session confirmation guardrails
This commit is contained in:
@@ -6,6 +6,7 @@ from app.core.config import settings
|
||||
from app.db.database import get_db
|
||||
from app.main import app
|
||||
from app.services.adapters.text.models import StoryOutput
|
||||
from app.services.voice_transcription_service import VoiceTranscriptionResult
|
||||
|
||||
|
||||
async def test_voice_session_create_and_fallback_turn_returns_audio(
|
||||
@@ -272,6 +273,82 @@ async def test_voice_session_uploaded_audio_turn_uses_demo_transcript_hint(
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
async def test_voice_session_low_confidence_turn_requests_confirmation(
|
||||
db_session,
|
||||
auth_token,
|
||||
):
|
||||
async def override_get_db():
|
||||
yield db_session
|
||||
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
|
||||
with (
|
||||
patch(
|
||||
"app.services.voice_session_service.generate_story_content",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_generate,
|
||||
patch(
|
||||
"app.services.voice_session_service.text_to_speech",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_tts,
|
||||
patch(
|
||||
"app.services.voice_session_service.transcribe_voice_audio",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_transcribe,
|
||||
):
|
||||
mock_tts.return_value = b"confirmation-audio"
|
||||
mock_transcribe.return_value = VoiceTranscriptionResult(
|
||||
transcript_text="我想听一个会发光的小恐龙故事",
|
||||
confidence=0.41,
|
||||
provider="openai",
|
||||
)
|
||||
|
||||
transport = ASGITransport(app=app)
|
||||
try:
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
client.cookies.set("access_token", auth_token)
|
||||
|
||||
response = await client.post("/api/voice-sessions", json={})
|
||||
assert response.status_code == 201
|
||||
session_id = response.json()["id"]
|
||||
|
||||
response = await client.post(
|
||||
f"/api/voice-sessions/{session_id}/turns",
|
||||
files={
|
||||
"audio_file": ("turn.webm", b"fake-webm-audio", "audio/webm"),
|
||||
},
|
||||
)
|
||||
assert response.status_code == 202
|
||||
turn_id = response.json()["turn_id"]
|
||||
|
||||
response = await client.get(
|
||||
f"/api/voice-sessions/{session_id}/turns/{turn_id}"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
turn_data = response.json()
|
||||
assert turn_data["status"] == "audio_ready"
|
||||
assert turn_data["requires_confirmation"] is True
|
||||
assert turn_data["understanding_summary"].startswith("本轮系统理解为")
|
||||
assert "请家长帮忙确认" in turn_data["confirmation_message"]
|
||||
assert turn_data["assistant_text"] == turn_data["confirmation_message"]
|
||||
|
||||
response = await client.get(f"/api/voice-sessions/{session_id}")
|
||||
assert response.status_code == 200
|
||||
session_data = response.json()
|
||||
assert session_data["latest_requires_confirmation"] is True
|
||||
assert "请家长帮忙确认" in session_data["latest_confirmation_message"]
|
||||
assert session_data["can_finalize"] is False
|
||||
assert session_data["story_state"]["narrative_segments"] == []
|
||||
assert any(
|
||||
event["event_type"] == "turn_confirmation_requested"
|
||||
for event in session_data["events"]
|
||||
)
|
||||
|
||||
mock_generate.assert_not_awaited()
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
async def test_voice_session_list_orders_recent_sessions_first(
|
||||
db_session,
|
||||
auth_token,
|
||||
|
||||
Reference in New Issue
Block a user