test_uniapp_contract.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  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 redis_probe import cmd
  18. BASE = "http://127.0.0.1:18080"
  19. MOBILE = "13900002222"
  20. YML = open("../yoshop-java/src/main/resources/application.yml", encoding="utf-8").read()
  21. DB_PWD = re.findall(r"password:\s*(.+)", YML)[0].strip().strip("\"'")
  22. REDIS_PWD = re.findall(r"password:\s*(.+)", YML)[1].strip().strip("\"'")
  23. S = requests.Session()
  24. S.trust_env = False
  25. passed = failed = 0
  26. def check(name, cond, detail=""):
  27. global passed, failed
  28. if cond:
  29. passed += 1
  30. print(f" PASS {name}")
  31. else:
  32. failed += 1
  33. print(f" FAIL {name} {detail}")
  34. def redis_cmd(*args):
  35. sock = socket.create_connection(("192.168.1.177", 6379), timeout=5)
  36. try:
  37. cmd(sock, "AUTH", REDIS_PWD)
  38. return cmd(sock, *args)
  39. finally:
  40. sock.close()
  41. def main():
  42. # 1. 设置项结构
  43. r = S.get(BASE + "/api/setting/data", timeout=10).json()
  44. setting = (r.get("data") or {}).get("setting")
  45. check("setting/data 有 setting 包裹", isinstance(setting, dict), list(r.get("data") or {}))
  46. check("setting.register 存在", isinstance((setting or {}).get("register"), dict),
  47. (setting or {}).get("register"))
  48. check("registerMethod 有值", (setting or {}).get("register", {}).get("registerMethod") == 10,
  49. (setting or {}).get("register"))
  50. # 2. 图形验证码
  51. r = S.get(BASE + "/api/captcha/image", timeout=10).json()
  52. data = r.get("data") or {}
  53. code = json.loads(redis_cmd("GET", "yoshop:api:captcha:" + str(data.get("key"))))["code"]
  54. check("验证码返回 key/base64", bool(data.get("key")) and str(data.get("base64", "")).startswith("data:image"))
  55. # 3. 发送短信(uniapp 结构:{form: {...}})
  56. r = S.post(BASE + "/api/captcha/sendSmsCaptcha",
  57. json={"form": {"mobile": MOBILE, "captchaCode": code, "captchaKey": data.get("key")}},
  58. timeout=10).json()
  59. check("form 包裹的发送短信成功", r.get("status") == 200, r)
  60. sms = json.loads(redis_cmd("GET", "yoshop:api:sms:" + MOBILE))["code"]
  61. # 4. 登录(uniapp 结构)
  62. r = S.post(BASE + "/api/passport/login",
  63. json={"form": {"mobile": MOBILE, "smsCode": sms, "isParty": False, "partyData": {}}},
  64. timeout=10).json()
  65. d = r.get("data") or {}
  66. check("form 包裹的登录成功", r.get("status") == 200 and r.get("message") == "登录成功", r)
  67. check("返回 userId/token", isinstance(d.get("userId"), int)
  68. and re.fullmatch(r"[0-9a-f]{32}", str(d.get("token"))) is not None, d)
  69. # 5. token 访问用户中心
  70. token = d.get("token")
  71. r = S.get(BASE + "/api/user/info", headers={"Access-Token": token}, timeout=10).json()
  72. info = (r.get("data") or {}).get("userInfo") or {}
  73. check("user/info 可访问", r.get("status") == 200, r)
  74. check("userInfo 有 userId/nickName", info.get("userId") == d.get("userId"), list(info)[:8])
  75. # 清理:删掉本次登录自动注册的用户与临时键
  76. conn = pymysql.connect(host="192.168.1.177", user="root", password=DB_PWD,
  77. database="shop", charset="utf8mb4", autocommit=True)
  78. try:
  79. cur = conn.cursor()
  80. cur.execute("select user_id from yoshop_user where mobile=%s", (MOBILE,))
  81. row = cur.fetchone()
  82. if row:
  83. cur.execute("delete from yoshop_user where user_id=%s", (row[0],))
  84. print(f" CLEAN 已删除测试用户 {row[0]}")
  85. finally:
  86. conn.close()
  87. for key in [f"yoshop:api:sms:{MOBILE}", f"yoshop:api:sms:send:{MOBILE}",
  88. "yoshop:api:token:" + str(token)]:
  89. redis_cmd("DEL", key)
  90. print(f"\n结果:PASS {passed} / FAIL {failed}")
  91. return 0 if failed == 0 else 1
  92. if __name__ == "__main__":
  93. sys.exit(main())