/** * `server/utils/wechatSubscribeRemote.ts` 的单测:把微信模板原文解析成关键词表, * 再按类型/中文标签把我们的变量名对应到微信**真实分配**的关键词编号。 * * 背景:公众平台创建模板时,关键词编号(thing1 / date2 …)是微信自动打的,猜不出来。 * 猜错的表现就是整条消息 47003,而报错点名的字段往往不是真凶 —— 所以解析和映射必须能单独测。 * * 运行:node --test test/wechatSubscribeRemote.test.mjs */ import { test } from 'node:test' import assert from 'node:assert/strict' import { keywordTypeOf, parseTemplateContent, resolveFieldsFromKeywords } from '../server/utils/wechatSubscribeRemote.ts' /** 微信「获取已有模板列表」返回里 content 的真实形状 */ const SONG_SELECTED_CONTENT = '歌曲名称:{{thing1.DATA}}\n播放日期:{{date2.DATA}}\n播放时间:{{time3.DATA}}\n序号:{{character_string4.DATA}}\n状态:{{phrase5.DATA}}\n' const SONG_REJECTED_CONTENT = '审核内容:{{thing6.DATA}}\n驳回原因:{{thing4.DATA}}\n' test('keywordTypeOf:去掉尾部数字得到关键词类型,认不出的记为 unknown', () => { assert.equal(keywordTypeOf('thing1'), 'thing') assert.equal(keywordTypeOf('character_string3'), 'character_string') assert.equal(keywordTypeOf('phone_number2'), 'phone_number') assert.equal(keywordTypeOf('foo2'), 'unknown') assert.equal(keywordTypeOf(''), 'unknown') }) test('parseTemplateContent:按出现顺序解析出 code / type / 中文标签', () => { const kws = parseTemplateContent(SONG_SELECTED_CONTENT) assert.deepEqual( kws.map((k) => k.code), ['thing1', 'date2', 'time3', 'character_string4', 'phrase5'] ) assert.deepEqual( kws.map((k) => k.type), ['thing', 'date', 'time', 'character_string', 'phrase'] ) assert.deepEqual( kws.map((k) => k.label), ['歌曲名称', '播放日期', '播放时间', '序号', '状态'] ) assert.deepEqual( kws.map((k) => k.index), [0, 1, 2, 3, 4] ) }) test('parseTemplateContent:没有占位符就返回空表,不会抛', () => { assert.deepEqual(parseTemplateContent(''), []) assert.deepEqual(parseTemplateContent('随便一段没有占位符的文字'), []) assert.deepEqual(parseTemplateContent(undefined), []) }) test('resolveFieldsFromKeywords:配置的编号和微信一致时保持 config 以外的等价结果', () => { const configured = { thing1: 'songTitle', date2: 'playDate', time3: 'playTime', character_string4: 'sequence', phrase5: 'status' } const r = resolveFieldsFromKeywords(parseTemplateContent(SONG_SELECTED_CONTENT), configured) assert.equal(r.source, 'wechat-api') assert.deepEqual(r.fields, configured) assert.deepEqual(r.invalidConfiguredCodes, []) assert.match(r.reason, /一致/) }) test('resolveFieldsFromKeywords:配置编号猜错了也能按类型/标签纠正回真实编号', () => { // 我们原来猜的是 thing2/date4/time1/character_string3/phrase5,实际是 thing1/date2/time3/character_string4/phrase5 const configured = { thing2: 'songTitle', date4: 'playDate', time1: 'playTime', character_string3: 'sequence', phrase5: 'status' } const r = resolveFieldsFromKeywords(parseTemplateContent(SONG_SELECTED_CONTENT), configured) assert.equal(r.source, 'wechat-api') assert.deepEqual(r.fields, { thing1: 'songTitle', date2: 'playDate', time3: 'playTime', character_string4: 'sequence', phrase5: 'status' }) // 配置里 4 个编号在微信模板里根本不存在 —— 这就是 47003 的直接证据 assert.deepEqual(r.invalidConfiguredCodes.sort(), [ 'character_string3', 'date4', 'thing2', 'time1' ]) assert.match(r.reason, /校准/) }) test('resolveFieldsFromKeywords:两个同类型关键词必须靠中文标签区分(驳回模板)', () => { const configured = { thing6: 'songTitle', thing4: 'reason' } const r = resolveFieldsFromKeywords(parseTemplateContent(SONG_REJECTED_CONTENT), configured) assert.equal(r.source, 'wechat-api') assert.deepEqual(r.fields, { thing6: 'songTitle', thing4: 'reason' }) assert.deepEqual(r.invalidConfiguredCodes, []) }) test('resolveFieldsFromKeywords:两个 thing 但标签认不出来时,不冒险互换,沿用配置', () => { const content = '{{thing1.DATA}}\n{{thing2.DATA}}\n' const configured = { thing6: 'songTitle', thing4: 'reason' } const r = resolveFieldsFromKeywords(parseTemplateContent(content), configured) assert.equal(r.source, 'config') assert.deepEqual(r.fields, configured) assert.match(r.reason, /标签不足以区分/) }) test('resolveFieldsFromKeywords:拿不到关键词表时一律沿用配置', () => { const configured = { thing2: 'songTitle' } const r = resolveFieldsFromKeywords([], configured) assert.equal(r.source, 'config') assert.deepEqual(r.fields, configured) }) test('resolveFieldsFromKeywords:变量类型未知时不做自动映射', () => { const r = resolveFieldsFromKeywords(parseTemplateContent(SONG_SELECTED_CONTENT), { thing1: 'whateverCustomVar' }) assert.equal(r.source, 'config') assert.match(r.reason, /类型未知/) })