"""Integration tests for account management endpoints.""" import pytest from httpx import AsyncClient class TestAccountEndpoints: """Test suite for account management endpoints.""" @pytest.mark.asyncio async def test_get_my_account(self, client: AsyncClient, auth_headers: dict): """Test getting current user's account.""" response = await client.get("/api/v1/accounts/me", headers=auth_headers) assert response.status_code == 200 data = response.json() assert "id" in data assert "name" in data assert "display_code" in data assert "owner_id" in data assert len(data["display_code"]) == 8 @pytest.mark.asyncio async def test_get_my_subscription(self, client: AsyncClient, auth_headers: dict): """Test getting current user's subscription details. The test_user fixture seeds a Pro/active Subscription so Pro-guarded routers work; reflect that in the expected plan. """ response = await client.get("/api/v1/accounts/me/subscription", headers=auth_headers) assert response.status_code == 200 data = response.json() assert "subscription" in data assert "limits" in data assert "usage" in data assert data["subscription"]["plan"] == "pro" assert data["subscription"]["status"] == "active" assert data["limits"]["max_trees"] == 25 assert data["limits"]["max_sessions_per_month"] == 200 @pytest.mark.asyncio async def test_get_my_members(self, client: AsyncClient, auth_headers: dict): """Test getting members of current user's account.""" response = await client.get("/api/v1/accounts/me/members", headers=auth_headers) assert response.status_code == 200 data = response.json() assert isinstance(data, list) assert len(data) >= 1 # Current user should be in members list assert any(m["account_role"] == "owner" for m in data) @pytest.mark.asyncio async def test_update_my_account(self, client: AsyncClient, auth_headers: dict): """Test updating account name.""" response = await client.patch( "/api/v1/accounts/me", json={"name": "Updated Account Name"}, headers=auth_headers ) assert response.status_code == 200 assert response.json()["name"] == "Updated Account Name" @pytest.mark.asyncio async def test_update_account_requires_owner(self, client: AsyncClient): """Test that non-owners cannot update account settings.""" # Register two users owner_data = { "email": "owner@example.com", "password": "OwnerPass123!", "name": "Owner" } await client.post("/api/v1/auth/register", json=owner_data) # Login as owner and create an invite login_resp = await client.post("/api/v1/auth/login/json", json={ "email": "owner@example.com", "password": "OwnerPass123!" }) owner_headers = {"Authorization": f"Bearer {login_resp.json()['access_token']}"} # Create invite invite_resp = await client.post( "/api/v1/accounts/me/invites", json={"email": "member@example.com", "role": "engineer"}, headers=owner_headers ) assert invite_resp.status_code == 201 invite_code = invite_resp.json()["code"] # Register member with account invite code member_data = { "email": "member@example.com", "password": "MemberPass123!", "name": "Member", "account_invite_code": invite_code } reg_resp = await client.post("/api/v1/auth/register", json=member_data) assert reg_resp.status_code == 201 assert reg_resp.json()["account_role"] == "engineer" # Login as member member_login = await client.post("/api/v1/auth/login/json", json={ "email": "member@example.com", "password": "MemberPass123!" }) member_headers = {"Authorization": f"Bearer {member_login.json()['access_token']}"} # Member should not be able to update account response = await client.patch( "/api/v1/accounts/me", json={"name": "Hacked Name"}, headers=member_headers ) assert response.status_code == 403 @pytest.mark.asyncio async def test_create_and_list_invites(self, client: AsyncClient, auth_headers: dict): """Test creating and listing account invites.""" # Create invite response = await client.post( "/api/v1/accounts/me/invites", json={"email": "invitee@example.com", "role": "engineer"}, headers=auth_headers ) assert response.status_code == 201 data = response.json() assert data["email"] == "invitee@example.com" assert data["role"] == "engineer" assert "code" in data # List invites list_response = await client.get("/api/v1/accounts/me/invites", headers=auth_headers) assert list_response.status_code == 200 invites = list_response.json() assert len(invites) >= 1 assert any(i["email"] == "invitee@example.com" for i in invites) @pytest.mark.asyncio async def test_register_with_account_invite(self, client: AsyncClient, auth_headers: dict): """Test that account invite code joins user to existing account.""" # Get current account account_resp = await client.get("/api/v1/accounts/me", headers=auth_headers) account_id = account_resp.json()["id"] # Create invite invite_resp = await client.post( "/api/v1/accounts/me/invites", json={"email": "joiner@example.com", "role": "viewer"}, headers=auth_headers ) invite_code = invite_resp.json()["code"] # Register with account invite code reg_resp = await client.post("/api/v1/auth/register", json={ "email": "joiner@example.com", "password": "JoinerPass123!", "name": "Joiner", "account_invite_code": invite_code }) assert reg_resp.status_code == 201 data = reg_resp.json() assert data["account_id"] == account_id assert data["account_role"] == "viewer" @pytest.mark.asyncio async def test_register_with_invalid_invite_code(self, client: AsyncClient): """Test that invalid account invite code is rejected.""" response = await client.post("/api/v1/auth/register", json={ "email": "bad@example.com", "password": "BadPassword123!", "name": "Bad User", "account_invite_code": "INVALID_CODE" }) assert response.status_code == 400 assert "invalid" in response.json()["detail"].lower()