Files
go_bt/tests/import_browser_batch_service.test.js

107 lines
4.1 KiB
JavaScript

'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const {
runImportBrowserBatch,
normalizeConcurrency,
DEFAULT_MAX_CONCURRENCY,
} = require('../src/services/import_browser_batch_service');
const { _private: { RateLimiter } } = require('../src/pipeline/browser_lifecycle');
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function waitUntil(predicate, timeoutMs = 1000) {
const startedAt = Date.now();
while (!predicate()) {
if (Date.now() - startedAt > timeoutMs) throw new Error('等待条件超时');
await sleep(5);
}
}
(async () => {
assert.strictEqual(normalizeConcurrency(30, 200), 30);
assert.strictEqual(normalizeConcurrency(999, 200), DEFAULT_MAX_CONCURRENCY);
assert.strictEqual(normalizeConcurrency(0, 200), 1);
assert.strictEqual(normalizeConcurrency(30, 4), 4);
const accounts = Array.from({ length: 8 }, (_, i) => ({ email: `user${i}@example.com` }));
let releaseGate;
const gate = new Promise(resolve => { releaseGate = resolve; });
let active = 0;
let maxActive = 0;
let started = 0;
const progress = [];
const batchPromise = runImportBrowserBatch(accounts, {
concurrency: 3,
acquireRequestSlot: async () => {},
processAccount: async () => {
started += 1;
active += 1;
maxActive = Math.max(maxActive, active);
await gate;
active -= 1;
return { action: 'created' };
},
onProgress: ({ completed }) => progress.push(completed),
});
await waitUntil(() => started === 3);
assert.strictEqual(maxActive, 3, '应同时启动三个账号,而不是逐个串行');
releaseGate();
const batchResult = await batchPromise;
assert.strictEqual(batchResult.completed, accounts.length);
assert.deepStrictEqual(progress, [1, 2, 3, 4, 5, 6, 7, 8]);
const processed = [];
const errors = [];
let acquired = 0;
const isolated = await runImportBrowserBatch(accounts, {
concurrency: 4,
needsRequestSlot: (_account, index) => index % 2 === 0,
acquireRequestSlot: async () => { acquired += 1; },
processAccount: async (account, index) => {
processed.push(account.email);
if (index === 2) throw new Error('单号失败');
return { action: 'created' };
},
onAccountError: (error, account) => errors.push(`${account.email}:${error.message}`),
});
assert.strictEqual(processed.length, accounts.length, '单号异常后其余账号仍应继续');
assert.strictEqual(isolated.results.filter(item => item && item.status === 'rejected').length, 1);
assert.strictEqual(errors.length, 1);
assert.strictEqual(acquired, 4);
const limiter = new RateLimiter(50);
const releasedAt = [];
await Promise.all(Array.from({ length: 5 }, () => limiter.acquire().then(() => releasedAt.push(Date.now()))));
for (let i = 1; i < releasedAt.length; i += 1) {
assert.ok(releasedAt[i] - releasedAt[i - 1] >= 12, '限速器不应突发放行整批请求');
}
const taskRunnerSource = fs.readFileSync(
path.join(__dirname, '../src/workers/task_runner.js'),
'utf8'
);
const importStart = taskRunnerSource.indexOf("case 'import_setup'");
const importEnd = taskRunnerSource.indexOf('\n default:', importStart);
const importSection = taskRunnerSource.slice(importStart, importEnd);
const disabledAt = importSection.indexOf('导入后自动创建比特浏览器已停用');
const firstBreakAt = importSection.indexOf('break;');
const legacyBrowserBatchAt = importSection.indexOf('runImportBrowserBatch(readyAccounts');
assert.ok(disabledAt >= 0, '导入初始化任务必须明确停用浏览器创建');
assert.ok(firstBreakAt > disabledAt, '停用提示后应立即结束导入初始化任务');
assert.ok(legacyBrowserBatchAt === -1 || firstBreakAt < legacyBrowserBatchAt, '旧浏览器批处理代码不得再被执行');
const appSource = fs.readFileSync(path.join(__dirname, '../public/app.js'), 'utf8');
assert.ok(!appSource.includes('_runImportSetup('), '前端导入成功后不得启动浏览器初始化任务');
console.log('import_browser_batch_service.test.js PASS');
})().catch(error => {
console.error(error);
process.exitCode = 1;
});