feat: indie status page MVP -- FastAPI + SQLite
- 8 DB models (services, incidents, monitors, subscribers, etc.) - Full CRUD API for services, incidents, monitors - Public status page with live data - Incident detail page with timeline - API key authentication - Uptime monitoring scheduler - 13 tests passing - TECHNICAL_DESIGN.md with full spec
This commit is contained in:
commit
902133edd3
4655 changed files with 1342691 additions and 0 deletions
154
tests/test_api_incidents.py
Normal file
154
tests/test_api_incidents.py
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
"""Test Incidents API endpoints."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_incident(client, api_key):
|
||||
"""Should create a new incident after creating a service."""
|
||||
# Create a service first
|
||||
svc = await client.post(
|
||||
"/api/v1/services/",
|
||||
json={"name": "Auth Service", "slug": "auth-service"},
|
||||
headers={"X-API-Key": api_key},
|
||||
)
|
||||
assert svc.status_code == 201
|
||||
service_id = svc.json()["id"]
|
||||
|
||||
# Create an incident
|
||||
response = await client.post(
|
||||
"/api/v1/incidents/",
|
||||
json={
|
||||
"service_id": service_id,
|
||||
"title": "Login failures",
|
||||
"status": "investigating",
|
||||
"severity": "major",
|
||||
},
|
||||
headers={"X-API-Key": api_key},
|
||||
)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["title"] == "Login failures"
|
||||
assert data["status"] == "investigating"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_incidents(client, api_key):
|
||||
"""Should list incidents."""
|
||||
response = await client.get("/api/v1/incidents/")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_incident_with_updates(client, api_key):
|
||||
"""Should get an incident and add an update."""
|
||||
# Create a service
|
||||
svc = await client.post(
|
||||
"/api/v1/services/",
|
||||
json={"name": "Storage", "slug": "storage"},
|
||||
headers={"X-API-Key": api_key},
|
||||
)
|
||||
service_id = svc.json()["id"]
|
||||
|
||||
# Create an incident
|
||||
inc = await client.post(
|
||||
"/api/v1/incidents/",
|
||||
json={
|
||||
"service_id": service_id,
|
||||
"title": "Data loss",
|
||||
"status": "investigating",
|
||||
"severity": "outage",
|
||||
},
|
||||
headers={"X-API-Key": api_key},
|
||||
)
|
||||
incident_id = inc.json()["id"]
|
||||
|
||||
# Add an update
|
||||
upd = await client.post(
|
||||
f"/api/v1/incidents/{incident_id}/updates",
|
||||
json={
|
||||
"status": "identified",
|
||||
"body": "We found the root cause.",
|
||||
},
|
||||
headers={"X-API-Key": api_key},
|
||||
)
|
||||
assert upd.status_code == 201
|
||||
assert upd.json()["status"] == "identified"
|
||||
|
||||
# Get the incident - should include updates
|
||||
get_resp = await client.get(f"/api/v1/incidents/{incident_id}")
|
||||
assert get_resp.status_code == 200
|
||||
data = get_resp.json()
|
||||
assert data["status"] == "identified"
|
||||
assert len(data["updates"]) == 1
|
||||
assert data["updates"][0]["body"] == "We found the root cause."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_incident(client, api_key):
|
||||
"""Should update incident status via PATCH."""
|
||||
# Create service + incident
|
||||
svc = await client.post(
|
||||
"/api/v1/services/",
|
||||
json={"name": "Cache", "slug": "cache"},
|
||||
headers={"X-API-Key": api_key},
|
||||
)
|
||||
service_id = svc.json()["id"]
|
||||
|
||||
inc = await client.post(
|
||||
"/api/v1/incidents/",
|
||||
json={
|
||||
"service_id": service_id,
|
||||
"title": "Slow responses",
|
||||
"status": "investigating",
|
||||
"severity": "minor",
|
||||
},
|
||||
headers={"X-API-Key": api_key},
|
||||
)
|
||||
incident_id = inc.json()["id"]
|
||||
|
||||
# Patch the incident
|
||||
patch_resp = await client.patch(
|
||||
f"/api/v1/incidents/{incident_id}",
|
||||
json={"status": "resolved"},
|
||||
headers={"X-API-Key": api_key},
|
||||
)
|
||||
assert patch_resp.status_code == 200
|
||||
data = patch_resp.json()
|
||||
assert data["status"] == "resolved"
|
||||
assert data["resolved_at"] is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_incident(client, api_key):
|
||||
"""Should delete an incident."""
|
||||
svc = await client.post(
|
||||
"/api/v1/services/",
|
||||
json={"name": "Email", "slug": "email-svc"},
|
||||
headers={"X-API-Key": api_key},
|
||||
)
|
||||
service_id = svc.json()["id"]
|
||||
|
||||
inc = await client.post(
|
||||
"/api/v1/incidents/",
|
||||
json={
|
||||
"service_id": service_id,
|
||||
"title": "Emails delayed",
|
||||
"status": "monitoring",
|
||||
"severity": "minor",
|
||||
},
|
||||
headers={"X-API-Key": api_key},
|
||||
)
|
||||
incident_id = inc.json()["id"]
|
||||
|
||||
del_resp = await client.delete(
|
||||
f"/api/v1/incidents/{incident_id}",
|
||||
headers={"X-API-Key": api_key},
|
||||
)
|
||||
assert del_resp.status_code == 204
|
||||
|
||||
# Verify it's gone
|
||||
get_resp = await client.get(f"/api/v1/incidents/{incident_id}")
|
||||
assert get_resp.status_code == 404
|
||||
Loading…
Add table
Add a link
Reference in a new issue