feat(admin): Phase 5 火山人像审核队列 — 跨团队 person 资产审核(列/筛/批量送审/轮询)
后端:adminpanel asset-reviews(列跨团队 person + 状态筛分页)+ submit(批量送审/重试)+ poll(轮询 processing), 复用 assets/review.py,IsPlatformAdmin + 审计;AdminReviewAssetSerializer;修 N+1(prefetch 缓存取首图,列表 5s→1s)。 前端:adminApi 审核系列;Admin 资产审核页(状态筛 + 多选表格 + 绿/红/灰状态盾 + 重试 + 吸底 bulk-bar 批量送审 + 刷新状态)。 测试:adminpanel 28 单测过(mock 火山:队列只列 person/状态筛/批量送审计数/poll 只轮 processing); 无头 e2e _admin-p5.mjs 5 断言过 + 0 console error(只读 poll,不点真送审);tsc+build 绿。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
01d4f235ed
commit
5aab8a568f
@@ -1,3 +1,5 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from rest_framework.test import APIClient
|
||||
from django.test import TestCase
|
||||
|
||||
@@ -232,3 +234,67 @@ class AdminQualityWordTests(TestCase):
|
||||
words = quality_words("video")
|
||||
self.assertIn("启用词", words)
|
||||
self.assertNotIn("停用词", words)
|
||||
|
||||
|
||||
class AdminAssetReviewTests(TestCase):
|
||||
"""Phase 5:跨团队人像审核队列(列/筛/批量送审/轮询)+ 权限。火山调用全 mock。"""
|
||||
|
||||
def setUp(self):
|
||||
from apps.assets.models import Asset
|
||||
|
||||
self.admin = User.objects.create_user(username="padmin5", password="x", is_platform_admin=True)
|
||||
self.normal = User.objects.create_user(username="normal5", password="x")
|
||||
self.teamA = Team.objects.create(name="TeamA", owner=self.normal)
|
||||
TeamMember.objects.create(team=self.teamA, user=self.normal, role=TeamMember.Role.OWNER)
|
||||
self.user2 = User.objects.create_user(username="u2-5", password="x")
|
||||
self.teamB = Team.objects.create(name="TeamB", owner=self.user2)
|
||||
|
||||
self.a_proc = Asset.objects.create(team=self.teamA, name="proc", asset_type="image", category=Asset.Category.PERSON, review_status="processing")
|
||||
self.a_active = Asset.objects.create(team=self.teamA, name="active", asset_type="image", category=Asset.Category.PERSON, review_status="active")
|
||||
self.a_failed = Asset.objects.create(team=self.teamB, name="failed", asset_type="image", category=Asset.Category.PERSON, review_status="failed")
|
||||
self.a_none = Asset.objects.create(team=self.teamA, name="none", asset_type="image", category=Asset.Category.PERSON, review_status="")
|
||||
# 非 person 资产不该进队列
|
||||
Asset.objects.create(team=self.teamA, name="scene", asset_type="image", category=Asset.Category.SCENE, review_status="")
|
||||
|
||||
self.ac = APIClient()
|
||||
self.ac.force_authenticate(self.admin)
|
||||
self.nc = APIClient()
|
||||
self.nc.force_authenticate(self.normal)
|
||||
|
||||
def test_list_permission_and_only_person_cross_team(self):
|
||||
self.assertEqual(self.nc.get("/api/admin/asset-reviews/").status_code, 403)
|
||||
r = self.ac.get("/api/admin/asset-reviews/")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.data["count"], 4) # 4 person 资产(跨 2 团队),scene 排除
|
||||
self.assertEqual({row["team_name"] for row in r.data["results"]}, {"TeamA", "TeamB"})
|
||||
|
||||
def test_filter_by_status(self):
|
||||
self.assertEqual(self.ac.get("/api/admin/asset-reviews/?review_status=processing").data["count"], 1)
|
||||
self.assertEqual(self.ac.get("/api/admin/asset-reviews/?review_status=active").data["count"], 1)
|
||||
self.assertEqual(self.ac.get("/api/admin/asset-reviews/?review_status=failed").data["count"], 1)
|
||||
self.assertEqual(self.ac.get("/api/admin/asset-reviews/?review_status=none").data["count"], 1)
|
||||
|
||||
def test_submit_calls_review_and_audit(self):
|
||||
with patch("apps.adminpanel.views.submit_asset_for_review") as mock_submit:
|
||||
r = self.ac.post(
|
||||
"/api/admin/asset-reviews/submit/",
|
||||
{"asset_ids": [str(self.a_none.id), str(self.a_failed.id)]},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.data["submitted"], 2)
|
||||
self.assertEqual(mock_submit.call_count, 2)
|
||||
self.assertTrue(AdminAuditLog.objects.filter(action="asset_review.submit").exists())
|
||||
|
||||
def test_submit_requires_admin(self):
|
||||
with patch("apps.adminpanel.views.submit_asset_for_review") as mock_submit:
|
||||
r = self.nc.post("/api/admin/asset-reviews/submit/", {"asset_ids": [str(self.a_none.id)]}, format="json")
|
||||
self.assertEqual(r.status_code, 403)
|
||||
mock_submit.assert_not_called()
|
||||
|
||||
def test_poll_only_processing(self):
|
||||
with patch("apps.adminpanel.views.poll_asset_review", return_value="active") as mock_poll:
|
||||
r = self.ac.post("/api/admin/asset-reviews/poll/", {}, format="json")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.data["polled"], 1) # 仅 a_proc 处于 processing
|
||||
mock_poll.assert_called_once()
|
||||
|
||||
Reference in New Issue
Block a user