credentials-precedence-smoke-test.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #!/usr/bin/env node
  2. 'use strict';
  3. const assert = require('assert/strict');
  4. const fs = require('fs');
  5. const os = require('os');
  6. const path = require('path');
  7. function writeEnv(filePath, values) {
  8. fs.writeFileSync(filePath, Object.entries(values).map(([key, value]) => `${key}=${value}`).join('\n') + '\n', 'utf8');
  9. }
  10. function envValue(filePath, key) {
  11. const content = fs.readFileSync(filePath, 'utf8');
  12. const match = content.match(new RegExp(`^${key}=(.*)$`, 'm'));
  13. return match ? match[1].trim() : undefined;
  14. }
  15. async function main() {
  16. const packageRoot = path.resolve(__dirname, '..');
  17. const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-credential-precedence-'));
  18. const previousCwd = process.cwd();
  19. const previousEnv = { ...process.env };
  20. let closeAgentWorkbenches = () => {};
  21. let otherCwd = '';
  22. try {
  23. process.env.HOME = fixtureRoot;
  24. process.env.USERPROFILE = fixtureRoot;
  25. process.env.QIWEI_PACKAGE_ROOT = fixtureRoot;
  26. process.env.QIWEI_WORKSPACE_ROOT = fixtureRoot;
  27. process.env.QIWEI_AUTH_TOKEN = 'startup-auth';
  28. process.env.QIWEI_UID = 'startup-uid';
  29. process.env.QIWEI_GUID = 'startup-guid';
  30. process.env.QIWEI_API_BASE = 'https://startup.example/api/qiwei';
  31. process.chdir(fixtureRoot);
  32. const envPath = path.join(fixtureRoot, '.env.local');
  33. writeEnv(envPath, {
  34. QIWEI_AUTH_TOKEN: 'file-auth-a',
  35. QIWEI_UID: 'file-uid-a',
  36. QIWEI_GUID: 'file-guid-a',
  37. QIWEI_API_BASE: 'https://file-a.example/api/qiwei',
  38. });
  39. const globalConfigDir = path.join(fixtureRoot, '.fmode');
  40. fs.mkdirSync(globalConfigDir, { recursive: true });
  41. fs.writeFileSync(path.join(globalConfigDir, 'config.json'), JSON.stringify({ sessionToken: 'global-auth' }), 'utf8');
  42. const credentials = require(path.join(packageRoot, 'mcp', 'src', 'core', 'credentials'));
  43. const { FmodeQiweiClient } = require(path.join(packageRoot, 'mcp', 'src', 'providers', 'fmode-agent-transport'));
  44. assert.equal(credentials.readQiweiAuthToken(), 'file-auth-a');
  45. assert.equal(credentials.readQiweiUid(), 'file-uid-a');
  46. assert.equal(credentials.readQiweiGuid({ uid: 'file-uid-a' }), 'file-guid-a');
  47. assert.equal(credentials.readQiweiApiBase(), 'https://file-a.example/api/qiwei');
  48. const dynamicClient = new FmodeQiweiClient({
  49. dynamicCredentials: true,
  50. authToken: 'cached-auth',
  51. uid: 'cached-uid',
  52. guid: 'cached-guid',
  53. apiBase: 'https://cached.example/api/qiwei',
  54. });
  55. assert.deepEqual(dynamicClient.context(), {
  56. token: 'file-auth-a',
  57. uid: 'file-uid-a',
  58. guid: 'file-guid-a',
  59. apiBase: 'https://file-a.example/api/qiwei',
  60. transportMode: 'fmode',
  61. upstreamToken: '',
  62. upstreamApiBase: 'https://manager.qiweapi.com/qiwe',
  63. });
  64. assert.equal(credentials.readQiweiAuthToken({ authToken: 'request-auth' }), 'request-auth');
  65. assert.equal(credentials.readQiweiUid({ uid: 'request-uid' }), 'request-uid');
  66. assert.equal(credentials.readQiweiGuid({ uid: 'request-uid', guid: 'request-guid' }), 'request-guid');
  67. assert.equal(credentials.readQiweiApiBase({ apiBase: 'https://request.example/api/qiwei' }), 'https://request.example/api/qiwei');
  68. writeEnv(envPath, {
  69. QIWEI_AUTH_TOKEN: 'file-auth-b',
  70. QIWEI_UID: 'file-uid-b',
  71. QIWEI_GUID: 'file-guid-b',
  72. QIWEI_API_BASE: 'https://file-b.example/api/qiwei',
  73. });
  74. assert.equal(credentials.readQiweiAuthToken(), 'file-auth-b');
  75. assert.equal(credentials.readQiweiUid(), 'file-uid-b');
  76. assert.equal(credentials.readQiweiGuid({ uid: 'file-uid-b' }), 'file-guid-b');
  77. assert.equal(credentials.readQiweiGuid({ uid: 'different-uid' }), '');
  78. assert.equal(credentials.readQiweiApiBase(), 'https://file-b.example/api/qiwei');
  79. assert.equal(dynamicClient.context().token, 'file-auth-b');
  80. assert.equal(dynamicClient.context().uid, 'file-uid-b');
  81. assert.equal(dynamicClient.context().guid, 'file-guid-b');
  82. const agentService = require(path.join(packageRoot, 'mcp', 'src', 'dashboard', 'agent-service'));
  83. closeAgentWorkbenches = agentService.closeAgentWorkbenches;
  84. const loadedAgentConfig = agentService.__testing.loadAgentConfig();
  85. assert.equal(loadedAgentConfig.qiwei.authToken, 'file-auth-b');
  86. assert.equal(loadedAgentConfig.qiwei.uid, 'file-uid-b');
  87. assert.equal(loadedAgentConfig.qiwei.guid, 'file-guid-b');
  88. assert.equal(loadedAgentConfig.qiwei.apiBase, 'https://file-b.example/api/qiwei');
  89. const globalCredentialPath = credentials.CREDENTIALS_FILE;
  90. credentials.saveQiweiClientConfig({
  91. uid: 'file-uid-b',
  92. guid: 'file-guid-b',
  93. apiBase: 'https://file-b.example/api/qiwei',
  94. envRoot: fixtureRoot,
  95. });
  96. // Persistence must follow the configured package root even when a caller
  97. // (for example an MCP client) has changed the process working directory.
  98. otherCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-credential-cwd-'));
  99. process.chdir(otherCwd);
  100. credentials.saveQiweiClientConfig({
  101. uid: 'file-uid-b',
  102. guid: 'file-guid-b',
  103. apiBase: 'https://file-b.example/api/qiwei',
  104. });
  105. assert.equal(envValue(envPath, 'QIWEI_GUID'), 'file-guid-b');
  106. assert.equal(fs.existsSync(path.join(otherCwd, '.env.local')), false);
  107. process.chdir(fixtureRoot);
  108. assert.equal(fs.existsSync(globalCredentialPath), false, 'standalone package must not write ~/.claude credentials');
  109. const cleared = credentials.clearQiweiClientConfig({ uid: 'file-uid-b', envRoot: fixtureRoot });
  110. assert.equal(cleared.cleared, true);
  111. assert.equal(envValue(envPath, 'QIWEI_UID'), '');
  112. assert.equal(envValue(envPath, 'QIWEI_GUID'), '');
  113. assert.equal(credentials.readQiweiUid(), '');
  114. assert.equal(credentials.readQiweiGuid(), '');
  115. writeEnv(envPath, {
  116. QIWEI_AUTH_TOKEN: '',
  117. QIWEI_UID: '',
  118. QIWEI_GUID: '',
  119. QIWEI_API_BASE: '',
  120. });
  121. delete process.env.QIWEI_AUTH_TOKEN;
  122. delete process.env.QIWE_AUTH_TOKEN;
  123. delete process.env.FMODE_API_KEY;
  124. delete process.env.FMODE_API_TOKEN;
  125. delete process.env.NEWAPI_TOKEN;
  126. delete process.env.ANTHROPIC_AUTH_TOKEN;
  127. assert.equal(credentials.readQiweiAuthToken(), '', 'standalone package must ignore ~/.fmode/config.json');
  128. process.stdout.write(`${JSON.stringify({
  129. status: 'ok',
  130. checks: 28,
  131. coverage: [
  132. 'request_values_override_package_file',
  133. 'package_file_overrides_startup_environment',
  134. 'updated_package_file_is_visible_without_restart',
  135. 'long_lived_client_reloads_package_credentials',
  136. 'workbench_scope_uses_package_file_credentials',
  137. 'uid_guid_are_kept_on_the_same_account',
  138. 'host_fmode_config_is_ignored',
  139. 'standalone_package_does_not_write_global_credentials',
  140. 'standalone_account_removal_clears_package_file',
  141. ],
  142. }, null, 2)}\n`);
  143. } finally {
  144. closeAgentWorkbenches();
  145. process.chdir(previousCwd);
  146. for (const key of Object.keys(process.env)) {
  147. if (!(key in previousEnv)) delete process.env[key];
  148. }
  149. for (const [key, value] of Object.entries(previousEnv)) process.env[key] = value;
  150. fs.rmSync(fixtureRoot, { recursive: true, force: true });
  151. if (otherCwd) fs.rmSync(otherCwd, { recursive: true, force: true });
  152. }
  153. }
  154. main().catch(error => {
  155. process.stderr.write(`${error.stack || error.message}\n`);
  156. process.exitCode = 1;
  157. });