| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- from datetime import UTC, datetime
- from fastapi.testclient import TestClient
- from zbt.commands.seed import build_seed_manifest
- from zbt.core.config import Settings
- from zbt.core.passwords import PasswordService
- from zbt.domains.catalog.repository import InMemoryCatalogRepository
- from zbt.domains.enrollment.repository import InMemoryEnrollmentRepository
- from zbt.domains.identity.repository import InMemoryIdentityRepository
- from zbt.main import create_app
- def build_test_app():
- catalog = InMemoryCatalogRepository()
- identities = InMemoryIdentityRepository()
- manifest = build_seed_manifest(PasswordService().hash("zaq1XSW@"))
- for admin in manifest.admin_users:
- identities.save_admin_user(admin)
- for product in manifest.products:
- catalog.save_product(product)
- for version in manifest.versions:
- catalog.save_version(version)
- return create_app(
- settings=Settings(
- app_env="test",
- jwt_access_secret="a" * 32,
- jwt_refresh_secret="b" * 32,
- field_encryption_key="c" * 32,
- ),
- identity_repository=identities,
- catalog_repository=catalog,
- enrollment_repository=InMemoryEnrollmentRepository(),
- clock=lambda: datetime(2026, 7, 26, tzinfo=UTC),
- )
- def login_headers(client: TestClient) -> dict[str, str]:
- token = client.post(
- "/api/v1/h5/auth/login",
- json={"mobile": "18800000001", "code": "147258"},
- ).json()["data"]["tokens"]["access_token"]
- return {"Authorization": f"Bearer {token}"}
- def test_eligible_customer_receives_deterministic_quote() -> None:
- with TestClient(build_test_app()) as client:
- headers = login_headers(client)
- product = next(
- item
- for item in client.get("/api/v1/h5/products", headers=headers).json()["data"]["items"]
- if item["product_code"] == "MED-SENIOR"
- )
- response = client.post(
- "/api/v1/h5/quotes",
- headers=headers,
- json={
- "product_id": product["product_id"],
- "plan_id": product["plans"][0]["id"],
- "insured": {
- "age": 65,
- "region_code": "510100",
- "occupation_code": "GENERAL",
- },
- "relationship": "PARENT",
- },
- )
- assert response.status_code == 201
- assert response.json()["data"]["eligible"] is True
- assert response.json()["data"]["premium_cents"] == 19900
- assert response.json()["data"]["rate_version"] == "rate-v1"
- def test_confirmed_draft_creates_one_idempotent_order() -> None:
- with TestClient(build_test_app()) as client:
- headers = login_headers(client)
- product = next(
- item
- for item in client.get("/api/v1/h5/products", headers=headers).json()["data"]["items"]
- if item["product_code"] == "MED-SENIOR"
- )
- quote_id = client.post(
- "/api/v1/h5/quotes",
- headers=headers,
- json={
- "product_id": product["product_id"],
- "plan_id": product["plans"][0]["id"],
- "insured": {
- "age": 65,
- "region_code": "510100",
- "occupation_code": "GENERAL",
- },
- "relationship": "PARENT",
- },
- ).json()["data"]["quote_id"]
- draft = client.post(
- "/api/v1/h5/enrollment-drafts",
- headers=headers,
- json={
- "quote_id": quote_id,
- "applicant": {"name": "张三", "id_no": "510100199001010001"},
- "insured": {"name": "张父", "id_no": "510100196101010001"},
- "contact": {"mobile": "18800000001"},
- },
- )
- confirmation = client.post(
- f"/api/v1/h5/enrollment-drafts/{draft.json()['data']['draft_id']}/confirmation",
- headers=headers,
- )
- order_headers = {
- **headers,
- "Idempotency-Key": "order-client-001",
- }
- first = client.post(
- "/api/v1/h5/orders",
- headers=order_headers,
- json={
- "draft_id": draft.json()["data"]["draft_id"],
- "confirmation_token": confirmation.json()["data"]["confirmation_token"],
- },
- )
- repeated = client.post(
- "/api/v1/h5/orders",
- headers=order_headers,
- json={
- "draft_id": draft.json()["data"]["draft_id"],
- "confirmation_token": confirmation.json()["data"]["confirmation_token"],
- },
- )
- assert draft.status_code == 201
- assert confirmation.status_code == 201
- assert first.status_code == 201
- assert first.json()["data"]["status"] == "PENDING_PAYMENT"
- assert first.json()["data"]["amount_cents"] == 19900
- assert repeated.status_code == 200
- assert repeated.json()["data"]["order_id"] == first.json()["data"]["order_id"]
- def test_mock_payment_callback_is_idempotent_and_issues_one_policy() -> None:
- with TestClient(build_test_app()) as client:
- headers = login_headers(client)
- product = next(
- item
- for item in client.get("/api/v1/h5/products", headers=headers).json()["data"]["items"]
- if item["product_code"] == "MED-SENIOR"
- )
- quote_id = client.post(
- "/api/v1/h5/quotes",
- headers=headers,
- json={
- "product_id": product["product_id"],
- "plan_id": product["plans"][0]["id"],
- "insured": {
- "age": 65,
- "region_code": "510100",
- "occupation_code": "GENERAL",
- },
- "relationship": "PARENT",
- },
- ).json()["data"]["quote_id"]
- draft_id = client.post(
- "/api/v1/h5/enrollment-drafts",
- headers=headers,
- json={
- "quote_id": quote_id,
- "applicant": {"name": "张三", "id_no": "510100199001010001"},
- "insured": {"name": "张父", "id_no": "510100196101010001"},
- "contact": {"mobile": "18800000001"},
- },
- ).json()["data"]["draft_id"]
- confirmation_token = client.post(
- f"/api/v1/h5/enrollment-drafts/{draft_id}/confirmation",
- headers=headers,
- ).json()["data"]["confirmation_token"]
- order = client.post(
- "/api/v1/h5/orders",
- headers={**headers, "Idempotency-Key": "order-client-002"},
- json={
- "draft_id": draft_id,
- "confirmation_token": confirmation_token,
- },
- ).json()["data"]
- payment = client.post(
- f"/api/v1/h5/orders/{order['order_id']}/payments",
- headers={**headers, "Idempotency-Key": "payment-client-001"},
- )
- invalid_callback = client.post(
- "/api/v1/callbacks/mock-payment",
- headers={"X-Mock-Pay-Signature": "invalid-signature"},
- json={
- "callback_no": "CB-INVALID-001",
- "provider_transaction_no": "MP-INVALID-001",
- "payment_no": payment.json()["data"]["payment_no"],
- "status": "SUCCEEDED",
- "amount_cents": 19900,
- "occurred_at": "2026-07-25T08:30:00.000Z",
- },
- )
- first_callback = client.post(
- f"/api/v1/dev/mock-payments/{payment.json()['data']['payment_id']}/complete",
- headers=headers,
- )
- repeated_callback = client.post(
- f"/api/v1/dev/mock-payments/{payment.json()['data']['payment_id']}/complete",
- headers=headers,
- )
- policies = client.get("/api/v1/h5/policies", headers=headers)
- admin_token = client.post(
- "/api/v1/admin/auth/login",
- json={"username": "admin", "password": "zaq1XSW@"},
- ).json()["data"]["tokens"]["access_token"]
- admin_dashboard = client.get(
- "/api/v1/admin/dashboard",
- headers={"Authorization": f"Bearer {admin_token}"},
- )
- admin_orders = client.get(
- "/api/v1/admin/orders",
- headers={"Authorization": f"Bearer {admin_token}"},
- )
- assert payment.status_code == 201
- assert invalid_callback.status_code == 401
- assert first_callback.status_code == 200
- assert first_callback.json()["data"]["payment_status"] == "SUCCEEDED"
- assert first_callback.json()["data"]["order_status"] == "ISSUED"
- assert first_callback.json()["data"]["policy"]["status"] == "ACTIVE"
- assert (
- repeated_callback.json()["data"]["policy"]["policy_id"]
- == (first_callback.json()["data"]["policy"]["policy_id"])
- )
- assert policies.json()["data"]["total"] == 1
- policy = policies.json()["data"]["items"][0]
- assert policy["product_name"] == "银龄守护医疗险"
- assert policy["plan_name"] == "银龄标准计划"
- assert policy["relationship"] == "PARENT"
- assert policy["order_no"] == order["order_no"]
- assert policy["applicant"]["name"] == "张三"
- assert policy["insured"]["name"] == "张父"
- assert admin_dashboard.status_code == 200
- assert admin_dashboard.json()["data"]["order_count"] == 1
- assert admin_dashboard.json()["data"]["policy_count"] == 1
- admin_order = admin_orders.json()["data"]["items"][0]
- assert admin_order["product_name"] == "银龄守护医疗险"
- assert admin_order["plan_name"] == "银龄标准计划"
- assert admin_order["relationship"] == "PARENT"
- assert admin_order["applicant"]["name"] == "张三"
- assert admin_order["contact"]["mobile"] == "18800000001"
- assert admin_order["payment"]["status"] == "SUCCEEDED"
- assert admin_order["policy_no"] == policy["policy_no"]
- def test_super_admin_can_update_role_permissions() -> None:
- with TestClient(build_test_app()) as client:
- admin_token = client.post(
- "/api/v1/admin/auth/login",
- json={"username": "admin", "password": "zaq1XSW@"},
- ).json()["data"]["tokens"]["access_token"]
- headers = {"Authorization": f"Bearer {admin_token}"}
- roles = client.get("/api/v1/admin/roles", headers=headers)
- update = client.put(
- "/api/v1/admin/roles/OPERATOR",
- headers=headers,
- json={
- "permissions": [
- "dashboard:read",
- "order:read",
- "policy:read",
- "role:read",
- ],
- "data_scope": "MASKED_ALL",
- },
- )
- protected = client.put(
- "/api/v1/admin/roles/SUPER_ADMIN",
- headers=headers,
- json={"permissions": [], "data_scope": "SELF"},
- )
- operator_login = client.post(
- "/api/v1/admin/auth/login",
- json={"username": "operator01", "password": "zaq1XSW@"},
- )
- assert roles.status_code == 200
- assert len(roles.json()["data"]["items"]) == 4
- assert update.status_code == 200
- assert update.json()["data"]["permissions"] == [
- "dashboard:read",
- "order:read",
- "policy:read",
- "role:read",
- ]
- assert protected.status_code == 409
- assert operator_login.json()["data"]["user"]["permissions"] == [
- "dashboard:read",
- "order:read",
- "policy:read",
- "role:read",
- ]
- def test_super_admin_can_maintain_admin_accounts_and_customers() -> None:
- with TestClient(build_test_app()) as client:
- customer_login = client.post(
- "/api/v1/h5/auth/login",
- json={"mobile": "18800000001", "code": "147258"},
- )
- customer_id = customer_login.json()["data"]["user"]["id"]
- admin_token = client.post(
- "/api/v1/admin/auth/login",
- json={"username": "admin", "password": "zaq1XSW@"},
- ).json()["data"]["tokens"]["access_token"]
- headers = {"Authorization": f"Bearer {admin_token}"}
- created = client.post(
- "/api/v1/admin/users/admins",
- headers=headers,
- json={
- "username": "service01",
- "password": "Initial1!",
- "display_name": "客服专员",
- "role_code": "OPERATOR",
- },
- )
- created_user = created.json()["data"]
- updated = client.put(
- f"/api/v1/admin/users/admins/{created_user['id']}",
- headers=headers,
- json={
- "display_name": "客服主管",
- "status": "ACTIVE",
- "role_code": "REVIEWER",
- },
- )
- password_reset = client.post(
- f"/api/v1/admin/users/admins/{created_user['id']}/password",
- headers=headers,
- json={"new_password": "Changed1!"},
- )
- customer_disabled = client.put(
- f"/api/v1/admin/users/customers/{customer_id}/status",
- headers=headers,
- json={"status": "DISABLED"},
- )
- users = client.get("/api/v1/admin/users", headers=headers)
- new_login = client.post(
- "/api/v1/admin/auth/login",
- json={"username": "service01", "password": "Changed1!"},
- )
- disabled_customer_login = client.post(
- "/api/v1/h5/auth/login",
- json={"mobile": "18800000001", "code": "147258"},
- )
- assert created.status_code == 201
- assert updated.status_code == 200
- assert updated.json()["data"]["display_name"] == "客服主管"
- assert updated.json()["data"]["roles"] == ["REVIEWER"]
- assert password_reset.status_code == 200
- assert customer_disabled.json()["data"]["status"] == "DISABLED"
- assert len(users.json()["data"]["admin_users"]) == 10
- assert len(users.json()["data"]["customers"]) == 1
- assert new_login.status_code == 200
- assert disabled_customer_login.status_code == 403
|