256 lines
9.9 KiB
Python
256 lines
9.9 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
* @time 2026-06-02
|
||
* @name test_qrcode_login.py
|
||
* @description 扫码登录API完整流程测试脚本
|
||
* @lastUpdate v10.3.0 使用现有测试账号; 测试登录→生成二维码→轮询→确认→获取token→取消
|
||
"""
|
||
|
||
import hmac
|
||
import hashlib
|
||
import base64
|
||
import time
|
||
import secrets
|
||
import json
|
||
import sys
|
||
|
||
try:
|
||
import requests
|
||
except ImportError:
|
||
print("need: pip3 install requests")
|
||
sys.exit(1)
|
||
|
||
BASE_URL = "https://tools.wktyl.com"
|
||
SECRET = "Xy7kP9mL2qR4wS8v"
|
||
TIMEOUT = 15
|
||
TEST_ACCOUNT = "123456"
|
||
TEST_PASSWORD = "123456"
|
||
|
||
PASS_COUNT = 0
|
||
FAIL_COUNT = 0
|
||
SKIP_COUNT = 0
|
||
|
||
|
||
def generate_receipt(action, payload, secret=SECRET):
|
||
ts = int(time.time())
|
||
nonce = secrets.token_hex(8)
|
||
payload_hash = hashlib.sha256(payload.encode()).hexdigest()[:16]
|
||
data = base64.b64encode(json.dumps({
|
||
'action': action,
|
||
'payload': payload_hash,
|
||
'ts': ts,
|
||
'nonce': nonce
|
||
}).encode()).decode()
|
||
sig = hmac.new(secret.encode(), data.encode(), hashlib.sha256).hexdigest()
|
||
return data, sig
|
||
|
||
|
||
def api_call(method, path, data=None, params=None, token=None):
|
||
url = f"{BASE_URL}{path}"
|
||
headers = {}
|
||
if token:
|
||
headers['token'] = token
|
||
try:
|
||
if method == 'GET':
|
||
resp = requests.get(url, params=params, headers=headers, timeout=TIMEOUT)
|
||
else:
|
||
resp = requests.post(url, data=data, headers=headers, timeout=TIMEOUT)
|
||
return resp.json()
|
||
except requests.exceptions.Timeout:
|
||
return {'code': -2, 'msg': 'timeout'}
|
||
except Exception as e:
|
||
return {'code': -1, 'msg': str(e)}
|
||
|
||
|
||
def assert_test(condition, test_name, detail=""):
|
||
global PASS_COUNT, FAIL_COUNT
|
||
if condition:
|
||
PASS_COUNT += 1
|
||
print(f" ✅ {test_name}")
|
||
else:
|
||
FAIL_COUNT += 1
|
||
print(f" ❌ {test_name} {detail}")
|
||
|
||
|
||
def skip_test(test_name, reason=""):
|
||
global SKIP_COUNT
|
||
SKIP_COUNT += 1
|
||
print(f" ⏭️ {test_name} {reason}")
|
||
|
||
|
||
def test_qrcode_login_flow():
|
||
print("\n" + "=" * 60)
|
||
print("🧪 扫码登录API完整流程测试")
|
||
print("=" * 60)
|
||
|
||
user_token = None
|
||
user_id = None
|
||
qr_code = None
|
||
poll_token = None
|
||
|
||
# ─── Step 1: 登录测试账号 ───
|
||
print("\n📌 Step 1: 登录测试账号")
|
||
result = api_call('POST', '/api/user_security/login', data={
|
||
'account': TEST_ACCOUNT,
|
||
'password': TEST_PASSWORD,
|
||
})
|
||
assert_test(result.get('code') == 1, "账号密码登录",
|
||
f"code={result.get('code')}, msg={result.get('msg')}")
|
||
|
||
if result.get('code') != 1:
|
||
print(f" ⚠️ 登录失败,跳过后续测试: {result.get('msg')}")
|
||
return
|
||
|
||
user_token = result.get('data', {}).get('token', '')
|
||
user_info = result.get('data', {}).get('userinfo', {})
|
||
user_id = user_info.get('id', '')
|
||
print(f" 📋 用户ID: {user_id}, Token: {user_token[:20]}...")
|
||
|
||
# ─── Step 2: 生成二维码 ───
|
||
print("\n📌 Step 2: 生成二维码")
|
||
result = api_call('GET', '/api/user_security/qrcodeGenerate')
|
||
assert_test(result.get('code') == 1, "生成二维码",
|
||
f"code={result.get('code')}, msg={result.get('msg')}")
|
||
|
||
if result.get('code') == 1:
|
||
qr_data = result.get('data', {})
|
||
qr_code = qr_data.get('code', '')
|
||
expire_time = qr_data.get('expire_time', 0)
|
||
expire_seconds = qr_data.get('expire_seconds', 0)
|
||
qrcode_url = qr_data.get('qrcode_url', '')
|
||
print(f" 📋 Code: {qr_code[:20]}...")
|
||
print(f" 📋 过期时间: {expire_time}, 有效期: {expire_seconds}秒")
|
||
print(f" 📋 二维码URL: {qrcode_url[:50]}...")
|
||
assert_test(len(qr_code) == 32, "Code长度为32位hex", f"实际长度: {len(qr_code)}")
|
||
assert_test(expire_seconds == 300, "有效期为300秒", f"实际: {expire_seconds}")
|
||
else:
|
||
print(f" ⚠️ 生成二维码失败,跳过后续测试")
|
||
return
|
||
|
||
# ─── Step 3: 轮询二维码状态(应为pending) ───
|
||
print("\n📌 Step 3: 轮询二维码状态(应为pending)")
|
||
result = api_call('GET', '/api/user_security/qrcodePoll', params={'code': qr_code})
|
||
assert_test(result.get('code') == 1, "轮询二维码状态",
|
||
f"code={result.get('code')}, msg={result.get('msg')}")
|
||
if result.get('code') == 1:
|
||
poll_status = result.get('data', {}).get('status', '')
|
||
assert_test(poll_status == 'pending', "状态为pending", f"实际: {poll_status}")
|
||
|
||
# ─── Step 4: 用登录的token确认扫码 ───
|
||
print("\n📌 Step 4: 用登录的token确认扫码")
|
||
result = api_call('POST', '/api/user_security/qrcodeConfirm', data={
|
||
'code': qr_code,
|
||
'platform': 'test_script',
|
||
'device_name': 'Test Device',
|
||
'app_name': 'test_qrcode_login',
|
||
}, token=user_token)
|
||
assert_test(result.get('code') == 1, "确认扫码",
|
||
f"code={result.get('code')}, msg={result.get('msg')}")
|
||
|
||
# ─── Step 5: 轮询二维码状态(应为confirmed,获取新token) ───
|
||
print("\n📌 Step 5: 轮询二维码状态(应为confirmed)")
|
||
result = api_call('GET', '/api/user_security/qrcodePoll', params={'code': qr_code})
|
||
assert_test(result.get('code') == 1, "轮询confirmed状态",
|
||
f"code={result.get('code')}, msg={result.get('msg')}")
|
||
if result.get('code') == 1:
|
||
poll_status = result.get('data', {}).get('status', '')
|
||
poll_token = result.get('data', {}).get('token', '')
|
||
poll_userinfo = result.get('data', {}).get('userinfo', {})
|
||
assert_test(poll_status == 'confirmed', "状态为confirmed", f"实际: {poll_status}")
|
||
assert_test(bool(poll_token), "返回Token", "token为空")
|
||
assert_test(bool(poll_userinfo), "返回用户信息", "userinfo为空")
|
||
if poll_token:
|
||
print(f" 📋 新Token: {poll_token[:20]}...")
|
||
print(f" 📋 用户: {poll_userinfo.get('username', 'N/A')}")
|
||
|
||
# ─── Step 6: 使用新token登录 ───
|
||
print("\n📌 Step 6: 使用新token登录")
|
||
if poll_token:
|
||
result = api_call('POST', '/api/user_security/tokenLogin', data={
|
||
'token': poll_token,
|
||
})
|
||
assert_test(result.get('code') == 1, "使用新Token登录",
|
||
f"code={result.get('code')}, msg={result.get('msg')}")
|
||
if result.get('code') == 1:
|
||
login_userinfo = result.get('data', {}).get('userinfo', {})
|
||
assert_test(login_userinfo.get('id') == user_id, "用户ID一致",
|
||
f"期望: {user_id}, 实际: {login_userinfo.get('id')}")
|
||
else:
|
||
skip_test("使用新Token登录", "无Token可用")
|
||
|
||
# ─── Step 7: 测试取消二维码 ───
|
||
print("\n📌 Step 7: 测试取消二维码")
|
||
result = api_call('GET', '/api/user_security/qrcodeGenerate')
|
||
if result.get('code') == 1:
|
||
cancel_code = result.get('data', {}).get('code', '')
|
||
result = api_call('POST', '/api/user_security/qrcodeCancel', data={'code': cancel_code})
|
||
assert_test(result.get('code') == 1, "取消二维码",
|
||
f"code={result.get('code')}, msg={result.get('msg')}")
|
||
result = api_call('GET', '/api/user_security/qrcodePoll', params={'code': cancel_code})
|
||
if result.get('code') == 1:
|
||
cancel_status = result.get('data', {}).get('status', '')
|
||
assert_test(cancel_status == 'cancelled', "取消后状态为cancelled",
|
||
f"实际: {cancel_status}")
|
||
else:
|
||
skip_test("取消二维码", "无法生成新二维码")
|
||
|
||
# ─── Step 8: 测试密保问题接口 ───
|
||
print("\n📌 Step 8: 测试密保问题接口")
|
||
result = api_call('GET', '/api/user_security/secQuestions')
|
||
assert_test(result.get('code') == 1, "获取密保问题列表",
|
||
f"code={result.get('code')}, msg={result.get('msg')}")
|
||
if result.get('code') == 1:
|
||
questions = result.get('data', {}).get('questions', [])
|
||
assert_test(len(questions) == 8, "密保问题数量为8", f"实际: {len(questions)}")
|
||
|
||
|
||
def test_edge_cases():
|
||
print("\n" + "=" * 60)
|
||
print("🧪 边界情况测试")
|
||
print("=" * 60)
|
||
|
||
# 无效code轮询
|
||
print("\n📌 测试无效code轮询")
|
||
result = api_call('GET', '/api/user_security/qrcodePoll', params={'code': 'invalid_code_12345'})
|
||
assert_test(result.get('code') == 0, "无效code返回错误",
|
||
f"code={result.get('code')}, msg={result.get('msg')}")
|
||
|
||
# 无效code确认(需登录,返回401)
|
||
print("\n📌 测试无效code确认(未登录)")
|
||
result = api_call('POST', '/api/user_security/qrcodeConfirm', data={'code': 'invalid_code_12345'})
|
||
assert_test(result.get('code') in [-1, 0, 401], "未登录确认返回错误",
|
||
f"code={result.get('code')}, msg={result.get('msg')}")
|
||
|
||
# 空code参数
|
||
print("\n📌 测试空code参数")
|
||
result = api_call('GET', '/api/user_security/qrcodePoll', params={'code': ''})
|
||
assert_test(result.get('code') == 0, "空code返回错误",
|
||
f"code={result.get('code')}, msg={result.get('msg')}")
|
||
|
||
|
||
if __name__ == '__main__':
|
||
print("🚀 闲言工具箱 - 扫码登录API测试")
|
||
print(f"📅 时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
||
print(f"🌐 基础URL: {BASE_URL}")
|
||
|
||
test_qrcode_login_flow()
|
||
test_edge_cases()
|
||
|
||
print("\n" + "=" * 60)
|
||
print("📊 测试结果汇总")
|
||
print("=" * 60)
|
||
total = PASS_COUNT + FAIL_COUNT + SKIP_COUNT
|
||
print(f" ✅ 通过: {PASS_COUNT}")
|
||
print(f" ❌ 失败: {FAIL_COUNT}")
|
||
print(f" ⏭️ 跳过: {SKIP_COUNT}")
|
||
print(f" 📋 总计: {total}")
|
||
print(f" 📈 通过率: {PASS_COUNT/total*100:.1f}%" if total > 0 else " 📈 无测试")
|
||
|
||
if FAIL_COUNT > 0:
|
||
print("\n⚠️ 存在失败测试,请检查!")
|
||
sys.exit(1)
|
||
else:
|
||
print("\n🎉 所有测试通过!")
|
||
sys.exit(0)
|