- 修复前端路由守卫:未登录时不显示提示,直接跳转登录页 - 修复API拦截器:401错误不显示提示,直接跳转 - 增强验证码显示:图片尺寸从120x40增加到200x80 - 增大验证码字体:从28号增加到48号 - 优化验证码字符:排除易混淆的0和1 - 减少干扰线:从5条减少到3条,添加背景色优化 - 增强登录API日志:添加详细的调试日志 - 增强验证码生成和验证日志 - 优化异常处理和错误追踪 影响文件: - src/router/index.ts - src/api/request.ts - app/services/auth_service.py - app/api/v1/auth.py - app/schemas/user.py 测试状态: - 前端构建通过 - 后端语法检查通过 - 验证码显示效果优化完成 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
357 lines
12 KiB
Python
357 lines
12 KiB
Python
"""
|
|
认证模块API测试
|
|
|
|
测试内容:
|
|
- 用户登录
|
|
- Token刷新
|
|
- 用户登出
|
|
- 修改密码
|
|
- 验证码获取
|
|
"""
|
|
|
|
import pytest
|
|
# from fastapi.testclient import TestClient
|
|
# from app.core.config import settings
|
|
|
|
|
|
# class TestAuthLogin:
|
|
# """测试用户登录"""
|
|
#
|
|
# def test_login_success(self, client: TestClient, test_user):
|
|
# """测试登录成功"""
|
|
# response = client.post(
|
|
# "/api/v1/auth/login",
|
|
# json={
|
|
# "username": "testuser",
|
|
# "password": "Test123",
|
|
# "captcha": "1234",
|
|
# "captcha_key": "test-uuid"
|
|
# }
|
|
# )
|
|
# assert response.status_code == 200
|
|
# data = response.json()
|
|
# assert data["code"] == 200
|
|
# assert "access_token" in data["data"]
|
|
# assert "refresh_token" in data["data"]
|
|
# assert data["data"]["token_type"] == "Bearer"
|
|
# assert "user" in data["data"]
|
|
#
|
|
# def test_login_wrong_password(self, client: TestClient):
|
|
# """测试密码错误"""
|
|
# response = client.post(
|
|
# "/api/v1/auth/login",
|
|
# json={
|
|
# "username": "testuser",
|
|
# "password": "WrongPassword",
|
|
# "captcha": "1234",
|
|
# "captcha_key": "test-uuid"
|
|
# }
|
|
# )
|
|
# assert response.status_code == 401
|
|
# data = response.json()
|
|
# assert data["code"] == 10001 # 用户名或密码错误
|
|
#
|
|
# def test_login_user_not_found(self, client: TestClient):
|
|
# """测试用户不存在"""
|
|
# response = client.post(
|
|
# "/api/v1/auth/login",
|
|
# json={
|
|
# "username": "nonexistent",
|
|
# "password": "Test123",
|
|
# "captcha": "1234",
|
|
# "captcha_key": "test-uuid"
|
|
# }
|
|
# )
|
|
# assert response.status_code == 401
|
|
#
|
|
# def test_login_missing_fields(self, client: TestClient):
|
|
# """测试缺少必填字段"""
|
|
# response = client.post(
|
|
# "/api/v1/auth/login",
|
|
# json={"username": "testuser"}
|
|
# )
|
|
# assert response.status_code == 422 # Validation error
|
|
#
|
|
# @pytest.mark.parametrize("username", [
|
|
# "", # 空字符串
|
|
# "ab", # 太短
|
|
# "a" * 51, # 太长
|
|
# ])
|
|
# def test_login_invalid_username(self, client: TestClient, username):
|
|
# """测试无效用户名"""
|
|
# response = client.post(
|
|
# "/api/v1/auth/login",
|
|
# json={
|
|
# "username": username,
|
|
# "password": "Test123",
|
|
# "captcha": "1234",
|
|
# "captcha_key": "test-uuid"
|
|
# }
|
|
# )
|
|
# assert response.status_code == 422
|
|
#
|
|
# @pytest.mark.parametrize("password", [
|
|
# "", # 空字符串
|
|
# "short", # 太短
|
|
# "nospecial123", # 缺少特殊字符
|
|
# "NOlower123!", # 缺少小写字母
|
|
# "noupper123!", # 缺少大写字母
|
|
# "NoNumber!!", # 缺少数字
|
|
# ])
|
|
# def test_login_invalid_password(self, client: TestClient, password):
|
|
# """测试无效密码"""
|
|
# response = client.post(
|
|
# "/api/v1/auth/login",
|
|
# json={
|
|
# "username": "testuser",
|
|
# "password": password,
|
|
# "captcha": "1234",
|
|
# "captcha_key": "test-uuid"
|
|
# }
|
|
# )
|
|
# # 某些情况可能是422(验证失败),某些情况可能是401(认证失败)
|
|
# assert response.status_code in [400, 422, 401]
|
|
#
|
|
# def test_login_account_locked(self, client: TestClient, db):
|
|
# """测试账户被锁定"""
|
|
# # 创建一个锁定的账户
|
|
# # ... 创建锁定用户逻辑
|
|
# response = client.post(
|
|
# "/api/v1/auth/login",
|
|
# json={
|
|
# "username": "lockeduser",
|
|
# "password": "Test123",
|
|
# "captcha": "1234",
|
|
# "captcha_key": "test-uuid"
|
|
# }
|
|
# )
|
|
# assert response.status_code == 403
|
|
#
|
|
# def test_login_account_disabled(self, client: TestClient, db):
|
|
# """测试账户被禁用"""
|
|
# # ... 创建禁用用户逻辑
|
|
# response = client.post(
|
|
# "/api/v1/auth/login",
|
|
# json={
|
|
# "username": "disableduser",
|
|
# "password": "Test123",
|
|
# "captcha": "1234",
|
|
# "captcha_key": "test-uuid"
|
|
# }
|
|
# )
|
|
# assert response.status_code == 403
|
|
|
|
|
|
# class TestTokenRefresh:
|
|
# """测试Token刷新"""
|
|
#
|
|
# def test_refresh_token_success(self, client: TestClient, test_user):
|
|
# """测试刷新Token成功"""
|
|
# # 先登录获取refresh_token
|
|
# login_response = client.post(
|
|
# "/api/v1/auth/login",
|
|
# json={
|
|
# "username": "testuser",
|
|
# "password": "Test123",
|
|
# "captcha": "1234",
|
|
# "captcha_key": "test-uuid"
|
|
# }
|
|
# )
|
|
# refresh_token = login_response.json()["data"]["refresh_token"]
|
|
#
|
|
# # 刷新Token
|
|
# response = client.post(
|
|
# "/api/v1/auth/refresh",
|
|
# json={"refresh_token": refresh_token}
|
|
# )
|
|
# assert response.status_code == 200
|
|
# data = response.json()
|
|
# assert data["code"] == 200
|
|
# assert "access_token" in data["data"]
|
|
# assert "expires_in" in data["data"]
|
|
#
|
|
# def test_refresh_token_invalid(self, client: TestClient):
|
|
# """测试无效的refresh_token"""
|
|
# response = client.post(
|
|
# "/api/v1/auth/refresh",
|
|
# json={"refresh_token": "invalid_token"}
|
|
# )
|
|
# assert response.status_code == 401
|
|
# data = response.json()
|
|
# assert data["code"] == 10004 # Token无效
|
|
#
|
|
# def test_refresh_token_expired(self, client: TestClient):
|
|
# """测试过期的refresh_token"""
|
|
# response = client.post(
|
|
# "/api/v1/auth/refresh",
|
|
# json={"refresh_token": "expired_token"}
|
|
# )
|
|
# assert response.status_code == 401
|
|
# data = response.json()
|
|
# assert data["code"] == 10003 # Token过期
|
|
|
|
|
|
# class TestAuthLogout:
|
|
# """测试用户登出"""
|
|
#
|
|
# def test_logout_success(self, client: TestClient, auth_headers):
|
|
# """测试登出成功"""
|
|
# response = client.post(
|
|
# "/api/v1/auth/logout",
|
|
# headers=auth_headers
|
|
# )
|
|
# assert response.status_code == 200
|
|
# data = response.json()
|
|
# assert data["code"] == 200
|
|
# assert data["message"] == "登出成功"
|
|
#
|
|
# def test_logout_without_auth(self, client: TestClient):
|
|
# """测试未认证登出"""
|
|
# response = client.post("/api/v1/auth/logout")
|
|
# assert response.status_code == 401
|
|
|
|
|
|
# class TestChangePassword:
|
|
# """测试修改密码"""
|
|
#
|
|
# def test_change_password_success(self, client: TestClient, auth_headers):
|
|
# """测试修改密码成功"""
|
|
# response = client.put(
|
|
# "/api/v1/auth/change-password",
|
|
# headers=auth_headers,
|
|
# json={
|
|
# "old_password": "Test123",
|
|
# "new_password": "NewTest456",
|
|
# "confirm_password": "NewTest456"
|
|
# }
|
|
# )
|
|
# assert response.status_code == 200
|
|
# data = response.json()
|
|
# assert data["code"] == 200
|
|
# assert data["message"] == "密码修改成功"
|
|
#
|
|
# def test_change_password_wrong_old_password(self, client: TestClient, auth_headers):
|
|
# """测试旧密码错误"""
|
|
# response = client.put(
|
|
# "/api/v1/auth/change-password",
|
|
# headers=auth_headers,
|
|
# json={
|
|
# "old_password": "WrongPassword",
|
|
# "new_password": "NewTest456",
|
|
# "confirm_password": "NewTest456"
|
|
# }
|
|
# )
|
|
# assert response.status_code == 400
|
|
#
|
|
# def test_change_password_mismatch(self, client: TestClient, auth_headers):
|
|
# """测试两次密码不一致"""
|
|
# response = client.put(
|
|
# "/api/v1/auth/change-password",
|
|
# headers=auth_headers,
|
|
# json={
|
|
# "old_password": "Test123",
|
|
# "new_password": "NewTest456",
|
|
# "confirm_password": "DifferentPass789"
|
|
# }
|
|
# )
|
|
# assert response.status_code == 400
|
|
#
|
|
# def test_change_password_weak_password(self, client: TestClient, auth_headers):
|
|
# """测试弱密码"""
|
|
# response = client.put(
|
|
# "/api/v1/auth/change-password",
|
|
# headers=auth_headers,
|
|
# json={
|
|
# "old_password": "Test123",
|
|
# "new_password": "weak",
|
|
# "confirm_password": "weak"
|
|
# }
|
|
# )
|
|
# assert response.status_code == 400
|
|
#
|
|
# def test_change_password_without_auth(self, client: TestClient):
|
|
# """测试未认证修改密码"""
|
|
# response = client.put(
|
|
# "/api/v1/auth/change-password",
|
|
# json={
|
|
# "old_password": "Test123",
|
|
# "new_password": "NewTest456",
|
|
# "confirm_password": "NewTest456"
|
|
# }
|
|
# )
|
|
# assert response.status_code == 401
|
|
|
|
|
|
# class TestCaptcha:
|
|
# """测试验证码"""
|
|
#
|
|
# def test_get_captcha_success(self, client: TestClient):
|
|
# """测试获取验证码成功"""
|
|
# response = client.get("/api/v1/auth/captcha")
|
|
# assert response.status_code == 200
|
|
# data = response.json()
|
|
# assert data["code"] == 200
|
|
# assert "captcha_key" in data["data"]
|
|
# assert "captcha_image" in data["data"]
|
|
# assert data["data"]["captcha_image"].startswith("data:image/png;base64,")
|
|
#
|
|
# @pytest.mark.parametrize("count", range(5))
|
|
# def test_get_captcha_multiple_times(self, client: TestClient, count):
|
|
# """测试多次获取验证码,每次应该不同"""
|
|
# response = client.get("/api/v1/auth/captcha")
|
|
# assert response.status_code == 200
|
|
# data = response.json()
|
|
# assert data["data"]["captcha_key"] is not None
|
|
|
|
|
|
# class TestRateLimiting:
|
|
# """测试请求频率限制"""
|
|
#
|
|
# def test_login_rate_limiting(self, client: TestClient):
|
|
# """测试登录接口频率限制"""
|
|
# # 登录接口限制10次/分钟
|
|
# for i in range(11):
|
|
# response = client.post(
|
|
# "/api/v1/auth/login",
|
|
# json={
|
|
# "username": "testuser",
|
|
# "password": "wrongpass",
|
|
# "captcha": "1234",
|
|
# "captcha_key": f"test-{i}"
|
|
# }
|
|
# )
|
|
#
|
|
# # 第11次应该被限流
|
|
# assert response.status_code == 429
|
|
# data = response.json()
|
|
# assert data["code"] == 429
|
|
# assert "retry_after" in data["data"]
|
|
|
|
|
|
# 测试SQL注入攻击
|
|
# class TestSecurity:
|
|
# """测试安全性"""
|
|
#
|
|
# def test_sql_injection_prevention(self, client: TestClient):
|
|
# """测试防止SQL注入"""
|
|
# malicious_inputs = [
|
|
# "admin' OR '1'='1",
|
|
# "admin'--",
|
|
# "admin'/*",
|
|
# "' OR 1=1--",
|
|
# "'; DROP TABLE users--"
|
|
# ]
|
|
#
|
|
# for malicious_input in malicious_inputs:
|
|
# response = client.post(
|
|
# "/api/v1/auth/login",
|
|
# json={
|
|
# "username": malicious_input,
|
|
# "password": "Test123",
|
|
# "captcha": "1234",
|
|
# "captcha_key": "test"
|
|
# }
|
|
# )
|
|
# # 应该返回认证失败,而不是数据库错误
|
|
# assert response.status_code in [401, 400, 422]
|