110 lines
3.6 KiB
JavaScript
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']);
|
|
});
|