| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- 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);
- });
|