"""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