"""积分消耗规则单元测试 (#1895) — v1.6.2: 仅保留 voice_clone 相关""" from __future__ import annotations import math import pytest from packages.domain.points_rules import ( FREE_USER_MULTIPLIER, MEMBER_DISCOUNT, MEMBERSHIP_PRICES, POINTS_PACKAGES, POINTS_SCENES, calculate_points_cost, ) class TestPointsScenesConfig: """场景配置完整性""" def test_voice_clone_scenes_defined(self): # 仅保留声音克隆两个场景 assert set(POINTS_SCENES.keys()) == {"voice_clone_train", "voice_clone_synth"} def test_required_keys_present(self): for key, scene in POINTS_SCENES.items(): assert "base_points" in scene, f"{key} missing base_points" assert "unit" in scene, f"{key} missing unit" assert "name" in scene, f"{key} missing name" def test_voice_clone_train_is_free(self): assert POINTS_SCENES["voice_clone_train"]["base_points"] == 0 def test_voice_clone_synth_is_per_minute(self): assert POINTS_SCENES["voice_clone_synth"]["base_points"] == 1 assert POINTS_SCENES["voice_clone_synth"]["unit"] == "分钟" class TestPointsPackages: def test_three_packages(self): assert len(POINTS_PACKAGES) == 3 assert POINTS_PACKAGES["starter_pack"]["points"] == 100 assert POINTS_PACKAGES["basic_pack"]["points"] == 500 assert POINTS_PACKAGES["pro_pack"]["points"] == 2000 class TestMembershipPrices: def test_three_plans(self): assert len(MEMBERSHIP_PRICES) == 3 assert MEMBERSHIP_PRICES["monthly"]["price_cents"] == 1990 assert MEMBERSHIP_PRICES["quarterly"]["duration_days"] == 90 assert MEMBERSHIP_PRICES["yearly"]["price_cents"] == 15900 class TestCalculatePointsCost: """核心计费逻辑""" # ── 声音克隆合成(按时长计费) ── def test_voice_clone_synth_base(self): cost = calculate_points_cost("voice_clone_synth", is_member=False, duration_minutes=3) assert cost == math.ceil(3 * FREE_USER_MULTIPLIER) def test_voice_clone_synth_rounds_up(self): cost = calculate_points_cost("voice_clone_synth", is_member=False, duration_minutes=2.3) assert cost == math.ceil(3 * FREE_USER_MULTIPLIER) def test_voice_clone_synth_minimum_1_minute(self): cost = calculate_points_cost("voice_clone_synth", is_member=False, duration_minutes=0.1) assert cost == math.ceil(1 * FREE_USER_MULTIPLIER) # ── 免费场景 ── def test_voice_clone_train_free(self): cost = calculate_points_cost("voice_clone_train", is_member=False) assert cost == 0 def test_voice_clone_train_free_for_member(self): cost = calculate_points_cost("voice_clone_train", is_member=True) assert cost == 0 # ── 会员折扣 ── def test_monthly_member_discount(self): cost = calculate_points_cost("voice_clone_synth", is_member=True, duration_minutes=1, member_type="monthly") assert cost == max(1, math.floor(1 * MEMBER_DISCOUNT["monthly"])) def test_yearly_member_deep_discount(self): cost = calculate_points_cost( "voice_clone_synth", is_member=True, duration_minutes=2, member_type="yearly", ) assert cost == max(1, math.floor(2 * MEMBER_DISCOUNT["yearly"])) def test_member_without_type_no_discount(self): cost = calculate_points_cost("voice_clone_synth", is_member=True, duration_minutes=1) assert cost == 1 # ── 已下线/未知场景(向后兼容:返回 0) ── @pytest.mark.parametrize( "scene", [ "ai_voice", "ai_video", "ai_digital_human", "ai_rewrite", "ai_cover", "ai_title", "douyin_extract", "nonexistent", ], ) def test_retired_scenes_return_zero(self, scene): assert calculate_points_cost(scene, is_member=False) == 0 assert calculate_points_cost(scene, is_member=True, duration_minutes=10) == 0