1d8ed53d1a
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 57s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m9s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m37s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m6s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m56s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 4m17s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 4m35s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m34s
AI Code Review / AI Code Review (pull_request) Successful in 6m37s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 9m30s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 7m10s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 35s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 55s
199 lines
6.3 KiB
Python
199 lines
6.3 KiB
Python
"""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
|