- 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
119 lines
No EOL
3.4 KiB
Python
119 lines
No EOL
3.4 KiB
Python
"""Test Services API endpoints."""
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_service(client, api_key):
|
|
"""Should create a new service."""
|
|
response = await client.post(
|
|
"/api/v1/services/",
|
|
json={
|
|
"name": "API Server",
|
|
"slug": "api-server",
|
|
},
|
|
headers={"X-API-Key": api_key},
|
|
)
|
|
assert response.status_code == 201
|
|
data = response.json()
|
|
assert data["name"] == "API Server"
|
|
assert data["slug"] == "api-server"
|
|
assert data["is_visible"] is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_services(client, api_key):
|
|
"""Should list all services."""
|
|
# Create a service first
|
|
await client.post(
|
|
"/api/v1/services/",
|
|
json={"name": "Database", "slug": "database"},
|
|
headers={"X-API-Key": api_key},
|
|
)
|
|
|
|
response = await client.get("/api/v1/services/")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert isinstance(data, list)
|
|
assert len(data) >= 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_api_key_required(client):
|
|
"""Should reject requests without API key for protected routes."""
|
|
response = await client.post(
|
|
"/api/v1/services/",
|
|
json={"name": "Unauthorized", "slug": "unauth"},
|
|
)
|
|
assert response.status_code == 422 # Missing required header
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_service(client, api_key):
|
|
"""Should get a single service by ID."""
|
|
create_resp = await client.post(
|
|
"/api/v1/services/",
|
|
json={"name": "Web App", "slug": "web-app"},
|
|
headers={"X-API-Key": api_key},
|
|
)
|
|
assert create_resp.status_code == 201
|
|
service_id = create_resp.json()["id"]
|
|
|
|
response = await client.get(f"/api/v1/services/{service_id}")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["id"] == service_id
|
|
assert data["name"] == "Web App"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_service(client, api_key):
|
|
"""Should update a service with PATCH."""
|
|
create_resp = await client.post(
|
|
"/api/v1/services/",
|
|
json={"name": "Old Name", "slug": "old-slug"},
|
|
headers={"X-API-Key": api_key},
|
|
)
|
|
service_id = create_resp.json()["id"]
|
|
|
|
response = await client.patch(
|
|
f"/api/v1/services/{service_id}",
|
|
json={"name": "New Name"},
|
|
headers={"X-API-Key": api_key},
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["name"] == "New Name"
|
|
assert data["slug"] == "old-slug" # unchanged
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_service(client, api_key):
|
|
"""Should delete a service."""
|
|
create_resp = await client.post(
|
|
"/api/v1/services/",
|
|
json={"name": "Delete Me", "slug": "delete-me"},
|
|
headers={"X-API-Key": api_key},
|
|
)
|
|
service_id = create_resp.json()["id"]
|
|
|
|
response = await client.delete(
|
|
f"/api/v1/services/{service_id}",
|
|
headers={"X-API-Key": api_key},
|
|
)
|
|
assert response.status_code == 204
|
|
|
|
# Verify it's gone
|
|
get_resp = await client.get(f"/api/v1/services/{service_id}")
|
|
assert get_resp.status_code == 404
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_service_not_found(client, api_key):
|
|
"""Should return 404 when updating nonexistent service."""
|
|
response = await client.patch(
|
|
"/api/v1/services/00000000-0000-0000-0000-000000000000",
|
|
json={"name": "Ghost"},
|
|
headers={"X-API-Key": api_key},
|
|
)
|
|
assert response.status_code == 404 |