Files
incidentops/tests/api/test_org.py

239 lines
7.0 KiB
Python

"""Integration tests for org endpoints."""
from __future__ import annotations
from uuid import UUID, uuid4
import asyncpg
import pytest
from httpx import AsyncClient
from app.core import security
from tests.api import helpers
pytestmark = pytest.mark.asyncio
API_PREFIX = "/v1/org"
async def _create_user_in_org(
conn: asyncpg.Connection,
*,
org_id: UUID,
email: str,
password: str,
role: str,
) -> UUID:
user_id = uuid4()
await conn.execute(
"INSERT INTO users (id, email, password_hash) VALUES ($1, $2, $3)",
user_id,
email,
security.hash_password(password),
)
await conn.execute(
"INSERT INTO org_members (id, user_id, org_id, role) VALUES ($1, $2, $3, $4)",
uuid4(),
user_id,
org_id,
role,
)
return user_id
async def _login(client: AsyncClient, *, email: str, password: str) -> dict:
response = await client.post(
"/v1/auth/login",
json={"email": email, "password": password},
)
response.raise_for_status()
return response.json()
async def test_get_org_returns_active_org(api_client: AsyncClient) -> None:
tokens = await helpers.register_user(
api_client,
email="org-owner@example.com",
password="OrgOwner1!",
org_name="Org Owner Inc",
)
response = await api_client.get(
API_PREFIX,
headers={"Authorization": f"Bearer {tokens['access_token']}",},
)
assert response.status_code == 200
data = response.json()
payload = security.decode_access_token(tokens["access_token"])
assert data["id"] == payload["org_id"]
assert data["name"] == "Org Owner Inc"
async def test_get_members_requires_admin(
api_client: AsyncClient,
db_admin: asyncpg.Connection,
) -> None:
owner_tokens = await helpers.register_user(
api_client,
email="owner@example.com",
password="OwnerPass1!",
org_name="Members Co",
)
payload = security.decode_access_token(owner_tokens["access_token"])
org_id = UUID(payload["org_id"])
member_password = "MemberPass1!"
await _create_user_in_org(
db_admin,
org_id=org_id,
email="member@example.com",
password=member_password,
role="member",
)
member_tokens = await _login(api_client, email="member@example.com", password=member_password)
admin_response = await api_client.get(
f"{API_PREFIX}/members",
headers={"Authorization": f"Bearer {owner_tokens['access_token']}"},
)
assert admin_response.status_code == 200
emails = {item["email"] for item in admin_response.json()}
assert emails == {"owner@example.com", "member@example.com"}
member_response = await api_client.get(
f"{API_PREFIX}/members",
headers={"Authorization": f"Bearer {member_tokens['access_token']}"},
)
assert member_response.status_code == 403
async def test_create_service_allows_member_and_persists(
api_client: AsyncClient,
db_admin: asyncpg.Connection,
) -> None:
owner_tokens = await helpers.register_user(
api_client,
email="service-owner@example.com",
password="ServiceOwner1!",
org_name="Service Org",
)
payload = security.decode_access_token(owner_tokens["access_token"])
org_id = UUID(payload["org_id"])
member_password = "CreateSvc1!"
await _create_user_in_org(
db_admin,
org_id=org_id,
email="svc-member@example.com",
password=member_password,
role="member",
)
member_tokens = await _login(api_client, email="svc-member@example.com", password=member_password)
response = await api_client.post(
f"{API_PREFIX}/services",
json={"name": "API Gateway", "slug": "api-gateway"},
headers={"Authorization": f"Bearer {member_tokens['access_token']}"},
)
assert response.status_code == 201
body = response.json()
row = await db_admin.fetchrow(
"SELECT org_id, slug FROM services WHERE id = $1",
UUID(body["id"]),
)
assert row is not None and row["org_id"] == org_id and row["slug"] == "api-gateway"
async def test_create_service_rejects_duplicate_slug(
api_client: AsyncClient,
db_admin: asyncpg.Connection,
) -> None:
tokens = await helpers.register_user(
api_client,
email="dup-owner@example.com",
password="DupOwner1!",
org_name="Dup Org",
)
payload = security.decode_access_token(tokens["access_token"])
org_id = UUID(payload["org_id"])
await db_admin.execute(
"INSERT INTO services (id, org_id, name, slug) VALUES ($1, $2, $3, $4)",
uuid4(),
org_id,
"Existing",
"duplicate",
)
response = await api_client.post(
f"{API_PREFIX}/services",
json={"name": "New", "slug": "duplicate"},
headers={"Authorization": f"Bearer {tokens['access_token']}"},
)
assert response.status_code == 409
async def test_notification_targets_admin_only_and_validation(
api_client: AsyncClient,
db_admin: asyncpg.Connection,
) -> None:
owner_tokens = await helpers.register_user(
api_client,
email="notify-owner@example.com",
password="NotifyOwner1!",
org_name="Notify Org",
)
payload = security.decode_access_token(owner_tokens["access_token"])
org_id = UUID(payload["org_id"])
member_password = "NotifyMember1!"
await _create_user_in_org(
db_admin,
org_id=org_id,
email="notify-member@example.com",
password=member_password,
role="member",
)
member_tokens = await _login(api_client, email="notify-member@example.com", password=member_password)
forbidden = await api_client.post(
f"{API_PREFIX}/notification-targets",
json={"name": "Webhook", "target_type": "webhook", "webhook_url": "https://example.com"},
headers={"Authorization": f"Bearer {member_tokens['access_token']}"},
)
assert forbidden.status_code == 403
missing_url = await api_client.post(
f"{API_PREFIX}/notification-targets",
json={"name": "Bad", "target_type": "webhook"},
headers={"Authorization": f"Bearer {owner_tokens['access_token']}"},
)
assert missing_url.status_code == 400
created = await api_client.post(
f"{API_PREFIX}/notification-targets",
json={"name": "Pager", "target_type": "webhook", "webhook_url": "https://example.com/hook"},
headers={"Authorization": f"Bearer {owner_tokens['access_token']}"},
)
assert created.status_code == 201
target_id = UUID(created.json()["id"])
row = await db_admin.fetchrow(
"SELECT org_id, name FROM notification_targets WHERE id = $1",
target_id,
)
assert row is not None and row["org_id"] == org_id
listing = await api_client.get(
f"{API_PREFIX}/notification-targets",
headers={"Authorization": f"Bearer {owner_tokens['access_token']}"},
)
assert listing.status_code == 200
names = [item["name"] for item in listing.json()]
assert names == ["Pager"]