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,943 @@
import { describe, expect, it, vi } from 'vitest';
import {
createLogger,
getLogLevel,
runWithRequestId,
setLogLevel,
subscribeLogs,
type LogEntry,
} from '@snowluma/common/logger';
import type { BridgeInterface } from '../../core/src/bridge/bridge-interface';
import { ApiHandler } from '../src/api-handler';
import { OneBotInstance } from '../src/instance';
import { buildApiContext, type OneBotInstanceContext } from '../src/instance-context';
import { hashMessageIdInt32, PRIVATE_NT_MESSAGE_EVENT } from '../src/message-id';
import { TempSessionStore } from '../src/temp-session-store';
import type { JsonObject, OneBotConfig } from '../src/types';
const SELF_ID = 10001;
const PEER_ID = 20002;
const RECEIPT = {
messageId: 123456,
sequence: 87,
clientSequence: 9,
random: 123456,
timestamp: 1_700_000_000,
};
type Receipt = typeof RECEIPT;
function makeContext(receiptOrReceipts: Receipt | Receipt[] = RECEIPT): {
ctx: OneBotInstanceContext;
dispatchEvent: ReturnType<typeof vi.fn<(event: JsonObject) => void>>;
events: Map<number, JsonObject>;
} {
const receipts = Array.isArray(receiptOrReceipts) ? receiptOrReceipts : [receiptOrReceipts];
let receiptIndex = 0;
const nextReceipt = () => {
const receipt = receipts[receiptIndex++];
if (!receipt) {
throw new Error('unexpected extra private send');
}
return receipt;
};
const events = new Map<number, JsonObject>();
const dispatchEvent = vi.fn<(event: JsonObject) => void>();
const bridge = {
identity: { nickname: 'SnowLuma' },
apis: {
message: {
sendPrivate: vi.fn(async () => nextReceipt()),
sendGroupTempMessage: vi.fn(async () => nextReceipt()),
sendC2cFile: vi.fn(async () => nextReceipt()),
},
groupFile: {
uploadPrivate: vi.fn(async () => ({
fileId: 'uploaded-file-uuid',
fileHash: 'uploaded-file-hash',
})),
},
forward: {
upload: vi.fn(async () => 'forward-res-id'),
},
},
resolveUserUid: vi.fn(async () => 'u_peer'),
recallUploadedFile: vi.fn((fileId: string) =>
fileId === 'uploaded-file-uuid'
? {
fileId,
scope: 'private',
userId: PEER_ID,
fileName: 'inline.txt',
fileSize: 3,
fileMd5: new Uint8Array(16),
fileSha1: new Uint8Array(20),
fileHash: 'uploaded-file-hash',
rememberedAt: Date.now(),
}
: undefined,
),
} as unknown as BridgeInterface;
const ctx = {
uin: String(SELF_ID),
selfId: SELF_ID,
bridge,
messageStore: {
findMedia: () => null,
findEvent: (messageId: number) => events.get(messageId) ?? null,
findMeta: () => null,
resolveReplySequence: () => null,
storeEvent: (
messageId: number,
_isGroup: boolean,
_sessionId: number,
_sequence: number,
_eventName: string,
event: JsonObject,
) => {
events.set(messageId, event);
return true;
},
},
mediaStore: {},
reactionStore: {},
tempSessions: new TempSessionStore(),
converterCtx: {
selfId: SELF_ID,
imageUrlResolver: null,
mediaUrlResolver: null,
messageIdResolver: null,
mediaSegmentSink: null,
},
config: {
networks: { httpServers: [], httpClients: [], wsServers: [], wsClients: [] },
} as OneBotConfig,
cacheMessageMeta: vi.fn(),
dispatchEvent,
} as unknown as OneBotInstanceContext;
return { ctx, dispatchEvent, events };
}
interface InstanceHarness {
dispatch(event: JsonObject, source: 'send' | 'bridge'): Promise<void>;
emitEvent: ReturnType<typeof vi.fn<(event: JsonObject) => Promise<void>>>;
stored: Map<number, JsonObject>;
}
function makeInstanceHarness(
options: {
statusCommand?: {
enabled: boolean;
swallow: boolean;
cooldownSeconds: number;
trigger: string;
};
logger?: ReturnType<typeof createLogger>;
} = {},
): InstanceHarness {
const stored = new Map<number, JsonObject>();
const emitEvent = vi.fn(async (_event: JsonObject) => {});
const instance = Object.create(OneBotInstance.prototype) as OneBotInstance;
Object.assign(instance as unknown as Record<string, unknown>, {
bridge: { identity: {} },
ctx: {
config: {
statusCommand: options.statusCommand ?? {
enabled: false,
swallow: false,
cooldownSeconds: 5,
trigger: '#sl',
},
},
},
log: options.logger ?? { success: vi.fn(), trace: vi.fn(), warn: vi.fn() },
messageStore: {
findMedia: () => null,
findEvent: (messageId: number) => stored.get(messageId) ?? null,
storeEvent: (
messageId: number,
_isGroup: boolean,
_sessionId: number,
_sequence: number,
_eventName: string,
event: JsonObject,
) => {
stored.set(messageId, event);
return true;
},
},
networkManager: { emitEvent },
acceptingActions: true,
uin: String(SELF_ID),
statusCommandCooldown: new Map([['p:20002', Date.now()]]),
recentSelfSentEvents: new Map(),
});
const dispatch = (
instance as unknown as {
dispatchEvent(event: JsonObject, source: 'send' | 'bridge'): Promise<void>;
}
).dispatchEvent.bind(instance);
return { dispatch, emitEvent, stored };
}
function privateSentEvent(overrides: JsonObject = {}): JsonObject {
return {
time: RECEIPT.timestamp,
self_id: SELF_ID,
post_type: 'message_sent',
message_type: 'private',
sub_type: 'friend',
message_id: 987654,
message_seq: RECEIPT.clientSequence,
user_id: SELF_ID,
target_id: PEER_ID,
message: [{ type: 'text', data: { text: 'hello' } }],
raw_message: 'hello',
sender: { user_id: SELF_ID, nickname: 'SnowLuma' },
...overrides,
};
}
function groupSentEvent(overrides: JsonObject = {}): JsonObject {
return {
...privateSentEvent(),
message_type: 'group',
sub_type: 'normal',
message_id: -7654321,
message_seq: RECEIPT.sequence,
group_id: 30003,
target_id: undefined,
...overrides,
};
}
describe('OneBot self-sent events', () => {
it('reports a client private send once across repeated confirmations and pushes', async () => {
const { dispatch, emitEvent, stored } = makeInstanceHarness();
const event = privateSentEvent({ message_id: -7654321 });
await Promise.all([dispatch(event, 'bridge'), dispatch({ ...event }, 'bridge')]);
await dispatch({ ...event, raw_message: 'canonical content' }, 'bridge');
expect(emitEvent).toHaveBeenCalledExactlyOnceWith(event);
expect(stored.get(-7654321)?.raw_message).toBe('canonical content');
});
it('keeps private action deduplication for more than one echo', async () => {
const { dispatch, emitEvent } = makeInstanceHarness();
const event = privateSentEvent();
await dispatch(event, 'send');
await dispatch({ ...event }, 'bridge');
await dispatch({ ...event }, 'bridge');
expect(emitEvent).toHaveBeenCalledOnce();
});
it('distinguishes private sends by identity and peer even when text is identical', async () => {
const { dispatch, emitEvent } = makeInstanceHarness();
const event = privateSentEvent();
await dispatch(event, 'bridge');
await dispatch({ ...event, message_seq: RECEIPT.clientSequence + 1 }, 'bridge');
await dispatch({ ...event, target_id: PEER_ID + 1 }, 'bridge');
await dispatch({ ...event, message_id: -7654321 }, 'bridge');
expect(emitEvent).toHaveBeenCalledTimes(4);
});
it('keeps private deduplication isolated and expires it without deleting history', async () => {
vi.useFakeTimers();
try {
const first = makeInstanceHarness();
const second = makeInstanceHarness();
const event = privateSentEvent();
await first.dispatch(event, 'bridge');
await second.dispatch(
{ ...event, self_id: SELF_ID + 1, user_id: SELF_ID + 1 },
'bridge',
);
expect(second.emitEvent).toHaveBeenCalledOnce();
await first.dispatch(event, 'bridge');
expect(first.emitEvent).toHaveBeenCalledOnce();
vi.advanceTimersByTime(120_000);
await first.dispatch(event, 'bridge');
expect(first.emitEvent).toHaveBeenCalledTimes(2);
expect(first.stored.has(987654)).toBe(true);
} finally {
vi.useRealTimers();
}
});
it('reports a client group send once across repeated server pushes', async () => {
const { dispatch, emitEvent, stored } = makeInstanceHarness();
const event = groupSentEvent();
await dispatch(event, 'bridge');
await dispatch({ ...event, raw_message: 'canonical echo' }, 'bridge');
await dispatch({ ...event, raw_message: 'latest canonical echo' }, 'bridge');
expect(emitEvent).toHaveBeenCalledOnce();
expect(emitEvent).toHaveBeenCalledWith(event);
expect(stored.get(-7654321)?.raw_message).toBe('latest canonical echo');
});
it('reports concurrent copies of a client group send only once', async () => {
const { dispatch, emitEvent } = makeInstanceHarness();
const event = groupSentEvent();
await Promise.all([
dispatch({ ...event }, 'bridge'),
dispatch({ ...event }, 'bridge'),
dispatch({ ...event }, 'bridge'),
]);
expect(emitEvent).toHaveBeenCalledOnce();
});
it('keeps group sends with identical text but different message identities', async () => {
const { dispatch, emitEvent } = makeInstanceHarness();
const event = groupSentEvent();
await dispatch(event, 'bridge');
await dispatch({ ...event, message_seq: RECEIPT.sequence + 1 }, 'bridge');
await dispatch({ ...event, group_id: 30004 }, 'bridge');
await dispatch({ ...event, message_id: -7654322 }, 'bridge');
expect(emitEvent).toHaveBeenCalledTimes(4);
});
it('keeps group deduplication isolated between account instances', async () => {
const first = makeInstanceHarness();
const second = makeInstanceHarness();
await first.dispatch(groupSentEvent(), 'bridge');
await second.dispatch(
groupSentEvent({ self_id: SELF_ID + 1, user_id: SELF_ID + 1 }),
'bridge',
);
expect(first.emitEvent).toHaveBeenCalledOnce();
expect(second.emitEvent).toHaveBeenCalledOnce();
});
it('expires group self-send identities without treating stored history as already reported', async () => {
vi.useFakeTimers();
try {
const { dispatch, emitEvent } = makeInstanceHarness();
const event = groupSentEvent();
await dispatch(event, 'bridge');
await dispatch({ ...event }, 'bridge');
expect(emitEvent).toHaveBeenCalledOnce();
vi.advanceTimersByTime(120_000);
await dispatch({ ...event }, 'bridge');
expect(emitEvent).toHaveBeenCalledTimes(2);
} finally {
vi.useRealTimers();
}
});
it('keeps the timestamp bound when a group sequence and hash are reused', async () => {
const { dispatch, emitEvent } = makeInstanceHarness();
const event = groupSentEvent();
await dispatch(event, 'bridge');
await dispatch({ ...event, time: RECEIPT.timestamp + 121 }, 'bridge');
expect(emitEvent).toHaveBeenCalledTimes(2);
});
it('does not publish an event suppressed by the persistence recall check', async () => {
const { dispatch, emitEvent } = makeInstanceHarness();
// The event's cache operation is the final authority after asynchronous
// conversion: a recall may have committed while media was resolving.
const event = privateSentEvent();
const original = OneBotInstance.prototype;
const cache = vi
.spyOn(
original as unknown as {
cacheMessageEvent(event: JsonObject): Promise<boolean>;
},
'cacheMessageEvent',
)
.mockResolvedValueOnce(false);
try {
await dispatch(event, 'bridge');
expect(emitEvent).not.toHaveBeenCalled();
} finally {
cache.mockRestore();
}
});
it('reports upload_private_file as a private self-sent message', async () => {
const { ctx, dispatchEvent } = makeContext();
const handler = new ApiHandler(buildApiContext(ctx));
const response = await handler.handle('upload_private_file', {
user_id: PEER_ID,
file: 'base64://AQID',
name: 'inline.txt',
});
expect(response).toMatchObject({
status: 'ok',
retcode: 0,
data: { file_id: 'uploaded-file-uuid' },
});
expect(ctx.bridge.apis.groupFile.uploadPrivate).toHaveBeenCalledWith(
PEER_ID,
'base64://AQID',
'inline.txt',
true,
false,
);
expect(ctx.bridge.apis.message.sendC2cFile).toHaveBeenCalledOnce();
expect(dispatchEvent).toHaveBeenCalledOnce();
expect(dispatchEvent).toHaveBeenCalledWith(
expect.objectContaining({
post_type: 'message_sent',
message_type: 'private',
self_id: SELF_ID,
user_id: SELF_ID,
target_id: PEER_ID,
message_seq: RECEIPT.clientSequence,
message: [
{
type: 'file',
data: expect.objectContaining({
file_id: 'uploaded-file-uuid',
file: 'inline.txt',
file_size: 3,
}),
},
],
}),
'send',
);
});
it('fails upload_private_file when publishing the private file fails', async () => {
const { ctx, dispatchEvent } = makeContext();
vi.mocked(ctx.bridge.apis.message.sendC2cFile).mockRejectedValueOnce(
new Error('private file publication rejected'),
);
const handler = new ApiHandler(buildApiContext(ctx));
const response = await handler.handle('upload_private_file', {
user_id: PEER_ID,
file: 'base64://AQID',
name: 'inline.txt',
});
expect(response).toMatchObject({
status: 'failed',
retcode: 100,
wording: 'private file publication rejected',
});
expect(dispatchEvent).not.toHaveBeenCalled();
});
it('does not report upload_private_file when upload_file is disabled', async () => {
const { ctx, dispatchEvent } = makeContext();
const handler = new ApiHandler(buildApiContext(ctx));
const response = await handler.handle('upload_private_file', {
user_id: PEER_ID,
file: 'base64://AQID',
name: 'inline.txt',
upload_file: false,
});
expect(response).toMatchObject({
status: 'ok',
retcode: 0,
data: { file_id: 'uploaded-file-uuid' },
});
expect(ctx.bridge.apis.message.sendC2cFile).not.toHaveBeenCalled();
expect(dispatchEvent).not.toHaveBeenCalled();
});
it('reports a private message sent through an action', async () => {
const { ctx, dispatchEvent } = makeContext();
const api = buildApiContext(ctx);
const result = await api.sendPrivateMessage(
PEER_ID,
[{ type: 'text', data: { text: 'hello' } }],
false,
);
expect(result.messageId).not.toBe(0);
expect(dispatchEvent).toHaveBeenCalledOnce();
expect(dispatchEvent).toHaveBeenCalledWith(
expect.objectContaining({
post_type: 'message_sent',
message_type: 'private',
sub_type: 'friend',
self_id: SELF_ID,
user_id: SELF_ID,
target_id: PEER_ID,
message_seq: RECEIPT.clientSequence,
}),
'send',
);
});
it('reports storage failure without failing or repeating an already successful send', async () => {
const { ctx, dispatchEvent, events } = makeContext();
vi.spyOn(ctx.messageStore, 'storeEvent').mockImplementation(() => {
throw new Error('disk full');
});
const handler = new ApiHandler(buildApiContext(ctx));
const entries: LogEntry[] = [];
const unsubscribe = subscribeLogs((entry) => entries.push(entry));
try {
const response = await handler.handle('send_private_msg', {
user_id: PEER_ID,
message: 'hello',
});
expect(response).toMatchObject({ status: 'ok', retcode: 0 });
expect(ctx.bridge.apis.message.sendPrivate).toHaveBeenCalledOnce();
expect(events.size).toBe(0);
expect(dispatchEvent).not.toHaveBeenCalled();
expect(entries).toContainEqual(
expect.objectContaining({
level: 'warn',
message: expect.stringMatching(/failed to cache self-sent message.*disk full/),
}),
);
} finally {
unsubscribe();
}
});
it('[#288] reports a private forward built through an action', async () => {
const { ctx, dispatchEvent } = makeContext();
const api = buildApiContext(ctx);
const result = await api.sendPrivateForwardMsg(PEER_ID, [
{
type: 'node',
data: {
user_id: SELF_ID,
nickname: 'SnowLuma',
content: [{ type: 'text', data: { text: 'forwarded' } }],
},
},
]);
expect(result.messageId).not.toBe(0);
expect(dispatchEvent).toHaveBeenCalledOnce();
expect(dispatchEvent).toHaveBeenCalledWith(
expect.objectContaining({
post_type: 'message_sent',
message_type: 'private',
self_id: SELF_ID,
user_id: SELF_ID,
target_id: PEER_ID,
message_seq: RECEIPT.clientSequence,
message: [
expect.objectContaining({
type: 'forward',
}),
],
}),
'send',
);
});
it('[#288] reports a cached message forwarded to a friend through an action', async () => {
const { ctx, dispatchEvent, events } = makeContext();
const sourceMessageId = 55;
events.set(sourceMessageId, {
message_id: sourceMessageId,
message_type: 'private',
user_id: PEER_ID,
message: [{ type: 'text', data: { text: 'forward me' } }],
});
const api = buildApiContext(ctx);
const result = await api.forwardSingleMsg(sourceMessageId, { userId: PEER_ID });
expect(result.messageId).not.toBe(0);
expect(dispatchEvent).toHaveBeenCalledOnce();
expect(dispatchEvent).toHaveBeenCalledWith(
expect.objectContaining({
post_type: 'message_sent',
message_type: 'private',
self_id: SELF_ID,
user_id: SELF_ID,
target_id: PEER_ID,
message_seq: RECEIPT.clientSequence,
message: [{ type: 'text', data: { text: 'forward me' } }],
}),
'send',
);
});
it('[#288] does not report a private forward without a reliable sequence', async () => {
const { ctx, dispatchEvent, events } = makeContext({ ...RECEIPT, sequence: 0 });
const sourceMessageId = 56;
events.set(sourceMessageId, {
message_id: sourceMessageId,
message_type: 'private',
user_id: PEER_ID,
message: [{ type: 'text', data: { text: 'forward me' } }],
});
const api = buildApiContext(ctx);
await api.forwardSingleMsg(sourceMessageId, { userId: PEER_ID });
expect(dispatchEvent).not.toHaveBeenCalled();
});
it('reports a private file sent through an action', async () => {
const { ctx, dispatchEvent } = makeContext();
const api = buildApiContext(ctx);
const result = await api.sendPrivateMessage(
PEER_ID,
[
{
type: 'file',
data: {
file_id: 'file-uuid',
name: 'report.txt',
size: 128,
md5: '00112233445566778899aabbccddeeff',
},
},
],
false,
);
expect(result.messageId).not.toBe(0);
expect(dispatchEvent).toHaveBeenCalledOnce();
expect(dispatchEvent).toHaveBeenCalledWith(
expect.objectContaining({
post_type: 'message_sent',
message_type: 'private',
self_id: SELF_ID,
user_id: SELF_ID,
target_id: PEER_ID,
message: [
expect.objectContaining({
type: 'file',
data: expect.objectContaining({
file_id: 'file-uuid',
file: 'report.txt',
file_size: 128,
}),
}),
],
}),
'send',
);
});
it('reports a private file uploaded and sent through an action', async () => {
const { ctx, dispatchEvent } = makeContext();
const api = buildApiContext(ctx);
await api.sendPrivateMessage(
PEER_ID,
[
{
type: 'file',
data: {
file: 'base64://AQID',
name: 'inline.txt',
},
},
],
false,
);
expect(dispatchEvent).toHaveBeenCalledOnce();
expect(dispatchEvent).toHaveBeenCalledWith(
expect.objectContaining({
post_type: 'message_sent',
message_type: 'private',
target_id: PEER_ID,
message: [
expect.objectContaining({
type: 'file',
data: expect.objectContaining({
file: 'inline.txt',
url: 'base64://AQID',
}),
}),
],
}),
'send',
);
});
it('reports each QQ message produced by a mixed private action', async () => {
const fileReceipt: Receipt = {
messageId: 654321,
sequence: 88,
clientSequence: 10,
random: 654321,
timestamp: RECEIPT.timestamp + 1,
};
const { ctx, dispatchEvent } = makeContext([RECEIPT, fileReceipt]);
const api = buildApiContext(ctx);
const result = await api.sendPrivateMessage(
PEER_ID,
[
{ type: 'text', data: { text: 'attachment:' } },
{
type: 'file',
data: {
file_id: 'mixed-file-uuid',
name: 'mixed.txt',
size: 64,
md5: '00112233445566778899aabbccddeeff',
},
},
],
false,
);
expect(dispatchEvent).toHaveBeenCalledTimes(2);
expect(dispatchEvent.mock.calls[0]?.[0]).toMatchObject({
post_type: 'message_sent',
message_seq: RECEIPT.clientSequence,
message: [{ type: 'text', data: { text: 'attachment:' } }],
});
expect(dispatchEvent.mock.calls[1]?.[0]).toMatchObject({
post_type: 'message_sent',
message_seq: fileReceipt.clientSequence,
message: [
{
type: 'file',
data: expect.objectContaining({ file_id: 'mixed-file-uuid' }),
},
],
});
expect(result.messageId).toBe(
hashMessageIdInt32(fileReceipt.sequence, PEER_ID, PRIVATE_NT_MESSAGE_EVENT),
);
});
it('reports each file in a multi-file private action separately', async () => {
const secondReceipt: Receipt = {
messageId: 654321,
sequence: 88,
clientSequence: 10,
random: 654321,
timestamp: RECEIPT.timestamp + 1,
};
const { ctx, dispatchEvent } = makeContext([RECEIPT, secondReceipt]);
const api = buildApiContext(ctx);
await api.sendPrivateMessage(
PEER_ID,
[
{
type: 'file',
data: {
file_id: 'first-file',
name: 'first.txt',
size: 1,
md5: '00112233445566778899aabbccddeeff',
},
},
{
type: 'file',
data: {
file_id: 'second-file',
name: 'second.txt',
size: 2,
md5: 'ffeeddccbbaa00998877665544332211',
},
},
],
false,
);
expect(dispatchEvent).toHaveBeenCalledTimes(2);
expect(
dispatchEvent.mock.calls.map(
([event]) => ((event.message as JsonObject[])[0]?.data as JsonObject)?.file_id,
),
).toEqual(['first-file', 'second-file']);
});
it('does not report a later QQ echo after the action event', async () => {
const { dispatch, emitEvent, stored } = makeInstanceHarness();
const sentEvent = privateSentEvent();
const qqEcho: JsonObject = {
...sentEvent,
raw_message: 'canonical echo',
sender: { user_id: SELF_ID, nickname: 'Server profile' },
};
await dispatch(sentEvent, 'send');
await dispatch(qqEcho, 'bridge');
expect(emitEvent).toHaveBeenCalledOnce();
expect(stored.get(987654)?.raw_message).toBe('canonical echo');
});
it('records one truthful handoff terminal for reporting, duplicate suppression, and status suppression', async () => {
const previousLevel = getLogLevel();
const entries: LogEntry[] = [];
const unsubscribe = subscribeLogs((entry) => entries.push(entry));
setLogLevel('trace');
try {
const logger = createLogger('Event').child({ uin: SELF_ID });
const reporting = makeInstanceHarness({ logger });
await runWithRequestId(6001, () => reporting.dispatch(privateSentEvent(), 'send'));
expect(reporting.emitEvent).toHaveBeenCalledOnce();
const duplicate = makeInstanceHarness({ logger });
await runWithRequestId(6002, async () => {
await duplicate.dispatch(privateSentEvent(), 'send');
await duplicate.dispatch(privateSentEvent({ raw_message: 'canonical' }), 'bridge');
});
expect(duplicate.emitEvent).toHaveBeenCalledOnce();
const status = makeInstanceHarness({
logger,
statusCommand: { enabled: true, swallow: true, cooldownSeconds: 5, trigger: '#sl' },
});
await runWithRequestId(6003, () =>
status.dispatch(
privateSentEvent({
post_type: 'message',
user_id: PEER_ID,
target_id: undefined,
raw_message: '#sl',
message: [{ type: 'text', data: { text: '#sl' } }],
}),
'bridge',
),
);
expect(status.emitEvent).not.toHaveBeenCalled();
const terminals = entries.filter(
(entry) =>
entry.scope === 'Event' &&
entry.level === 'trace' &&
entry.message.startsWith('event_terminal'),
);
expect(terminals.filter((entry) => entry.req === 6001)).toHaveLength(1);
expect(terminals.find((entry) => entry.req === 6001)?.message).toContain(
'outcome=reporting_started',
);
expect(terminals.filter((entry) => entry.req === 6002)).toHaveLength(2);
expect(
terminals.find(
(entry) =>
entry.req === 6002 && entry.message.includes('reason=duplicate_self_echo'),
)?.message,
).toContain('outcome=suppressed');
expect(terminals.filter((entry) => entry.req === 6003)).toHaveLength(1);
expect(terminals[terminals.length - 1]?.message).toContain(
'outcome=suppressed reason=status_command',
);
} finally {
unsubscribe();
setLogLevel(previousLevel);
}
});
it('does not dispatch an event for a receipt without a reliable sequence', async () => {
const { ctx, dispatchEvent } = makeContext({ ...RECEIPT, sequence: 0 });
const api = buildApiContext(ctx);
await api.sendPrivateMessage(PEER_ID, [{ type: 'text', data: { text: 'hello' } }], false);
expect(dispatchEvent).not.toHaveBeenCalled();
});
it('keeps an earlier QQ echo and does not report the later action event', async () => {
const { ctx, dispatchEvent, events } = makeContext();
const messageId = hashMessageIdInt32(RECEIPT.sequence, PEER_ID, PRIVATE_NT_MESSAGE_EVENT);
const qqEcho: JsonObject = {
time: RECEIPT.timestamp,
self_id: SELF_ID,
post_type: 'message_sent',
message_type: 'private',
sub_type: 'friend',
message_id: messageId,
message_seq: RECEIPT.clientSequence,
user_id: SELF_ID,
target_id: PEER_ID,
message: [{ type: 'text', data: { text: 'hello' } }],
raw_message: 'canonical echo',
sender: { user_id: SELF_ID, nickname: 'Server profile' },
};
events.set(messageId, qqEcho);
const api = buildApiContext(ctx);
const result = await api.sendPrivateMessage(
PEER_ID,
[{ type: 'text', data: { text: 'hello' } }],
false,
);
expect(result.messageId).toBe(messageId);
expect(dispatchEvent).not.toHaveBeenCalled();
expect(events.get(messageId)).toBe(qqEcho);
});
it('does not invent a friend event for a group temporary-session send', async () => {
const groupId = 30003;
const { ctx, dispatchEvent } = makeContext();
ctx.tempSessions.record(PEER_ID, groupId);
const api = buildApiContext(ctx);
await api.sendPrivateMessage(
PEER_ID,
[{ type: 'text', data: { text: 'hello' } }],
false,
groupId,
);
expect(dispatchEvent).not.toHaveBeenCalled();
});
it('does not suppress a bridge echo that was cached but never reported', async () => {
const { dispatch, emitEvent, stored } = makeInstanceHarness();
const cached: JsonObject = {
time: RECEIPT.timestamp,
self_id: SELF_ID,
post_type: 'message_sent',
message_type: 'group',
sub_type: 'normal',
message_id: 7654321,
message_seq: RECEIPT.sequence,
group_id: 30003,
user_id: SELF_ID,
message: [],
raw_message: '',
sender: { user_id: SELF_ID },
};
stored.set(7654321, cached);
await dispatch({ ...cached, raw_message: 'canonical echo' }, 'bridge');
expect(emitEvent).toHaveBeenCalledOnce();
expect(stored.get(7654321)?.raw_message).toBe('canonical echo');
});
it('does not suppress a hash collision from another conversation', async () => {
const { dispatch, emitEvent } = makeInstanceHarness();
const sentEvent = privateSentEvent();
await dispatch(sentEvent, 'send');
await dispatch({ ...sentEvent, target_id: PEER_ID + 1 }, 'bridge');
expect(emitEvent).toHaveBeenCalledTimes(2);
});
});