feat: 接入hotmail api对接与跨域规则
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
test('Hotmail API对接界面文案应启用 remote 模式并隐藏禁用态', () => {
|
||||
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||
|
||||
assert.match(
|
||||
html,
|
||||
/data-hotmail-service-mode="remote"[^>]*>\s*API对接\s*<\/button>/,
|
||||
'remote 模式按钮应显示为 API对接'
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
html,
|
||||
/data-hotmail-service-mode="remote"[^>]*\sdisabled(\s|>)/,
|
||||
'API对接按钮不应继续被禁用'
|
||||
);
|
||||
assert.match(
|
||||
html,
|
||||
/<span class="data-label">API对接<\/span>/,
|
||||
'Hotmail 配置行文案应改为 API对接'
|
||||
);
|
||||
});
|
||||
|
||||
test('Hotmail API对接应接入微软邮箱 helper 而不是旧远程服务占位', () => {
|
||||
const background = fs.readFileSync('background.js', 'utf8');
|
||||
|
||||
assert.match(background, /importScripts\([^)]*microsoft-email\.js[^)]*\)/, 'background 应加载微软邮箱 helper');
|
||||
assert.match(background, /fetchMicrosoftVerificationCode/, '步骤 4\/7 应接入微软验证码读取 helper');
|
||||
assert.match(background, /fetchMicrosoftMailboxMessages/, '账号校验和最新验证码应接入微软邮箱列表 helper');
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
test('manifest 应声明 declarativeNetRequest 权限用于微软 token 接口跨域头处理', () => {
|
||||
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
|
||||
|
||||
assert.equal(
|
||||
Array.isArray(manifest.permissions) && manifest.permissions.includes('declarativeNetRequest'),
|
||||
true,
|
||||
'manifest 缺少 declarativeNetRequest 权限'
|
||||
);
|
||||
});
|
||||
|
||||
test('background 应注册删除 Origin 请求头的动态规则', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
|
||||
assert.match(source, /setupDeclarativeNetRequestRules\(\)/, '应初始化跨域请求头规则');
|
||||
assert.match(source, /chrome\.declarativeNetRequest\.updateDynamicRules\(/, '应使用 declarativeNetRequest 动态规则');
|
||||
assert.match(source, /header:\s*'Origin'\s*,\s*operation:\s*'remove'/, '应删除 Origin 请求头');
|
||||
assert.match(source, /login\.microsoftonline\.com\/\*\/oauth2\/v2\.0\/token/, '规则应只命中微软 token 接口');
|
||||
assert.match(source, /resourceTypes:\s*\[\s*'xmlhttprequest'\s*\]/, '规则应限制在 xmlhttprequest');
|
||||
});
|
||||
@@ -12,6 +12,7 @@ const {
|
||||
getHotmailMailApiRequestConfig,
|
||||
getHotmailVerificationPollConfig,
|
||||
getHotmailVerificationRequestTimestamp,
|
||||
normalizeHotmailServiceMode,
|
||||
normalizeHotmailMailApiMessages,
|
||||
parseHotmailImportText,
|
||||
pickHotmailAccountForRun,
|
||||
@@ -461,6 +462,14 @@ test('getHotmailMailApiRequestConfig defines third-party mail API request defaul
|
||||
});
|
||||
});
|
||||
|
||||
test('normalizeHotmailServiceMode supports API对接 remote 模式并默认回退到本地助手', () => {
|
||||
assert.equal(normalizeHotmailServiceMode('remote'), 'remote');
|
||||
assert.equal(normalizeHotmailServiceMode('REMOTE'), 'remote');
|
||||
assert.equal(normalizeHotmailServiceMode('local'), 'local');
|
||||
assert.equal(normalizeHotmailServiceMode(''), 'local');
|
||||
assert.equal(normalizeHotmailServiceMode('unknown'), 'local');
|
||||
});
|
||||
|
||||
test('parseHotmailImportText parses account lines in email----password----clientId----token format', () => {
|
||||
const parsed = parseHotmailImportText(`
|
||||
账号----密码----ID----Token
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const {
|
||||
extractVerificationCodeFromMessages,
|
||||
fetchMicrosoftMailboxMessages,
|
||||
fetchMicrosoftVerificationCode,
|
||||
} = require('../microsoft-email.js');
|
||||
|
||||
test('extractVerificationCodeFromMessages 只命中过滤时间后的 OpenAI 微软邮件', () => {
|
||||
const result = extractVerificationCodeFromMessages([
|
||||
{
|
||||
From: { EmailAddress: { Address: 'noreply@openai.com' } },
|
||||
Subject: 'Your code is 112233',
|
||||
BodyPreview: '112233',
|
||||
ReceivedDateTime: '2026-04-14T09:00:00.000Z',
|
||||
Id: 'too-old',
|
||||
},
|
||||
{
|
||||
From: { EmailAddress: { Address: 'alerts@example.com' } },
|
||||
Subject: 'Your code is 223344',
|
||||
BodyPreview: '223344',
|
||||
ReceivedDateTime: '2026-04-14T10:00:00.000Z',
|
||||
Id: 'wrong-sender',
|
||||
},
|
||||
{
|
||||
From: { EmailAddress: { Address: 'account-security@openai.com' } },
|
||||
Subject: 'OpenAI verification',
|
||||
BodyPreview: 'Use 334455 to continue',
|
||||
ReceivedDateTime: '2026-04-14T10:05:00.000Z',
|
||||
Id: 'matched',
|
||||
},
|
||||
], {
|
||||
filterAfterTimestamp: Date.UTC(2026, 3, 14, 9, 30, 0),
|
||||
});
|
||||
|
||||
assert.deepEqual(result, {
|
||||
code: '334455',
|
||||
emailTimestamp: Date.UTC(2026, 3, 14, 10, 5, 0),
|
||||
messageId: 'matched',
|
||||
sender: 'account-security@openai.com',
|
||||
subject: 'OpenAI verification',
|
||||
});
|
||||
});
|
||||
|
||||
test('fetchMicrosoftMailboxMessages 使用 refresh token 拉取微软邮箱列表并返回新 token', async () => {
|
||||
const requests = [];
|
||||
const fetchImpl = async (url, options = {}) => {
|
||||
requests.push({ url, options });
|
||||
if (String(url).includes('/oauth2/v2.0/token')) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
access_token: 'access-token-1',
|
||||
refresh_token: 'refresh-token-next',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
value: [
|
||||
{
|
||||
From: { EmailAddress: { Address: 'noreply@openai.com' } },
|
||||
Subject: 'OpenAI verification',
|
||||
BodyPreview: 'Use 445566 to continue',
|
||||
ReceivedDateTime: '2026-04-14T10:06:00.000Z',
|
||||
Id: 'mail-1',
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
const result = await fetchMicrosoftMailboxMessages({
|
||||
clientId: 'client-1',
|
||||
refreshToken: 'refresh-token-1',
|
||||
top: 5,
|
||||
fetchImpl,
|
||||
});
|
||||
|
||||
assert.equal(requests.length, 2);
|
||||
assert.equal(result.nextRefreshToken, 'refresh-token-next');
|
||||
assert.equal(result.messages.length, 1);
|
||||
assert.equal(result.messages[0].id, 'mail-1');
|
||||
});
|
||||
|
||||
test('fetchMicrosoftVerificationCode 会重试直到命中最新验证码', async () => {
|
||||
let messageRequestCount = 0;
|
||||
const logs = [];
|
||||
const fetchImpl = async (url) => {
|
||||
if (String(url).includes('/oauth2/v2.0/token')) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
access_token: 'access-token-2',
|
||||
refresh_token: 'refresh-token-next-2',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
messageRequestCount += 1;
|
||||
if (messageRequestCount === 1) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
value: [{
|
||||
From: { EmailAddress: { Address: 'alerts@example.com' } },
|
||||
Subject: 'Nothing useful',
|
||||
BodyPreview: 'No code',
|
||||
ReceivedDateTime: '2026-04-14T10:00:00.000Z',
|
||||
Id: 'mail-ignore',
|
||||
}],
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
value: [{
|
||||
From: { EmailAddress: { Address: 'noreply@openai.com' } },
|
||||
Subject: 'Your verification code',
|
||||
BodyPreview: '667788',
|
||||
ReceivedDateTime: '2026-04-14T10:10:00.000Z',
|
||||
Id: 'mail-hit',
|
||||
}],
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
const result = await fetchMicrosoftVerificationCode({
|
||||
token: 'refresh-token-2',
|
||||
clientId: 'client-2',
|
||||
maxRetries: 2,
|
||||
retryDelayMs: 0,
|
||||
fetchImpl,
|
||||
log: (message) => logs.push(message),
|
||||
filterAfterTimestamp: Date.UTC(2026, 3, 14, 9, 0, 0),
|
||||
});
|
||||
|
||||
assert.equal(result.code, '667788');
|
||||
assert.equal(result.messageId, 'mail-hit');
|
||||
assert.equal(result.nextRefreshToken, 'refresh-token-next-2');
|
||||
assert.equal(logs.some((message) => /retrying/i.test(message)), true);
|
||||
});
|
||||
Reference in New Issue
Block a user