import pytest from fastapi.testclient import TestClient from sqlalchemy import create_engine, select from sqlalchemy.orm import Session from sqlalchemy.pool import StaticPool from app import main from app.database import Base, get_db from app.models import AdminUser, RefundPaymentAccount ENDPOINT = "/api/admin/refund-payment-accounts" PAYLOAD = dict(name="公司付款账户", alipay_account="payer@example.com", balance="5000.01", single_limit="500.50", is_active=True) @pytest.fixture def setup(): engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool) Base.metadata.create_all(engine) with Session(engine) as db: admin = AdminUser(username="payer-admin", display_name="管理员", password_hash="unused", role_code="super_admin") db.add(admin) db.commit() main.app.dependency_overrides[get_db] = lambda: db main.app.dependency_overrides[main.current_user] = lambda: admin try: with TestClient(main.app, base_url="https://testserver") as client: yield db, client, admin finally: main.app.dependency_overrides.clear() engine.dispose() def test_create_edit_and_persist_cents(setup): db, client, _ = setup response = client.post(ENDPOINT, json={**PAYLOAD, "name": " 公司付款账户 "}) assert response.status_code == 201, response.text record = response.json()["data"] assert record["name"] == "公司付款账户" assert record["balance_fen"] == 500001 assert record["single_limit_fen"] == 50050 response = client.patch(f"{ENDPOINT}/{record['id']}", json={**PAYLOAD, "balance": "0.00", "is_active": False}) assert response.status_code == 200, response.text db.expire_all() saved = db.get(RefundPaymentAccount, record["id"]) assert saved.balance_fen == 0 assert not saved.is_active assert saved.updated_at is not None assert client.get(ENDPOINT).json()["data"]["items"][0]["is_active"] is False def test_duplicate_and_failed_edit_preserve_accounts(setup): db, client, _ = setup first = client.post(ENDPOINT, json=PAYLOAD).json()["data"] second = client.post(ENDPOINT, json={**PAYLOAD, "alipay_account": "second@example.com"}).json()["data"] assert client.post(ENDPOINT, json=PAYLOAD).status_code == 409 assert client.patch(f"{ENDPOINT}/{second['id']}", json=PAYLOAD).status_code == 409 db.expire_all() assert len(db.scalars(select(RefundPaymentAccount)).all()) == 2 assert db.get(RefundPaymentAccount, second["id"]).alipay_account == "second@example.com" assert db.get(RefundPaymentAccount, first["id"]).balance_fen == 500001 def test_search_pagination_and_literal_wildcard(setup): _, client, _ = setup for index in range(3): assert client.post(ENDPOINT, json={**PAYLOAD, "name": f"付款{index}", "alipay_account": f"payer{index}@example.com"}).status_code == 201 data = client.get(ENDPOINT, params={"page": 2, "page_size": 2}).json()["data"] assert data["total"] == 3 assert len(data["items"]) == 1 data = client.get(ENDPOINT, params={"q": "payer1"}).json()["data"] assert data["total"] == 1 assert data["items"][0]["name"] == "付款1" assert client.get(ENDPOINT, params={"q": "%"}).json()["data"]["total"] == 0 @pytest.mark.parametrize("changes", [ {"name": " "}, {"alipay_account": " "}, {"balance": "-1"}, {"balance": "1.001"}, {"single_limit": "0"}, {"single_limit": "10000000"}, {"balance": "NaN"}, {"single_limit": "Infinity"}, ]) def test_reject_invalid_account(setup, changes): _, client, _ = setup response = client.post(ENDPOINT, json={**PAYLOAD, **changes}) assert response.status_code == 422, response.text def test_permissions_and_missing_account(setup): db, client, admin = setup assert client.patch(f"{ENDPOINT}/999", json=PAYLOAD).status_code == 404 admin.role_code = "operator" db.commit() assert client.get(ENDPOINT).status_code == 403 assert client.post(ENDPOINT, json=PAYLOAD).status_code == 403 assert client.patch(f"{ENDPOINT}/999", json=PAYLOAD).status_code == 403 assert client.delete(f"{ENDPOINT}/999").status_code == 403 def test_delete_account_and_preserve_other_accounts(setup): db, client, _ = setup first = client.post(ENDPOINT, json=PAYLOAD).json()["data"] other = client.post(ENDPOINT, json={**PAYLOAD, "alipay_account": "other@example.com"}).json()["data"] response = client.delete(f"{ENDPOINT}/{first['id']}") assert response.status_code == 200, response.text db.expire_all() assert db.get(RefundPaymentAccount, first["id"]) is None assert db.get(RefundPaymentAccount, other["id"]) is not None assert client.get(ENDPOINT).json()["data"]["total"] == 1 assert client.delete(f"{ENDPOINT}/{first['id']}").status_code == 404