import uuid from decimal import Decimal from allauth.account.models import EmailAddress from django.contrib.auth import get_user, get_user_model from django.contrib.staticfiles import finders from django.core import mail from django.core.cache import cache from django.test import Client, TestCase, override_settings from django.utils import timezone from rest_framework.test import APIClient from apps.ai.models import AiModel, ModelAlias from apps.billing.models import ( CallRecord, ExchangeRate, PointsLedger, PricingRule, RechargeOrder, ) from apps.users.models import ApiKey, UserWallet @override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend") class PortalAccountFlowTests(TestCase): password = "Strong-pass-123" def setUp(self): cache.clear() def create_verified_user(self): suffix = uuid.uuid4().hex[:8] user = get_user_model().objects.create_user( username=f"portal-user-{suffix}", email=f"portal-user-{suffix}@example.com", password=self.password, ) EmailAddress.objects.create( user=user, email=user.email, verified=True, primary=True, ) return user def create_recharge_order( self, user, *, order_no: str, amount: str, points: int, status: str = RechargeOrder.Status.PAID, ): return RechargeOrder.objects.create( user=user, order_no=order_no, amount_money=Decimal(amount), currency="CNY", pay_method=RechargeOrder.PayMethod.WEIXIN, exchange_rate=Decimal("10.0000"), points_granted=points, status=status, payment_txn_no=f"txn-{order_no}" if status == RechargeOrder.Status.PAID else "", paid_at=timezone.now() if status == RechargeOrder.Status.PAID else None, ) def create_call_record( self, user, api_key, *, alias: str, points_cost: int, status: str = CallRecord.Status.SUCCESS, ): return CallRecord.objects.create( user=user, api_key=api_key, operation_type=CallRecord.OperationType.TITLE, alias=alias, model_used="gpt-5.5", points_cost=points_cost, status=status, ) def create_model_alias( self, *, alias: str, operation_type: str = ModelAlias.OperationType.TITLE, capabilities: list[str] | None = None, api_type: str = AiModel.ApiType.CHAT, url: str = "https://provider-secret.example/v1/chat/completions", model_sku: str = "secret-sku-gpt-5.5", ) -> ModelAlias: ai_model = AiModel.objects.create( name=f"{alias}-{uuid.uuid4().hex[:8]}", url=url, model=model_sku, api_type=api_type, api_key_encrypted="encrypted-provider-key", capabilities=capabilities if capabilities is not None else ["text"], extra_body={"internal": "provider-extra-secret"}, is_active=True, ) return ModelAlias.objects.create( alias=alias, operation_type=operation_type, ai_model=ai_model, ) def assert_nav_link_active(self, response, *, href: str, label: str): html = response.content.decode("utf-8") self.assertIn( f'{label}', html, ) def assert_nav_link_inactive(self, response, *, href: str, label: str): html = response.content.decode("utf-8") self.assertIn( f'{label}', html, ) def test_signup_creates_user_wallet_with_zero_points_no_ledger_and_can_login(self): suffix = uuid.uuid4().hex[:8] email = f"signup-{suffix}@example.com" response = self.client.post( "/signup", { "username": f"signup-{suffix}", "email": email, "password1": self.password, "password2": self.password, }, ) self.assertEqual(response.status_code, 302) user = get_user_model().objects.get(email=email) email_address = EmailAddress.objects.get(user=user, email=email) wallet = UserWallet.objects.get(user=user) self.assertFalse(email_address.verified) self.assertTrue(email_address.primary) self.assertEqual(wallet.points_balance, 0) self.assertFalse(PointsLedger.objects.filter(user=user).exists()) self.assertEqual(len(mail.outbox), 0) self.assertTrue(get_user(self.client).is_authenticated) def test_user_can_login_without_verified_email(self): suffix = uuid.uuid4().hex[:8] user = get_user_model().objects.create_user( username=f"unverified-{suffix}", email=f"unverified-{suffix}@example.com", password=self.password, ) EmailAddress.objects.create( user=user, email=user.email, verified=False, primary=True, ) login_response = self.client.post( "/login", {"login": user.email, "password": self.password}, ) self.assertEqual(login_response.status_code, 302) self.assertEqual(login_response["Location"], "/dashboard") self.assertTrue(get_user(self.client).is_authenticated) def test_user_can_login_view_dashboard_and_logout(self): user = self.create_verified_user() UserWallet.objects.create(user=user, points_balance=42) login_response = self.client.post( "/login", {"login": user.email, "password": self.password}, ) self.assertEqual(login_response.status_code, 302) self.assertEqual(login_response["Location"], "/dashboard") self.assertTrue(get_user(self.client).is_authenticated) dashboard_response = self.client.get("/dashboard") self.assertEqual(dashboard_response.status_code, 200) self.assertContains(dashboard_response, user.email) self.assertContains(dashboard_response, "42") logout_response = self.client.post("/logout") self.assertEqual(logout_response.status_code, 302) self.assertEqual(logout_response["Location"], "/login") self.assertFalse(get_user(self.client).is_authenticated) def test_dashboard_requires_session_login(self): response = self.client.get("/dashboard") self.assertEqual(response.status_code, 302) self.assertTrue(response["Location"].startswith("/login?next=")) def test_login_post_is_csrf_protected(self): csrf_client = Client(enforce_csrf_checks=True) response = csrf_client.post( "/login", {"login": "nobody@example.com", "password": "password"}, ) self.assertEqual(response.status_code, 403) def test_apikeys_requires_session_login(self): response = self.client.get("/apikeys") self.assertEqual(response.status_code, 302) self.assertTrue(response["Location"].startswith("/login?next=")) def test_models_page_requires_session_login(self): response = self.client.get("/models") self.assertEqual(response.status_code, 302) self.assertTrue(response["Location"].startswith("/login?next=")) def test_models_page_lists_public_aliases_prices_and_unpriced_state(self): user = self.create_verified_user() title_alias = self.create_model_alias(alias="title-standard", capabilities=["text"]) image_alias = self.create_model_alias( alias="image-edit", operation_type=ModelAlias.OperationType.IMAGE, capabilities=["image", "vision"], api_type=AiModel.ApiType.IMAGES_EDITS, url="https://provider-secret.example/v1/images/edits", model_sku="secret-sku-image-2", ) PricingRule.objects.create( operation_type=title_alias.operation_type, alias=title_alias.alias, resolution="", points_cost=2, ) PricingRule.objects.create( operation_type=image_alias.operation_type, alias=image_alias.alias, resolution="1k", points_cost=12, is_active=False, ) self.client.force_login(user) response = self.client.get("/models") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context["models"]), 2) self.assertContains(response, "可用模型") self.assertContains(response, "title-standard") self.assertContains(response, "生成标题") self.assertContains(response, "默认") self.assertContains(response, "2 点") self.assertContains(response, "image-edit") self.assertContains(response, "生成图片") self.assertContains(response, "需要") self.assertContains(response, "暂未定价") self.assertNotContains(response, "secret-sku") self.assertNotContains(response, "provider-secret.example") self.assertNotContains(response, "encrypted-provider-key") self.assertNotContains(response, "provider-extra-secret") self.assertNotContains(response, "api_key") self.assertNotContains(response, "api_key_encrypted") self.assertNotContains(response, "extra_body") def test_authenticated_nav_highlights_current_page_only(self): user = self.create_verified_user() self.client.force_login(user) cases = ( ("/dashboard", "/dashboard", "控制台"), ("/recharge", "/recharge", "充值"), ("/apikeys", "/apikeys", "API Key"), ("/models", "/models", "可用模型"), ("/records/recharge", "/records/recharge", "充值记录"), ("/records/usage", "/records/usage", "消费记录"), ) for path, href, label in cases: with self.subTest(path=path): response = self.client.get(path) self.assertEqual(response.status_code, 200) self.assert_nav_link_active(response, href=href, label=label) if path != "/recharge": self.assert_nav_link_inactive( response, href="/recharge", label="充值", ) def test_create_api_key_shows_plaintext_once_and_stores_only_hash(self): user = self.create_verified_user() self.client.force_login(user) response = self.client.post("/apikeys", {"name": "desktop"}, follow=True) self.assertEqual(response.status_code, 200) api_key = ApiKey.objects.get(user=user) new_api_key = response.context["new_api_key"] raw_key = new_api_key["raw_key"] self.assertTrue(raw_key.startswith("sk_cmhub_")) self.assertEqual(api_key.name, "desktop") self.assertEqual(api_key.key_prefix, raw_key[: ApiKey.KEY_PREFIX_LENGTH]) self.assertNotEqual(api_key.key_hash, raw_key) self.assertNotIn(raw_key, str(api_key.__dict__)) self.assertTrue(api_key.matches_key(raw_key)) self.assertContains(response, raw_key) self.assertContains(response, api_key.key_prefix) self.assertNotContains(response, api_key.key_hash) second_response = self.client.get("/apikeys") self.assertEqual(second_response.status_code, 200) self.assertNotContains(second_response, raw_key) self.assertContains(second_response, api_key.key_prefix) self.assertNotContains(second_response, api_key.key_hash) def test_apikey_list_only_shows_current_user_prefix(self): user = self.create_verified_user() other_user = self.create_verified_user() own_key, own_raw_key = ApiKey.create_for_user(user, name="desktop") other_key, other_raw_key = ApiKey.create_for_user(other_user, name="other") self.client.force_login(user) response = self.client.get("/apikeys") self.assertEqual(response.status_code, 200) self.assertContains(response, own_key.key_prefix) self.assertContains(response, "desktop") self.assertNotContains(response, own_raw_key) self.assertNotContains(response, own_key.key_hash) self.assertNotContains(response, other_key.key_prefix) self.assertNotContains(response, other_raw_key) self.assertNotContains(response, other_key.key_hash) def test_delete_api_key_revokes_it_and_external_api_returns_403(self): user = self.create_verified_user() api_key, raw_key = ApiKey.create_for_user(user, name="desktop") self.client.force_login(user) response = self.client.post(f"/apikeys/{api_key.pk}/delete") self.assertEqual(response.status_code, 302) self.assertEqual(response["Location"], "/apikeys") api_key.refresh_from_db() self.assertEqual(api_key.status, ApiKey.Status.REVOKED) api_client = APIClient() api_response = api_client.get( "/api/v1/balance", HTTP_AUTHORIZATION=f"Bearer {raw_key}", ) self.assertEqual(api_response.status_code, 403) self.assertEqual(api_response.data["error"]["code"], "account_disabled") def test_delete_api_key_does_not_allow_other_users_key(self): user = self.create_verified_user() other_user = self.create_verified_user() other_key, _raw_key = ApiKey.create_for_user(other_user, name="other") self.client.force_login(user) response = self.client.post(f"/apikeys/{other_key.pk}/delete") self.assertEqual(response.status_code, 404) other_key.refresh_from_db() self.assertEqual(other_key.status, ApiKey.Status.ACTIVE) def test_apikey_create_and_delete_posts_are_csrf_protected(self): user = self.create_verified_user() api_key, _raw_key = ApiKey.create_for_user(user, name="desktop") csrf_client = Client(enforce_csrf_checks=True) csrf_client.force_login(user) create_response = csrf_client.post("/apikeys", {"name": "new"}) delete_response = csrf_client.post(f"/apikeys/{api_key.pk}/delete") self.assertEqual(create_response.status_code, 403) self.assertEqual(delete_response.status_code, 403) api_key.refresh_from_db() self.assertEqual(api_key.status, ApiKey.Status.ACTIVE) def test_dashboard_shows_account_summary_recent_records_for_current_user_only(self): user = self.create_verified_user() other_user = self.create_verified_user() UserWallet.objects.create(user=user, points_balance=160) UserWallet.objects.create(user=other_user, points_balance=999) api_key, _raw_key = ApiKey.create_for_user(user, name="desktop") other_key, _other_raw_key = ApiKey.create_for_user(other_user, name="other") order = self.create_recharge_order( user, order_no="R-OWN-PAID-503", amount="20.00", points=200, ) self.create_recharge_order( other_user, order_no="R-OTHER-PAID-503", amount="99.00", points=990, ) PointsLedger.objects.create( user=user, change_type=PointsLedger.ChangeType.RECHARGE, points_delta=200, balance_after=200, ref_order_id=order.id, ) call = self.create_call_record( user, api_key, alias="title-standard", points_cost=50, status=CallRecord.Status.FAILED, ) other_call = self.create_call_record( other_user, other_key, alias="other-alias", points_cost=70, ) PointsLedger.objects.create( user=user, change_type=PointsLedger.ChangeType.CONSUME, points_delta=-50, balance_after=150, ref_call=call, ) PointsLedger.objects.create( user=user, change_type=PointsLedger.ChangeType.REFUND, points_delta=10, balance_after=160, ref_call=call, ) PointsLedger.objects.create( user=other_user, change_type=PointsLedger.ChangeType.CONSUME, points_delta=-70, balance_after=929, ref_call=other_call, ) self.client.force_login(user) response = self.client.get("/dashboard") self.assertEqual(response.status_code, 200) self.assertEqual(response.context["balance"].points_balance, 160) self.assertEqual(response.context["balance"].ledger_balance, 160) self.assertEqual(response.context["recharge_total_amount"], Decimal("20.00")) self.assertEqual(response.context["recharge_points_total"], 200) self.assertEqual(response.context["consumed_points_total"], 50) self.assertEqual(response.context["refunded_points_total"], 10) self.assertEqual(response.context["net_used_points"], 40) self.assertContains(response, "R-OWN-PAID-503") self.assertContains(response, "title-standard") self.assertNotContains(response, "R-OTHER-PAID-503") self.assertNotContains(response, "other-alias") def test_recharge_records_require_login_and_only_show_current_user_orders(self): user = self.create_verified_user() other_user = self.create_verified_user() paid_order = self.create_recharge_order( user, order_no="R-OWN-RECHARGE-503", amount="30.00", points=300, ) self.create_recharge_order( user, order_no="R-OWN-PENDING-503", amount="88.00", points=880, status=RechargeOrder.Status.PENDING, ) self.create_recharge_order( other_user, order_no="R-OTHER-RECHARGE-503", amount="99.00", points=990, ) PointsLedger.objects.create( user=user, change_type=PointsLedger.ChangeType.RECHARGE, points_delta=300, balance_after=300, ref_order_id=paid_order.id, ) anonymous_response = self.client.get("/records/recharge") self.assertEqual(anonymous_response.status_code, 302) self.assertTrue(anonymous_response["Location"].startswith("/login?next=")) self.client.force_login(user) response = self.client.get("/records/recharge") self.assertEqual(response.status_code, 200) self.assertEqual(response.context["recharge_total_amount"], Decimal("30.00")) self.assertEqual(response.context["recharge_points_total"], 300) self.assertEqual(response.context["paid_recharge_count"], 1) self.assertContains(response, "R-OWN-RECHARGE-503") self.assertContains(response, "R-OWN-PENDING-503") self.assertNotContains(response, "R-OTHER-RECHARGE-503") def test_recharge_records_paginate_more_than_page_size(self): user = self.create_verified_user() other_user = self.create_verified_user() self.create_recharge_order( other_user, order_no="R-PAGE-OTHER-505", amount="99.00", points=990, ) for index in range(55): self.create_recharge_order( user, order_no=f"R-PAGE-{index:03d}", amount="1.00", points=10, ) self.client.force_login(user) first_page = self.client.get("/records/recharge") second_page = self.client.get("/records/recharge?page=2") self.assertEqual(first_page.status_code, 200) self.assertEqual(first_page.context["records_total_count"], 55) self.assertEqual(first_page.context["page_obj"].number, 1) self.assertEqual(len(first_page.context["recharge_orders"]), 50) self.assertContains(first_page, "共 55 条,每页 50 条") self.assertContains(first_page, "R-PAGE-054") self.assertNotContains(first_page, "R-PAGE-000") self.assertNotContains(first_page, "R-PAGE-OTHER-505") self.assertEqual(second_page.status_code, 200) self.assertEqual(second_page.context["page_obj"].number, 2) self.assertEqual(len(second_page.context["recharge_orders"]), 5) self.assertContains(second_page, "R-PAGE-000") self.assertNotContains(second_page, "R-PAGE-054") self.assertNotContains(second_page, "R-PAGE-OTHER-505") def test_usage_records_require_login_and_only_show_current_user_usage(self): user = self.create_verified_user() other_user = self.create_verified_user() api_key, _raw_key = ApiKey.create_for_user(user, name="desktop") other_key, _other_raw_key = ApiKey.create_for_user(other_user, name="other") call = self.create_call_record( user, api_key, alias="title-standard", points_cost=40, status=CallRecord.Status.FAILED, ) other_call = self.create_call_record( other_user, other_key, alias="other-alias", points_cost=90, ) PointsLedger.objects.create( user=user, change_type=PointsLedger.ChangeType.CONSUME, points_delta=-40, balance_after=60, ref_call=call, ) PointsLedger.objects.create( user=user, change_type=PointsLedger.ChangeType.REFUND, points_delta=15, balance_after=75, ref_call=call, ) PointsLedger.objects.create( user=other_user, change_type=PointsLedger.ChangeType.CONSUME, points_delta=-90, balance_after=10, ref_call=other_call, ) anonymous_response = self.client.get("/records/usage") self.assertEqual(anonymous_response.status_code, 302) self.assertTrue(anonymous_response["Location"].startswith("/login?next=")) self.client.force_login(user) response = self.client.get("/records/usage") self.assertEqual(response.status_code, 200) self.assertEqual(response.context["consumed_points_total"], 40) self.assertEqual(response.context["refunded_points_total"], 15) self.assertEqual(response.context["net_used_points"], 25) self.assertContains(response, "title-standard") self.assertContains(response, api_key.key_prefix) self.assertContains(response, "-40") self.assertContains(response, "15") self.assertNotContains(response, "other-alias") self.assertNotContains(response, other_key.key_prefix) def test_usage_records_paginate_more_than_page_size(self): user = self.create_verified_user() other_user = self.create_verified_user() api_key, _raw_key = ApiKey.create_for_user(user, name="desktop") other_key, _other_raw_key = ApiKey.create_for_user(other_user, name="other") other_call = self.create_call_record( other_user, other_key, alias="other-page-alias", points_cost=99, ) PointsLedger.objects.create( user=other_user, change_type=PointsLedger.ChangeType.CONSUME, points_delta=-99, balance_after=0, ref_call=other_call, ) for index in range(55): call = self.create_call_record( user, api_key, alias=f"title-page-{index:03d}", points_cost=1, ) PointsLedger.objects.create( user=user, change_type=PointsLedger.ChangeType.CONSUME, points_delta=-1, balance_after=100 - index, ref_call=call, ) self.client.force_login(user) first_page = self.client.get("/records/usage") second_page = self.client.get("/records/usage?page=2") self.assertEqual(first_page.status_code, 200) self.assertEqual(first_page.context["records_total_count"], 55) self.assertEqual(first_page.context["page_obj"].number, 1) self.assertEqual(len(first_page.context["usage_entries"]), 50) self.assertContains(first_page, "共 55 条,每页 50 条") self.assertContains(first_page, "title-page-054") self.assertNotContains(first_page, "title-page-000") self.assertNotContains(first_page, "other-page-alias") self.assertEqual(second_page.status_code, 200) self.assertEqual(second_page.context["page_obj"].number, 2) self.assertEqual(len(second_page.context["usage_entries"]), 5) self.assertContains(second_page, "title-page-000") self.assertNotContains(second_page, "title-page-054") self.assertNotContains(second_page, "other-page-alias") def test_recharge_page_requires_login_and_shows_form(self): user = self.create_verified_user() UserWallet.objects.create(user=user, points_balance=25) anonymous_response = self.client.get("/recharge") self.assertEqual(anonymous_response.status_code, 302) self.assertTrue(anonymous_response["Location"].startswith("/login?next=")) self.client.force_login(user) response = self.client.get("/recharge") self.assertEqual(response.status_code, 200) self.assertEqual(response.context["balance"].points_balance, 25) self.assertContains(response, "充值金额") self.assertContains(response, "创建订单") self.assertContains(response, "微信") self.assertNotContains(response, "支付宝") self.assertContains(response, "portal/vendor/bootstrap/bootstrap.min.css") self.assertContains(response, "portal/vendor/qrcode/qrcode.js") self.assertContains(response, "QRCode.toCanvas") self.assertNotContains(response, "cdn.jsdelivr.net") self.assertIsNotNone( finders.find("portal/vendor/bootstrap/bootstrap.min.css") ) self.assertIsNotNone(finders.find("portal/vendor/qrcode/qrcode.js")) @override_settings(PAYMENT_CALLBACK_MODE="mock") def test_recharge_page_post_creates_pending_order_without_crediting_wallet_or_ledger(self): user = self.create_verified_user() UserWallet.objects.create(user=user, points_balance=5) ExchangeRate.objects.create( currency="CNY", points_per_unit=Decimal("10.0000"), effective_from=timezone.now(), ) self.client.force_login(user) response = self.client.post( "/recharge", {"amount": "20.00", "pay_method": RechargeOrder.PayMethod.WEIXIN}, ) self.assertEqual(response.status_code, 302) self.assertTrue(response["Location"].startswith("/recharge?order_no=")) order = RechargeOrder.objects.get(user=user) self.assertEqual(order.amount_money, Decimal("20.00")) self.assertEqual(order.exchange_rate, Decimal("10.0000")) self.assertEqual(order.points_granted, 200) self.assertEqual(order.status, RechargeOrder.Status.PENDING) self.assertTrue(order.code_url.startswith("weixin://wxpay/cmhub-mock")) self.assertIsNotNone(order.expires_at) wallet = UserWallet.objects.get(user=user) self.assertEqual(wallet.points_balance, 5) self.assertFalse( PointsLedger.objects.filter( user=user, ref_order_id=order.id, ).exists() ) follow_response = self.client.get(response["Location"]) self.assertEqual(follow_response.status_code, 200) self.assertEqual(follow_response.context["current_order"], order) self.assertContains(follow_response, order.order_no) self.assertContains(follow_response, "weixin://wxpay/cmhub-mock") self.assertTrue(follow_response.context["is_mock_payment_mode"]) self.assertContains(follow_response, "当前为支付测试模式") self.assertContains(follow_response, "不能使用微信或支付宝真实付款") self.assertContains(follow_response, 'data-status-url="/api/v1/recharge/status"') self.assertContains(follow_response, "data-recharge-order") @override_settings(PAYMENT_CALLBACK_MODE="sdk") def test_recharge_page_does_not_show_mock_notice_in_sdk_mode(self): user = self.create_verified_user() UserWallet.objects.create(user=user, points_balance=0) order = self.create_recharge_order( user, order_no="R-SDK-RECHARGE-PAGE-504", amount="20.00", points=200, status=RechargeOrder.Status.PENDING, ) order.code_url = "weixin://wxpay/bizpayurl?pr=real-native-ticket" order.save(update_fields=("code_url", "updated_at")) self.client.force_login(user) response = self.client.get(f"/recharge?order_no={order.order_no}") self.assertEqual(response.status_code, 200) self.assertFalse(response.context["is_mock_payment_mode"]) self.assertContains(response, "weixin://wxpay/bizpayurl") self.assertNotContains(response, "当前为支付测试模式") self.assertNotContains(response, "不能使用微信或支付宝真实付款") def test_recharge_page_current_order_only_shows_current_user_order(self): user = self.create_verified_user() other_user = self.create_verified_user() UserWallet.objects.create(user=user, points_balance=0) other_order = self.create_recharge_order( other_user, order_no="R-OTHER-RECHARGE-PAGE-504", amount="50.00", points=500, status=RechargeOrder.Status.PENDING, ) self.client.force_login(user) response = self.client.get(f"/recharge?order_no={other_order.order_no}") self.assertEqual(response.status_code, 200) self.assertIsNone(response.context["current_order"]) self.assertNotContains(response, other_order.order_no) @override_settings(RECHARGE_MAX_AMOUNT_CNY="100.00") def test_recharge_page_rejects_amount_above_configured_maximum(self): user = self.create_verified_user() UserWallet.objects.create(user=user, points_balance=0) ExchangeRate.objects.create( currency="CNY", points_per_unit=Decimal("10.0000"), effective_from=timezone.now(), ) self.client.force_login(user) response = self.client.post( "/recharge", {"amount": "100.01", "pay_method": RechargeOrder.PayMethod.WEIXIN}, ) self.assertEqual(response.status_code, 200) self.assertContains(response, "单笔充值金额不能超过 100.00 CNY") self.assertFalse(RechargeOrder.objects.filter(user=user).exists()) def test_recharge_page_rejects_hidden_alipay_submit(self): user = self.create_verified_user() UserWallet.objects.create(user=user, points_balance=0) ExchangeRate.objects.create( currency="CNY", points_per_unit=Decimal("10.0000"), effective_from=timezone.now(), ) self.client.force_login(user) response = self.client.post( "/recharge", {"amount": "20.00", "pay_method": RechargeOrder.PayMethod.ALIPAY}, ) self.assertEqual(response.status_code, 200) self.assertContains(response, "选择一个有效的选项") self.assertFalse(RechargeOrder.objects.filter(user=user).exists()) def test_recharge_page_post_is_csrf_protected(self): user = self.create_verified_user() csrf_client = Client(enforce_csrf_checks=True) csrf_client.force_login(user) response = csrf_client.post( "/recharge", {"amount": "20.00", "pay_method": RechargeOrder.PayMethod.WEIXIN}, ) self.assertEqual(response.status_code, 403) self.assertFalse(RechargeOrder.objects.filter(user=user).exists())