voice-token-fallback-smoke-test.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. 'use strict';
  2. const assert = require('assert/strict');
  3. const fs = require('fs');
  4. const os = require('os');
  5. const path = require('path');
  6. const {
  7. VoiceCloneService,
  8. } = require('../mcp/src/core/voice-clone-service');
  9. const {
  10. readFmodeVoiceToken,
  11. readFmodeVoiceTokenStatus,
  12. saveFmodeVoiceToken,
  13. } = require('../mcp/src/core/credentials');
  14. const root = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-voice-token-smoke-'));
  15. const envSnapshot = {
  16. QIWEI_WORKSPACE_ROOT: process.env.QIWEI_WORKSPACE_ROOT,
  17. QIWEI_PACKAGE_ROOT: process.env.QIWEI_PACKAGE_ROOT,
  18. QIWEI_VOICE_AUTH_TOKEN: process.env.QIWEI_VOICE_AUTH_TOKEN,
  19. };
  20. function restoreEnv() {
  21. for (const [key, value] of Object.entries(envSnapshot)) {
  22. if (value === undefined) delete process.env[key];
  23. else process.env[key] = value;
  24. }
  25. fs.rmSync(root, { recursive: true, force: true });
  26. }
  27. async function main() {
  28. const validToken = 'sk-test-voice-token-0123456789';
  29. const sessionToken = 'r:test-session-token';
  30. const envPath = path.join(root, '.env.local');
  31. fs.writeFileSync(envPath, 'KEEP_ME=preserved\n# existing setting\n', 'utf8');
  32. process.env.QIWEI_WORKSPACE_ROOT = root;
  33. delete process.env.QIWEI_PACKAGE_ROOT;
  34. delete process.env.QIWEI_VOICE_AUTH_TOKEN;
  35. const saved = saveFmodeVoiceToken({ token: validToken, envRoot: root });
  36. assert.equal(saved.configured, true);
  37. assert.equal(saved.path, envPath);
  38. const persisted = fs.readFileSync(envPath, 'utf8');
  39. assert.match(persisted, /^KEEP_ME=preserved$/m);
  40. assert.match(persisted, /^QIWEI_VOICE_AUTH_TOKEN=sk-/m);
  41. assert.doesNotMatch(JSON.stringify(saved), new RegExp(validToken));
  42. const status = readFmodeVoiceTokenStatus({}, {
  43. processEnv: {},
  44. fileEnv: { QIWEI_VOICE_AUTH_TOKEN: validToken },
  45. claudeEnv: {},
  46. fmodeConfig: {},
  47. });
  48. assert.equal(status.configured, true);
  49. assert.equal(status.format, 'fmode-sk');
  50. assert.notEqual(status.masked, validToken);
  51. assert.doesNotMatch(JSON.stringify(status), new RegExp(validToken));
  52. assert.equal(readFmodeVoiceToken({ authToken: validToken }, {
  53. processEnv: {}, fileEnv: {}, claudeEnv: {}, fmodeConfig: {},
  54. }), validToken);
  55. assert.equal(readFmodeVoiceToken({}, {
  56. processEnv: {},
  57. fileEnv: { QIWEI_VOICE_AUTH_TOKEN: sessionToken },
  58. claudeEnv: {},
  59. fmodeConfig: {},
  60. }), '');
  61. assert.throws(() => saveFmodeVoiceToken({ token: sessionToken, envRoot: root }), /sk-/);
  62. let dynamicToken = '';
  63. const dynamicStatus = () => readFmodeVoiceTokenStatus({}, {
  64. processEnv: dynamicToken ? { QIWEI_VOICE_AUTH_TOKEN: dynamicToken } : {},
  65. fileEnv: {},
  66. claudeEnv: {},
  67. fmodeConfig: {},
  68. });
  69. const service = new VoiceCloneService({
  70. config: {
  71. endpoint: 'https://server.fmode.cn/api/voice/indextts2',
  72. authTokenResolver: () => dynamicToken,
  73. authTokenStatusResolver: dynamicStatus,
  74. },
  75. qiwei: {},
  76. });
  77. assert.equal(service.status().configured, false);
  78. dynamicToken = validToken;
  79. const configured = service.status();
  80. assert.equal(configured.configured, true);
  81. assert.equal(configured.authTokenConfigured, true);
  82. assert.notEqual(configured.authTokenMasked, validToken);
  83. assert.doesNotMatch(JSON.stringify(configured), new RegExp(validToken));
  84. assert.throws(() => service.requireReady(), /尚未初始化本人声音/);
  85. console.log('[ok] voice token fallback persistence, masking, validation, and hot reload');
  86. }
  87. main()
  88. .catch(error => {
  89. console.error(error.message || 'voice token smoke failed');
  90. process.exitCode = 1;
  91. })
  92. .finally(restoreEnv);