Files
FlowPilot/tests/hotmail-auto-run-preflight.test.js
T

315 lines
9.3 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { pickHotmailAccountForRun } = require('../hotmail-utils.js');
const source = fs.readFileSync('background.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) {
return '';
}
let parenDepth = 0;
let signatureEnded = false;
let braceStart = -1;
for (let index = start; index < source.length; index += 1) {
const ch = source[index];
if (ch === '(') {
parenDepth += 1;
} else if (ch === ')') {
parenDepth -= 1;
if (parenDepth === 0) {
signatureEnded = true;
}
} else if (ch === '{' && signatureEnded) {
braceStart = index;
break;
}
}
if (braceStart < 0) {
return '';
}
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 isAuthorizedHotmailRunAccountSource = extractFunction('isAuthorizedHotmailRunAccount');
const isPendingHotmailVerificationCandidateSource = extractFunction('isPendingHotmailVerificationCandidate');
const compareHotmailAccountAllocationPrioritySource = extractFunction('compareHotmailAccountAllocationPriority');
const pickPendingHotmailAccountForVerificationSource = extractFunction('pickPendingHotmailAccountForVerification');
const ensureHotmailAccountForFlowSource = extractFunction('ensureHotmailAccountForFlow');
const ensureHotmailMailboxReadyForAutoRunRoundSource = extractFunction('ensureHotmailMailboxReadyForAutoRunRound');
function createHotmailPreflightApi(initialState, verifyImpl = async () => ({ account: null, messageCount: 0 })) {
const factory = new Function('deps', `
let currentState = JSON.parse(JSON.stringify(deps.initialState));
const getState = async () => ({
...currentState,
hotmailAccounts: Array.isArray(currentState.hotmailAccounts)
? currentState.hotmailAccounts.map((account) => ({ ...account }))
: [],
});
const normalizeHotmailAccounts = (accounts) => Array.isArray(accounts)
? accounts.map((account) => ({ ...account }))
: [];
const findHotmailAccount = (accounts, accountId) => normalizeHotmailAccounts(accounts)
.find((account) => account.id === accountId) || null;
const setCurrentHotmailAccount = async (accountId) => {
const state = await getState();
const account = findHotmailAccount(state.hotmailAccounts, accountId);
if (!account) {
throw new Error('missing Hotmail account');
}
currentState = {
...currentState,
currentHotmailAccountId: accountId,
};
return account;
};
const pickHotmailAccountForRun = deps.pickHotmailAccountForRun;
const verifyHotmailAccount = async (accountId) => deps.verifyHotmailAccount(accountId, async () => getState());
const isHotmailProvider = (stateOrProvider) => {
const provider = typeof stateOrProvider === 'string'
? stateOrProvider
: stateOrProvider?.mailProvider;
return provider === 'hotmail-api';
};
const addLog = async (message, level = 'info') => {
deps.logs.push({ message, level });
};
const throwIfStopped = () => {};
${isAuthorizedHotmailRunAccountSource}
${isPendingHotmailVerificationCandidateSource}
${compareHotmailAccountAllocationPrioritySource}
${pickPendingHotmailAccountForVerificationSource}
${ensureHotmailAccountForFlowSource}
${ensureHotmailMailboxReadyForAutoRunRoundSource}
return {
ensureHotmailAccountForFlow,
ensureHotmailMailboxReadyForAutoRunRound: typeof ensureHotmailMailboxReadyForAutoRunRound === 'function'
? ensureHotmailMailboxReadyForAutoRunRound
: undefined,
getState,
};
`);
const logs = [];
return {
api: factory({
initialState,
logs,
pickHotmailAccountForRun,
verifyHotmailAccount: verifyImpl,
}),
logs,
};
}
test('ensureHotmailAccountForFlow skips excluded current hotmail account when allocating a fresh account', async () => {
const { api } = createHotmailPreflightApi({
mailProvider: 'hotmail-api',
currentHotmailAccountId: 'primary',
hotmailAccounts: [
{
id: 'primary',
email: 'primary@hotmail.com',
status: 'authorized',
refreshToken: 'rt-primary',
used: false,
lastUsedAt: 1,
},
{
id: 'backup',
email: 'backup@hotmail.com',
status: 'authorized',
refreshToken: 'rt-backup',
used: false,
lastUsedAt: 2,
},
],
});
const account = await api.ensureHotmailAccountForFlow({
allowAllocate: true,
markUsed: false,
excludeIds: ['primary'],
});
assert.equal(account.id, 'backup');
});
test('ensureHotmailMailboxReadyForAutoRunRound switches to another hotmail account after a verification failure', async () => {
const verifyCalls = [];
const { api, logs } = createHotmailPreflightApi({
mailProvider: 'hotmail-api',
currentHotmailAccountId: 'primary',
hotmailAccounts: [
{
id: 'primary',
email: 'primary@hotmail.com',
status: 'authorized',
refreshToken: 'rt-primary',
used: false,
lastUsedAt: 1,
},
{
id: 'backup',
email: 'backup@hotmail.com',
status: 'authorized',
refreshToken: 'rt-backup',
used: false,
lastUsedAt: 2,
},
],
}, async (accountId, getState) => {
verifyCalls.push(accountId);
const state = await getState();
const account = state.hotmailAccounts.find((item) => item.id === accountId);
if (accountId === 'primary') {
throw new Error('INBOX unavailable');
}
return {
account,
messageCount: 4,
};
});
assert.equal(typeof api.ensureHotmailMailboxReadyForAutoRunRound, 'function');
const account = await Promise.race([
api.ensureHotmailMailboxReadyForAutoRunRound({
targetRun: 1,
totalRuns: 3,
attemptRun: 1,
}),
new Promise((_, reject) => {
setTimeout(() => reject(new Error('Hotmail auto-run preflight timed out')), 200);
}),
]);
const state = await api.getState();
assert.equal(account.id, 'backup');
assert.equal(state.currentHotmailAccountId, 'backup');
assert.deepEqual(verifyCalls, ['primary', 'backup']);
assert.ok(logs.some(({ message }) => /切换下一个 Hotmail 账号/.test(message)));
});
test('ensureHotmailMailboxReadyForAutoRunRound verifies pending hotmail accounts when no authorized account exists yet', async () => {
const verifyCalls = [];
const { api } = createHotmailPreflightApi({
mailProvider: 'hotmail-api',
currentHotmailAccountId: null,
hotmailAccounts: [
{
id: 'pending-1',
email: 'pending-1@hotmail.com',
status: 'pending',
refreshToken: 'rt-pending-1',
used: false,
lastUsedAt: 0,
},
],
}, async (accountId, getState) => {
verifyCalls.push(accountId);
const state = await getState();
const account = state.hotmailAccounts.find((item) => item.id === accountId);
return {
account: {
...account,
status: 'authorized',
},
messageCount: 2,
};
});
const account = await api.ensureHotmailMailboxReadyForAutoRunRound({
targetRun: 1,
totalRuns: 1,
attemptRun: 1,
});
const state = await api.getState();
assert.equal(account.id, 'pending-1');
assert.equal(state.currentHotmailAccountId, 'pending-1');
assert.deepEqual(verifyCalls, ['pending-1']);
});
test('ensureHotmailMailboxReadyForAutoRunRound falls back to pending hotmail accounts after authorized accounts fail', async () => {
const verifyCalls = [];
const { api, logs } = createHotmailPreflightApi({
mailProvider: 'hotmail-api',
currentHotmailAccountId: 'authorized-primary',
hotmailAccounts: [
{
id: 'authorized-primary',
email: 'authorized-primary@hotmail.com',
status: 'authorized',
refreshToken: 'rt-authorized-primary',
used: false,
lastUsedAt: 1,
},
{
id: 'pending-backup',
email: 'pending-backup@hotmail.com',
status: 'pending',
refreshToken: 'rt-pending-backup',
used: false,
lastUsedAt: 2,
},
],
}, async (accountId, getState) => {
verifyCalls.push(accountId);
const state = await getState();
const account = state.hotmailAccounts.find((item) => item.id === accountId);
if (accountId === 'authorized-primary') {
throw new Error('INBOX unavailable');
}
return {
account: {
...account,
status: 'authorized',
},
messageCount: 3,
};
});
const account = await Promise.race([
api.ensureHotmailMailboxReadyForAutoRunRound({
targetRun: 1,
totalRuns: 2,
attemptRun: 1,
}),
new Promise((_, reject) => {
setTimeout(() => reject(new Error('Hotmail auto-run pending fallback timed out')), 200);
}),
]);
const state = await api.getState();
assert.equal(account.id, 'pending-backup');
assert.equal(state.currentHotmailAccountId, 'pending-backup');
assert.deepEqual(verifyCalls, ['authorized-primary', 'pending-backup']);
assert.ok(logs.some(({ message }) => /待校验|未校验/.test(message)));
});