feat: 更新 OAuth 流程中的日志信息,增强密码缺失时的处理逻辑,添加相关测试
This commit is contained in:
+3
-5
@@ -6600,11 +6600,10 @@ async function runPreStep6CookieCleanup() {
|
||||
|
||||
async function refreshOAuthUrlBeforeStep6(state) {
|
||||
if (state?.contributionModeExpected && !state?.contributionMode) {
|
||||
throw new Error('步骤 7:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA 面板。请重新进入贡献模式后再点击自动。');
|
||||
throw new Error('步骤 7:当前自动流程预期使用贡献模式,但运行态 contributionMode 已丢失,已阻止回退到普通 CPA / SUB2API 链路。请重新进入贡献模式后再点击自动。');
|
||||
}
|
||||
if (state?.contributionMode && contributionOAuthManager?.startContributionFlow) {
|
||||
await addLog('步骤 7:contributionMode=true,正在通过公开贡献接口申请 OAuth 链接...', 'info');
|
||||
await addLog('步骤 7:贡献模式正在申请贡献登录地址...');
|
||||
await addLog('步骤 7:contributionMode=true,走公开贡献接口,正在申请 OAuth 登录地址...', 'info');
|
||||
const contributionState = await contributionOAuthManager.startContributionFlow({
|
||||
nickname: state.email,
|
||||
openAuthTab: false,
|
||||
@@ -6617,8 +6616,7 @@ async function refreshOAuthUrlBeforeStep6(state) {
|
||||
await handleStepData(1, { oauthUrl });
|
||||
return oauthUrl;
|
||||
}
|
||||
await addLog(`步骤 7:正在刷新登录用的 ${getPanelModeLabel(state)} OAuth 链接...`);
|
||||
await addLog(`步骤 7:contributionMode=${Boolean(state?.contributionMode)},当前将回退到 ${getPanelModeLabel(state)} 面板刷新 OAuth。`, 'warn');
|
||||
await addLog(`步骤 7:contributionMode=false,走普通 CPA / SUB2API 链路(当前面板:${getPanelModeLabel(state)}),正在刷新 OAuth 登录地址...`, 'info');
|
||||
console.log(LOG_PREFIX, '[refreshOAuthUrlBeforeStep6] requesting fresh OAuth directly from panel');
|
||||
const refreshResult = await requestOAuthUrlFromPanel(state, { logLabel: '步骤 7' });
|
||||
await handleStepData(1, refreshResult);
|
||||
|
||||
+10
-2
@@ -1913,10 +1913,18 @@ async function step6SwitchToOneTimeCodeLogin(snapshot) {
|
||||
|
||||
async function step6LoginFromPasswordPage(payload, snapshot) {
|
||||
const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
|
||||
const hasPassword = Boolean(String(payload?.password || '').trim());
|
||||
|
||||
if (currentSnapshot.passwordInput) {
|
||||
if (!payload.password) {
|
||||
throw new Error('登录时缺少密码,步骤 7 无法继续。');
|
||||
if (!hasPassword) {
|
||||
if (currentSnapshot.switchTrigger) {
|
||||
log('步骤 7:当前未提供密码,改走一次性验证码登录。', 'warn');
|
||||
return step6SwitchToOneTimeCodeLogin(currentSnapshot);
|
||||
}
|
||||
|
||||
return createStep6RecoverableResult('missing_password_and_one_time_code_trigger', currentSnapshot, {
|
||||
message: '登录时未提供密码,且当前页面没有可用的一次性验证码登录入口。',
|
||||
});
|
||||
}
|
||||
|
||||
log('步骤 7:已进入密码页,准备填写密码...');
|
||||
|
||||
@@ -458,8 +458,7 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
|
||||
assert.equal(oauthUrl, 'https://auth.example.com/oauth?state=oauth-state-001');
|
||||
assert.deepStrictEqual(calls, [
|
||||
{ type: 'log', message: '步骤 7:contributionMode=true,正在通过公开贡献接口申请 OAuth 链接...' },
|
||||
{ type: 'log', message: '步骤 7:贡献模式正在申请贡献登录地址...' },
|
||||
{ type: 'log', message: '步骤 7:contributionMode=true,走公开贡献接口,正在申请 OAuth 登录地址...' },
|
||||
{
|
||||
type: 'contribution',
|
||||
options: {
|
||||
@@ -488,6 +487,63 @@ return { refreshOAuthUrlBeforeStep6 };
|
||||
delete globalThis.LOG_PREFIX;
|
||||
});
|
||||
|
||||
test('refreshOAuthUrlBeforeStep6 logs the normal CPA/SUB2API path explicitly when contributionMode=false', async () => {
|
||||
const bundle = extractFunction(backgroundSource, 'refreshOAuthUrlBeforeStep6');
|
||||
const calls = [];
|
||||
|
||||
const api = new Function(`
|
||||
${bundle}
|
||||
return { refreshOAuthUrlBeforeStep6 };
|
||||
`)();
|
||||
|
||||
globalThis.addLog = async (message) => {
|
||||
calls.push({ type: 'log', message });
|
||||
};
|
||||
globalThis.contributionOAuthManager = {
|
||||
async startContributionFlow() {
|
||||
calls.push({ type: 'contribution' });
|
||||
return {
|
||||
contributionAuthUrl: 'https://auth.example.com/oauth?state=unexpected',
|
||||
};
|
||||
},
|
||||
};
|
||||
globalThis.handleStepData = async (step, payload) => {
|
||||
calls.push({ type: 'step', step, payload });
|
||||
};
|
||||
globalThis.getPanelModeLabel = () => 'SUB2API';
|
||||
globalThis.requestOAuthUrlFromPanel = async () => {
|
||||
calls.push({ type: 'panel' });
|
||||
return { oauthUrl: 'https://panel.example.com/oauth' };
|
||||
};
|
||||
globalThis.LOG_PREFIX = '[test]';
|
||||
|
||||
const oauthUrl = await api.refreshOAuthUrlBeforeStep6({
|
||||
contributionMode: false,
|
||||
panelMode: 'sub2api',
|
||||
email: 'user@example.com',
|
||||
});
|
||||
|
||||
assert.equal(oauthUrl, 'https://panel.example.com/oauth');
|
||||
assert.deepStrictEqual(calls, [
|
||||
{ type: 'log', message: '步骤 7:contributionMode=false,走普通 CPA / SUB2API 链路(当前面板:SUB2API),正在刷新 OAuth 登录地址...' },
|
||||
{ type: 'panel' },
|
||||
{
|
||||
type: 'step',
|
||||
step: 1,
|
||||
payload: {
|
||||
oauthUrl: 'https://panel.example.com/oauth',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
delete globalThis.addLog;
|
||||
delete globalThis.contributionOAuthManager;
|
||||
delete globalThis.handleStepData;
|
||||
delete globalThis.getPanelModeLabel;
|
||||
delete globalThis.requestOAuthUrlFromPanel;
|
||||
delete globalThis.LOG_PREFIX;
|
||||
});
|
||||
|
||||
test('executeStep10 blocks silent fallback when contributionModeExpected=true but contributionMode=false', async () => {
|
||||
const bundle = extractFunction(backgroundSource, 'executeStep10');
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('content/signup-page.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
const start = markers
|
||||
.map((marker) => source.indexOf(marker))
|
||||
.find((index) => index >= 0);
|
||||
if (start < 0) {
|
||||
throw new Error(`missing function ${name}`);
|
||||
}
|
||||
|
||||
let parenDepth = 0;
|
||||
let signatureEnded = false;
|
||||
let braceStart = -1;
|
||||
for (let i = start; i < source.length; i += 1) {
|
||||
const ch = source[i];
|
||||
if (ch === '(') {
|
||||
parenDepth += 1;
|
||||
} else if (ch === ')') {
|
||||
parenDepth -= 1;
|
||||
if (parenDepth === 0) {
|
||||
signatureEnded = true;
|
||||
}
|
||||
} else if (ch === '{' && signatureEnded) {
|
||||
braceStart = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (braceStart < 0) {
|
||||
throw new Error(`missing body for function ${name}`);
|
||||
}
|
||||
|
||||
let depth = 0;
|
||||
let end = braceStart;
|
||||
for (; end < source.length; end += 1) {
|
||||
const ch = source[end];
|
||||
if (ch === '{') depth += 1;
|
||||
if (ch === '}') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
end += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return source.slice(start, end);
|
||||
}
|
||||
|
||||
const bundle = extractFunction('step6LoginFromPasswordPage');
|
||||
|
||||
function createApi() {
|
||||
return new Function(`
|
||||
${bundle}
|
||||
return { step6LoginFromPasswordPage };
|
||||
`)();
|
||||
}
|
||||
|
||||
function cleanupGlobals() {
|
||||
delete globalThis.normalizeStep6Snapshot;
|
||||
delete globalThis.inspectLoginAuthState;
|
||||
delete globalThis.log;
|
||||
delete globalThis.step6SwitchToOneTimeCodeLogin;
|
||||
delete globalThis.createStep6RecoverableResult;
|
||||
delete globalThis.fillInput;
|
||||
delete globalThis.humanPause;
|
||||
delete globalThis.sleep;
|
||||
delete globalThis.triggerLoginSubmitAction;
|
||||
delete globalThis.waitForStep6PasswordSubmitTransition;
|
||||
}
|
||||
|
||||
test('step6LoginFromPasswordPage switches to one-time-code login when password is missing but switch trigger exists', async () => {
|
||||
const api = createApi();
|
||||
const logs = [];
|
||||
const snapshot = {
|
||||
state: 'password_page',
|
||||
passwordInput: { id: 'password' },
|
||||
switchTrigger: { id: 'otp' },
|
||||
};
|
||||
|
||||
globalThis.normalizeStep6Snapshot = (value) => value;
|
||||
globalThis.inspectLoginAuthState = () => snapshot;
|
||||
globalThis.log = (message, level = 'info') => {
|
||||
logs.push({ message, level });
|
||||
};
|
||||
globalThis.step6SwitchToOneTimeCodeLogin = async (value) => {
|
||||
assert.strictEqual(value, snapshot);
|
||||
return { step6Outcome: 'success', via: 'switch_to_one_time_code_login' };
|
||||
};
|
||||
globalThis.createStep6RecoverableResult = () => {
|
||||
throw new Error('should not create recoverable result when switch trigger exists');
|
||||
};
|
||||
globalThis.fillInput = () => {
|
||||
throw new Error('should not fill password when password is missing');
|
||||
};
|
||||
globalThis.humanPause = async () => {};
|
||||
globalThis.sleep = async () => {};
|
||||
globalThis.triggerLoginSubmitAction = async () => {};
|
||||
globalThis.waitForStep6PasswordSubmitTransition = async () => {
|
||||
throw new Error('should not submit password when password is missing');
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await api.step6LoginFromPasswordPage({ email: 'user@example.com', password: '' }, snapshot);
|
||||
|
||||
assert.deepStrictEqual(result, { step6Outcome: 'success', via: 'switch_to_one_time_code_login' });
|
||||
assert.deepStrictEqual(logs, [
|
||||
{ message: '步骤 7:当前未提供密码,改走一次性验证码登录。', level: 'warn' },
|
||||
]);
|
||||
} finally {
|
||||
cleanupGlobals();
|
||||
}
|
||||
});
|
||||
|
||||
test('step6LoginFromPasswordPage returns a recoverable result when password is missing and no one-time-code trigger exists', async () => {
|
||||
const api = createApi();
|
||||
const snapshot = {
|
||||
state: 'password_page',
|
||||
passwordInput: { id: 'password' },
|
||||
switchTrigger: null,
|
||||
};
|
||||
|
||||
globalThis.normalizeStep6Snapshot = (value) => value;
|
||||
globalThis.inspectLoginAuthState = () => snapshot;
|
||||
globalThis.log = () => {};
|
||||
globalThis.step6SwitchToOneTimeCodeLogin = async () => {
|
||||
throw new Error('should not switch without a one-time-code trigger');
|
||||
};
|
||||
globalThis.createStep6RecoverableResult = (reason, stateSnapshot, details) => ({
|
||||
step6Outcome: 'recoverable',
|
||||
reason,
|
||||
stateSnapshot,
|
||||
...details,
|
||||
});
|
||||
globalThis.fillInput = () => {
|
||||
throw new Error('should not fill password when password is missing');
|
||||
};
|
||||
globalThis.humanPause = async () => {};
|
||||
globalThis.sleep = async () => {};
|
||||
globalThis.triggerLoginSubmitAction = async () => {};
|
||||
globalThis.waitForStep6PasswordSubmitTransition = async () => {
|
||||
throw new Error('should not submit password when password is missing');
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await api.step6LoginFromPasswordPage({ email: 'user@example.com', password: '' }, snapshot);
|
||||
|
||||
assert.deepStrictEqual(result, {
|
||||
step6Outcome: 'recoverable',
|
||||
reason: 'missing_password_and_one_time_code_trigger',
|
||||
stateSnapshot: snapshot,
|
||||
message: '登录时未提供密码,且当前页面没有可用的一次性验证码登录入口。',
|
||||
});
|
||||
} finally {
|
||||
cleanupGlobals();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user