""" 资产总览功能 - 全面测试脚本 测试覆盖:账户 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)