SnowLuma v2.4.2 source snapshot
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run

This commit is contained in:
TanTan committed 2026-11-13 09:02:00 +08:00
commit fa9c73363d
1367 files changed
+355735

No files matched your search

@@ -0,0 +1,325 @@
import { describe, expect, it, vi } from 'vitest';
import type { BridgeInterface } from '../../src/bridge/bridge-interface';
import { ApiHandler, type ApiActionContext } from '../src/api-handler';
import type { OneBotInstanceContext } from '../src/instance-context';
import {
deleteMessage,
sendGroupMessage,
sendPrivateMessage,
setEssenceMessage,
} from '../src/modules/message-actions';
import type { MessageMeta } from '../src/types';
function instanceContext(
sendGroup: ReturnType<typeof vi.fn>,
sendPrivate: ReturnType<typeof vi.fn> = vi.fn(),
): OneBotInstanceContext {
const bridge = {
identity: { nickname: 'SnowLuma' },
apis: { message: { sendGroup, sendPrivate } },
resolveUserUid: vi.fn(async () => 'u_peer'),
} as unknown as BridgeInterface;
return {
uin: '10001',
selfId: 10001,
bridge,
messageStore: {
resolveReplySequence: () => 0,
findEvent: () => null,
findMeta: () => null,
storeEvent: vi.fn(),
} as never,
cacheMessageMeta: vi.fn(),
mediaStore: {} as never,
converterCtx: {
selfId: 10001,
imageUrlResolver: null,
mediaUrlResolver: null,
messageIdResolver: null,
mediaSegmentSink: null,
},
musicSignUrl: '',
} as unknown as OneBotInstanceContext;
}
describe('outbound message validation at the Action boundary', () => {
const sendReceipt = {
messageId: 1,
sequence: 100,
clientSequence: 0,
random: 1,
timestamp: 1700000000,
};
it('returns BAD_REQUEST and performs no send when a later segment is unknown', async () => {
const bridgeSend = vi.fn();
const ref = instanceContext(bridgeSend);
const api = new ApiHandler({
sendGroupMessage: (groupId, message, autoEscape) =>
sendGroupMessage(ref, groupId, message, autoEscape),
} as ApiActionContext);
const response = await api.handle('send_group_msg', {
group_id: 12345,
message: [
{ type: 'text', data: { text: 'must not be sent' } },
{ type: 'unknown_late_segment', data: {} },
],
});
expect(response).toMatchObject({
status: 'failed',
retcode: 1400,
wording: expect.stringContaining('unknown message segment type'),
});
expect(bridgeSend).not.toHaveBeenCalled();
});
it('returns BAD_REQUEST and performs no send for malformed JSON cards', async () => {
const bridgeSend = vi.fn();
const ref = instanceContext(bridgeSend);
const api = new ApiHandler({
sendGroupMessage: (groupId, message, autoEscape) =>
sendGroupMessage(ref, groupId, message, autoEscape),
} as ApiActionContext);
const response = await api.handle('send_group_msg', {
group_id: 12345,
message: [{ type: 'json', data: { data: 'not-json' } }],
});
expect(response).toMatchObject({
status: 'failed',
retcode: 1400,
wording: expect.stringContaining('must contain a JSON object'),
});
expect(bridgeSend).not.toHaveBeenCalled();
});
it('sends object-valued JSON cards from AstrBot-compatible clients', async () => {
const bridgeSend = vi.fn(async () => sendReceipt);
const ref = instanceContext(bridgeSend);
const api = new ApiHandler({
sendGroupMessage: (groupId, message, autoEscape) =>
sendGroupMessage(ref, groupId, message, autoEscape),
} as ApiActionContext);
const response = await api.handle('send_group_msg', {
group_id: 12345,
message: [
{
type: 'json',
data: {
data: { app: 'com.tencent.music.lua', meta: { music: { title: '晴天' } } },
config: { token: 'signed-token' },
},
},
],
});
expect(response).toMatchObject({ status: 'ok', retcode: 0 });
expect(bridgeSend).toHaveBeenCalledOnce();
expect(bridgeSend.mock.calls[0]?.[1]).toEqual([
{
type: 'json',
text: '{"app":"com.tencent.music.lua","meta":{"music":{"title":"晴天"}}}',
},
]);
});
it('reports the public data field when a JSON segment uses data.text', async () => {
const bridgeSend = vi.fn();
const ref = instanceContext(bridgeSend);
const api = new ApiHandler({
sendGroupMessage: (groupId, message, autoEscape) =>
sendGroupMessage(ref, groupId, message, autoEscape),
} as ApiActionContext);
const response = await api.handle('send_group_msg', {
group_id: 12345,
message: [
{
type: 'json',
data: { text: '{"app":"com.tencent.contact.lua"}' },
},
],
});
expect(response).toMatchObject({
status: 'failed',
retcode: 1400,
wording:
'message segment "json" field "data" must be a JSON object or non-empty JSON string',
});
expect(bridgeSend).not.toHaveBeenCalled();
});
it('rejects video mixed with other visible content before sending', async () => {
const bridgeSend = vi.fn(async () => sendReceipt);
const ref = instanceContext(bridgeSend);
const api = new ApiHandler({
sendGroupMessage: (groupId, message, autoEscape) =>
sendGroupMessage(ref, groupId, message, autoEscape),
} as ApiActionContext);
const response = await api.handle('send_group_msg', {
group_id: 12345,
message: [
{ type: 'video', data: { file: 'https://example.com/video.mp4' } },
{ type: 'text', data: { text: 'must not become hidden content' } },
],
});
expect(response).toMatchObject({
status: 'failed',
retcode: 1400,
wording: expect.stringContaining('video'),
});
expect(bridgeSend).not.toHaveBeenCalled();
});
it('rejects mixed video content before resolving a later segment', async () => {
const bridgeSend = vi.fn(async () => sendReceipt);
const getGroupRecommendArk = vi.fn(async () => '{"app":"contact"}');
const ref = instanceContext(bridgeSend);
(ref.bridge.apis as any).contacts = { getGroupRecommendArk };
const api = new ApiHandler({
sendGroupMessage: (groupId, message, autoEscape) =>
sendGroupMessage(ref, groupId, message, autoEscape),
} as ApiActionContext);
const response = await api.handle('send_group_msg', {
group_id: 12345,
message: [
{ type: 'video', data: { file: 'https://example.com/video.mp4' } },
{ type: 'contact', data: { type: 'group', id: 67890 } },
],
});
expect(response).toMatchObject({ status: 'failed', retcode: 1400 });
expect(getGroupRecommendArk).not.toHaveBeenCalled();
expect(bridgeSend).not.toHaveBeenCalled();
});
it('rejects multiple video segments before sending', async () => {
const bridgeSend = vi.fn(async () => sendReceipt);
const ref = instanceContext(bridgeSend);
const api = new ApiHandler({
sendGroupMessage: (groupId, message, autoEscape) =>
sendGroupMessage(ref, groupId, message, autoEscape),
} as ApiActionContext);
const response = await api.handle('send_group_msg', {
group_id: 12345,
message: [
{ type: 'video', data: { file: 'https://example.com/first.mp4' } },
{ type: 'video', data: { file: 'https://example.com/second.mp4' } },
],
});
expect(response).toMatchObject({
status: 'failed',
retcode: 1400,
wording: expect.stringContaining('video'),
});
expect(bridgeSend).not.toHaveBeenCalled();
});
it('rejects hidden content after a video in CQ string form', async () => {
const bridgeSend = vi.fn(async () => sendReceipt);
const ref = instanceContext(bridgeSend);
const api = new ApiHandler({
sendGroupMessage: (groupId, message, autoEscape) =>
sendGroupMessage(ref, groupId, message, autoEscape),
} as ApiActionContext);
const response = await api.handle('send_group_msg', {
group_id: 12345,
message: '[CQ:video,file=https://example.com/video.mp4]hidden text',
});
expect(response).toMatchObject({ status: 'failed', retcode: 1400 });
expect(bridgeSend).not.toHaveBeenCalled();
});
it('rejects video mixed with private-chat content before sending', async () => {
const privateSend = vi.fn(async () => sendReceipt);
const ref = instanceContext(vi.fn(), privateSend);
const api = new ApiHandler({
sendPrivateMessage: (userId, message, autoEscape, tempGroupId) =>
sendPrivateMessage(ref, userId, message, autoEscape, tempGroupId),
} as ApiActionContext);
const response = await api.handle('send_private_msg', {
user_id: 67890,
message: [
{ type: 'video', data: { file: 'https://example.com/video.mp4' } },
{ type: 'text', data: { text: 'must not become hidden content' } },
],
});
expect(response).toMatchObject({
status: 'failed',
retcode: 1400,
wording: expect.stringContaining('only segment'),
});
expect(privateSend).not.toHaveBeenCalled();
});
it('keeps exact empty text placeholders compatible with a single video', async () => {
const bridgeSend = vi.fn(async () => sendReceipt);
const ref = instanceContext(bridgeSend);
const api = new ApiHandler({
sendGroupMessage: (groupId, message, autoEscape) =>
sendGroupMessage(ref, groupId, message, autoEscape),
} as ApiActionContext);
const response = await api.handle('send_group_msg', {
group_id: 12345,
message: [
{ type: 'text', data: { text: '' } },
{ type: 'video', data: { file: 'https://example.com/video.mp4' } },
{ type: 'text', data: { text: '' } },
],
});
expect(response).toMatchObject({ status: 'ok', retcode: 0 });
expect(bridgeSend).toHaveBeenCalledOnce();
expect(bridgeSend.mock.calls[0]?.[1]).toMatchObject([{ type: 'video' }]);
});
});
describe('server-sequence action validation', () => {
const localOnlyMeta: MessageMeta = {
isGroup: true,
targetId: 12345,
sequence: 0,
sequenceAuthoritative: false,
eventName: 'group_message',
clientSequence: 0,
random: 0,
timestamp: 0,
};
it('does not recall a message that has only a local id', async () => {
const recallGroup = vi.fn();
const bridge = { apis: { message: { recallGroup } } } as unknown as BridgeInterface;
await expect(deleteMessage(bridge, localOnlyMeta)).rejects.toThrow(
'no authoritative QQ sequence',
);
expect(recallGroup).not.toHaveBeenCalled();
});
it('does not mark a message that has only a local id as essence', async () => {
const setEssence = vi.fn();
const bridge = { apis: { interaction: { setEssence } } } as unknown as BridgeInterface;
const store = { findMeta: () => localOnlyMeta } as any;
await expect(setEssenceMessage(bridge, store, 1, true)).rejects.toThrow(
'no authoritative QQ sequence',
);
expect(setEssence).not.toHaveBeenCalled();
});
});