"""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"]