feat: 初始化 Gitea Action 发送钉钉机器人消息项目
This commit is contained in:
939
tests/unit/message.test.js
Normal file
939
tests/unit/message.test.js
Normal file
@@ -0,0 +1,939 @@
|
||||
/**
|
||||
* message.js 文件单元测试
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { MESSAGE_TYPES } from '../../src/config.js';
|
||||
import {
|
||||
buildActionCardMessage,
|
||||
buildFeedCardMessage,
|
||||
buildLinkMessage,
|
||||
buildMarkdownMessage,
|
||||
buildMessage,
|
||||
buildTextMessage,
|
||||
getMessageSummary,
|
||||
validateMessage,
|
||||
} from '../../src/message.js';
|
||||
import { assertDingTalkMessage } from '../helpers/assertions.js';
|
||||
|
||||
describe('测试 message.js 文件', () => {
|
||||
describe('测试 buildTextMessage 函数', () => {
|
||||
it('应该构建基本的文本消息', () => {
|
||||
const message = buildTextMessage('Hello, World!');
|
||||
|
||||
assertDingTalkMessage(message, MESSAGE_TYPES.TEXT);
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.TEXT);
|
||||
expect(message.text.content).toBe('Hello, World!');
|
||||
expect(message.at).toBeUndefined();
|
||||
});
|
||||
|
||||
it('应该构建包含@手机号的文本消息', () => {
|
||||
const atMobiles = ['13800138000', '13900139000'];
|
||||
const message = buildTextMessage('Hello, World!', atMobiles);
|
||||
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.TEXT);
|
||||
expect(message.text.content).toBe('Hello, World!');
|
||||
expect(message.at).toEqual({
|
||||
atMobiles: ['13800138000', '13900139000'],
|
||||
isAtAll: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('应该构建@所有人的文本消息', () => {
|
||||
const message = buildTextMessage('Hello, World!', [], true);
|
||||
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.TEXT);
|
||||
expect(message.text.content).toBe('Hello, World!');
|
||||
expect(message.at).toEqual({
|
||||
atMobiles: [],
|
||||
isAtAll: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('应该构建同时包含@手机号和@所有人的文本消息', () => {
|
||||
const atMobiles = ['13800138000'];
|
||||
const message = buildTextMessage('Hello, World!', atMobiles, true);
|
||||
|
||||
expect(message.at).toEqual({
|
||||
atMobiles: ['13800138000'],
|
||||
isAtAll: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('应该对缺少内容抛出错误', () => {
|
||||
expect(() => buildTextMessage()).toThrow('Content is required for text message');
|
||||
expect(() => buildTextMessage('')).toThrow('Content is required for text message');
|
||||
expect(() => buildTextMessage(null)).toThrow('Content is required for text message');
|
||||
expect(() => buildTextMessage(123)).toThrow('Content is required for text message');
|
||||
});
|
||||
|
||||
it('应该处理空的@手机号数组', () => {
|
||||
const message = buildTextMessage('Hello, World!', []);
|
||||
|
||||
expect(message.at).toBeUndefined();
|
||||
});
|
||||
|
||||
it('应该处理 null/undefined 的@手机号', () => {
|
||||
const message1 = buildTextMessage('Hello, World!', null);
|
||||
const message2 = buildTextMessage('Hello, World!', undefined);
|
||||
|
||||
expect(message1.at).toBeUndefined();
|
||||
expect(message2.at).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('测试 buildMarkdownMessage 函数', () => {
|
||||
it('应该构建基本的 Markdown 消息', () => {
|
||||
const message = buildMarkdownMessage('标题', '## Markdown 内容');
|
||||
|
||||
assertDingTalkMessage(message, MESSAGE_TYPES.MARKDOWN);
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.MARKDOWN);
|
||||
expect(message.markdown.title).toBe('标题');
|
||||
expect(message.markdown.text).toBe('## Markdown 内容');
|
||||
expect(message.at).toBeUndefined();
|
||||
});
|
||||
|
||||
it('应该构建包含@手机号的 Markdown 消息', () => {
|
||||
const atMobiles = ['13800138000', '13900139000'];
|
||||
const message = buildMarkdownMessage('标题', '## Markdown 内容', atMobiles);
|
||||
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.MARKDOWN);
|
||||
expect(message.markdown.title).toBe('标题');
|
||||
expect(message.markdown.text).toBe('## Markdown 内容\n\n@13800138000 @13900139000');
|
||||
expect(message.at).toEqual({
|
||||
atMobiles: ['13800138000', '13900139000'],
|
||||
isAtAll: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('应该构建@所有人的 Markdown 消息', () => {
|
||||
const message = buildMarkdownMessage('标题', '## Markdown 内容', [], true);
|
||||
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.MARKDOWN);
|
||||
expect(message.markdown.text).toBe('## Markdown 内容\n\n@所有人');
|
||||
expect(message.at).toEqual({
|
||||
atMobiles: [],
|
||||
isAtAll: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('应该构建同时包含@手机号和@所有人的 Markdown 消息', () => {
|
||||
const atMobiles = ['13800138000'];
|
||||
const message = buildMarkdownMessage('标题', '## Markdown 内容', atMobiles, true);
|
||||
|
||||
expect(message.markdown.text).toBe('## Markdown 内容\n\n@13800138000\n\n@所有人');
|
||||
expect(message.at).toEqual({
|
||||
atMobiles: ['13800138000'],
|
||||
isAtAll: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('应该对缺少标题抛出错误', () => {
|
||||
expect(() => buildMarkdownMessage()).toThrow('Title is required for markdown message');
|
||||
expect(() => buildMarkdownMessage('')).toThrow('Title is required for markdown message');
|
||||
expect(() => buildMarkdownMessage(null)).toThrow('Title is required for markdown message');
|
||||
expect(() => buildMarkdownMessage(123)).toThrow('Title is required for markdown message');
|
||||
});
|
||||
|
||||
it('应该对缺少内容抛出错误', () => {
|
||||
expect(() => buildMarkdownMessage('标题')).toThrow(
|
||||
'Content is required for markdown message',
|
||||
);
|
||||
expect(() => buildMarkdownMessage('标题', '')).toThrow(
|
||||
'Content is required for markdown message',
|
||||
);
|
||||
expect(() => buildMarkdownMessage('标题', null)).toThrow(
|
||||
'Content is required for markdown message',
|
||||
);
|
||||
expect(() => buildMarkdownMessage('标题', 123)).toThrow(
|
||||
'Content is required for markdown message',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('测试 buildLinkMessage 函数', () => {
|
||||
it('应该构建基本的链接消息', () => {
|
||||
const message = buildLinkMessage('链接标题', '链接描述', 'https://example.com');
|
||||
|
||||
assertDingTalkMessage(message, MESSAGE_TYPES.LINK);
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.LINK);
|
||||
expect(message.link.title).toBe('链接标题');
|
||||
expect(message.link.text).toBe('链接描述');
|
||||
expect(message.link.messageUrl).toBe('https://example.com');
|
||||
expect(message.link.picUrl).toBeUndefined();
|
||||
});
|
||||
|
||||
it('应该构建包含图片 URL 的链接消息', () => {
|
||||
const message = buildLinkMessage(
|
||||
'链接标题',
|
||||
'链接描述',
|
||||
'https://example.com',
|
||||
'https://example.com/image.jpg',
|
||||
);
|
||||
|
||||
expect(message.link.picUrl).toBe('https://example.com/image.jpg');
|
||||
});
|
||||
|
||||
it('应该忽略无效的图片 URL', () => {
|
||||
const message = buildLinkMessage(
|
||||
'链接标题',
|
||||
'链接描述',
|
||||
'https://example.com',
|
||||
'not-a-valid-url',
|
||||
);
|
||||
|
||||
expect(message.link.picUrl).toBeUndefined();
|
||||
});
|
||||
|
||||
it('应该忽略非字符串的图片 URL', () => {
|
||||
const message = buildLinkMessage('链接标题', '链接描述', 'https://example.com', 123);
|
||||
|
||||
expect(message.link.picUrl).toBeUndefined();
|
||||
});
|
||||
|
||||
it('应该对缺少标题抛出错误', () => {
|
||||
expect(() => buildLinkMessage()).toThrow('Title is required for link message');
|
||||
expect(() => buildLinkMessage('')).toThrow('Title is required for link message');
|
||||
expect(() => buildLinkMessage(null)).toThrow('Title is required for link message');
|
||||
expect(() => buildLinkMessage(123)).toThrow('Title is required for link message');
|
||||
});
|
||||
|
||||
it('应该对缺少内容抛出错误', () => {
|
||||
expect(() => buildLinkMessage('标题')).toThrow('Content is required for link message');
|
||||
expect(() => buildLinkMessage('标题', '')).toThrow('Content is required for link message');
|
||||
expect(() => buildLinkMessage('标题', null)).toThrow('Content is required for link message');
|
||||
expect(() => buildLinkMessage('标题', 123)).toThrow('Content is required for link message');
|
||||
});
|
||||
|
||||
it('应该对缺少链接 URL 抛出错误', () => {
|
||||
expect(() => buildLinkMessage('标题', '内容')).toThrow(
|
||||
'Link URL is required for link message',
|
||||
);
|
||||
expect(() => buildLinkMessage('标题', '内容', '')).toThrow(
|
||||
'Link URL is required for link message',
|
||||
);
|
||||
expect(() => buildLinkMessage('标题', '内容', null)).toThrow(
|
||||
'Link URL is required for link message',
|
||||
);
|
||||
expect(() => buildLinkMessage('标题', '内容', 123)).toThrow(
|
||||
'Link URL is required for link message',
|
||||
);
|
||||
});
|
||||
|
||||
it('应该对无效的链接 URL 格式抛出错误', () => {
|
||||
expect(() => buildLinkMessage('标题', '内容', 'not-a-url')).toThrow(
|
||||
'Link URL must be a valid HTTP/HTTPS URL',
|
||||
);
|
||||
expect(() => buildLinkMessage('标题', '内容', 'ftp://example.com')).toThrow(
|
||||
'Link URL must be a valid HTTP/HTTPS URL',
|
||||
);
|
||||
});
|
||||
|
||||
it('应该接受 HTTP 和 HTTPS URL', () => {
|
||||
const httpMessage = buildLinkMessage('标题', '内容', 'http://example.com');
|
||||
const httpsMessage = buildLinkMessage('标题', '内容', 'https://example.com');
|
||||
|
||||
expect(httpMessage.link.messageUrl).toBe('http://example.com');
|
||||
expect(httpsMessage.link.messageUrl).toBe('https://example.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('测试 buildActionCardMessage 函数', () => {
|
||||
it('应该构建单按钮 ActionCard 消息', () => {
|
||||
const options = {
|
||||
btnOrientation: '0',
|
||||
singleTitle: '查看详情',
|
||||
singleURL: 'https://example.com/detail',
|
||||
};
|
||||
|
||||
const message = buildActionCardMessage('ActionCard 标题', 'ActionCard 内容', options);
|
||||
|
||||
assertDingTalkMessage(message, MESSAGE_TYPES.ACTION_CARD);
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.ACTION_CARD);
|
||||
expect(message.actionCard.title).toBe('ActionCard 标题');
|
||||
expect(message.actionCard.text).toBe('ActionCard 内容');
|
||||
expect(message.actionCard.btnOrientation).toBe('0');
|
||||
expect(message.actionCard.singleTitle).toBe('查看详情');
|
||||
expect(message.actionCard.singleURL).toBe('https://example.com/detail');
|
||||
expect(message.actionCard.btns).toBeUndefined();
|
||||
});
|
||||
|
||||
it('应该构建多按钮 ActionCard 消息', () => {
|
||||
const options = {
|
||||
btnOrientation: '1',
|
||||
buttons: [
|
||||
{ title: '按钮1', actionURL: 'https://example.com/action1' },
|
||||
{ title: '按钮2', actionURL: 'https://example.com/action2' },
|
||||
],
|
||||
};
|
||||
|
||||
const message = buildActionCardMessage('ActionCard 标题', 'ActionCard 内容', options);
|
||||
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.ACTION_CARD);
|
||||
expect(message.actionCard.btnOrientation).toBe('1');
|
||||
expect(message.actionCard.btns).toEqual([
|
||||
{ title: '按钮1', actionURL: 'https://example.com/action1' },
|
||||
{ title: '按钮2', actionURL: 'https://example.com/action2' },
|
||||
]);
|
||||
expect(message.actionCard.singleTitle).toBeUndefined();
|
||||
expect(message.actionCard.singleURL).toBeUndefined();
|
||||
});
|
||||
|
||||
it('应该使用默认的按钮方向', () => {
|
||||
const options = {
|
||||
singleTitle: '查看详情',
|
||||
singleURL: 'https://example.com/detail',
|
||||
};
|
||||
|
||||
const message = buildActionCardMessage('ActionCard 标题', 'ActionCard 内容', options);
|
||||
|
||||
expect(message.actionCard.btnOrientation).toBe('0');
|
||||
});
|
||||
|
||||
it('应该处理空选项', () => {
|
||||
expect(() => buildActionCardMessage('标题', '内容', {})).toThrow(
|
||||
'actionCard requires either singleTitle/singleURL or buttons',
|
||||
);
|
||||
});
|
||||
|
||||
it('应该处理未定义的选项', () => {
|
||||
expect(() => buildActionCardMessage('标题', '内容')).toThrow(
|
||||
'actionCard requires either singleTitle/singleURL or buttons',
|
||||
);
|
||||
});
|
||||
|
||||
it('应该对缺少标题抛出错误', () => {
|
||||
expect(() => buildActionCardMessage()).toThrow('Title is required for actionCard message');
|
||||
expect(() => buildActionCardMessage('')).toThrow('Title is required for actionCard message');
|
||||
expect(() => buildActionCardMessage(null)).toThrow(
|
||||
'Title is required for actionCard message',
|
||||
);
|
||||
expect(() => buildActionCardMessage(123)).toThrow('Title is required for actionCard message');
|
||||
});
|
||||
|
||||
it('应该对缺少内容抛出错误', () => {
|
||||
expect(() => buildActionCardMessage('标题')).toThrow(
|
||||
'Content is required for actionCard message',
|
||||
);
|
||||
expect(() => buildActionCardMessage('标题', '')).toThrow(
|
||||
'Content is required for actionCard message',
|
||||
);
|
||||
expect(() => buildActionCardMessage('标题', null)).toThrow(
|
||||
'Content is required for actionCard message',
|
||||
);
|
||||
expect(() => buildActionCardMessage('标题', 123)).toThrow(
|
||||
'Content is required for actionCard message',
|
||||
);
|
||||
});
|
||||
|
||||
it('应该对既没有单按钮也没有按钮数组的情况抛出错误', () => {
|
||||
expect(() => buildActionCardMessage('标题', '内容', {})).toThrow(
|
||||
'actionCard requires either singleTitle/singleURL or buttons',
|
||||
);
|
||||
});
|
||||
|
||||
it('应该处理空的按钮数组', () => {
|
||||
const options = { buttons: [] };
|
||||
|
||||
expect(() => buildActionCardMessage('标题', '内容', options)).toThrow(
|
||||
'actionCard requires either singleTitle/singleURL or buttons',
|
||||
);
|
||||
});
|
||||
|
||||
it('应该处理非数组的按钮', () => {
|
||||
const options = { buttons: 'not-an-array' };
|
||||
|
||||
expect(() => buildActionCardMessage('标题', '内容', options)).toThrow(
|
||||
'actionCard requires either singleTitle/singleURL or buttons',
|
||||
);
|
||||
});
|
||||
|
||||
it('当同时提供单按钮和按钮数组时应该优先使用单按钮', () => {
|
||||
const options = {
|
||||
singleTitle: '单按钮',
|
||||
singleURL: 'https://example.com/single',
|
||||
buttons: [{ title: '多按钮', actionURL: 'https://example.com/multi' }],
|
||||
};
|
||||
|
||||
const message = buildActionCardMessage('标题', '内容', options);
|
||||
|
||||
expect(message.actionCard.singleTitle).toBe('单按钮');
|
||||
expect(message.actionCard.singleURL).toBe('https://example.com/single');
|
||||
expect(message.actionCard.btns).toEqual([
|
||||
{ title: '多按钮', actionURL: 'https://example.com/multi' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('测试 buildFeedCardMessage 函数', () => {
|
||||
it('应该构建 FeedCard 消息', () => {
|
||||
const links = [
|
||||
{
|
||||
title: '新闻1',
|
||||
messageURL: 'https://example.com/news1',
|
||||
picURL: 'https://example.com/pic1.jpg',
|
||||
},
|
||||
{
|
||||
title: '新闻2',
|
||||
messageURL: 'https://example.com/news2',
|
||||
picURL: 'https://example.com/pic2.jpg',
|
||||
},
|
||||
];
|
||||
|
||||
const message = buildFeedCardMessage(links);
|
||||
|
||||
assertDingTalkMessage(message, MESSAGE_TYPES.FEED_CARD);
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.FEED_CARD);
|
||||
expect(message.feedCard.links).toEqual(links);
|
||||
});
|
||||
|
||||
it('应该构建包含单个链接的 FeedCard 消息', () => {
|
||||
const links = [
|
||||
{
|
||||
title: '单个新闻',
|
||||
messageURL: 'https://example.com/news',
|
||||
picURL: 'https://example.com/pic.jpg',
|
||||
},
|
||||
];
|
||||
|
||||
const message = buildFeedCardMessage(links);
|
||||
|
||||
expect(message.feedCard.links).toHaveLength(1);
|
||||
expect(message.feedCard.links[0]).toEqual(links[0]);
|
||||
});
|
||||
|
||||
it('应该对缺少链接抛出错误', () => {
|
||||
expect(() => buildFeedCardMessage()).toThrow('FeedCard requires a non-empty links array');
|
||||
expect(() => buildFeedCardMessage(null)).toThrow('FeedCard requires a non-empty links array');
|
||||
expect(() => buildFeedCardMessage('not-an-array')).toThrow(
|
||||
'FeedCard requires a non-empty links array',
|
||||
);
|
||||
});
|
||||
|
||||
it('应该对空链接数组抛出错误', () => {
|
||||
expect(() => buildFeedCardMessage([])).toThrow('FeedCard requires a non-empty links array');
|
||||
});
|
||||
|
||||
it('应该正确映射链接属性', () => {
|
||||
const links = [
|
||||
{
|
||||
title: '测试标题',
|
||||
messageURL: 'https://test.com',
|
||||
picURL: 'https://test.com/pic.jpg',
|
||||
extraProperty: 'should be ignored',
|
||||
},
|
||||
];
|
||||
|
||||
const message = buildFeedCardMessage(links);
|
||||
|
||||
expect(message.feedCard.links[0]).toEqual({
|
||||
title: '测试标题',
|
||||
messageURL: 'https://test.com',
|
||||
picURL: 'https://test.com/pic.jpg',
|
||||
});
|
||||
expect(message.feedCard.links[0].extraProperty).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('测试 buildMessage 函数', () => {
|
||||
it('应该根据配置构建文本消息', () => {
|
||||
const config = {
|
||||
messageType: 'text',
|
||||
content: 'Hello, World!',
|
||||
atMobiles: ['13800138000'],
|
||||
atAll: false,
|
||||
};
|
||||
|
||||
const message = buildMessage(config);
|
||||
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.TEXT);
|
||||
expect(message.text.content).toBe('Hello, World!');
|
||||
expect(message.at.atMobiles).toEqual(['13800138000']);
|
||||
});
|
||||
|
||||
it('应该根据配置构建 Markdown 消息', () => {
|
||||
const config = {
|
||||
messageType: 'markdown',
|
||||
title: 'Markdown 标题',
|
||||
content: '## Markdown 内容',
|
||||
atMobiles: [],
|
||||
atAll: true,
|
||||
};
|
||||
|
||||
const message = buildMessage(config);
|
||||
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.MARKDOWN);
|
||||
expect(message.markdown.title).toBe('Markdown 标题');
|
||||
expect(message.at.isAtAll).toBe(true);
|
||||
});
|
||||
|
||||
it('应该根据配置构建链接消息', () => {
|
||||
const config = {
|
||||
messageType: 'link',
|
||||
title: '链接标题',
|
||||
content: '链接描述',
|
||||
linkUrl: 'https://example.com',
|
||||
picUrl: 'https://example.com/pic.jpg',
|
||||
};
|
||||
|
||||
const message = buildMessage(config);
|
||||
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.LINK);
|
||||
expect(message.link.title).toBe('链接标题');
|
||||
expect(message.link.messageUrl).toBe('https://example.com');
|
||||
expect(message.link.picUrl).toBe('https://example.com/pic.jpg');
|
||||
});
|
||||
|
||||
it('应该根据配置构建 ActionCard 消息', () => {
|
||||
const config = {
|
||||
messageType: 'actionCard',
|
||||
title: 'ActionCard 标题',
|
||||
content: 'ActionCard 内容',
|
||||
btnOrientation: '1',
|
||||
buttons: [{ title: '按钮1', actionURL: 'https://example.com/action1' }],
|
||||
};
|
||||
|
||||
const message = buildMessage(config);
|
||||
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.ACTION_CARD);
|
||||
expect(message.actionCard.title).toBe('ActionCard 标题');
|
||||
expect(message.actionCard.btnOrientation).toBe('1');
|
||||
expect(message.actionCard.btns).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('应该根据配置构建 FeedCard 消息', () => {
|
||||
const config = {
|
||||
messageType: 'feedCard',
|
||||
feedLinks: [
|
||||
{
|
||||
title: '新闻1',
|
||||
messageURL: 'https://example.com/news1',
|
||||
picURL: 'https://example.com/pic1.jpg',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const message = buildMessage(config);
|
||||
|
||||
expect(message.msgtype).toBe(MESSAGE_TYPES.FEED_CARD);
|
||||
expect(message.feedCard.links).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('应该对不支持的消息类型抛出错误', () => {
|
||||
const config = {
|
||||
messageType: 'unsupported',
|
||||
content: 'Test content',
|
||||
};
|
||||
|
||||
expect(() => buildMessage(config)).toThrow('Unsupported message type: unsupported');
|
||||
});
|
||||
});
|
||||
|
||||
describe('测试 validateMessage 函数', () => {
|
||||
it('应该验证有效的文本消息', () => {
|
||||
const message = {
|
||||
msgtype: 'text',
|
||||
text: {
|
||||
content: 'Hello, World!',
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('应该验证有效的 Markdown 消息', () => {
|
||||
const message = {
|
||||
msgtype: 'markdown',
|
||||
markdown: {
|
||||
title: '标题',
|
||||
text: '## 内容',
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('应该验证有效的链接消息', () => {
|
||||
const message = {
|
||||
msgtype: 'link',
|
||||
link: {
|
||||
title: '链接标题',
|
||||
text: '链接描述',
|
||||
messageUrl: 'https://example.com',
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('应该验证有效的单按钮 ActionCard 消息', () => {
|
||||
const message = {
|
||||
msgtype: 'actionCard',
|
||||
actionCard: {
|
||||
title: 'ActionCard 标题',
|
||||
text: 'ActionCard 内容',
|
||||
singleTitle: '查看详情',
|
||||
singleURL: 'https://example.com',
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('应该验证有效的多按钮 ActionCard 消息', () => {
|
||||
const message = {
|
||||
msgtype: 'actionCard',
|
||||
actionCard: {
|
||||
title: 'ActionCard 标题',
|
||||
text: 'ActionCard 内容',
|
||||
btns: [
|
||||
{ title: '按钮1', actionURL: 'https://example.com/action1' },
|
||||
{ title: '按钮2', actionURL: 'https://example.com/action2' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('应该验证有效的 FeedCard 消息', () => {
|
||||
const message = {
|
||||
msgtype: 'feedCard',
|
||||
feedCard: {
|
||||
links: [
|
||||
{
|
||||
title: '新闻1',
|
||||
messageURL: 'https://example.com/news1',
|
||||
picURL: 'https://example.com/pic1.jpg',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('应该拒绝 null/undefined 消息', () => {
|
||||
expect(validateMessage(null).isValid).toBe(false);
|
||||
expect(validateMessage(undefined).isValid).toBe(false);
|
||||
expect(validateMessage('not-an-object').isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('应该拒绝没有 msgtype 的消息', () => {
|
||||
const message = {
|
||||
text: { content: 'Hello' },
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(false);
|
||||
expect(result.errors).toContain('Invalid or missing msgtype');
|
||||
});
|
||||
|
||||
it('应该拒绝带有无效 msgtype 的消息', () => {
|
||||
const message = {
|
||||
msgtype: 'invalid',
|
||||
text: { content: 'Hello' },
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(false);
|
||||
expect(result.errors).toContain('Invalid or missing msgtype');
|
||||
});
|
||||
|
||||
it('应该拒绝没有内容的文本消息', () => {
|
||||
const message = {
|
||||
msgtype: 'text',
|
||||
text: {},
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(false);
|
||||
expect(result.errors).toContain('Text message must have content');
|
||||
});
|
||||
|
||||
it('应该拒绝没有标题或内容的 Markdown 消息', () => {
|
||||
const message1 = {
|
||||
msgtype: 'markdown',
|
||||
markdown: { text: '内容' },
|
||||
};
|
||||
|
||||
const message2 = {
|
||||
msgtype: 'markdown',
|
||||
markdown: { title: '标题' },
|
||||
};
|
||||
|
||||
expect(validateMessage(message1).isValid).toBe(false);
|
||||
expect(validateMessage(message2).isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('应该拒绝缺少字段的链接消息', () => {
|
||||
const message = {
|
||||
msgtype: 'link',
|
||||
link: { title: '标题' },
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(false);
|
||||
expect(result.errors).toContain('Link message must have title, text, and messageUrl');
|
||||
});
|
||||
|
||||
it('应该拒绝单按钮不完整的 ActionCard 消息', () => {
|
||||
const message = {
|
||||
msgtype: 'actionCard',
|
||||
actionCard: {
|
||||
title: '标题',
|
||||
text: '内容',
|
||||
singleTitle: '按钮',
|
||||
// missing singleURL
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(false);
|
||||
expect(result.errors).toContain(
|
||||
'ActionCard single button requires both singleTitle and singleURL',
|
||||
);
|
||||
});
|
||||
|
||||
it('应该拒绝带有无效按钮的 ActionCard 消息', () => {
|
||||
const message = {
|
||||
msgtype: 'actionCard',
|
||||
actionCard: {
|
||||
title: '标题',
|
||||
text: '内容',
|
||||
btns: [
|
||||
{ title: '按钮1' }, // missing actionURL
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(false);
|
||||
expect(result.errors).toContain('Each ActionCard button must have title and actionURL');
|
||||
});
|
||||
|
||||
it('应该拒绝没有链接的 FeedCard 消息', () => {
|
||||
const message = {
|
||||
msgtype: 'feedCard',
|
||||
feedCard: {},
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(false);
|
||||
expect(result.errors).toContain('FeedCard must have a non-empty links array');
|
||||
});
|
||||
|
||||
it('应该拒绝带有无效链接的 FeedCard 消息', () => {
|
||||
const message = {
|
||||
msgtype: 'feedCard',
|
||||
feedCard: {
|
||||
links: [
|
||||
{ title: '新闻1' }, // missing messageURL and picURL
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateMessage(message);
|
||||
|
||||
expect(result.isValid).toBe(false);
|
||||
expect(result.errors).toContain('Each FeedCard link must have title, messageURL and picURL');
|
||||
});
|
||||
});
|
||||
|
||||
describe('测试 getMessageSummary 函数', () => {
|
||||
it('应该返回文本消息的摘要', () => {
|
||||
const message = {
|
||||
msgtype: 'text',
|
||||
text: {
|
||||
content: 'Hello, World!',
|
||||
},
|
||||
};
|
||||
|
||||
const summary = getMessageSummary(message);
|
||||
|
||||
expect(summary).toBe('Type: text, Content: "Hello, World!"');
|
||||
});
|
||||
|
||||
it('应该截断长文本内容', () => {
|
||||
const longContent = 'A'.repeat(60);
|
||||
const message = {
|
||||
msgtype: 'text',
|
||||
text: {
|
||||
content: longContent,
|
||||
},
|
||||
};
|
||||
|
||||
const summary = getMessageSummary(message);
|
||||
|
||||
expect(summary).toContain(`Type: text, Content: "${'A'.repeat(50)}..."`);
|
||||
});
|
||||
|
||||
it('应该返回 Markdown 消息的摘要', () => {
|
||||
const message = {
|
||||
msgtype: 'markdown',
|
||||
markdown: {
|
||||
title: 'Markdown 标题',
|
||||
text: '## Markdown 内容',
|
||||
},
|
||||
};
|
||||
|
||||
const summary = getMessageSummary(message);
|
||||
|
||||
expect(summary).toBe('Type: markdown, Title: "Markdown 标题", Text: "## Markdown 内容"');
|
||||
});
|
||||
|
||||
it('应该返回链接消息的摘要', () => {
|
||||
const message = {
|
||||
msgtype: 'link',
|
||||
link: {
|
||||
title: '链接标题',
|
||||
text: '链接描述',
|
||||
messageUrl: 'https://example.com',
|
||||
},
|
||||
};
|
||||
|
||||
const summary = getMessageSummary(message);
|
||||
|
||||
expect(summary).toBe('Type: link, Title: "链接标题", URL: "https://example.com"');
|
||||
});
|
||||
|
||||
it('应该返回单按钮 ActionCard 消息的摘要', () => {
|
||||
const message = {
|
||||
msgtype: 'actionCard',
|
||||
actionCard: {
|
||||
title: 'ActionCard 标题',
|
||||
text: 'ActionCard 内容',
|
||||
singleTitle: '查看详情',
|
||||
},
|
||||
};
|
||||
|
||||
const summary = getMessageSummary(message);
|
||||
|
||||
expect(summary).toBe('Type: actionCard, Title: "ActionCard 标题", Single: "查看详情"');
|
||||
});
|
||||
|
||||
it('应该返回多按钮 ActionCard 消息的摘要', () => {
|
||||
const message = {
|
||||
msgtype: 'actionCard',
|
||||
actionCard: {
|
||||
title: 'ActionCard 标题',
|
||||
text: 'ActionCard 内容',
|
||||
btns: [
|
||||
{ title: '按钮1', actionURL: 'https://example.com/action1' },
|
||||
{ title: '按钮2', actionURL: 'https://example.com/action2' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const summary = getMessageSummary(message);
|
||||
|
||||
expect(summary).toBe('Type: actionCard, Title: "ActionCard 标题", Buttons: 2');
|
||||
});
|
||||
|
||||
it('应该返回 FeedCard 消息的摘要', () => {
|
||||
const message = {
|
||||
msgtype: 'feedCard',
|
||||
feedCard: {
|
||||
links: [
|
||||
{
|
||||
title: '新闻1',
|
||||
messageURL: 'https://example.com/news1',
|
||||
picURL: 'https://example.com/pic1.jpg',
|
||||
},
|
||||
{
|
||||
title: '新闻2',
|
||||
messageURL: 'https://example.com/news2',
|
||||
picURL: 'https://example.com/pic2.jpg',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const summary = getMessageSummary(message);
|
||||
|
||||
expect(summary).toBe('Type: feedCard, Links: 2');
|
||||
});
|
||||
|
||||
it('应该包含 @所有人 信息', () => {
|
||||
const message = {
|
||||
msgtype: 'text',
|
||||
text: {
|
||||
content: 'Hello, World!',
|
||||
},
|
||||
at: {
|
||||
isAtAll: true,
|
||||
atMobiles: [],
|
||||
},
|
||||
};
|
||||
|
||||
const summary = getMessageSummary(message);
|
||||
|
||||
expect(summary).toBe('Type: text, Content: "Hello, World!", @All: true');
|
||||
});
|
||||
|
||||
it('应该包含 @手机号 信息', () => {
|
||||
const message = {
|
||||
msgtype: 'text',
|
||||
text: {
|
||||
content: 'Hello, World!',
|
||||
},
|
||||
at: {
|
||||
isAtAll: false,
|
||||
atMobiles: ['13800138000', '13900139000'],
|
||||
},
|
||||
};
|
||||
|
||||
const summary = getMessageSummary(message);
|
||||
|
||||
expect(summary).toBe('Type: text, Content: "Hello, World!", @Mobiles: 2');
|
||||
});
|
||||
|
||||
it('应该同时包含 @所有人 和 @手机号 信息', () => {
|
||||
const message = {
|
||||
msgtype: 'text',
|
||||
text: {
|
||||
content: 'Hello, World!',
|
||||
},
|
||||
at: {
|
||||
isAtAll: true,
|
||||
atMobiles: ['13800138000'],
|
||||
},
|
||||
};
|
||||
|
||||
const summary = getMessageSummary(message);
|
||||
|
||||
expect(summary).toBe('Type: text, Content: "Hello, World!", @All: true, @Mobiles: 1');
|
||||
});
|
||||
|
||||
it('应该处理无效的消息', () => {
|
||||
expect(getMessageSummary(null)).toBe('Invalid message');
|
||||
expect(getMessageSummary({})).toBe('Invalid message');
|
||||
expect(getMessageSummary({ msgtype: null })).toBe('Invalid message');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user