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,63 @@
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { typesForDirection, INPUT_SUGAR_SEGMENTS } from '@snowluma/protocol/element-manifest';
import { ELEMENT_CODECS } from '../src/event-converter/element-codecs';
// 对账测试(onebot 侧)—— 拿 element-manifest 核对「按 element.type 收敛」的两个
// onebot 方向。阶段 2 起这两向已合入 ELEMENT_CODECS 表,故直接用**表的键**核对,
// 比扫源码正则更硬:
// S 收·转 codec.toSegment MessageElement → OneBot 段
// P 发·解 codec.fromSegment OneBot 段 → MessageElement
// protocol 侧的 D/W 由 protocol/tests/element-manifest.test.ts 核对(包边界所限)。
//
// 架构护栏:谁在表里增删一个方向却没同步 element-manifest,测试当场报红。
function codecTypesWith(dir: 'toSegment' | 'fromSegment'): string[] {
return Object.entries(ELEMENT_CODECS)
.filter(([, codec]) => typeof codec[dir] === 'function')
.map(([type]) => type)
.sort();
}
const sorted = (s: Iterable<string>): string[] => [...s].sort();
describe('element-manifest 对账(onebot 侧:S 收·转 / P 发·解)', () => {
it('S:ELEMENT_CODECS 里有 toSegment 的类型 == 清单声明的 S=yes', () => {
expect(codecTypesWith('toSegment')).toEqual(sorted(typesForDirection('S')));
});
it('P:ELEMENT_CODECS 里有 fromSegment 的类型 == 清单声明的 P=yes', () => {
expect(codecTypesWith('fromSegment')).toEqual(sorted(typesForDirection('P')));
});
it('OneBot 输入词仍由 message-parser 前置处理或明确拒绝,未被静默丢弃', () => {
// 输入词(骰子/分享/node/anonymous/…)不进 codec 表,留在 parser 前置处理。
// 用源码扫描锁住它们确实存在,防止将来重构时被悄悄删掉。
const abs = fileURLToPath(new URL('../src/message-parser.ts', import.meta.url));
const src = readFileSync(abs, 'utf8')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\/\/.*$/gm, '');
const cases = new Set<string>();
for (const m of src.matchAll(/case\s*'([A-Za-z0-9_]+)'\s*:/g)) {
cases.add(m[1]!);
}
const sugarPresent = [...cases].filter((t) => INPUT_SUGAR_SEGMENTS.has(t)).sort();
expect(sugarPresent).toEqual(sorted(INPUT_SUGAR_SEGMENTS));
// 且 message-parser 不再手写任何真实元素类型的 case(已全部下沉到 codec 表)。
const realLeftInParser = [...cases].filter((t) => !INPUT_SUGAR_SEGMENTS.has(t));
expect(realLeftInParser).toEqual([]);
});
it('poke 的 P/S 字段都使用 subType,不再漂移到 faceId', async () => {
const parsed = await ELEMENT_CODECS.poke!.fromSegment!({ type: 7 });
expect(parsed).toEqual({ type: 'poke', subType: 7 });
expect(parsed).not.toHaveProperty('faceId');
const segment = await ELEMENT_CODECS.poke!.toSegment!(parsed!, {} as never);
expect(segment).toEqual({ type: 'poke', data: { type: 7 } });
});
});