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
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:
commit
fa9c73363d
1367 files changed
+355735
No files matched your search
@@ -0,0 +1,350 @@
|
||||
// When an incoming message quotes one SnowLuma's store doesn't have, the
|
||||
// receive pipeline fetches just that message from the server (group →
|
||||
// getGroupMessageBySeq, c2c → timestamp roam lookup, both rate-limited) and
|
||||
// persists it under the SAME id the reply resolves to, so a consumer's get_msg
|
||||
// on the quote hits. These tests cover the gating + keying of that back-fill.
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { backfillReplyTarget } from '../src/modules/message-actions';
|
||||
import {
|
||||
GROUP_MESSAGE_EVENT,
|
||||
PRIVATE_NT_MESSAGE_EVENT,
|
||||
hashMessageIdInt32,
|
||||
} from '../src/message-id';
|
||||
import { createMessageIdResolver } from '../src/message-id-resolver';
|
||||
|
||||
const SELF = 10001;
|
||||
|
||||
const converterCtx = {
|
||||
selfId: SELF,
|
||||
imageUrlResolver: null,
|
||||
mediaUrlResolver: null,
|
||||
messageIdResolver: createMessageIdResolver({ resolvePrivateReplyMessageId: async () => null }),
|
||||
mediaSegmentSink: null,
|
||||
} as any;
|
||||
|
||||
function fakeStore(has = false) {
|
||||
return {
|
||||
findEvent: vi.fn(() => (has ? ({ message_id: 1 } as any) : null)),
|
||||
storeMeta: vi.fn(),
|
||||
storeEvent: vi.fn(),
|
||||
findMeta: vi.fn(async () => null),
|
||||
resolvePrivateReplyMessageId: vi.fn(async () => null),
|
||||
storeHistoryEvent: vi.fn(async () => true),
|
||||
};
|
||||
}
|
||||
|
||||
function groupEvent(replySeq: number | null) {
|
||||
const elements: any[] = replySeq != null ? [{ type: 'reply', replySeq }] : [];
|
||||
elements.push({ type: 'text', text: '同意' });
|
||||
return {
|
||||
kind: 'group_message',
|
||||
groupName: '',
|
||||
time: 1,
|
||||
selfUin: SELF,
|
||||
groupId: 700,
|
||||
senderUin: 900,
|
||||
senderNick: '',
|
||||
senderCard: '',
|
||||
senderRole: 'member',
|
||||
msgSeq: 999,
|
||||
msgId: 2,
|
||||
elements,
|
||||
} as any;
|
||||
}
|
||||
|
||||
function fetchedGroupMessage(msgSeq: number) {
|
||||
return {
|
||||
kind: 'group_message',
|
||||
groupName: '',
|
||||
time: 1700000000,
|
||||
selfUin: SELF,
|
||||
groupId: 700,
|
||||
senderUin: 800,
|
||||
senderNick: 'orig',
|
||||
senderCard: '',
|
||||
senderRole: 'member',
|
||||
msgSeq,
|
||||
msgId: 1,
|
||||
elements: [{ type: 'text', text: 'quoted' }],
|
||||
} as any;
|
||||
}
|
||||
|
||||
function fetchedFriendMessage(msgSeq: number, senderUin: number) {
|
||||
return {
|
||||
kind: 'friend_message',
|
||||
time: 1700000000,
|
||||
selfUin: SELF,
|
||||
senderUin,
|
||||
peerUin: senderUin === SELF ? 448671521 : senderUin,
|
||||
senderNick: 'orig',
|
||||
msgSeq,
|
||||
ntMsgSeq: msgSeq + 1000,
|
||||
clientSeq: msgSeq,
|
||||
sequenceAuthoritative: true,
|
||||
msgId: 1,
|
||||
elements: [{ type: 'text', text: 'quoted' }],
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe('backfillReplyTarget', () => {
|
||||
it('group: fetches the quoted message and stores it under the resolved reply id', async () => {
|
||||
const store = fakeStore(false);
|
||||
const getGroupMessageBySeq = vi.fn(async () => fetchedGroupMessage(123));
|
||||
const ref = {
|
||||
selfId: SELF,
|
||||
converterCtx,
|
||||
messageStore: store,
|
||||
bridge: { apis: { message: { getGroupMessageBySeq } }, resolveUserUid: vi.fn() },
|
||||
} as any;
|
||||
|
||||
await backfillReplyTarget(ref, groupEvent(123));
|
||||
|
||||
expect(getGroupMessageBySeq).toHaveBeenCalledWith(700, 123, SELF);
|
||||
const targetId = hashMessageIdInt32(123, 700, GROUP_MESSAGE_EVENT);
|
||||
expect(store.storeEvent).toHaveBeenCalledWith(
|
||||
targetId,
|
||||
true,
|
||||
700,
|
||||
123,
|
||||
GROUP_MESSAGE_EVENT,
|
||||
expect.objectContaining({ message_id: targetId, message_type: 'group' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('c2c: resolves the friend uid, fetches, and stores under the peer session', async () => {
|
||||
const store = fakeStore(false);
|
||||
const getC2cLatestHistory = vi.fn(async () => [fetchedFriendMessage(456, 448671521)]);
|
||||
const resolveUserUid = vi.fn(async () => 'u_friend');
|
||||
const ref = {
|
||||
selfId: SELF,
|
||||
converterCtx,
|
||||
messageStore: store,
|
||||
bridge: { apis: { message: { getC2cLatestHistory } }, resolveUserUid },
|
||||
} as any;
|
||||
const event = {
|
||||
kind: 'friend_message',
|
||||
time: 1700000100,
|
||||
selfUin: SELF,
|
||||
senderUin: 448671521,
|
||||
senderNick: '',
|
||||
msgSeq: 999,
|
||||
msgId: 2,
|
||||
elements: [
|
||||
{ type: 'reply', replySeq: 456 },
|
||||
{ type: 'text', text: '同意' },
|
||||
],
|
||||
} as any;
|
||||
|
||||
await backfillReplyTarget(ref, event);
|
||||
|
||||
expect(resolveUserUid).toHaveBeenCalledWith(448671521);
|
||||
expect(getC2cLatestHistory).toHaveBeenCalledWith('u_friend', 20, SELF, 1700000101);
|
||||
const targetId = hashMessageIdInt32(1456, 448671521, PRIVATE_NT_MESSAGE_EVENT);
|
||||
expect(store.storeHistoryEvent).toHaveBeenCalledWith(
|
||||
targetId,
|
||||
false,
|
||||
448671521,
|
||||
1456,
|
||||
PRIVATE_NT_MESSAGE_EVENT,
|
||||
expect.objectContaining({ message_id: targetId, message_type: 'private' }),
|
||||
{
|
||||
sequenceAuthoritative: true,
|
||||
meta: expect.objectContaining({
|
||||
sequence: 1456,
|
||||
clientSequence: 456,
|
||||
privateDirection: 'incoming',
|
||||
sequenceAuthoritative: true,
|
||||
}),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('c2c: a bot self-sent quoted message is keyed under the peer (not self)', async () => {
|
||||
// The fetched message was sent by us (senderUin = self) — it converts with
|
||||
// user_id=self, but its canonical id still belongs to the peer conversation.
|
||||
const store = fakeStore(false);
|
||||
const getC2cLatestHistory = vi.fn(async () => [fetchedFriendMessage(456, SELF)]);
|
||||
const ref = {
|
||||
selfId: SELF,
|
||||
converterCtx,
|
||||
messageStore: store,
|
||||
bridge: {
|
||||
apis: { message: { getC2cLatestHistory } },
|
||||
resolveUserUid: vi.fn(async () => 'u_friend'),
|
||||
},
|
||||
} as any;
|
||||
const event = {
|
||||
kind: 'friend_message',
|
||||
time: 1700000100,
|
||||
selfUin: SELF,
|
||||
senderUin: 448671521,
|
||||
senderNick: '',
|
||||
msgSeq: 999,
|
||||
msgId: 2,
|
||||
elements: [{ type: 'reply', replySeq: 456, replySenderUin: SELF }],
|
||||
} as any;
|
||||
|
||||
await backfillReplyTarget(ref, event);
|
||||
|
||||
const targetId = hashMessageIdInt32(1456, 448671521, PRIVATE_NT_MESSAGE_EVENT);
|
||||
expect(store.storeHistoryEvent).toHaveBeenCalledWith(
|
||||
targetId,
|
||||
false,
|
||||
448671521,
|
||||
1456,
|
||||
PRIVATE_NT_MESSAGE_EVENT,
|
||||
expect.objectContaining({ message_id: targetId }),
|
||||
{
|
||||
sequenceAuthoritative: true,
|
||||
meta: expect.objectContaining({ privateDirection: 'outgoing' }),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('c2c: a self-sent reply echo backfills under the conversation peer', async () => {
|
||||
const peerUin = 448671521;
|
||||
const store = fakeStore(false);
|
||||
const getC2cLatestHistory = vi.fn(async () => [fetchedFriendMessage(456, peerUin)]);
|
||||
const ref = {
|
||||
selfId: SELF,
|
||||
converterCtx,
|
||||
messageStore: store,
|
||||
bridge: {
|
||||
apis: { message: { getC2cLatestHistory } },
|
||||
resolveUserUid: vi.fn(async () => 'u_friend'),
|
||||
},
|
||||
} as any;
|
||||
const event = {
|
||||
kind: 'friend_message',
|
||||
time: 1700000100,
|
||||
selfUin: SELF,
|
||||
senderUin: SELF,
|
||||
peerUin,
|
||||
senderNick: '',
|
||||
msgSeq: 999,
|
||||
msgId: 2,
|
||||
elements: [{ type: 'reply', replySeq: 456 }],
|
||||
} as any;
|
||||
|
||||
await backfillReplyTarget(ref, event);
|
||||
|
||||
const targetId = hashMessageIdInt32(1456, peerUin, PRIVATE_NT_MESSAGE_EVENT);
|
||||
expect(store.storeHistoryEvent).toHaveBeenCalledWith(
|
||||
targetId,
|
||||
false,
|
||||
peerUin,
|
||||
1456,
|
||||
PRIVATE_NT_MESSAGE_EVENT,
|
||||
expect.objectContaining({ message_id: targetId, message_type: 'private' }),
|
||||
{
|
||||
sequenceAuthoritative: true,
|
||||
meta: expect.objectContaining({ privateDirection: 'incoming' }),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('no-op when the quoted message is already stored (no fetch)', async () => {
|
||||
const store = fakeStore(true); // findEvent hits
|
||||
const getGroupMessageBySeq = vi.fn();
|
||||
const ref = {
|
||||
selfId: SELF,
|
||||
converterCtx,
|
||||
messageStore: store,
|
||||
bridge: { apis: { message: { getGroupMessageBySeq } }, resolveUserUid: vi.fn() },
|
||||
} as any;
|
||||
|
||||
await backfillReplyTarget(ref, groupEvent(123));
|
||||
|
||||
expect(getGroupMessageBySeq).not.toHaveBeenCalled();
|
||||
expect(store.storeEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('no-op when the message has no reply element', async () => {
|
||||
const store = fakeStore(false);
|
||||
const getGroupMessageBySeq = vi.fn();
|
||||
const ref = {
|
||||
selfId: SELF,
|
||||
converterCtx,
|
||||
messageStore: store,
|
||||
bridge: { apis: { message: { getGroupMessageBySeq } }, resolveUserUid: vi.fn() },
|
||||
} as any;
|
||||
|
||||
await backfillReplyTarget(ref, groupEvent(null));
|
||||
|
||||
expect(getGroupMessageBySeq).not.toHaveBeenCalled();
|
||||
expect(store.storeEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('reconstructs from replyElements when the server has nothing (tier 2)', async () => {
|
||||
const store = fakeStore(false);
|
||||
const getGroupMessageBySeq = vi.fn(async () => null);
|
||||
const ref = {
|
||||
selfId: SELF,
|
||||
converterCtx,
|
||||
messageStore: store,
|
||||
bridge: { apis: { message: { getGroupMessageBySeq } }, resolveUserUid: vi.fn() },
|
||||
} as any;
|
||||
const event = {
|
||||
kind: 'group_message',
|
||||
groupName: '',
|
||||
time: 1,
|
||||
selfUin: SELF,
|
||||
groupId: 700,
|
||||
senderUin: 900,
|
||||
senderNick: '',
|
||||
senderCard: '',
|
||||
senderRole: 'member',
|
||||
msgSeq: 999,
|
||||
msgId: 2,
|
||||
elements: [
|
||||
{
|
||||
type: 'reply',
|
||||
replySeq: 123,
|
||||
replySenderUin: 800,
|
||||
replyElements: [{ type: 'text', text: 'quoted' }],
|
||||
},
|
||||
],
|
||||
} as any;
|
||||
|
||||
await backfillReplyTarget(ref, event);
|
||||
|
||||
const targetId = hashMessageIdInt32(123, 700, GROUP_MESSAGE_EVENT);
|
||||
expect(store.storeEvent).toHaveBeenCalledOnce();
|
||||
expect(store.storeEvent).toHaveBeenCalledWith(
|
||||
targetId,
|
||||
true,
|
||||
700,
|
||||
123,
|
||||
GROUP_MESSAGE_EVENT,
|
||||
expect.objectContaining({ message_id: targetId, message_type: 'group', user_id: 800 }),
|
||||
{ sequenceAuthoritative: false },
|
||||
);
|
||||
});
|
||||
|
||||
it('stores a [引用消息] placeholder when server fetch and replyElements both miss (tier 3)', async () => {
|
||||
const store = fakeStore(false);
|
||||
const getGroupMessageBySeq = vi.fn(async () => null);
|
||||
const ref = {
|
||||
selfId: SELF,
|
||||
converterCtx,
|
||||
messageStore: store,
|
||||
bridge: { apis: { message: { getGroupMessageBySeq } }, resolveUserUid: vi.fn() },
|
||||
} as any;
|
||||
|
||||
await backfillReplyTarget(ref, groupEvent(123));
|
||||
|
||||
expect(getGroupMessageBySeq).toHaveBeenCalledOnce();
|
||||
const targetId = hashMessageIdInt32(123, 700, GROUP_MESSAGE_EVENT);
|
||||
expect(store.storeEvent).toHaveBeenCalledOnce();
|
||||
expect(store.storeEvent).toHaveBeenCalledWith(
|
||||
targetId,
|
||||
true,
|
||||
700,
|
||||
123,
|
||||
GROUP_MESSAGE_EVENT,
|
||||
expect.objectContaining({ message_id: targetId, message_type: 'group' }),
|
||||
{ sequenceAuthoritative: false },
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user