video-compliance-ai/backend/tests/test_dashboard_api.py
Your Name 864af19011 test: 补全后端 API 测试覆盖(Organizations/Projects/Dashboard/Briefs/Export)
新增 157 个测试,总计 368 个测试全部通过:
- Organizations API: 50 tests (品牌方↔代理商↔达人关系管理 + 搜索 + 权限)
- Projects API: 44 tests (CRUD + 分页 + 状态筛选 + 代理商分配 + 权限)
- Dashboard API: 17 tests (三端工作台统计 + 角色隔离 + 认证)
- Briefs API: 24 tests (CRUD + 权限 + 数据完整性)
- Export API: 22 tests (CSV 导出 + UTF-8 BOM + 角色权限 + 格式验证)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 18:18:49 +08:00

289 lines
11 KiB
Python

"""
Dashboard API comprehensive tests.
Tests cover the three dashboard endpoints:
- GET /api/v1/dashboard/creator (creator role only)
- GET /api/v1/dashboard/agency (agency role only)
- GET /api/v1/dashboard/brand (brand role only)
Each endpoint returns zero-valued stats for a freshly registered user
and enforces role-based access (403 for wrong roles, 401 for unauthenticated).
Uses the SQLite-backed test client from conftest.py.
"""
import uuid
import pytest
from httpx import AsyncClient
from app.main import app
from app.middleware.rate_limit import RateLimitMiddleware
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
API = "/api/v1"
REGISTER_URL = f"{API}/auth/register"
DASHBOARD_CREATOR_URL = f"{API}/dashboard/creator"
DASHBOARD_AGENCY_URL = f"{API}/dashboard/agency"
DASHBOARD_BRAND_URL = f"{API}/dashboard/brand"
# ---------------------------------------------------------------------------
# Auto-clear rate limiter state before each test
# ---------------------------------------------------------------------------
@pytest.fixture(autouse=True)
def _clear_rate_limiter():
"""Reset the in-memory rate limiter between tests."""
mw = app.middleware_stack
while mw is not None:
if isinstance(mw, RateLimitMiddleware):
mw.requests.clear()
break
mw = getattr(mw, "app", None)
yield
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _email(prefix: str = "user") -> str:
return f"{prefix}-{uuid.uuid4().hex[:8]}@test.com"
async def _register(client: AsyncClient, role: str, name: str | None = None):
"""Register a user via the API and return (access_token, user_data)."""
email = _email(role)
resp = await client.post(REGISTER_URL, json={
"email": email,
"password": "test123456",
"name": name or f"Test {role.title()}",
"role": role,
})
assert resp.status_code == 201, f"Registration failed for {role}: {resp.text}"
data = resp.json()
return data["access_token"], data["user"]
def _auth(token: str) -> dict:
"""Return Authorization header dict."""
return {"Authorization": f"Bearer {token}"}
# ===========================================================================
# Test class: Creator Dashboard
# ===========================================================================
class TestCreatorDashboard:
"""GET /api/v1/dashboard/creator"""
@pytest.mark.asyncio
async def test_creator_dashboard_happy_path(self, client: AsyncClient):
"""Creator gets dashboard stats -- all zeros for a freshly registered user."""
token, user = await _register(client, "creator")
resp = await client.get(DASHBOARD_CREATOR_URL, headers=_auth(token))
assert resp.status_code == 200
data = resp.json()
assert data["total_tasks"] == 0
assert data["pending_script"] == 0
assert data["pending_video"] == 0
assert data["in_review"] == 0
assert data["completed"] == 0
assert data["rejected"] == 0
@pytest.mark.asyncio
async def test_creator_dashboard_response_keys(self, client: AsyncClient):
"""Creator dashboard response contains all expected keys."""
token, _ = await _register(client, "creator")
resp = await client.get(DASHBOARD_CREATOR_URL, headers=_auth(token))
assert resp.status_code == 200
data = resp.json()
expected_keys = {
"total_tasks", "pending_script", "pending_video",
"in_review", "completed", "rejected",
}
assert expected_keys.issubset(set(data.keys()))
@pytest.mark.asyncio
async def test_creator_dashboard_forbidden_for_brand(self, client: AsyncClient):
"""Brand role cannot access creator dashboard -- expects 403."""
token, _ = await _register(client, "brand")
resp = await client.get(DASHBOARD_CREATOR_URL, headers=_auth(token))
assert resp.status_code == 403
@pytest.mark.asyncio
async def test_creator_dashboard_forbidden_for_agency(self, client: AsyncClient):
"""Agency role cannot access creator dashboard -- expects 403."""
token, _ = await _register(client, "agency")
resp = await client.get(DASHBOARD_CREATOR_URL, headers=_auth(token))
assert resp.status_code == 403
# ===========================================================================
# Test class: Agency Dashboard
# ===========================================================================
class TestAgencyDashboard:
"""GET /api/v1/dashboard/agency"""
@pytest.mark.asyncio
async def test_agency_dashboard_happy_path(self, client: AsyncClient):
"""Agency gets dashboard stats -- all zeros for a freshly registered user."""
token, user = await _register(client, "agency")
resp = await client.get(DASHBOARD_AGENCY_URL, headers=_auth(token))
assert resp.status_code == 200
data = resp.json()
assert data["pending_review"]["script"] == 0
assert data["pending_review"]["video"] == 0
assert data["pending_appeal"] == 0
assert data["today_passed"]["script"] == 0
assert data["today_passed"]["video"] == 0
assert data["in_progress"]["script"] == 0
assert data["in_progress"]["video"] == 0
assert data["total_creators"] == 0
assert data["total_tasks"] == 0
@pytest.mark.asyncio
async def test_agency_dashboard_response_keys(self, client: AsyncClient):
"""Agency dashboard response contains all expected keys."""
token, _ = await _register(client, "agency")
resp = await client.get(DASHBOARD_AGENCY_URL, headers=_auth(token))
assert resp.status_code == 200
data = resp.json()
expected_keys = {
"pending_review", "pending_appeal", "today_passed",
"in_progress", "total_creators", "total_tasks",
}
assert expected_keys.issubset(set(data.keys()))
@pytest.mark.asyncio
async def test_agency_dashboard_nested_review_counts(self, client: AsyncClient):
"""Agency dashboard nested ReviewCount objects have correct structure."""
token, _ = await _register(client, "agency")
resp = await client.get(DASHBOARD_AGENCY_URL, headers=_auth(token))
assert resp.status_code == 200
data = resp.json()
for key in ("pending_review", "today_passed", "in_progress"):
assert "script" in data[key], f"Missing 'script' in {key}"
assert "video" in data[key], f"Missing 'video' in {key}"
assert isinstance(data[key]["script"], int)
assert isinstance(data[key]["video"], int)
@pytest.mark.asyncio
async def test_agency_dashboard_forbidden_for_creator(self, client: AsyncClient):
"""Creator role cannot access agency dashboard -- expects 403."""
token, _ = await _register(client, "creator")
resp = await client.get(DASHBOARD_AGENCY_URL, headers=_auth(token))
assert resp.status_code == 403
@pytest.mark.asyncio
async def test_agency_dashboard_forbidden_for_brand(self, client: AsyncClient):
"""Brand role cannot access agency dashboard -- expects 403."""
token, _ = await _register(client, "brand")
resp = await client.get(DASHBOARD_AGENCY_URL, headers=_auth(token))
assert resp.status_code == 403
# ===========================================================================
# Test class: Brand Dashboard
# ===========================================================================
class TestBrandDashboard:
"""GET /api/v1/dashboard/brand"""
@pytest.mark.asyncio
async def test_brand_dashboard_happy_path(self, client: AsyncClient):
"""Brand gets dashboard stats -- all zeros for a freshly registered user."""
token, user = await _register(client, "brand")
resp = await client.get(DASHBOARD_BRAND_URL, headers=_auth(token))
assert resp.status_code == 200
data = resp.json()
assert data["total_projects"] == 0
assert data["active_projects"] == 0
assert data["pending_review"]["script"] == 0
assert data["pending_review"]["video"] == 0
assert data["total_agencies"] == 0
assert data["total_tasks"] == 0
assert data["completed_tasks"] == 0
@pytest.mark.asyncio
async def test_brand_dashboard_response_keys(self, client: AsyncClient):
"""Brand dashboard response contains all expected keys."""
token, _ = await _register(client, "brand")
resp = await client.get(DASHBOARD_BRAND_URL, headers=_auth(token))
assert resp.status_code == 200
data = resp.json()
expected_keys = {
"total_projects", "active_projects", "pending_review",
"total_agencies", "total_tasks", "completed_tasks",
}
assert expected_keys.issubset(set(data.keys()))
@pytest.mark.asyncio
async def test_brand_dashboard_forbidden_for_creator(self, client: AsyncClient):
"""Creator role cannot access brand dashboard -- expects 403."""
token, _ = await _register(client, "creator")
resp = await client.get(DASHBOARD_BRAND_URL, headers=_auth(token))
assert resp.status_code == 403
@pytest.mark.asyncio
async def test_brand_dashboard_forbidden_for_agency(self, client: AsyncClient):
"""Agency role cannot access brand dashboard -- expects 403."""
token, _ = await _register(client, "agency")
resp = await client.get(DASHBOARD_BRAND_URL, headers=_auth(token))
assert resp.status_code == 403
# ===========================================================================
# Test class: Dashboard Authentication
# ===========================================================================
class TestDashboardAuth:
"""Unauthenticated access to all dashboard endpoints."""
@pytest.mark.asyncio
async def test_creator_dashboard_unauthenticated(self, client: AsyncClient):
"""Unauthenticated request to creator dashboard returns 401."""
resp = await client.get(DASHBOARD_CREATOR_URL)
assert resp.status_code == 401
@pytest.mark.asyncio
async def test_agency_dashboard_unauthenticated(self, client: AsyncClient):
"""Unauthenticated request to agency dashboard returns 401."""
resp = await client.get(DASHBOARD_AGENCY_URL)
assert resp.status_code == 401
@pytest.mark.asyncio
async def test_brand_dashboard_unauthenticated(self, client: AsyncClient):
"""Unauthenticated request to brand dashboard returns 401."""
resp = await client.get(DASHBOARD_BRAND_URL)
assert resp.status_code == 401
@pytest.mark.asyncio
async def test_dashboard_with_invalid_token(self, client: AsyncClient):
"""Request with an invalid Bearer token returns 401."""
headers = {"Authorization": "Bearer invalid-garbage-token"}
for url in (DASHBOARD_CREATOR_URL, DASHBOARD_AGENCY_URL, DASHBOARD_BRAND_URL):
resp = await client.get(url, headers=headers)
assert resp.status_code == 401, f"Expected 401 for {url}, got {resp.status_code}"