'use strict'; const assert = require('assert/strict'); const fs = require('fs'); const os = require('os'); const path = require('path'); const { VoiceCloneService, } = require('../mcp/src/core/voice-clone-service'); const { readFmodeVoiceToken, readFmodeVoiceTokenStatus, saveFmodeVoiceToken, } = require('../mcp/src/core/credentials'); const root = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-voice-token-smoke-')); const envSnapshot = { QIWEI_WORKSPACE_ROOT: process.env.QIWEI_WORKSPACE_ROOT, QIWEI_PACKAGE_ROOT: process.env.QIWEI_PACKAGE_ROOT, QIWEI_VOICE_AUTH_TOKEN: process.env.QIWEI_VOICE_AUTH_TOKEN, }; function restoreEnv() { for (const [key, value] of Object.entries(envSnapshot)) { if (value === undefined) delete process.env[key]; else process.env[key] = value; } fs.rmSync(root, { recursive: true, force: true }); } async function main() { const validToken = 'sk-test-voice-token-0123456789'; const sessionToken = 'r:test-session-token'; const envPath = path.join(root, '.env.local'); fs.writeFileSync(envPath, 'KEEP_ME=preserved\n# existing setting\n', 'utf8'); process.env.QIWEI_WORKSPACE_ROOT = root; delete process.env.QIWEI_PACKAGE_ROOT; delete process.env.QIWEI_VOICE_AUTH_TOKEN; const saved = saveFmodeVoiceToken({ token: validToken, envRoot: root }); assert.equal(saved.configured, true); assert.equal(saved.path, envPath); const persisted = fs.readFileSync(envPath, 'utf8'); assert.match(persisted, /^KEEP_ME=preserved$/m); assert.match(persisted, /^QIWEI_VOICE_AUTH_TOKEN=sk-/m); assert.doesNotMatch(JSON.stringify(saved), new RegExp(validToken)); const status = readFmodeVoiceTokenStatus({}, { processEnv: {}, fileEnv: { QIWEI_VOICE_AUTH_TOKEN: validToken }, claudeEnv: {}, fmodeConfig: {}, }); assert.equal(status.configured, true); assert.equal(status.format, 'fmode-sk'); assert.notEqual(status.masked, validToken); assert.doesNotMatch(JSON.stringify(status), new RegExp(validToken)); assert.equal(readFmodeVoiceToken({ authToken: validToken }, { processEnv: {}, fileEnv: {}, claudeEnv: {}, fmodeConfig: {}, }), validToken); assert.equal(readFmodeVoiceToken({}, { processEnv: {}, fileEnv: { QIWEI_VOICE_AUTH_TOKEN: sessionToken }, claudeEnv: {}, fmodeConfig: {}, }), ''); assert.throws(() => saveFmodeVoiceToken({ token: sessionToken, envRoot: root }), /sk-/); let dynamicToken = ''; const dynamicStatus = () => readFmodeVoiceTokenStatus({}, { processEnv: dynamicToken ? { QIWEI_VOICE_AUTH_TOKEN: dynamicToken } : {}, fileEnv: {}, claudeEnv: {}, fmodeConfig: {}, }); const service = new VoiceCloneService({ config: { endpoint: 'https://server.fmode.cn/api/voice/indextts2', authTokenResolver: () => dynamicToken, authTokenStatusResolver: dynamicStatus, }, qiwei: {}, }); assert.equal(service.status().configured, false); dynamicToken = validToken; const configured = service.status(); assert.equal(configured.configured, true); assert.equal(configured.authTokenConfigured, true); assert.notEqual(configured.authTokenMasked, validToken); assert.doesNotMatch(JSON.stringify(configured), new RegExp(validToken)); assert.throws(() => service.requireReady(), /尚未初始化本人声音/); console.log('[ok] voice token fallback persistence, masking, validation, and hot reload'); } main() .catch(error => { console.error(error.message || 'voice token smoke failed'); process.exitCode = 1; }) .finally(restoreEnv);