import pytest from sqlalchemy import func, select from monlet_server.models import Check, Event from ._helpers import make_event, make_heartbeat @pytest.mark.asyncio async def test_event_accepted_and_state(app_client, auth_headers, session): await app_client.post("/api/v1/heartbeat", json=make_heartbeat(), headers=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_duplicate_event_deduplicated(app_client, auth_headers): await app_client.post("/api/v1/heartbeat", json=make_heartbeat(), headers=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, auth_headers, observed_at): await app_client.post("/api/v1/heartbeat", json=make_heartbeat(), headers=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, auth_headers, session): await app_client.post("/api/v1/heartbeat", json=make_heartbeat(), headers=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, auth_headers, session): await app_client.post("/api/v1/heartbeat", json=make_heartbeat(), headers=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, auth_headers, session): await app_client.post("/api/v1/heartbeat", json=make_heartbeat(), headers=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, auth_headers, session): from datetime import UTC, datetime, timedelta await app_client.post("/api/v1/heartbeat", json=make_heartbeat(), headers=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