test: 添加 API 自动化测试脚本
- 测试前端页面可访问性(4 个页面) - 测试用户认证系统(登录/获取用户/错误处理/token 刷新) - 测试区域系统(列表/省级/详情/子区域) - 测试文章系统(列表/区域筛选) - 测试特色服务(列表/区域筛选) - 测试评论和评分系统 - 测试 Admin 后台 - 共 19 个测试用例,彩色输出
This commit is contained in:
349
city-manual/test_api.py
Normal file
349
city-manual/test_api.py
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
城市手册 - API 自动化测试脚本
|
||||||
|
测试所有已实现的功能
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
BASE_URL = 'http://127.0.0.1:81/api'
|
||||||
|
FRONTEND_URL = 'http://127.0.0.1:81'
|
||||||
|
|
||||||
|
# 颜色输出
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RESET = '\033[0m'
|
||||||
|
|
||||||
|
def log_success(msg):
|
||||||
|
print(f"{GREEN}✓{RESET} {msg}")
|
||||||
|
|
||||||
|
def log_error(msg):
|
||||||
|
print(f"{RED}✗{RESET} {msg}")
|
||||||
|
|
||||||
|
def log_info(msg):
|
||||||
|
print(f"{YELLOW}→{RESET} {msg}")
|
||||||
|
|
||||||
|
def test_frontend_pages():
|
||||||
|
"""测试前端页面可访问性"""
|
||||||
|
log_info("\n📄 测试前端页面...")
|
||||||
|
|
||||||
|
pages = [
|
||||||
|
('/', '首页'),
|
||||||
|
('/login/', '登录页'),
|
||||||
|
('/register/', '注册页'),
|
||||||
|
('/cities/', '城市列表页'),
|
||||||
|
]
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
for path, name in pages:
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{FRONTEND_URL}{path}", timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
log_success(f"{name} ({path}) - {resp.status_code}")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"{name} ({path}) - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"{name} ({path}) - {str(e)}")
|
||||||
|
|
||||||
|
return passed, len(pages)
|
||||||
|
|
||||||
|
def test_user_auth():
|
||||||
|
"""测试用户认证系统"""
|
||||||
|
log_info("\n🔐 测试用户系统...")
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
total = 4
|
||||||
|
|
||||||
|
# 1. 测试登录
|
||||||
|
try:
|
||||||
|
resp = requests.post(f"{BASE_URL}/token/", json={
|
||||||
|
'username': 'demo',
|
||||||
|
'password': 'demo123'
|
||||||
|
}, timeout=5)
|
||||||
|
|
||||||
|
if resp.status_code == 200 and 'access' in resp.json():
|
||||||
|
token = resp.json()['access']
|
||||||
|
log_success(f"用户登录 - {resp.status_code}")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"用户登录 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"用户登录 - {str(e)}")
|
||||||
|
token = None
|
||||||
|
|
||||||
|
# 2. 测试获取当前用户信息
|
||||||
|
if token:
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/users/me/",
|
||||||
|
headers={'Authorization': f'Bearer {token}'},
|
||||||
|
timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
user = resp.json()
|
||||||
|
log_success(f"获取用户信息 - {resp.status_code} (用户名:{user.get('username', 'N/A')})")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"获取用户信息 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"获取用户信息 - {str(e)}")
|
||||||
|
|
||||||
|
# 3. 测试错误密码登录
|
||||||
|
try:
|
||||||
|
resp = requests.post(f"{BASE_URL}/token/", json={
|
||||||
|
'username': 'demo',
|
||||||
|
'password': 'wrongpassword'
|
||||||
|
}, timeout=5)
|
||||||
|
|
||||||
|
if resp.status_code == 401:
|
||||||
|
log_success(f"错误密码拒绝 - {resp.status_code}")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"错误密码拒绝 - {resp.status_code} (应该返回 401)")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"错误密码拒绝 - {str(e)}")
|
||||||
|
|
||||||
|
# 4. 测试 token 刷新端点存在
|
||||||
|
try:
|
||||||
|
# 只需要测试端点存在,不实际刷新(因为需要有效的 refresh token)
|
||||||
|
resp = requests.options(f"{BASE_URL}/token/refresh/", timeout=5)
|
||||||
|
# OPTIONS 请求应该返回 200 或 401(需要认证)
|
||||||
|
if resp.status_code in [200, 401, 403]:
|
||||||
|
log_success(f"Token 刷新端点 - 可用")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"Token 刷新端点 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"Token 刷新端点 - {str(e)}")
|
||||||
|
|
||||||
|
return passed, total
|
||||||
|
|
||||||
|
def test_regions():
|
||||||
|
"""测试区域系统"""
|
||||||
|
log_info("\n🗺️ 测试区域系统...")
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
total = 4
|
||||||
|
|
||||||
|
# 1. 获取区域列表
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/regions/", timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
count = data.get('count', 0)
|
||||||
|
log_success(f"区域列表 - {resp.status_code} (共{count}个区域)")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"区域列表 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"区域列表 - {str(e)}")
|
||||||
|
|
||||||
|
# 2. 获取省级区域
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/regions/provinces/", timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
# API 可能返回 list 或 paginated response
|
||||||
|
if isinstance(data, list):
|
||||||
|
count = len(data)
|
||||||
|
else:
|
||||||
|
count = data.get('count', len(data))
|
||||||
|
log_success(f"省级区域 - {resp.status_code} (共{count}个省)")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"省级区域 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"省级区域 - {str(e)}")
|
||||||
|
|
||||||
|
# 3. 获取区域详情(北京)
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/regions/1/", timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
log_success(f"区域详情 - {resp.status_code} ({data.get('name', 'N/A')})")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"区域详情 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"区域详情 - {str(e)}")
|
||||||
|
|
||||||
|
# 4. 获取子区域
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/regions/3/children/", timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
# API 可能返回 list 或 paginated response
|
||||||
|
if isinstance(data, list):
|
||||||
|
count = len(data)
|
||||||
|
else:
|
||||||
|
count = data.get('count', len(data))
|
||||||
|
log_success(f"子区域 - {resp.status_code} (广东省有{count}个子区域)")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"子区域 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"子区域 - {str(e)}")
|
||||||
|
|
||||||
|
return passed, total
|
||||||
|
|
||||||
|
def test_articles():
|
||||||
|
"""测试文章系统"""
|
||||||
|
log_info("\n📝 测试文章系统...")
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
total = 2
|
||||||
|
|
||||||
|
# 1. 获取文章列表
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/articles/", timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
count = data.get('count', 0)
|
||||||
|
log_success(f"文章列表 - {resp.status_code} (共{count}篇文章)")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"文章列表 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"文章列表 - {str(e)}")
|
||||||
|
|
||||||
|
# 2. 按区域筛选文章
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/articles/?region=1", timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
count = data.get('count', 0)
|
||||||
|
log_success(f"按区域筛选文章 - {resp.status_code} (北京有{count}篇文章)")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"按区域筛选文章 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"按区域筛选文章 - {str(e)}")
|
||||||
|
|
||||||
|
return passed, total
|
||||||
|
|
||||||
|
def test_services():
|
||||||
|
"""测试特色服务系统"""
|
||||||
|
log_info("\n🏪 测试特色服务...")
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
total = 2
|
||||||
|
|
||||||
|
# 1. 获取服务列表
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/services/", timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
count = data.get('count', 0)
|
||||||
|
log_success(f"服务列表 - {resp.status_code} (共{count}个服务)")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"服务列表 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"服务列表 - {str(e)}")
|
||||||
|
|
||||||
|
# 2. 按区域筛选服务
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/services/?region=1", timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
count = data.get('count', 0)
|
||||||
|
log_success(f"按区域筛选服务 - {resp.status_code} (北京有{count}个服务)")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"按区域筛选服务 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"按区域筛选服务 - {str(e)}")
|
||||||
|
|
||||||
|
return passed, total
|
||||||
|
|
||||||
|
def test_comments_ratings():
|
||||||
|
"""测试评论和评分系统"""
|
||||||
|
log_info("\n💬 测试评论和评分...")
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
total = 2
|
||||||
|
|
||||||
|
# 1. 获取评论列表
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/comments/", timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
log_success(f"评论列表 - {resp.status_code}")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"评论列表 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"评论列表 - {str(e)}")
|
||||||
|
|
||||||
|
# 2. 获取评分列表
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{BASE_URL}/ratings/", timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
log_success(f"评分列表 - {resp.status_code}")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
log_error(f"评分列表 - {resp.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"评分列表 - {str(e)}")
|
||||||
|
|
||||||
|
return passed, total
|
||||||
|
|
||||||
|
def test_admin():
|
||||||
|
"""测试 Admin 后台"""
|
||||||
|
log_info("\n⚙️ 测试 Admin 后台...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.get(f"{FRONTEND_URL}/admin/", timeout=5, allow_redirects=False)
|
||||||
|
if resp.status_code in [200, 302]:
|
||||||
|
log_success(f"Admin 后台 - {resp.status_code}")
|
||||||
|
return 1, 1
|
||||||
|
else:
|
||||||
|
log_error(f"Admin 后台 - {resp.status_code}")
|
||||||
|
return 0, 1
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"Admin 后台 - {str(e)}")
|
||||||
|
return 0, 1
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("📖 城市手册 - API 自动化测试")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
total_passed = 0
|
||||||
|
total_tests = 0
|
||||||
|
|
||||||
|
# 运行所有测试
|
||||||
|
tests = [
|
||||||
|
test_frontend_pages,
|
||||||
|
test_user_auth,
|
||||||
|
test_regions,
|
||||||
|
test_articles,
|
||||||
|
test_services,
|
||||||
|
test_comments_ratings,
|
||||||
|
test_admin,
|
||||||
|
]
|
||||||
|
|
||||||
|
for test_func in tests:
|
||||||
|
try:
|
||||||
|
passed, total = test_func()
|
||||||
|
total_passed += passed
|
||||||
|
total_tests += total
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"{test_func.__name__} 异常:{str(e)}")
|
||||||
|
|
||||||
|
# 打印总结
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print(f"📊 测试结果:{total_passed}/{total_tests} 通过")
|
||||||
|
|
||||||
|
if total_passed == total_tests:
|
||||||
|
print(f"{GREEN}✅ 所有测试通过!{RESET}")
|
||||||
|
else:
|
||||||
|
failed = total_tests - total_passed
|
||||||
|
print(f"{RED}⚠️ {failed} 个测试失败{RESET}")
|
||||||
|
print("="*60 + "\n")
|
||||||
|
|
||||||
|
return 0 if total_passed == total_tests else 1
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user