149 lines
5.8 KiB
Python
149 lines
5.8 KiB
Python
import pytest
|
|
from sqlalchemy import func, select
|
|
|
|
from monlet_server.models import Agent, Check, Event
|
|
|
|
from ._helpers import make_event, register_agent
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_event_accepted_and_state(app_client, ui_auth_headers, session):
|
|
auth_headers = await register_agent(app_client, ui_auth_headers)
|
|
ev = make_event(status="ok", output="disk ok")
|
|
r = await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-1", "events": [ev]}, headers=auth_headers
|
|
)
|
|
assert r.status_code == 202
|
|
assert r.json() == {"accepted": 1, "deduplicated": 0}
|
|
n = (await session.execute(select(func.count()).select_from(Event))).scalar_one()
|
|
assert n == 1
|
|
chk = (await session.execute(select(Check))).scalar_one()
|
|
assert chk.status == "ok"
|
|
assert chk.summary == ev["output"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_event_before_heartbeat_is_dropped(app_client, auth_headers, session):
|
|
ev = make_event(status="warning", exit_code=1)
|
|
r = await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-replay", "events": [ev]}, headers=auth_headers
|
|
)
|
|
assert r.status_code == 202
|
|
assert r.json() == {"accepted": 0, "deduplicated": 0}
|
|
n = (await session.execute(select(func.count()).select_from(Agent))).scalar_one()
|
|
assert n == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_duplicate_event_deduplicated(app_client, ui_auth_headers):
|
|
auth_headers = await register_agent(app_client, ui_auth_headers)
|
|
ev = make_event(status="critical", exit_code=2)
|
|
r1 = await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-1", "events": [ev]}, headers=auth_headers
|
|
)
|
|
r2 = await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-1", "events": [ev]}, headers=auth_headers
|
|
)
|
|
assert r1.json() == {"accepted": 1, "deduplicated": 0}
|
|
assert r2.json() == {"accepted": 0, "deduplicated": 1}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_empty_batch_400(app_client, auth_headers):
|
|
r = await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-1", "events": []}, headers=auth_headers
|
|
)
|
|
assert r.status_code == 400
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("observed_at", ["2026-05-27T09:00:00", "2026-05-27T09:00:00+04:00"])
|
|
async def test_event_requires_utc_timestamp(app_client, ui_auth_headers, observed_at):
|
|
auth_headers = await register_agent(app_client, ui_auth_headers)
|
|
ev = make_event(observed_at=observed_at)
|
|
r = await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-1", "events": [ev]}, headers=auth_headers
|
|
)
|
|
assert r.status_code == 400
|
|
assert r.json()["error"]["code"] == "validation"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_oversize_batch_400(app_client, auth_headers):
|
|
evs = [make_event() for _ in range(201)]
|
|
r = await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-1", "events": evs}, headers=auth_headers
|
|
)
|
|
assert r.status_code == 400
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_repeated_same_status_not_stored(app_client, ui_auth_headers, session):
|
|
auth_headers = await register_agent(app_client, ui_auth_headers)
|
|
evs = [make_event(status="ok") for _ in range(5)]
|
|
r = await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-1", "events": evs}, headers=auth_headers
|
|
)
|
|
assert r.status_code == 202
|
|
assert r.json() == {"accepted": 1, "deduplicated": 4}
|
|
n = (await session.execute(select(func.count()).select_from(Event))).scalar_one()
|
|
assert n == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_status_transitions_stored(app_client, ui_auth_headers, session):
|
|
auth_headers = await register_agent(app_client, ui_auth_headers)
|
|
seq = [
|
|
make_event(status="ok"),
|
|
make_event(status="warning", exit_code=1),
|
|
make_event(status="warning", exit_code=1),
|
|
make_event(status="critical", exit_code=2),
|
|
make_event(status="ok"),
|
|
]
|
|
r = await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-1", "events": seq}, headers=auth_headers
|
|
)
|
|
assert r.status_code == 202
|
|
assert r.json() == {"accepted": 4, "deduplicated": 1}
|
|
n = (await session.execute(select(func.count()).select_from(Event))).scalar_one()
|
|
assert n == 4
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_same_status_different_exit_code_stored(app_client, ui_auth_headers, session):
|
|
auth_headers = await register_agent(app_client, ui_auth_headers)
|
|
seq = [
|
|
make_event(status="warning", exit_code=1),
|
|
make_event(status="warning", exit_code=3),
|
|
]
|
|
r = await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-1", "events": seq}, headers=auth_headers
|
|
)
|
|
assert r.json() == {"accepted": 2, "deduplicated": 0}
|
|
n = (await session.execute(select(func.count()).select_from(Event))).scalar_one()
|
|
assert n == 2
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_late_event_does_not_move_state(app_client, ui_auth_headers, session):
|
|
from datetime import UTC, datetime, timedelta
|
|
|
|
auth_headers = await register_agent(app_client, ui_auth_headers)
|
|
newer = datetime.now(UTC).replace(microsecond=0)
|
|
older = (newer - timedelta(minutes=5)).isoformat()
|
|
ev_now = make_event(
|
|
status="critical", exit_code=2, observed_at=newer.isoformat(), output="current critical"
|
|
)
|
|
ev_old = make_event(status="ok", exit_code=0, observed_at=older)
|
|
await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-1", "events": [ev_now]}, headers=auth_headers
|
|
)
|
|
await app_client.post(
|
|
"/api/v1/events", json={"agent_id": "agent-1", "events": [ev_old]}, headers=auth_headers
|
|
)
|
|
chk = (await session.execute(select(Check))).scalar_one()
|
|
assert chk.status == "critical"
|
|
assert chk.summary == ev_now["output"]
|
|
n = (await session.execute(select(func.count()).select_from(Event))).scalar_one()
|
|
assert n == 1
|