Files
prop-ai-hr/mobile-uni/tests/native-resilience.test.mjs
T

110 lines
3.6 KiB
JavaScript

import assert from 'node:assert/strict';
import { after, test } from 'node:test';
import { fileURLToPath } from 'node:url';
import { createServer } from 'vite';
const storage = new Map([
['aihr_mobile_access_token', 'expired-token'],
['aihr_mobile_client_id', 'app-client'],
['aihr_mobile_phone', '13800000000'],
['aihr_mobile_tenant_id', '000000']
]);
const redirects = [];
let uploadShouldFail = false;
globalThis.plus = {};
globalThis.getCurrentPages = () => [{
route: 'pages/user/sop/index',
$page: { fullPath: '/pages/user/sop/index?source=rc006' }
}];
globalThis.uni = {
getStorageSync: (key) => storage.get(key) || '',
setStorageSync: (key, value) => storage.set(key, value),
removeStorageSync: (key) => storage.delete(key),
reLaunch: ({ url }) => redirects.push(url),
request: ({ fail }) => {
queueMicrotask(() => fail({ errMsg: 'request:fail network' }));
return { abort: () => {} };
},
uploadFile: ({ fail, success }) => {
queueMicrotask(() => uploadShouldFail
? fail({ errMsg: 'uploadFile:fail network' })
: success({ statusCode: 200, data: '{"code":200,"data":{"text":"ok"}}' }));
return { abort: () => {} };
}
};
const vite = await createServer({
root: fileURLToPath(new URL('..', import.meta.url)),
configFile: false,
optimizeDeps: { noDiscovery: true },
server: { middlewareMode: true, hmr: false },
appType: 'custom'
});
after(async () => {
await vite.close();
delete globalThis.plus;
delete globalThis.uni;
delete globalThis.window;
delete globalThis.getCurrentPages;
});
test('ASR upload failure has a stable retryable result', async () => {
uploadShouldFail = true;
const { transcribeSpeechFile } = await vite.ssrLoadModule('/src/services/speech.ts');
await assert.rejects(
transcribeSpeechFile({ path: '/tmp/voice.mp3', name: 'voice.mp3', size: 3 }),
/语音上传失败/
);
});
test('TTS network failure falls back to device speech when supported', async () => {
const spoken = [];
globalThis.window = {
SpeechSynthesisUtterance: class {
constructor(text) { this.text = text; }
},
speechSynthesis: {
speak: (utterance) => spoken.push(utterance.text),
cancel: () => {}
}
};
const states = [];
const errors = [];
const { createSpeechPlaybackController } = await vite.ssrLoadModule('/src/services/speech.ts');
const controller = createSpeechPlaybackController(
(state) => states.push(state.status),
(message) => errors.push(message)
);
await controller.toggle('answer-1', '请先确认问题,再说明处理时限');
assert.deepEqual(spoken, ['请先确认问题,再说明处理时限']);
assert.ok(states.includes('loading'));
assert.ok(states.includes('playing'));
assert.deepEqual(errors, []);
controller.destroy();
});
test('401 clears the expired session and relaunches login while 403 does not', async () => {
const { ApiError, readPayload } = await vite.ssrLoadModule('/src/services/api.ts');
assert.throws(
() => readPayload(401, { code: 401, msg: 'login expired' }),
(error) => error instanceof ApiError && error.statusCode === 401
);
assert.equal(storage.has('aihr_mobile_access_token'), false);
assert.equal(storage.get('aihr_mobile_login_redirect'), '/pages/user/sop/index?source=rc006');
assert.deepEqual(redirects, ['/pages/auth/login/index']);
storage.set('aihr_mobile_access_token', 'new-token');
assert.throws(
() => readPayload(403, { code: 403, msg: 'forbidden' }),
(error) => error instanceof ApiError && error.statusCode === 403
);
assert.equal(storage.get('aihr_mobile_access_token'), 'new-token');
assert.deepEqual(redirects, ['/pages/auth/login/index']);
});