"""catalog 应用服务单测:会员套餐 / 积分包从共享库读取与字段映射。""" from __future__ import annotations from unittest.mock import MagicMock, patch import pytest @pytest.fixture(autouse=True) def _clear_cache(): from packages.application.catalog import admin_catalog admin_catalog._cache.clear() yield admin_catalog._cache.clear() def _row(**kw): row = MagicMock() for k, v in kw.items(): setattr(row, k, v) return row class TestMembershipPlans: def test_yearly_plan_mapping(self): from packages.application.catalog import admin_catalog row = _row( plan_key="premium_yearly", name="高级会员年卡", description="年度订阅", monthly_price=0, yearly_price=399, quotas={"4k": True, "batch_render": True, "credits_per_month": 500}, display_order=1, ) session = MagicMock() session.execute.return_value.fetchall.return_value = [row] sl = MagicMock(return_value=session) with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True): plans = admin_catalog.get_membership_plans() assert len(plans) == 1 p = plans[0] assert p["plan_id"] == "premium_yearly" assert p["billing_cycle"] == "yearly" assert p["price_cents"] == 39900 assert p["monthly_price_cents"] == 3325 assert p["duration_days"] == 365 assert p["features"]["4K 超清分辨率"] is True assert p["features"]["credits_per_month"] == 500 session.close.assert_called_once() def test_monthly_plan_mapping(self): from packages.application.catalog import admin_catalog row = _row( plan_key="premium_monthly", name="高级会员月卡", description=None, monthly_price=39, yearly_price=0, quotas=None, display_order=2, ) session = MagicMock() session.execute.return_value.fetchall.return_value = [row] sl = MagicMock(return_value=session) with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True): plans = admin_catalog.get_membership_plans() assert len(plans) == 1 p = plans[0] assert p["billing_cycle"] == "monthly" assert p["price_cents"] == 3900 assert p["monthly_price_cents"] == 3900 assert p["duration_days"] == 30 assert p["features"] == {} def test_both_cycles_expanded(self): from packages.application.catalog import admin_catalog row = _row( plan_key="premium", name="高级会员", description=None, monthly_price=39, yearly_price=399, quotas={}, display_order=1, ) session = MagicMock() session.execute.return_value.fetchall.return_value = [row] sl = MagicMock(return_value=session) with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True): plans = admin_catalog.get_membership_plans() cycles = {p["billing_cycle"] for p in plans} assert cycles == {"yearly", "monthly"} def test_no_session_returns_empty(self): from packages.application.catalog import admin_catalog with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", None, create=True): assert admin_catalog.get_membership_plans() == [] class TestPointsPackages: def test_package_mapping_with_bonus(self): from packages.application.catalog import admin_catalog row = _row( package_key="pkg_100", name="100元充值包", price=100, credits=1000, bonus_credits=100, is_recommended=True, description="推荐", sort_order=4, ) session = MagicMock() session.execute.return_value.fetchall.return_value = [row] sl = MagicMock(return_value=session) with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True): packages = admin_catalog.get_points_packages() assert len(packages) == 1 pkg = packages[0] assert pkg["code"] == "pkg_100" assert pkg["points"] == 1100 assert pkg["price_cents"] == 10000 assert pkg["is_recommended"] is True assert pkg["unit_price"] == "¥0.091/积分" def test_zero_credits_unit_price_safe(self): from packages.application.catalog import admin_catalog row = _row( package_key="pkg_0", name="空包", price=0, credits=0, bonus_credits=0, is_recommended=False, description=None, sort_order=0, ) session = MagicMock() session.execute.return_value.fetchall.return_value = [row] sl = MagicMock(return_value=session) with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True): packages = admin_catalog.get_points_packages() assert packages[0]["points"] == 0 assert packages[0]["price_cents"] == 0 assert packages[0]["unit_price"] == "¥0.000/积分" def test_no_session_returns_empty(self): from packages.application.catalog import admin_catalog with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", None, create=True): assert admin_catalog.get_points_packages() == [] class TestPackagesRoute: def test_get_packages_route_returns_items(self): from app.api.routes.points import get_packages cu = MagicMock() cu.user.member_type = None rows = [ { "code": "pkg_10", "name": "10元充值包", "points": 100, "price_cents": 1000, "unit_price": "¥0.100/积分", } ] with patch( "packages.application.catalog.admin_catalog.get_points_packages", return_value=rows, ): resp = get_packages(current_user=cu) assert len(resp.packages) == 1 item = resp.packages[0] assert item.code == "pkg_10" assert item.points == 100 assert item.price_cents == 1000