File size: 3,410 Bytes
0c035a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def create_lesson(client):
    response = client.post(
        "/api/v1/sessions",
        json={"topic": "Gravity", "learner_level": "beginner"},
    )
    assert response.status_code == 201
    return response.json()["id"]


def test_complete_teaching_flow(client):
    session_id = create_lesson(client)
    with client.stream(
        "POST",
        f"/api/v1/sessions/{session_id}/teach",
        json={"text": "Why do things fall?"},
    ) as response:
        body = "".join(response.iter_text())

    assert response.status_code == 200
    assert "event: text_delta" in body
    assert "event: lesson_payload" in body
    assert "event: audio_ready" in body
    assert "event: done" in body

    session = client.get(f"/api/v1/sessions/{session_id}").json()
    assert len(session["messages"]) == 2
    assert len(session["artifacts"]) == 1
    quiz_id = session["artifacts"][0]["quiz"][0]["id"]

    answer = client.post(
        f"/api/v1/sessions/{session_id}/quiz/{quiz_id}/answer",
        json={"answer": "Attracts"},
    )
    assert answer.status_code == 200
    assert answer.json()["is_correct"] is True
    assert answer.json()["score"] == 1.0


def test_input_validation_and_missing_resources(client):
    session_id = create_lesson(client)
    empty = client.post(
        f"/api/v1/sessions/{session_id}/teach", json={"text": "   "}
    )
    assert empty.status_code == 422
    assert empty.json()["detail"]["code"] == "EMPTY_TRANSCRIPT"

    missing = client.get("/api/v1/sessions/not-real")
    assert missing.status_code == 404


def test_audio_and_session_deletion(client):
    session_id = create_lesson(client)
    client.post(
        f"/api/v1/sessions/{session_id}/teach",
        json={"text": "Explain gravity."},
    )
    audio_id = "11111111-1111-1111-1111-111111111111"
    audio = client.get(f"/api/v1/audio/{audio_id}")
    assert audio.status_code == 200
    assert audio.headers["content-type"] == "audio/mpeg"

    deleted = client.delete(f"/api/v1/sessions/{session_id}")
    assert deleted.status_code == 204
    assert client.get(f"/api/v1/sessions/{session_id}").status_code == 404
    assert client.get(f"/api/v1/audio/{audio_id}").status_code == 404


def test_live_websocket_voice_turn(client):
    session_id = create_lesson(client)
    with client.websocket_connect(
        f"/api/v1/sessions/{session_id}/live"
    ) as websocket:
        ready = websocket.receive_json()
        assert ready["type"] == "ready"
        assert ready["input_audio"]["sample_rate"] == 16000
        assert ready["output_audio"]["sample_rate"] == 24000

        websocket.send_bytes(b"fake-microphone-pcm")
        input_transcription = websocket.receive_json()
        output_transcription = websocket.receive_json()
        audio = websocket.receive_bytes()
        lesson_payload = websocket.receive_json()
        turn_complete = websocket.receive_json()

        assert input_transcription["type"] == "input_transcription"
        assert output_transcription["type"] == "output_transcription"
        assert audio == b"fake-live-pcm"
        assert lesson_payload["type"] == "lesson_payload"
        assert turn_complete["type"] == "turn_complete"

    session = client.get(f"/api/v1/sessions/{session_id}").json()
    assert [message["role"] for message in session["messages"]] == [
        "student",
        "teacher",
    ]
    assert len(session["artifacts"]) == 1