Files
shanxia2.0/miniprogram/scripts/test-wechat-binding-flow.mjs
T

115 lines
5.5 KiB
JavaScript

import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
const deferred = () => { let resolve; const promise = new Promise(r => { resolve = r }); return { promise, resolve } }
function setup(overrides = {}) {
const source = readFileSync(new URL('../utils/wechatBindingFlow.js', import.meta.url), 'utf8').replace(/^export /gm, '')
const { createWechatBindingFlow, initialBindingState } = new Function(`${source}; return { createWechatBindingFlow, initialBindingState }`)()
let token = 'user-a', active = true
const state = initialBindingState(), calls = []
const api = {
status: async () => ({ enabled: true, bound: false, subscribed: false }),
start: async () => ({ sessionId: 'session-12345678901234567890', authorizeUrl: 'https://open.weixin.qq.com/connect/oauth2/authorize?state=s' }),
session: async () => ({ status: 'scanned', subscribed: true, verificationCode: '123456' }),
confirm: async () => { calls.push('confirm'); return { enabled: true, bound: true, subscribed: true } },
...overrides
}
const flow = createWechatBindingFlow({ state, api, getSessionKey: () => token, isActive: () => active,
openAuthorization: async (url) => calls.push(url), askConfirmation: async () => { calls.push('ask'); return true } })
return { flow, state, calls, setToken: v => { token = v }, hide: () => { active = false; flow.cancel() }, show: () => { active = true } }
}
test('explicit mobile start automatically confirms after authorization returns', async () => {
const p = setup()
await p.flow.refresh()
assert.deepEqual(p.calls, [])
await p.flow.start()
assert.equal(p.state.bound, false)
assert.equal(p.state.sessionStatus, 'waiting')
p.hide(); p.show()
await p.flow.refresh()
assert.equal(p.state.bound, true)
assert.equal(p.state.sessionId, '')
assert.equal(p.calls.includes('ask'), false)
assert.equal(p.calls.filter(v => v === 'confirm').length, 1)
})
test('unsubscribed or waiting sessions cannot be confirmed', async () => {
for (const result of [{ status: 'waiting', subscribed: false }, { status: 'scanned', subscribed: false, verificationCode: '123456' }]) {
const p = setup({ session: async () => result })
await p.flow.refresh(); await p.flow.start(); await p.flow.refresh()
assert.ok(!p.calls.includes('confirm'))
assert.equal(p.state.bound, false)
}
})
test('failed confirmation does not claim bound and can retry', async () => {
let fail = true
const p = setup({ confirm: async () => { if (fail) throw new Error('该微信已绑定其他账号'); return { enabled: true, bound: true, subscribed: true } } })
await p.flow.refresh(); await p.flow.start(); await p.flow.refresh()
assert.equal(p.state.bound, false)
assert.match(p.state.error, /其他账号/)
fail = false
await p.flow.refresh()
assert.equal(p.state.bound, true)
})
test('duplicate start clicks create only one binding session', async () => {
const pending = deferred(); let count = 0
const p = setup({ start: () => { count++; return pending.promise } })
await p.flow.refresh()
const first = p.flow.start(); await p.flow.start()
assert.equal(count, 1)
pending.resolve({ sessionId: 'session-12345678901234567890', authorizeUrl: 'url' }); await first
})
test('late authorization start after account switch or hide cannot open webview', async () => {
for (const action of ['account', 'hide']) {
const pending = deferred(), p = setup({ start: () => pending.promise })
await p.flow.refresh(); const start = p.flow.start()
if (action === 'account') p.setToken('user-b'); else p.hide()
pending.resolve({ sessionId: 'session-12345678901234567890', authorizeUrl: 'url' }); await start
assert.deepEqual(p.calls, [])
}
})
test('account switch clears old authorization and late confirmation cannot apply', async () => {
const pending = deferred(), p = setup({ confirm: () => pending.promise })
await p.flow.refresh(); await p.flow.start()
const confirmation = p.flow.refresh(); await new Promise(resolve => setImmediate(resolve))
p.setToken('user-b'); await p.flow.refresh()
pending.resolve({ enabled: true, bound: true, subscribed: true }); await confirmation
assert.equal(p.state.bound, false)
assert.equal(p.state.sessionId, '')
})
test('expired sessions allow restart and disabled feature creates no session', async () => {
const p = setup({ session: async () => ({ status: 'expired', subscribed: false }) })
await p.flow.refresh(); await p.flow.start(); await p.flow.refresh()
assert.equal(p.state.sessionId, '')
assert.match(p.state.error, /过期/)
const disabled = setup({ status: async () => ({ enabled: false, bound: false, subscribed: false }) })
await disabled.flow.refresh(); await disabled.flow.start()
assert.deepEqual(disabled.calls, [])
})
test('ordinary page entry never starts or confirms a binding', async () => {
const p = setup()
await p.flow.refresh(); await p.flow.refresh()
assert.deepEqual(p.calls, [])
})
test('switching account or hiding while reading candidate prevents auto confirmation', async () => {
for (const action of ['account', 'hide']) {
const pending = deferred(), p = setup({ session: () => pending.promise })
await p.flow.refresh(); await p.flow.start()
const reading = p.flow.refresh(); await new Promise(resolve => setImmediate(resolve))
if (action === 'account') p.setToken('user-b'); else p.hide()
pending.resolve({ status: 'scanned', subscribed: true, verificationCode: '123456' })
await reading
assert.equal(p.calls.includes('confirm'), false)
}
})