269 lines
9.4 KiB
Python
269 lines
9.4 KiB
Python
"""组织管理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()
|