cleanup:删除冗余代码

This commit is contained in:
2026-01-29 16:56:39 +08:00
parent 3d9c7a2b28
commit 44038da2aa
3 changed files with 1 additions and 823 deletions

View File

@@ -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 };
module.exports = { getJiraKey, getGiteaInfo, saveMapping };

View File

@@ -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);
});

View File

@@ -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);
});