"""组织管理API测试 - 验证 /api/v1/organization/* 端点""" import uuid import pytest import pytest_asyncio from httpx import AsyncClient, ASGITransport from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession, create_async_engine from sqlalchemy.pool import StaticPool from app.database import Base from app.main import app from app.models.user import User from app.models.organization import Organization, OrgMember from app.api.deps import get_current_user, get_db from tests.fixtures.auth import _to_uuid @pytest_asyncio.fixture async def async_engine(): engine = create_async_engine( "sqlite+aiosqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine await engine.dispose() @pytest_asyncio.fixture async def async_session(async_engine): async_session_maker = async_sessionmaker( async_engine, class_=AsyncSession, expire_on_commit=False, autoflush=False, autocommit=False, ) async with async_session_maker() as session: yield session @pytest_asyncio.fixture async def test_user(async_session): user = User( id=str(uuid.uuid4()), email="test@example.com", password="hashed_password", firstName="Test User", plan="free", max_queries=5, isActive=True, emailVerified=True, ) async_session.add(user) await async_session.commit() await async_session.refresh(user) return user @pytest_asyncio.fixture async def test_organization(async_session, test_user): org = Organization( id=uuid.uuid4(), name="Test Organization", slug="test-org", plan="free", ) async_session.add(org) await async_session.flush() test_user.organization_id = org.id async_session.add(test_user) membership = OrgMember( id=uuid.uuid4(), organization_id=org.id, user_id=_to_uuid(test_user.id), role="owner", ) async_session.add(membership) await async_session.commit() await async_session.refresh(org) return org @pytest_asyncio.fixture async def async_client(async_session, test_user): async def override_get_db(): yield async_session async def override_get_current_user(): return test_user app.dependency_overrides[get_db] = override_get_db app.dependency_overrides[get_current_user] = override_get_current_user transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as client: yield client app.dependency_overrides.clear() class TestOrganizationRoutes: """组织管理API端点测试""" @pytest.mark.asyncio async def test_routes_are_registered(self): """验证组织管理路由已正确注册到app""" from app.main import app as main_app org_routes = [ route.path for route in main_app.routes if hasattr(route, 'path') and route.path.startswith('/api/v1/organization') ] print(f"\n已注册的组织路由: {org_routes}") assert '/api/v1/organization/info' in org_routes, "路由未注册" assert '/api/v1/organization/members' in org_routes, "路由未注册" assert '/api/v1/organization/members/invite' in org_routes, "路由未注册" @pytest.mark.asyncio async def test_direct_endpoint_call(self, async_session, test_user, test_organization): """直接测试端点调用""" async def override_get_db(): yield async_session async def override_get_current_user(): return test_user from app.main import app as test_app test_app.dependency_overrides[get_db] = override_get_db test_app.dependency_overrides[get_current_user] = override_get_current_user transport = ASGITransport(app=test_app) async with AsyncClient(transport=transport, base_url="http://test") as client: # Check which routes are available available_routes = [r.path for r in test_app.routes if hasattr(r, 'path') and 'organization' in r.path] print(f"\n测试app中的组织路由: {available_routes}") response = await client.get("/api/v1/organization/info") print(f"Response status: {response.status_code}") print(f"Response: {response.text[:200]}") assert response.status_code == 200 test_app.dependency_overrides.clear() @pytest.mark.asyncio async def test_organization_info_endpoint_exists(self, async_client, test_organization): """验证 /api/v1/organization/info 端点存在并返回200""" response = await async_client.get("/api/v1/organization/info") assert response.status_code == 200, f"期望返回200,实际返回 {response.status_code}" data = response.json() assert "id" in data assert "name" in data assert "slug" in data @pytest.mark.asyncio async def test_organization_members_endpoint_exists(self, async_client, test_organization): """验证 /api/v1/organization/members 端点存在并返回200""" response = await async_client.get("/api/v1/organization/members") assert response.status_code == 200, f"期望返回200,实际返回 {response.status_code}" data = response.json() assert isinstance(data, list) @pytest.mark.asyncio async def test_organization_members_invite_endpoint_exists(self, async_client, test_organization, async_session): """验证 /api/v1/organization/members/invite 端点存在""" invite_user = User( id=str(uuid.uuid4()), email="newuser@example.com", password="hashed_password", firstName="New User", plan="free", max_queries=5, isActive=True, emailVerified=True, ) async_session.add(invite_user) await async_session.commit() response = await async_client.post( "/api/v1/organization/members/invite", json={"email": "newuser@example.com", "role": "viewer"} ) assert response.status_code == 201, f"期望返回201,实际返回 {response.status_code}" @pytest.mark.asyncio async def test_organization_member_role_endpoint_exists(self, async_client, test_organization, async_session, test_user): """验证 /api/v1/organization/members/{id}/role 端点存在""" new_user = User( id=str(uuid.uuid4()), email="member@example.com", password="hashed_password", firstName="Member User", plan="free", max_queries=5, isActive=True, emailVerified=True, ) async_session.add(new_user) await async_session.flush() membership = OrgMember( id=uuid.uuid4(), organization_id=test_organization.id, user_id=new_user.id, role="viewer", ) async_session.add(membership) await async_session.commit() response = await async_client.put( f"/api/v1/organization/members/{new_user.id}/role", json={"role": "admin"} ) assert response.status_code == 200, f"期望返回200,实际返回 {response.status_code}" @pytest.mark.asyncio async def test_organization_member_delete_endpoint_exists(self, async_client, test_organization, async_session): """验证 /api/v1/organization/members/{id} 端点存在""" new_user = User( id=str(uuid.uuid4()), email="todelete@example.com", password="hashed_password", firstName="Delete User", plan="free", max_queries=5, isActive=True, emailVerified=True, ) async_session.add(new_user) await async_session.flush() membership = OrgMember( id=uuid.uuid4(), organization_id=test_organization.id, user_id=new_user.id, role="viewer", ) async_session.add(membership) await async_session.commit() response = await async_client.delete(f"/api/v1/organization/members/{new_user.id}") assert response.status_code == 204, f"期望返回204,实际返回 {response.status_code}" @pytest.mark.asyncio async def test_user_without_organization_returns_404(self, async_client, test_user, async_session): """验证未加入组织的用户访问 /api/v1/organization/info 返回404""" response = await async_client.get("/api/v1/organization/info") assert response.status_code == 404, f"期望返回404,实际返回 {response.status_code}" @pytest.mark.asyncio async def test_unauthorized_access(self, async_session): """验证未授权访问返回401""" async def override_get_db(): yield async_session app.dependency_overrides[get_db] = override_get_db app.dependency_overrides.pop(get_current_user, None) transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as client: response = await client.get("/api/v1/organization/info") assert response.status_code == 401, f"期望返回401,实际返回 {response.status_code}" app.dependency_overrides.clear()