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,187 @@
import { describe, expect, it, vi } from 'vitest';
import type { BridgeInterface } from '../../core/src/bridge/bridge-interface';
import type { OneBotInstanceContext } from '../src/instance-context';
import {
sendGroupMessage,
sendPrivateForwardMessage,
sendPrivateMessage,
} from '../src/modules/message-actions';
import { TempSessionStore } from '../src/temp-session-store';
const receipt = {
messageId: 1,
sequence: 100,
clientSequence: 0,
random: 1,
timestamp: 1_700_000_000,
};
function makeContext(messageApi: Record<string, ReturnType<typeof vi.fn>>): OneBotInstanceContext {
return {
uin: '10001',
selfId: 10001,
bridge: {
identity: { nickname: 'SnowLuma' },
resolveUserUid: vi.fn(),
apis: { message: messageApi },
} as unknown as BridgeInterface,
messageStore: {
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,
},
tempSessions: new TempSessionStore(),
musicSignUrl: '',
} as unknown as OneBotInstanceContext;
}
describe('private window shake messages', () => {
it('sends a poke segment through the ordinary friend private path', async () => {
const sendPrivate = vi.fn(async () => ({ ...receipt, clientSequence: 9 }));
const ctx = makeContext({ sendPrivate });
await sendPrivateMessage(ctx, 67890, [{ type: 'poke', data: { type: 1 } }], false);
expect(sendPrivate).toHaveBeenCalledWith(67890, [{ type: 'poke', subType: 1 }]);
});
it('normalizes the legacy shake segment to the same private element', async () => {
const sendPrivate = vi.fn(async () => ({ ...receipt, clientSequence: 9 }));
const ctx = makeContext({ sendPrivate });
await sendPrivateMessage(ctx, 67890, [{ type: 'shake', data: {} }], false);
expect(sendPrivate).toHaveBeenCalledWith(67890, [{ type: 'poke', subType: 1 }]);
});
it('rejects mixed private content before resolving an earlier segment', async () => {
const sendPrivate = vi.fn(async () => ({ ...receipt, clientSequence: 9 }));
const ctx = makeContext({ sendPrivate });
await expect(
sendPrivateMessage(
ctx,
67890,
[
{ type: 'at', data: { qq: 12345 } },
{ type: 'poke', data: { type: 1 } },
],
false,
),
).rejects.toMatchObject({
code: 'UNSENDABLE_TYPE',
elementType: 'poke',
message: expect.stringContaining('only segment'),
});
expect(ctx.bridge.resolveUserUid).not.toHaveBeenCalled();
expect(sendPrivate).not.toHaveBeenCalled();
});
it('keeps empty text significant for the standalone window-shake policy', async () => {
const sendPrivate = vi.fn(async () => ({ ...receipt, clientSequence: 9 }));
const ctx = makeContext({ sendPrivate });
await expect(
sendPrivateMessage(
ctx,
67890,
[
{ type: 'poke', data: { type: 1 } },
{ type: 'text', data: { text: '' } },
],
false,
),
).rejects.toMatchObject({
code: 'UNSENDABLE_TYPE',
elementType: 'poke',
message: expect.stringContaining('only segment'),
});
expect(sendPrivate).not.toHaveBeenCalled();
});
it('rejects group window shakes before resolving an earlier segment', async () => {
const sendGroup = vi.fn(async () => receipt);
const ctx = makeContext({ sendGroup });
await expect(
sendGroupMessage(
ctx,
12345,
[
{ type: 'at', data: { qq: 12345 } },
{ type: 'poke', data: { type: 1 } },
],
false,
),
).rejects.toMatchObject({
code: 'UNSENDABLE_TYPE',
elementType: 'poke',
message: expect.stringContaining('direct private chat'),
});
expect(ctx.bridge.resolveUserUid).not.toHaveBeenCalled();
expect(sendGroup).not.toHaveBeenCalled();
});
it('rejects poke segments in group temporary sessions before calling the bridge', async () => {
const sendGroupTempMessage = vi.fn(async () => ({ ...receipt, clientSequence: 9 }));
const ctx = makeContext({ sendGroupTempMessage });
ctx.tempSessions.record(67890, 12345);
await expect(
sendPrivateMessage(ctx, 67890, [{ type: 'poke', data: { type: 1 } }], false, 12345),
).rejects.toMatchObject({
code: 'UNSENDABLE_TYPE',
elementType: 'poke',
message: expect.stringContaining('direct private chat'),
});
expect(sendGroupTempMessage).not.toHaveBeenCalled();
});
it('rejects a later forward window shake before parsing an earlier node', async () => {
const upload = vi.fn();
const sendPrivate = vi.fn();
const ctx = makeContext({ sendPrivate });
(ctx.bridge.apis as unknown as Record<string, unknown>).forward = { upload };
await expect(
sendPrivateForwardMessage(ctx, 67890, [
{
type: 'node',
data: {
user_id: 10001,
nickname: 'alice',
content: [{ type: 'at', data: { qq: 12345 } }],
},
},
{
type: 'node',
data: {
user_id: 10002,
nickname: 'bob',
content: [{ type: 'poke', data: { type: 1 } }],
},
},
]),
).rejects.toMatchObject({
code: 'UNSENDABLE_TYPE',
elementType: 'poke',
});
expect(ctx.bridge.resolveUserUid).not.toHaveBeenCalled();
expect(upload).not.toHaveBeenCalled();
expect(sendPrivate).not.toHaveBeenCalled();
});
});