feat: 初始化 Gitea Action 发送钉钉机器人消息项目
This commit is contained in:
495
tests/integration/index.test.js
Normal file
495
tests/integration/index.test.js
Normal file
@@ -0,0 +1,495 @@
|
||||
/**
|
||||
* index.js 集成测试
|
||||
* 测试主入口文件的完整执行流程
|
||||
*/
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { main } from '../../src/index.js';
|
||||
import { invalidConfigs } from '../fixtures/configs/invalid-configs.js';
|
||||
import { validConfigs } from '../fixtures/configs/valid-configs.js';
|
||||
import { dingTalkResponses } from '../fixtures/responses/dingtalk-responses.js';
|
||||
import {
|
||||
clearActionEnv,
|
||||
createEnvMock,
|
||||
createEnvSnapshot,
|
||||
restoreEnvSnapshot,
|
||||
} from '../helpers/env-mock.js';
|
||||
|
||||
// Mock fetch API
|
||||
const mockFetch = vi.fn();
|
||||
vi.stubGlobal('fetch', mockFetch);
|
||||
|
||||
describe('index.js 集成测试', () => {
|
||||
let envSnapshot;
|
||||
let consoleSpy;
|
||||
|
||||
/**
|
||||
* 设置 HTTP Mock 的辅助函数
|
||||
*
|
||||
* @param {object} mockResponse - 模拟的 HTTP 响应,格式: { statusCode, data }
|
||||
*/
|
||||
async function setupHttpMock(mockResponse) {
|
||||
const { statusCode = 200, data = {} } = mockResponse;
|
||||
const responseText = typeof data === 'string' ? data : JSON.stringify(data);
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
status: statusCode,
|
||||
text: async () => responseText,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置网络错误 Mock 的辅助函数
|
||||
*
|
||||
* @param {string} errorMessage - 错误消息
|
||||
*/
|
||||
async function setupNetworkErrorMock(errorMessage = 'Network error') {
|
||||
mockFetch.mockRejectedValueOnce(new Error(errorMessage));
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
// 保存环境变量快照
|
||||
envSnapshot = createEnvSnapshot();
|
||||
|
||||
// 清理 Action 环境变量
|
||||
clearActionEnv();
|
||||
|
||||
// Mock console 方法
|
||||
consoleSpy = {
|
||||
log: vi.spyOn(console, 'log').mockImplementation(() => {}),
|
||||
error: vi.spyOn(console, 'error').mockImplementation(() => {}),
|
||||
warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
|
||||
debug: vi.spyOn(console, 'debug').mockImplementation(() => {}),
|
||||
};
|
||||
|
||||
// Mock process.exit
|
||||
vi.spyOn(process, 'exit').mockImplementation(() => {});
|
||||
|
||||
// Mock Date.now for consistent timing
|
||||
vi.spyOn(Date, 'now').mockReturnValue(1640995200000); // 2022-01-01 00:00:00
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// 恢复环境变量
|
||||
restoreEnvSnapshot(envSnapshot);
|
||||
|
||||
// 清理 fetch mock
|
||||
mockFetch.mockClear();
|
||||
|
||||
// 恢复所有 mocks
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('成功场景测试', () => {
|
||||
it('应该成功发送文本消息', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.text);
|
||||
|
||||
// Mock HTTP 请求
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.success.withMessageId });
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.messageId).toBe('msg789012');
|
||||
expect(result.details).toMatchObject({
|
||||
duration: expect.any(Number),
|
||||
statusCode: 200,
|
||||
messageSummary: expect.stringContaining('text'),
|
||||
});
|
||||
|
||||
// 验证日志输出
|
||||
expect(consoleSpy.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('🚀 DingTalk Gitea Action started'),
|
||||
);
|
||||
expect(consoleSpy.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('✅ Message sent successfully!'),
|
||||
);
|
||||
|
||||
// 验证 fetch 被调用
|
||||
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('应该成功发送 Markdown 消息', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.markdown);
|
||||
|
||||
// Mock HTTP 请求
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.success.standard });
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.details.messageSummary).toContain('markdown');
|
||||
|
||||
// 验证 fetch 被调用
|
||||
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('应该成功发送 Link 消息', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.link);
|
||||
|
||||
// Mock HTTP 请求
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.success.standard });
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.details.messageSummary).toContain('link');
|
||||
|
||||
// 验证 fetch 被调用
|
||||
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('应该成功发送 ActionCard 消息', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.actionCard);
|
||||
|
||||
// Mock HTTP 请求
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.success.standard });
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.details.messageSummary).toContain('actionCard');
|
||||
|
||||
// 验证 fetch 被调用
|
||||
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('应该成功发送 FeedCard 消息', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.feedCard);
|
||||
|
||||
// Mock HTTP 请求
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.success.standard });
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.details.messageSummary).toContain('feedCard');
|
||||
|
||||
// 验证 fetch 被调用
|
||||
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('应该在调试模式下输出详细信息', async () => {
|
||||
// 设置环境变量(包含调试模式)
|
||||
createEnvMock({
|
||||
...validConfigs.envVars.text,
|
||||
DEBUG: 'true',
|
||||
});
|
||||
|
||||
// Mock HTTP 请求
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.success.standard });
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// 验证调试日志
|
||||
expect(consoleSpy.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('[INFO] Debug mode: true'),
|
||||
);
|
||||
expect(consoleSpy.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('[DEBUG] Message payload:'),
|
||||
);
|
||||
expect(consoleSpy.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining('[DEBUG] Full response:'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('失败场景测试', () => {
|
||||
it('应该处理配置解析错误', async () => {
|
||||
// 设置无效的环境变量
|
||||
createEnvMock(invalidConfigs.envVars.missingWebhook);
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Action execution failed');
|
||||
expect(result.details.errorType).toBe('Error');
|
||||
|
||||
// 验证错误日志
|
||||
expect(consoleSpy.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Action execution failed'),
|
||||
);
|
||||
});
|
||||
|
||||
it('应该处理消息验证错误', async () => {
|
||||
// 设置会导致消息验证失败的环境变量
|
||||
createEnvMock({
|
||||
INPUT_WEBHOOK_URL: 'https://oapi.dingtalk.com/robot/send?access_token=test',
|
||||
INPUT_MESSAGE_TYPE: 'text',
|
||||
INPUT_CONTENT: '', // 空内容会导致验证失败
|
||||
});
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Action execution failed');
|
||||
});
|
||||
|
||||
it('应该处理 DingTalk API 错误', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.text);
|
||||
|
||||
// Mock HTTP 请求返回错误
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.error.invalidToken });
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Failed to send message');
|
||||
expect(result.details.errcode).toBe(310000);
|
||||
expect(result.details.errmsg).toBe('keywords not in content');
|
||||
|
||||
// 验证错误日志
|
||||
expect(consoleSpy.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Failed to send message'),
|
||||
);
|
||||
expect(consoleSpy.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining('DingTalk error code: 310000'),
|
||||
);
|
||||
});
|
||||
|
||||
it('应该处理网络错误', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.text);
|
||||
|
||||
// Mock 网络错误
|
||||
await setupNetworkErrorMock('ECONNREFUSED');
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.details.networkError).toContain('ECONNREFUSED');
|
||||
|
||||
// 验证错误日志
|
||||
expect(consoleSpy.error).toHaveBeenCalledWith(expect.stringContaining('Network error'));
|
||||
});
|
||||
|
||||
it('应该处理超时错误', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.text);
|
||||
|
||||
// Mock 超时错误
|
||||
await setupNetworkErrorMock('ETIMEDOUT');
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.details.networkError).toContain('ETIMEDOUT');
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界条件测试', () => {
|
||||
it('应该处理响应验证失败但仍然成功的情况', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.text);
|
||||
|
||||
// Mock 返回格式不完整但成功的响应(缺少 errmsg 字段)
|
||||
await setupHttpMock({
|
||||
statusCode: 200,
|
||||
data: {
|
||||
errcode: 0,
|
||||
// 缺少 errmsg 字段,这会触发验证失败
|
||||
},
|
||||
});
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.messageId).toBeUndefined();
|
||||
|
||||
// 验证警告日志
|
||||
expect(consoleSpy.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Response validation failed'),
|
||||
);
|
||||
});
|
||||
|
||||
it('应该处理空的错误消息', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.text);
|
||||
|
||||
// Mock 返回空错误消息的失败响应
|
||||
await setupHttpMock({
|
||||
statusCode: 400,
|
||||
data: {
|
||||
errcode: 300001,
|
||||
errmsg: '',
|
||||
},
|
||||
});
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Failed to send message');
|
||||
});
|
||||
|
||||
it('应该正确计算执行时间', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.text);
|
||||
|
||||
// Mock HTTP 请求
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.success.standard });
|
||||
|
||||
// Mock Date.now 返回递增的时间
|
||||
let callCount = 0;
|
||||
vi.spyOn(Date, 'now').mockImplementation(() => {
|
||||
callCount++;
|
||||
return 1640995200000 + callCount * 1000; // 每次调用增加1秒
|
||||
});
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.details.duration).toBeGreaterThan(0);
|
||||
|
||||
// 验证执行时间日志
|
||||
expect(consoleSpy.log).toHaveBeenCalledWith(expect.stringContaining('Total execution time:'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Action 输出测试', () => {
|
||||
it('应该在成功时设置正确的 Action 输出', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.text);
|
||||
|
||||
// Mock HTTP 请求
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.success.withMessageId });
|
||||
|
||||
// Mock GitHub Actions 输出
|
||||
const outputSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// 验证 Action 输出
|
||||
expect(outputSpy).toHaveBeenCalledWith('::set-output name=success::true');
|
||||
expect(outputSpy).toHaveBeenCalledWith('::set-output name=message_id::msg789012');
|
||||
expect(outputSpy).toHaveBeenCalledWith('::set-output name=error_message::');
|
||||
});
|
||||
|
||||
it('应该在失败时设置正确的 Action 输出', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.text);
|
||||
|
||||
// Mock HTTP 请求返回错误
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.error.invalidToken });
|
||||
|
||||
// Mock GitHub Actions 输出
|
||||
const outputSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(false);
|
||||
|
||||
// 验证 Action 输出
|
||||
expect(outputSpy).toHaveBeenCalledWith('::set-output name=success::false');
|
||||
expect(outputSpy).toHaveBeenCalledWith('::set-output name=message_id::');
|
||||
expect(outputSpy).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/::set-output name=error_message::Failed to send message/),
|
||||
);
|
||||
});
|
||||
|
||||
it('应该在异常时设置正确的 Action 输出', async () => {
|
||||
// 设置无效的环境变量
|
||||
createEnvMock(invalidConfigs.envVars.missingWebhook);
|
||||
|
||||
// Mock GitHub Actions 输出 (使用 console.log 而不是 process.stdout.write)
|
||||
const outputSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(false);
|
||||
|
||||
// 验证 Action 输出
|
||||
expect(outputSpy).toHaveBeenCalledWith('::set-output name=success::false');
|
||||
expect(outputSpy).toHaveBeenCalledWith('::set-output name=message_id::');
|
||||
expect(outputSpy).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/::set-output name=error_message::Action execution failed/),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能测试', () => {
|
||||
it('应该在合理时间内完成执行', async () => {
|
||||
// 设置环境变量
|
||||
createEnvMock(validConfigs.envVars.text);
|
||||
|
||||
// Mock HTTP 请求
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.success.standard });
|
||||
|
||||
// 记录开始时间
|
||||
const startTime = Date.now();
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 计算执行时间
|
||||
const executionTime = Date.now() - startTime;
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(true);
|
||||
expect(executionTime).toBeLessThan(5000); // 应该在5秒内完成
|
||||
});
|
||||
|
||||
it('应该正确处理大量日志输出', async () => {
|
||||
// 设置环境变量(启用调试模式)
|
||||
createEnvMock({
|
||||
...validConfigs.envVars.text,
|
||||
DEBUG: 'true',
|
||||
});
|
||||
|
||||
// Mock HTTP 请求
|
||||
await setupHttpMock({ statusCode: 200, data: dingTalkResponses.success.standard });
|
||||
|
||||
// 执行主函数
|
||||
const result = await main();
|
||||
|
||||
// 验证结果
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// 验证日志调用次数
|
||||
expect(consoleSpy.log.mock.calls.length).toBeGreaterThan(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user