fix(微信订阅): 用微信真实关键词修正歌曲排期模板(thing1/phrase2/time3/character_string4),并修掉字段认领顺序导致的校准失效
This commit is contained in:
@@ -84,17 +84,23 @@ test('空白与换行会被压成一行(微信不接受换行)', () => {
|
||||
})
|
||||
|
||||
test('SONG_SELECTED 的内置字段映射:每个字段名后缀都要跟发出去的值类型匹配', () => {
|
||||
// 字段映射的唯一来源是 server/utils/wechatSubscribeTemplates.ts(那里有更完整的测试)
|
||||
/*
|
||||
* 字段映射的唯一来源是 server/utils/wechatSubscribeTemplates.ts(那里有更完整的测试)。
|
||||
* 这里的重点不是"是不是这几个编号",而是**每个键的后缀(类型)和值匹配**:
|
||||
* thing1 / phrase2 / time3 / character_string4
|
||||
* 真实模板内容(微信 /wxaapi/newtmpl/gettemplate 读出来):
|
||||
* 歌曲名称:{{thing1.DATA}} 排期状态:{{phrase2.DATA}}
|
||||
* 播放时间:{{time3.DATA}} 排期序号:{{character_string4.DATA}}
|
||||
* ⚠️ 这张模板**没有日期字段**,日期含在 time3 的 'YYYY-MM-DD HH:mm' 里。
|
||||
*/
|
||||
const fields = {
|
||||
thing2: 'songTitle',
|
||||
date4: 'playDate',
|
||||
time1: 'playTime',
|
||||
character_string3: 'sequence',
|
||||
phrase5: 'status'
|
||||
thing1: 'songTitle',
|
||||
phrase2: 'status',
|
||||
time3: 'playTime',
|
||||
character_string4: 'sequence'
|
||||
}
|
||||
const vars = {
|
||||
songTitle: '晴天',
|
||||
playDate: '2026-02-14',
|
||||
playTime: '2026-02-14 12:30',
|
||||
sequence: '1',
|
||||
status: '已排期'
|
||||
@@ -102,11 +108,10 @@ test('SONG_SELECTED 的内置字段映射:每个字段名后缀都要跟发出
|
||||
const out = {}
|
||||
for (const key of Object.keys(fields)) out[key] = clampField(key, vars[fields[key]])
|
||||
|
||||
assert.equal(out.thing2, '晴天')
|
||||
assert.equal(out.date4, '2026-02-14')
|
||||
assert.match(out.time1, /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/)
|
||||
assert.equal(out.character_string3, '1')
|
||||
assert.equal(out.phrase5, '已排期') // 3 个汉字本来就合规,不补全角空格
|
||||
assert.equal(out.thing1, '晴天')
|
||||
assert.match(out.time3, /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/)
|
||||
assert.equal(out.character_string4, '1')
|
||||
assert.equal(out.phrase2, '已排期') // 3 个汉字本来就合规,不补全角空格
|
||||
// 不能有任何字段是空值:空值同样会被微信拒
|
||||
for (const key of Object.keys(out)) assert.ok(out[key].length > 0, key + ' 不能为空')
|
||||
})
|
||||
@@ -160,9 +165,12 @@ test('内置模板与线上代码一致(直接读源码,防止映射被改
|
||||
|
||||
const selected = block('SONG_SELECTED')
|
||||
assert.match(selected, /oLTAefO92xYYlGRYmcsm5AfUUlUI5IKFfXhIVNrItSk/)
|
||||
for (const f of ['thing2', 'date4', 'time1', 'character_string3', 'phrase5']) {
|
||||
// 这四个编号是微信侧真实分配的(见上方模板原文),改了就必须同步改这里
|
||||
for (const f of ['thing1', 'phrase2', 'time3', 'character_string4']) {
|
||||
assert.match(selected, new RegExp(f + ":\\s*'"), f + ' 的映射丢了')
|
||||
}
|
||||
// 这张模板没有日期字段,配置里不该出现 date 型键
|
||||
assert.doesNotMatch(selected, /date\d+:/, 'SONG_SELECTED 的模板里没有日期关键词')
|
||||
})
|
||||
|
||||
test('beijingNow 是合法的 time 型值,且与北京时间一致', () => {
|
||||
|
||||
@@ -123,6 +123,66 @@ test('resolveFieldsFromKeywords:拿不到关键词表时一律沿用配置', (
|
||||
assert.deepEqual(r.fields, configured)
|
||||
})
|
||||
|
||||
/**
|
||||
* 回归:线上真实踩过的坑。
|
||||
*
|
||||
* 旧配置是「按常见命名猜的」thing2/date4/time1/character_string3/phrase5,五个编号全错,
|
||||
* 而且模板里**根本没有日期关键词**。当时按变量顺序贪心认领,playDate(首选 date,模板里
|
||||
* 没有 date)先把 character_string4 抢走,轮到 sequence 就没位置了,整张模板退回旧配置,
|
||||
* 发出去就是 `47003 data.thing1.value is empty`。
|
||||
*/
|
||||
const REAL_SONG_SELECTED_CONTENT =
|
||||
'歌曲名称:{{thing1.DATA}}\n排期状态:{{phrase2.DATA}}\n播放时间:{{time3.DATA}}\n排期序号:{{character_string4.DATA}}\n'
|
||||
|
||||
test('回归:真实排期模板 + 猜错的旧配置 → 校准成正确的四个编号,并丢掉模板里没有的 playDate', () => {
|
||||
const wrongConfigured = {
|
||||
thing2: 'songTitle',
|
||||
date4: 'playDate',
|
||||
time1: 'playTime',
|
||||
character_string3: 'sequence',
|
||||
phrase5: 'status'
|
||||
}
|
||||
const r = resolveFieldsFromKeywords(
|
||||
parseTemplateContent(REAL_SONG_SELECTED_CONTENT),
|
||||
wrongConfigured
|
||||
)
|
||||
assert.equal(r.source, 'wechat-api')
|
||||
assert.deepEqual(r.fields, {
|
||||
thing1: 'songTitle',
|
||||
phrase2: 'status',
|
||||
time3: 'playTime',
|
||||
character_string4: 'sequence'
|
||||
})
|
||||
// 模板里没有日期关键词 → playDate 被忽略(发了微信也不认)
|
||||
assert.deepEqual(r.unresolvedVariables, ['playDate'])
|
||||
assert.deepEqual(r.unclaimedCodes, [], '四个关键词必须全部填上,否则微信会报 value is empty')
|
||||
assert.match(r.reason, /已忽略/)
|
||||
})
|
||||
|
||||
test('回归:真实排期模板 + 已修正的内置配置 → 一致,不做任何改动', () => {
|
||||
const good = {
|
||||
thing1: 'songTitle',
|
||||
phrase2: 'status',
|
||||
time3: 'playTime',
|
||||
character_string4: 'sequence'
|
||||
}
|
||||
const r = resolveFieldsFromKeywords(parseTemplateContent(REAL_SONG_SELECTED_CONTENT), good)
|
||||
assert.equal(r.source, 'wechat-api')
|
||||
assert.deepEqual(r.fields, good)
|
||||
assert.deepEqual(r.invalidConfiguredCodes, [])
|
||||
assert.deepEqual(r.unclaimedCodes, [])
|
||||
assert.equal(r.reason, '配置与微信真实模板一致')
|
||||
})
|
||||
|
||||
test('unclaimedCodes:模板里有、配置里没有的关键词要被点名(这是 value is empty 的来源)', () => {
|
||||
const r = resolveFieldsFromKeywords(parseTemplateContent(REAL_SONG_SELECTED_CONTENT), {
|
||||
thing1: 'songTitle'
|
||||
})
|
||||
assert.deepEqual(r.unclaimedCodes.sort(), ['character_string4', 'phrase2', 'time3'])
|
||||
// 填不满模板时不动配置
|
||||
assert.equal(r.source, 'config')
|
||||
})
|
||||
|
||||
test('resolveFieldsFromKeywords:变量类型未知时不做自动映射', () => {
|
||||
const r = resolveFieldsFromKeywords(parseTemplateContent(SONG_SELECTED_CONTENT), {
|
||||
thing1: 'whateverCustomVar'
|
||||
|
||||
@@ -30,12 +30,18 @@ test('内置模板:两个模板的 ID 与字段映射和公众平台一致', (
|
||||
|
||||
const selected = BUILTIN_TEMPLATES.SONG_SELECTED
|
||||
assert.equal(selected.id, 'oLTAefO92xYYlGRYmcsm5AfUUlUI5IKFfXhIVNrItSk')
|
||||
/*
|
||||
* 这四个键是微信侧真实分配的编号(模板原文:
|
||||
* 歌曲名称:{{thing1.DATA}} 排期状态:{{phrase2.DATA}}
|
||||
* 播放时间:{{time3.DATA}} 排期序号:{{character_string4.DATA}}
|
||||
* )。这张模板没有日期关键词 —— 曾经在这里写过一个不存在的 date4,
|
||||
* 微信就回 `47003 data.thing1.value is empty`。
|
||||
*/
|
||||
assert.deepEqual(selected.fields, {
|
||||
thing2: 'songTitle',
|
||||
date4: 'playDate',
|
||||
time1: 'playTime',
|
||||
character_string3: 'sequence',
|
||||
phrase5: 'status'
|
||||
thing1: 'songTitle',
|
||||
phrase2: 'status',
|
||||
time3: 'playTime',
|
||||
character_string4: 'sequence'
|
||||
})
|
||||
|
||||
// 每个字段名都要带类型后缀(thing2/date4/…),否则 clampField 认不出类型、不会裁剪
|
||||
@@ -139,17 +145,16 @@ test('findEmptyFields:变量没给或全是空白 → 点名到字段和变量
|
||||
findEmptyFields(conf, {})
|
||||
.map((f) => f.field)
|
||||
.sort(),
|
||||
['character_string3', 'date4', 'phrase5', 'thing2', 'time1']
|
||||
['character_string4', 'phrase2', 'thing1', 'time3']
|
||||
)
|
||||
|
||||
const partial = findEmptyFields(conf, {
|
||||
songTitle: '晴天',
|
||||
playDate: ' ',
|
||||
playTime: '2026-02-14 12:30',
|
||||
playTime: ' ',
|
||||
sequence: '1',
|
||||
status: '已排期'
|
||||
})
|
||||
assert.deepEqual(partial, [{ field: 'date4', variable: 'playDate' }])
|
||||
assert.deepEqual(partial, [{ field: 'time3', variable: 'playTime' }])
|
||||
})
|
||||
|
||||
test('findEmptyDataFields:拼完 data 再兜一层空值检查', () => {
|
||||
@@ -173,10 +178,10 @@ test('buildTemplateData:按映射取值,并交给 clamp 处理(time 有兜
|
||||
},
|
||||
clampField
|
||||
)
|
||||
assert.deepEqual(Object.keys(data), ['thing2', 'date4', 'time1', 'character_string3', 'phrase5'])
|
||||
assert.equal(data.thing2.value, '晴天')
|
||||
assert.equal(data.character_string3.value, '3')
|
||||
assert.equal(data.phrase5.value, '已排期')
|
||||
assert.deepEqual(Object.keys(data), ['thing1', 'phrase2', 'time3', 'character_string4'])
|
||||
assert.equal(data.thing1.value, '晴天')
|
||||
assert.equal(data.character_string4.value, '3')
|
||||
assert.equal(data.phrase2.value, '已排期')
|
||||
assert.deepEqual(findEmptyDataFields(data), [], '有完整变量时不该出现空字段')
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user