#!/usr/bin/env node 'use strict'; const assert = require('assert/strict'); const fs = require('fs'); const os = require('os'); const path = require('path'); function writeEnv(filePath, values) { fs.writeFileSync(filePath, Object.entries(values).map(([key, value]) => `${key}=${value}`).join('\n') + '\n', 'utf8'); } function envValue(filePath, key) { const content = fs.readFileSync(filePath, 'utf8'); const match = content.match(new RegExp(`^${key}=(.*)$`, 'm')); return match ? match[1].trim() : undefined; } async function main() { const packageRoot = path.resolve(__dirname, '..'); const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-credential-precedence-')); const previousCwd = process.cwd(); const previousEnv = { ...process.env }; let closeAgentWorkbenches = () => {}; let otherCwd = ''; try { process.env.HOME = fixtureRoot; process.env.USERPROFILE = fixtureRoot; process.env.QIWEI_PACKAGE_ROOT = fixtureRoot; process.env.QIWEI_WORKSPACE_ROOT = fixtureRoot; process.env.QIWEI_AUTH_TOKEN = 'startup-auth'; process.env.QIWEI_UID = 'startup-uid'; process.env.QIWEI_GUID = 'startup-guid'; process.env.QIWEI_API_BASE = 'https://startup.example/api/qiwei'; process.chdir(fixtureRoot); const envPath = path.join(fixtureRoot, '.env.local'); writeEnv(envPath, { QIWEI_AUTH_TOKEN: 'file-auth-a', QIWEI_UID: 'file-uid-a', QIWEI_GUID: 'file-guid-a', QIWEI_API_BASE: 'https://file-a.example/api/qiwei', }); const globalConfigDir = path.join(fixtureRoot, '.fmode'); fs.mkdirSync(globalConfigDir, { recursive: true }); fs.writeFileSync(path.join(globalConfigDir, 'config.json'), JSON.stringify({ sessionToken: 'global-auth' }), 'utf8'); const credentials = require(path.join(packageRoot, 'mcp', 'src', 'core', 'credentials')); const { FmodeQiweiClient } = require(path.join(packageRoot, 'mcp', 'src', 'providers', 'fmode-agent-transport')); assert.equal(credentials.readQiweiAuthToken(), 'file-auth-a'); assert.equal(credentials.readQiweiUid(), 'file-uid-a'); assert.equal(credentials.readQiweiGuid({ uid: 'file-uid-a' }), 'file-guid-a'); assert.equal(credentials.readQiweiApiBase(), 'https://file-a.example/api/qiwei'); const dynamicClient = new FmodeQiweiClient({ dynamicCredentials: true, authToken: 'cached-auth', uid: 'cached-uid', guid: 'cached-guid', apiBase: 'https://cached.example/api/qiwei', }); assert.deepEqual(dynamicClient.context(), { token: 'file-auth-a', uid: 'file-uid-a', guid: 'file-guid-a', apiBase: 'https://file-a.example/api/qiwei', transportMode: 'fmode', upstreamToken: '', upstreamApiBase: 'https://manager.qiweapi.com/qiwe', }); assert.equal(credentials.readQiweiAuthToken({ authToken: 'request-auth' }), 'request-auth'); assert.equal(credentials.readQiweiUid({ uid: 'request-uid' }), 'request-uid'); assert.equal(credentials.readQiweiGuid({ uid: 'request-uid', guid: 'request-guid' }), 'request-guid'); assert.equal(credentials.readQiweiApiBase({ apiBase: 'https://request.example/api/qiwei' }), 'https://request.example/api/qiwei'); writeEnv(envPath, { QIWEI_AUTH_TOKEN: 'file-auth-b', QIWEI_UID: 'file-uid-b', QIWEI_GUID: 'file-guid-b', QIWEI_API_BASE: 'https://file-b.example/api/qiwei', }); assert.equal(credentials.readQiweiAuthToken(), 'file-auth-b'); assert.equal(credentials.readQiweiUid(), 'file-uid-b'); assert.equal(credentials.readQiweiGuid({ uid: 'file-uid-b' }), 'file-guid-b'); assert.equal(credentials.readQiweiGuid({ uid: 'different-uid' }), ''); assert.equal(credentials.readQiweiApiBase(), 'https://file-b.example/api/qiwei'); assert.equal(dynamicClient.context().token, 'file-auth-b'); assert.equal(dynamicClient.context().uid, 'file-uid-b'); assert.equal(dynamicClient.context().guid, 'file-guid-b'); const agentService = require(path.join(packageRoot, 'mcp', 'src', 'dashboard', 'agent-service')); closeAgentWorkbenches = agentService.closeAgentWorkbenches; const loadedAgentConfig = agentService.__testing.loadAgentConfig(); assert.equal(loadedAgentConfig.qiwei.authToken, 'file-auth-b'); assert.equal(loadedAgentConfig.qiwei.uid, 'file-uid-b'); assert.equal(loadedAgentConfig.qiwei.guid, 'file-guid-b'); assert.equal(loadedAgentConfig.qiwei.apiBase, 'https://file-b.example/api/qiwei'); const globalCredentialPath = credentials.CREDENTIALS_FILE; credentials.saveQiweiClientConfig({ uid: 'file-uid-b', guid: 'file-guid-b', apiBase: 'https://file-b.example/api/qiwei', envRoot: fixtureRoot, }); // Persistence must follow the configured package root even when a caller // (for example an MCP client) has changed the process working directory. otherCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-credential-cwd-')); process.chdir(otherCwd); credentials.saveQiweiClientConfig({ uid: 'file-uid-b', guid: 'file-guid-b', apiBase: 'https://file-b.example/api/qiwei', }); assert.equal(envValue(envPath, 'QIWEI_GUID'), 'file-guid-b'); assert.equal(fs.existsSync(path.join(otherCwd, '.env.local')), false); process.chdir(fixtureRoot); assert.equal(fs.existsSync(globalCredentialPath), false, 'standalone package must not write ~/.claude credentials'); const cleared = credentials.clearQiweiClientConfig({ uid: 'file-uid-b', envRoot: fixtureRoot }); assert.equal(cleared.cleared, true); assert.equal(envValue(envPath, 'QIWEI_UID'), ''); assert.equal(envValue(envPath, 'QIWEI_GUID'), ''); assert.equal(credentials.readQiweiUid(), ''); assert.equal(credentials.readQiweiGuid(), ''); writeEnv(envPath, { QIWEI_AUTH_TOKEN: '', QIWEI_UID: '', QIWEI_GUID: '', QIWEI_API_BASE: '', }); delete process.env.QIWEI_AUTH_TOKEN; delete process.env.QIWE_AUTH_TOKEN; delete process.env.FMODE_API_KEY; delete process.env.FMODE_API_TOKEN; delete process.env.NEWAPI_TOKEN; delete process.env.ANTHROPIC_AUTH_TOKEN; assert.equal(credentials.readQiweiAuthToken(), '', 'standalone package must ignore ~/.fmode/config.json'); process.stdout.write(`${JSON.stringify({ status: 'ok', checks: 28, coverage: [ 'request_values_override_package_file', 'package_file_overrides_startup_environment', 'updated_package_file_is_visible_without_restart', 'long_lived_client_reloads_package_credentials', 'workbench_scope_uses_package_file_credentials', 'uid_guid_are_kept_on_the_same_account', 'host_fmode_config_is_ignored', 'standalone_package_does_not_write_global_credentials', 'standalone_account_removal_clears_package_file', ], }, null, 2)}\n`); } finally { closeAgentWorkbenches(); process.chdir(previousCwd); for (const key of Object.keys(process.env)) { if (!(key in previousEnv)) delete process.env[key]; } for (const [key, value] of Object.entries(previousEnv)) process.env[key] = value; fs.rmSync(fixtureRoot, { recursive: true, force: true }); if (otherCwd) fs.rmSync(otherCwd, { recursive: true, force: true }); } } main().catch(error => { process.stderr.write(`${error.stack || error.message}\n`); process.exitCode = 1; });