test_uniapp_contract.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. # -*- coding: utf-8 -*-
  2. """uniapp 客户端契约测试:按 uniapp 的真实提交/解包结构验证关键链路。
  3. 与 test_api_batch*.py 的区别:那些脚本用平铺 JSON,这里用 uniapp 实际发出的
  4. `{form: {...}}` 包裹结构,并覆盖客户端对响应外层键的硬依赖。
  5. 覆盖:
  6. 1. /api/setting/data 必须带 setting 包裹(登录页 SettingModel 读 result.data.setting)
  7. 2. 图形验证码 → 发送短信 → 手机号登录(全部用 {form: ...} 包裹)
  8. 3. 登录后的 token 能否访问 /api/user/info
  9. 前置:Java 服务运行在 18080,Redis / MySQL 可连(读 application.yml 取密码)。
  10. """
  11. import json
  12. import re
  13. import socket
  14. import sys
  15. import requests
  16. import pymysql
  17. from _root import ROOT
  18. from redis_probe import cmd
  19. BASE = "http://127.0.0.1:18080"
  20. MOBILE = "13900002222"
  21. YML = (ROOT / "yoshop-java/src/main/resources/application.yml").read_text(encoding="utf-8")
  22. DB_PWD = re.findall(r"password:\s*(.+)", YML)[0].strip().strip("\"'")
  23. REDIS_PWD = re.findall(r"password:\s*(.+)", YML)[1].strip().strip("\"'")
  24. S = requests.Session()
  25. S.trust_env = False
  26. passed = failed = 0
  27. def check(name, cond, detail=""):
  28. global passed, failed
  29. if cond:
  30. passed += 1
  31. print(f" PASS {name}")
  32. else:
  33. failed += 1
  34. print(f" FAIL {name} {detail}")
  35. def redis_cmd(*args):
  36. sock = socket.create_connection(("192.168.1.177", 6379), timeout=5)
  37. try:
  38. cmd(sock, "AUTH", REDIS_PWD)
  39. return cmd(sock, *args)
  40. finally:
  41. sock.close()
  42. def main():
  43. # 1. 设置项结构
  44. r = S.get(BASE + "/api/setting/data", timeout=10).json()
  45. setting = (r.get("data") or {}).get("setting")
  46. check("setting/data 有 setting 包裹", isinstance(setting, dict), list(r.get("data") or {}))
  47. check("setting.register 存在", isinstance((setting or {}).get("register"), dict),
  48. (setting or {}).get("register"))
  49. check("registerMethod 有值", (setting or {}).get("register", {}).get("registerMethod") == 10,
  50. (setting or {}).get("register"))
  51. # 2. 图形验证码
  52. r = S.get(BASE + "/api/captcha/image", timeout=10).json()
  53. data = r.get("data") or {}
  54. code = json.loads(redis_cmd("GET", "yoshop:api:captcha:" + str(data.get("key"))))["code"]
  55. check("验证码返回 key/base64", bool(data.get("key")) and str(data.get("base64", "")).startswith("data:image"))
  56. # 3. 发送短信(uniapp 结构:{form: {...}})
  57. r = S.post(BASE + "/api/captcha/sendSmsCaptcha",
  58. json={"form": {"mobile": MOBILE, "captchaCode": code, "captchaKey": data.get("key")}},
  59. timeout=10).json()
  60. check("form 包裹的发送短信成功", r.get("status") == 200, r)
  61. sms = json.loads(redis_cmd("GET", "yoshop:api:sms:" + MOBILE))["code"]
  62. # 4. 登录(uniapp 结构)
  63. r = S.post(BASE + "/api/passport/login",
  64. json={"form": {"mobile": MOBILE, "smsCode": sms, "isParty": False, "partyData": {}}},
  65. timeout=10).json()
  66. d = r.get("data") or {}
  67. check("form 包裹的登录成功", r.get("status") == 200 and r.get("message") == "登录成功", r)
  68. check("返回 userId/token", isinstance(d.get("userId"), int)
  69. and re.fullmatch(r"[0-9a-f]{32}", str(d.get("token"))) is not None, d)
  70. # 5. token 访问用户中心
  71. token = d.get("token")
  72. r = S.get(BASE + "/api/user/info", headers={"Access-Token": token}, timeout=10).json()
  73. info = (r.get("data") or {}).get("userInfo") or {}
  74. check("user/info 可访问", r.get("status") == 200, r)
  75. check("userInfo 有 userId/nickName", info.get("userId") == d.get("userId"), list(info)[:8])
  76. # 清理:删掉本次登录自动注册的用户与临时键
  77. conn = pymysql.connect(host="192.168.1.177", user="root", password=DB_PWD,
  78. database="shop", charset="utf8mb4", autocommit=True)
  79. try:
  80. cur = conn.cursor()
  81. cur.execute("select user_id from yoshop_user where mobile=%s", (MOBILE,))
  82. row = cur.fetchone()
  83. if row:
  84. cur.execute("delete from yoshop_user where user_id=%s", (row[0],))
  85. print(f" CLEAN 已删除测试用户 {row[0]}")
  86. finally:
  87. conn.close()
  88. for key in [f"yoshop:api:sms:{MOBILE}", f"yoshop:api:sms:send:{MOBILE}",
  89. "yoshop:api:token:" + str(token)]:
  90. redis_cmd("DEL", key)
  91. print(f"\n结果:PASS {passed} / FAIL {failed}")
  92. return 0 if failed == 0 else 1
  93. if __name__ == "__main__":
  94. sys.exit(main())