feat(agent): add learn action flow and grounding guards
This commit is contained in:
@@ -15,8 +15,10 @@ garantit que l'env est defini AVANT tout import.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -273,6 +275,107 @@ def test_reenroll_after_uninstall_reactivates(agents_client):
|
||||
assert agent["version"] == "1.1.0"
|
||||
|
||||
|
||||
def test_reenroll_after_admin_revoke_is_forbidden(agents_client):
|
||||
client, token, _ = agents_client
|
||||
|
||||
client.post(
|
||||
"/api/v1/agents/enroll",
|
||||
json={"machine_id": "revoked-001", "user_name": "Revoked"},
|
||||
headers=_auth_headers(token),
|
||||
)
|
||||
revoke = client.post(
|
||||
"/api/v1/agents/uninstall",
|
||||
json={"machine_id": "revoked-001", "reason": "admin_revoke"},
|
||||
headers=_auth_headers(token),
|
||||
)
|
||||
assert revoke.status_code == 200
|
||||
|
||||
resp = client.post(
|
||||
"/api/v1/agents/enroll",
|
||||
json={"machine_id": "revoked-001", "user_name": "Revoked Again"},
|
||||
headers=_auth_headers(token),
|
||||
)
|
||||
|
||||
assert resp.status_code == 403, resp.text
|
||||
detail = resp.json()["detail"]
|
||||
assert detail["error"] == "agent_revoked"
|
||||
assert detail["existing"]["machine_id"] == "revoked-001"
|
||||
assert detail["existing"]["uninstall_reason"] == "admin_revoke"
|
||||
|
||||
|
||||
def test_revoked_agent_cannot_stream_or_poll(agents_client):
|
||||
client, token, _ = agents_client
|
||||
|
||||
client.post(
|
||||
"/api/v1/agents/enroll",
|
||||
json={"machine_id": "revoked-runtime-001", "user_name": "Runtime"},
|
||||
headers=_auth_headers(token),
|
||||
)
|
||||
client.post(
|
||||
"/api/v1/agents/uninstall",
|
||||
json={"machine_id": "revoked-runtime-001", "reason": "admin_revoke"},
|
||||
headers=_auth_headers(token),
|
||||
)
|
||||
|
||||
event_resp = client.post(
|
||||
"/api/v1/traces/stream/event",
|
||||
json={
|
||||
"session_id": "sess_revoked_runtime",
|
||||
"timestamp": time.time(),
|
||||
"event": {"type": "heartbeat"},
|
||||
"machine_id": "revoked-runtime-001",
|
||||
},
|
||||
headers=_auth_headers(token),
|
||||
)
|
||||
assert event_resp.status_code == 403, event_resp.text
|
||||
assert event_resp.json()["detail"]["error"] == "agent_not_active"
|
||||
|
||||
next_resp = client.get(
|
||||
"/api/v1/traces/stream/replay/next",
|
||||
params={
|
||||
"session_id": "sess_revoked_runtime",
|
||||
"machine_id": "revoked-runtime-001",
|
||||
},
|
||||
headers=_auth_headers(token),
|
||||
)
|
||||
assert next_resp.status_code == 403, next_resp.text
|
||||
assert next_resp.json()["detail"]["error"] == "agent_not_active"
|
||||
|
||||
|
||||
def test_active_agent_stream_updates_last_seen(agents_client):
|
||||
client, token, registry = agents_client
|
||||
machine_id = "last-seen-001"
|
||||
|
||||
client.post(
|
||||
"/api/v1/agents/enroll",
|
||||
json={"machine_id": machine_id, "user_name": "Seen"},
|
||||
headers=_auth_headers(token),
|
||||
)
|
||||
stale = "2000-01-01T00:00:00+00:00"
|
||||
with sqlite3.connect(str(registry.db_path)) as conn:
|
||||
conn.execute(
|
||||
"UPDATE enrolled_agents SET last_seen_at = ? WHERE machine_id = ?",
|
||||
(stale, machine_id),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
resp = client.post(
|
||||
"/api/v1/traces/stream/event",
|
||||
json={
|
||||
"session_id": "sess_last_seen",
|
||||
"timestamp": time.time(),
|
||||
"event": {"type": "heartbeat"},
|
||||
"machine_id": machine_id,
|
||||
},
|
||||
headers=_auth_headers(token),
|
||||
)
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = registry.get(machine_id)
|
||||
assert row is not None
|
||||
assert row["last_seen_at"] != stale
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /api/v1/agents/fleet
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user