From a4b61c7dbb7d7e48cc98baac9bd916fbdf412f0b Mon Sep 17 00:00:00 2001 From: Xiaoxia Agent Date: Sun, 4 Oct 2026 00:19:58 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(catalog):=20=E4=BC=9A=E5=91=98?= =?UTF-8?q?=E5=A5=97=E9=A4=90=E4=B8=8E=E7=A7=AF=E5=88=86=E5=8C=85=E6=94=B9?= =?UTF-8?q?=E8=AF=BB=E7=AE=A1=E7=90=86=E5=90=8E=E5=8F=B0=E7=9C=9F=E5=AE=9E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 application/catalog 服务,从共享库 plans / credit_packages 读取 - /subscription/plans 不再返回硬编码月卡/季卡/年卡,仅返回 is_enabled=true 套餐 - /points/packages 改读 credit_packages(is_active=true) - 价格统一元转分;quotas 映射为中文 features - 30 秒 TTL 缓存,后台启停/改价后用户端最多 30 秒生效 --- apps/api/app/api/routes/points.py | 27 +-- apps/api/app/api/routes/subscription.py | 28 +-- packages/application/catalog/__init__.py | 1 + packages/application/catalog/admin_catalog.py | 160 ++++++++++++++++++ 4 files changed, 180 insertions(+), 36 deletions(-) create mode 100644 packages/application/catalog/__init__.py create mode 100644 packages/application/catalog/admin_catalog.py diff --git a/apps/api/app/api/routes/points.py b/apps/api/app/api/routes/points.py index 531a68cd6..17a0a8e70 100644 --- a/apps/api/app/api/routes/points.py +++ b/apps/api/app/api/routes/points.py @@ -145,19 +145,22 @@ def get_rules( def get_packages( current_user: AuthenticatedUser = Depends(get_current_user), ): - """查询可购买的积分包列表。""" - packages = [] - for code, pkg in POINTS_PACKAGES.items(): - unit_price = f"¥{pkg['price_cents'] / 100 / pkg['points']:.3f}/积分" - packages.append( - PointsPackageItem( - code=code, - name=pkg["name"], - points=pkg["points"], - price_cents=pkg["price_cents"], - unit_price=unit_price, - ) + """查询可购买的积分包列表(读管理后台 credit_packages 表真实数据)。 + + 仅返回 is_active=true;后台改价/启停后最多 30 秒生效。 + """ + from packages.application.catalog.admin_catalog import get_points_packages + + packages = [ + PointsPackageItem( + code=row["code"], + name=row["name"], + points=row["points"], + price_cents=row["price_cents"], + unit_price=row["unit_price"], ) + for row in get_points_packages() + ] mt = _member_type(current_user) discount = MEMBER_DISCOUNT.get(mt) if mt else None return PointsPackagesResponse(packages=packages, user_discount=discount) diff --git a/apps/api/app/api/routes/subscription.py b/apps/api/app/api/routes/subscription.py index 3067ac2fc..0e1bb76a3 100755 --- a/apps/api/app/api/routes/subscription.py +++ b/apps/api/app/api/routes/subscription.py @@ -86,33 +86,13 @@ async def get_current_subscription( def list_membership_plans( current_user: AuthenticatedUser = Depends(get_current_user), ) -> dict[str, list[dict[str, Any]]]: - """查询所有会员档位(供前端会员购买页展示)。 + """查询可购买的会员套餐(读管理后台 plans 表真实数据)。 - 返回 points 积分体系下的会员档位(月卡/季卡/年卡),含价格、时长、积分折扣等信息。 + 仅返回 is_enabled=true 的套餐;后台启停/改价后最多 30 秒生效。 """ - from packages.domain.points_rules import MEMBER_DISCOUNT, MEMBERSHIP_PRICES + from packages.application.catalog.admin_catalog import get_membership_plans - plans: list[dict[str, Any]] = [] - for plan_id, info in MEMBERSHIP_PRICES.items(): - days = info["duration_days"] - monthly_cents = round(info["price_cents"] * 30 / days) - features: dict[str, Any] = {"max_resolution": "1080p"} - if plan_id == MembershipType.MONTHLY: - features.update({"free_clips_daily": 2}) - elif plan_id == MembershipType.QUARTERLY: - features.update({"free_clips_daily": 5}) - elif plan_id == MembershipType.YEARLY: - features.update({"free_clips_daily": "unlimited"}) - plans.append({ - "plan_id": plan_id, - "name": info["name"], - "price_cents": info["price_cents"], - "monthly_price_cents": monthly_cents, - "duration_days": days, - "points_discount": MEMBER_DISCOUNT.get(plan_id, 1.0), - "features": features, - }) - return {"plans": plans} + return {"plans": get_membership_plans()} @router.get("/billing-records", response_model=list[BillingRecord]) diff --git a/packages/application/catalog/__init__.py b/packages/application/catalog/__init__.py new file mode 100644 index 000000000..2224531b0 --- /dev/null +++ b/packages/application/catalog/__init__.py @@ -0,0 +1 @@ +"""应用层:对外展示目录(套餐/积分包)。""" diff --git a/packages/application/catalog/admin_catalog.py b/packages/application/catalog/admin_catalog.py new file mode 100644 index 000000000..f37dc3d27 --- /dev/null +++ b/packages/application/catalog/admin_catalog.py @@ -0,0 +1,160 @@ +"""读取管理后台配置的会员套餐 / 积分充值包(共享库真实数据)。 + +替代旧的硬编码 MEMBERSHIP_PRICES / POINTS_PACKAGES。 +短 TTL 缓存(30 秒),后台改价/启停后用户端最多 30 秒可见。 +""" + +from __future__ import annotations + +import threading +import time +from typing import Any + +_CACHE_TTL = 30.0 +_lock = threading.Lock() +_cache: dict[str, tuple[float, Any]] = {} + +_QUOTA_LABELS = { + "4k": "4K 超清分辨率", + "batch_render": "批量渲染", + "priority_queue": "优先处理队列", + "ai_matting": "AI 智能抠像", + "remove_watermark": "去水印", +} + + +def _cached(key: str, loader): + now = time.time() + hit = _cache.get(key) + if hit and now - hit[0] < _CACHE_TTL: + return hit[1] + with _lock: + hit = _cache.get(key) + if hit and time.time() - hit[0] < _CACHE_TTL: + return hit[1] + value = loader() + _cache[key] = (time.time(), value) + return value + + +def _quota_features(quotas: dict[str, Any] | None) -> dict[str, Any]: + quotas = quotas or {} + features: dict[str, Any] = {} + for k, v in quotas.items(): + if k == "credits_per_month": + features["credits_per_month"] = v + elif k in _QUOTA_LABELS: + features[_QUOTA_LABELS[k]] = v + else: + features[k] = v + return features + + +def get_membership_plans() -> list[dict[str, Any]]: + """读取 is_enabled=true 的套餐,按年/月周期展开为用户端档位。""" + + def _load() -> list[dict[str, Any]]: + from sqlalchemy import text + + from packages.adapters.sqlalchemy_impl.session import SessionLocal + + if SessionLocal is None: + return [] + + session = SessionLocal() + try: + rows = session.execute( + text( + """ + SELECT plan_key, name, description, monthly_price, yearly_price, + quotas, display_order + FROM plans + WHERE is_enabled = TRUE + ORDER BY display_order NULLS LAST, created_at + """ + ) + ).fetchall() + finally: + session.close() + + plans: list[dict[str, Any]] = [] + for r in rows: + base_features = _quota_features(r.quotas if isinstance(r.quotas, dict) else None) + if r.yearly_price and float(r.yearly_price) > 0: + plans.append( + { + "plan_id": r.plan_key, + "billing_cycle": "yearly", + "name": r.name, + "description": r.description, + "price_cents": int(round(float(r.yearly_price) * 100)), + "monthly_price_cents": int(round(float(r.yearly_price) * 100 / 12)), + "duration_days": 365, + "features": dict(base_features), + } + ) + if r.monthly_price and float(r.monthly_price) > 0: + plans.append( + { + "plan_id": r.plan_key, + "billing_cycle": "monthly", + "name": r.name, + "description": r.description, + "price_cents": int(round(float(r.monthly_price) * 100)), + "monthly_price_cents": int(round(float(r.monthly_price) * 100)), + "duration_days": 30, + "features": dict(base_features), + } + ) + return plans + + return _cached("membership_plans", _load) + + +def get_points_packages() -> list[dict[str, Any]]: + """读取 is_active=true 的积分充值包。""" + + def _load() -> list[dict[str, Any]]: + from sqlalchemy import text + + from packages.adapters.sqlalchemy_impl.session import SessionLocal + + if SessionLocal is None: + return [] + + session = SessionLocal() + try: + rows = session.execute( + text( + """ + SELECT package_key, name, price, credits, bonus_credits, + is_recommended, description, sort_order + FROM credit_packages + WHERE is_active = TRUE + ORDER BY sort_order NULLS LAST, price + """ + ) + ).fetchall() + finally: + session.close() + + packages: list[dict[str, Any]] = [] + for r in rows: + total_points = int(r.credits or 0) + int(r.bonus_credits or 0) + price_cents = int(round(float(r.price) * 100)) + unit = (price_cents / 100 / total_points) if total_points else 0 + packages.append( + { + "code": r.package_key, + "name": r.name, + "points": total_points, + "bonus_credits": int(r.bonus_credits or 0), + "price_cents": price_cents, + "unit_price": f"¥{unit:.3f}/积分", + "is_recommended": bool(r.is_recommended), + "description": r.description, + } + ) + return packages + + return _cached("points_packages", _load) -- 2.54.0 From f2629da64f8a347502dde8f1363f100daa2003e1 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 3 Oct 2026 16:27:54 +0000 Subject: [PATCH 2/5] style: auto-format with black + isort + ruff + prettier [skip ci-format-check] --- packages/application/catalog/admin_catalog.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/application/catalog/admin_catalog.py b/packages/application/catalog/admin_catalog.py index f37dc3d27..2ca68a543 100644 --- a/packages/application/catalog/admin_catalog.py +++ b/packages/application/catalog/admin_catalog.py @@ -63,17 +63,13 @@ def get_membership_plans() -> list[dict[str, Any]]: session = SessionLocal() try: - rows = session.execute( - text( - """ + rows = session.execute(text(""" SELECT plan_key, name, description, monthly_price, yearly_price, quotas, display_order FROM plans WHERE is_enabled = TRUE ORDER BY display_order NULLS LAST, created_at - """ - ) - ).fetchall() + """)).fetchall() finally: session.close() @@ -124,17 +120,13 @@ def get_points_packages() -> list[dict[str, Any]]: session = SessionLocal() try: - rows = session.execute( - text( - """ + rows = session.execute(text(""" SELECT package_key, name, price, credits, bonus_credits, is_recommended, description, sort_order FROM credit_packages WHERE is_active = TRUE ORDER BY sort_order NULLS LAST, price - """ - ) - ).fetchall() + """)).fetchall() finally: session.close() -- 2.54.0 From 2d0c4d94bfdbbe2fad411fcae8e2e6d91848d8cb Mon Sep 17 00:00:00 2001 From: Xiaoxia Agent Date: Sun, 4 Oct 2026 00:30:04 +0800 Subject: [PATCH 3/5] =?UTF-8?q?test:=20=E5=A5=97=E9=A4=90=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=8D=95=E6=B5=8B=E6=94=B9=E4=B8=BA=E6=96=AD=E8=A8=80?= =?UTF-8?q?=E8=AF=BB=E5=90=8E=E5=8F=B0=20catalog=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=EF=BC=88mock=20=E6=97=A7=E4=B8=89=E6=A1=A3=E7=A1=AC=E7=BC=96?= =?UTF-8?q?=E7=A0=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/test_points_routes.py | 59 +++++++++++++++++++------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/tests/unit/test_points_routes.py b/tests/unit/test_points_routes.py index 2b8a7f08b..6dc9e8968 100644 --- a/tests/unit/test_points_routes.py +++ b/tests/unit/test_points_routes.py @@ -173,33 +173,46 @@ class TestSubscriptionPlans: _spec.loader.exec_module(_mod) return _mod.list_membership_plans - def test_plans_endpoint_returns_three_tiers(self): - import os # noqa: F401 (used by _import_plans_fn) - + def test_plans_endpoint_reads_admin_table(self): + """/subscription/plans 改读管理后台 plans 表:返回 catalog 服务提供的真实档位。""" list_membership_plans = self._import_plans_fn() - resp = list_membership_plans(current_user=_make_cu()) + real_plan = { + "plan_id": "premium_yearly", + "billing_cycle": "yearly", + "name": "高级会员年卡", + "description": "高级会员年度订阅,享受全部功能", + "price_cents": 39900, + "monthly_price_cents": 3325, + "duration_days": 365, + "features": { + "4K 超清分辨率": True, + "批量渲染": True, + "优先处理队列": True, + "credits_per_month": 500, + }, + } + with patch( + "packages.application.catalog.admin_catalog.get_membership_plans", + return_value=[real_plan], + ): + resp = list_membership_plans(current_user=_make_cu()) plans = resp["plans"] - plan_ids = {p["plan_id"] for p in plans} - assert plan_ids == {"monthly", "quarterly", "yearly"} - for p in plans: - assert p["price_cents"] > 0 - assert p["duration_days"] in (30, 90, 365) - assert 0 < p["points_discount"] <= 1.0 - assert "max_resolution" in p["features"] - - def test_longer_plans_cheaper_per_month(self): - import os # noqa: F401 + assert len(plans) == 1 + p0 = plans[0] + assert p0["plan_id"] == "premium_yearly" + assert p0["price_cents"] == 39900 + assert p0["duration_days"] == 365 + assert p0["features"]["4K 超清分辨率"] is True + def test_plans_endpoint_empty_when_all_disabled(self): + """后台停用全部套餐时,用户端返回空列表。""" list_membership_plans = self._import_plans_fn() - resp = list_membership_plans(current_user=_make_cu()) - plans = resp["plans"] - monthly = next(p for p in plans if p["plan_id"] == "monthly") - quarterly = next(p for p in plans if p["plan_id"] == "quarterly") - yearly = next(p for p in plans if p["plan_id"] == "yearly") - assert monthly["monthly_price_cents"] == 1990 - assert quarterly["monthly_price_cents"] < monthly["monthly_price_cents"] - assert yearly["monthly_price_cents"] < quarterly["monthly_price_cents"] - + with patch( + "packages.application.catalog.admin_catalog.get_membership_plans", + return_value=[], + ): + resp = list_membership_plans(current_user=_make_cu()) + assert resp["plans"] == [] # ── P1-7: multiplier consistency ────────────────────────────────────── -- 2.54.0 From 8c3b5fc6cd4092407d0f7cb66ee31834b9b918a3 Mon Sep 17 00:00:00 2001 From: Xiaoxia Agent Date: Sun, 4 Oct 2026 00:46:22 +0800 Subject: [PATCH 4/5] =?UTF-8?q?test:=20catalog=20=E5=A5=97=E9=A4=90/?= =?UTF-8?q?=E7=A7=AF=E5=88=86=E5=8C=85=E5=AD=97=E6=AE=B5=E6=98=A0=E5=B0=84?= =?UTF-8?q?=E5=8D=95=E6=B5=8B=EF=BC=8C=E6=BB=A1=E8=B6=B3=E5=A2=9E=E9=87=8F?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/test_catalog.py | 212 +++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 tests/unit/test_catalog.py diff --git a/tests/unit/test_catalog.py b/tests/unit/test_catalog.py new file mode 100644 index 000000000..2cb53612c --- /dev/null +++ b/tests/unit/test_catalog.py @@ -0,0 +1,212 @@ +"""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 -- 2.54.0 From 1d8ed53d1a31a21fb33a9a86e6548243ceb4fbbd Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 3 Oct 2026 16:50:44 +0000 Subject: [PATCH 5/5] style: auto-format with black + isort + ruff + prettier [skip ci-format-check] --- tests/unit/test_catalog.py | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/tests/unit/test_catalog.py b/tests/unit/test_catalog.py index 2cb53612c..448bb6717 100644 --- a/tests/unit/test_catalog.py +++ b/tests/unit/test_catalog.py @@ -40,9 +40,7 @@ class TestMembershipPlans: session.execute.return_value.fetchall.return_value = [row] sl = MagicMock(return_value=session) - with patch( - "packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True - ): + with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True): plans = admin_catalog.get_membership_plans() assert len(plans) == 1 @@ -72,9 +70,7 @@ class TestMembershipPlans: session.execute.return_value.fetchall.return_value = [row] sl = MagicMock(return_value=session) - with patch( - "packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True - ): + with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True): plans = admin_catalog.get_membership_plans() assert len(plans) == 1 @@ -101,9 +97,7 @@ class TestMembershipPlans: session.execute.return_value.fetchall.return_value = [row] sl = MagicMock(return_value=session) - with patch( - "packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True - ): + 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} @@ -112,9 +106,7 @@ class TestMembershipPlans: 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 - ): + with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", None, create=True): assert admin_catalog.get_membership_plans() == [] @@ -136,9 +128,7 @@ class TestPointsPackages: session.execute.return_value.fetchall.return_value = [row] sl = MagicMock(return_value=session) - with patch( - "packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True - ): + with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True): packages = admin_catalog.get_points_packages() assert len(packages) == 1 @@ -166,9 +156,7 @@ class TestPointsPackages: session.execute.return_value.fetchall.return_value = [row] sl = MagicMock(return_value=session) - with patch( - "packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True - ): + with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", sl, create=True): packages = admin_catalog.get_points_packages() assert packages[0]["points"] == 0 @@ -178,9 +166,7 @@ class TestPointsPackages: 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 - ): + with patch("packages.adapters.sqlalchemy_impl.session.SessionLocal", None, create=True): assert admin_catalog.get_points_packages() == [] -- 2.54.0