Files
dsh_shenxian/poc/business-plugins-im/minimal-callbacks/self-check.mjs
T
admin 5ec99ce227
build / build-and-scan (push) Canceled after 0s
chore: 文档库全量重新入库(2026-10-20)
2026-10-20 15:05:55 +08:00

127 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 自检(**一键跑**):`node self-check.mjs`
*
* 它证明本示例的五件事 —— 都不接真平台,靠 `mock-platform.mjs` 跑完整协议:
* ① 登记成功,且送上平台的 manifest **不含函数**(跨进程只传数据);
* ② 声明里 `onTimeout: 'deny'` 在位(回合制的命门);
* ③ **同步发言规则**真的生效:非当前回合者 ⇒ 回 `{ok:false}`(**且原因可读**);
* ④ **异步事件订阅**真的跑到:`event.deliver` ⇒ 你的 `onEvent` 执行 ⇒ 出了一帧;
* ⑤ **未接线时行为不变**:缺 `baseUrl` / `token` ⇒ **不抛**、不启长轮询(插件其余功能不受影响)。
*
* 退出码:0 = 全绿;1 = 有红。⛔ 红就是红 —— 别把"没报错"当通过。
*/
import { decideTurn, manifest, start, ROOM_TYPE, PLUGIN_ID } from './index.mjs'
import { startMockPlatform } from './mock-platform.mjs'
let failed = 0
let passed = 0
function check(name, condition, extra = '') {
if (condition) {
passed += 1
console.log(` ✅ ${name}`)
} else {
failed += 1
console.log(` ❌ ${name}${extra === '' ? '' : ` —— ${extra}`}`)
}
}
const ROOM_ID = 'room_demo_1'
const eventOf = (authorId) => ({
name: 'message.created',
roomId: ROOM_ID,
roomType: ROOM_TYPE,
at: 1_758_000_000_000,
data: { messageId: 'm1', seq: 1, authorId, authorKind: 'user', via: 'client', visibility: 'room', payload: { kind: 'text', text: 'hi' } },
})
async function main() {
console.log('── ① 纯函数判定(回合制语义,可单测)')
check('未开局(turnOf 空)⇒ 放行', decideTurn({ config: { turnOf: '' }, authorId: 'u1' }).ok === true)
check('轮到本人 ⇒ 放行', decideTurn({ config: { turnOf: 'u2' }, authorId: 'u2' }).ok === true)
const rejected = decideTurn({ config: { turnOf: 'u1' }, authorId: 'u2' })
check('没轮到 ⇒ 拒,且原因可读', rejected.ok === false && typeof rejected.detail === 'string' && rejected.detail.length > 0, JSON.stringify(rejected))
check('bot / agent 代答不参与回合(⛔ 否则会自卡)', decideTurn({ config: { turnOf: 'u1' }, authorId: 'bot_x', authorKind: 'bot' }).ok === true)
console.log('\n── ② 声明面(不接平台也能看)')
check(`清单里 onTimeout = 'deny'`, manifest.speakRules[0].onTimeout === 'deny')
const platform = await startMockPlatform()
let host
try {
console.log('\n── ③ 接"平台",跑真实协议(登记 → 长轮询 → 回投)')
const logLines = []
const boot = await start({
baseUrl: platform.url,
token: platform.token,
onLog: (e) => logLines.push(e),
})
host = boot.host
check('登记成功(reg.ok === true)', boot.reg.ok === true, JSON.stringify(boot.reg))
check('拼上了实例凭据', platform.lastToken === platform.token)
// 送上平台的 manifest:有声明、但**没有函数**。
const sent = platform.registered?.manifest ?? {}
check('manifest 送上去了且声明在', Array.isArray(sent.speakRules) && sent.speakRules.length === 1)
check('🔴 manifest 里没有函数(check / onEvent 已被 SDK 剔除)', sent.speakRules[0].check === undefined && sent.events[0].onEvent === undefined, JSON.stringify(Object.keys(sent.speakRules[0])))
check('送上平台的 onTimeout 也是 deny', sent.speakRules[0].onTimeout === 'deny')
check('长轮询已启动(callbacks.started)', host.callbacks.started === true)
// ⑶ 同步发言规则:没轮到的发言必须被拒。
platform.queueSpeakCheck({
callId: 'c1',
input: { roomId: ROOM_ID, roomType: ROOM_TYPE, authorId: 'u2', authorKind: 'user', config: { turnOf: 'u1' }, envelope: { via: 'client' } },
})
const r1 = await platform.waitResult('c1')
check('⚠️ 非当前回合者 ⇒ 平台收到 ok:false(拒)', r1 !== null && r1.ok === false, JSON.stringify(r1))
check('拒绝原因可读(不是空 detail)', r1 !== null && typeof r1.detail === 'string' && r1.detail.includes('还没轮到'), JSON.stringify(r1))
// 轮到本人 ⇒ 必须放行(否则规则把正常发言也卡死 = 净变差)。
platform.queueSpeakCheck({
callId: 'c2',
input: { roomId: ROOM_ID, roomType: ROOM_TYPE, authorId: 'u1', authorKind: 'user', config: { turnOf: 'u1' }, envelope: { via: 'client' } },
})
const r2 = await platform.waitResult('c2')
check('✅ 轮到本人 ⇒ 放行(ok:true)', r2 !== null && r2.ok === true, JSON.stringify(r2))
// ⑷ 异步事件订阅:事件送达 ⇒ onEvent 在本进程执行 ⇒ 出一帧。
platform.queueEvent({ callId: 'e1', event: eventOf('u1') })
const r3 = await platform.waitResult('e1')
check('事件回投成功(ok:true)', r3 !== null && r3.ok === true, JSON.stringify(r3))
const deadline = Date.now() + 2000
while (platform.frames.length === 0 && Date.now() < deadline) await new Promise((r) => setTimeout(r, 10))
check('⚠️ onEvent 真的执行了(平台收到 1 帧)', platform.frames.length === 1, `frames=${platform.frames.length}`)
check('帧内容 = 事件里的作者', platform.frames[0]?.data?.authorId === 'u1', JSON.stringify(platform.frames[0]))
check('出向带了 outboundId', platform.frames[0]?.outboundId === 'minimal_out')
check('读数可观测(items / settled > 0)', host.callbacks.items >= 3 && host.callbacks.settled >= 3, JSON.stringify(host.callbacks))
check('整轮无 onLog 报错', logLines.length === 0, JSON.stringify(logLines))
await host.close()
check('close() 后长轮询已停', host.callbacks.stopped === true)
host = undefined
} finally {
await platform.close()
}
console.log('\n── ④ 未接线(缺 baseUrl / token)⇒ 行为必须不变')
const offline = await start({ baseUrl: '', token: '', onLog: () => {} })
check('不抛(start 正常返回)', offline.host !== undefined)
check('不启长轮询(callbacks.started === false)', offline.host.callbacks.started === false)
check('登记失败回具名结论(not-configured)', offline.reg.ok === false && offline.reg.reason === 'not-configured', JSON.stringify(offline.reg))
check('出向回具名结论而非抛错', (await offline.host.frame({ roomId: ROOM_ID, channel: 'x', data: {} })).reason === 'not-configured')
await offline.host.close()
console.log(`\n══════ 结果:${passed} 例通过 / ${failed} 例失败 ══════`)
console.log(`(插件身份 ${PLUGIN_ID} · 房间类型 ${ROOM_TYPE})`)
process.exit(failed === 0 ? 0 : 1)
}
main().catch((err) => {
console.error('自检自身崩了:', err)
process.exit(1)
})