74 lines
2.6 KiB
Python
74 lines
2.6 KiB
Python
import pytest
|
|
from sqlalchemy import select
|
|
|
|
from monlet_server.models import Agent
|
|
|
|
from ._helpers import make_heartbeat
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_heartbeat_creates_agent(app_client, auth_headers, session):
|
|
r = await app_client.post("/api/v1/heartbeat", json=make_heartbeat(), headers=auth_headers)
|
|
assert r.status_code == 202
|
|
res = await session.execute(select(Agent).where(Agent.agent_id == "agent-1"))
|
|
a = res.scalar_one()
|
|
assert a.hostname == "agent-1"
|
|
assert a.status == "alive"
|
|
assert a.pending_key_hash
|
|
assert a.accepted_key_hash is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_heartbeat_upsert(app_client, auth_headers, session):
|
|
await app_client.post("/api/v1/heartbeat", json=make_heartbeat(), headers=auth_headers)
|
|
hb = make_heartbeat()
|
|
hb["features"] = {"push": True, "metrics": False}
|
|
await app_client.post("/api/v1/heartbeat", json=hb, headers=auth_headers)
|
|
res = await session.execute(select(Agent).where(Agent.agent_id == "agent-1"))
|
|
a = res.scalar_one()
|
|
assert a.pending_features == {"push": True, "metrics": False}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_heartbeat_validation_400(app_client, auth_headers):
|
|
r = await app_client.post(
|
|
"/api/v1/heartbeat",
|
|
json={
|
|
"agent_id": "bad id!",
|
|
"observed_at": "x",
|
|
"hostname": "h",
|
|
"features": {"push": True, "metrics": False},
|
|
},
|
|
headers=auth_headers,
|
|
)
|
|
assert r.status_code == 400
|
|
assert r.json()["error"]["code"] == "validation"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("observed_at", ["2026-05-27T09:00:00", "2026-05-27T09:00:00+04:00"])
|
|
async def test_heartbeat_requires_utc_timestamp(app_client, auth_headers, observed_at):
|
|
hb = make_heartbeat()
|
|
hb["observed_at"] = observed_at
|
|
r = await app_client.post("/api/v1/heartbeat", json=hb, headers=auth_headers)
|
|
assert r.status_code == 400
|
|
assert r.json()["error"]["code"] == "validation"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_heartbeat_rejects_extra_fields(app_client, auth_headers):
|
|
hb = make_heartbeat()
|
|
hb["mode"] = "hybrid"
|
|
r = await app_client.post("/api/v1/heartbeat", json=hb, headers=auth_headers)
|
|
assert r.status_code == 400
|
|
assert r.json()["error"]["code"] == "validation"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_heartbeat_rejects_sensitive_label_keys(app_client, auth_headers):
|
|
hb = make_heartbeat()
|
|
hb["labels"] = {"api_key": "secret"}
|
|
r = await app_client.post("/api/v1/heartbeat", json=hb, headers=auth_headers)
|
|
assert r.status_code == 400
|
|
assert r.json()["error"]["code"] == "validation"
|