"""用户端 Web 自助入口页面测试(T12.2/T12.3)。""" from __future__ import annotations from fastapi.testclient import TestClient from data.orders.dao import OrdersDAO def test_public_landing_page_served(client): resp = client.get("/") assert resp.status_code == 200 assert "text/html" in resp.headers["content-type"] body = resp.text assert "高考志愿填报智能系统" in body assert "用户端 Web 自助服务" in body assert 'href="/pricing"' in body assert "49元 AI方案审核" in body def test_public_pricing_page_served(client): resp = client.get("/pricing") assert resp.status_code == 200 assert "text/html" in resp.headers["content-type"] body = resp.text assert "服务套餐" in body assert "49元 AI方案审核" in body assert "99元 完整志愿方案" in body assert "199元 深度辅导版" in body assert 'data-package="audit"' in body assert 'data-package="standard"' in body assert 'data-package="premium"' in body assert "支付接入建设中" in body def test_public_create_order_endpoint(client): resp = client.post( "/api/public/orders", json={ "service_version": "standard", "amount_cents": 9900, "customer_name": "张家长", "customer_phone": "13800138000", "customer_email": "parent@example.com", "candidate_name": "张三", "candidate_province": "湖南", }, ) assert resp.status_code == 201, resp.text body = resp.json() assert body["order_id"].startswith("GKO-") assert body["source"] == "web" assert body["status"] == "pending" assert body["service_version"] == "standard" assert body["amount_cents"] == 9900 assert body["next_step"] == "payment" assert body["checkout_url"].startswith("/pay/mock/") assert "/portal/" in body["portal_status_url"] with OrdersDAO.connect(client.app.state.settings.orders_db_path) as dao: created = dao.get(body["order_id"]) assert created.customer_email == "parent@example.com" def test_public_create_order_rejects_missing_contact(client): resp = client.post( "/api/public/orders", json={ "service_version": "audit", "amount_cents": 4900, "customer_name": "李家长", "candidate_province": "广东", }, ) assert resp.status_code == 422 body = resp.json() assert body["message"] == "请求数据未通过校验" assert ( body["detail"]["fields"][0]["reason"] == "Value error, customer_phone / customer_wechat 至少填写一个" ) def test_public_create_order_rejects_price_tampering(client): resp = client.post( "/api/public/orders", json={ "service_version": "standard", "amount_cents": 1, "customer_name": "张家长", "customer_phone": "13800138000", "customer_email": "parent@example.com", "candidate_province": "湖南", }, ) assert resp.status_code == 422 body = resp.json() assert body["message"] == "请求数据未通过校验" assert ( body["detail"]["fields"][0]["reason"] == "Value error, amount_cents 与套餐价格不一致" ) def test_mock_payment_complete_rejects_wrong_portal_token(client): create_resp = client.post( "/api/public/orders", json={ "service_version": "standard", "amount_cents": 9900, "customer_name": "张家长", "customer_phone": "13800138000", "customer_email": "parent@example.com", "candidate_province": "湖南", }, ) assert create_resp.status_code == 201, create_resp.text body = create_resp.json() payment_id = body["checkout_url"].split("/pay/mock/")[1].split("?")[0] resp = client.post(f"/pay/mock/{payment_id}/complete?token=wrong-token") assert resp.status_code == 401 or resp.status_code == 403 def test_payment_return_redirects_to_portal_status(client): create_resp = client.post( "/api/public/orders", json={ "service_version": "standard", "amount_cents": 9900, "customer_name": "张家长", "customer_phone": "13800138000", "customer_email": "parent@example.com", "candidate_province": "湖南", }, ) assert create_resp.status_code == 201, create_resp.text body = create_resp.json() token = body["portal_status_url"].split("/portal/")[1].split("/status")[0] resp = client.get(f"/portal/payment-return?token={token}", follow_redirects=False) assert resp.status_code == 303, resp.text assert resp.headers["location"] == f"/portal/{token}/status" def test_public_create_order_returns_503_without_creating_orphan_order_when_provider_unavailable( tmp_path, monkeypatch ): admin_db = tmp_path / "admin.db" orders_db = tmp_path / "orders.db" share_db = tmp_path / "share.db" share_reports = tmp_path / "share_reports" share_reports.mkdir() monkeypatch.setenv("GAOKAO_ENV", "dev") monkeypatch.setenv("GAOKAO_DB_PATH", str(admin_db)) monkeypatch.setenv("GAOKAO_ORDERS_DB_PATH", str(orders_db)) monkeypatch.setenv("GAOKAO_SHARE_DB_PATH", str(share_db)) monkeypatch.setenv("GAOKAO_SHARE_REPORT_DIR", str(share_reports)) monkeypatch.setenv("GAOKAO_ORDERS_FERNET_KEY", "test-secret-for-web-self-service") monkeypatch.setenv("GAOKAO_JWT_SECRET", "x" * 64) monkeypatch.setenv("GAOKAO_JWT_EXP_MIN", "5") monkeypatch.setenv("GAOKAO_ADMIN_USER", "admin") monkeypatch.setenv("GAOKAO_ADMIN_PASS", "test-pass-123") monkeypatch.setenv("GAOKAO_PAYMENT_PROVIDER", "alipay") monkeypatch.setenv("GAOKAO_PAYMENT_BASE_URL", "http://testserver") monkeypatch.setenv("GAOKAO_PAYMENT_WEBHOOK_SECRET", "missing-real-provider-env") from admin.app import create_app from admin.config import load_settings settings = load_settings() app = create_app(settings) with TestClient(app) as client: resp = client.post( "/api/public/orders", json={ "service_version": "standard", "amount_cents": 9900, "customer_name": "张家长", "customer_phone": "13800138000", "candidate_province": "湖南", }, ) assert resp.status_code == 503, resp.text assert "payment provider unavailable" in resp.text with OrdersDAO.connect(settings.orders_db_path) as dao: assert dao.count() == 0 def test_public_create_order_returns_503_without_creating_orphan_order_when_checkout_fails( tmp_path, monkeypatch ): admin_db = tmp_path / "admin.db" orders_db = tmp_path / "orders.db" share_db = tmp_path / "share.db" share_reports = tmp_path / "share_reports" share_reports.mkdir() monkeypatch.setenv("GAOKAO_ENV", "dev") monkeypatch.setenv("GAOKAO_DB_PATH", str(admin_db)) monkeypatch.setenv("GAOKAO_ORDERS_DB_PATH", str(orders_db)) monkeypatch.setenv("GAOKAO_SHARE_DB_PATH", str(share_db)) monkeypatch.setenv("GAOKAO_SHARE_REPORT_DIR", str(share_reports)) monkeypatch.setenv("GAOKAO_ORDERS_FERNET_KEY", "test-secret-for-web-self-service") monkeypatch.setenv("GAOKAO_JWT_SECRET", "x" * 64) monkeypatch.setenv("GAOKAO_JWT_EXP_MIN", "5") monkeypatch.setenv("GAOKAO_ADMIN_USER", "admin") monkeypatch.setenv("GAOKAO_ADMIN_PASS", "test-pass-123") monkeypatch.setenv("GAOKAO_PAYMENT_PROVIDER", "mock") monkeypatch.setenv("GAOKAO_PAYMENT_BASE_URL", "http://testserver") monkeypatch.setenv("GAOKAO_PAYMENT_WEBHOOK_SECRET", "test-payment-secret") from admin.app import create_app from admin.config import load_settings from data.payments.service import PaymentError, PaymentService original = PaymentService.create_checkout def _boom(self, order_id: str, *, portal_token: str): raise PaymentError("checkout transport failed") monkeypatch.setattr(PaymentService, "create_checkout", _boom) settings = load_settings() app = create_app(settings) with TestClient(app) as client: resp = client.post( "/api/public/orders", json={ "service_version": "standard", "amount_cents": 9900, "customer_name": "张家长", "customer_phone": "13800138000", "candidate_province": "湖南", }, ) monkeypatch.setattr(PaymentService, "create_checkout", original) assert resp.status_code == 503, resp.text assert "payment checkout unavailable" in resp.text with OrdersDAO.connect(settings.orders_db_path) as dao: assert dao.count() == 0