import test from 'node:test'; import assert from 'node:assert/strict'; import vm from 'node:vm'; import { build } from 'esbuild'; // Exercise the actual request/composable code against a controlled uni transport, without sending business requests. const bundled = await build({ stdin: { contents: `export { session, clearSession } from './src/stores/state'; export { get } from './src/api/client'; export { useCommand } from './src/composables/useCommand'; export { useResource } from './src/composables/useResource'; export { usePaged } from './src/composables/usePaged';`, resolveDir: process.cwd(), loader: 'ts', }, bundle: true, write: false, platform: 'node', format: 'cjs', define: { 'import.meta.env.VITE_API_BASE_URL': '""', 'process.env.NODE_ENV': '"production"', }, }); function fixture() { const calls = []; const storage = new Map(); const transport = { request: (options) => calls.push(options), getStorageSync: (key) => (storage.has(key) ? JSON.parse(storage.get(key)) : ''), setStorageSync: (key, value) => storage.set(key, JSON.stringify(value)), removeStorageSync: (key) => storage.delete(key), stopPullDownRefresh() {}, showToast() {}, }; const context = vm.createContext({ module: { exports: {} }, exports: {}, uni: transport, console, setTimeout, clearTimeout, }); vm.runInContext(bundled.outputFiles[0].text, context); const app = context.module.exports; Object.assign(app.session, { token: 'a'.repeat(64), userId: '11', active: { role: 'enterprise_admin', resource_id: 4, name: 'fixture' }, }); return { ...app, calls, storage }; } test('protected requests carry mini token; public requests do not', async () => { const app = fixture(); const publicRequest = app.get('/config', {}, false); assert.equal(app.calls[0].header.Authorization, undefined); app.calls[0].success({ statusCode: 200, data: { code: 0, data: { enabled: true } }, }); await publicRequest; const protectedRequest = app.get('/me'); assert.equal(app.calls[1].header.Authorization, 'Bearer ' + 'a'.repeat(64)); app.calls[1].success({ statusCode: 200, data: { code: 0, data: { user_id: '11' } }, }); assert.equal((await protectedRequest).user_id, '11'); }); test('HTTP 401 clears the current session and rejects the request', async () => { const app = fixture(); const request = app.get('/me'); app.calls[0].success({ statusCode: 401, data: { code: 40101, message: '请先登录' }, }); await assert.rejects(request, /请先登录/); assert.equal(app.session.token, ''); assert.equal(app.session.active, null); }); test('a late response from an old identity cannot update data or invalidate a new login', async () => { const app = fixture(); const request = app.get('/me'); app.session.epoch++; app.session.token = 'b'.repeat(64); app.calls[0].success({ statusCode: 401, data: { code: 40101, message: 'old token' }, }); await assert.rejects(request, /身份已切换/); assert.equal(app.session.token, 'b'.repeat(64)); }); test('logout synchronously clears loaded private data and list items', async () => { const app = fixture(); const detail = app.useResource(async () => ({ name: 'private' })); const list = app.usePaged(async () => ({ items: [{ id: 1 }], has_more: false, total: 1, })); await detail.load(); await list.load(true); assert.equal(detail.data.value.name, 'private'); assert.equal(list.items.value.length, 1); app.clearSession(); assert.equal(detail.data.value, null); assert.equal(list.items.value.length, 0); }); test('a slow old filter cannot overwrite a newer paginated result', async () => { const app = fixture(); const pending = []; const list = app.usePaged(() => new Promise((resolve) => pending.push(resolve))); const first = list.load(true); const second = list.load(true); pending[1]({ items: [{ id: 2 }], total: 1, has_more: false }); await second; pending[0]({ items: [{ id: 1 }], total: 1, has_more: false }); await first; assert.equal(list.items.value[0].id, 2); assert.equal(list.data.value.items[0].id, 2); }); test('pagination appends each page, stops at the end, and clears metadata on logout', async () => { const app = fixture(); const pages = []; const list = app.usePaged(async (page) => { pages.push(page); return { items: [{ id: page }], total: 2, has_more: page < 2, total_cents: 266400 }; }); await list.load(true); assert.equal(list.data.value.total_cents, 266400); await list.load(); await list.load(); assert.deepEqual(pages, [1, 2]); assert.equal(list.items.value.length, 2); assert.equal(list.hasMore.value, false); app.clearSession(); assert.equal(list.data.value, null); assert.equal(list.items.value.length, 0); }); test('double taps are ignored, network retries reuse persisted key, and success is delivered once', async () => { const app = fixture(); let completions = 0; const command = app.useCommand( () => '/enterprises/4/deliveries', () => { completions++; }, ); const first = command.execute({ sku_id: 2, quantity: 8 }); await command.execute({ sku_id: 2, quantity: 8 }); assert.equal(app.calls.length, 1); const key = app.calls[0].data.request_key; app.calls[0].fail(); await first; assert.equal(command.pending, true); assert.equal(app.storage.size, 1); const retry = command.retry(); assert.equal(app.calls[1].data.request_key, key); app.calls[1].success({ statusCode: 200, data: { code: 0, data: { id: 9 } } }); await retry; assert.equal(command.pending, false); assert.equal(app.storage.size, 0); assert.equal(completions, 1); }); test('checkout submission repairs a stored payload missing its request key and keeps it on retry', async () => { const app = fixture(); const path = '/enterprises/49/checkouts'; const payload = { items: [{ package_id: 1, quantity: 2 }], expected_amount_cents: 1000, agreement_accepted: true, invoice: null }; app.storage.set(`water.command:11:${path}`, JSON.stringify(payload)); const command = app.useCommand(() => path, () => {}); const attempt = command.execute(payload); const key = app.calls[0].data.request_key; assert.match(key, /^[A-Za-z0-9_-]{8,80}$/); app.calls[0].fail(); await attempt; const retry = command.retry(); assert.equal(app.calls[1].data.request_key, key); app.calls[1].success({ statusCode: 200, data: { code: 0, data: { id: 9 } } }); await retry; assert.equal(app.storage.size, 0); }); test('an unresolved command survives page recreation and cannot be replaced with edited input', async () => { const app = fixture(); const path = () => '/enterprises/4/deliveries'; const first = app.useCommand(path, () => {}); const attempt = first.execute({ quantity: 8 }); app.calls[0].fail(); await attempt; const reopened = app.useCommand(path, () => {}); await reopened.execute({ quantity: 9 }); assert.equal(app.calls.length, 1); assert.equal(reopened.pending, true); const retry = reopened.retry(); assert.equal(app.calls[1].data.quantity, 8); app.calls[1].success({ statusCode: 422, data: { code: 42201, message: 'test rejection' }, }); await retry; assert.equal(app.storage.size, 0); });