940 lines
30 KiB
JavaScript
940 lines
30 KiB
JavaScript
/**
|
|
* 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');
|
|
});
|
|
});
|
|
});
|