diff --git a/docs/TEST_REPORT.md b/docs/TEST_REPORT.md new file mode 100644 index 0000000000000000000000000000000000000000..8b591c4c0bbdfdc6c4751ceb624c1c9af5d823b6 --- /dev/null +++ b/docs/TEST_REPORT.md @@ -0,0 +1,69 @@ +# InterConnect-Server 测试总结报告 + +## 1. 测试概览 + +本项目已完成全面的自动化测试套件构建,覆盖了核心业务逻辑、数据库操作、认证机制以及 API 接口。 + +- **测试框架**: Jest +- **API 测试工具**: Supertest +- **测试结果**: ✅ 全部通过 (30/30 测试用例) +- **测试时间**: 2026-01-23 + +## 2. 测试覆盖范围 + +测试套件分为单元测试 (Unit Tests) 和集成测试 (Integration Tests) 两部分。 + +### 2.1 单元测试 (Unit Tests) + +针对各个独立模块进行的功能验证。 + +#### 认证模块 (`src/auth.js`) +- **测试文件**: `tests/unit/auth.test.js` +- **覆盖内容**: + - `verifyApiKey`: 验证 API Key 的提取、校验和错误处理(401 Unauthorized)。 + - `requireAdminKey`: 验证管理员权限控制。 + - `requireRegularOrAdminKey`: 验证普通用户与管理员的权限兼容性。 + - `requireAnyKey`: 验证基础访问权限。 + +#### 数据库模块 (`src/database.js`) +- **测试文件**: `tests/unit/database.test.js` +- **覆盖内容**: + - **初始化**: 验证数据库表结构 (`api_keys`, `event_logs`, `ai_config`) 的自动创建。 + - **密钥管理**: 验证 Admin Key 的初始生成、Regular Key 和 Server Key 的创建与关联。 + - **验证逻辑**: 验证 `verifyApiKey` 的准确性。 + - **事件日志**: 验证 `logEvent` 和 `getRecentEvents` 的读写功能。 + - **AI 配置**: 验证 AI 配置信息的保存、读取和更新。 + +#### WebSocket 管理器 (`src/websocket.js`) +- **测试文件**: `tests/unit/websocket.test.js` +- **覆盖内容**: + - **连接管理**: 验证连接跟踪 (`connect`) 和断开处理 (`disconnect`)。 + - **广播功能**: 验证 `broadcastToAll` 能正确向所有活跃连接发送消息。 + - **异常处理**: 验证在广播过程中自动清理已关闭的连接。 + +### 2.2 集成测试 (Integration Tests) + +针对 HTTP API 接口的端到端测试。 + +#### API 接口 (`src/routes/*.js`) +- **测试文件**: `tests/integration/api.test.js` +- **覆盖内容**: + - **基础端点**: + - `GET /`: 验证服务器欢迎信息。 + - `GET /health`: 验证健康检查接口返回 `healthy` 状态。 + - **受保护路由**: + - `POST /api/events`: 验证无 Key、无效 Key 和有效 Key 的访问控制及业务逻辑。 + - **密钥管理 API**: + - `GET /manage/keys`: 验证 Admin 只有权访问。 + - `POST /manage/keys`: 验证密钥创建流程。 + +## 3. 测试环境配置 + +为了支持测试,对项目进行了以下配置(现已清理): +1. **依赖**: 安装了 `jest` 和 `supertest`。 +2. **配置**: 添加了 `jest.config.js` 和 `tests/setup.js`。 +3. **代码调整**: `src/server.js` 采用了条件启动模式,允许测试框架导入 App 实例而不自动监听端口。 + +## 4. 结论 + +当前代码库的核心功能稳定,权限控制逻辑严密,数据库操作符合预期。所有测试用例均已通过,可以放心地进行部署或后续开发。 diff --git a/hf_repo/.gitignore b/hf_repo/.gitignore index dc1638545f197128dcb850f5ed0791d2133e1af1..01699a3f6bf13d32c00b827f7a769ac49f205d54 100644 --- a/hf_repo/.gitignore +++ b/hf_repo/.gitignore @@ -57,3 +57,4 @@ cli/*.log *.dockerfile +ICS-测试数据.txt diff --git a/hf_repo/hf_repo/cli/cli.js b/hf_repo/hf_repo/cli/cli.js index cb79ee0985ac9181dee33ed26142fda018b0ea76..868be113d5a52cdebb09ca59005c3b7ac36b3261 100644 --- a/hf_repo/hf_repo/cli/cli.js +++ b/hf_repo/hf_repo/cli/cli.js @@ -111,8 +111,10 @@ class MinecraftWSCLIClient { return true; } - async healthCheck() { - return await this.request('GET', '/health'); + async resetAIConfig() { + this.ensureAdminKeyForManagement(); + await this.request('DELETE', '/api/ai/config'); + return true; } } @@ -125,6 +127,22 @@ program .option('-s, --server-url ', 'API服务器URL', process.env.MC_WS_API_URL || HARDCODED_API_URL) .option('-k, --admin-key ', '用于管理操作的Admin Key', process.env.ADMIN_KEY || HARDCODED_ADMIN_KEY); +program + .command('reset-ai-config') + .description('重置/清除 AI 配置 (使用 Admin Key)') + .action(async () => { + try { + const opts = program.opts(); + const client = new MinecraftWSCLIClient(opts.serverUrl, opts.adminKey); + await client.resetAIConfig(); + console.log(`\x1b[32m✅ AI 配置已成功重置/清除!\x1b[0m`); + console.log('现在您可以重新在 Dashboard 配置 AI 设置。'); + } catch (error) { + console.error(`\x1b[31m❌ 错误: ${error.message}\x1b[0m`); + process.exit(1); + } + }); + program .command('create-key ') .description('创建新的API密钥') diff --git a/hf_repo/hf_repo/dashboard/public/app.js b/hf_repo/hf_repo/dashboard/public/app.js index 4948be20bae648f050142e5d62f18c1cd66e8a37..7d598555193c2c907b24f3bfe922136cc80813fd 100644 --- a/hf_repo/hf_repo/dashboard/public/app.js +++ b/hf_repo/hf_repo/dashboard/public/app.js @@ -35,8 +35,83 @@ const aiSystemPromptInput = document.getElementById('ai-system-prompt'); const aiEnabledCheckbox = document.getElementById('ai-enabled'); const aiTestBtn = document.getElementById('ai-test-btn'); const aiDeleteBtn = document.getElementById('ai-delete-btn'); +const aiProviderSelect = document.getElementById('ai-provider-select'); +const systemLogs = document.getElementById('system-logs'); +const clearLogsBtn = document.getElementById('clear-logs-btn'); + +// AI Providers Configuration +const AI_PROVIDERS = { + openai: { + url: 'https://api.openai.com/v1/chat/completions', + model: 'gpt-3.5-turbo' + }, + siliconflow: { + url: 'https://api.siliconflow.cn/v1/chat/completions', + model: 'deepseek-ai/DeepSeek-R1' + }, + gemini: { + url: 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions', + model: 'gemini-2.0-flash-exp' + }, + deepseek: { + url: 'https://api.deepseek.com/chat/completions', + model: 'deepseek-chat' + }, + moonshot: { + url: 'https://api.moonshot.cn/v1/chat/completions', + model: 'moonshot-v1-8k' + }, + custom: { + url: '', + model: '' + } +}; + +if (aiProviderSelect) { + aiProviderSelect.addEventListener('change', (e) => { + const provider = AI_PROVIDERS[e.target.value]; + if (provider && e.target.value !== 'custom') { + if (aiApiUrlInput) aiApiUrlInput.value = provider.url; + if (aiModelIdInput) aiModelIdInput.value = provider.model; + } + }); +} + const aiStatus = document.getElementById('ai-status'); +// Theme Management +const themeToggleBtns = document.querySelectorAll('.theme-toggle'); + +function initTheme() { + const savedTheme = localStorage.getItem('theme') || 'dark'; + document.documentElement.setAttribute('data-theme', savedTheme); + updateThemeIcons(savedTheme); +} + +function toggleTheme() { + const currentTheme = document.documentElement.getAttribute('data-theme'); + const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; + + document.documentElement.setAttribute('data-theme', newTheme); + localStorage.setItem('theme', newTheme); + updateThemeIcons(newTheme); +} + +function updateThemeIcons(theme) { + const text = theme === 'dark' ? '浅色模式' : '深色模式'; + + themeToggleBtns.forEach(btn => { + btn.textContent = text; + }); +} + +// Initialize Theme +initTheme(); + +themeToggleBtns.forEach(btn => { + btn.addEventListener('click', toggleTheme); +}); + function authHeaders(key) { return { Authorization: `Bearer ${key}` }; } @@ -172,33 +247,102 @@ function renderAdminKeys(keys) { return; } - adminKeysList.innerHTML = keys.map((key) => ` -
+ const adminKeys = keys.filter(k => k.keyType === 'admin'); + const regularKeys = keys.filter(k => k.keyType === 'regular'); + const serverKeys = keys.filter(k => k.keyType === 'server'); + const serverKeysMap = {}; + + serverKeys.forEach(key => { + if (key.regularKeyId) { + if (!serverKeysMap[key.regularKeyId]) { + serverKeysMap[key.regularKeyId] = []; + } + serverKeysMap[key.regularKeyId].push(key); + } + }); + + let html = ''; + + // Render Admin Keys + if (adminKeys.length > 0) { + html += '

管理员密钥 (Admin Keys)

'; + html += adminKeys.map(key => renderKeyCard(key)).join(''); + } + + // Render Regular Keys with nested Server Keys + if (regularKeys.length > 0) { + html += '

用户密钥 (Regular Keys)

'; + html += regularKeys.map(regularKey => { + const childServerKeys = serverKeysMap[regularKey.id] || []; + return ` +
+
+ ${renderKeyCard(regularKey)} + ${childServerKeys.length > 0 ? `` : ''} +
+ ${childServerKeys.length > 0 ? ` + + ` : ''} +
+ `; + }).join(''); + } + + // Render Orphaned Server Keys (if any) + const linkedServerKeyIds = new Set(Object.values(serverKeysMap).flat().map(k => k.id)); + const orphanServerKeys = serverKeys.filter(k => !linkedServerKeyIds.has(k.id)); + + if (orphanServerKeys.length > 0) { + html += '

独立服务器密钥 (Orphan Server Keys)

'; + html += orphanServerKeys.map(key => renderKeyCard(key)).join(''); + } + + adminKeysList.innerHTML = html; +} + +function toggleGroup(headerElement) { + const nestedContainer = headerElement.nextElementSibling; + const toggleIcon = headerElement.querySelector('.toggle-icon'); + + if (nestedContainer && nestedContainer.classList.contains('nested-server-keys')) { + nestedContainer.classList.toggle('hidden'); + if (toggleIcon) { + toggleIcon.style.transform = nestedContainer.classList.contains('hidden') ? 'rotate(0deg)' : 'rotate(180deg)'; + } + } +} + +function renderKeyCard(key, isNested = false) { + return ` +

${key.keyType === 'admin' ? 'Admin' : key.keyType === 'server' ? 'Server' : 'Regular'} - ${key.isActive ? 'Active' : 'Inactive'} + ${key.isActive ? '已启用' : '已停用'} ${key.name}

ID: ${key.id}

Prefix: ${key.keyPrefix}

${key.serverId ? `

Server ID: ${key.serverId}

` : ''} -

Created: ${new Date(key.createdAt).toLocaleString()}

-

Last Used: ${key.lastUsed ? new Date(key.lastUsed).toLocaleString() : 'Never'}

+

创建时间: ${new Date(key.createdAt).toLocaleString()}

+

最后使用: ${key.lastUsed ? new Date(key.lastUsed).toLocaleString() : '从未'}

${key.isActive - ? `` - : `` + ? `` + : `` } - +
- `).join(''); + `; } function renderUserServerKeys(keys) { @@ -217,20 +361,20 @@ function renderUserServerKeys(keys) {

Server - ${key.isActive ? 'Active' : 'Inactive'} + ${key.isActive ? '已启用' : '已停用'} ${key.name}

ID: ${key.id}

Prefix: ${key.keyPrefix}

${key.serverId ? `

Server ID: ${key.serverId}

` : ''} -

Created: ${new Date(key.createdAt).toLocaleString()}

-

Last Used: ${key.lastUsed ? new Date(key.lastUsed).toLocaleString() : 'Never'}

+

创建时间: ${new Date(key.createdAt).toLocaleString()}

+

最后使用: ${key.lastUsed ? new Date(key.lastUsed).toLocaleString() : '从未'}

${key.isActive - ? `` - : `` + ? `` + : `` }
@@ -293,7 +437,7 @@ async function deleteKey(keyId, keyName) { if (currentRole !== 'admin') { return; } - if (!confirm(`Delete key "${keyName}"? This action cannot be undone.`)) { + if (!confirm(`确定要删除密钥 "${keyName}" 吗? 此操作无法撤销。`)) { return; } try { @@ -365,21 +509,6 @@ async function loadStats() { connections.textContent = data.active_ws || 0; } - const totalKeys = document.getElementById('user-stat-total-keys'); - if (totalKeys) { - totalKeys.textContent = data.keys_total || 0; - } - - const adminKeys = document.getElementById('user-stat-admin-keys'); - if (adminKeys) { - adminKeys.textContent = data.admin_active || 0; - } - - const regularKeys = document.getElementById('user-stat-regular-keys'); - if (regularKeys) { - regularKeys.textContent = data.regular_active || 0; - } - const serverKeys = document.getElementById('user-stat-server-keys'); if (serverKeys) { serverKeys.textContent = data.server_active || 0; @@ -737,7 +866,7 @@ async function deleteAIConfig() { if (!apiKey) { return; } - if (!confirm('Are you sure you want to delete the AI configuration?')) { + if (!confirm('确定要删除 AI 配置吗?')) { return; } diff --git a/hf_repo/hf_repo/dashboard/public/index.html b/hf_repo/hf_repo/dashboard/public/index.html index ca06a8a7cbaf41bfd8bed0674ca4fea03c984cfe..0dff2f3dbfabe960d232125afddcfa0cfb060c76 100644 --- a/hf_repo/hf_repo/dashboard/public/index.html +++ b/hf_repo/hf_repo/dashboard/public/index.html @@ -3,7 +3,10 @@ - Minecraft WebSocket API - 控制台 + InterConnect Dashboard + + + @@ -28,6 +31,9 @@