163 lines
5.0 KiB
JavaScript
163 lines
5.0 KiB
JavaScript
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 (payload, value) => {
|
|
assert.deepStrictEqual(payload, { email: 'user@example.com', password: '' });
|
|
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();
|
|
}
|
|
});
|