From 47cfc14f684e237072221cc88036e5e0a3858cf0 Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Wed, 24 Dec 2025 20:37:33 +0800 Subject: [PATCH] =?UTF-8?q?test=EF=BC=9A=E6=B7=BB=E5=8A=A0=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E7=B3=BB=E7=BB=9F=E6=B5=8B=E8=AF=95=E5=A5=97=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加UI测试场景,支持模拟各种认证场景 - 实现API测试脚本,覆盖登录、注册、验证码等接口 - 添加测试文档,说明测试用例和预期结果 - 支持自动化测试和手动测试验证 --- tests/api/README.md | 150 ++++++++++ tests/api/api_test.py | 506 +++++++++++++++++++++++++++++++++ tests/api/simple_api_test.py | 108 +++++++ tests/auth/README.md | 165 +++++++++++ tests/auth/auth_ui_test.gd | 464 ++++++++++++++++++++++++++++++ tests/auth/auth_ui_test.gd.uid | 1 + tests/auth/auth_ui_test.tscn | 117 ++++++++ 7 files changed, 1511 insertions(+) create mode 100644 tests/api/README.md create mode 100644 tests/api/api_test.py create mode 100644 tests/api/simple_api_test.py create mode 100644 tests/auth/README.md create mode 100644 tests/auth/auth_ui_test.gd create mode 100644 tests/auth/auth_ui_test.gd.uid create mode 100644 tests/auth/auth_ui_test.tscn diff --git a/tests/api/README.md b/tests/api/README.md new file mode 100644 index 0000000..d519929 --- /dev/null +++ b/tests/api/README.md @@ -0,0 +1,150 @@ +# API接口测试 + +本目录包含用于测试whaleTown项目API接口的测试脚本。 + +## 测试脚本说明 + +### 1. `simple_api_test.py` - 简化版测试 +快速验证API接口的基本连通性和功能。 + +**使用方法:** +```bash +# 使用默认服务器地址 +python tests/api/simple_api_test.py + +# 使用自定义服务器地址 +python tests/api/simple_api_test.py http://localhost:3000 +``` + +**测试内容:** +- ✅ 应用状态检查 (`GET /`) +- ✅ 发送邮箱验证码 (`POST /auth/send-email-verification`) +- ✅ 用户注册 (`POST /auth/register`) +- ✅ 用户登录 (`POST /auth/login`) +- ✅ 无效登录测试 +- ✅ 管理员登录测试 + +### 2. `api_test.py` - 完整版测试 +全面的API接口测试,包括边界条件和错误处理。 + +**使用方法:** +```bash +# 基础测试 +python tests/api/api_test.py + +# 详细日志 +python tests/api/api_test.py --verbose + +# 自定义参数 +python tests/api/api_test.py --base-url http://localhost:3000 --test-email custom@example.com +``` + +**测试内容:** +- 应用状态接口测试 +- 用户认证功能测试 +- 管理员功能测试 +- 用户状态管理测试 +- 错误处理测试 +- 频率限制测试 + +## 测试结果示例 + +### 成功的测试输出 +``` +[18:28:55] 🚀 开始基础API测试... +[18:28:55] 测试目标: https://whaletownend.xinghangee.icu +[18:28:55] ✅ 应用状态检查 +[18:28:55] 状态码: 200 +[18:28:55] 响应: Pixel Game Server is running! +[18:28:55] ✅ 发送邮箱验证码 +[18:28:55] 状态码: 200 +[18:28:55] 📧 获取到验证码: 046189 +[18:28:55] ✅ 用户注册 +[18:28:55] 状态码: 201 +[18:28:55] 🎯 基础测试完成! +``` + +### API接口验证结果 + +根据测试结果,以下接口已验证可用: + +#### ✅ 可用接口 +1. **应用状态** - `GET /` + - 状态码: 200 + - 响应: "Pixel Game Server is running!" + +2. **发送邮箱验证码** - `POST /auth/send-email-verification` + - 状态码: 200 + - 功能: 正常发送验证码 + - 频率限制: 1分钟内限制重复发送 + +3. **用户注册** - `POST /auth/register` + - 状态码: 201 (成功) / 400 (参数错误) + - 需要邮箱验证码 + - 支持完整的参数验证 + +4. **用户登录** - `POST /auth/login` + - 状态码: 200 + - 支持用户名/邮箱登录 + - 正确的错误处理 + +#### ❌ 不可用接口 +1. **管理员登录** - `POST /admin/auth/login` + - 状态码: 404 + - 错误: "Cannot POST /admin/auth/login" + - 可能的原因: 路径不正确或功能未实现 + +## 测试发现的问题 + +### 1. 管理员接口路径问题 +- 文档中的 `/admin/auth/login` 返回404 +- 需要确认正确的管理员登录路径 + +### 2. 频率限制功能正常 +- 验证码发送有1分钟的频率限制 +- 错误消息清晰:"请等待 XX 秒后再试" + +### 3. 参数验证严格 +- 注册时必须提供邮箱验证码 +- 错误消息准确:"提供邮箱时必须提供邮箱验证码" + +## 建议的测试流程 + +### 开发环境测试 +1. 运行简化版测试验证基本功能 +2. 检查所有接口的连通性 +3. 验证错误处理是否正确 + +### 生产环境测试 +1. 使用完整版测试进行全面验证 +2. 测试所有边界条件 +3. 验证安全功能(频率限制、权限控制) + +## 扩展测试 + +### 添加新的测试用例 +1. 在对应的测试脚本中添加新的测试方法 +2. 遵循现有的测试模式 +3. 添加适当的错误处理 + +### 自定义测试参数 +```python +# 修改测试配置 +tester = SimpleAPITester("http://your-server.com") +tester.run_basic_tests() +``` + +## 注意事项 + +1. **频率限制**: 测试时注意API的频率限制,避免被限制 +2. **测试数据**: 使用时间戳生成唯一的测试数据 +3. **网络超时**: 设置合适的请求超时时间 +4. **错误处理**: 测试脚本包含完整的错误处理逻辑 + +## 依赖要求 + +```bash +pip install requests +``` + +测试脚本使用Python标准库和requests库,无需额外依赖。 \ No newline at end of file diff --git a/tests/api/api_test.py b/tests/api/api_test.py new file mode 100644 index 0000000..fa4bcfb --- /dev/null +++ b/tests/api/api_test.py @@ -0,0 +1,506 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +API接口测试脚本 + +根据 docs/api-documentation.md 文档自动测试所有API接口 +支持完整的功能测试、边界条件测试和错误处理测试 + +使用方法: + python tests/api/api_test.py + python tests/api/api_test.py --base-url http://localhost:3000 + python tests/api/api_test.py --verbose --test-email custom@example.com +""" + +import requests +import json +import time +import argparse +import sys +from datetime import datetime +from typing import Dict, Any, Optional, List +import uuid + + +class APITester: + """API接口测试类""" + + def __init__(self, base_url: str = "https://whaletownend.xinghangee.icu", + test_email: str = "test@example.com", verbose: bool = False): + self.base_url = base_url.rstrip('/') + self.test_email = test_email + self.verbose = verbose + self.session = requests.Session() + self.session.headers.update({ + 'Content-Type': 'application/json', + 'User-Agent': 'whaleTown-API-Tester/1.0' + }) + + # 测试数据存储 + self.test_data = { + 'admin_token': None, + 'user_token': None, + 'test_user_id': None, + 'verification_code': None + } + + # 测试结果统计 + self.stats = { + 'total': 0, + 'passed': 0, + 'failed': 0, + 'errors': [] + } + + def log(self, message: str, level: str = "INFO"): + """日志输出""" + timestamp = datetime.now().strftime("%H:%M:%S") + if level == "ERROR" or self.verbose: + print(f"[{timestamp}] {level}: {message}") + + def make_request(self, method: str, endpoint: str, data: Dict = None, + headers: Dict = None, expected_status: int = None) -> Dict: + """发送HTTP请求""" + url = f"{self.base_url}{endpoint}" + request_headers = self.session.headers.copy() + if headers: + request_headers.update(headers) + + try: + if method.upper() == 'GET': + response = self.session.get(url, headers=request_headers, timeout=30) + elif method.upper() == 'POST': + response = self.session.post(url, json=data, headers=request_headers, timeout=30) + elif method.upper() == 'PUT': + response = self.session.put(url, json=data, headers=request_headers, timeout=30) + else: + raise ValueError(f"不支持的HTTP方法: {method}") + + # 检查状态码 + if expected_status and response.status_code != expected_status: + self.log(f"状态码不匹配: 期望 {expected_status}, 实际 {response.status_code}", "ERROR") + + # 尝试解析JSON + try: + result = response.json() + except json.JSONDecodeError: + result = {"raw_response": response.text, "status_code": response.status_code} + + result['status_code'] = response.status_code + return result + + except requests.exceptions.RequestException as e: + self.log(f"请求失败: {str(e)}", "ERROR") + return {"error": str(e), "status_code": 0} + + def assert_response(self, response: Dict, expected_success: bool = True, + expected_fields: List[str] = None, test_name: str = ""): + """验证响应结果""" + self.stats['total'] += 1 + + try: + # 检查基本结构 + if 'error' in response: + raise AssertionError(f"请求错误: {response['error']}") + + # 检查success字段 + if 'success' in response: + if response['success'] != expected_success: + raise AssertionError(f"success字段不匹配: 期望 {expected_success}, 实际 {response['success']}") + + # 检查必需字段 + if expected_fields: + for field in expected_fields: + if field not in response: + raise AssertionError(f"缺少必需字段: {field}") + + self.stats['passed'] += 1 + self.log(f"✅ {test_name} - 通过") + return True + + except AssertionError as e: + self.stats['failed'] += 1 + error_msg = f"❌ {test_name} - 失败: {str(e)}" + self.log(error_msg, "ERROR") + self.stats['errors'].append(error_msg) + return False + + def test_app_status(self): + """测试应用状态接口""" + self.log("开始测试应用状态接口...") + + response = self.make_request('GET', '/') + + # 应用状态接口可能返回不同的字段结构 + if response.get('status_code') == 200: + # 检查是否有基本的状态信息 + has_status_info = any(key in response for key in ['service', 'status', 'version', 'timestamp']) + if has_status_info: + self.stats['total'] += 1 + self.stats['passed'] += 1 + self.log(f"✅ 获取应用状态 - 通过") + else: + self.stats['total'] += 1 + self.stats['failed'] += 1 + error_msg = f"❌ 获取应用状态 - 失败: 响应格式不正确" + self.log(error_msg, "ERROR") + self.stats['errors'].append(error_msg) + else: + self.stats['total'] += 1 + self.stats['failed'] += 1 + error_msg = f"❌ 获取应用状态 - 失败: HTTP状态码 {response.get('status_code')}" + self.log(error_msg, "ERROR") + self.stats['errors'].append(error_msg) + + def test_send_verification_code(self) -> Optional[str]: + """测试发送邮箱验证码""" + self.log("开始测试发送邮箱验证码...") + + response = self.make_request('POST', '/auth/send-email-verification', { + 'email': self.test_email + }) + + # 测试模式下返回206状态码,success为false但有验证码 + if response.get('status_code') == 206: + if 'data' in response and 'verification_code' in response['data']: + verification_code = response['data']['verification_code'] + self.test_data['verification_code'] = verification_code + self.log(f"获取到验证码: {verification_code}") + self.stats['total'] += 1 + self.stats['passed'] += 1 + self.log(f"✅ 发送邮箱验证码(测试模式) - 通过") + return verification_code + elif response.get('success') == True: + # 正常模式 + self.assert_response( + response, + expected_success=True, + expected_fields=['data'], + test_name="发送邮箱验证码" + ) + if 'data' in response and 'verification_code' in response['data']: + return response['data']['verification_code'] + + # 测试失败 + self.stats['total'] += 1 + self.stats['failed'] += 1 + error_msg = f"❌ 发送邮箱验证码 - 失败: 无法获取验证码" + self.log(error_msg, "ERROR") + self.stats['errors'].append(error_msg) + return None + + def test_user_register(self) -> Optional[str]: + """测试用户注册""" + self.log("开始测试用户注册...") + + # 先获取验证码 + verification_code = self.test_send_verification_code() + if not verification_code: + self.log("无法获取验证码,跳过注册测试", "ERROR") + return None + + # 生成唯一用户名 + username = f"testuser_{int(time.time())}" + + response = self.make_request('POST', '/auth/register', { + 'username': username, + 'password': 'Test123456', + 'nickname': '测试用户', + 'email': self.test_email + }) + + if self.assert_response( + response, + expected_success=True, + expected_fields=['data'], + test_name="用户注册" + ): + if 'data' in response and 'user' in response['data']: + user_id = response['data']['user'].get('id') + self.test_data['test_user_id'] = user_id + self.log(f"注册成功,用户ID: {user_id}") + + # 保存用户token + if 'access_token' in response['data']: + self.test_data['user_token'] = response['data']['access_token'] + + return user_id + + return None + + def test_user_login(self): + """测试用户登录""" + self.log("开始测试用户登录...") + + # 使用已注册的用户登录 + username = f"testuser_{int(time.time() - 1)}" # 使用之前的用户名 + + response = self.make_request('POST', '/auth/login', { + 'identifier': username, + 'password': 'Test123456' + }) + + # 如果用户不存在,尝试注册后登录 + if not response.get('success'): + self.log("用户不存在,先注册用户...") + self.test_user_register() + + # 重新尝试登录 + response = self.make_request('POST', '/auth/login', { + 'identifier': username, + 'password': 'Test123456' + }) + + self.assert_response( + response, + expected_success=True, + expected_fields=['data'], + test_name="用户登录" + ) + + def test_admin_login(self) -> Optional[str]: + """测试管理员登录""" + self.log("开始测试管理员登录...") + + # 尝试不同的管理员登录路径 + admin_endpoints = ['/admin/auth/login', '/admin/login'] + + for endpoint in admin_endpoints: + response = self.make_request('POST', endpoint, { + 'identifier': 'admin', + 'password': 'Admin123456' + }) + + if response.get('status_code') != 404: + if self.assert_response( + response, + expected_success=True, + expected_fields=['data'], + test_name=f"管理员登录({endpoint})" + ): + if 'data' in response and 'access_token' in response['data']: + admin_token = response['data']['access_token'] + self.test_data['admin_token'] = admin_token + self.log("管理员登录成功") + return admin_token + break + + # 如果所有端点都失败 + self.stats['total'] += 1 + self.stats['failed'] += 1 + error_msg = f"❌ 管理员登录 - 失败: 所有管理员登录端点都不可用" + self.log(error_msg, "ERROR") + self.stats['errors'].append(error_msg) + return None + + def test_admin_get_users(self): + """测试获取用户列表""" + self.log("开始测试获取用户列表...") + + admin_token = self.test_data.get('admin_token') + if not admin_token: + admin_token = self.test_admin_login() + + if not admin_token: + self.log("无管理员token,跳过用户列表测试", "ERROR") + return + + response = self.make_request('GET', '/admin/users?limit=10&offset=0', + headers={'Authorization': f'Bearer {admin_token}'}) + + self.assert_response( + response, + expected_success=True, + expected_fields=['data'], + test_name="获取用户列表" + ) + + def test_user_status_management(self): + """测试用户状态管理""" + self.log("开始测试用户状态管理...") + + admin_token = self.test_data.get('admin_token') + user_id = self.test_data.get('test_user_id') + + if not admin_token: + admin_token = self.test_admin_login() + + if not user_id: + user_id = self.test_user_register() + + if not admin_token or not user_id: + self.log("缺少必要数据,跳过用户状态管理测试", "ERROR") + return + + # 测试修改用户状态 + response = self.make_request('PUT', f'/admin/users/{user_id}/status', + data={ + 'status': 'locked', + 'reason': '测试锁定' + }, + headers={'Authorization': f'Bearer {admin_token}'}) + + self.assert_response( + response, + expected_success=True, + expected_fields=['data'], + test_name="修改用户状态" + ) + + # 测试用户状态统计 + response = self.make_request('GET', '/admin/users/status-stats', + headers={'Authorization': f'Bearer {admin_token}'}) + + self.assert_response( + response, + expected_success=True, + expected_fields=['data'], + test_name="用户状态统计" + ) + + def test_error_cases(self): + """测试错误情况""" + self.log("开始测试错误情况...") + + # 测试无效登录 + response = self.make_request('POST', '/auth/login', { + 'identifier': 'nonexistent', + 'password': 'wrongpassword' + }) + + self.assert_response( + response, + expected_success=False, + expected_fields=['error_code'], + test_name="无效用户登录" + ) + + # 测试无效验证码 + response = self.make_request('POST', '/auth/verify-email', { + 'email': self.test_email, + 'verification_code': '000000' + }) + + self.assert_response( + response, + expected_success=False, + test_name="无效验证码验证" + ) + + # 测试无权限访问管理员接口 + response = self.make_request('GET', '/admin/users') + + # 应该返回401或403 + if response.get('status_code') in [401, 403]: + self.assert_response( + response, + expected_success=False, + test_name="无权限访问管理员接口" + ) + + def test_rate_limiting(self): + """测试频率限制""" + self.log("开始测试频率限制...") + + # 快速连续发送验证码请求 + for i in range(3): + response = self.make_request('POST', '/auth/send-email-verification', { + 'email': f'ratelimit{i}@example.com' + }) + + if response.get('status_code') == 429: + self.assert_response( + response, + expected_success=False, + test_name="频率限制触发" + ) + break + + time.sleep(0.1) # 短暂延迟 + + def run_all_tests(self): + """运行所有测试""" + self.log("🚀 开始API接口测试...") + self.log(f"测试目标: {self.base_url}") + self.log(f"测试邮箱: {self.test_email}") + + start_time = time.time() + + try: + # 基础功能测试 + self.test_app_status() + + # 认证相关测试 + self.test_send_verification_code() + self.test_user_register() + self.test_user_login() + + # 管理员功能测试 + self.test_admin_login() + self.test_admin_get_users() + self.test_user_status_management() + + # 错误处理测试 + self.test_error_cases() + + # 安全功能测试 + self.test_rate_limiting() + + except KeyboardInterrupt: + self.log("测试被用户中断", "ERROR") + except Exception as e: + self.log(f"测试过程中发生异常: {str(e)}", "ERROR") + + # 输出测试结果 + end_time = time.time() + duration = end_time - start_time + + print("\n" + "="*60) + print("📊 测试结果统计") + print("="*60) + print(f"总测试数: {self.stats['total']}") + print(f"通过数量: {self.stats['passed']}") + print(f"失败数量: {self.stats['failed']}") + print(f"成功率: {(self.stats['passed']/self.stats['total']*100):.1f}%" if self.stats['total'] > 0 else "0%") + print(f"测试耗时: {duration:.2f}秒") + + if self.stats['errors']: + print("\n❌ 失败的测试:") + for error in self.stats['errors']: + print(f" {error}") + + if self.stats['failed'] == 0: + print("\n🎉 所有测试通过!") + return True + else: + print(f"\n⚠️ 有 {self.stats['failed']} 个测试失败") + return False + + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description='whaleTown API接口测试工具') + parser.add_argument('--base-url', default='https://whaletownend.xinghangee.icu', + help='API服务器地址 (默认: https://whaletownend.xinghangee.icu)') + parser.add_argument('--test-email', default='test@example.com', + help='测试邮箱地址 (默认: test@example.com)') + parser.add_argument('--verbose', '-v', action='store_true', + help='显示详细日志') + + args = parser.parse_args() + + # 创建测试器并运行测试 + tester = APITester( + base_url=args.base_url, + test_email=args.test_email, + verbose=args.verbose + ) + + success = tester.run_all_tests() + + # 根据测试结果设置退出码 + sys.exit(0 if success else 1) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/tests/api/simple_api_test.py b/tests/api/simple_api_test.py new file mode 100644 index 0000000..1fca453 --- /dev/null +++ b/tests/api/simple_api_test.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +简化版API接口测试脚本 + +快速验证API接口的基本连通性和功能 +""" + +import requests +import json +import time +from datetime import datetime + + +class SimpleAPITester: + def __init__(self, base_url="https://whaletownend.xinghangee.icu"): + self.base_url = base_url.rstrip('/') + self.session = requests.Session() + self.session.headers.update({ + 'Content-Type': 'application/json', + 'User-Agent': 'whaleTown-Simple-Tester/1.0' + }) + + def log(self, message): + timestamp = datetime.now().strftime("%H:%M:%S") + print(f"[{timestamp}] {message}") + + def test_endpoint(self, method, endpoint, data=None, description=""): + """测试单个端点""" + url = f"{self.base_url}{endpoint}" + + try: + if method.upper() == 'GET': + response = self.session.get(url, timeout=10) + elif method.upper() == 'POST': + response = self.session.post(url, json=data, timeout=10) + + self.log(f"✅ {description}") + self.log(f" 状态码: {response.status_code}") + + try: + result = response.json() + self.log(f" 响应: {json.dumps(result, ensure_ascii=False, indent=2)[:200]}...") + return result + except: + self.log(f" 响应: {response.text[:100]}...") + return {"status_code": response.status_code, "text": response.text} + + except Exception as e: + self.log(f"❌ {description} - 错误: {str(e)}") + return None + + def run_basic_tests(self): + """运行基础测试""" + self.log("🚀 开始基础API测试...") + self.log(f"测试目标: {self.base_url}") + + # 1. 测试应用状态 + self.test_endpoint('GET', '/', description="应用状态检查") + + # 2. 测试发送验证码 + result = self.test_endpoint('POST', '/auth/send-email-verification', + {'email': 'test@example.com'}, + "发送邮箱验证码") + + # 3. 如果获取到验证码,测试注册 + if result and 'data' in result and 'verification_code' in result['data']: + verification_code = result['data']['verification_code'] + self.log(f"📧 获取到验证码: {verification_code}") + + # 测试用户注册(需要验证码) + username = f"testuser_{int(time.time())}" + self.test_endpoint('POST', '/auth/register', { + 'username': username, + 'password': 'Test123456', + 'nickname': '测试用户', + 'email': 'test@example.com', + 'email_verification_code': verification_code # 添加验证码 + }, "用户注册") + + # 测试用户登录 + self.test_endpoint('POST', '/auth/login', { + 'identifier': username, + 'password': 'Test123456' + }, "用户登录") + + # 4. 测试错误情况 + self.test_endpoint('POST', '/auth/login', { + 'identifier': 'nonexistent', + 'password': 'wrongpassword' + }, "无效登录测试") + + # 5. 测试管理员登录(可能失败) + self.test_endpoint('POST', '/admin/auth/login', { + 'identifier': 'admin', + 'password': 'Admin123456' + }, "管理员登录测试") + + self.log("🎯 基础测试完成!") + + +if __name__ == '__main__': + import sys + + base_url = sys.argv[1] if len(sys.argv) > 1 else "https://whaletownend.xinghangee.icu" + + tester = SimpleAPITester(base_url) + tester.run_basic_tests() \ No newline at end of file diff --git a/tests/auth/README.md b/tests/auth/README.md new file mode 100644 index 0000000..817e81f --- /dev/null +++ b/tests/auth/README.md @@ -0,0 +1,165 @@ +# 认证UI测试 + +这个文件夹包含了登录和注册界面的UI响应测试,具有美观优雅的反馈系统。 + +## 文件说明 + +- `auth_ui_test.tscn` - 测试场景文件 +- `auth_ui_test.gd` - 测试脚本 +- `README.md` - 本说明文件 + +## 如何运行测试 + +### 方法1:在Godot编辑器中运行 +1. 在Godot编辑器中打开 `tests/auth/auth_ui_test.tscn` 文件 +2. 点击编辑器右上角的"播放场景"按钮(或按F6键) +3. 如果仍然跳转到主场景,请尝试以下步骤: + - 确保当前打开的是测试场景文件 + - 在场景面板中右键点击根节点,选择"Change Scene" + - 或者在项目设置中临时更改主场景为测试场景 + +### 方法2:通过代码运行 +在任何脚本中添加以下代码来切换到测试场景: +```gdscript +get_tree().change_scene_to_file("res://tests/auth/auth_ui_test.tscn") +``` + +## 界面布局 + +### 左侧:认证界面 +- 完整的登录和注册界面 +- 实时表单验证 +- Toast消息显示 +- 字段错误提示 + +### 右侧:测试控制面板 +#### 🧪 UI响应测试标题 +#### 📋 测试反馈面板 +- **智能反馈系统**:实时显示测试执行状态 +- **颜色编码**: + - 🔵 蓝色:信息提示 + - 🟢 绿色:成功响应 + - 🔴 红色:错误响应 +- **详细信息**: + - 测试名称和类型 + - HTTP状态码 + - 响应消息和错误代码 + - 预期的UI反馈效果 +- **自动滚动**:新消息自动滚动到可见区域 + +#### 🎯 测试用例按钮 +16个精美设计的测试按钮,按功能分组: + +## 测试功能 + +### 登录测试(4个) +- ✅ **登录成功**(状态码200) + - 预期:绿色Toast "登录成功!正在进入鲸鱼镇...",跳转到主场景 +- ❌ **用户名或密码错误**(状态码401) + - 预期:红色Toast "用户名或密码错误,请检查后重试" +- ❌ **用户不存在**(状态码404) + - 预期:红色Toast "用户不存在,请先注册" +- ❌ **参数错误**(状态码400) + - 预期:红色Toast,根据具体错误显示 + +### 发送验证码测试(5个) +- ✅ **发送成功**(状态码200) + - 预期:绿色Toast "验证码已发送到您的邮箱" +- ❌ **邮箱格式错误**(状态码400) + - 预期:红色Toast "请输入有效的邮箱地址" +- ❌ **请求过频**(状态码429) + - 预期:红色Toast "请求过于频繁,请稍后再试" +- ❌ **服务器错误**(状态码500) + - 预期:红色Toast "服务器繁忙,请稍后再试" +- ❌ **网络错误**(状态码0) + - 预期:红色Toast "网络连接失败,请检查网络连接" + +### 邮箱验证测试(3个) +- ✅ **验证成功**(状态码200) + - 预期:绿色Toast "邮箱验证成功,正在注册..." +- ❌ **验证码错误**(状态码400) + - 预期:红色Toast "验证码错误或已过期" +- ❌ **验证码不存在**(状态码404) + - 预期:红色Toast "请先获取验证码" + +### 注册测试(6个) +- ✅ **注册成功**(状态码201) + - 预期:绿色Toast "注册成功!欢迎加入鲸鱼镇",切换到登录界面 +- ❌ **参数错误**(状态码400) + - 预期:红色Toast,根据具体错误显示 +- ❌ **缺少验证码**(状态码400) + - 预期:红色Toast "请先获取并输入邮箱验证码" +- ❌ **用户已存在**(状态码409) + - 预期:红色Toast "用户名或邮箱已被使用,请换一个" +- ❌ **请求过频**(状态码429) + - 预期:红色Toast "注册请求过于频繁,请稍后再试" +- ❌ **服务器错误**(状态码500) + - 预期:红色Toast "注册失败,请稍后再试" + +## 测试步骤 + +1. **启动测试场景** + - 运行测试场景后,右侧反馈面板会显示欢迎信息和使用说明 + +2. **准备测试数据** + - 在左侧认证界面切换到注册界面 + - 填写测试数据: + - 用户名:testuser + - 邮箱:test@example.com + - 密码:password123 + - 确认密码:password123 + - 验证码:123456 + +3. **执行测试** + - 点击右侧对应的测试按钮 + - 观察反馈面板中的详细信息: + - 🚀 测试开始提示 + - 📡 请求类型和状态码 + - ⚡ HTTP响应模拟 + - 🎯 预期UI反馈说明 + - ✅/❌ 测试完成状态 + +4. **观察UI反馈** + - 左侧界面的实际反馈效果 + - Toast消息的颜色和内容 + - 字段错误提示的显示 + - 按钮状态的变化 + +## 反馈系统特色 + +### 🎨 美观设计 +- 使用Emoji图标增强视觉效果 +- 颜色编码区分不同状态 +- 圆角边框和阴影效果 +- 按钮分组和分隔符 + +### 📊 详细分析 +- 实时显示测试执行过程 +- 预期vs实际效果对比 +- HTTP响应详细解析 +- 错误代码和消息说明 + +### 🔄 交互体验 +- 自动滚动到最新消息 +- 按钮悬停效果 +- 平滑的颜色过渡 +- 响应式布局 + +## 注意事项 + +- 测试场景会自动断开`login_success`信号,避免跳转到主场景 +- 按ESC键可以优雅退出测试(显示退出提示) +- 所有测试都是模拟的,不会发送真实的网络请求 +- 反馈面板会保留测试历史,便于对比分析 +- 测试按钮按功能分组,每组之间有分隔符 + +## 预期行为验证 + +这个测试系统不仅模拟后端响应,还会告诉你每种情况下应该看到什么UI反馈,帮助你验证: + +1. **Toast消息系统**是否正常工作 +2. **字段验证错误**是否正确显示 +3. **按钮状态管理**是否符合预期 +4. **用户体验流程**是否顺畅 + +通过对比预期效果和实际效果,可以快速发现UI反馈系统中的问题并进行修复。 \ No newline at end of file diff --git a/tests/auth/auth_ui_test.gd b/tests/auth/auth_ui_test.gd new file mode 100644 index 0000000..7930df0 --- /dev/null +++ b/tests/auth/auth_ui_test.gd @@ -0,0 +1,464 @@ +extends Control + +# 认证UI测试场景 +# 用于测试登录和注册界面的各种响应情况 + +@onready var auth_scene: Control = $AuthScene +@onready var test_panel: Panel = $TestPanel +@onready var test_buttons: VBoxContainer = $TestPanel/VBoxContainer/ScrollContainer/TestButtons +@onready var feedback_panel: Panel = $TestPanel/VBoxContainer/FeedbackPanel +@onready var feedback_text: RichTextLabel = $TestPanel/VBoxContainer/FeedbackPanel/FeedbackContainer/FeedbackScroll/FeedbackText + +# 反馈样式 - 动态创建 +var feedback_styles = {} + +var test_scenarios = [ + { + "name": "✅ 登录成功", + "type": "login", + "response_code": 200, + "response_data": { + "success": true, + "data": { + "user": { + "id": "123", + "username": "testuser", + "email": "test@example.com" + }, + "token": "jwt_token_here" + }, + "message": "登录成功" + } + }, + { + "name": "❌ 登录失败-用户名或密码错误", + "type": "login", + "response_code": 401, + "response_data": { + "success": false, + "message": "用户名或密码错误", + "error_code": "INVALID_CREDENTIALS" + } + }, + { + "name": "❌ 登录失败-用户不存在", + "type": "login", + "response_code": 404, + "response_data": { + "success": false, + "message": "用户不存在", + "error_code": "USER_NOT_FOUND" + } + }, + { + "name": "❌ 登录失败-参数错误", + "type": "login", + "response_code": 400, + "response_data": { + "success": false, + "message": "用户名格式错误", + "error_code": "INVALID_PARAMETERS" + } + }, + { + "name": "✅ 发送验证码成功", + "type": "send_code", + "response_code": 200, + "response_data": { + "success": true, + "data": {"verification_code": "123456"}, + "message": "验证码已发送" + } + }, + { + "name": "❌ 发送验证码-邮箱格式错误", + "type": "send_code", + "response_code": 400, + "response_data": { + "success": false, + "message": "邮箱格式错误", + "error_code": "INVALID_EMAIL" + } + }, + { + "name": "❌ 发送验证码-请求过频", + "type": "send_code", + "response_code": 429, + "response_data": { + "success": false, + "message": "请求过于频繁", + "error_code": "TOO_MANY_REQUESTS" + } + }, + { + "name": "❌ 发送验证码-服务器错误", + "type": "send_code", + "response_code": 500, + "response_data": { + "success": false, + "message": "服务器内部错误", + "error_code": "INTERNAL_ERROR" + } + }, + { + "name": "❌ 发送验证码-网络错误", + "type": "send_code", + "response_code": 0, + "response_data": {} + }, + { + "name": "✅ 邮箱验证成功", + "type": "verify_email", + "response_code": 200, + "response_data": { + "success": true, + "message": "邮箱验证成功" + } + }, + { + "name": "❌ 邮箱验证-验证码错误", + "type": "verify_email", + "response_code": 400, + "response_data": { + "success": false, + "message": "验证码错误或已过期", + "error_code": "INVALID_CODE" + } + }, + { + "name": "❌ 邮箱验证-验证码不存在", + "type": "verify_email", + "response_code": 404, + "response_data": { + "success": false, + "message": "验证码不存在", + "error_code": "CODE_NOT_FOUND" + } + }, + { + "name": "✅ 注册成功", + "type": "register", + "response_code": 201, + "response_data": { + "success": true, + "data": { + "user": { + "id": "123", + "username": "testuser", + "email": "test@example.com" + } + }, + "message": "注册成功" + } + }, + { + "name": "❌ 注册失败-参数错误", + "type": "register", + "response_code": 400, + "response_data": { + "success": false, + "message": "用户名格式错误", + "error_code": "INVALID_USERNAME" + } + }, + { + "name": "❌ 注册失败-缺少验证码", + "type": "register", + "response_code": 400, + "response_data": { + "success": false, + "message": "提供邮箱时必须提供邮箱验证码", + "error_code": "REGISTER_FAILED" + } + }, + { + "name": "❌ 注册失败-用户已存在", + "type": "register", + "response_code": 409, + "response_data": { + "success": false, + "message": "用户名已存在", + "error_code": "USER_EXISTS" + } + }, + { + "name": "❌ 注册失败-请求过频", + "type": "register", + "response_code": 429, + "response_data": { + "success": false, + "message": "注册请求过于频繁,请5分钟后再试", + "error_code": "TOO_MANY_REQUESTS", + "throttle_info": { + "limit": 3, + "window_seconds": 300, + "current_requests": 3, + "reset_time": "2025-12-24T11:26:41.136Z" + } + } + }, + { + "name": "❌ 注册失败-服务器错误", + "type": "register", + "response_code": 500, + "response_data": { + "success": false, + "message": "服务器内部错误", + "error_code": "INTERNAL_ERROR" + } + } +] + +func _ready(): + print("🧪 认证UI测试场景已加载") + + # 初始化反馈样式 + create_feedback_styles() + + # 初始化反馈面板 + show_feedback("🎯 测试场景已准备就绪", "info") + add_feedback_line("💡 使用说明:") + add_feedback_line("1️⃣ 点击下方测试按钮模拟后端响应") + add_feedback_line("2️⃣ 观察左侧认证界面的UI反馈") + add_feedback_line("3️⃣ 按ESC键退出测试") + add_feedback_line("") + + # 确保auth_scene已准备好 + if auth_scene == null: + show_feedback("❌ 错误:无法找到AuthScene节点", "error") + return + + # 等待一帧确保所有节点都已初始化 + await get_tree().process_frame + setup_test_buttons() + + # 断开auth_scene的login_success信号,避免跳转到主场景 + if auth_scene.has_signal("login_success"): + var connections = auth_scene.get_signal_connection_list("login_success") + for connection in connections: + auth_scene.disconnect("login_success", connection.callable) + add_feedback_line("🔗 已断开login_success信号连接") + +# 创建反馈样式 +func create_feedback_styles(): + # 成功样式 - 绿色 + var success_style = StyleBoxFlat.new() + success_style.bg_color = Color(0.2, 0.8, 0.3, 0.9) + success_style.border_color = Color(0.1, 0.6, 0.2, 1) + success_style.border_width_left = 2 + success_style.border_width_top = 2 + success_style.border_width_right = 2 + success_style.border_width_bottom = 2 + success_style.corner_radius_top_left = 8 + success_style.corner_radius_top_right = 8 + success_style.corner_radius_bottom_left = 8 + success_style.corner_radius_bottom_right = 8 + + # 错误样式 - 红色 + var error_style = StyleBoxFlat.new() + error_style.bg_color = Color(0.8, 0.2, 0.2, 0.9) + error_style.border_color = Color(0.6, 0.1, 0.1, 1) + error_style.border_width_left = 2 + error_style.border_width_top = 2 + error_style.border_width_right = 2 + error_style.border_width_bottom = 2 + error_style.corner_radius_top_left = 8 + error_style.corner_radius_top_right = 8 + error_style.corner_radius_bottom_left = 8 + error_style.corner_radius_bottom_right = 8 + + # 信息样式 - 蓝色 + var info_style = StyleBoxFlat.new() + info_style.bg_color = Color(0.2, 0.5, 0.8, 0.9) + info_style.border_color = Color(0.1, 0.4, 0.6, 1) + info_style.border_width_left = 2 + info_style.border_width_top = 2 + info_style.border_width_right = 2 + info_style.border_width_bottom = 2 + info_style.corner_radius_top_left = 8 + info_style.corner_radius_top_right = 8 + info_style.corner_radius_bottom_left = 8 + info_style.corner_radius_bottom_right = 8 + + feedback_styles = { + "success": success_style, + "error": error_style, + "info": info_style + } + +func setup_test_buttons(): + # 创建测试按钮 + for i in range(test_scenarios.size()): + var scenario = test_scenarios[i] + var button = Button.new() + button.text = scenario.name + button.custom_minimum_size.y = 35 + button.pressed.connect(_on_test_button_pressed.bind(scenario)) + + # 为按钮添加样式 + var style_normal = StyleBoxFlat.new() + style_normal.bg_color = Color(0.9, 0.9, 0.9, 1) + style_normal.border_color = Color(0.7, 0.7, 0.7, 1) + style_normal.border_width_left = 1 + style_normal.border_width_top = 1 + style_normal.border_width_right = 1 + style_normal.border_width_bottom = 1 + style_normal.corner_radius_top_left = 4 + style_normal.corner_radius_top_right = 4 + style_normal.corner_radius_bottom_left = 4 + style_normal.corner_radius_bottom_right = 4 + + var style_hover = style_normal.duplicate() + style_hover.bg_color = Color(0.8, 0.8, 0.8, 1) + + button.add_theme_stylebox_override("normal", style_normal) + button.add_theme_stylebox_override("hover", style_hover) + button.add_theme_color_override("font_color", Color(0.2, 0.2, 0.2, 1)) + + test_buttons.add_child(button) + + # 添加分隔符(每4个按钮一组) + if (i + 1) % 4 == 0 and i < test_scenarios.size() - 1: + var separator = HSeparator.new() + separator.custom_minimum_size.y = 10 + test_buttons.add_child(separator) + + add_feedback_line("✨ 已创建 %d 个测试按钮" % test_scenarios.size()) + +func _on_test_button_pressed(scenario: Dictionary): + # 显示测试开始信息 + show_feedback("🚀 执行测试: %s" % scenario.name, "info") + add_feedback_line("📡 请求类型: %s" % scenario.type) + add_feedback_line("📊 状态码: %d" % scenario.response_code) + + # 确保auth_scene存在 + if auth_scene == null: + show_feedback("❌ 错误:AuthScene节点不存在", "error") + return + + # 模拟HTTP响应 + simulate_http_response(scenario) + +func simulate_http_response(scenario: Dictionary): + # 设置当前请求类型 + auth_scene.current_request_type = scenario.type + + # 准备模拟数据 + var response_code = scenario.response_code + var response_data = scenario.response_data + var response_text = JSON.stringify(response_data) + var response_body = response_text.to_utf8_buffer() + var headers = PackedStringArray() + + add_feedback_line("⚡ 模拟HTTP响应...") + + # 模拟HTTP请求完成 + auth_scene._on_http_request_completed(0, response_code, headers, response_body) + + # 分析响应结果 + analyze_response(scenario, response_data) + +func analyze_response(scenario: Dictionary, response_data: Dictionary): + var response_code = scenario.response_code + var is_success = response_code >= 200 and response_code < 300 + + # 显示响应分析 + if is_success: + show_feedback("✅ 测试完成 - 成功响应", "success") + else: + show_feedback("❌ 测试完成 - 错误响应", "error") + + # 显示详细信息 + if response_data.has("message"): + add_feedback_line("💬 响应消息: %s" % response_data.message) + + if response_data.has("error_code"): + add_feedback_line("🏷️ 错误代码: %s" % response_data.error_code) + + if response_data.has("data"): + add_feedback_line("📦 响应数据: %s" % str(response_data.data)) + + # 预期的UI反馈 + var expected_feedback = get_expected_ui_feedback(scenario) + add_feedback_line("🎯 预期UI反馈: %s" % expected_feedback) + + add_feedback_line("─────────────────────") + +func get_expected_ui_feedback(scenario: Dictionary) -> String: + var response_code = scenario.response_code + var type = scenario.type + + match type: + "login": + match response_code: + 200: + return "绿色Toast: 登录成功,跳转到主场景" + 400: + return "红色Toast: 登录信息格式错误" + 401: + return "红色Toast: 用户名或密码错误" + 404: + return "红色Toast: 用户不存在" + 429: + return "红色Toast: 登录请求过于频繁" + 500: + return "红色Toast: 服务器繁忙" + "send_code": + match response_code: + 200, 206: + return "绿色Toast: 验证码已发送" + 400: + return "红色Toast: 邮箱格式错误" + 429: + return "红色Toast: 请求过于频繁" + 500: + return "红色Toast: 服务器繁忙" + 0: + return "红色Toast: 网络连接失败" + "verify_email": + match response_code: + 200: + return "绿色Toast: 邮箱验证成功" + 400: + return "红色Toast: 验证码错误" + 404: + return "红色Toast: 请先获取验证码" + 500: + return "红色Toast: 验证失败" + "register": + match response_code: + 201: + return "绿色Toast: 注册成功,切换到登录界面" + 400: + return "红色Toast: 根据具体错误显示(验证码缺失、格式错误等)" + 409: + return "红色Toast: 用户名或邮箱已存在" + 429: + return "红色Toast: 注册请求过于频繁,请稍后再试" + 500: + return "红色Toast: 注册失败" + + return "未知响应" + +# 反馈面板功能 +func show_feedback(message: String, type: String = "info"): + # 更改面板样式 + if feedback_styles.has(type): + feedback_panel.add_theme_stylebox_override("panel", feedback_styles[type]) + + # 清空并显示新消息 + feedback_text.text = "[b]%s[/b]" % message + +func add_feedback_line(message: String): + feedback_text.text += "\n" + message + # 自动滚动到底部 + await get_tree().process_frame + var scroll = feedback_text.get_parent() + if scroll is ScrollContainer: + scroll.scroll_vertical = scroll.get_v_scroll_bar().max_value + +func _input(event): + if event.is_action_pressed("ui_cancel"): + show_feedback("👋 退出测试场景", "info") + await get_tree().create_timer(0.5).timeout + get_tree().quit() diff --git a/tests/auth/auth_ui_test.gd.uid b/tests/auth/auth_ui_test.gd.uid new file mode 100644 index 0000000..6255f7d --- /dev/null +++ b/tests/auth/auth_ui_test.gd.uid @@ -0,0 +1 @@ +uid://ddb8v5c6aeqe7 diff --git a/tests/auth/auth_ui_test.tscn b/tests/auth/auth_ui_test.tscn new file mode 100644 index 0000000..2cd159e --- /dev/null +++ b/tests/auth/auth_ui_test.tscn @@ -0,0 +1,117 @@ +[gd_scene load_steps=4 format=3 uid="uid://bvn8y7x2qkqxe"] + +[ext_resource type="Script" uid="uid://ddb8v5c6aeqe7" path="res://tests/auth/auth_ui_test.gd" id="1_test_script"] +[ext_resource type="PackedScene" uid="uid://by7m8snb4xllf" path="res://scenes/auth_scene.tscn" id="2_auth_scene"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_feedback_info"] +bg_color = Color(0.2, 0.5, 0.8, 0.9) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.1, 0.4, 0.6, 1) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[node name="AuthUITest" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_test_script") + +[node name="AuthScene" parent="." instance=ExtResource("2_auth_scene")] +layout_mode = 1 + +[node name="TestPanel" type="Panel" parent="."] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -350.0 +offset_top = 20.0 +offset_right = -20.0 +offset_bottom = 620.0 +grow_horizontal = 0 + +[node name="VBoxContainer" type="VBoxContainer" parent="TestPanel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 10.0 +offset_top = 10.0 +offset_right = -10.0 +offset_bottom = -10.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="TitleLabel" type="Label" parent="TestPanel/VBoxContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0.2, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 18 +text = "🧪 UI响应测试" +horizontal_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="TestPanel/VBoxContainer"] +layout_mode = 2 + +[node name="FeedbackPanel" type="Panel" parent="TestPanel/VBoxContainer"] +custom_minimum_size = Vector2(0, 120) +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_feedback_info") + +[node name="FeedbackContainer" type="VBoxContainer" parent="TestPanel/VBoxContainer/FeedbackPanel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 10.0 +offset_top = 8.0 +offset_right = -10.0 +offset_bottom = -8.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="FeedbackTitle" type="Label" parent="TestPanel/VBoxContainer/FeedbackPanel/FeedbackContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 1, 1, 1) +theme_override_font_sizes/font_size = 14 +text = "📋 测试反馈" + +[node name="FeedbackScroll" type="ScrollContainer" parent="TestPanel/VBoxContainer/FeedbackPanel/FeedbackContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="FeedbackText" type="RichTextLabel" parent="TestPanel/VBoxContainer/FeedbackPanel/FeedbackContainer/FeedbackScroll"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_colors/default_color = Color(1, 1, 1, 1) +theme_override_font_sizes/normal_font_size = 12 +bbcode_enabled = true +text = "点击下方测试按钮开始测试..." +fit_content = true +scroll_following = true + +[node name="HSeparator2" type="HSeparator" parent="TestPanel/VBoxContainer"] +layout_mode = 2 + +[node name="ButtonsLabel" type="Label" parent="TestPanel/VBoxContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0.2, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 14 +text = "🎯 测试用例" +horizontal_alignment = 1 + +[node name="ScrollContainer" type="ScrollContainer" parent="TestPanel/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="TestButtons" type="VBoxContainer" parent="TestPanel/VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3