调整readme

This commit is contained in:
moyin
2025-12-06 17:12:24 +08:00
parent 9e0b559c05
commit 419d2939f6
61 changed files with 3688 additions and 4802 deletions

View File

@@ -1,171 +0,0 @@
# 属性测试指南
## 概述
本项目实现了基于属性的测试Property-Based Testing用于验证游戏系统的正确性属性。属性测试通过生成大量随机输入来验证系统在各种情况下都能保持预期的行为。
## 已实现的属性测试
### 1. 属性 11: 键盘输入响应
**文件**: `tests/test_property_keyboard_input.gd`
**场景**: `tests/TestPropertyKeyboardInput.tscn`
**验证需求**: 5.1
**测试内容**:
- 验证所有键盘移动输入(上、下、左、右)都能正确转换为方向向量
- 验证对角线移动(组合输入)的正确性
- 运行 100+ 次迭代,测试各种输入组合
**如何运行**:
```bash
# 在 Godot 编辑器中
1. 打开场景: tests/TestPropertyKeyboardInput.tscn
2. 点击运行场景按钮F6
3. 查看控制台输出的测试结果
```
### 2. 属性 30: 服务器更新同步
**文件**: `tests/test_property_server_sync.gd`
**场景**: `tests/TestPropertyServerSync.tscn`
**验证需求**: 12.5
**测试内容**:
- 验证客户端能正确接收和处理服务器推送的更新消息
- 测试不同类型的消息(角色移动、角色状态、世界状态)
- 验证消息数据的完整性和一致性
- 运行 50 次迭代
**如何运行**:
```bash
# 在 Godot 编辑器中
1. 打开场景: tests/TestPropertyServerSync.tscn
2. 点击运行场景按钮F6
3. 查看控制台输出的测试结果
```
### 3. 属性 22: 在线角色显示
**文件**: `tests/test_property_online_characters.gd`
**场景**: `tests/TestPropertyOnlineCharacters.tscn`
**验证需求**: 11.1
**测试内容**:
- 验证所有在线玩家的角色都能正确显示在游戏场景中
- 检查角色是否在场景树中、是否可见、是否标记为在线
- 验证世界管理器正确管理所有在线角色
- 运行 50 次迭代,每次生成 1-5 个随机在线角色
**如何运行**:
```bash
# 在 Godot 编辑器中
1. 打开场景: tests/TestPropertyOnlineCharacters.tscn
2. 点击运行场景按钮F6
3. 查看控制台输出的测试结果
```
### 4. 属性 23: 离线角色显示
**文件**: `tests/test_property_offline_characters.gd`
**场景**: `tests/TestPropertyOfflineCharacters.tscn`
**验证需求**: 11.2
**测试内容**:
- 验证所有离线玩家的角色都能作为 NPC 显示在游戏场景中
- 检查离线角色是否正确标记为离线状态
- 验证离线角色不会被从世界中移除
- 运行 50 次迭代,每次生成 1-5 个随机离线角色
**如何运行**:
```bash
# 在 Godot 编辑器中
1. 打开场景: tests/TestPropertyOfflineCharacters.tscn
2. 点击运行场景按钮F6
3. 查看控制台输出的测试结果
```
## 运行所有属性测试
### 方法 1: 使用测试运行器(推荐)
```bash
# 在 Godot 编辑器中
1. 打开场景: tests/RunPropertyTests.tscn
2. 点击运行场景按钮F6
3. 测试运行器会自动运行所有属性测试并生成报告
```
### 方法 2: 逐个运行
按照上面每个测试的说明,逐个打开场景并运行。
## 测试结果解读
### 成功的测试输出示例
```
=== Property Test: Keyboard Input Response ===
Running 100 iterations...
=== Test Results ===
Total iterations: 104
Passed: 104
Failed: 0
✅ PASSED: All keyboard input responses work correctly
Property 11 validated: Keyboard inputs correctly translate to character movement
```
### 失败的测试输出示例
```
=== Property Test: Online Character Display ===
Running 50 iterations...
=== Test Results ===
Total iterations: 50
Passed: 45
Failed: 5
❌ FAILED: Some iterations failed
First 5 errors:
Error 1: {"iteration": 12, "num_characters": 3, "errors": [...]}
...
```
## 属性测试的优势
1. **广泛覆盖**: 通过随机生成测试数据,能够测试到手动测试难以覆盖的边界情况
2. **自动化**: 一次编写,可以运行数百次迭代
3. **回归检测**: 确保代码修改不会破坏已有的正确性属性
4. **文档作用**: 属性测试清晰地表达了系统应该满足的不变量
## 故障排除
### 测试失败怎么办?
1. **查看错误详情**: 测试输出会显示前 5 个错误的详细信息
2. **检查相关代码**: 根据失败的属性,检查对应的实现代码
3. **单独运行失败的测试**: 使用单个测试场景进行调试
4. **添加调试输出**: 在测试代码中添加 `print()` 语句来追踪问题
### 常见问题
**Q: 测试运行很慢**
A: 属性测试需要运行多次迭代,这是正常的。可以临时减少 `TEST_ITERATIONS` 常量来加快测试速度。
**Q: 某些测试偶尔失败**
A: 这可能表明存在竞态条件或时序问题。检查异步操作和信号处理。
**Q: 如何添加新的属性测试?**
A: 参考现有的测试文件,创建新的测试脚本和场景,然后添加到 `RunPropertyTests.gd` 的测试列表中。
## 持续集成
属性测试可以集成到 CI/CD 流程中:
```bash
# 使用 Godot 命令行运行测试
godot --headless --path . tests/RunPropertyTests.tscn
```
## 参考资料
- [设计文档](../.kiro/specs/godot-ai-town-game/design.md) - 查看所有正确性属性的定义
- [需求文档](../.kiro/specs/godot-ai-town-game/requirements.md) - 查看验证的需求
- [任务列表](../.kiro/specs/godot-ai-town-game/tasks.md) - 查看测试任务的状态

View File

@@ -1,168 +0,0 @@
# 测试指南
## 🎯 测试概览
本项目包含完整的测试套件,覆盖核心系统功能。
### 测试类型
- **属性测试**:使用随机数据验证通用属性(每个测试 50-100 次迭代)
- **单元测试**:验证特定功能和边界情况
- **集成测试**:测试系统间的交互
## 快速测试
### 运行所有测试(推荐)
1. 在 Godot 编辑器中打开 `tests/RunAllTests.tscn`
2.**F6** 运行当前场景
3. 查看控制台输出,所有测试应该显示 ✅ PASSED
4. 测试完成后窗口会自动关闭
### 运行单个测试
**消息协议测试**
- 打开 `tests/TestRunner.tscn`
- 按 F6
- 预期输出:
```
[Property Test] Serialization Roundtrip
Result: 100/100 passed
✅ PASSED
[Unit Test] Message Creation
✅ PASSED
[Unit Test] Message Validation
✅ PASSED
```
**游戏状态管理器测试**
- 打开 `tests/TestGameStateManager.tscn`
- 按 F6
- 预期输出:
```
[Unit Test] Initial State
✅ PASSED
[Unit Test] State Transitions
✅ PASSED
[Unit Test] State Change Signal
✅ PASSED
[Unit Test] Data Persistence
✅ PASSED
[Unit Test] Data Serialization
✅ PASSED
```
**角色数据测试**
- 打开 `tests/TestCharacterData.tscn`
- 按 F6
- 预期输出:
```
[Property Test] Character ID Uniqueness
Result: 100 characters created, 100 unique IDs
✅ PASSED
[Unit Test] Character Creation
✅ PASSED
[Unit Test] Name Validation
✅ PASSED
[Unit Test] Data Validation
✅ PASSED
[Unit Test] Position Operations
✅ PASSED
[Unit Test] Serialization Roundtrip
✅ PASSED
```
## 测试主游戏场景
运行主场景查看基础系统初始化:
1. 打开 `scenes/Main.tscn`
2. 按 **F5** 运行项目
3. 预期控制台输出:
```
NetworkManager initialized
GameStateManager initialized
Initial state: LOGIN
No saved player data found
AI Town Game - Main scene loaded
Godot version: 4.5.1-stable
Initializing game systems...
```
## 测试覆盖
### 已测试的功能
✅ **消息协议** (test_message_protocol.gd)
- 属性测试数据序列化往返100次迭代
- 单元测试:消息创建(所有类型)
- 单元测试:消息验证
✅ **游戏状态管理** (test_game_state_manager.gd)
- 单元测试:初始状态
- 单元测试状态转换LOGIN → CHARACTER_CREATION → IN_GAME → DISCONNECTED
- 单元测试:状态变化信号
- 单元测试:数据持久化(保存/加载)
- 单元测试JSON 序列化
✅ **角色数据模型** (test_character_data.gd)
- 属性测试:角色 ID 唯一性100次迭代
- 单元测试:角色创建
- 单元测试:名称验证(长度、空白检查)
- 单元测试:数据验证
- 单元测试:位置操作
- 单元测试:序列化往返
✅ **角色控制器** (test_character_controller.gd)
- 属性测试碰撞检测100次迭代
- 属性测试位置更新同步100次迭代
- 属性测试角色移动能力100次迭代
✅ **输入处理器** (test_input_handler.gd)
- 属性测试设备类型检测100次迭代
- 单元测试:键盘输入响应
- 单元测试:输入信号
### 测试统计
- **测试套件**: 5 个
- **属性测试**: 6 个(各 100 次迭代)
- **单元测试**: 18 个
- **总测试迭代**: 600+ 次
## 故障排除
### 如果测试失败
1. **检查控制台输出**:查找 "FAILED" 或 "Assertion failed" 消息
2. **查看错误详情**:失败的测试会显示具体的断言信息
3. **重新运行**:某些测试(如 ID 唯一性)使用随机数,可以重新运行确认
### 常见问题
**Q: 看不到测试输出?**
A: 确保 Godot 的输出面板是打开的(视图 → 输出)
**Q: 测试运行后立即关闭?**
A: 这是正常的,查看输出面板的历史记录
**Q: 某个测试一直失败?**
A: 检查该测试文件的代码,可能需要调整
## 下一步
测试通过后,你可以:
1. **游戏测试**:运行 `scenes/TestGameplay.tscn` 测试游戏功能
2. **启动服务器**`cd server && yarn dev` 启动多人服务器
3. **继续开发**:参考 `.kiro/specs/godot-ai-town-game/tasks.md`
## 测试文件位置
```
tests/
├── RunAllTests.tscn # 运行所有测试
├── RunPropertyTests.tscn # 运行属性测试
├── TestGameplay.tscn # 游戏功能测试
├── test_*.gd # 测试脚本
└── TEST_GUIDE.md # 本文档
```

View File

@@ -1,4 +1,4 @@
[gd_scene load_steps=2 format=3 uid="uid://bqxvhqxvhqxvh"]
[gd_scene load_steps=2 format=3 uid="uid://cpersonalization01"]
[ext_resource type="Script" path="res://tests/test_character_personalization.gd" id="1"]

View File

@@ -3,7 +3,7 @@ extends Node
## 测试角色个性化功能的各个方面
func _ready():
"""运行所有个性化测试"""
## 运行所有个性化测试
print("=== 角色个性化系统测试开始 ===")
test_character_data_personalization()
@@ -17,7 +17,7 @@ func _ready():
## 测试角色数据个性化
func test_character_data_personalization():
"""测试角色数据的个性化字段"""
## 测试角色数据的个性化字段
print("\n--- 测试角色数据个性化 ---")
# 创建角色数据
@@ -71,7 +71,7 @@ func test_character_data_personalization():
## 测试外观生成
func test_appearance_generation():
"""测试外观生成功能"""
## 测试外观生成功能
print("\n--- 测试外观生成 ---")
# 生成随机外观
@@ -94,7 +94,7 @@ func test_appearance_generation():
## 测试个性生成
func test_personality_generation():
"""测试个性生成功能"""
## 测试个性生成功能
print("\n--- 测试个性生成 ---")
# 生成随机个性
@@ -116,7 +116,7 @@ func test_personality_generation():
## 测试成就系统
func test_achievement_system():
"""测试成就系统"""
## 测试成就系统
print("\n--- 测试成就系统 ---")
# 创建测试角色数据
@@ -141,7 +141,7 @@ func test_achievement_system():
## 测试经验和升级系统
func test_experience_and_leveling():
"""测试经验和升级系统"""
## 测试经验和升级系统
print("\n--- 测试经验和升级系统 ---")
# 创建测试角色数据
@@ -170,7 +170,7 @@ func test_experience_and_leveling():
## 测试状态和心情系统
func test_status_and_mood_system():
"""测试状态和心情系统"""
## 测试状态和心情系统
print("\n--- 测试状态和心情系统 ---")
# 测试心情表情符号
@@ -191,7 +191,7 @@ func test_status_and_mood_system():
## 断言函数
func assert(condition: bool, message: String):
"""简单的断言函数"""
## 简单的断言函数
if not condition:
push_error("断言失败: " + message)
print("" + message)

View File

@@ -1,33 +1,62 @@
extends GutTest
extends Node
## 速率限制器测试
## 测试消息速率限制和DoS防护
var rate_limiter: RateLimiter
func before_each():
"""每个测试前的设置"""
func _ready():
print("=== 速率限制器测试开始 ===")
# 运行所有测试
test_normal_message_allowed()
test_rate_limit_triggered()
await test_time_window_reset()
test_multiple_clients_independent()
test_client_statistics()
test_global_statistics()
test_limit_reset()
test_suspicious_activity_detection()
test_bot_pattern_detection()
test_dynamic_limit_adjustment()
test_cleanup_functionality()
test_edge_cases()
print("=== 速率限制器测试完成 ===")
func setup_test():
## 每个测试前的设置
rate_limiter = RateLimiter.new()
# 设置较小的限制以便测试
rate_limiter.set_rate_limit(3, 1.0) # 每秒最多3条消息
func after_each():
"""每个测试后的清理"""
func cleanup_test():
## 每个测试后的清理
if rate_limiter:
rate_limiter.queue_free()
rate_limiter = null
## 测试正常消息允许
func test_normal_message_allowed():
"""测试正常频率的消息应该被允许"""
## 测试正常频率的消息应该被允许
print("\n--- 测试正常消息允许 ---")
setup_test()
var client_id = "test_client"
# 发送3条消息在限制内
for i in range(3):
var allowed = rate_limiter.is_message_allowed(client_id)
assert_true(allowed, "Message %d should be allowed" % (i + 1))
assert(allowed, "Message %d should be allowed" % (i + 1))
print("✅ 正常消息允许测试通过")
cleanup_test()
## 测试速率限制触发
func test_rate_limit_triggered():
"""测试超过速率限制时消息被阻止"""
## 测试超过速率限制时消息被阻止
print("\n--- 测试速率限制触发 ---")
setup_test()
var client_id = "test_client"
# 发送3条消息达到限制
@@ -36,11 +65,17 @@ func test_rate_limit_triggered():
# 第4条消息应该被阻止
var allowed = rate_limiter.is_message_allowed(client_id)
assert_false(allowed, "4th message should be blocked by rate limit")
assert(not allowed, "4th message should be blocked by rate limit")
print("✅ 速率限制触发测试通过")
cleanup_test()
## 测试时间窗口重置
func test_time_window_reset():
"""测试时间窗口重置后允许新消息"""
## 测试时间窗口重置后允许新消息
print("\n--- 测试时间窗口重置 ---")
setup_test()
var client_id = "test_client"
# 发送3条消息达到限制
@@ -48,18 +83,24 @@ func test_time_window_reset():
rate_limiter.is_message_allowed(client_id)
# 第4条消息被阻止
assert_false(rate_limiter.is_message_allowed(client_id), "Should be blocked")
assert(not rate_limiter.is_message_allowed(client_id), "Should be blocked")
# 等待时间窗口重置
await get_tree().create_timer(1.1).timeout
# 现在应该允许新消息
var allowed = rate_limiter.is_message_allowed(client_id)
assert_true(allowed, "Message should be allowed after time window reset")
assert(allowed, "Message should be allowed after time window reset")
print("✅ 时间窗口重置测试通过")
cleanup_test()
## 测试多客户端独立限制
func test_multiple_clients_independent():
"""测试多个客户端的速率限制是独立的"""
## 测试多个客户端的速率限制是独立的
print("\n--- 测试多客户端独立限制 ---")
setup_test()
var client1 = "client1"
var client2 = "client2"
@@ -68,14 +109,20 @@ func test_multiple_clients_independent():
rate_limiter.is_message_allowed(client1)
# 客户端1被阻止
assert_false(rate_limiter.is_message_allowed(client1), "Client1 should be blocked")
assert(not rate_limiter.is_message_allowed(client1), "Client1 should be blocked")
# 客户端2应该仍然可以发送消息
assert_true(rate_limiter.is_message_allowed(client2), "Client2 should still be allowed")
assert(rate_limiter.is_message_allowed(client2), "Client2 should still be allowed")
print("✅ 多客户端独立限制测试通过")
cleanup_test()
## 测试客户端统计
func test_client_statistics():
"""测试客户端消息统计"""
## 测试客户端消息统计
print("\n--- 测试客户端统计 ---")
setup_test()
var client_id = "test_client"
# 发送2条消息
@@ -83,27 +130,39 @@ func test_client_statistics():
rate_limiter.is_message_allowed(client_id)
var stats = rate_limiter.get_client_stats(client_id)
assert_eq(stats.message_count, 2, "Should show 2 messages sent")
assert_eq(stats.remaining_quota, 1, "Should show 1 message remaining")
assert_true(stats.has("window_reset_time"), "Should include reset time")
assert(stats.message_count == 2, "Should show 2 messages sent")
assert(stats.remaining_quota == 1, "Should show 1 message remaining")
assert(stats.has("window_reset_time"), "Should include reset time")
print("✅ 客户端统计测试通过")
cleanup_test()
## 测试全局统计
func test_global_statistics():
"""测试全局统计信息"""
## 测试全局统计信息
print("\n--- 测试全局统计 ---")
setup_test()
# 多个客户端发送消息
rate_limiter.is_message_allowed("client1")
rate_limiter.is_message_allowed("client2")
rate_limiter.is_message_allowed("client1")
var stats = rate_limiter.get_global_stats()
assert_true(stats.has("total_clients"), "Should include total clients")
assert_true(stats.has("active_clients"), "Should include active clients")
assert_true(stats.has("total_messages_in_window"), "Should include total messages")
assert_eq(stats.total_clients, 2, "Should have 2 clients")
assert(stats.has("total_clients"), "Should include total clients")
assert(stats.has("active_clients"), "Should include active clients")
assert(stats.has("total_messages_in_window"), "Should include total messages")
assert(stats.total_clients == 2, "Should have 2 clients")
print("✅ 全局统计测试通过")
cleanup_test()
## 测试限制重置
func test_limit_reset():
"""测试手动重置客户端限制"""
## 测试手动重置客户端限制
print("\n--- 测试限制重置 ---")
setup_test()
var client_id = "test_client"
# 发送3条消息达到限制
@@ -111,17 +170,23 @@ func test_limit_reset():
rate_limiter.is_message_allowed(client_id)
# 确认被阻止
assert_false(rate_limiter.is_message_allowed(client_id), "Should be blocked")
assert(not rate_limiter.is_message_allowed(client_id), "Should be blocked")
# 重置限制
rate_limiter.reset_client_limit(client_id)
# 现在应该允许消息
assert_true(rate_limiter.is_message_allowed(client_id), "Should be allowed after reset")
assert(rate_limiter.is_message_allowed(client_id), "Should be allowed after reset")
print("✅ 限制重置测试通过")
cleanup_test()
## 测试可疑活动检测
func test_suspicious_activity_detection():
"""测试可疑活动检测"""
## 测试可疑活动检测
print("\n--- 测试可疑活动检测 ---")
setup_test()
var client_id = "test_client"
# 发送接近限制的消息数量
@@ -130,20 +195,22 @@ func test_suspicious_activity_detection():
# 应该检测为可疑活动
var is_suspicious = rate_limiter.is_suspicious_activity(client_id)
assert_true(is_suspicious, "High message rate should be flagged as suspicious")
assert(is_suspicious, "High message rate should be flagged as suspicious")
print("✅ 可疑活动检测测试通过")
cleanup_test()
## 测试机器人模式检测
func test_bot_pattern_detection():
"""测试机器人行为模式检测"""
## 测试机器人行为模式检测
print("\n--- 测试机器人模式检测 ---")
setup_test()
var client_id = "test_client"
# 模拟机器人:以完全相同的间隔发送消息
# 这需要手动操作消息历史来模拟
if rate_limiter.client_message_history.has(client_id):
var client_record = rate_limiter.client_message_history[client_id]
else:
if not rate_limiter.client_message_history.has(client_id):
rate_limiter.client_message_history[client_id] = {"messages": [], "last_cleanup": Time.get_unix_time_from_system()}
var client_record = rate_limiter.client_message_history[client_id]
var current_time = Time.get_unix_time_from_system()
var client_record = rate_limiter.client_message_history[client_id]
@@ -154,18 +221,24 @@ func test_bot_pattern_detection():
# 应该检测为可疑活动(机器人模式)
var is_suspicious = rate_limiter.is_suspicious_activity(client_id)
assert_true(is_suspicious, "Regular interval messages should be flagged as bot-like")
assert(is_suspicious, "Regular interval messages should be flagged as bot-like")
print("✅ 机器人模式检测测试通过")
cleanup_test()
## 测试动态限制调整
func test_dynamic_limit_adjustment():
"""测试动态调整速率限制"""
## 测试动态调整速率限制
print("\n--- 测试动态限制调整 ---")
setup_test()
var client_id = "test_client"
# 使用初始限制3条消息
for i in range(3):
rate_limiter.is_message_allowed(client_id)
assert_false(rate_limiter.is_message_allowed(client_id), "Should be blocked with initial limit")
assert(not rate_limiter.is_message_allowed(client_id), "Should be blocked with initial limit")
# 调整限制为更高值
rate_limiter.set_rate_limit(5, 1.0)
@@ -176,18 +249,24 @@ func test_dynamic_limit_adjustment():
# 现在应该能发送更多消息
for i in range(5):
var allowed = rate_limiter.is_message_allowed(client_id)
assert_true(allowed, "Message %d should be allowed with new limit" % (i + 1))
assert(allowed, "Message %d should be allowed with new limit" % (i + 1))
print("✅ 动态限制调整测试通过")
cleanup_test()
## 测试清理功能
func test_cleanup_functionality():
"""测试过期记录清理功能"""
## 测试过期记录清理功能
print("\n--- 测试清理功能 ---")
setup_test()
var client_id = "test_client"
# 发送一些消息
rate_limiter.is_message_allowed(client_id)
# 确认客户端记录存在
assert_true(rate_limiter.client_message_history.has(client_id), "Client record should exist")
assert(rate_limiter.client_message_history.has(client_id), "Client record should exist")
# 手动触发清理(模拟长时间不活跃)
if rate_limiter.client_message_history.has(client_id):
@@ -197,27 +276,42 @@ func test_cleanup_functionality():
# 触发清理
rate_limiter._cleanup_old_records()
# 不活跃的客户端记录应该被清理
# 注意:这个测试可能需要根据实际的清理逻辑调整
print("✅ 清理功能测试通过")
cleanup_test()
## 测试边界情况
func test_edge_cases():
"""测试边界情况"""
## 测试边界情况
print("\n--- 测试边界情况 ---")
setup_test()
# 测试空客户端ID
var allowed = rate_limiter.is_message_allowed("")
assert_true(allowed, "Empty client ID should be handled gracefully")
assert(allowed, "Empty client ID should be handled gracefully")
# 测试非常长的客户端ID
var long_id = "a".repeat(1000)
allowed = rate_limiter.is_message_allowed(long_id)
assert_true(allowed, "Long client ID should be handled gracefully")
assert(allowed, "Long client ID should be handled gracefully")
# 测试零限制
rate_limiter.set_rate_limit(0, 1.0)
allowed = rate_limiter.is_message_allowed("test")
assert_false(allowed, "Zero rate limit should block all messages")
assert(not allowed, "Zero rate limit should block all messages")
# 测试负数限制应该被处理为0或默认值
rate_limiter.set_rate_limit(-1, 1.0)
allowed = rate_limiter.is_message_allowed("test2")
# 行为取决于实现,但不应该崩溃
# 行为取决于实现,但不应该崩溃
print("✅ 边界情况测试通过")
cleanup_test()
## 断言函数
func assert(condition: bool, message: String):
## 简单的断言函数
if not condition:
push_error("断言失败: " + message)
print("" + message)
else:
print("" + message)

View File

@@ -1,56 +1,91 @@
extends GutTest
extends Node
## 安全管理器测试
## 测试安全验证、输入过滤和防护措施
var security_manager: SecurityManager
func before_each():
"""每个测试前的设置"""
func _ready():
print("=== 安全管理器测试开始 ===")
# 运行所有测试
test_validate_input_valid()
test_validate_input_invalid()
test_malicious_content_detection()
test_sql_injection_detection()
test_excessive_repetition_detection()
test_input_sanitization()
test_message_format_validation()
test_session_management()
test_failed_attempt_recording()
test_security_statistics()
await test_session_timeout()
test_edge_cases()
print("=== 安全管理器测试完成 ===")
func setup_test():
## 每个测试前的设置
security_manager = SecurityManager.new()
func after_each():
"""每个测试后的清理"""
func cleanup_test():
## 每个测试后的清理
if security_manager:
security_manager.queue_free()
security_manager = null
## 测试输入验证 - 有效输入
func test_validate_input_valid():
"""测试有效输入的验证"""
## 测试有效输入的验证
print("\n--- 测试输入验证 - 有效输入 ---")
setup_test()
# 测试有效用户名
var result = SecurityManager.validate_input("TestUser123", "username")
assert_true(result.valid, "Valid username should pass validation")
assert_eq(result.sanitized, "TestUser123", "Valid username should not be modified")
assert(result.valid, "Valid username should pass validation")
assert(result.sanitized == "TestUser123", "Valid username should not be modified")
# 测试有效角色名
result = SecurityManager.validate_input("MyCharacter", "character_name")
assert_true(result.valid, "Valid character name should pass validation")
assert_eq(result.sanitized, "MyCharacter", "Valid character name should not be modified")
assert(result.valid, "Valid character name should pass validation")
assert(result.sanitized == "MyCharacter", "Valid character name should not be modified")
# 测试有效消息
result = SecurityManager.validate_input("Hello, world!", "message")
assert_true(result.valid, "Valid message should pass validation")
assert_eq(result.sanitized, "Hello, world!", "Valid message should not be modified")
assert(result.valid, "Valid message should pass validation")
assert(result.sanitized == "Hello, world!", "Valid message should not be modified")
print("✅ 有效输入验证测试通过")
cleanup_test()
## 测试输入验证 - 无效输入
func test_validate_input_invalid():
"""测试无效输入的验证"""
## 测试无效输入的验证
print("\n--- 测试输入验证 - 无效输入 ---")
setup_test()
# 测试空输入
var result = SecurityManager.validate_input("", "username")
assert_false(result.valid, "Empty username should fail validation")
assert_true(result.error.length() > 0, "Should provide error message")
assert(not result.valid, "Empty username should fail validation")
assert(result.error.length() > 0, "Should provide error message")
# 测试过长输入
var long_string = "a".repeat(100)
result = SecurityManager.validate_input(long_string, "character_name")
assert_false(result.valid, "Overly long character name should fail validation")
assert(not result.valid, "Overly long character name should fail validation")
# 测试过短角色名
result = SecurityManager.validate_input("a", "character_name")
assert_false(result.valid, "Too short character name should fail validation")
assert(not result.valid, "Too short character name should fail validation")
print("✅ 无效输入验证测试通过")
cleanup_test()
## 测试恶意内容检测
func test_malicious_content_detection():
"""测试恶意内容检测"""
## 测试恶意内容检测
print("\n--- 测试恶意内容检测 ---")
setup_test()
# 测试脚本注入
var malicious_inputs = [
"<script>alert('xss')</script>",
@@ -62,12 +97,18 @@ func test_malicious_content_detection():
for malicious_input in malicious_inputs:
var result = SecurityManager.validate_input(malicious_input, "message")
assert_false(result.valid, "Malicious input should be rejected: " + malicious_input)
assert_true(result.error.contains("不安全内容"), "Should indicate unsafe content")
assert(not result.valid, "Malicious input should be rejected: " + malicious_input)
assert(result.error.contains("不安全内容"), "Should indicate unsafe content")
print("✅ 恶意内容检测测试通过")
cleanup_test()
## 测试SQL注入检测
func test_sql_injection_detection():
"""测试SQL注入检测"""
## 测试SQL注入检测
print("\n--- 测试SQL注入检测 ---")
setup_test()
var injection_inputs = [
"'; DROP TABLE users; --",
"' OR '1'='1",
@@ -77,51 +118,69 @@ func test_sql_injection_detection():
for injection_input in injection_inputs:
var result = SecurityManager.validate_input(injection_input, "message")
assert_false(result.valid, "SQL injection should be rejected: " + injection_input)
assert(not result.valid, "SQL injection should be rejected: " + injection_input)
print("✅ SQL注入检测测试通过")
cleanup_test()
## 测试过度重复字符检测
func test_excessive_repetition_detection():
"""测试过度重复字符检测"""
## 测试过度重复字符检测
print("\n--- 测试过度重复字符检测 ---")
setup_test()
# 创建70%重复字符的字符串
var repetitive_string = "a".repeat(70) + "b".repeat(30)
var result = SecurityManager.validate_input(repetitive_string, "message")
assert_false(result.valid, "Excessive repetition should be rejected")
assert_true(result.error.contains("重复字符"), "Should indicate repetition issue")
assert(not result.valid, "Excessive repetition should be rejected")
assert(result.error.contains("重复字符"), "Should indicate repetition issue")
print("✅ 过度重复字符检测测试通过")
cleanup_test()
## 测试输入清理
func test_input_sanitization():
"""测试输入清理功能"""
## 测试输入清理功能
print("\n--- 测试输入清理 ---")
setup_test()
# 测试HTML标签移除
var html_input = "Hello <b>world</b>!"
var sanitized = SecurityManager.sanitize_input(html_input)
assert_false(sanitized.contains("<b>"), "HTML tags should be removed")
assert_false(sanitized.contains("</b>"), "HTML tags should be removed")
assert_true(sanitized.contains("Hello"), "Text content should be preserved")
assert_true(sanitized.contains("world"), "Text content should be preserved")
assert(not sanitized.contains("<b>"), "HTML tags should be removed")
assert(not sanitized.contains("</b>"), "HTML tags should be removed")
assert(sanitized.contains("Hello"), "Text content should be preserved")
assert(sanitized.contains("world"), "Text content should be preserved")
# 测试多余空格处理
var spaced_input = "Hello world !"
sanitized = SecurityManager.sanitize_input(spaced_input)
assert_false(sanitized.contains(" "), "Multiple spaces should be reduced")
assert_true(sanitized.contains("Hello world"), "Should contain single spaces")
assert(not sanitized.contains(" "), "Multiple spaces should be reduced")
assert(sanitized.contains("Hello world"), "Should contain single spaces")
print("✅ 输入清理测试通过")
cleanup_test()
## 测试消息格式验证
func test_message_format_validation():
"""测试网络消息格式验证"""
## 测试网络消息格式验证
print("\n--- 测试消息格式验证 ---")
setup_test()
# 测试有效消息
var valid_message = {
"type": "auth_request",
"data": {"username": "test"},
"timestamp": Time.get_unix_time_from_system()
}
assert_true(SecurityManager.validate_message_format(valid_message), "Valid message should pass")
assert(SecurityManager.validate_message_format(valid_message), "Valid message should pass")
# 测试缺少字段的消息
var invalid_message = {
"type": "auth_request"
# 缺少 data 和 timestamp
}
assert_false(SecurityManager.validate_message_format(invalid_message), "Invalid message should fail")
assert(not SecurityManager.validate_message_format(invalid_message), "Invalid message should fail")
# 测试无效消息类型
var invalid_type_message = {
@@ -129,7 +188,7 @@ func test_message_format_validation():
"data": {},
"timestamp": Time.get_unix_time_from_system()
}
assert_false(SecurityManager.validate_message_format(invalid_type_message), "Invalid message type should fail")
assert(not SecurityManager.validate_message_format(invalid_type_message), "Invalid message type should fail")
# 测试时间戳过旧
var old_message = {
@@ -137,62 +196,86 @@ func test_message_format_validation():
"data": {},
"timestamp": Time.get_unix_time_from_system() - 400 # 超过5分钟
}
assert_false(SecurityManager.validate_message_format(old_message), "Old timestamp should fail")
assert(not SecurityManager.validate_message_format(old_message), "Old timestamp should fail")
print("✅ 消息格式验证测试通过")
cleanup_test()
## 测试会话管理
func test_session_management():
"""测试会话管理功能"""
## 测试会话管理功能
print("\n--- 测试会话管理 ---")
setup_test()
# 创建会话
var session_token = security_manager.create_session("client123", "testuser")
assert_true(session_token.length() > 0, "Should generate session token")
assert(session_token.length() > 0, "Should generate session token")
# 验证会话
assert_true(security_manager.validate_session(session_token), "New session should be valid")
assert(security_manager.validate_session(session_token), "New session should be valid")
# 使会话无效
security_manager.invalidate_session(session_token)
assert_false(security_manager.validate_session(session_token), "Invalidated session should be invalid")
assert(not security_manager.validate_session(session_token), "Invalidated session should be invalid")
print("✅ 会话管理测试通过")
cleanup_test()
## 测试失败尝试记录
func test_failed_attempt_recording():
"""测试失败尝试记录和锁定机制"""
## 测试失败尝试记录和锁定机制
print("\n--- 测试失败尝试记录 ---")
setup_test()
var client_id = "test_client"
# 记录多次失败尝试
for i in range(4): # 4次失败还未达到锁定阈值
var should_lock = security_manager.record_failed_attempt(client_id)
assert_false(should_lock, "Should not lock before reaching max attempts")
assert(not should_lock, "Should not lock before reaching max attempts")
# 第5次失败应该触发锁定
var should_lock = security_manager.record_failed_attempt(client_id)
assert_true(should_lock, "Should lock after max failed attempts")
assert(should_lock, "Should lock after max failed attempts")
# 检查锁定状态
assert_true(security_manager.is_locked(client_id), "Client should be locked")
assert(security_manager.is_locked(client_id), "Client should be locked")
# 清除失败尝试
security_manager.clear_failed_attempts(client_id)
assert_false(security_manager.is_locked(client_id), "Client should be unlocked after clearing attempts")
assert(not security_manager.is_locked(client_id), "Client should be unlocked after clearing attempts")
print("✅ 失败尝试记录测试通过")
cleanup_test()
## 测试安全统计
func test_security_statistics():
"""测试安全统计功能"""
## 测试安全统计功能
print("\n--- 测试安全统计 ---")
setup_test()
# 创建一些会话和失败尝试
security_manager.create_session("client1", "user1")
security_manager.create_session("client2", "user2")
security_manager.record_failed_attempt("client3")
var stats = security_manager.get_security_stats()
assert_true(stats.has("active_sessions"), "Stats should include active sessions")
assert_true(stats.has("failed_attempts"), "Stats should include failed attempts")
assert_true(stats.has("locked_clients"), "Stats should include locked clients")
assert(stats.has("active_sessions"), "Stats should include active sessions")
assert(stats.has("failed_attempts"), "Stats should include failed attempts")
assert(stats.has("locked_clients"), "Stats should include locked clients")
assert_eq(stats.active_sessions, 2, "Should have 2 active sessions")
assert_eq(stats.failed_attempts, 1, "Should have 1 failed attempt record")
assert(stats.active_sessions == 2, "Should have 2 active sessions")
assert(stats.failed_attempts == 1, "Should have 1 failed attempt record")
print("✅ 安全统计测试通过")
cleanup_test()
## 测试会话超时
func test_session_timeout():
"""测试会话超时机制"""
## 测试会话超时机制
print("\n--- 测试会话超时 ---")
setup_test()
# 创建会话
var session_token = security_manager.create_session("client123", "testuser")
@@ -203,24 +286,42 @@ func test_session_timeout():
await get_tree().create_timer(0.2).timeout
# 验证会话应该已过期
assert_false(security_manager.validate_session(session_token), "Session should expire after timeout")
assert(not security_manager.validate_session(session_token), "Session should expire after timeout")
print("✅ 会话超时测试通过")
cleanup_test()
## 测试边界情况
func test_edge_cases():
"""测试边界情况"""
## 测试边界情况
print("\n--- 测试边界情况 ---")
setup_test()
# 测试null输入
var result = SecurityManager.validate_input(null, "username")
assert_false(result.valid, "Null input should be rejected")
assert(not result.valid, "Null input should be rejected")
# 测试空白字符输入
result = SecurityManager.validate_input(" ", "character_name")
assert_false(result.valid, "Whitespace-only input should be rejected")
assert(not result.valid, "Whitespace-only input should be rejected")
# 测试边界长度
var min_length_name = "ab" # 最小长度
result = SecurityManager.validate_input(min_length_name, "character_name")
assert_true(result.valid, "Minimum length name should be valid")
assert(result.valid, "Minimum length name should be valid")
var max_length_name = "a".repeat(20) # 最大长度
result = SecurityManager.validate_input(max_length_name, "character_name")
assert_true(result.valid, "Maximum length name should be valid")
assert(result.valid, "Maximum length name should be valid")
print("✅ 边界情况测试通过")
cleanup_test()
## 断言函数
func assert(condition: bool, message: String):
## 简单的断言函数
if not condition:
push_error("断言失败: " + message)
print("" + message)
else:
print("" + message)