重构更新账号日志功能,添加开发者模式

This commit is contained in:
QLHazyCoder
2026-04-17 18:29:53 +08:00
parent 3b28845d86
commit ccea024f9a
16 changed files with 1245 additions and 458 deletions
@@ -105,6 +105,10 @@ return {
api.normalizePersistentSettingValue('accountRunHistoryHelperBaseUrl', 'http://127.0.0.1:17373/append-account-log'),
'http://127.0.0.1:17373'
);
assert.equal(
api.normalizePersistentSettingValue('accountRunHistoryHelperBaseUrl', 'http://127.0.0.1:17373/sync-account-run-records'),
'http://127.0.0.1:17373'
);
assert.equal(
api.normalizeAccountRunHistoryHelperBaseUrl(''),
'http://127.0.0.1:17373'
@@ -16,12 +16,15 @@ test('account run history module exposes a factory', () => {
assert.equal(typeof api?.createAccountRunHistoryHelpers, 'function');
});
test('account run history helper normalizes records and persists without helper upload when local helper is disabled', async () => {
test('account run history helper upgrades old records, filters stopped items and stores normalized failed snapshot records', async () => {
const source = fs.readFileSync('background/account-run-history.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundAccountRunHistory;`)(globalScope);
let storedHistory = [{ email: 'old@example.com', password: 'old-pass', status: 'success', recordedAt: '2026-04-17T00:00:00.000Z' }];
let storedHistory = [
{ email: 'old@example.com', password: 'old-pass', status: 'success', recordedAt: '2026-04-17T00:00:00.000Z' },
{ email: 'stop@example.com', password: 'stop-pass', status: 'stopped', recordedAt: '2026-04-17T00:10:00.000Z' },
];
let fetchCalled = false;
global.fetch = async () => {
fetchCalled = true;
@@ -46,6 +49,10 @@ test('account run history helper normalizes records and persists without helper
getState: async () => ({
email: ' latest@example.com ',
password: ' secret ',
autoRunning: true,
autoRunCurrentRun: 2,
autoRunTotalRuns: 10,
autoRunAttemptRun: 3,
accountRunHistoryTextEnabled: false,
accountRunHistoryHelperBaseUrl: '',
}),
@@ -53,24 +60,133 @@ test('account run history helper normalizes records and persists without helper
});
const record = helpers.buildAccountRunHistoryRecord(
{ email: ' latest@example.com ', password: ' secret ' },
' FAILED ',
' reason '
{
email: ' latest@example.com ',
password: ' secret ',
autoRunning: true,
autoRunCurrentRun: 2,
autoRunTotalRuns: 10,
autoRunAttemptRun: 3,
},
'step8_failed',
'步骤 8:认证页进入了手机号页面,当前不是 OAuth 同意页,无法继续自动授权。'
);
assert.deepStrictEqual(record, {
recordId: 'latest@example.com',
email: 'latest@example.com',
password: 'secret',
status: 'failed',
recordedAt: record.recordedAt,
reason: 'reason',
finalStatus: 'failed',
finishedAt: record.finishedAt,
retryCount: 2,
failureLabel: '出现手机号验证',
failureDetail: '步骤 8:认证页进入了手机号页面,当前不是 OAuth 同意页,无法继续自动授权。',
failedStep: 8,
source: 'auto',
autoRunContext: {
currentRun: 2,
totalRuns: 10,
attemptRun: 3,
},
});
const appended = await helpers.appendAccountRunRecord('failed', null, 'boom');
const appended = await helpers.appendAccountRunRecord('step8_failed', null, '步骤 8:认证页进入了手机号页面,当前不是 OAuth 同意页,无法继续自动授权。');
assert.equal(appended.email, 'latest@example.com');
assert.equal(appended.status, 'failed');
assert.equal(storedHistory.length, 2);
assert.equal(storedHistory[1].reason, 'boom');
assert.equal(appended.finalStatus, 'failed');
assert.equal(appended.failureLabel, '出现手机号验证');
assert.equal(storedHistory.length, 2, '旧的 stopped 记录应在新结构中被过滤掉');
assert.equal(storedHistory.some((item) => item.email === 'stop@example.com'), false);
assert.equal(storedHistory.some((item) => item.email === 'latest@example.com' && item.retryCount === 2), true);
assert.equal(storedHistory.some((item) => item.email === 'old@example.com'), true);
assert.equal(fetchCalled, false);
assert.equal(helpers.shouldAppendAccountRunTextFile({ accountRunHistoryTextEnabled: false, accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373' }), false);
assert.equal(helpers.shouldAppendAccountRunTextFile({ accountRunHistoryTextEnabled: true, accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373' }), true);
assert.equal(helpers.buildAccountRunHistoryRecord({ email: 'a@b.com', password: 'x' }, 'stopped', 'stop'), null);
});
test('account run history helper clears persisted records and syncs full snapshot payload to local helper', async () => {
const source = fs.readFileSync('background/account-run-history.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundAccountRunHistory;`)(globalScope);
let storedHistory = [{
recordId: 'user@example.com',
email: 'user@example.com',
password: 'secret',
finalStatus: 'failed',
finishedAt: '2026-04-17T01:00:00.000Z',
retryCount: 1,
failureLabel: '步骤 6 失败',
failureDetail: '步骤 6:判断失败后已重试 2 次,仍未成功。',
failedStep: 6,
source: 'auto',
autoRunContext: {
currentRun: 1,
totalRuns: 5,
attemptRun: 2,
},
}];
const fetchCalls = [];
global.fetch = async (url, options = {}) => {
fetchCalls.push({
url,
options,
});
return {
ok: true,
json: async () => ({
ok: true,
filePath: 'C:/tmp/account-run-history.json',
}),
};
};
const logs = [];
const helpers = api.createAccountRunHistoryHelpers({
ACCOUNT_RUN_HISTORY_STORAGE_KEY: 'accountRunHistory',
addLog: async (message, level) => {
logs.push({ message, level });
},
buildLocalHelperEndpoint: (baseUrl, path) => `${baseUrl}${path}`,
chrome: {
storage: {
local: {
get: async () => ({ accountRunHistory: storedHistory }),
set: async (payload) => {
storedHistory = payload.accountRunHistory;
},
},
},
},
getErrorMessage: (error) => error?.message || String(error || ''),
getState: async () => ({
accountRunHistoryTextEnabled: true,
accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373',
}),
normalizeAccountRunHistoryHelperBaseUrl: (value) => String(value || '').trim(),
});
const payload = helpers.buildAccountRunHistorySnapshotPayload(storedHistory);
assert.deepStrictEqual(payload.summary, {
total: 1,
success: 0,
failed: 1,
retryTotal: 1,
});
const clearResult = await helpers.clearAccountRunHistory();
assert.deepStrictEqual(clearResult, { clearedCount: 1 });
assert.deepStrictEqual(storedHistory, []);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0].url, 'http://127.0.0.1:17373/sync-account-run-records');
assert.deepStrictEqual(JSON.parse(fetchCalls[0].options.body), {
generatedAt: JSON.parse(fetchCalls[0].options.body).generatedAt,
summary: {
total: 0,
success: 0,
failed: 0,
retryTotal: 0,
},
records: [],
});
assert.equal(logs[0].message, '账号记录快照已同步到本地:C:/tmp/account-run-history.json');
});
@@ -0,0 +1,190 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
const start = markers
.map((marker) => sidepanelSource.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 < sidepanelSource.length; i += 1) {
const ch = sidepanelSource[i];
if (ch === '(') {
parenDepth += 1;
} else if (ch === ')') {
parenDepth -= 1;
if (parenDepth === 0) {
signatureEnded = true;
}
} else if (ch === '{' && signatureEnded) {
braceStart = i;
break;
}
}
let depth = 0;
let end = braceStart;
for (; end < sidepanelSource.length; end += 1) {
const ch = sidepanelSource[end];
if (ch === '{') depth += 1;
if (ch === '}') {
depth -= 1;
if (depth === 0) {
end += 1;
break;
}
}
}
return sidepanelSource.slice(start, end);
}
function createButton() {
return {
disabled: false,
textContent: '',
hidden: false,
listeners: {},
addEventListener(type, handler) {
this.listeners[type] = handler;
},
};
}
function createContainer() {
return {
innerHTML: '',
textContent: '',
hidden: true,
listeners: {},
addEventListener(type, handler) {
this.listeners[type] = handler;
},
};
}
test('sidepanel html contains account records overlay and manager script', () => {
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
const managerIndex = html.indexOf('<script src="account-records-manager.js"></script>');
const sidepanelIndex = html.indexOf('<script src="sidepanel.js"></script>');
assert.match(html, /id="btn-open-account-records"/);
assert.match(html, /id="account-records-overlay"/);
assert.match(html, /id="account-records-list"/);
assert.match(html, /id="account-records-stats"/);
assert.match(html, /id="btn-clear-account-records"/);
assert.notEqual(managerIndex, -1);
assert.notEqual(sidepanelIndex, -1);
assert.ok(managerIndex < sidepanelIndex);
});
test('sidepanel account records helper normalizes snapshot helper base url', () => {
const bundle = [
extractFunction('normalizeAccountRunHistoryHelperBaseUrlValue'),
].join('\n');
const api = new Function(`
const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = 'http://127.0.0.1:17373';
${bundle}
return { normalizeAccountRunHistoryHelperBaseUrlValue };
`)();
assert.equal(
api.normalizeAccountRunHistoryHelperBaseUrlValue('http://127.0.0.1:17373/sync-account-run-records'),
'http://127.0.0.1:17373'
);
});
test('account records manager exposes a factory and renders summarized paginated records', () => {
const source = fs.readFileSync('sidepanel/account-records-manager.js', 'utf8');
const windowObject = {};
const api = new Function('window', `${source}; return window.SidepanelAccountRecordsManager;`)(windowObject);
assert.equal(typeof api?.createAccountRecordsManager, 'function');
const btnOpenAccountRecords = createButton();
const btnCloseAccountRecords = createButton();
const btnClearAccountRecords = createButton();
const btnAccountRecordsPrev = createButton();
const btnAccountRecordsNext = createButton();
const overlay = createContainer();
const list = createContainer();
const stats = createContainer();
const meta = createContainer();
const pageLabel = createContainer();
const manager = api.createAccountRecordsManager({
state: {
getLatestState: () => ({
accountRunHistory: [
{
email: 'success@example.com',
password: 'secret',
finalStatus: 'success',
finishedAt: '2026-04-17T04:31:00.000Z',
retryCount: 0,
failureLabel: '流程完成',
},
{
email: 'failed@example.com',
password: 'secret',
finalStatus: 'failed',
finishedAt: '2026-04-17T04:29:00.000Z',
retryCount: 2,
failureLabel: '出现手机号验证',
},
],
}),
syncLatestState() {},
},
dom: {
accountRecordsList: list,
accountRecordsMeta: meta,
accountRecordsOverlay: overlay,
accountRecordsPageLabel: pageLabel,
accountRecordsStats: stats,
btnAccountRecordsNext,
btnAccountRecordsPrev,
btnClearAccountRecords,
btnCloseAccountRecords,
btnOpenAccountRecords,
},
helpers: {
escapeHtml: (value) => String(value || ''),
openConfirmModal: async () => true,
showToast() {},
},
runtime: {
sendMessage: async () => ({ clearedCount: 2 }),
},
constants: {
displayTimeZone: 'Asia/Shanghai',
pageSize: 10,
},
});
assert.equal(typeof manager.bindEvents, 'function');
assert.equal(typeof manager.render, 'function');
assert.equal(typeof manager.openPanel, 'function');
manager.bindEvents();
manager.render();
assert.match(meta.textContent, /共 2 条/);
assert.match(stats.innerHTML, /重试/);
assert.match(list.innerHTML, /success@example\.com/);
assert.match(list.innerHTML, /出现手机号验证/);
assert.match(list.innerHTML, /重试 2/);
assert.equal(pageLabel.textContent, '1 / 1');
assert.equal(btnClearAccountRecords.disabled, false);
});
-118
View File
@@ -1,118 +0,0 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
const start = markers
.map((marker) => sidepanelSource.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 < sidepanelSource.length; i += 1) {
const ch = sidepanelSource[i];
if (ch === '(') {
parenDepth += 1;
} else if (ch === ')') {
parenDepth -= 1;
if (parenDepth === 0) {
signatureEnded = true;
}
} else if (ch === '{' && signatureEnded) {
braceStart = i;
break;
}
}
let depth = 0;
let end = braceStart;
for (; end < sidepanelSource.length; end += 1) {
const ch = sidepanelSource[end];
if (ch === '{') depth += 1;
if (ch === '}') {
depth -= 1;
if (depth === 0) {
end += 1;
break;
}
}
}
return sidepanelSource.slice(start, end);
}
test('sidepanel html contains account run history strip under log header', () => {
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
assert.match(html, /id="account-run-history-strip"/);
assert.match(html, /id="account-run-history-meta"/);
assert.match(html, /id="account-run-history-stats"/);
assert.match(html, /id="account-run-history-list"/);
assert.match(html, /id="input-account-run-history-text-enabled"/);
assert.match(html, /id="input-account-run-history-helper-base-url"/);
});
test('sidepanel account run history helpers classify statuses and summarize counts', () => {
const bundle = [
extractFunction('normalizeAccountRunHistoryHelperBaseUrlValue'),
extractFunction('parseAccountRunStatus'),
extractFunction('summarizeAccountRunHistory'),
extractFunction('buildAccountRunHistoryDetailText'),
].join('\n');
const api = new Function(`
const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = 'http://127.0.0.1:17373';
${bundle}
return { normalizeAccountRunHistoryHelperBaseUrlValue, parseAccountRunStatus, summarizeAccountRunHistory, buildAccountRunHistoryDetailText };
`)();
assert.deepStrictEqual(api.parseAccountRunStatus('step7_failed'), {
kind: 'failed',
label: '步7失败',
});
assert.deepStrictEqual(api.parseAccountRunStatus('step4_stopped'), {
kind: 'stopped',
label: '步4停止',
});
assert.deepStrictEqual(api.parseAccountRunStatus('success'), {
kind: 'success',
label: '成功',
});
assert.deepStrictEqual(api.summarizeAccountRunHistory([
{ status: 'success' },
{ status: 'step7_failed' },
{ status: 'stopped' },
{ status: 'step2_failed' },
]), {
total: 4,
success: 1,
failed: 2,
stopped: 1,
other: 0,
});
assert.equal(
api.buildAccountRunHistoryDetailText({ status: 'success', reason: '' }),
'流程已完成并写入本地记录'
);
assert.equal(
api.buildAccountRunHistoryDetailText({ status: 'step7_failed', reason: '' }),
'流程执行失败,已保留当前账号快照'
);
assert.equal(
api.buildAccountRunHistoryDetailText({ status: 'stopped', reason: '手动停止' }),
'手动停止'
);
assert.equal(
api.normalizeAccountRunHistoryHelperBaseUrlValue('http://127.0.0.1:17373/append-account-log'),
'http://127.0.0.1:17373'
);
});