video-shuoshan/backend/tests/test_1080p_api.py
seaislee1209 ecdb9cb471
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m45s
fix: v0.19.3 admin settings GET 补返回 1080P 两个单价字段
v0.19.0 做 1080P 时 QuotaConfig 加了 base_token_price_1080p 和
base_token_price_1080p_video 字段, serializer (PUT) 和计费逻辑
(_get_token_price) 都处理了, 但 _settings_dict (GET) 漏了两行,
导致管理后台设置页两个 1080P 单价输入框显示空白。

实际影响
- DB 值对 (51 / 31), 计费走 _get_token_price 直接读 DB, 计费一直正确
- 前端 SettingsPage fetchSettings 用 setSettings(data) 覆盖,
  GET 返回缺字段 -> state 变 undefined -> 输入框显示空
- 管理员点保存: undefined 被 JSON.stringify 省略 -> PUT body 不含
  这两字段 -> serializer validated_data 里没有 -> DB 未改
- 所以目前"巧合安全", 但风险: 管理员在空输入框填数字后清空,
  Number("") = 0 会覆盖 DB, 把单价刷成 0

修复
- backend/apps/generation/views.py _settings_dict() 加两行返回
  base_token_price_1080p / base_token_price_1080p_video
- 前端 GET 后 state 直接拿到 51 / 31, 输入框自动显示, 不依赖"巧合"

回归测试 (backend/tests/test_1080p_api.py)
- 新增 TestAdminSettingsResponse.test_get_returns_all_token_price_fields
  断言 GET /admin/settings 返回 6 个 token_price 字段全齐
- 失败消息明示: "缺字段会导致前端输入框显示空" 以防以后再漏

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 19:09:55 +08:00

165 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
1080P API 集成测试 — 验证 video_generate_view 的入口校验。
"""
import os
import sys
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
django.setup()
import unittest
from django.test import TestCase
from django.contrib.auth import get_user_model
from rest_framework.test import APIClient
from apps.accounts.models import Team
from apps.generation.models import QuotaConfig
User = get_user_model()
class TestVideoGenerateResolution(TestCase):
"""video_generate_view 的分辨率+模型组合校验。"""
def setUp(self):
# 初始化 QuotaConfig
QuotaConfig.objects.get_or_create(pk=1)
# 建测试 team + user
self.team = Team.objects.create(
name='test-1080p',
is_active=True,
monthly_spending_limit=1000,
markup_percentage=0,
balance=1000,
frozen_amount=0,
)
self.user = User.objects.create_user(
username='test_1080p_user',
email='test1080p@example.com',
password='testpass123',
team=self.team,
spending_limit=-1,
daily_generation_limit=-1,
monthly_generation_limit=-1,
)
self.client = APIClient()
self.client.force_authenticate(user=self.user)
def test_reject_fast_plus_1080p(self):
"""原则 1Fast + 1080P 组合必须 400 拒绝,不能静默降级。"""
resp = self.client.post('/api/v1/video/generate', {
'prompt': '测试',
'mode': 'universal',
'model': 'seedance_2.0_fast',
'aspect_ratio': '16:9',
'duration': 5,
'resolution': '1080p',
'references': [],
}, format='json')
self.assertEqual(resp.status_code, 400)
body = resp.json()
self.assertEqual(body.get('error'), 'invalid_resolution')
# 提示信息要明确告知用户原因
self.assertIn('1080P', body.get('message', ''))
self.assertIn('Fast', body.get('message', ''))
def test_reject_adaptive_ratio(self):
"""原则 1adaptive 不在 6 选 1 白名单,拒绝。"""
resp = self.client.post('/api/v1/video/generate', {
'prompt': '测试',
'mode': 'universal',
'model': 'seedance_2.0',
'aspect_ratio': 'adaptive',
'duration': 5,
'resolution': '720p',
'references': [],
}, format='json')
self.assertEqual(resp.status_code, 400)
# serializer 错误aspect_ratio 不在 choices
self.assertIn('aspect_ratio', str(resp.content))
def test_reject_invalid_resolution(self):
"""resolution 不在 480p/720p/1080p 白名单,拒绝。"""
resp = self.client.post('/api/v1/video/generate', {
'prompt': '测试',
'mode': 'universal',
'model': 'seedance_2.0',
'aspect_ratio': '16:9',
'duration': 5,
'resolution': '4K',
'references': [],
}, format='json')
self.assertEqual(resp.status_code, 400)
def test_resolution_default_720p_when_missing(self):
"""旧客户端不传 resolution 字段时serializer default='720p' 生效。"""
# 不传 resolution兼容旧客户端
resp = self.client.post('/api/v1/video/generate', {
'prompt': '测试',
'mode': 'universal',
'model': 'seedance_2.0',
'aspect_ratio': '16:9',
'duration': 5,
'references': [],
}, format='json')
# serializer 应该接受default='720p');可能因火山 API 未开通等其他原因失败,
# 但不该是 resolution 相关的 400 错误
if resp.status_code == 400:
body = resp.json()
self.assertNotEqual(body.get('error'), 'invalid_resolution')
def test_accept_valid_1080p_airdrama(self):
"""原则AirDrama + 1080P 组合合法,不被 400 拒绝。"""
resp = self.client.post('/api/v1/video/generate', {
'prompt': '测试',
'mode': 'universal',
'model': 'seedance_2.0',
'aspect_ratio': '16:9',
'duration': 5,
'resolution': '1080p',
'references': [],
}, format='json')
# 不应该因为分辨率被 400可能因余额/API 未开通等其他原因失败)
if resp.status_code == 400:
body = resp.json()
self.assertNotEqual(body.get('error'), 'invalid_resolution')
class TestAdminSettingsResponse(TestCase):
"""GET /api/v1/admin/settings 必须返回所有 token_price 字段,
以防 v0.19.0 那种"字段在 serializer 里加了、但 _settings_dict 漏了"的回归。"""
def setUp(self):
QuotaConfig.objects.get_or_create(pk=1)
self.admin = User.objects.create_user(
username='test_admin_settings',
email='test_admin_settings@example.com',
password='testpass123',
is_staff=True,
is_superuser=True,
)
self.client = APIClient()
self.client.force_authenticate(user=self.admin)
def test_get_returns_all_token_price_fields(self):
"""GET 返回 4 档单价(全部分辨率 + 是否含视频),缺一不可 — 缺字段会导致前端输入框显示空。"""
resp = self.client.get('/api/v1/admin/settings')
self.assertEqual(resp.status_code, 200)
body = resp.json()
for field in (
'base_token_price',
'base_token_price_video',
'base_token_price_fast',
'base_token_price_fast_video',
'base_token_price_1080p',
'base_token_price_1080p_video',
):
self.assertIn(field, body, f'GET /admin/settings response missing {field!r} — 前端这个输入框会显示空')
self.assertIsInstance(body[field], (int, float), f'{field} 应该是数字类型')
if __name__ == '__main__':
unittest.main(verbosity=2)