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

+908
View File
@@ -0,0 +1,908 @@
import { describe, it, expect, vi } from 'vitest';
import { protobuf_decode, protobuf_encode } from '@snowluma/proton';
import type { QFaceExtra, QSmallFaceExtra } from '@snowluma/proto-defs/element';
import type { OidbBase } from '@snowluma/proto-defs/oidb';
import type {
OidbFetchSysFacesReq,
OidbFetchSysFacesResp,
OidbFetchSysFaceReq,
OidbFetchSysFaceResp,
} from '@snowluma/proto-defs/oidb-actions/sys-faces';
import {
FetchSysFaces,
findFaceEntity,
isSuperFaceEntry,
isSuperFaceId,
type SysFaceEntry,
type SysFacePackEntry,
} from '../src/oidb-services/sys-faces/fetch-sys-faces';
import {
faceWireFor,
SysFaceStore,
sysFaceStore,
type SysFaceCatalogSnapshot,
type SysFaceCatalogStorage,
} from '../src/sys-face-store';
import { buildSendElems } from '../src/element-builder';
import { FetchSysFace } from '../src/oidb-services/sys-faces/fetch-sys-face';
function deferred<T>() {
let resolve!: (value: T) => void;
let reject!: (error: Error) => void;
const promise = new Promise<T>((yes, no) => {
resolve = yes;
reject = no;
});
return { promise, resolve, reject };
}
function entry(
qSid: string,
aniStickerType: number | null,
aniStickerPackId: number | null,
aniStickerId: number | null,
qDes = '',
emojiNameAlias: string[] = [],
): SysFaceEntry {
return {
qSid,
qDes,
emCode: '',
qCid: null,
aniStickerType,
aniStickerPackId,
aniStickerId,
url: null,
emojiNameAlias,
aniStickerWidth: null,
aniStickerHeight: null,
};
}
// Animated faces include ordinary type-1 stickers as well as special types.
const CATALOG: SysFacePackEntry[] = [
{ packName: '经典', emojis: [entry('14', null, null, null, '/微笑', ['smile'])] },
{
packName: '超级表情',
emojis: [
entry('392', 3, 2, 38, '/龙年快乐'), // super → CommonElem 37
entry('424', 1, 1, 52),
entry('358', 2, 1, 33), // super (packId 1 but type 2) → CommonElem 37
],
},
];
class MemoryCatalogStorage implements SysFaceCatalogStorage {
saved: SysFaceCatalogSnapshot[] = [];
constructor(private readonly initial: SysFaceCatalogSnapshot | null = null) {}
async load(): Promise<SysFaceCatalogSnapshot | null> {
return this.initial;
}
async save(snapshot: SysFaceCatalogSnapshot): Promise<void> {
this.saved.push(snapshot);
}
}
describe('isSuperFaceEntry / isSuperFaceId', () => {
it('recognizes every animated sticker type', () => {
expect(isSuperFaceEntry(entry('x', 3, 2, 1))).toBe(true);
expect(isSuperFaceEntry(entry('x', 2, 1, 1))).toBe(true); // type≠1
expect(isSuperFaceEntry(entry('x', 1, 2, 1))).toBe(true); // pack≠1
expect(isSuperFaceEntry(entry('x', 1, 1, 52))).toBe(true);
expect(isSuperFaceEntry(entry('x', 0, 0, 0))).toBe(false);
expect(isSuperFaceEntry(entry('x', null, null, null))).toBe(false); // not aniSticker
});
it('isSuperFaceId walks the packs by id', () => {
expect(isSuperFaceId(CATALOG, 392)).toBe(true);
expect(isSuperFaceId(CATALOG, 424)).toBe(true);
expect(isSuperFaceId(CATALOG, 14)).toBe(false);
expect(isSuperFaceId(CATALOG, 99999)).toBe(false); // unknown
});
it('findFaceEntity returns the matching emoji or null', () => {
expect(findFaceEntity(CATALOG, 392)?.qSid).toBe('392');
expect(findFaceEntity(CATALOG, 99999)).toBeNull();
});
});
describe('faceWireFor — classification', () => {
it('super face → super wire with pack/sticker ids', () => {
expect(faceWireFor(entry('392', 3, 2, 38), 392)).toEqual({
kind: 'super',
packId: '2',
stickerId: '38',
stickerType: 3,
});
});
it('rejects incomplete super-face metadata instead of filling guessed ids', () => {
expect(() => faceWireFor(entry('392', 3, 2, null), 392)).toThrow(
/incomplete super-face metadata/,
);
});
it('uses the animation metadata for type-1 stickers too', () => {
expect(faceWireFor(entry('424', 1, 1, 52), 424)).toEqual({
kind: 'super',
packId: '1',
stickerId: '52',
stickerType: 1,
});
});
it('permits uncatalogued ids using their small representation', () => {
expect(faceWireFor(null, 14)).toEqual({ kind: 'classic' });
expect(faceWireFor(undefined, 504)).toEqual({ kind: 'small' });
});
});
describe('FetchSysFaces protocol contract', () => {
it('encodes the native 0x9154_1 request layout', () => {
const body = FetchSysFaces.serialize({} as never, {});
expect(body).toEqual({
field1: 0,
field2: 7,
field3: 0,
});
const decoded = protobuf_decode<OidbBase<OidbFetchSysFacesReq>>(
FetchSysFaces.encode({ body }),
);
expect(decoded.body).toEqual({
field1: null,
field2: 7,
field3: null,
field4: null,
});
});
});
describe('FetchSysFace protocol contract', () => {
it('queries the requested id and preserves animation metadata from the response', async () => {
const sendRawPacket = vi.fn(async (_command: string, _body: Uint8Array) => ({
success: true,
gotResponse: true,
errorCode: 0,
errorMessage: '',
responseData: protobuf_encode<OidbBase<OidbFetchSysFaceResp>>({
body: {
emoji: {
qSid: '507',
qDes: '/test',
aniStickerType: 1,
aniStickerPackId: 4,
aniStickerId: 107,
aniStickerWidth: 200,
aniStickerHeight: 240,
},
},
}),
}));
const store = new SysFaceStore({ fetchCatalog: async () => CATALOG });
await expect(store.resolveWire({ sendRawPacket }, 507)).resolves.toEqual({
kind: 'super',
packId: '4',
stickerId: '107',
stickerType: 1,
});
expect(sendRawPacket).toHaveBeenCalledOnce();
const [command, bytes] = sendRawPacket.mock.calls[0]!;
expect(command).toBe('OidbSvcTrpcTcp.0x9155_1');
expect(protobuf_decode<OidbBase<OidbFetchSysFaceReq>>(bytes)).toMatchObject({
command: 0x9155,
subCommand: 1,
body: { qSid: '507' },
});
expect(store.lookup(507)).toMatchObject({ aniStickerWidth: 200, aniStickerHeight: 240 });
});
it('does not invent metadata for an empty lookup result', () => {
expect(FetchSysFace.deserialize({} as never, {})).toBeNull();
});
});
describe('FetchSysFaces.deserialize', () => {
it('decodes every group in the third panel, including unnamed and hidden faces (#482)', () => {
const wire = protobuf_encode<OidbFetchSysFacesResp>({
specialMagicFace: {
emojiList: [
{ emojiPackName: 'MagicFace', emojiDetail: [{ qSid: '358' }] },
{ emojiDetail: [{ qSid: '504', unknown10: 1 }, { qSid: '505' }] },
{ emojiPackName: 'Limited', emojiDetail: [{ qSid: '506' }, { qSid: '507' }] },
],
},
});
const packs = FetchSysFaces.deserialize(
{} as never,
protobuf_decode<OidbFetchSysFacesResp>(wire),
);
expect(packs.map((pack) => pack.packName)).toEqual(['MagicFace', 'MagicFace', 'Limited']);
expect(packs.flatMap((pack) => pack.emojis.map((face) => face.qSid))).toEqual([
'358',
'504',
'505',
'506',
'507',
]);
});
it('flattens common + big + magic packs', () => {
const packs = FetchSysFaces.deserialize(
{} as never,
{
commonFace: {
emojiList: [{ emojiPackName: '经典', emojiDetail: [{ qSid: '14' }] }],
},
specialBigFace: {
emojiList: [
{
emojiPackName: '超级',
emojiDetail: [{ qSid: '392', aniStickerType: 3, aniStickerPackId: 2 }],
},
],
},
specialMagicFace: { emojiList: [{ emojiDetail: [{ qSid: '999' }] }] },
} as never,
);
expect(packs.map((p) => p.packName)).toEqual(['经典', '超级', 'MagicFace']);
expect(packs[1].emojis[0].aniStickerType).toBe(3);
});
it('preserves Unicode catalog identifiers returned by QQ', () => {
const packs = FetchSysFaces.deserialize(
{} as never,
{
commonFace: {
emojiList: [
{
emojiPackName: 'emoji',
emojiDetail: [
{ qSid: '😊', qDes: '/嘿嘿', qCid: 128522, emCode: '400832' },
],
},
],
},
} as never,
);
expect(packs).toEqual([
{
packName: 'emoji',
emojis: [
expect.objectContaining({
qSid: '😊',
qDes: '/嘿嘿',
qCid: 128522,
emCode: '400832',
}),
],
},
]);
});
it('fails closed when an id field uses an unsupported wire encoding', () => {
const driftedFace = { qDes: '/未知表情' };
Object.defineProperty(driftedFace, Symbol.for('snowluma.proton.unknownFields'), {
value: {
fields: [{ fieldNumber: 1, wireType: 0, count: 1, totalByteLength: 1 }],
totalOccurrences: 1,
omittedOccurrences: 0,
omittedByteLength: 0,
},
});
expect(() =>
FetchSysFaces.deserialize(
{} as never,
{
specialMagicFace: { emojiList: [{ emojiDetail: [driftedFace] }] },
} as never,
),
).toThrow(/unsupported wire encoding.*wireTypes=0/);
});
});
describe('SysFaceStore — persistent catalog and query seam', () => {
const sender = {} as never;
it.each(['snapshot', 'read error'] as const)(
'does not replace a refreshed catalog with a late cache %s',
async (result) => {
const cache = deferred<SysFaceCatalogSnapshot | null>();
const store = new SysFaceStore({
storage: { load: () => cache.promise, save: async () => {} },
fetchCatalog: async () => CATALOG,
});
const ready = store.ensureReady(sender);
await store.refresh(sender);
if (result === 'snapshot') {
cache.resolve({ schemaVersion: 1, fetchedAt: 1, packs: CATALOG.slice(0, 1) });
} else {
cache.reject(new Error('obsolete cache read failed'));
}
await ready;
await expect(store.getCatalog(sender)).resolves.toEqual(CATALOG);
expect(store.lookup(392)?.qSid).toBe('392');
},
);
it('waits for a refresh that started during the disk read', async () => {
const cache = deferred<SysFaceCatalogSnapshot | null>();
const remote = deferred<SysFacePackEntry[]>();
const store = new SysFaceStore({
storage: { load: () => cache.promise, save: async () => {} },
fetchCatalog: () => remote.promise,
});
let ready = false;
const reading = store.ensureReady(sender).then(() => {
ready = true;
});
const refreshing = store.refresh(sender);
cache.resolve({ schemaVersion: 1, fetchedAt: 1, packs: CATALOG.slice(0, 1) });
await new Promise((resolve) => setImmediate(resolve));
expect(ready).toBe(false);
remote.resolve(CATALOG);
await Promise.all([reading, refreshing]);
expect(store.lookup(392)?.qSid).toBe('392');
});
it('retains a failed cache read until an explicit refresh repairs the catalog', async () => {
const load = vi.fn(async () => {
throw new Error('corrupt cache');
});
const fetchCatalog = vi.fn(async () => CATALOG);
const store = new SysFaceStore({ storage: { load, save: async () => {} }, fetchCatalog });
await expect(store.ensureReady(sender)).rejects.toThrow('corrupt cache');
await expect(store.ensureReady(sender)).rejects.toThrow('corrupt cache');
expect(load).toHaveBeenCalledOnce();
expect(fetchCatalog).not.toHaveBeenCalled();
await store.refresh(sender);
await expect(store.getCatalog(sender)).resolves.toEqual(CATALOG);
});
it('retries a failed login prewarm, then shares the completed job', async () => {
const load = vi.fn(async () => null);
const fetchCatalog = vi
.fn()
.mockRejectedValueOnce(new Error('network failed'))
.mockResolvedValueOnce(CATALOG);
const store = new SysFaceStore({ storage: { load, save: async () => {} }, fetchCatalog });
await expect(store.prewarm(sender)).rejects.toThrow('network failed');
const retry = store.prewarm(sender);
expect(store.prewarm(sender)).toBe(retry);
await retry;
await store.prewarm(sender);
expect(load).toHaveBeenCalledOnce();
expect(fetchCatalog).toHaveBeenCalledTimes(2);
});
it('accepts face id 0 from the authoritative catalog', async () => {
const zero = entry('0', null, null, null, '/惊讶');
const store = new SysFaceStore({
storage: new MemoryCatalogStorage({
schemaVersion: 1,
fetchedAt: 123,
packs: [{ packName: '经典', emojis: [zero] }],
}),
fetchCatalog: async () => {
throw new Error('network must not be used');
},
});
await store.ensureReady(sender);
expect(store.lookup(0)?.qDes).toBe('/惊讶');
expect(store.classify(0)).toEqual({ kind: 'classic' });
});
it('loads a persisted catalog and resolves ids, descriptions, aliases, and pack names', async () => {
const storage = new MemoryCatalogStorage({
schemaVersion: 1,
fetchedAt: 123,
packs: CATALOG,
});
const store = new SysFaceStore({
storage,
fetchCatalog: async () => {
throw new Error('network must not be used');
},
});
await store.ensureReady(sender);
expect(store.lookup(392)?.qDes).toBe('/龙年快乐');
expect(store.search('龙年').map((face) => face.qSid)).toEqual(['392']);
expect(store.search('SMILE').map((face) => face.qSid)).toEqual(['14']);
expect(store.search('经典').map((face) => face.qSid)).toEqual(['14']);
});
it('persists and searches Unicode catalog ids without exposing them as numeric face ids', async () => {
const unicodeFace = entry('😊', null, null, null, '/嘿嘿');
unicodeFace.qCid = 128522;
unicodeFace.emCode = '400832';
const catalog: SysFacePackEntry[] = [{ packName: 'emoji', emojis: [unicodeFace] }];
const storage = new MemoryCatalogStorage();
const store = new SysFaceStore({
storage,
now: () => 456,
fetchCatalog: async () => catalog,
});
await expect(store.refresh(sender)).resolves.toEqual(catalog);
expect(storage.saved[0]?.packs).toEqual(catalog);
expect(store.search('😊').map((face) => face.qSid)).toEqual(['😊']);
expect(store.search('嘿嘿').map((face) => face.qSid)).toEqual(['😊']);
expect(store.lookup(128522)).toBeNull();
});
it('preserves one face across multiple catalog packs', async () => {
const commonFace = entry('474', 1, 4, 76, '/给你一拳');
commonFace.emCode = '10474';
const specialFace: SysFaceEntry = {
...commonFace,
qDes: '/给你一拳(黄脸)',
emojiNameAlias: ['punch'],
};
const catalog: SysFacePackEntry[] = [
{ packName: '超级表情', emojis: [commonFace] },
{ packName: 'QQ黄脸', emojis: [specialFace] },
];
const storage = new MemoryCatalogStorage();
const store = new SysFaceStore({
storage,
now: () => 456,
fetchCatalog: async () => catalog,
});
await expect(store.refresh(sender)).resolves.toEqual(catalog);
expect(storage.saved[0]?.packs).toEqual(catalog);
await expect(store.getCatalog(sender)).resolves.toEqual(catalog);
expect(store.lookup(474)).toEqual(commonFace);
expect(store.search('超级表情').map((face) => face.qSid)).toEqual(['474']);
expect(store.search('QQ黄脸').map((face) => face.qSid)).toEqual(['474']);
expect(store.search('punch').map((face) => face.qSid)).toEqual(['474']);
const restored = new SysFaceStore({
storage: new MemoryCatalogStorage(storage.saved[0]!),
fetchCatalog: async () => {
throw new Error('network must not be used');
},
});
await restored.ensureReady(sender);
await expect(restored.getCatalog(sender)).resolves.toEqual(catalog);
expect(restored.search('QQ黄脸').map((face) => face.qSid)).toEqual(['474']);
});
it('still rejects empty identifiers and duplicates within one pack', () => {
const store = new SysFaceStore();
expect(() =>
store.load([{ packName: 'emoji', emojis: [entry('', null, null, null)] }]),
).toThrow(/invalid system face id/);
expect(() =>
store.load([
{
packName: 'emoji',
emojis: [entry('😊', null, null, null), entry('😊', null, null, null)],
},
]),
).toThrow(/duplicate system face id/);
expect(() =>
store.load([
{ packName: '超级表情', emojis: [entry('474', 1, 4, 76)] },
{
packName: 'QQ黄脸',
emojis: [entry('474', 1, 4, 76), entry('474', 1, 4, 76)],
},
]),
).toThrow(/duplicate system face id 474 within pack 1/);
});
it('rejects cross-pack copies with conflicting send metadata', () => {
const store = new SysFaceStore();
expect(() =>
store.load([
{ packName: '超级表情', emojis: [entry('474', 1, 4, 76)] },
{ packName: 'QQ黄脸', emojis: [entry('474', 3, 2, 38)] },
]),
).toThrow(/conflicting system face send metadata.*pack 0.*pack 1/);
});
it('allows non-addressable Unicode entries to carry panel-specific metadata', () => {
const store = new SysFaceStore();
expect(() =>
store.load([
{ packName: 'emoji', emojis: [entry('😊', null, null, null, '/微笑')] },
{ packName: '最近使用', emojis: [entry('😊', 3, 2, 38, '/最近使用')] },
]),
).not.toThrow();
expect(store.search('最近使用').map((face) => face.qSid)).toEqual(['😊']);
});
it('coalesces concurrent refreshes and persists the authoritative result once', async () => {
const storage = new MemoryCatalogStorage();
let fetches = 0;
let release!: (packs: SysFacePackEntry[]) => void;
const pending = new Promise<SysFacePackEntry[]>((resolve) => {
release = resolve;
});
const store = new SysFaceStore({
storage,
now: () => 456,
fetchCatalog: async () => {
fetches += 1;
return pending;
},
});
const first = store.refresh(sender);
const second = store.refresh(sender);
release(CATALOG);
await Promise.all([first, second]);
expect(fetches).toBe(1);
expect(storage.saved).toEqual([
{
schemaVersion: 1,
fetchedAt: 456,
packs: CATALOG,
},
]);
});
it('keeps usable faces when a server pack contains an id-less record', async () => {
const storage = new MemoryCatalogStorage();
const response = FetchSysFaces.deserialize(sender, {
commonFace: {
emojiList: [
{ emojiPackName: '经典', emojiDetail: [{ qSid: '14', qDes: '/微笑' }] },
{ emojiPackName: '小表情', emojiDetail: [{ qSid: '424', qDes: '/比心' }] },
],
},
specialBigFace: {
emojiList: [
{
emojiPackName: '超级表情',
emojiDetail: [{ qSid: '392', qDes: '/龙年快乐' }],
},
],
},
specialMagicFace: {
emojiList: [
{
emojiDetail: [{ qDes: '/目录占位' }, { qSid: '999', qDes: '/魔法表情' }],
},
],
},
} as never);
const store = new SysFaceStore({
storage,
now: () => 456,
fetchCatalog: async () => response,
});
await expect(store.refresh(sender)).resolves.toHaveLength(4);
expect(store.lookup(14)?.qDes).toBe('/微笑');
expect(store.lookup(999)?.qDes).toBe('/魔法表情');
expect(storage.saved[0]?.packs[3]?.emojis.map((face) => face.qSid)).toEqual(['999']);
});
it('refreshes a cache miss once, then keeps an authoritative not-found result', async () => {
const storage = new MemoryCatalogStorage({
schemaVersion: 1,
fetchedAt: 123,
packs: CATALOG.slice(0, 1),
});
let fetches = 0;
const store = new SysFaceStore({
fetchFace: async () => null,
storage,
fetchCatalog: async () => {
fetches += 1;
return CATALOG;
},
});
expect((await store.resolve(sender, 392))?.qSid).toBe('392');
expect(await store.resolve(sender, 99999)).toBeNull();
expect(await store.resolve(sender, 99999)).toBeNull();
expect(await store.resolveWire(sender, 99999)).toEqual({ kind: 'small' });
expect(fetches).toBe(1);
});
it('prewarms from login only once per process while sharing the result across accounts', async () => {
let fetches = 0;
const store = new SysFaceStore({
storage: new MemoryCatalogStorage(),
fetchCatalog: async () => {
fetches += 1;
return CATALOG;
},
});
await Promise.all([
store.prewarm({} as never),
store.prewarm({} as never),
store.prewarm({} as never),
]);
await store.prewarm({} as never);
expect(fetches).toBe(1);
});
it('waits for an in-flight login refresh before resolving a cached face', async () => {
const staleCatalog: SysFacePackEntry[] = [
{
packName: '旧目录',
emojis: [entry('392', 1, 1, 52)],
},
];
let release!: (packs: SysFacePackEntry[]) => void;
let markFetchStarted!: () => void;
const fetchStarted = new Promise<void>((resolve) => {
markFetchStarted = resolve;
});
const pending = new Promise<SysFacePackEntry[]>((resolve) => {
release = resolve;
});
const store = new SysFaceStore({
storage: new MemoryCatalogStorage({
schemaVersion: 1,
fetchedAt: 123,
packs: staleCatalog,
}),
fetchCatalog: async () => {
markFetchStarted();
return pending;
},
});
const prewarm = store.prewarm(sender);
await fetchStarted;
const resolved = store.resolveWire(sender, 392);
let settled = false;
void resolved.finally(() => {
settled = true;
});
await Promise.resolve();
expect(settled).toBe(false);
release(CATALOG);
await expect(resolved).resolves.toMatchObject({ kind: 'super' });
await prewarm;
});
});
describe('makeFaceElem (via buildSendElems) — three-way wire encoding', () => {
const ctx = { bridge: {} as never };
it.each(['0', '2', 'variant'])(
'preserves an explicit animation result %s',
async (resultId) => {
sysFaceStore.load(CATALOG);
const [element] = await buildSendElems([{ type: 'face', faceId: 392, resultId }], ctx);
expect(protobuf_decode<QFaceExtra>(element.commonElem!.pbElem!)).toMatchObject({
qsid: 392,
resultId,
randomType: 1,
});
},
);
it('keeps the default animation choice when no result is supplied', async () => {
sysFaceStore.load(CATALOG);
const [element] = await buildSendElems([{ type: 'face', faceId: 392 }], ctx);
const payload = protobuf_decode<QFaceExtra>(element.commonElem!.pbElem!);
expect(payload.resultId).toBeNull();
expect(payload.randomType).toBe(1);
});
it.each([14, 504])(
'rejects an animation result without animation metadata for %i',
async (faceId) => {
sysFaceStore.load(CATALOG);
await expect(
buildSendElems([{ type: 'face', faceId, resultId: '0' }]),
).rejects.toMatchObject({
code: 'INVALID_FIELD',
elementType: 'face',
field: 'resultId',
});
},
);
it('rejects incompatible animation options before loading the catalog', async () => {
await expect(
buildSendElems([{ type: 'face', faceId: 392, large: false, resultId: '0' }], ctx),
).rejects.toMatchObject({ code: 'INVALID_FIELD', field: 'resultId' });
});
it('awaits an authoritative refresh when the first send misses the persisted catalog', async () => {
sysFaceStore.load(CATALOG.slice(0, 1));
let requests = 0;
const responseData = protobuf_encode<OidbBase<OidbFetchSysFacesResp>>({
body: {
commonFace: {
emojiList: [
{
emojiPackName: '经典',
emojiDetail: [{ qSid: '14', qDes: '/微笑' }],
},
],
},
specialBigFace: {
emojiList: [
{
emojiPackName: '超级表情',
emojiDetail: [
{
qSid: '392',
qDes: '/龙年快乐',
aniStickerType: 3,
aniStickerPackId: 2,
aniStickerId: 38,
},
],
},
],
},
},
});
const liveCtx = {
bridge: {
sendRawPacket: async () => {
requests += 1;
return {
success: true,
gotResponse: true,
errorCode: 0,
responseData,
};
},
} as never,
};
const [elem] = await buildSendElems([{ type: 'face', faceId: 392 }], liveCtx);
expect(requests).toBe(1);
expect(elem.commonElem?.serviceType).toBe(37);
});
it('encodes each face id by its catalog classification', async () => {
sysFaceStore.load(CATALOG); // warm → ensureWarm no-ops, classify uses the catalog
const [superElem] = await buildSendElems([{ type: 'face', faceId: 392 }], ctx);
expect(superElem.commonElem?.serviceType).toBe(37);
const big = protobuf_decode<QFaceExtra>(superElem.commonElem!.pbElem!);
expect(big.qsid).toBe(392);
expect(big.packId).toBe('2');
expect(big.stickerId).toBe('38');
expect(superElem.commonElem?.businessType).toBe(3);
const [animated] = await buildSendElems([{ type: 'face', faceId: 424 }], ctx);
expect(animated.commonElem?.serviceType).toBe(37);
expect(protobuf_decode<QFaceExtra>(animated.commonElem!.pbElem!).stickerId).toBe('52');
const [smallElem] = await buildSendElems(
[{ type: 'face', faceId: 424, large: false }],
ctx,
);
expect(smallElem.commonElem?.serviceType).toBe(33);
const small = protobuf_decode<QSmallFaceExtra>(smallElem.commonElem!.pbElem!);
expect(small.faceId).toBe(424);
const [classicElem] = await buildSendElems([{ type: 'face', faceId: 14 }], ctx);
expect(classicElem.face?.index).toBe(14);
});
it('allows special animated faces to be sent small and normalizes newer business types', async () => {
sysFaceStore.load([{ packName: 'special', emojis: [entry('504', 4, 1, 90)] }]);
const [large] = await buildSendElems([{ type: 'face', faceId: 504, large: true }]);
expect(large.commonElem?.businessType).toBe(1);
expect(protobuf_decode<QFaceExtra>(large.commonElem!.pbElem!).stickerType).toBe(4);
const [small] = await buildSendElems([{ type: 'face', faceId: 504, large: false }]);
expect(small.commonElem?.serviceType).toBe(33);
expect(protobuf_decode<QSmallFaceExtra>(small.commonElem!.pbElem!).faceId).toBe(504);
});
it('sends uncatalogued ids without inventing animation metadata', async () => {
sysFaceStore.load(CATALOG);
const [element] = await buildSendElems([{ type: 'face', faceId: 504 }]);
expect(element.commonElem?.serviceType).toBe(33);
expect(protobuf_decode<QSmallFaceExtra>(element.commonElem!.pbElem!).faceId).toBe(504);
});
});
describe('system faces outside visible panels', () => {
it.each(['result', 'error'] as const)(
'ignores an older catalog’s lookup %s after refresh',
async (outcome) => {
const stale = deferred<SysFaceEntry | null>();
const started = deferred<void>();
const latest = entry('507', 1, 4, 108);
const fetchFace = vi
.fn()
.mockImplementationOnce(() => {
started.resolve();
return stale.promise;
})
.mockResolvedValue(latest);
const store = new SysFaceStore({ fetchCatalog: async () => CATALOG, fetchFace });
const first = store.resolveWire({} as never, 507);
await started.promise;
await store.refresh({} as never);
const second = store.resolveWire({} as never, 507);
if (outcome === 'result') {
stale.resolve(entry('507', 1, 4, 107));
} else {
stale.reject(new Error('outdated lookup failed'));
}
const expected = { kind: 'super', packId: '4', stickerId: '108', stickerType: 1 };
await expect(first).resolves.toEqual(expected);
await expect(second).resolves.toEqual(expected);
expect(fetchFace).toHaveBeenCalledTimes(2);
expect(store.lookup(507)).toEqual(latest);
},
);
it('uses an explicitly loaded catalog when an older single-face lookup completes', async () => {
const stale = deferred<SysFaceEntry | null>();
const started = deferred<void>();
const fetchFace = vi.fn(() => {
started.resolve();
return stale.promise;
});
const store = new SysFaceStore({ fetchCatalog: async () => CATALOG, fetchFace });
const result = store.resolveWire({} as never, 507);
await started.promise;
const latest = entry('507', 1, 4, 108);
store.load([{ packName: '最新', emojis: [latest] }]);
stale.resolve(entry('507', 1, 4, 107));
await expect(result).resolves.toMatchObject({ stickerId: '108' });
expect(store.lookup(507)).toEqual(latest);
});
it('resolves missing animation metadata once and shares concurrent lookups', async () => {
const fetchFace = vi.fn(async () => entry('507', 1, 4, 107));
const store = new SysFaceStore({ fetchCatalog: async () => CATALOG, fetchFace });
const [first, second] = await Promise.all([
store.resolveWire({} as never, 507),
store.resolveWire({} as never, 507),
]);
expect(first).toEqual({ kind: 'super', packId: '4', stickerId: '107', stickerType: 1 });
expect(second).toEqual(first);
expect(store.lookup(507)?.qSid).toBe('507');
await store.resolveWire({} as never, 507);
expect(fetchFace).toHaveBeenCalledOnce();
});
it('does not silently send a small face after a lookup failure and permits retry', async () => {
const fetchFace = vi
.fn()
.mockRejectedValueOnce(new Error('lookup failed'))
.mockResolvedValueOnce(entry('507', 1, 4, 107));
const store = new SysFaceStore({ fetchCatalog: async () => CATALOG, fetchFace });
await expect(store.resolveWire({} as never, 507)).rejects.toThrow('lookup failed');
await expect(store.resolveWire({} as never, 507)).resolves.toMatchObject({ kind: 'super' });
});
it('rejects mismatched or incomplete animation metadata', async () => {
for (const face of [entry('506', 1, 4, 107), entry('507', 1, 4, null)]) {
const store = new SysFaceStore({
fetchCatalog: async () => CATALOG,
fetchFace: async () => face,
});
await expect(store.resolveWire({} as never, 507)).rejects.toThrow();
expect(store.lookup(507)).toBeNull();
}
});
});