File size: 3,560 Bytes
57c13f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""Tests d'intégration de l'API AstroDynamics."""

import pytest


def test_health_returns_loaded_model(client) -> None:
    response = client.get("/health")

    assert response.status_code == 200

    payload = response.json()

    assert payload["status"] == "ok"
    assert payload["environment"] == "LunarLander-v3"
    assert payload["algorithm"] == "DQN"
    assert payload["model"] == "dqn_lunarlander_final.zip"


@pytest.mark.parametrize("endpoint", ["/play", "/predict"])
def test_prediction_returns_valid_action(client, endpoint: str) -> None:
    response = client.post(
        endpoint,
        json={
            "observation": [
                0.0,   # position x
                1.0,   # position y
                0.0,   # vitesse x
                -0.2,  # vitesse y
                0.0,   # angle
                0.0,   # vitesse angulaire
                0.0,   # contact jambe gauche
                0.0,   # contact jambe droite
            ]
        },
    )

    assert response.status_code == 200

    payload = response.json()

    assert payload["action"] in {0, 1, 2, 3}
    assert isinstance(payload["action_label"], str)
    assert len(payload["q_values"]) == 4
    assert all(isinstance(value, float) for value in payload["q_values"])
    assert isinstance(payload["circumstance"], str)


@pytest.mark.parametrize(
    "observation",
    [
        [],
        [0.0] * 7,
        [0.0] * 9,
    ],
)
def test_prediction_rejects_invalid_observation_length(
    client,
    observation: list[float],
) -> None:
    response = client.post(
        "/predict",
        json={"observation": observation},
    )

    assert response.status_code == 422


def test_metrics_are_initially_empty(client) -> None:
    response = client.get("/metrics")

    assert response.status_code == 200

    payload = response.json()

    assert payload["summary"]["number_of_episodes"] == 0
    assert payload["summary"]["mean_reward"] is None
    assert payload["summary"]["std_reward"] is None
    assert payload["episodes"] == []
    assert payload["action_distribution"] == {}
    assert payload["decisions_by_circumstance"] == {}


@pytest.mark.integration
def test_episode_is_played_and_persisted(client) -> None:
    """
    Joue un véritable épisode avec le modèle sauvegardé.

    L'animation est désactivée pour accélérer la CI et limiter la RAM.
    """
    response = client.post(
        "/episodes",
        json={
            "seed": 42,
            "capture_animation": False,
            "frame_stride": 5,
        },
    )

    assert response.status_code == 200

    episode = response.json()

    assert len(episode["episode_id"]) == 32
    assert episode["seed"] == 42
    assert episode["steps"] > 0
    assert isinstance(episode["reward"], float)
    assert isinstance(episode["success"], bool)
    assert episode["animation_url"] is None
    assert sum(episode["action_counts"].values()) == episode["steps"]

    metrics_response = client.get("/metrics")

    assert metrics_response.status_code == 200

    metrics = metrics_response.json()

    assert metrics["summary"]["number_of_episodes"] == 1
    assert metrics["episodes"][0]["episode_id"] == episode["episode_id"]
    assert metrics["episodes"][0]["seed"] == 42
    assert metrics["episodes"][0]["reward"] == pytest.approx(
        episode["reward"]
    )


def test_unknown_animation_returns_404(client) -> None:
    unknown_id = "0" * 32

    response = client.get(
        f"/episodes/{unknown_id}/animation"
    )

    assert response.status_code == 404