Files
go_bt/tests/security_completion_status.test.js
T

234 lines
10 KiB
JavaScript

const assert = require('assert');
const fs = require('fs');
const path = require('path');
const screenshotPath = require.resolve('../src/utils/screenshot');
const screenshotModule = require(screenshotPath);
const originalSaveErrorScreenshot = screenshotModule.saveErrorScreenshot;
screenshotModule.saveErrorScreenshot = async () => null;
function loadStepWithResult(stepModule, serviceModule, exportName, result) {
const stepPath = require.resolve(stepModule);
const servicePath = require.resolve(serviceModule);
const service = require(servicePath);
const original = service[exportName];
service[exportName] = typeof result === 'function' ? result : async () => result;
delete require.cache[stepPath];
const step = require(stepPath);
service[exportName] = original;
return step;
}
function createContext(flags = {}, accountExtras = {}) {
const state = { password_changed: 0, fa_changed: 0, ...flags };
const statuses = [];
const messages = [];
const dbWrites = [];
const account = { email: 'status@example.com', ...accountExtras };
return {
ctx: {
account,
page: { url: () => 'https://myaccount.google.com/' },
log: () => {},
results: {},
shouldStop: false,
keepBrowserOpen: false,
dbWrite: async (name, args) => {
dbWrites.push({ name, args });
if (name === 'getSetting') return args[1];
if (name === 'getAccount') return { email: account.email, ...state };
if (name === 'updateAccountPassword') state.password_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),
},
statuses,
messages,
dbWrites,
};
}
(async () => {
try {
let change2faStep = loadStepWithResult(
'../src/pipeline/steps/change_2fa',
'../src/services/change_2fa_service',
'change2faForAccount',
[true, '2FA密钥更改成功', 'NEW2FAKEY'],
);
let run = createContext({ password_changed: 1 });
await change2faStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), '未验证已改密和2FA', '已改密账号改2FA成功后应等待ChatGPT验证');
run = createContext({ password_changed: 0 });
await change2faStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), '2FA修改成功', '未改密账号改2FA成功后应显示2FA修改成功');
run = createContext({ password_changed: 1, chatgpt_status: '失败' });
await change2faStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), '未注册成功已改密和2FA', 'ChatGPT已失败的账号完成2FA后应进入新失败状态');
assert.strictEqual(run.messages.length, 0, '后完成2FA时不得覆盖原ChatGPT失败处理结论');
assert.strictEqual(
run.dbWrites.some(write => write.name === 'updateAccountStatusReason' && write.args[1] == null),
false,
'后完成2FA时不得清除原ChatGPT失败原因',
);
let change2faCalls = 0;
change2faStep = loadStepWithResult(
'../src/pipeline/steps/change_2fa',
'../src/services/change_2fa_service',
'change2faForAccount',
async () => {
change2faCalls++;
return [true, '2FA密钥更改成功', 'NEW2FAKEY'];
},
);
run = createContext({ password_changed: 1, fa_changed: 1, secret_key: 'EXISTING2FAKEY' });
run.ctx.taskType = 'security_chatgpt';
run.ctx.params = { skipCompletedSecuritySteps: true };
run.ctx.results.changePassword = { success: true, skipped: true };
await change2faStep(run.ctx);
assert.strictEqual(change2faCalls, 0, '组合任务选择跳过时,已完成的2FA不得再次修改');
assert.strictEqual(run.ctx.results.change2fa.skipped, true);
assert.strictEqual(run.statuses.at(-1), '未验证已改密和2FA');
assert.match(run.messages.at(-1), /密码和2FA均已修改/);
run = createContext({ password_changed: 1, fa_changed: 1 });
run.ctx.taskType = 'security_chatgpt';
run.ctx.params = { skipCompletedSecuritySteps: false };
await change2faStep(run.ctx);
assert.strictEqual(change2faCalls, 1, '组合任务选择覆盖时必须继续修改2FA');
run = createContext({ password_changed: 1, fa_changed: 1 });
run.ctx.taskType = 'change_2fa';
run.ctx.params = { skipCompletedSecuritySteps: true };
await change2faStep(run.ctx);
assert.strictEqual(change2faCalls, 2, '单独更改2FA不得受组合任务跳过选项影响');
change2faStep = loadStepWithResult(
'../src/pipeline/steps/change_2fa',
'../src/services/change_2fa_service',
'change2faForAccount',
[false, '无法提取新的2FA密钥', null],
);
run = createContext({ password_changed: 1 });
await change2faStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), '2FA修改失败', '进入2FA步骤后修改失败应显示2FA修改失败');
const changePasswordStep = loadStepWithResult(
'../src/pipeline/steps/change_password',
'../src/services/change_password_service',
'changePasswordForAccount',
[true, '密码更改成功', 'NewPassword123'],
);
run = createContext({ fa_changed: 1 });
await changePasswordStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), '未验证已改密和2FA', '已改2FA账号改密成功后应等待ChatGPT验证');
run = createContext({ fa_changed: 0 });
await changePasswordStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), '改密成功', '未改2FA账号改密成功后应保持改密成功');
run = createContext({ fa_changed: 1, chatgpt_status: '失败' });
await changePasswordStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), '未注册成功已改密和2FA', 'ChatGPT已失败的账号完成改密后应进入新失败状态');
assert.strictEqual(run.messages.length, 0, '后完成改密时不得覆盖原ChatGPT失败处理结论');
assert.strictEqual(
run.dbWrites.some(write => write.name === 'updateAccountStatusReason' && write.args[1] == null),
false,
'后完成改密时不得清除原ChatGPT失败原因',
);
let changePasswordCalls = 0;
const guardedChangePasswordStep = loadStepWithResult(
'../src/pipeline/steps/change_password',
'../src/services/change_password_service',
'changePasswordForAccount',
async () => {
changePasswordCalls++;
return [true, '密码更改成功', 'NewPassword123'];
},
);
run = createContext({ password_changed: 1, fa_changed: 0, password: 'ExistingPassword' });
run.ctx.taskType = 'security_chatgpt';
run.ctx.params = { skipCompletedSecuritySteps: true };
await guardedChangePasswordStep(run.ctx);
assert.strictEqual(changePasswordCalls, 0, '组合任务选择跳过时,已完成的密码不得再次修改');
assert.strictEqual(run.ctx.results.changePassword.skipped, true);
assert.strictEqual(run.statuses.at(-1), '改密成功');
run = createContext({ password_changed: 1, fa_changed: 0 });
run.ctx.taskType = 'security_chatgpt';
run.ctx.params = { skipCompletedSecuritySteps: false };
await guardedChangePasswordStep(run.ctx);
assert.strictEqual(changePasswordCalls, 1, '组合任务选择覆盖时必须继续修改密码');
run = createContext({ password_changed: 1, fa_changed: 0 });
run.ctx.taskType = 'change_password';
run.ctx.params = { skipCompletedSecuritySteps: true };
await guardedChangePasswordStep(run.ctx);
assert.strictEqual(changePasswordCalls, 2, '单独改密码不得受组合任务跳过选项影响');
run = createContext({ fa_changed: 1 }, { _forcedPasswordChangeHandled: true });
await changePasswordStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), '未验证已改密和2FA', '登录阶段强制改密完成后也应等待ChatGPT验证');
const chatgptRegisterStep = loadStepWithResult(
'../src/pipeline/steps/chatgpt_register',
'../src/services/chatgpt_service',
'loginOrRegisterChatGPT',
{
ok: true,
created: false,
status: '已登录',
session: { user: { email: 'status@example.com' }, token: 'test-session' },
},
);
run = createContext({ password_changed: 1, fa_changed: 1 });
await chatgptRegisterStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), '已验证改密和2FA', '两项安全修改完成后ChatGPT成功才应进入已验证状态');
run = createContext({ password_changed: 1, fa_changed: 0 });
await chatgptRegisterStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), 'ChatGPT成功', '安全修改未全部完成时ChatGPT成功不应误标为已验证两项修改');
const failedChatgptRegisterStep = loadStepWithResult(
'../src/pipeline/steps/chatgpt_register',
'../src/services/chatgpt_service',
'loginOrRegisterChatGPT',
{ ok: false, code: 'CHATGPT_AUTH_ROUTE_ERROR', msg: 'ChatGPT登录失败' },
);
run = createContext({ password_changed: 1, fa_changed: 1 });
await failedChatgptRegisterStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), '未注册成功已改密和2FA', '安全修改均完成后ChatGPT失败应进入新失败状态');
assert.strictEqual(run.ctx.shouldStop, true);
run = createContext({ password_changed: 1, fa_changed: 0 });
await failedChatgptRegisterStep(run.ctx);
assert.strictEqual(run.statuses.at(-1), 'ChatGPT失败', '安全修改未全部完成时仍应保持普通ChatGPT失败');
const root = path.join(__dirname, '..');
const html = fs.readFileSync(path.join(root, 'public', 'index.html'), 'utf8');
const appSource = fs.readFileSync(path.join(root, 'public', 'app.js'), 'utf8');
assert.ok(
(html.match(/value="未注册成功已改密和2FA"/g) || []).length >= 2,
'原地下拉和批量状态下拉都应包含新状态',
);
assert.match(appSource, /_LOGIN_FAIL_STATUSES:[\s\S]{0,160}未注册成功已改密和2FA/,
'新状态必须归入登录失败分类');
assert.match(appSource, /startsWith\('未注册成功已改密和2FA'\)[\s\S]{0,80}status-ChatGPT失败/,
'新状态应使用失败颜色');
console.log('security_completion_status.test.js: PASS');
} finally {
screenshotModule.saveErrorScreenshot = originalSaveErrorScreenshot;
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});