初始化提交:go比特版本精简
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
GOOGLE_APP_PASSWORDS_URL,
|
||||
DEFAULT_APP_NAME,
|
||||
createGmailAppPassword,
|
||||
_private,
|
||||
} = require('../src/services/gmail_app_password_service');
|
||||
|
||||
class FakeLocator {
|
||||
constructor(page, kind, visible) {
|
||||
this.page = page;
|
||||
this.kind = kind;
|
||||
this.visible = visible;
|
||||
}
|
||||
|
||||
first() { return this; }
|
||||
nth() { return this; }
|
||||
async count() { return 1; }
|
||||
async isVisible() { return this.visible; }
|
||||
async fill(value) {
|
||||
assert.strictEqual(this.kind, 'appName');
|
||||
this.page.appName = value;
|
||||
}
|
||||
async click() {
|
||||
if (this.kind === 'create') this.page.created = true;
|
||||
if (this.kind === 'done') this.page.doneClicked = true;
|
||||
}
|
||||
}
|
||||
|
||||
class FakePage {
|
||||
constructor({ unavailable = false, reauth = false, language = 'en' } = {}) {
|
||||
this.currentUrl = 'about:blank';
|
||||
this.unavailable = unavailable;
|
||||
this.reauth = reauth;
|
||||
this.language = language;
|
||||
this.created = false;
|
||||
this.doneClicked = false;
|
||||
this.appName = '';
|
||||
}
|
||||
|
||||
url() { return this.currentUrl; }
|
||||
async goto(url) {
|
||||
this.currentUrl = this.reauth
|
||||
? 'https://accounts.google.com/signin/v2/challenge/pwd'
|
||||
: url;
|
||||
this.reauth = false;
|
||||
}
|
||||
async waitForLoadState() {}
|
||||
async waitForTimeout() {}
|
||||
async evaluate() {
|
||||
if (this.unavailable) {
|
||||
return {
|
||||
dialogText: '',
|
||||
bodyText: 'App passwords are not available for your account.',
|
||||
readonlyValues: [],
|
||||
};
|
||||
}
|
||||
if (this.created) {
|
||||
return {
|
||||
dialogText: 'Your app password\nabcd efgh ijkl mnop\nUse it in your app',
|
||||
bodyText: 'Your app password',
|
||||
readonlyValues: [],
|
||||
};
|
||||
}
|
||||
return { dialogText: '', bodyText: 'App passwords', readonlyValues: [] };
|
||||
}
|
||||
locator(selector) {
|
||||
const onForm = this.currentUrl === GOOGLE_APP_PASSWORDS_URL && !this.unavailable;
|
||||
if (!selector.includes('following::button') && selector.includes('input')
|
||||
&& (selector.includes('App name') || selector.includes('type="text"'))) {
|
||||
return new FakeLocator(this, 'appName', onForm && !this.created);
|
||||
}
|
||||
if ((this.language === 'en' && selector.includes('Create'))
|
||||
|| (this.language === 'vi' && selector.includes('Tạo'))
|
||||
|| (this.language === 'other' && selector.includes('following::button[1]'))) {
|
||||
return new FakeLocator(this, 'create', onForm && !!this.appName && !this.created);
|
||||
}
|
||||
if ((this.language === 'en' && selector.includes('Done'))
|
||||
|| (this.language === 'vi' && selector.includes('Xong'))) {
|
||||
return new FakeLocator(this, 'done', this.created);
|
||||
}
|
||||
return new FakeLocator(this, 'missing', false);
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
assert.strictEqual(DEFAULT_APP_NAME, 'zhaoqiaoqing2001');
|
||||
assert.strictEqual(_private.normalizeAppPassword('abcd efgh ijkl mnop'), 'abcdefghijklmnop');
|
||||
assert.strictEqual(_private.normalizeAppPassword('too-short'), null);
|
||||
assert.strictEqual(_private.extractAppPasswordFromSnapshot({
|
||||
dialogText: 'Your app password: abcd efgh ijkl mnop',
|
||||
readonlyValues: [],
|
||||
}), 'abcdefghijklmnop');
|
||||
assert.strictEqual(_private.extractAppPasswordFromSnapshot({
|
||||
dialogText: 'Application name: zhaoqiaoqing2001',
|
||||
readonlyValues: [],
|
||||
}), null, '不得把16位应用名称误识别成密码');
|
||||
|
||||
{
|
||||
const page = new FakePage();
|
||||
const result = await createGmailAppPassword(page, {
|
||||
email: 'mail@example.com',
|
||||
password: 'not-used',
|
||||
});
|
||||
assert.strictEqual(result.ok, true);
|
||||
assert.strictEqual(result.appPassword, 'abcdefghijklmnop');
|
||||
assert.strictEqual(page.appName, DEFAULT_APP_NAME);
|
||||
assert.strictEqual(page.doneClicked, true);
|
||||
}
|
||||
|
||||
{
|
||||
const page = new FakePage({ reauth: true });
|
||||
let verificationCalled = false;
|
||||
const result = await createGmailAppPassword(page, {
|
||||
email: 'mail@example.com',
|
||||
password: 'not-logged',
|
||||
}, () => {}, {
|
||||
continueGoogleVerificationFn: async currentPage => {
|
||||
verificationCalled = true;
|
||||
currentPage.currentUrl = GOOGLE_APP_PASSWORDS_URL;
|
||||
return [true, '登录成功'];
|
||||
},
|
||||
});
|
||||
assert.strictEqual(verificationCalled, true);
|
||||
assert.strictEqual(result.ok, true);
|
||||
}
|
||||
|
||||
{
|
||||
// Google 会根据账号语言显示越南语按钮:Tạo(创建)/ Xong(完成)。
|
||||
const page = new FakePage({ language: 'vi' });
|
||||
const result = await createGmailAppPassword(page, {
|
||||
email: 'mail@example.com',
|
||||
password: 'not-used',
|
||||
});
|
||||
assert.strictEqual(result.ok, true);
|
||||
assert.strictEqual(page.doneClicked, true);
|
||||
}
|
||||
|
||||
{
|
||||
// 未知语言不依赖按钮文字,使用应用名称输入框后的首个按钮。
|
||||
const page = new FakePage({ language: 'other' });
|
||||
const result = await createGmailAppPassword(page, {
|
||||
email: 'mail@example.com',
|
||||
password: 'not-used',
|
||||
});
|
||||
assert.strictEqual(result.ok, true);
|
||||
}
|
||||
|
||||
{
|
||||
// 回归真实故障:第一次 TOTP 验证后回到账户首页,再次打开应用密码页时
|
||||
// Google 又要求 TOTP。流程必须继续原地验证,不能等待应用名称框超时。
|
||||
const page = new FakePage();
|
||||
let appPasswordNavigationCount = 0;
|
||||
page.goto = async url => {
|
||||
if (url === GOOGLE_APP_PASSWORDS_URL) {
|
||||
appPasswordNavigationCount++;
|
||||
page.currentUrl = 'https://accounts.google.com/v3/signin/challenge/totp?continue=apppasswords';
|
||||
return;
|
||||
}
|
||||
page.currentUrl = url;
|
||||
};
|
||||
let verificationCount = 0;
|
||||
const result = await createGmailAppPassword(page, {
|
||||
email: 'mail@example.com',
|
||||
password: 'not-used',
|
||||
secret: 'JBSWY3DPEHPK3PXP',
|
||||
}, () => {}, {
|
||||
ensureLoggedInFn: async () => {
|
||||
throw new Error('TOTP challenge 不应跳回普通登录入口');
|
||||
},
|
||||
continueGoogleVerificationFn: async currentPage => {
|
||||
verificationCount++;
|
||||
currentPage.currentUrl = verificationCount === 1
|
||||
? 'https://myaccount.google.com/'
|
||||
: GOOGLE_APP_PASSWORDS_URL;
|
||||
return [true, '验证成功'];
|
||||
},
|
||||
});
|
||||
assert.strictEqual(result.ok, true);
|
||||
assert.strictEqual(verificationCount, 2);
|
||||
assert.strictEqual(appPasswordNavigationCount, 2);
|
||||
}
|
||||
|
||||
{
|
||||
const page = new FakePage({ unavailable: true });
|
||||
const result = await createGmailAppPassword(page, { email: 'mail@example.com', password: 'x' });
|
||||
assert.strictEqual(result.ok, false);
|
||||
assert.match(result.msg, /无法使用应用专用密码/);
|
||||
}
|
||||
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gmail-app-password-'));
|
||||
let database = null;
|
||||
try {
|
||||
const Config = require('../src/core/config');
|
||||
Config.DATA_DIR = tempDir;
|
||||
Config.DB_PATH = path.join(tempDir, 'accounts.db');
|
||||
const db = require('../src/core/database');
|
||||
database = db;
|
||||
db.initDB();
|
||||
db.upsertAccount({ email: 'mail@example.com', password: 'password' });
|
||||
assert.strictEqual(db.hasGmailAppPassword('mail@example.com'), false);
|
||||
assert.strictEqual(db.upsertGmailAppPassword('mail@example.com', 'abcd efgh ijkl mnop'), true);
|
||||
assert.strictEqual(db.hasGmailAppPassword('MAIL@example.com'), true);
|
||||
const saved = db.getGmailAppPassword('mail@example.com');
|
||||
assert.strictEqual(saved.email, 'mail@example.com');
|
||||
assert.strictEqual(saved.app_password, 'abcdefghijklmnop');
|
||||
assert.strictEqual(db.getAllAccounts()[0].gmail_app_password, 'abcdefghijklmnop');
|
||||
db.upsertGmailAppPassword('mail@example.com', 'qrst uvwx yzab cdef');
|
||||
assert.strictEqual(db.getGmailAppPassword('mail@example.com').app_password, 'qrstuvwxyzabcdef');
|
||||
assert.throws(() => db.upsertGmailAppPassword('mail@example.com', 'invalid'), /16 位/);
|
||||
} finally {
|
||||
if (database) database.getDb().close();
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
console.log('gmail_app_password_service.test.js: PASS');
|
||||
})().catch(error => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user