Files
whale-town-front/tests/unit/test_chat_manager.gd
王浩 fb7cba4088 feat:实现聊天系统核心功能
- 添加 SocketIOClient.gd 实现 Socket.IO 协议封装
- 添加 WebSocketManager.gd 管理连接生命周期和自动重连
- 添加 ChatManager.gd 实现聊天业务逻辑与会话管理
  - 支持当前会话缓存(最多 100 条消息)
  - 支持历史消息按需加载(每次 100 条)
  - 每次登录/重连自动重置会话缓存
  - 客户端频率限制(10 条/分钟)
  - Token 管理与认证
- 添加 ChatMessage.gd/tscn 消息气泡 UI 组件
- 添加 ChatUI.gd/tscn 聊天界面
- 在 EventNames.gd 添加 7 个聊天事件常量
- 在 AuthManager.gd 添加 game_token 管理方法
- 添加完整的单元测试(128 个测试用例)
  - test_socketio_client.gd (42 个测试)
  - test_websocket_manager.gd (38 个测试)
  - test_chat_manager.gd (48 个测试)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-07 17:42:31 +08:00

358 lines
11 KiB
GDScript

extends GutTest
# ============================================================================
# test_chat_manager.gd - ChatManager 单元测试
# ============================================================================
# 测试聊天系统业务逻辑的功能
#
# 测试覆盖:
# - 初始化测试
# - Token 管理
# - 频率限制
# - 消息历史管理
# - 错误处理
# - 信号发射
# ============================================================================
var chat_manager: ChatManager
# ============================================================================
# 测试设置和清理
# ============================================================================
func before_each():
# 每个测试前创建新实例
chat_manager = ChatManager.new()
add_child(chat_manager)
func after_each():
# 每个测试后清理
if is_instance_valid(chat_manager):
chat_manager.queue_free()
chat_manager = null
# ============================================================================
# 初始化测试
# ============================================================================
func test_chat_manager_initialization():
# 测试管理器初始化
assert_not_null(chat_manager, "ChatManager 应该成功初始化")
assert_not_null(chat_manager._websocket_manager, "WebSocket 管理器应该被创建")
assert_not_null(chat_manager._socket_client, "Socket.IO 客户端应该被创建")
assert_false(chat_manager.is_connected(), "初始状态应该是未连接")
# ============================================================================
# Token 管理测试
# ============================================================================
func test_set_game_token():
# 测试设置游戏 token
chat_manager.set_game_token("test_token_123")
assert_eq(chat_manager.get_game_token(), "test_token_123",
"Token 应该被正确设置")
func test_get_game_token_initially_empty():
# 测试初始 token 为空
assert_eq(chat_manager.get_game_token(), "",
"初始 token 应该为空字符串")
func test_set_empty_token():
# 测试设置空 token
chat_manager.set_game_token("")
assert_eq(chat_manager.get_game_token(), "",
"空 token 应该被接受")
func test_update_token():
# 测试更新 token
chat_manager.set_game_token("token1")
assert_eq(chat_manager.get_game_token(), "token1", "第一个 token 应该被设置")
chat_manager.set_game_token("token2")
assert_eq(chat_manager.get_game_token(), "token2", "token 应该被更新")
# ============================================================================
# 频率限制测试
# ============================================================================
func test_can_send_message_initially():
# 测试初始状态可以发送消息
assert_true(chat_manager.can_send_message(),
"初始状态应该可以发送消息")
func test_can_send_message_after_one_send():
# 测试发送一条消息后仍可发送
chat_manager._record_message_timestamp()
assert_true(chat_manager.can_send_message(),
"发送一条消息后应该仍可以发送")
func test_rate_limit_exceeded():
# 测试达到频率限制
# 记录 10 条消息(达到限制)
for i in range(10):
chat_manager._record_message_timestamp()
assert_false(chat_manager.can_send_message(),
"达到频率限制后不应该可以发送消息")
func test_rate_limit_window_expires():
# 测试频率限制窗口过期
# 记录 10 条消息
for i in range(10):
chat_manager._record_message_timestamp()
# 模拟时间流逝(将时间戳设置为很久以前)
var old_time := Time.get_unix_time_from_system() - 100.0
chat_manager._message_timestamps = []
for i in range(10):
chat_manager._message_timestamps.append(old_time)
assert_true(chat_manager.can_send_message(),
"旧消息过期后应该可以发送新消息")
func test_get_time_until_next_message():
# 测试获取下次可发送消息的等待时间
chat_manager._message_timestamps = []
var wait_time := chat_manager.get_time_until_next_message()
assert_eq(wait_time, 0.0, "没有消息时等待时间应该为 0")
# ============================================================================
# 消息历史管理测试
# ============================================================================
func test_add_message_to_history():
# 测试添加消息到历史
var message := {
"from_user": "Alice",
"content": "Hello!",
"timestamp": 1000.0,
"is_self": false
}
chat_manager._add_message_to_history(message)
assert_eq(chat_manager._message_history.size(), 1, "历史应该有 1 条消息")
assert_eq(chat_manager._message_history[0].from_user, "Alice",
"消息发送者应该是 Alice")
func test_message_history_limit():
# 测试消息历史限制(最多 100 条)
# 添加 101 条消息
for i in range(101):
var message := {
"from_user": "User" + str(i),
"content": "Message " + str(i),
"timestamp": float(i),
"is_self": false
}
chat_manager._add_message_to_history(message)
assert_eq(chat_manager._message_history.size(), 100,
"消息历史应该被限制在 100 条")
func test_get_message_history():
# 测试获取消息历史
var message1 := {"from_user": "Alice", "content": "Hi", "timestamp": 100.0}
var message2 := {"from_user": "Bob", "content": "Hello", "timestamp": 200.0}
chat_manager._add_message_to_history(message1)
chat_manager._add_message_to_history(message2)
var history := chat_manager.get_message_history()
assert_eq(history.size(), 2, "应该返回 2 条消息")
assert_eq(history[0].content, "Hi", "第一条消息内容应该匹配")
func test_clear_message_history():
# 测试清空消息历史
var message := {"from_user": "Alice", "content": "Hi", "timestamp": 100.0}
chat_manager._add_message_to_history(message)
assert_eq(chat_manager._message_history.size(), 1, "应该有 1 条消息")
chat_manager.clear_message_history()
assert_eq(chat_manager._message_history.size(), 0, "历史应该被清空")
# ============================================================================
# 错误处理测试
# ============================================================================
func test_error_message_mapping():
# 测试错误消息映射
var error_codes := ["AUTH_FAILED", "RATE_LIMIT", "CONTENT_FILTERED",
"CONTENT_TOO_LONG", "PERMISSION_DENIED", "SESSION_EXPIRED",
"ZULIP_ERROR", "INTERNAL_ERROR"]
for code in error_codes:
assert_true(ChatManager.CHAT_ERROR_MESSAGES.has(code),
"错误码 " + code + " 应该有对应的错误消息")
func test_handle_error():
# 测试错误处理
watch_signals(chat_manager)
chat_manager._handle_error("AUTH_FAILED", "测试错误")
assert_signal_emitted(chat_manager, "chat_error_occurred",
"应该发射 chat_error_occurred 信号")
# ============================================================================
# 常量测试
# ============================================================================
func test_websocket_url_constant():
# 测试 WebSocket URL 常量
assert_eq(ChatManager.WEBSOCKET_URL, "wss://whaletownend.xinghangee.icu/game",
"WebSocket URL 应该匹配")
func test_rate_limit_constants():
# 测试频率限制常量
assert_eq(ChatManager.RATE_LIMIT_MESSAGES, 10,
"频率限制消息数应该是 10")
assert_eq(ChatManager.RATE_LIMIT_WINDOW, 60.0,
"频率限制时间窗口应该是 60 秒")
func test_message_limit_constants():
# 测试消息限制常量
assert_eq(ChatManager.MAX_MESSAGE_LENGTH, 1000,
"最大消息长度应该是 1000")
assert_eq(ChatManager.MAX_MESSAGE_HISTORY, 100,
"最大消息历史应该是 100")
# ============================================================================
# 信号测试
# ============================================================================
func test_chat_message_sent_signal():
# 测试消息发送信号
watch_signals(chat_manager)
chat_manager.emit_signal("chat_message_sent", "msg123", 1000.0)
assert_signal_emitted(chat_manager, "chat_message_sent",
"应该发射 chat_message_sent 信号")
func test_chat_message_received_signal():
# 测试消息接收信号
watch_signals(chat_manager)
chat_manager.emit_signal("chat_message_received", "Alice", "Hello!", true, 1000.0)
assert_signal_emitted(chat_manager, "chat_message_received",
"应该发射 chat_message_received 信号")
func test_chat_error_occurred_signal():
# 测试错误发生信号
watch_signals(chat_manager)
chat_manager.emit_signal("chat_error_occurred", "AUTH_FAILED", "认证失败")
assert_signal_emitted(chat_manager, "chat_error_occurred",
"应该发射 chat_error_occurred 信号")
func test_chat_connection_state_changed_signal():
# 测试连接状态变化信号
watch_signals(chat_manager)
chat_manager.emit_signal("chat_connection_state_changed",
WebSocketManager.ConnectionState.CONNECTED)
assert_signal_emitted(chat_manager, "chat_connection_state_changed",
"应该发射 chat_connection_state_changed 信号")
# ============================================================================
# 边界条件测试
# ============================================================================
func test_empty_message_content():
# 测试空消息内容
var message := {
"from_user": "Alice",
"content": "",
"timestamp": 1000.0,
"is_self": false
}
chat_manager._add_message_to_history(message)
assert_eq(chat_manager._message_history.size(), 1,
"空消息应该被添加到历史")
func test_very_long_message_content():
# 测试超长消息内容(边界测试)
var long_content := "x".repeat(1000)
var message := {
"from_user": "Alice",
"content": long_content,
"timestamp": 1000.0,
"is_self": false
}
chat_manager._add_message_to_history(message)
assert_eq(chat_manager._message_history[0].content.length(), 1000,
"超长消息应该被保留")
func test_unicode_in_message():
# 测试消息中的 Unicode 字符
var message := {
"from_user": "玩家",
"content": "你好,世界!🎮🎉",
"timestamp": 1000.0,
"is_self": false
}
chat_manager._add_message_to_history(message)
assert_eq(chat_manager._message_history[0].content, "你好,世界!🎮🎉",
"Unicode 内容应该被正确保留")
func test_zero_timestamp():
# 测试零时间戳
var message := {
"from_user": "Alice",
"content": "Hello",
"timestamp": 0.0,
"is_self": false
}
chat_manager._add_message_to_history(message)
assert_eq(chat_manager._message_history[0].timestamp, 0.0,
"零时间戳应该被保留")
# ============================================================================
# 消息时间戳记录测试
# ============================================================================
func test_record_message_timestamp():
# 测试记录消息时间戳
chat_manager._record_message_timestamp()
assert_eq(chat_manager._message_timestamps.size(), 1,
"应该记录 1 个时间戳")
func test_multiple_message_timestamps():
# 测试记录多个消息时间戳
for i in range(5):
chat_manager._record_message_timestamp()
assert_eq(chat_manager._message_timestamps.size(), 5,
"应该记录 5 个时间戳")
func test_timestamps_in_order():
# 测试时间戳应该按顺序记录
chat_manager._record_message_timestamp()
await get_tree().process_frame
chat_manager._record_message_timestamp()
assert_gt(chat_manager._message_timestamps[1],
chat_manager._message_timestamps[0],
"后来的时间戳应该更大")