'use strict'; const assert = require('assert'); const screenshotPath = require.resolve('../src/utils/screenshot'); const screenshotModule = require(screenshotPath); const originalSaveErrorScreenshot = screenshotModule.saveErrorScreenshot; screenshotModule.saveErrorScreenshot = async () => null; const passwordService = require('../src/services/change_password_service'); const twofaService = require('../src/services/change_2fa_service'); const originalPasswordService = passwordService.changePasswordForAccount; const originalTwofaService = twofaService.change2faForAccount; let passwordCalls = 0; let twofaCalls = 0; passwordService.changePasswordForAccount = async () => { passwordCalls++; return [true, '密码更改成功', 'NEW_PASSWORD']; }; twofaService.change2faForAccount = async () => { twofaCalls++; return [true, '2FA密钥更改成功', 'NEW_2FA_KEY']; }; const passwordStepPath = require.resolve('../src/pipeline/steps/change_password'); const twofaStepPath = require.resolve('../src/pipeline/steps/change_2fa'); delete require.cache[passwordStepPath]; delete require.cache[twofaStepPath]; const changePasswordStep = require(passwordStepPath); const change2faStep = require(twofaStepPath); function makeContext(flags, { skip = true, taskType = 'security_chatgpt', missingAccount = false } = {}) { const state = { email: 'matrix@example.com', password: 'CURRENT_PASSWORD', secret_key: 'CURRENT_2FA_KEY', password_changed: 0, fa_changed: 0, ...flags, }; const statuses = []; const messages = []; const ctx = { account: { ...state }, page: { url: () => 'https://myaccount.google.com/' }, taskType, params: { skipCompletedSecuritySteps: skip }, log: () => {}, results: {}, shouldStop: false, keepBrowserOpen: false, dbWrite: async (name, args) => { if (name === 'getSetting') return args[1]; if (name === 'getAccount') return missingAccount ? null : { ...state }; if (name === 'updateAccountPassword') { state.password = args[1]; state.password_changed = 1; } if (name === 'updateAccount2faKey') { state.secret_key = args[1]; state.fa_changed = 1; } if (name === 'updateAccountFlag') state[args[1]] = args[2]; return true; }, updateStatus: async (_email, status) => statuses.push(status), updateMessage: async (_email, message) => messages.push(message), }; return { ctx, state, statuses, messages }; } async function runCombo(flags, skip = true) { passwordCalls = 0; twofaCalls = 0; const run = makeContext(flags, { skip }); await changePasswordStep(run.ctx); if (!run.ctx.shouldStop) await change2faStep(run.ctx); return { ...run, passwordCalls, twofaCalls }; } (async () => { try { let run = await runCombo({ password_changed: 0, fa_changed: 0 }); assert.deepStrictEqual([run.passwordCalls, run.twofaCalls], [1, 1], '00应执行改密和2FA'); run = await runCombo({ password_changed: 1, fa_changed: 0 }); assert.deepStrictEqual([run.passwordCalls, run.twofaCalls], [0, 1], '10应只执行2FA'); run = await runCombo({ password_changed: 0, fa_changed: 1 }); assert.deepStrictEqual([run.passwordCalls, run.twofaCalls], [1, 0], '01应只执行改密'); run = await runCombo({ password_changed: 1, fa_changed: 1 }); assert.deepStrictEqual([run.passwordCalls, run.twofaCalls], [0, 0], '11应两项都跳过'); assert.strictEqual(run.statuses.at(-1), '未验证已改密和2FA'); assert.match(run.messages.at(-1), /密码和2FA均已修改/); run = await runCombo({ password_changed: 1, fa_changed: 1 }, false); assert.deepStrictEqual([run.passwordCalls, run.twofaCalls], [1, 1], '覆盖模式必须无视完成标记'); passwordCalls = 0; let standalone = makeContext({ password_changed: 1 }, { taskType: 'change_password', skip: true }); await changePasswordStep(standalone.ctx); assert.strictEqual(passwordCalls, 1, '单独改密码必须保持原行为'); twofaCalls = 0; standalone = makeContext({ fa_changed: 1 }, { taskType: 'change_2fa', skip: true }); await change2faStep(standalone.ctx); assert.strictEqual(twofaCalls, 1, '单独改2FA必须保持原行为'); passwordCalls = 0; const missing = makeContext({}, { missingAccount: true }); await assert.rejects( () => changePasswordStep(missing.ctx), /停止以避免重复修改/, '跳过模式读取不到数据库账号时必须停止,不能盲目覆盖', ); assert.strictEqual(passwordCalls, 0); console.log('security_combo_skip_matrix.test.js: PASS'); } finally { passwordService.changePasswordForAccount = originalPasswordService; twofaService.change2faForAccount = originalTwofaService; screenshotModule.saveErrorScreenshot = originalSaveErrorScreenshot; } })().catch(error => { console.error(error); process.exitCode = 1; });