#!/usr/bin/env node 'use strict'; const assert = require('assert/strict'); const fs = require('fs'); const os = require('os'); const path = require('path'); 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 tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'qiwei-account-switch-')); const previousCwd = process.cwd(); const previousEnv = { ...process.env }; const originalFetch = global.fetch; const requests = []; try { process.env.USERPROFILE = tempRoot; process.env.HOME = tempRoot; process.env.QIWEI_WORKSPACE_ROOT = tempRoot; process.env.QIWEI_OUTPUTS_DIR = path.join(tempRoot, 'outputs'); process.env.CLAUDE_CODE_WORKDIR = tempRoot; process.env.QIWEI_AUTH_TOKEN = 'test-account-switch-token'; delete process.env.QIWEI_UID; delete process.env.QIWE_UID; delete process.env.QIWEI_GUID; delete process.env.QIWE_GUID; process.chdir(tempRoot); const credentials = require(path.join(packageRoot, 'mcp', 'src', 'core', 'credentials')); const login = require(path.join(packageRoot, 'mcp', 'src', 'tools', 'qiwei-login-run')); assert.equal(path.dirname(credentials.CREDENTIALS_FILE), path.join(tempRoot, '.claude')); const envFile = path.join(tempRoot, '.env.local'); credentials.saveQiweiClientConfig({ uid: 'uid-account-a', guid: 'guid-account-a', apiBase: 'https://gateway.example/api/qiwei', envRoot: tempRoot, }); assert.equal(envValue(envFile, 'QIWEI_UID'), 'uid-account-a'); assert.equal(envValue(envFile, 'QIWEI_GUID'), 'guid-account-a'); assert.equal(credentials.readQiweiGuid({ uid: 'uid-account-b' }), ''); let checkLoggedIn = false; global.fetch = async (rawUrl, options = {}) => { const url = new URL(String(rawUrl)); const body = options.body ? JSON.parse(options.body) : {}; requests.push({ path: url.pathname, body }); let data = {}; if (url.pathname.endsWith('/login/start')) data = { uid: body.uid }; else if (url.pathname.endsWith('/login/check')) { data = checkLoggedIn ? { uid: body.uid, status: 2, detail: { userId: 'user-b', nickname: '账号 B', guid: 'guid-account-b' } } : { uid: body.uid, status: 10, detail: {} }; } else if (url.pathname.endsWith('/login/verify')) data = { accepted: true }; else if (url.pathname.endsWith('/login/status')) data = { configured: true, online: true, statusCode: 2, detail: { userId: 'user-b', nickname: '账号 B' } }; return new Response(JSON.stringify({ code: 0, data }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); }; const common = { uid: 'uid-account-b', authToken: 'test-account-switch-token', apiBase: 'https://gateway.example/api/qiwei', flowUi: false, openBrowser: false, persistConfig: false, skipSubscriptionCheck: true, }; await login.qiweiLoginStart(common); const pending = await login.qiweiLoginCheck(common); assert.equal(pending.status, 'needs_verify_code'); await login.qiweiLoginVerify({ ...common, code: '123456' }); for (const request of requests.slice(0, 3)) { assert.equal(request.body.uid, 'uid-account-b'); assert.equal(Object.prototype.hasOwnProperty.call(request.body, 'guid'), false); } checkLoggedIn = true; const loggedIn = await login.qiweiLoginCheck(common); assert.equal(loggedIn.summary.loggedIn, true); assert.equal(loggedIn.data.guid, 'guid-account-b'); assert.equal(Object.prototype.hasOwnProperty.call(requests.at(-1).body, 'guid'), false); const { switchActiveAccount } = require(path.join(packageRoot, 'mcp', 'src', 'dashboard', 'agent-service')); await switchActiveAccount({ uid: 'uid-account-b', guid: loggedIn.data.guid, apiBase: common.apiBase, userId: 'user-b', nickname: '账号 B', }); assert.equal(envValue(envFile, 'QIWEI_UID'), 'uid-account-b'); assert.equal(envValue(envFile, 'QIWEI_GUID'), 'guid-account-b'); assert.equal(credentials.readQiweiGuid({ uid: 'uid-account-b' }), 'guid-account-b'); await switchActiveAccount({ uid: 'uid-account-c', guid: '', apiBase: common.apiBase, userId: 'user-c', nickname: '账号 C', }); assert.equal(envValue(envFile, 'QIWEI_UID'), 'uid-account-c'); assert.equal(envValue(envFile, 'QIWEI_GUID'), ''); assert.equal(credentials.readQiweiGuid({ uid: 'uid-account-c' }), ''); assert.equal(JSON.parse(fs.readFileSync(credentials.CREDENTIALS_FILE, 'utf8')).guid, undefined); const appSource = fs.readFileSync(path.join(packageRoot, 'mcp', 'src', 'dashboard', 'app.js'), 'utf8'); assert.match(appSource, /qiwei-\$\{workspaceId\}-accounts/); assert.match(appSource, /await initializeWorkspaceAccountStorage\(\);[\s\S]*if \(currentAccount\(\)\?\.uid\)/); assert.match(appSource, /addAccount\(account, \{ activate: false \}\)[\s\S]*await switchAccount\(accountId/); assert.match(appSource, /if \(!switched\)[\s\S]*state\.currentAccountId = previousAccountId/); assert.match(appSource, /statusCode === 4[\s\S]*restorePreviousAccountSelection\(\)/); process.stdout.write(`${JSON.stringify({ status: 'ok', checks: 20, coverage: [ 'new_uid_does_not_reuse_old_guid', 'verify_and_check_keep_uid_isolation', 'successful_switch_persists_uid_guid', 'guidless_switch_clears_stale_guid', 'workspace_scoped_browser_accounts', 'failed_or_cancelled_login_restores_previous_account', ], }, null, 2)}\n`); } finally { global.fetch = originalFetch; 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(tempRoot, { recursive: true, force: true }); } } main().catch(error => { process.stderr.write(`${error.stack || error.message}\n`); process.exitCode = 1; });