"""Tests intégration HTTP de GET /api/v1/agents/update/check — DETTE-022 v2. Endpoint GATED (flag RPA_AUTO_UPDATE_SERVER_ENABLED), best-effort : - flag OFF par défaut → 503 (anti-régression : aucun effet sur le pipeline). - flag ON → 200 + payload {update_available, latest_version, update_type, url}. - auth Bearer requise (dépendance globale _verify_token). La logique PURE est testée sans serveur dans tests/unit/test_update_check_server.py (DETTE-013). Ici on vérifie le branchement HTTP minimal. """ from __future__ import annotations import sys from pathlib import Path import pytest _ROOT = str(Path(__file__).resolve().parents[2]) if _ROOT not in sys.path: sys.path.insert(0, _ROOT) pytestmark = pytest.mark.integration _TEST_API_TOKEN = "test_update_check_endpoint_token" @pytest.fixture def client(monkeypatch): monkeypatch.setenv("RPA_API_TOKEN", _TEST_API_TOKEN) from fastapi.testclient import TestClient from agent_v0.server_v1 import api_stream monkeypatch.setattr(api_stream, "API_TOKEN", _TEST_API_TOKEN) return TestClient(api_stream.app, raise_server_exceptions=False) def _auth_headers(): return {"Authorization": f"Bearer {_TEST_API_TOKEN}"} class TestUpdateCheckEndpointFlag: def test_disabled_by_default_returns_503(self, client, monkeypatch): monkeypatch.delenv("RPA_AUTO_UPDATE_SERVER_ENABLED", raising=False) resp = client.get( "/api/v1/agents/update/check?current_version=1.0.1", headers=_auth_headers(), ) assert resp.status_code == 503 assert "RPA_AUTO_UPDATE_SERVER_ENABLED" in resp.text class TestUpdateCheckEndpointEnabled: @pytest.fixture(autouse=True) def _enable_flag(self, monkeypatch): monkeypatch.setenv("RPA_AUTO_UPDATE_SERVER_ENABLED", "true") # Version cible explicite pour rendre le test déterministe. monkeypatch.setenv("RPA_AGENT_LATEST_VERSION", "1.0.2") def test_update_available(self, client): resp = client.get( "/api/v1/agents/update/check?current_version=1.0.1&machine_id=pc-1", headers=_auth_headers(), ) assert resp.status_code == 200 body = resp.json() assert body["update_available"] is True assert body["latest_version"] == "1.0.2" assert body["update_type"] == "code-only" assert "1.0.2" in body["url"] def test_up_to_date(self, client): resp = client.get( "/api/v1/agents/update/check?current_version=1.0.2&machine_id=pc-1", headers=_auth_headers(), ) assert resp.status_code == 200 body = resp.json() assert body["update_available"] is False def test_requires_auth(self, client): resp = client.get( "/api/v1/agents/update/check?current_version=1.0.1", ) assert resp.status_code == 401 class TestUpdateCheckCanary: """Canary : seul le poste canary se voit proposer la nouvelle version. On n'utilise PAS RPA_AGENT_LATEST_VERSION (var legacy globale) : on pilote la version cible via la politique canary (stable + canary + allow-list). """ @pytest.fixture(autouse=True) def _enable_canary(self, monkeypatch): monkeypatch.setenv("RPA_AUTO_UPDATE_SERVER_ENABLED", "true") # Legacy OFF pour que la politique canary pilote la décision. monkeypatch.delenv("RPA_AGENT_LATEST_VERSION", raising=False) monkeypatch.setenv("RPA_AGENT_STABLE_VERSION", "1.0.1") monkeypatch.setenv("RPA_AGENT_CANARY_VERSION", "1.0.2") monkeypatch.setenv("RPA_AGENT_CANARY_MACHINES", "lea-4zbgwxty") def test_poste_canary_recoit_la_nouvelle_version(self, client): resp = client.get( "/api/v1/agents/update/check" "?current_version=1.0.1&machine_id=lea-4zbgwxty", headers=_auth_headers(), ) assert resp.status_code == 200 body = resp.json() assert body["update_available"] is True assert body["latest_version"] == "1.0.2" def test_poste_hors_canary_reste_a_jour_sur_stable(self, client): # Poste NON canary, déjà en 1.0.1 = stable → pas de MAJ (blast radius # borné : la 1.0.2 ne fuite pas hors de la liste canary). resp = client.get( "/api/v1/agents/update/check" "?current_version=1.0.1&machine_id=un-autre-poste", headers=_auth_headers(), ) assert resp.status_code == 200 body = resp.json() assert body["update_available"] is False