Files
xiaoxia-saas/tests/unit/test_lipsync_points.py
T
xiaoxia-agent 3ba25dcd01
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m58s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m18s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
feat(points): P2 step 2.2 - wire lipsync points gate
- create_lipsync_job(): deduct ai_digital_human points (15/min), estimated from audio_duration (if provided) or script_text (chars/240), min 1 min; refund on ValueError/MediaKitError/Exception; refund on failed job
- preview_tts(): deduct ai_digital_human points by script_text length; refund on MediaKitError/Exception
- Both gated by settings.points_enabled (default false)
- Raises HTTP 402 INSUFFICIENT_POINTS on insufficient balance
- 14 new unit tests; 15468 total passed
2026-09-16 00:23:22 +08:00

97 lines
3.0 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.
"""lipsync 积分扣点单元测试 (#1895 P2 step 2.2)"""
from __future__ import annotations
import math
from unittest.mock import MagicMock
import pytest
from fastapi import HTTPException
def _make_cu(user_id="user-1", is_member=False, member_type=None):
cu = MagicMock()
cu.user.id = user_id
cu.user.is_member = is_member
cu.user.member_type = member_type
return cu
class TestLipsyncDurationEstimate:
@pytest.mark.parametrize(
"text,expected",
[
("你好", 1.0),
("你" * 240, 1.0),
("你" * 241, 2.0),
("你" * 1000, 5.0),
],
)
def test_text_estimate(self, text, expected):
est = max(1.0, math.ceil(len(text) / 240))
assert est == expected
@pytest.mark.parametrize(
"seconds,expected",
[
(30, 1.0),
(60, 1.0),
(61, 2.0),
(120, 2.0),
(180, 3.0),
],
)
def test_audio_duration_estimate(self, seconds, expected):
est = max(1.0, math.ceil(seconds / 60.0))
assert est == expected
class TestLipsyncPointsDeduction:
def _deduct(self, text="你好", audio_duration=None, enabled=True, success=True, balance=100, **cu_kw):
from packages.domain.points_rules import calculate_points_cost
svc = MagicMock() if enabled else None
cu = _make_cu(**cu_kw)
if svc is None:
return 0, cu
if audio_duration and audio_duration > 0:
est = max(1.0, math.ceil(audio_duration / 60.0))
elif text:
est = max(1.0, math.ceil(len(text) / 240))
else:
est = 1.0
cost = calculate_points_cost(
"ai_digital_human",
is_member=getattr(cu.user, "is_member", False),
duration_minutes=est,
member_type=getattr(cu.user, "member_type", None),
)
svc.deduct_points.return_value = {"success": success, "balance": balance}
res = svc.deduct_points(cu.user.id, cost, "ai_digital_human", MagicMock())
if not res["success"]:
raise HTTPException(status_code=402, detail={"code": "INSUFFICIENT_POINTS"})
return cost, cu
def test_disabled(self):
cost, _ = self._deduct(enabled=False)
assert cost == 0
def test_short_text_min_1min(self):
cost, _ = self._deduct(text="你好")
assert cost >= 15 # 15 base/min for free user × 1.15
def test_audio_duration_used(self):
cost_long, _ = self._deduct(audio_duration=180) # 3min
cost_short, _ = self._deduct(audio_duration=30) # 1min
assert cost_long > cost_short
def test_insufficient_402(self):
with pytest.raises(HTTPException) as ei:
self._deduct(text="你" * 500, success=False, balance=0)
assert ei.value.status_code == 402
def test_member_cheaper(self):
cm, _ = self._deduct(text="你" * 500, is_member=True, member_type="yearly")
cf, _ = self._deduct(text="你" * 500, is_member=False)
assert cm < cf