import assert from 'node:assert/strict'; import { after, test } from 'node:test'; import { fileURLToPath } from 'node:url'; import { createServer } from 'vite'; 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; }); const load = () => vite.ssrLoadModule('/src/utils/microphone.ts'); test('底层音频错误一律翻译为中文,英文原文不得透给用户', async () => { const { describeMicrophoneError } = await load(); const fallback = '实时陪练连接失败,请使用录音对练'; // 这些是 Chromium / WebRTC 的真实原文,真机上曾直接展示给用户。 const rawErrors = [ new Error('Could not start audio source'), Object.assign(new Error('Permission denied'), { name: 'NotAllowedError' }), Object.assign(new Error('Requested device not found'), { name: 'NotFoundError' }), Object.assign(new Error('Could not start audio source'), { name: 'NotReadableError' }), 'getUserMedia requires a secure context' ]; for (const error of rawErrors) { const text = describeMicrophoneError(error, fallback); assert.match(text, /[一-龥]/, `应翻译为中文: ${error}`); assert.ok(!/[A-Za-z]{6,}/.test(text), `不应残留英文原文: ${text}`); } }); test('业务层自抛的中文错误原样透出,不被兜底文案吞掉', async () => { const { describeMicrophoneError } = await load(); const own = '实时语音模块尚未就绪,请稍后重试'; assert.equal(describeMicrophoneError(new Error(own), '兜底文案'), own); }); test('无法识别的错误回落到兜底文案', async () => { const { describeMicrophoneError } = await load(); assert.equal(describeMicrophoneError(new Error('zzz unknown'), '兜底文案'), '兜底文案'); assert.equal(describeMicrophoneError(undefined, '兜底文案'), '兜底文案'); }); test('Android 运行时主动申请 RECORD_AUDIO 并区分拒绝与永久拒绝', async () => { const requested = []; globalThis.plus = { android: { requestPermissions: (permissions, success) => { requested.push(...permissions); success(pendingResult); } } }; let pendingResult = { granted: ['android.permission.RECORD_AUDIO'] }; const { requestMicrophonePermission } = await load(); assert.equal(await requestMicrophonePermission(), 'granted'); assert.deepEqual(requested, ['android.permission.RECORD_AUDIO']); pendingResult = { deniedPresent: ['android.permission.RECORD_AUDIO'] }; assert.equal(await requestMicrophonePermission(), 'denied'); pendingResult = { deniedAlways: ['android.permission.RECORD_AUDIO'] }; assert.equal(await requestMicrophonePermission(), 'denied-always'); delete globalThis.plus; }); test('非 App 运行时返回 unsupported,交由平台原生授权流程处理', async () => { delete globalThis.plus; const { requestMicrophonePermission } = await load(); assert.equal(await requestMicrophonePermission(), 'unsupported'); }); test('永久拒绝的引导文案指向系统设置,普通拒绝不误导', async () => { const { microphoneDeniedMessage } = await load(); assert.match(microphoneDeniedMessage('denied-always'), /系统设置/); assert.ok(!/系统设置/.test(microphoneDeniedMessage('denied'))); });