Files
dsh-context/tests/client/balance.spec.ts
T
bowenliang123 87a2893190 refactor(comments): clean up the test-suite comments to the guideline
Apply the AGENTS.md comments guideline to every .ts under tests/: remove
what the code already expresses, keep only what it cannot.

Removed: spec-header coverage inventories the describe/test tree already
names; comments restating the next line's assertion or its message; helper
JSDoc whose body already says it; within-file duplicates; five stale
claims (trendChart's "six aggregated turns" against agg.length === 8,
agentGraph's "six categories" against CATS' seven, fold-timing's "cheapest
call LAST" against a loop that feeds it first, fold-surface's unreachable
plain-JSON note, contextView's "two turns" for a one-turn fixture).

Kept and condensed: the projection-freeze class, strict wire/state-schema
hazards, retention and pricing windows, pairing rules, hostile-input
rationale, fixture and expectation-array ground truth, jsdom mechanics,
and issue-numbered regressions.

Comment lines 2604 -> 2014 (-22%); 85 module headers kept, 30 coverage
inventories dropped; every surviving comment line is within the 140-column
limit, with 36 wrapped lines joined onto fuller ones.

No logic, string, identifier, import or directive changed: a comment-strip
gate proves each file's comment-free code is byte-identical to HEAD, and a
line-level proof pairs all 845 changed lines with an unchanged code part.
pnpm run lint && pnpm run test (106 files / 2061 tests, per-file 100%
coverage) && pnpm run build all pass.
2026-10-10 00:50:55 +08:00

345 lines
14 KiB
TypeScript

// The client balance reader (src/client/balance.ts): the payload boundary proof and the read that paints the
// remembered figure while a background route read revalidates it — failures serve the remembered figure, never a blank.
import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, test, vi } from 'vitest'
import {
balanceEntryOf,
PLATFORM_BALANCE_STORAGE_KEY,
platformBalanceOf,
readPlatformBalance,
resetPlatformBalance,
setPlatformBalanceStorage,
type StorageFace,
} from '../../src/client/balance'
import type { PlatformBalance } from '../../src/shared/types'
const WIRE_BALANCE = {
isAvailable: true,
balances: [
{ currency: 'CNY', total: 110, granted: 10, toppedUp: 100 },
{ currency: 'USD', total: 1.5, granted: 0.5, toppedUp: 1 },
],
}
/** The CNY entry of a wire payload shaped like the route's. */
function wire(total: number): { isAvailable: boolean; balances: { currency: string; total: number; granted: number; toppedUp: number }[] } {
return { isAvailable: true, balances: [{ currency: 'CNY', total, granted: 0, toppedUp: total }] }
}
function stubRoute(body: unknown | undefined, mode: 'ok' | 'reject' | 'status' = 'ok'): { calls: () => number } {
let n = 0
vi.stubGlobal('fetch', async () => {
n++
if (mode === 'reject') throw new Error('down')
if (mode === 'status') return { ok: false, status: 404, json: async () => ({}) }
return { ok: true, status: 200, json: async () => body }
})
return { calls: () => n }
}
/** A route answer that lands when released, so a caller can inspect the pending state. */
function gatedRoute(): { release: () => void; calls: () => number } {
let release!: () => void
const gate = new Promise<void>((resolve) => { release = resolve })
let n = 0
vi.stubGlobal('fetch', async () => {
n++
await gate
return { ok: true, json: async () => ({ ok: true, value: WIRE_BALANCE }) }
})
return { release, calls: () => n }
}
/** A storage double over a plain map, so absence, garbage, and refusal are all reachable. */
class FakeStorage implements StorageFace {
store = new Map<string, string>()
constructor(seed?: Record<string, string>) {
for (const [k, v] of Object.entries(seed ?? {})) this.store.set(k, v)
}
getItem(key: string): string | null {
return this.store.get(key) ?? null
}
setItem(key: string, value: string): void {
this.store.set(key, value)
}
removeItem(key: string): void {
this.store.delete(key)
}
}
/** Drain the promise chains a pending route answer settles, then report. */
async function drain(): Promise<void> {
for (let i = 0; i < 8; i++) await Promise.resolve()
}
/** Collect the figures a read's background refresh reports. */
function collector(): { seen: PlatformBalance[]; onRefresh: (v: PlatformBalance) => void } {
const seen: PlatformBalance[] = []
return { seen, onRefresh: (v) => seen.push(v) }
}
/** A storage double pre-seeded with the remembered figure, as the previous open left it. */
function seededStorage(): FakeStorage {
const fake = new FakeStorage({ [PLATFORM_BALANCE_STORAGE_KEY]: JSON.stringify(WIRE_BALANCE) })
setPlatformBalanceStorage(fake)
return fake
}
/** Clear the real storage the module falls back to; a refused getter has nothing to clear. */
function clearRealStorage(): void {
try {
globalThis.localStorage.clear()
} catch {
// A swapped-in refused getter: the next test restores the real one.
}
}
beforeEach(() => {
resetPlatformBalance()
clearRealStorage()
})
afterEach(() => {
resetPlatformBalance()
clearRealStorage()
vi.unstubAllGlobals()
vi.restoreAllMocks()
})
describe('platformBalanceOf', () => {
test('a delivered wire balance parses whole', () => {
assert.deepEqual(platformBalanceOf(WIRE_BALANCE), WIRE_BALANCE)
})
test('absent or non-record payloads are no balance', () => {
assert.equal(platformBalanceOf(undefined), null)
assert.equal(platformBalanceOf(null), null)
assert.equal(platformBalanceOf('balance'), null)
assert.equal(platformBalanceOf([]), null)
})
test('a payload with no valid entry is no balance', () => {
assert.equal(platformBalanceOf({ isAvailable: true }), null)
assert.equal(platformBalanceOf({ balances: 'CNY' }), null)
assert.equal(platformBalanceOf({ balances: [null, 'CNY', [], {}] }), null)
assert.equal(platformBalanceOf({
balances: [{ currency: '', total: 1, granted: 0, toppedUp: 0 }],
}), null)
assert.equal(platformBalanceOf({
balances: [{ currency: 'CNY', total: 'abc', granted: 0, toppedUp: 0 }],
}), null)
assert.equal(platformBalanceOf({
balances: [{ currency: 'CNY', total: -1, granted: 0, toppedUp: 0 }],
}), null)
})
test('entries failing the shape drop whole, valid siblings survive', () => {
const balance = platformBalanceOf({
isAvailable: false,
balances: [
null,
{ currency: 'EUR', total: 2, granted: 0 },
{ currency: 'EUR', total: 2, granted: 0, toppedUp: 2 },
],
})
assert.deepEqual(balance, {
isAvailable: false,
balances: [{ currency: 'EUR', total: 2, granted: 0, toppedUp: 2 }],
})
})
test('a hostile entry throwing on property access drops without throwing', () => {
const hostile = new Proxy({}, { get() { throw new Error('hostile') } })
assert.equal(platformBalanceOf({ balances: [hostile] }), null)
const mixed = platformBalanceOf({ balances: [hostile, WIRE_BALANCE.balances[0]] })
assert.deepEqual(mixed?.balances, [{ currency: 'CNY', total: 110, granted: 10, toppedUp: 100 }])
})
})
describe('balanceEntryOf', () => {
test('absent or empty balances pick nothing', () => {
assert.equal(balanceEntryOf(null, 'cny'), null)
assert.equal(balanceEntryOf(undefined, 'usd'), null)
assert.equal(balanceEntryOf({ isAvailable: true, balances: [] }, 'cny'), null)
})
test('the display currency wins; the account\'s first currency is the fallback', () => {
assert.deepEqual(balanceEntryOf(WIRE_BALANCE, 'cny'), WIRE_BALANCE.balances[0])
assert.deepEqual(balanceEntryOf(WIRE_BALANCE, 'usd'), WIRE_BALANCE.balances[1])
const eurOnly = platformBalanceOf({ balances: [{ currency: 'EUR', total: 2, granted: 0, toppedUp: 2 }] })
assert.deepEqual(balanceEntryOf(eurOnly, 'usd'), eurOnly?.balances[0] ?? null)
})
})
describe('readPlatformBalance', () => {
test('nothing remembered shows nothing while the first read is pending', async () => {
const { release } = gatedRoute()
const { seen, onRefresh } = collector()
assert.equal(readPlatformBalance(onRefresh), null, 'no remembered figure to paint')
release()
await drain()
assert.equal(seen.length, 1, 'the read still lands once answered')
})
test('a landing figure reports through the refresh and is served from memory next time', async () => {
const { calls } = stubRoute({ ok: true, value: WIRE_BALANCE })
const first = collector()
assert.equal(readPlatformBalance(first.onRefresh), null, 'the first open has nothing to paint')
await drain()
assert.deepEqual(first.seen, [WIRE_BALANCE])
assert.equal(calls(), 1)
const second = collector()
assert.deepEqual(readPlatformBalance(second.onRefresh), WIRE_BALANCE, 'the memory figure paints at once')
await drain()
assert.deepEqual(second.seen, [WIRE_BALANCE])
assert.equal(calls(), 2, 'every open revalidates regardless of what it could paint')
})
test('the stored figure paints before the read lands, then the live one replaces it', async () => {
seededStorage()
const fresh = wire(99)
stubRoute({ ok: true, value: fresh })
const { seen, onRefresh } = collector()
assert.deepEqual(readPlatformBalance(onRefresh), WIRE_BALANCE, 'the stored figure shows at once')
await drain()
assert.deepEqual(seen, [fresh])
assert.deepEqual(readPlatformBalance(() => {}), fresh, 'memory now outranks storage')
})
test('an open while a read is in flight joins that read and lands with it (issue #82)', async () => {
const { release, calls } = gatedRoute()
const a = collector()
const b = collector()
assert.equal(readPlatformBalance(a.onRefresh), null)
assert.equal(readPlatformBalance(b.onRefresh), null, 'nothing remembered until a read lands')
await Promise.resolve()
release()
await drain()
assert.deepEqual(a.seen, [WIRE_BALANCE], 'the open that started the read reports its figure')
assert.deepEqual(b.seen, [WIRE_BALANCE], 'the joining open takes the landing for its own too')
assert.equal(calls(), 1, 'one read serves both opens')
assert.deepEqual(readPlatformBalance(() => {}), WIRE_BALANCE, 'and the joined read is the memory figure afterwards')
})
test('a joiner after one read landed starts a fresh read of its own', async () => {
stubRoute({ ok: true, value: WIRE_BALANCE })
const first = collector()
readPlatformBalance(first.onRefresh)
await drain()
const gated = gatedRoute()
const second = collector()
assert.deepEqual(readPlatformBalance(second.onRefresh), WIRE_BALANCE, 'the memory figure paints at once')
await Promise.resolve()
gated.release()
await drain()
assert.deepEqual(second.seen, [WIRE_BALANCE], 'its own read revalidates and reports')
assert.equal(gated.calls(), 1, 'the fresh read went through the new gate')
})
test('every absent answer notifies nobody and keeps the remembered figure', async () => {
for (const [label, body, mode] of [
['route absent (404)', undefined, 'status'],
['transport down', undefined, 'reject'],
['definitive absence', { ok: true, value: null }, 'ok'],
['bad envelope', { ok: false }, 'ok'],
['value not a record', { ok: true, value: 'nope' }, 'ok'],
] as [string, unknown, 'ok' | 'reject' | 'status'][]) {
resetPlatformBalance()
seededStorage()
stubRoute(body, mode)
const { seen, onRefresh } = collector()
assert.deepEqual(readPlatformBalance(onRefresh), WIRE_BALANCE, label)
await drain()
assert.equal(seen.length, 0, label)
assert.deepEqual(readPlatformBalance(() => {}), WIRE_BALANCE, label + ' — the figure survives the miss')
}
})
test('garbage in storage is no remembered figure', async () => {
stubRoute({ ok: true, value: WIRE_BALANCE })
for (const raw of ['not json', 'null', '"CNY"', '{"isAvailable":true}', '{"balances":[{"currency":"CNY"}]}']) {
resetPlatformBalance()
setPlatformBalanceStorage(new FakeStorage({ [PLATFORM_BALANCE_STORAGE_KEY]: raw }))
const { onRefresh } = collector()
assert.equal(readPlatformBalance(onRefresh), null, raw)
await drain()
assert.deepEqual(readPlatformBalance(() => {}), WIRE_BALANCE)
}
})
test('unreadable storage costs the head start, never the read', async () => {
const throwing = new FakeStorage()
vi.spyOn(throwing, 'getItem').mockImplementation(() => { throw new Error('denied') })
setPlatformBalanceStorage(throwing)
stubRoute({ ok: true, value: WIRE_BALANCE })
const { seen, onRefresh } = collector()
assert.equal(readPlatformBalance(onRefresh), null, 'no remembered figure to paint')
await drain()
assert.deepEqual(seen, [WIRE_BALANCE])
})
test('unwritable storage never blocks the live figure', async () => {
const refusing = new FakeStorage()
vi.spyOn(refusing, 'setItem').mockImplementation(() => { throw new Error('quota') })
setPlatformBalanceStorage(refusing)
stubRoute({ ok: true, value: WIRE_BALANCE })
const { seen, onRefresh } = collector()
readPlatformBalance(onRefresh)
await drain()
assert.deepEqual(seen, [WIRE_BALANCE])
})
test('absent storage is the same as empty storage', async () => {
setPlatformBalanceStorage(undefined)
const real = Object.getOwnPropertyDescriptor(globalThis, 'localStorage')
Object.defineProperty(globalThis, 'localStorage', { configurable: true, value: undefined })
try {
stubRoute({ ok: true, value: WIRE_BALANCE })
const { seen, onRefresh } = collector()
assert.equal(readPlatformBalance(onRefresh), null)
await drain()
assert.deepEqual(seen, [WIRE_BALANCE], 'the read is unaffected')
} finally {
Object.defineProperty(globalThis, 'localStorage', real as PropertyDescriptor)
}
})
test('storage itself can be refused: no remembered figure, no crash', async () => {
setPlatformBalanceStorage(undefined)
const real = Object.getOwnPropertyDescriptor(globalThis, 'localStorage')
Object.defineProperty(globalThis, 'localStorage', { configurable: true, get() { throw new Error('denied') } })
try {
stubRoute({ ok: true, value: WIRE_BALANCE })
const { seen, onRefresh } = collector()
assert.equal(readPlatformBalance(onRefresh), null)
await drain()
assert.deepEqual(seen, [WIRE_BALANCE], 'the read is unaffected')
} finally {
Object.defineProperty(globalThis, 'localStorage', real as PropertyDescriptor)
}
})
test('a reset drops the remembered figure, its storage, and the storage swap', async () => {
const fake = seededStorage()
stubRoute({ ok: true, value: WIRE_BALANCE })
readPlatformBalance(() => {})
await drain()
resetPlatformBalance()
assert.equal(fake.store.has(PLATFORM_BALANCE_STORAGE_KEY), false, 'storage is emptied')
const { onRefresh } = collector()
assert.equal(readPlatformBalance(onRefresh), null, 'real storage is re-detected, and it is empty too')
})
test('a reset over refused storage still clears the memory figure', async () => {
setPlatformBalanceStorage(null)
stubRoute({ ok: true, value: WIRE_BALANCE })
readPlatformBalance(() => {})
await drain()
assert.deepEqual(readPlatformBalance(() => {}), WIRE_BALANCE)
resetPlatformBalance()
assert.equal(readPlatformBalance(() => {}), null)
})
})