diff --git a/src/db/issueMap.js b/src/db/issueMap.js index 027959e..85d5348 100644 --- a/src/db/issueMap.js +++ b/src/db/issueMap.js @@ -18,16 +18,4 @@ function saveMapping(repoKey, giteaId, jiraKey, jiraId) { stmt.run(repoKey, giteaId, jiraKey, jiraId); } -//获取指定仓库的所有映射记录 -function getMappingsByRepo(repoKey) { - const rows = db.prepare('SELECT gitea_id, jira_key, jira_id FROM issue_mapping WHERE repo_key = ?').all(repoKey); - return rows; -} - -//统计各仓库的同步数量 -function getStats() { - const rows = db.prepare('SELECT repo_key, COUNT(*) as count FROM issue_mapping GROUP BY repo_key').all(); - return rows; -} - -module.exports = { getJiraKey, getGiteaInfo, saveMapping, getMappingsByRepo, getStats }; \ No newline at end of file +module.exports = { getJiraKey, getGiteaInfo, saveMapping }; \ No newline at end of file diff --git a/src/utils/tests_created_by_claude/cleanup-test-issues.js b/src/utils/tests_created_by_claude/cleanup-test-issues.js deleted file mode 100644 index d140be5..0000000 --- a/src/utils/tests_created_by_claude/cleanup-test-issues.js +++ /dev/null @@ -1,196 +0,0 @@ -/** - * 测试工单清理脚本 - * 清理所有标题包含 [TEST] 的测试工单 - */ - -const axios = require('axios'); -const path = require('path'); -require('dotenv').config({ path: path.join(__dirname, '../../../.env') }); - -const GITEA_API = process.env.GITEA_BASE_URL; -const GITEA_TOKEN = process.env.GITEA_TOKEN; -const JIRA_API = process.env.JIRA_BASE_URL; -const JIRA_AUTH = process.env.JIRA_PAT - ? { 'Authorization': `Bearer ${process.env.JIRA_PAT}` } - : { 'Authorization': `Basic ${Buffer.from(`${process.env.JIRA_USERNAME}:${process.env.JIRA_PASSWORD}`).toString('base64')}` }; - -const REPO = { - owner: 'loren', - repo: 'issueBotTest' -}; - -const JIRA_PROJECT_KEY = process.env.JIRA_PROJECT_KEY_1 || 'TEST'; - -const colors = { - reset: '\x1b[0m', - green: '\x1b[32m', - red: '\x1b[31m', - yellow: '\x1b[33m', - cyan: '\x1b[36m' -}; - -function log(color, message) { - console.log(`${color}${message}${colors.reset}`); -} - -async function cleanupGiteaIssues() { - log(colors.cyan, '\n[Gitea] 查找测试工单...'); - - try { - // 获取所有 open 状态的 issues - const openResponse = await axios.get( - `${GITEA_API}/repos/${REPO.owner}/${REPO.repo}/issues`, - { - headers: { 'Authorization': `token ${GITEA_TOKEN}` }, - params: { state: 'open', per_page: 100 } - } - ); - - // 获取所有 closed 状态的 issues - const closedResponse = await axios.get( - `${GITEA_API}/repos/${REPO.owner}/${REPO.repo}/issues`, - { - headers: { 'Authorization': `token ${GITEA_TOKEN}` }, - params: { state: 'closed', per_page: 100 } - } - ); - - const allIssues = [...openResponse.data, ...closedResponse.data]; - const testIssues = allIssues.filter(issue => - issue.title.includes('[TEST]') || - issue.title.includes('测试') || - issue.body?.includes('[自动化测试]') - ); - - log(colors.yellow, `找到 ${testIssues.length} 个测试工单`); - - let closedCount = 0; - for (const issue of testIssues) { - try { - // 先打开(如果是关闭的) - if (issue.state === 'closed') { - await axios.patch( - `${GITEA_API}/repos/${REPO.owner}/${REPO.repo}/issues/${issue.number}`, - { state: 'open' }, - { headers: { 'Authorization': `token ${GITEA_TOKEN}` } } - ); - await new Promise(resolve => setTimeout(resolve, 50)); - } - - // 关闭工单 - await axios.patch( - `${GITEA_API}/repos/${REPO.owner}/${REPO.repo}/issues/${issue.number}`, - { state: 'closed' }, - { headers: { 'Authorization': `token ${GITEA_TOKEN}` } } - ); - - closedCount++; - log(colors.green, ` ✓ 关闭 #${issue.number}: ${issue.title}`); - - // 避免过快请求 - await new Promise(resolve => setTimeout(resolve, 50)); - } catch (error) { - log(colors.red, ` ✗ 关闭失败 #${issue.number}: ${error.message}`); - } - } - - log(colors.green, `\n[Gitea] 成功关闭 ${closedCount}/${testIssues.length} 个测试工单`); - return closedCount; - } catch (error) { - log(colors.red, `[Gitea] 清理失败: ${error.message}`); - return 0; - } -} - -async function cleanupJiraIssues() { - log(colors.cyan, '\n[Jira] 查找测试工单...'); - - try { - // 使用 JQL 查询测试工单(使用 text ~ 进行全文搜索) - const jql = `project = ${JIRA_PROJECT_KEY} AND (text ~ "TEST" OR text ~ "测试" OR text ~ "自动化测试") ORDER BY created DESC`; - - const searchResponse = await axios.get( - `${JIRA_API}/rest/api/2/search`, - { - headers: { ...JIRA_AUTH, 'Content-Type': 'application/json' }, - params: { jql, maxResults: 200 } - } - ); - - const testIssues = searchResponse.data.issues || []; - log(colors.yellow, `找到 ${testIssues.length} 个测试工单`); - - let closedCount = 0; - for (const issue of testIssues) { - try { - // 获取工单的转换选项 - const transitionsResponse = await axios.get( - `${JIRA_API}/rest/api/2/issue/${issue.key}/transitions`, - { headers: { ...JIRA_AUTH, 'Content-Type': 'application/json' } } - ); - - // 查找 "完成"、"Done"、"关闭" 等转换 - const closeTransition = transitionsResponse.data.transitions.find(t => - t.name === '完成' || - t.name === 'Done' || - t.name === '关闭' || - t.to.name === 'Done' || - t.to.statusCategory?.key === 'done' - ); - - if (closeTransition) { - await axios.post( - `${JIRA_API}/rest/api/2/issue/${issue.key}/transitions`, - { transition: { id: closeTransition.id } }, - { headers: { ...JIRA_AUTH, 'Content-Type': 'application/json' } } - ); - - closedCount++; - log(colors.green, ` ✓ 关闭 ${issue.key}: ${issue.fields.summary}`); - } else { - log(colors.yellow, ` ⚠ ${issue.key} 无可用的关闭转换`); - } - - // 避免过快请求 - await new Promise(resolve => setTimeout(resolve, 50)); - } catch (error) { - log(colors.red, ` ✗ 关闭失败 ${issue.key}: ${error.message}`); - } - } - - log(colors.green, `\n[Jira] 成功关闭 ${closedCount}/${testIssues.length} 个测试工单`); - return closedCount; - } catch (error) { - log(colors.red, `[Jira] 清理失败: ${error.message}`); - if (error.response) { - log(colors.red, ` 状态码: ${error.response.status}`); - log(colors.red, ` 响应: ${JSON.stringify(error.response.data)}`); - } - return 0; - } -} - -async function main() { - log(colors.cyan, '========================================'); - log(colors.cyan, ' 测试工单清理脚本'); - log(colors.cyan, '========================================\n'); - - log(colors.yellow, '警告: 此脚本将关闭所有标题包含 [TEST] 或 "测试" 的工单'); - log(colors.yellow, '按 Ctrl+C 取消,或等待 3 秒后开始...\n'); - - await new Promise(resolve => setTimeout(resolve, 3000)); - - const giteaClosed = await cleanupGiteaIssues(); - const jiraClosed = await cleanupJiraIssues(); - - log(colors.cyan, '\n========================================'); - log(colors.cyan, '清理完成'); - log(colors.cyan, '========================================'); - log(colors.green, `Gitea: 关闭 ${giteaClosed} 个工单`); - log(colors.green, `Jira: 关闭 ${jiraClosed} 个工单`); -} - -main().catch(error => { - log(colors.red, `\n脚本执行失败: ${error.message}`); - process.exit(1); -}); diff --git a/src/utils/tests_created_by_claude/comprehensive-test.js b/src/utils/tests_created_by_claude/comprehensive-test.js deleted file mode 100644 index ed587a6..0000000 --- a/src/utils/tests_created_by_claude/comprehensive-test.js +++ /dev/null @@ -1,614 +0,0 @@ -const crypto = require('crypto'); -const axios = require('axios'); -const path = require('path'); - -require('dotenv').config({ path: path.join(__dirname, '../../../.env') }); - -// 配置 - 使用真实仓库 -const BASE_URL = 'http://localhost:3000'; -const WEBHOOK_SECRET = process.env.GITEA_WEBHOOK_SECRET; -const JIRA_BASE_URL = process.env.JIRA_BASE_URL; -const GITEA_BASE_URL = process.env.GITEA_BASE_URL; - -// 使用 mappings.js 中配置的真实仓库 -const REAL_REPO = { - owner: 'loren', - repo: 'issueBotTest', - fullName: 'loren/issueBotTest' -}; - -const JIRA_PROJECT = { - key: process.env.JIRA_PROJECT_KEY_1 || 'TEST', - id: process.env.JIRA_PROJECT_ID_1 || '10000' -}; - -// 测试统计 -let totalTests = 0; -let passedTests = 0; -let failedTests = 0; -const testResults = []; - -const colors = { - reset: '\x1b[0m', - green: '\x1b[32m', - red: '\x1b[31m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - cyan: '\x1b[36m', - magenta: '\x1b[35m', - gray: '\x1b[90m' -}; - -function log(color, message) { - console.log(`${color}${message}${colors.reset}`); -} - -function createSignature(payload) { - const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET); - return hmac.update(JSON.stringify(payload)).digest('hex'); -} - -async function sendGiteaWebhook(payload, signature = null, useSignature = true) { - const headers = { - 'Content-Type': 'application/json', - 'X-Gitea-Event': 'issues' - }; - - if (useSignature) { - headers['X-Gitea-Signature'] = signature || createSignature(payload); - } - - try { - const response = await axios.post(`${BASE_URL}/hooks/gitea`, payload, { - headers, - timeout: 10000 - }); - return { success: true, status: response.status, data: response.data }; - } catch (error) { - return { - success: false, - status: error.response?.status, - data: error.response?.data, - error: error.message - }; - } -} - -async function sendJiraWebhook(payload) { - try { - const response = await axios.post(`${BASE_URL}/hooks/jira`, payload, { - headers: { 'Content-Type': 'application/json' }, - timeout: 10000 - }); - return { success: true, status: response.status, data: response.data }; - } catch (error) { - return { - success: false, - status: error.response?.status, - data: error.response?.data, - error: error.message - }; - } -} - -function createGiteaPayload(action = 'opened', issueNumber = null, overrides = {}) { - const number = issueNumber || Math.floor(Math.random() * 100000); - return { - action, - sender: overrides.sender || { id: 1, username: REAL_REPO.owner }, - issue: { - number, - title: overrides.title || `[测试] 工单 #${number}`, - body: overrides.body || '这是测试工单的描述内容', - state: action === 'closed' ? 'closed' : 'open', - labels: overrides.labels || [], - milestone: overrides.milestone || null, - assignee: overrides.assignee || null, - html_url: `${GITEA_BASE_URL}/${REAL_REPO.owner}/${REAL_REPO.repo}/issues/${number}`, - ...overrides.issue - }, - repository: { - name: REAL_REPO.repo, - owner: { username: REAL_REPO.owner }, - ...overrides.repository - }, - comment: overrides.comment || null - }; -} - -function createJiraPayload(event = 'jira:issue_created', issueKey = null, overrides = {}) { - const key = issueKey || `${JIRA_PROJECT.key}-${Math.floor(Math.random() * 10000)}`; - return { - webhookEvent: event, - user: overrides.user || { - accountId: 'test-user-123', - displayName: 'Test User', - name: 'testuser' - }, - issue: { - key, - id: Math.floor(Math.random() * 100000), - fields: { - summary: overrides.summary || `[测试] Jira 工单 ${key}`, - description: overrides.description || '这是从 Jira 创建的测试工单', - project: { key: JIRA_PROJECT.key, id: JIRA_PROJECT.id }, - issuetype: { id: '10001', name: '故事' }, - priority: { id: '3', name: 'Medium' }, - status: { - name: '待办', - statusCategory: { key: 'new' } - }, - assignee: overrides.assignee || null, - customfield_10105: overrides.sprint || null, - ...overrides.fields - } - }, - comment: overrides.comment || null, - changelog: overrides.changelog || null - }; -} - -async function test(category, name, expectations, fn) { - totalTests++; - const testId = `${category}.${totalTests}`; - - log(colors.cyan, `\n▶ ${testId} ${name}`); - - if (expectations) { - log(colors.gray, ' 预期结果:'); - if (expectations.gitea) { - log(colors.gray, ` 📝 Gitea: ${expectations.gitea}`); - } - if (expectations.jira) { - log(colors.gray, ` 📋 Jira: ${expectations.jira}`); - } - if (expectations.logs) { - log(colors.gray, ` 📄 日志: ${expectations.logs}`); - } - } - - try { - await fn(); - passedTests++; - log(colors.green, ` ✓ 通过`); - testResults.push({ id: testId, name, status: 'PASS' }); - return true; - } catch (error) { - failedTests++; - log(colors.red, ` ✗ 失败: ${error.message}`); - testResults.push({ id: testId, name, status: 'FAIL', error: error.message }); - return false; - } -} - -function assert(condition, message) { - if (!condition) { - throw new Error(message); - } -} - -async function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -// ==================== 测试用例 ==================== - -async function runTests() { - log(colors.magenta, '\n' + '='.repeat(60)); - log(colors.magenta, ' 🧪 Gitea-Jira 双向同步综合测试'); - log(colors.magenta, '='.repeat(60)); - log(colors.blue, `\n配置信息:`); - log(colors.cyan, ` 仓库: ${REAL_REPO.fullName}`); - log(colors.cyan, ` Jira 项目: ${JIRA_PROJECT.key}`); - log(colors.cyan, ` Webhook URL: ${BASE_URL}`); - - // ========== 1. 安全性测试 ========== - log(colors.yellow, '\n\n📌 1. 安全性测试'); - - await test('SEC', '无签名请求被拒绝', { - logs: '应输出 "Request missing signature header"' - }, async () => { - const payload = createGiteaPayload('opened'); - const result = await sendGiteaWebhook(payload, null, false); - assert(result.status === 401, `Expected 401, got ${result.status}`); - }); - - await test('SEC', '错误签名被拒绝', { - logs: '应输出 "Invalid signature detected"' - }, async () => { - const payload = createGiteaPayload('opened'); - const result = await sendGiteaWebhook(payload, 'wrong_signature'); - assert(result.status === 401, `Expected 401, got ${result.status}`); - }); - - await test('SEC', '缺失必要字段的payload被拒绝', { - logs: '应输出 "Invalid payload structure"' - }, async () => { - const payload = { action: 'opened' }; // 缺少 issue 和 repository - const sig = createSignature(payload); - const result = await sendGiteaWebhook(payload, sig); - assert(result.status === 400, `Expected 400, got ${result.status}`); - }); - - // ========== 2. Gitea -> Jira 基础功能测试 ========== - log(colors.yellow, '\n\n📌 2. Gitea → Jira 基础同步测试'); - - await test('G2J', '创建工单(无标签)', { - gitea: '工单 #XXX 已创建', - jira: `在 ${JIRA_PROJECT.key} 项目创建工单,标题、描述同步,添加来源评论`, - logs: `[${REAL_REPO.fullName}] [GITEA->JIRA] Created ${JIRA_PROJECT.key}-XXX from #XXX` - }, async () => { - const payload = createGiteaPayload('opened', null, { - title: '[测试] 基础工单创建' - }); - const result = await sendGiteaWebhook(payload); - assert(result.success && result.status === 200, 'Webhook should succeed'); - await sleep(1000); - }); - - await test('G2J', '创建工单(带优先级标签)', { - gitea: '工单带 testhigh 标签', - jira: `创建 High 优先级工单`, - logs: `日志显示工单创建成功` - }, async () => { - const payload = createGiteaPayload('opened', null, { - title: '[测试] 高优先级工单', - labels: [{ name: 'testhigh' }] - }); - const result = await sendGiteaWebhook(payload); - assert(result.success, 'Webhook should succeed'); - await sleep(1000); - }); - - await test('G2J', '创建工单(带类型标签)', { - gitea: '工单带 testbug 标签', - jira: `创建 Bug 类型工单`, - logs: `日志显示工单创建成功` - }, async () => { - const payload = createGiteaPayload('opened', null, { - title: '[测试] Bug 类型工单', - labels: [{ name: 'testbug' }] - }); - const result = await sendGiteaWebhook(payload); - assert(result.success, 'Webhook should succeed'); - await sleep(1000); - }); - - await test('G2J', '创建工单(带里程碑)', { - gitea: '工单关联 v1.0.0 里程碑', - jira: `工单分配到 Sprint 37`, - logs: `日志显示工单创建成功` - }, async () => { - const payload = createGiteaPayload('opened', null, { - title: '[测试] 带里程碑的工单', - milestone: { title: 'v1.0.0' } - }); - const result = await sendGiteaWebhook(payload); - assert(result.success, 'Webhook should succeed'); - await sleep(1000); - }); - - // ========== 3. Gitea -> Jira 状态同步测试 ========== - log(colors.yellow, '\n\n📌 3. Gitea → Jira 状态同步测试'); - - const issueNum = Math.floor(Math.random() * 100000); - - await test('G2J', '创建后关闭工单', { - gitea: `关闭工单 #${issueNum}`, - jira: `对应 Jira 工单状态变为"完成"`, - logs: `[${REAL_REPO.fullName}] [GITEA->JIRA] Closed ${JIRA_PROJECT.key}-XXX` - }, async () => { - // 先创建 - let payload = createGiteaPayload('opened', issueNum, { - title: '[测试] 状态同步工单' - }); - await sendGiteaWebhook(payload); - await sleep(2000); - - // 再关闭 - payload = createGiteaPayload('closed', issueNum); - const result = await sendGiteaWebhook(payload); - assert(result.success, 'Close webhook should succeed'); - await sleep(1000); - }); - - await test('G2J', '重新打开工单', { - gitea: `重新打开工单 #${issueNum}`, - jira: `对应 Jira 工单状态变为"处理中"`, - logs: `[${REAL_REPO.fullName}] [GITEA->JIRA] Reopened ${JIRA_PROJECT.key}-XXX` - }, async () => { - const payload = createGiteaPayload('reopened', issueNum); - const result = await sendGiteaWebhook(payload); - assert(result.success, 'Reopen webhook should succeed'); - await sleep(1000); - }); - - // ========== 4. Jira -> Gitea 同步测试 ========== - log(colors.yellow, '\n\n📌 4. Jira → Gitea 反向同步测试'); - - await test('J2G', 'Jira 创建工单(需要手动创建或模拟)', { - jira: `在 ${JIRA_PROJECT.key} 项目手动创建工单`, - gitea: `在 ${REAL_REPO.fullName} 自动创建对应工单,带来源评论`, - logs: `[${REAL_REPO.fullName}] [JIRA->GITEA] Created #XXX from ${JIRA_PROJECT.key}-XXX` - }, async () => { - const payload = createJiraPayload('jira:issue_created'); - const result = await sendJiraWebhook(payload); - assert(result.success, 'Jira webhook should succeed'); - await sleep(1000); - }); - - await test('J2G', 'Jira 修改优先级', { - jira: `将工单优先级改为 High`, - gitea: `对应 Gitea 工单标签更新为 testhigh`, - logs: `[${REAL_REPO.fullName}] [JIRA->GITEA] Updated #XXX` - }, async () => { - const payload = createJiraPayload('jira:issue_updated', null, { - changelog: { - items: [{ - field: 'priority', - fromString: 'Medium', - toString: 'High' - }] - }, - fields: { - priority: { id: '2', name: 'High' } - } - }); - const result = await sendJiraWebhook(payload); - assert(result.success, 'Priority change webhook should succeed'); - await sleep(1000); - }); - - await test('J2G', 'Jira 修改类型', { - jira: `将工单类型改为 Bug`, - gitea: `对应 Gitea 工单标签更新为 testbug`, - logs: `[${REAL_REPO.fullName}] [JIRA->GITEA] Updated #XXX` - }, async () => { - const payload = createJiraPayload('jira:issue_updated', null, { - changelog: { - items: [{ - field: 'issuetype', - fromString: '故事', - toString: 'Bug' - }] - }, - fields: { - issuetype: { id: '10004', name: 'Bug' } - } - }); - const result = await sendJiraWebhook(payload); - assert(result.success, 'Type change webhook should succeed'); - await sleep(1000); - }); - - await test('J2G', 'Jira 修改 Sprint', { - jira: `将工单加入 Sprint 37`, - gitea: `对应 Gitea 工单里程碑设置为 v1.0.0`, - logs: `[${REAL_REPO.fullName}] [JIRA->GITEA] Updated #XXX` - }, async () => { - const payload = createJiraPayload('jira:issue_updated', null, { - changelog: { - items: [{ - field: 'Sprint', - fromString: null, - toString: 'issueBot 1.0.0' - }] - }, - fields: { - customfield_10105: [ - 'com.atlassian.greenhopper.service.sprint.Sprint@123[id=37,name=issueBot 1.0.0,state=ACTIVE]' - ] - } - }); - const result = await sendJiraWebhook(payload); - assert(result.success, 'Sprint change webhook should succeed'); - await sleep(1000); - }); - - // ========== 5. 边界情况测试 ========== - log(colors.yellow, '\n\n📌 5. 边界情况测试'); - - await test('EDGE', '标题包含#不同步标记', { - gitea: '创建标题包含 #不同步 的工单', - jira: `不创建 Jira 工单`, - logs: `无同步日志` - }, async () => { - const payload = createGiteaPayload('opened', null, { - title: '测试工单 #不同步 测试' - }); - const result = await sendGiteaWebhook(payload); - assert(result.success, 'Webhook accepted'); - await sleep(500); - }); - - await test('EDGE', '超长标题(500字符)', { - gitea: '创建超长标题工单', - jira: `创建工单,标题可能被截断`, - logs: `工单创建成功` - }, async () => { - const longTitle = '[测试] ' + 'A'.repeat(500); - const payload = createGiteaPayload('opened', null, { - title: longTitle - }); - const result = await sendGiteaWebhook(payload); - assert(result.success, 'Webhook should succeed'); - await sleep(1000); - }); - - await test('EDGE', '超长描述(5000字符)', { - gitea: '创建超长描述工单', - jira: `创建工单,描述完整同步`, - logs: `工单创建成功` - }, async () => { - const longBody = '测试内容\n'.repeat(500); - const payload = createGiteaPayload('opened', null, { - body: longBody - }); - const result = await sendGiteaWebhook(payload); - assert(result.success, 'Webhook should succeed'); - await sleep(1000); - }); - - await test('EDGE', '空描述工单', { - gitea: '创建无描述工单', - jira: `创建工单,描述为空或"No description"`, - logs: `工单创建成功` - }, async () => { - const payload = createGiteaPayload('opened', null, { - body: '' - }); - const result = await sendGiteaWebhook(payload); - assert(result.success, 'Webhook should succeed'); - await sleep(1000); - }); - - await test('EDGE', '多个标签同时存在', { - gitea: '工单带多个标签(优先级+类型+其他)', - jira: `创建工单,正确映射优先级和类型,其他标签忽略`, - logs: `工单创建成功` - }, async () => { - const payload = createGiteaPayload('opened', null, { - labels: [ - { name: 'testhighest' }, - { name: 'testbug' }, - { name: 'enhancement' } - ] - }); - const result = await sendGiteaWebhook(payload); - assert(result.success, 'Webhook should succeed'); - await sleep(1000); - }); - - await test('EDGE', '未配置的仓库', { - gitea: '向未配置的仓库发送 webhook', - jira: `不创建 Jira 工单`, - logs: `[unknown/repo] Repository not configured` - }, async () => { - const payload = createGiteaPayload('opened', null, { - repository: { - name: 'unknown-repo', - owner: { username: 'unknown-user' } - } - }); - const result = await sendGiteaWebhook(payload); - assert(result.status === 500, 'Should return error'); - }); - - // ========== 6. 并发和压力测试 ========== - log(colors.yellow, '\n\n📌 6. 并发和压力测试'); - - await test('STRESS', '连续快速创建10个工单', { - gitea: '快速创建10个工单', - jira: `创建10个对应工单`, - logs: `应该全部成功,但可能触发熔断器` - }, async () => { - const promises = []; - for (let i = 0; i < 10; i++) { - const payload = createGiteaPayload('opened', null, { - title: `[压力测试] 工单 ${i + 1}/10` - }); - promises.push(sendGiteaWebhook(payload)); - } - const results = await Promise.all(promises); - const successCount = results.filter(r => r.success).length; - log(colors.cyan, ` 成功: ${successCount}/10`); - assert(successCount >= 8, `Expected >=8 success, got ${successCount}`); - await sleep(2000); - }); - - await test('STRESS', '并发修改同一工单', { - gitea: '对同一工单发送多个修改请求', - jira: `工单更新成功,锁机制防止竞态`, - logs: `可能看到锁定和重试日志` - }, async () => { - const issueNum = Math.floor(Math.random() * 100000); - - // 先创建 - await sendGiteaWebhook(createGiteaPayload('opened', issueNum)); - await sleep(1000); - - // 并发修改 - const promises = []; - for (let i = 0; i < 5; i++) { - const payload = createGiteaPayload('edited', issueNum, { - title: `[并发测试] 版本 ${i + 1}` - }); - promises.push(sendGiteaWebhook(payload)); - } - const results = await Promise.all(promises); - const successCount = results.filter(r => r.success).length; - assert(successCount >= 4, `Expected >=4 success, got ${successCount}`); - await sleep(1000); - }); - - // ========== 7. 机器人防死循环测试 ========== - log(colors.yellow, '\n\n📌 7. 机器人防死循环测试'); - - await test('BOT', 'Gitea 机器人操作被忽略', { - gitea: '机器人账号创建工单', - jira: `不创建 Jira 工单`, - logs: `无同步日志(被静默忽略)` - }, async () => { - const payload = createGiteaPayload('opened', null, { - sender: { - id: parseInt(process.env.GITEA_BOT_ID || '0'), - username: process.env.GITEA_BOT_NAME || 'issuebot' - } - }); - const result = await sendGiteaWebhook(payload); - assert(result.success, 'Webhook accepted but ignored'); - await sleep(500); - }); - - await test('BOT', 'Jira 机器人操作被忽略', { - jira: '机器人账号创建工单', - gitea: `不创建 Gitea 工单`, - logs: `无同步日志(被静默忽略)` - }, async () => { - const payload = createJiraPayload('jira:issue_created', null, { - user: { - accountId: process.env.JIRA_BOT_ID || 'bot-id', - displayName: 'Issue Bot', - name: process.env.JIRA_BOT_NAME || 'issuebot' - } - }); - const result = await sendJiraWebhook(payload); - assert(result.success, 'Webhook accepted but ignored'); - await sleep(500); - }); - - // ========== 测试总结 ========== - log(colors.magenta, '\n\n' + '='.repeat(60)); - log(colors.magenta, ' 📊 测试结果汇总'); - log(colors.magenta, '='.repeat(60)); - - const passRate = ((passedTests / totalTests) * 100).toFixed(1); - log(colors.cyan, `\n总计: ${totalTests} 个测试`); - log(colors.green, `✓ 通过: ${passedTests} (${passRate}%)`); - log(colors.red, `✗ 失败: ${failedTests} (${(100 - passRate).toFixed(1)}%)`); - - if (failedTests > 0) { - log(colors.red, '\n失败的测试:'); - testResults - .filter(r => r.status === 'FAIL') - .forEach(r => { - log(colors.red, ` ${r.id} ${r.name}: ${r.error}`); - }); - } - - log(colors.blue, `\n💡 提示:`); - log(colors.cyan, ` 1. 检查 logs/sync-${new Date().toISOString().split('T')[0]}.log 查看详细日志`); - log(colors.cyan, ` 2. 手动验证 Jira 项目 ${JIRA_PROJECT.key} 和 Gitea 仓库 ${REAL_REPO.fullName}`); - log(colors.cyan, ` 3. Jira→Gitea 测试需要确保工单已建立映射关系`); - log(colors.cyan, ` 4. 压力测试可能触发熔断器(10秒内>20请求)`); - - log(colors.magenta, '\n' + '='.repeat(60) + '\n'); - - process.exit(failedTests > 0 ? 1 : 0); -} - -// 运行测试 -runTests().catch(error => { - log(colors.red, `\n测试执行出错: ${error.message}`); - process.exit(1); -});