const test = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const vm = require('node:vm') const ts = require('typescript') function load(file, deps = {}) { const exports = {} vm.runInNewContext(ts.transpileModule(fs.readFileSync(path.join(__dirname, '..', file), 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2019 } }).outputText, { exports, require: id => deps[id], setTimeout, clearTimeout }) return exports } const ritual = load('src/utils/incenseRitual.ts') const mock = load('src/utils/tiangongMock.ts', { './incenseRitual': ritual }) const { createTiangongStore } = load('src/utils/tiangongStore.ts', { './tiangongMock': mock, './incenseRitual': ritual }) const defer = () => { let resolve, reject; const promise = new Promise((ok, fail) => { resolve = ok; reject = fail }); return { promise, resolve, reject } } function harness() { let key = 'account-a', time = 1000, rows = [{ id: 81, name: '财神', holdingId: 726, active: true, incenseCount: 7, days: 3 }] let loader = async () => rows, offer = async () => ({ id: 99 }) const calls = [] const store = createTiangongStore({ key: () => key, now: () => time, resolve: name => name === '财神' ? 'wealth' : undefined, load: () => loader(), offer: (...args) => { calls.push(args); return offer(...args) }, dedicate: async (...args) => { calls.push(args); return { id: 100 } } }) return { store, calls, key: value => { key = value }, time: value => { time = value }, rows: value => { rows = value }, load: value => { loader = value }, offer: value => { offer = value } } } test('真实状态不预置财神,按后端持有ID提交,后端确认后才计数', async () => { const h = harness() assert.equal(h.store.snapshot().deities.wealth.unlocked, false) await h.store.refresh() const response = defer(); h.offer(() => response.promise) const request = h.store.incense('wealth') assert.equal(h.store.snapshot().deities.wealth.incenseCount, 7) assert.equal(h.store.snapshot().deities.wealth.lit, false) assert.equal(h.calls[0][0], 726); assert.equal(h.calls[0][1], 2) response.resolve({ id: 10 }); await request assert.equal(h.store.snapshot().deities.wealth.incenseCount, 8) assert.equal(h.store.snapshot().deities.wealth.incenseUntil, 16000) }) test('失败无计数无自动重试;晚到查询不覆盖已确认上香', async () => { const h = harness(); await h.store.refresh() h.offer(async () => { throw new Error('网络失败') }) await assert.rejects(h.store.incense('wealth'), /网络失败/) assert.equal(h.calls.length, 1); assert.equal(h.store.snapshot().deities.wealth.incenseCount, 7) const read = defer(); h.load(() => read.promise) const refreshing = h.store.refresh(true) h.offer(async () => ({ id: 1 })); await h.store.incense('wealth') read.resolve([{ name: '财神', holdingId: 726, active: true, incenseCount: 7 }]); await refreshing assert.equal(h.store.snapshot().deities.wealth.incenseCount, 8) }) test('乱序并发响应逐次累计,不让旧点击缩短最后截止时间', async () => { const h = harness(); await h.store.refresh() const a = defer(), b = defer(); let count = 0 h.offer(() => (++count === 1 ? a : b).promise) const first = h.store.incense('wealth'); h.time(4000); const second = h.store.incense('wealth') b.resolve({ id: 2 }); assert.equal((await second).startsRound, true) a.resolve({ id: 1 }); assert.equal((await first).startsRound, false) assert.equal(h.store.snapshot().deities.wealth.incenseCount, 9) assert.equal(h.store.snapshot().deities.wealth.incenseUntil, 19000) h.time(19001); assert.equal(h.store.snapshot().deities.wealth.lit, false) }) test('写入在途不采纳可能已包含该流水的GET,避免回包后再次加一', async () => { const h = harness(); await h.store.refresh() const response = defer(), read = defer() h.load(() => read.promise) const refreshing = h.store.refresh(true) h.offer(() => response.promise); const sending = h.store.incense('wealth') read.resolve([{ name: '财神', holdingId: 726, active: true, incenseCount: 8 }]); await refreshing assert.equal(h.store.snapshot().deities.wealth.incenseCount, 7) await h.store.refresh(true) response.resolve({ id: 2 }); await sending assert.equal(h.store.snapshot().deities.wealth.incenseCount, 8) }) test('切换账号丢弃旧响应,不以Mock兜底;未绑定不可提交', async () => { const h = harness(); await h.store.refresh() const response = defer(); h.offer(() => response.promise) const sending = h.store.incense('wealth') h.key('account-b'); assert.equal(h.store.snapshot().deities.wealth.unlocked, false) response.resolve({ id: 10 }); await assert.rejects(sending, /登录状态/) h.rows([]); await h.store.refresh() await assert.rejects(h.store.incense('wealth'), /绑定/) h.load(async () => { throw new Error('未部署') }); await assert.rejects(h.store.refresh(true), /未部署/) assert.equal(h.store.snapshot().deities.wealth.unlocked, false) }) test('祈愿和任意姓名回向去空白提交,不携带功德或注册收件人ID', async () => { const h = harness(); await h.store.refresh() await h.store.wish('wealth', ' 愿顺遂 ') await h.store.dedicate('wealth', ' 家人 ', ' 平安 ') assert.deepEqual(h.calls, [[726, 3, '愿顺遂'], [726, '家人', '平安']]) await assert.rejects(h.store.wish('wealth', ' '), /愿望/) await assert.rejects(h.store.dedicate('wealth', '', '祝愿'), /回向对象/) assert.equal(h.calls.length, 2) })