初始化提交:go比特版本精简
This commit is contained in:
@@ -0,0 +1,354 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const { _private: loginPrivate } = require('../src/services/login_service');
|
||||
const { _private: chatgptPrivate } = require('../src/services/chatgpt_service');
|
||||
const {
|
||||
handleIdentityVerification,
|
||||
handleChallengeSelection,
|
||||
inputCurrent2fa,
|
||||
_private: commonAuthPrivate,
|
||||
} = require('../src/services/common_auth');
|
||||
|
||||
function emptyLocator() {
|
||||
return {
|
||||
count: async () => 0,
|
||||
nth: () => { throw new Error('empty locator'); },
|
||||
};
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const securityCodeText = [
|
||||
'Get a code to sign in',
|
||||
'To get your security code, go to g.co/sc in a new browser window',
|
||||
'Enter security code',
|
||||
'Try another way',
|
||||
].join('\n');
|
||||
|
||||
assert.strictEqual(loginPrivate.hasSecurityCodePageText(securityCodeText), true);
|
||||
assert.strictEqual(commonAuthPrivate.hasSecurityCodePageText(securityCodeText), true);
|
||||
assert.strictEqual(
|
||||
loginPrivate.hasSecurityCodePageText('Get a verification code from the Google Authenticator app'),
|
||||
false,
|
||||
'Authenticator 页面不能被识别为 g.co 安全码页面',
|
||||
);
|
||||
|
||||
const phoneTabletOnlyText = [
|
||||
"Verify it's you",
|
||||
'Choose a way to verify',
|
||||
"Use your phone or tablet to get a security code (even if it's offline)",
|
||||
].join('\n');
|
||||
{
|
||||
const page = {
|
||||
url: () => 'https://accounts.google.com/v3/signin/challenge/selection',
|
||||
locator: selector => selector === 'body'
|
||||
? { innerText: async () => phoneTabletOnlyText }
|
||||
: emptyLocator(),
|
||||
};
|
||||
assert.strictEqual(
|
||||
await commonAuthPrivate.detectPhoneTabletOnlyChallenge(page),
|
||||
true,
|
||||
'只有手机/平板安全码时应立即识别',
|
||||
);
|
||||
assert.strictEqual(await loginPrivate.detectPhoneTabletOnlyChallenge(page), true);
|
||||
}
|
||||
|
||||
{
|
||||
let optionsSettled = false;
|
||||
const makeMissing = () => ({
|
||||
count: async () => 0,
|
||||
nth() { return this; },
|
||||
first() { return this; },
|
||||
or() { return this; },
|
||||
waitFor: async () => { throw new Error('not visible'); },
|
||||
isVisible: async () => false,
|
||||
textContent: async () => '',
|
||||
});
|
||||
const page = {
|
||||
url: () => 'https://accounts.google.com/v3/signin/challenge/selection',
|
||||
locator: selector => {
|
||||
if (selector === 'body') {
|
||||
return {
|
||||
...makeMissing(),
|
||||
innerText: async () => optionsSettled
|
||||
? phoneTabletOnlyText
|
||||
: "Verify it's you\nChoose a way to verify",
|
||||
};
|
||||
}
|
||||
return makeMissing();
|
||||
},
|
||||
waitForLoadState: async () => {},
|
||||
waitForTimeout: async () => { optionsSettled = true; },
|
||||
};
|
||||
const result = await handleIdentityVerification(page, {}, () => {});
|
||||
assert.deepStrictEqual(
|
||||
result,
|
||||
[false, '需手机/平板验证', 'PHONE_TABLET_VERIFY'],
|
||||
'selection 首次正文未稳定时,等待后必须复检唯一手机/平板方式',
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const page = {
|
||||
url: () => 'https://accounts.google.com/v3/signin/challenge/selection',
|
||||
locator: selector => selector === 'body'
|
||||
? { innerText: async () => `${phoneTabletOnlyText}\nGet a verification code from the Google Authenticator app` }
|
||||
: emptyLocator(),
|
||||
};
|
||||
assert.strictEqual(
|
||||
await commonAuthPrivate.detectPhoneTabletOnlyChallenge(page),
|
||||
false,
|
||||
'仍有Authenticator选项时不得误判为只能手机/平板验证',
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const heading = {
|
||||
first() { return this; },
|
||||
textContent: async () => "Verify it's you",
|
||||
};
|
||||
const page = {
|
||||
url: () => 'https://accounts.google.com/v3/signin/challenge/ootp',
|
||||
locator: selector => {
|
||||
if (selector === 'body') return { innerText: async () => phoneTabletOnlyText };
|
||||
if (selector.startsWith('h1#headingText')) return heading;
|
||||
return emptyLocator();
|
||||
},
|
||||
};
|
||||
assert.strictEqual(await commonAuthPrivate.detectVerifyItsYouOotp(page), true);
|
||||
assert.strictEqual(
|
||||
await commonAuthPrivate.detectPhoneTabletOnlyChallenge(page),
|
||||
true,
|
||||
'OOTP 页面只剩手机/平板安全码时也应直接识别',
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const makeMissing = () => ({
|
||||
count: async () => 0,
|
||||
nth() { return this; },
|
||||
first() { return this; },
|
||||
or() { return this; },
|
||||
waitFor: async () => { throw new Error('not visible'); },
|
||||
isVisible: async () => false,
|
||||
textContent: async () => '',
|
||||
});
|
||||
const heading = {
|
||||
...makeMissing(),
|
||||
textContent: async () => "Verify it's you",
|
||||
};
|
||||
const page = {
|
||||
url: () => 'https://accounts.google.com/v3/signin/challenge/ootp',
|
||||
locator: selector => {
|
||||
if (selector === 'body') return { ...makeMissing(), innerText: async () => phoneTabletOnlyText };
|
||||
if (selector.startsWith('h1#headingText')) return heading;
|
||||
return makeMissing();
|
||||
},
|
||||
waitForLoadState: async () => {},
|
||||
waitForTimeout: async () => {},
|
||||
};
|
||||
const result = await handleIdentityVerification(page, {}, () => {});
|
||||
assert.deepStrictEqual(
|
||||
result,
|
||||
[false, '需手机/平板验证', 'PHONE_TABLET_VERIFY'],
|
||||
'Verify it’s you + 唯一手机/平板方式必须返回明确机器分类',
|
||||
);
|
||||
const oauthResult = await chatgptPrivate.handleGoogleOAuth(page, {}, () => {});
|
||||
assert.strictEqual(oauthResult.code, 'PHONE_TABLET_VERIFY', 'ChatGPT OAuth 必须保留手机/平板分类');
|
||||
}
|
||||
|
||||
{
|
||||
const makeMissing = () => ({
|
||||
count: async () => 0,
|
||||
nth() { return this; },
|
||||
first() { return this; },
|
||||
or() { return this; },
|
||||
waitFor: async () => { throw new Error('not visible'); },
|
||||
isVisible: async () => false,
|
||||
textContent: async () => '',
|
||||
});
|
||||
const heading = {
|
||||
...makeMissing(),
|
||||
textContent: async () => "Verify it's you",
|
||||
};
|
||||
const page = {
|
||||
url: () => 'https://accounts.google.com/v3/signin/challenge/ootp',
|
||||
locator: selector => {
|
||||
if (selector === 'body') return { ...makeMissing(), innerText: async () => "Verify it's you" };
|
||||
if (selector.startsWith('h1#headingText')) return heading;
|
||||
return makeMissing();
|
||||
},
|
||||
waitForLoadState: async () => {},
|
||||
waitForTimeout: async () => {},
|
||||
};
|
||||
const result = await handleIdentityVerification(page, {}, () => {});
|
||||
assert.strictEqual(result[0], false);
|
||||
assert.strictEqual(result[2], 'VERIFY_ITS_YOU');
|
||||
assert.match(result[1], /Verify it.s you/);
|
||||
}
|
||||
|
||||
{
|
||||
const page = {
|
||||
url: () => 'https://accounts.google.com/v3/signin/challenge/custom',
|
||||
locator: selector => selector === 'body'
|
||||
? { innerText: async () => securityCodeText }
|
||||
: emptyLocator(),
|
||||
};
|
||||
assert.strictEqual(await loginPrivate.isSecurityCodeChallenge(page), true);
|
||||
assert.strictEqual(await commonAuthPrivate.isSecurityCodeChallenge(page), true);
|
||||
}
|
||||
|
||||
{
|
||||
const page = {
|
||||
url: () => 'https://accounts.google.com/signin/challenge/selection',
|
||||
locator: selector => selector === 'body'
|
||||
? { innerText: async () => `Choose how you want to sign in\n${securityCodeText}\nGoogle Authenticator` }
|
||||
: emptyLocator(),
|
||||
};
|
||||
assert.strictEqual(
|
||||
await loginPrivate.isSecurityCodeChallenge(page),
|
||||
false,
|
||||
'验证方式列表即使展示 g.co 选项,也不能被当作当前安全码页面',
|
||||
);
|
||||
assert.strictEqual(await commonAuthPrivate.isSecurityCodeChallenge(page), false);
|
||||
}
|
||||
|
||||
{
|
||||
let bodyText = securityCodeText;
|
||||
const tryAnotherButton = {
|
||||
isVisible: async () => true,
|
||||
scrollIntoViewIfNeeded: async () => {},
|
||||
click: async () => {
|
||||
bodyText = 'Choose how you want to sign in\nGoogle Authenticator';
|
||||
},
|
||||
evaluate: async () => true,
|
||||
};
|
||||
const page = {
|
||||
url: () => 'https://accounts.google.com/v3/signin/challenge/custom',
|
||||
locator: selector => {
|
||||
if (selector === 'body') return { innerText: async () => bodyText };
|
||||
if (selector === 'text="Try another way"') {
|
||||
return { count: async () => 1, nth: () => tryAnotherButton };
|
||||
}
|
||||
return emptyLocator();
|
||||
},
|
||||
waitForTimeout: async () => {},
|
||||
};
|
||||
const result = await loginPrivate.tryLeaveAlternateChallenge(page, () => {}, 50);
|
||||
assert.strictEqual(result.clicked, true);
|
||||
assert.strictEqual(result.moved, true, '同一 URL 内切换到验证方式列表也应视为成功离开');
|
||||
}
|
||||
|
||||
{
|
||||
const attemptedSelectors = [];
|
||||
let currentUrl = 'https://accounts.google.com/signin/challenge/selection';
|
||||
const authButton = {
|
||||
isVisible: async () => true,
|
||||
scrollIntoViewIfNeeded: async () => {},
|
||||
click: async () => {
|
||||
currentUrl = 'https://accounts.google.com/signin/challenge/totp';
|
||||
},
|
||||
evaluate: async () => true,
|
||||
};
|
||||
const page = {
|
||||
url: () => currentUrl,
|
||||
locator: selector => {
|
||||
attemptedSelectors.push(selector);
|
||||
if (selector === 'li:has-text("Get a verification code from the Google Authenticator app")') {
|
||||
return { count: async () => 1, nth: () => authButton };
|
||||
}
|
||||
return emptyLocator();
|
||||
},
|
||||
waitForTimeout: async () => {},
|
||||
waitForLoadState: async () => {},
|
||||
};
|
||||
assert.strictEqual(await handleChallengeSelection(page, () => {}), true);
|
||||
assert.strictEqual(
|
||||
attemptedSelectors.includes('div[data-challengetype="6"]'),
|
||||
false,
|
||||
'不稳定的 challenge type 不能再作为 Authenticator 兜底',
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const invalidResult = await inputCurrent2fa(
|
||||
{ url: () => 'https://accounts.google.com/signin/challenge/totp' },
|
||||
'1//not-a-totp-secret',
|
||||
() => {},
|
||||
);
|
||||
assert.strictEqual(invalidResult.ok, false);
|
||||
assert.match(invalidResult.msg, /2FA密钥格式不正确|Base32/);
|
||||
}
|
||||
|
||||
{
|
||||
let filledCode = '';
|
||||
const input = {
|
||||
first() { return this; },
|
||||
waitFor: async () => {},
|
||||
fill: async value => { if (value) filledCode = value; },
|
||||
press: async () => {},
|
||||
};
|
||||
const missingButton = {
|
||||
first() { return this; },
|
||||
waitFor: async () => { throw new Error('button unavailable'); },
|
||||
};
|
||||
const page = {
|
||||
url: () => 'https://accounts.google.com/signin/challenge/totp',
|
||||
locator: selector => selector.includes('input[id="totpPin"]') ? input : missingButton,
|
||||
waitForLoadState: async () => {},
|
||||
};
|
||||
const result = await inputCurrent2fa(page, 'JBSWY3DPEHPK3PXP', () => {});
|
||||
assert.strictEqual(result.ok, true, '页面只有totpPin输入框时也应能提交2FA');
|
||||
assert.match(filledCode, /^\d{6}$/);
|
||||
}
|
||||
|
||||
{
|
||||
const alert = {
|
||||
isVisible: async () => true,
|
||||
innerText: async () => 'Wrong code. Try again.',
|
||||
};
|
||||
const page = {
|
||||
locator: selector => {
|
||||
if (selector.startsWith('[role="alert"]')) {
|
||||
return { count: async () => 1, nth: () => alert };
|
||||
}
|
||||
if (selector === 'body') return { innerText: async () => 'Wrong code. Try again.' };
|
||||
return emptyLocator();
|
||||
},
|
||||
};
|
||||
assert.strictEqual(await commonAuthPrivate.detectTotpRejection(page), 'Wrong code. Try again.');
|
||||
assert.strictEqual(await loginPrivate.detectTotpRejection(page), 'Wrong code. Try again.');
|
||||
}
|
||||
|
||||
{
|
||||
let secondClicked = false;
|
||||
const failingButton = {
|
||||
isVisible: async () => true,
|
||||
scrollIntoViewIfNeeded: async () => {},
|
||||
click: async () => { throw new Error('intercepted'); },
|
||||
evaluate: async () => { throw new Error('intercepted'); },
|
||||
};
|
||||
const workingButton = {
|
||||
isVisible: async () => true,
|
||||
scrollIntoViewIfNeeded: async () => {},
|
||||
click: async () => { secondClicked = true; },
|
||||
evaluate: async () => true,
|
||||
};
|
||||
const page = {
|
||||
locator: selector => {
|
||||
if (selector === 'first') return { count: async () => 1, nth: () => failingButton };
|
||||
if (selector === 'second') return { count: async () => 1, nth: () => workingButton };
|
||||
return emptyLocator();
|
||||
},
|
||||
};
|
||||
const selector = await commonAuthPrivate.clickFirstVisible(page, ['first', 'second'], 20);
|
||||
assert.strictEqual(selector, 'second');
|
||||
assert.strictEqual(secondClicked, true, '点击失败后必须继续尝试下一个可点击元素');
|
||||
}
|
||||
|
||||
console.log('security_code_challenge.test.js: PASS');
|
||||
})().catch(error => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user