Files
ToDoList/tests/test_accounts.py
祀梦 2979197b1c release: Elysia ToDo v1.0.0
鍏ㄦ爤涓汉淇℃伅绠$悊搴旂敤锛岄泦鎴愬緟鍔炰换鍔°€佷範鎯墦鍗°€佺邯蹇垫棩鎻愰啋銆佽祫浜ф€昏鍔熻兘銆

Made-with: Cursor
2026-03-14 22:21:26 +08:00

619 lines
22 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
资产总览功能 - 全面测试脚本
测试覆盖:账户 CRUD、余额更新、变更历史、分期计划 CRUD、还款操作
"""
import requests
import sys
from datetime import date
BASE_URL = "http://localhost:23994/api"
passed = 0
failed = 0
errors = []
def test(name, condition, detail=""):
global passed, failed
if condition:
passed += 1
print(f" [PASS] {name}")
else:
failed += 1
errors.append(name)
print(f" [FAIL] {name} {detail}")
def section(title):
print(f"\n{'='*60}")
print(f" {title}")
print(f"{'='*60}")
# ============================================================
section("1. 账户 CRUD 测试")
# ============================================================
# 1.1 创建存款账户 - 微信
print("\n--- 创建存款账户 ---")
r = requests.post(f"{BASE_URL}/accounts", json={
"name": "微信",
"account_type": "savings",
"balance": 5800.50,
"icon": "wechat",
"color": "#67C23A",
"is_active": True,
"description": "日常零钱"
})
test("创建微信账户", r.status_code == 201, f"status={r.status_code}")
wechat = r.json()
test("微信账户ID存在", wechat.get("id") is not None)
test("微信余额正确", wechat.get("balance") == 5800.50)
test("微信类型正确", wechat.get("account_type") == "savings")
wechat_id = wechat["id"]
# 1.2 创建存款账户 - 支付宝
r = requests.post(f"{BASE_URL}/accounts", json={
"name": "支付宝",
"account_type": "savings",
"balance": 12300.00,
"icon": "alipay",
"color": "#1677FF",
"is_active": True,
"description": "工资卡"
})
test("创建支付宝账户", r.status_code == 201, f"status={r.status_code}")
alipay = r.json()
alipay_id = alipay["id"]
# 1.3 创建存款账户 - 银行卡
r = requests.post(f"{BASE_URL}/accounts", json={
"name": "招商银行",
"account_type": "savings",
"balance": 45600.00,
"icon": "bank",
"color": "#FF6B6B",
"is_active": True
})
test("创建招商银行账户", r.status_code == 201, f"status={r.status_code}")
bank = r.json()
bank_id = bank["id"]
# 1.4 创建欠款账户 - 花呗
r = requests.post(f"{BASE_URL}/accounts", json={
"name": "花呗",
"account_type": "debt",
"balance": 3000.00,
"icon": "credit-card",
"color": "#FFB347",
"is_active": True,
"description": "分3期每月12号还"
})
test("创建花呗账户", r.status_code == 201, f"status={r.status_code}")
huabei = r.json()
huabei_id = huabei["id"]
# 1.5 创建欠款账户 - 白条
r = requests.post(f"{BASE_URL}/accounts", json={
"name": "白条",
"account_type": "debt",
"balance": 2000.00,
"icon": "ticket",
"color": "#E6A23C",
"is_active": True,
"description": "分6期每月15号还"
})
test("创建白条账户", r.status_code == 201, f"status={r.status_code}")
baitiao = r.json()
baitiao_id = baitiao["id"]
# 1.6 创建一个已禁用的账户
r = requests.post(f"{BASE_URL}/accounts", json={
"name": "已注销信用卡",
"account_type": "debt",
"balance": 0,
"icon": "credit-card",
"color": "#909399",
"is_active": False,
"description": "测试禁用状态"
})
test("创建已禁用账户", r.status_code == 201)
disabled_id = r.json()["id"]
# ============================================================
section("2. 获取账户列表测试")
# ============================================================
print("\n--- 获取所有账户 ---")
r = requests.get(f"{BASE_URL}/accounts")
test("获取账户列表 200", r.status_code == 200, f"status={r.status_code}")
accounts = r.json()
test("账户总数正确", len(accounts) >= 5, f"实际数量: {len(accounts)}")
savings = [a for a in accounts if a["account_type"] == "savings" and a["is_active"]]
debt = [a for a in accounts if a["account_type"] == "debt" and a["is_active"]]
test("活跃存款账户数量", len(savings) == 3, f"实际: {len(savings)}")
test("活跃欠款账户数量", len(debt) == 2, f"实际: {len(debt)}")
# 2.1 获取单个账户
print("\n--- 获取单个账户 ---")
r = requests.get(f"{BASE_URL}/accounts/{wechat_id}")
test("获取单个微信账户 200", r.status_code == 200)
test("账户名称正确", r.json().get("name") == "微信")
# 2.2 获取不存在的账户
r = requests.get(f"{BASE_URL}/accounts/99999")
test("获取不存在账户 404", r.status_code == 404, f"status={r.status_code}")
# ============================================================
section("3. 更新账户测试")
# ============================================================
print("\n--- 更新账户信息 ---")
r = requests.put(f"{BASE_URL}/accounts/{wechat_id}", json={
"description": "日常零钱+红包"
})
test("更新微信描述 200", r.status_code == 200, f"status={r.status_code}")
test("描述更新成功", r.json().get("description") == "日常零钱+红包")
# 3.1 确认 balance 字段被忽略
r = requests.put(f"{BASE_URL}/accounts/{wechat_id}", json={
"balance": 99999 # 应该被忽略
})
test("更新忽略 balance", r.json().get("balance") == 5800.50,
f"余额不应被修改, 实际: {r.json().get('balance')}")
# ============================================================
section("4. 余额更新与变更历史测试")
# ============================================================
print("\n--- 更新微信余额 ---")
r = requests.post(f"{BASE_URL}/accounts/{wechat_id}/balance", json={
"new_balance": 6800.50,
"note": "收到红包"
})
test("更新微信余额 200", r.status_code == 200, f"status={r.status_code}")
test("微信余额更新成功", r.json().get("balance") == 6800.50)
print("\n--- 再次更新微信余额 ---")
r = requests.post(f"{BASE_URL}/accounts/{wechat_id}/balance", json={
"new_balance": 5300.50,
"note": "日常消费"
})
test("微信消费后余额", r.json().get("balance") == 5300.50)
print("\n--- 更新支付宝余额 ---")
r = requests.post(f"{BASE_URL}/accounts/{alipay_id}/balance", json={
"new_balance": 15300.00,
"note": "工资到账"
})
test("支付宝工资到账 200", r.status_code == 200)
test("支付宝余额正确", r.json().get("balance") == 15300.00)
print("\n--- 更新招商银行余额 ---")
r = requests.post(f"{BASE_URL}/accounts/{bank_id}/balance", json={
"new_balance": 40000.00,
"note": "取现"
})
test("招商银行取现 200", r.status_code == 200)
print("\n--- 更新花呗余额 ---")
r = requests.post(f"{BASE_URL}/accounts/{huabei_id}/balance", json={
"new_balance": 2500.00,
"note": "提前还款500"
})
test("花呗提前还款 200", r.status_code == 200)
test("花呗余额更新", r.json().get("balance") == 2500.00)
# ============================================================
section("5. 变更历史测试")
# ============================================================
print("\n--- 查看微信变更历史 ---")
r = requests.get(f"{BASE_URL}/accounts/{wechat_id}/history", params={
"page": 1,
"page_size": 10
})
test("获取微信历史 200", r.status_code == 200, f"status={r.status_code}")
history = r.json()
test("历史总数正确", history.get("total") == 2, f"实际: {history.get('total')}")
test("分页参数正确", history.get("page") == 1 and history.get("page_size") == 10)
if history.get("records"):
first_record = history["records"][0]
test("最新记录是消费", first_record.get("change_amount") == -1500.0,
f"实际: {first_record.get('change_amount')}")
test("消费前余额正确", first_record.get("balance_before") == 6800.50)
test("消费后余额正确", first_record.get("balance_after") == 5300.50)
test("消费备注正确", first_record.get("note") == "日常消费")
test("记录有创建时间", first_record.get("created_at") is not None)
print("\n--- 查看支付宝变更历史 ---")
r = requests.get(f"{BASE_URL}/accounts/{alipay_id}/history")
history_alipay = r.json()
test("支付宝历史 200", r.status_code == 200)
test("支付宝历史有记录", history_alipay.get("total") == 1)
print("\n--- 查看从未变更的账户历史 ---")
r = requests.get(f"{BASE_URL}/accounts/{baitiao_id}/history")
test("白条历史 200", r.status_code == 200)
test("白条无变更记录", r.json().get("total") == 0)
# ============================================================
section("6. 分期还款计划 CRUD 测试")
# ============================================================
# 6.1 为花呗创建分期计划3期每月12号每期1000
print("\n--- 创建花呗分期计划 ---")
r = requests.post(f"{BASE_URL}/debt-installments", json={
"account_id": huabei_id,
"total_amount": 3000.00,
"total_periods": 3,
"current_period": 1,
"payment_day": 12,
"payment_amount": 1000.00,
"start_date": "2026-03-12",
"is_completed": False
})
test("创建花呗分期 201", r.status_code == 201, f"status={r.status_code}")
huabei_inst = r.json()
test("花呗分期ID存在", huabei_inst.get("id") is not None)
test("花呗下次还款日期已计算", huabei_inst.get("next_payment_date") is not None)
test("花呗距今天数已计算", huabei_inst.get("days_until_payment") is not None)
test("花呗剩余期数", huabei_inst.get("remaining_periods") == 3)
test("花呗账户名称关联", huabei_inst.get("account_name") == "花呗")
huabei_inst_id = huabei_inst["id"]
# 6.2 为白条创建分期计划6期每月15号每期333.33
print("\n--- 创建白条分期计划 ---")
r = requests.post(f"{BASE_URL}/debt-installments", json={
"account_id": baitiao_id,
"total_amount": 2000.00,
"total_periods": 6,
"current_period": 3,
"payment_day": 15,
"payment_amount": 333.33,
"start_date": "2026-01-15",
"is_completed": False
})
test("创建白条分期 201", r.status_code == 201, f"status={r.status_code}")
baitiao_inst = r.json()
test("白条分期第3期", baitiao_inst.get("current_period") == 3)
test("白条剩余期数", baitiao_inst.get("remaining_periods") == 4)
baitiao_inst_id = baitiao_inst["id"]
# 6.3 验证不能给存款账户创建分期
print("\n--- 验证存款账户不能创建分期 ---")
r = requests.post(f"{BASE_URL}/debt-installments", json={
"account_id": wechat_id,
"total_amount": 1000,
"total_periods": 1,
"current_period": 1,
"payment_day": 1,
"payment_amount": 1000,
"start_date": "2026-04-01",
"is_completed": False
})
test("存款账户不能分期 400", r.status_code == 400, f"status={r.status_code}")
# 6.4 验证不存在的账户
r = requests.post(f"{BASE_URL}/debt-installments", json={
"account_id": 99999,
"total_amount": 1000,
"total_periods": 1,
"current_period": 1,
"payment_day": 1,
"payment_amount": 1000,
"start_date": "2026-04-01",
"is_completed": False
})
test("不存在账户不能分期 404", r.status_code == 404, f"status={r.status_code}")
# ============================================================
section("7. 获取分期计划列表测试")
# ============================================================
print("\n--- 获取所有分期计划 ---")
r = requests.get(f"{BASE_URL}/debt-installments")
test("获取分期列表 200", r.status_code == 200, f"status={r.status_code}")
installments = r.json()
test("分期计划总数 >= 2", len(installments) >= 2, f"实际: {len(installments)}")
# 验证排序:未完成的排前面,临近的排前面
if len(installments) >= 2:
first_active = next((i for i in installments if not i["is_completed"]), None)
test("列表第一个是未完成的", first_active is not None)
if first_active:
test("第一个有还款日期", first_active.get("next_payment_date") is not None)
test("第一个有距今天数", first_active.get("days_until_payment") is not None)
# 验证每个计划都有计算字段
for inst in installments:
test(f"分期#{inst['id']}有计算字段",
inst.get("next_payment_date") is not None and
inst.get("days_until_payment") is not None and
inst.get("remaining_periods") is not None,
f"id={inst['id']}")
# ============================================================
section("8. 更新分期计划测试")
# ============================================================
print("\n--- 更新花呗分期计划 ---")
r = requests.put(f"{BASE_URL}/debt-installments/{huabei_inst_id}", json={
"total_amount": 3500.00,
"payment_amount": 1166.67
})
test("更新花呗分期 200", r.status_code == 200, f"status={r.status_code}")
updated = r.json()
test("花呗总额更新", updated.get("total_amount") == 3500.00)
test("花呗每期金额更新", updated.get("payment_amount") == 1166.67)
# 恢复
requests.put(f"{BASE_URL}/debt-installments/{huabei_inst_id}", json={
"total_amount": 3000.00,
"payment_amount": 1000.00
})
# ============================================================
section("9. 标记还款测试(核心流程)")
# ============================================================
print("\n--- 花呗还款第1期 ---")
r = requests.patch(f"{BASE_URL}/debt-installments/{huabei_inst_id}/pay")
test("花呗还款第1期 200", r.status_code == 200, f"status={r.status_code}")
paid = r.json()
test("当前期数变为2", paid.get("current_period") == 2, f"实际: {paid.get('current_period')}")
test("未完成", paid.get("is_completed") == False)
test("剩余期数变为2", paid.get("remaining_periods") == 2)
print("\n--- 花呗还款第2期 ---")
r = requests.patch(f"{BASE_URL}/debt-installments/{huabei_inst_id}/pay")
test("花呗还款第2期 200", r.status_code == 200)
paid = r.json()
test("当前期数变为3", paid.get("current_period") == 3)
test("剩余期数变为1", paid.get("remaining_periods") == 1)
print("\n--- 花呗还款第3期最后一期---")
r = requests.patch(f"{BASE_URL}/debt-installments/{huabei_inst_id}/pay")
test("花呗还款第3期 200", r.status_code == 200)
paid = r.json()
test("标记为已完成", paid.get("is_completed") == True)
test("当前期数等于总期数", paid.get("current_period") == 3)
test("剩余期数为0", paid.get("remaining_periods") == 0)
print("\n--- 已完成的分期不能继续还款 ---")
r = requests.patch(f"{BASE_URL}/debt-installments/{huabei_inst_id}/pay")
test("已完成分期拒绝还款 400", r.status_code == 400, f"status={r.status_code}")
# ============================================================
section("10. 删除操作测试")
# ============================================================
# 10.1 删除分期计划
print("\n--- 删除白条分期计划 ---")
r = requests.delete(f"{BASE_URL}/debt-installments/{baitiao_inst_id}")
test("删除白条分期 200", r.status_code == 200, f"status={r.status_code}")
# 验证删除后列表中没有了
r = requests.get(f"{BASE_URL}/debt-installments")
ids = [i["id"] for i in r.json()]
test("白条分期已从列表移除", baitiao_inst_id not in ids)
# 10.2 删除已禁用账户
print("\n--- 删除已禁用账户 ---")
r = requests.delete(f"{BASE_URL}/accounts/{disabled_id}")
test("删除已禁用账户 200", r.status_code == 200)
# 10.3 验证删除后
r = requests.get(f"{BASE_URL}/accounts")
ids = [a["id"] for a in r.json()]
test("已禁用账户已移除", disabled_id not in ids)
# 10.4 重新创建一个花呗分期用来测试级联删除
print("\n--- 测试级联删除 ---")
r = requests.post(f"{BASE_URL}/debt-installments", json={
"account_id": huabei_id,
"total_amount": 1000.00,
"total_periods": 1,
"current_period": 1,
"payment_day": 25,
"payment_amount": 1000.00,
"start_date": "2026-03-25",
"is_completed": False
})
cascade_inst_id = r.json()["id"]
r = requests.delete(f"{BASE_URL}/accounts/{huabei_id}")
test("级联删除花呗账户 200", r.status_code == 200)
# 验证分期计划也被删除了
r = requests.get(f"{BASE_URL}/debt-installments")
ids = [i["id"] for i in r.json()]
test("花呗关联分期也被删除", cascade_inst_id not in ids)
# 验证历史记录也被删除
r = requests.get(f"{BASE_URL}/accounts/{huabei_id}/history")
test("花呗账户删除后历史不可访问", r.status_code == 404, f"status={r.status_code}")
# ============================================================
section("11. 边界条件测试")
# ============================================================
print("\n--- 余额更新到0 ---")
r = requests.post(f"{BASE_URL}/accounts/{baitiao_id}/balance", json={
"new_balance": 0,
"note": "还清"
})
test("余额归零 200", r.status_code == 200)
test("余额为0", r.json().get("balance") == 0)
print("\n--- 余额更新到大额 ---")
r = requests.post(f"{BASE_URL}/accounts/{baitiao_id}/balance", json={
"new_balance": 999999.99,
"note": "测试大额"
})
test("大额余额 200", r.status_code == 200)
test("大额余额正确", r.json().get("balance") == 999999.99)
# 恢复
requests.post(f"{BASE_URL}/accounts/{baitiao_id}/balance", json={
"new_balance": 2000.00,
"note": "恢复"
})
print("\n--- 参数校验:缺失必填字段 ---")
r = requests.post(f"{BASE_URL}/accounts", json={
"name": "",
})
test("空名称被拒绝 422", r.status_code == 422, f"status={r.status_code}")
print("\n--- 参数校验:无效类型 ---")
r = requests.post(f"{BASE_URL}/accounts", json={
"name": "测试账户",
"account_type": "invalid_type",
})
test("无效类型被拒绝 422", r.status_code == 422, f"status={r.status_code}")
print("\n--- 分期参数校验 ---")
r = requests.post(f"{BASE_URL}/debt-installments", json={
"account_id": baitiao_id,
"total_amount": 0,
"total_periods": 1,
"current_period": 1,
"payment_day": 1,
"payment_amount": 0,
"start_date": "2026-04-01",
"is_completed": False
})
test("零金额分期被拒绝 422", r.status_code == 422, f"status={r.status_code}")
# ============================================================
section("12. 账户列表的分期信息附加测试")
# ============================================================
print("\n--- 获取白条账户(验证分期信息附加) ---")
# 先给白条创建一个分期
r = requests.post(f"{BASE_URL}/debt-installments", json={
"account_id": baitiao_id,
"total_amount": 2000.00,
"total_periods": 6,
"current_period": 2,
"payment_day": 15,
"payment_amount": 333.33,
"start_date": "2026-01-15",
"is_completed": False
})
test("白条分期重建 201", r.status_code == 201)
r = requests.get(f"{BASE_URL}/accounts")
accounts = r.json()
baitiao_acc = next((a for a in accounts if a["id"] == baitiao_id), None)
test("白条账户有分期附加信息", baitiao_acc is not None and baitiao_acc.get("installments") is not None)
# 存款账户不应有分期信息
wechat_acc = next((a for a in accounts if a["id"] == wechat_id), None)
if wechat_acc:
test("微信账户有分期字段(空列表)", wechat_acc.get("installments") is not None)
test("微信分期列表为空", len(wechat_acc.get("installments", [])) == 0)
# ============================================================
section("13. 历史分页测试")
# ============================================================
print("\n--- 历史分页 ---")
# 先给招商银行做多次变更
for i, (bal, note) in enumerate([
(45000, "存入"),
(42000, "消费"),
(50000, "转入"),
(48000, "理财"),
(52000, "收益"),
]):
requests.post(f"{BASE_URL}/accounts/{bank_id}/balance", json={
"new_balance": bal,
"note": note
})
r = requests.get(f"{BASE_URL}/accounts/{bank_id}/history", params={"page": 1, "page_size": 3})
test("历史分页第1页 200", r.status_code == 200)
page1 = r.json()
test("每页3条", len(page1.get("records", [])) == 3, f"实际: {len(page1.get('records', []))}")
test("总记录6条", page1.get("total") == 6, f"实际: {page1.get('total')}")
r = requests.get(f"{BASE_URL}/accounts/{bank_id}/history", params={"page": 2, "page_size": 3})
test("历史分页第2页 200", r.status_code == 200)
page2 = r.json()
test("第2页3条", len(page2.get("records", [])) == 3, f"实际: {len(page2.get('records', []))}")
# 验证第1页和第2页没有重复
page1_ids = {r["id"] for r in page1["records"]}
page2_ids = {r["id"] for r in page2["records"]}
test("两页记录不重复", len(page1_ids & page2_ids) == 0)
# ============================================================
section("14. 花呗还款日期计算验证")
# ============================================================
print("\n--- 验证还款日期计算 ---")
today = date.today()
r = requests.get(f"{BASE_URL}/debt-installments")
for inst in r.json():
if inst["is_completed"]:
continue
next_date_str = inst.get("next_payment_date")
days_until = inst.get("days_until_payment")
if next_date_str:
next_date = date.fromisoformat(next_date_str)
expected_days = (next_date - today).days
test(f"分期#{inst['id']}天数计算正确", days_until == expected_days,
f"计算: {days_until}, 预期: {expected_days}")
# ============================================================
section("15. 保留测试数据(跳过清理)")
# ============================================================
print("\n--- 保留测试数据供页面展示 ---")
r = requests.get(f"{BASE_URL}/accounts")
remaining_accounts = len(r.json())
test("账户数据已保留", remaining_accounts > 0, f"保留: {remaining_accounts} 个账户")
r = requests.get(f"{BASE_URL}/debt-installments")
remaining_installs = len(r.json())
test("分期数据已保留", remaining_installs >= 0, f"保留: {remaining_installs} 个分期")
# ============================================================
# 最终报告
# ============================================================
print(f"\n{'='*60}")
print(f" 测试报告")
print(f"{'='*60}")
print(f" 通过: {passed}")
print(f" 失败: {failed}")
print(f" 总计: {passed + failed}")
if errors:
print(f"\n 失败项:")
for e in errors:
print(f" - {e}")
print(f"{'='*60}")
if failed > 0:
sys.exit(1)
else:
print("\n 全部测试通过!")
sys.exit(0)