feat: add staged subscription rollout mode

This commit is contained in:
QiuSW
2026-07-22 15:13:28 +08:00
parent e3b90ccea5
commit 2604a59d67
17 changed files with 551 additions and 87 deletions
+117 -13
View File
@@ -63,7 +63,12 @@ from apps.billing.services import RechargePayment
from apps.moderation.models import SensitiveWord
from apps.moderation.providers.keyword import reset_keyword_matcher_cache
from apps.portal.models import DownloadRelease
from apps.licensing.models import ClientDevice, SoftwareOrder, SoftwarePlan
from apps.licensing.models import (
ClientDevice,
SoftwareEntitlement,
SoftwareOrder,
SoftwarePlan,
)
from apps.licensing.services import (
create_software_order,
grant_software_entitlement,
@@ -1437,6 +1442,82 @@ class GenerateApiTests(TestCase):
starts_at=starts_at,
)
@override_settings(
CMSHOPEE_SUBSCRIPTION_MODE="open",
PUBLIC_BASE_URL="https://cm.example.test",
)
def test_cmshopee_open_mode_returns_compatible_access_without_writing_entitlement(self):
response = self.client.get(
"/api/v1/cmshopee/subscription/status",
**self.auth_header(),
)
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertEqual(data["product_code"], "cmshopee")
self.assertEqual(data["status"], "active")
self.assertTrue(data["allowed"])
self.assertIsNone(data["code"])
self.assertEqual(data["account"]["username"], self.user.username)
self.assertEqual(data["plan"]["code"], "development-open")
self.assertEqual(data["plan"]["display_name"], "开发测试长期会员")
self.assertEqual(data["expires_at"], data["plan"]["expires_at"])
self.assertIsNone(data["grace_expires_at"])
self.assertEqual(data["manage_url"], "https://cm.example.test/subscription")
self.assertEqual(data["access_source"], "open_mode")
self.assertEqual(data["entitlement_status"], "required")
self.assertTrue(data["notice_id"])
self.assertFalse(SoftwareEntitlement.objects.filter(user=self.user).exists())
@override_settings(CMSHOPEE_SUBSCRIPTION_MODE="shadow")
def test_cmshopee_shadow_mode_allows_missing_entitlement_and_reports_real_status(self):
response = self.client.get(
"/api/v1/cmshopee/subscription/status",
**self.auth_header(),
)
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertEqual(data["status"], "active")
self.assertTrue(data["allowed"])
self.assertEqual(data["access_source"], "shadow_fallback")
self.assertEqual(data["entitlement_status"], "required")
self.assertEqual(data["plan"]["code"], "shadow-fallback")
@override_settings(CMSHOPEE_SUBSCRIPTION_MODE="open")
def test_cmshopee_open_mode_submit_does_not_create_entitlement(self):
response = self.post_with_provider(
"/api/v1/cmshopee/generate/title",
{"prompt": "开发测试标题", "model": self.title_alias},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(self.provider.text_calls), 1)
self.assertFalse(SoftwareEntitlement.objects.filter(user=self.user).exists())
@override_settings(CMSHOPEE_SUBSCRIPTION_MODE="shadow")
def test_cmshopee_shadow_submit_logs_real_entitlement_status(self):
with self.assertLogs("cmhub.licensing.authorization", level="INFO") as captured:
response = self.post_with_provider(
"/api/v1/cmshopee/generate/title",
{"prompt": "影子模式标题", "model": self.title_alias},
)
self.assertEqual(response.status_code, 200)
events = [
getattr(record, "cmshopee_authorization", None)
for record in captured.records
if getattr(record, "cmshopee_authorization", None)
]
self.assertEqual(len(events), 1)
self.assertEqual(events[0]["subscription_mode"], "shadow")
self.assertEqual(events[0]["access_source"], "shadow_fallback")
self.assertEqual(events[0]["entitlement_status"], "required")
self.assertTrue(events[0]["would_reject"])
self.assertEqual(events[0]["would_reject_code"], "subscription_required")
self.assertTrue(events[0]["final_allowed"])
@override_settings(CMSHOPEE_SUBSCRIPTION_MODE="enforce")
def test_cmshopee_subscription_status_is_required_without_entitlement(self):
response = self.client.get(
"/api/v1/cmshopee/subscription/status",
@@ -1444,17 +1525,29 @@ class GenerateApiTests(TestCase):
)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.json(),
{
"product_code": "cmshopee",
"status": "required",
"allowed": False,
"code": "subscription_required",
"plan": None,
},
data = response.json()
self.assertEqual(data["status"], "required")
self.assertFalse(data["allowed"])
self.assertEqual(data["code"], "subscription_required")
self.assertIsNone(data["plan"])
self.assertEqual(data["entitlement_status"], "required")
self.assertEqual(data["access_source"], "entitlement")
@override_settings(
CMSHOPEE_SUBSCRIPTION_MODE="",
CMSHOPEE_SUBSCRIPTION_ENFORCEMENT=True,
)
def test_cmshopee_legacy_enforcement_setting_remains_supported(self):
response = self.client.get(
"/api/v1/cmshopee/subscription/status",
**self.auth_header(),
)
self.assertEqual(response.status_code, 200)
self.assertFalse(response.json()["allowed"])
self.assertEqual(response.json()["code"], "subscription_required")
@override_settings(CMSHOPEE_SUBSCRIPTION_MODE="enforce")
def test_cmshopee_subscription_status_is_active_without_device_session(self):
entitlement = self.create_cmshopee_entitlement()
@@ -1468,8 +1561,18 @@ class GenerateApiTests(TestCase):
self.assertEqual(data["status"], "active")
self.assertTrue(data["allowed"])
self.assertIsNone(data["code"])
self.assertEqual(data["plan"]["display_name"], entitlement.plan_name)
self.assertEqual(data["plan"]["name"], entitlement.plan_name)
self.assertEqual(data["plan"]["code"], f"plan-{entitlement.source_plan_id}")
self.assertEqual(data["expires_at"], entitlement.expires_at.isoformat())
self.assertEqual(
data["grace_expires_at"],
entitlement.grace_expires_at.isoformat(),
)
self.assertEqual(data["access_source"], "entitlement")
self.assertEqual(data["entitlement_status"], "active")
@override_settings(CMSHOPEE_SUBSCRIPTION_MODE="enforce")
def test_cmshopee_subscription_status_is_expired_after_grace_period(self):
self.create_cmshopee_entitlement(
starts_at=timezone.now() - timedelta(days=40),
@@ -1485,8 +1588,9 @@ class GenerateApiTests(TestCase):
self.assertEqual(data["status"], "expired")
self.assertFalse(data["allowed"])
self.assertEqual(data["code"], "subscription_expired")
self.assertEqual(data["entitlement_status"], "expired")
@override_settings(CMSHOPEE_SUBSCRIPTION_ENFORCEMENT=True)
@override_settings(CMSHOPEE_SUBSCRIPTION_MODE="enforce")
def test_cmshopee_enforcement_rejects_without_subscription(self):
response = self.post_with_provider(
"/api/v1/cmshopee/generate/title",
@@ -1498,7 +1602,7 @@ class GenerateApiTests(TestCase):
self.assertEqual(self.provider.text_calls, [])
self.assert_generation_not_charged()
@override_settings(CMSHOPEE_SUBSCRIPTION_ENFORCEMENT=True)
@override_settings(CMSHOPEE_SUBSCRIPTION_MODE="enforce")
def test_cmshopee_account_subscription_allows_multiple_device_contexts(self):
self.create_cmshopee_entitlement()
@@ -1516,7 +1620,7 @@ class GenerateApiTests(TestCase):
self.assertEqual(second_response.status_code, 200)
self.assertEqual(len(self.provider.text_calls), 2)
@override_settings(CMSHOPEE_SUBSCRIPTION_ENFORCEMENT=True)
@override_settings(CMSHOPEE_SUBSCRIPTION_MODE="enforce")
def test_generic_generation_remains_available_without_subscription(self):
response = self.post_with_provider(
"/api/v1/generate/title",