const test = require('node:test'); const assert = require('node:assert'); const path = require('path'); function freshMirror() { const p = require.resolve(path.join(__dirname, '..', 'sources', 'mirror.js')); delete require.cache[p]; return require(p); } test('tryMirrors: 内容级错误立即返回,不再试其它镜像', async () => { const m = freshMirror(); const tried = []; await assert.rejects( m.tryMirrors('p1', ['a', 'b', 'c'], async (x) => { tried.push(x); throw m.contentError('该 DOI 不存在'); }), /不存在/ ); assert.deepStrictEqual(tried, ['a'], '内容级错误不该继续轮询'); }); test('tryMirrors: 内容级错误不拉黑镜像,下次仍优先使用', async () => { const m = freshMirror(); await assert.rejects(m.tryMirrors('p2', ['a', 'b'], async () => { throw m.contentError('没有这篇'); })); const tried = []; await m.tryMirrors('p2', ['a', 'b'], async (x) => { tried.push(x); return 'ok'; }); assert.strictEqual(tried[0], 'a', '健康镜像被误拉黑了'); }); test('tryMirrors: 真实网络故障会依次换镜像', async () => { const m = freshMirror(); const tried = []; const r = await m.tryMirrors('p3', ['a', 'b', 'c'], async (x) => { tried.push(x); if (x !== 'c') throw new Error('网络连接失败,请检查网络或代理设置'); return 'ok'; }); assert.strictEqual(r, 'ok'); assert.deepStrictEqual(tried, ['a', 'b', 'c']); }); test('tryMirrors: 成功镜像会被记住并优先', async () => { const m = freshMirror(); await m.tryMirrors('p4', ['a', 'b', 'c'], async (x) => { if (x !== 'c') throw new Error('请求超时,站点无响应'); return 'ok'; }); const tried = []; await m.tryMirrors('p4', ['a', 'b', 'c'], async (x) => { tried.push(x); return 'ok'; }); assert.strictEqual(tried[0], 'c', '上次成功的镜像没有被优先'); }); test('tryMirrors: 全部失败时抛出最后一个错误', async () => { const m = freshMirror(); await assert.rejects( m.tryMirrors('p5', ['a', 'b'], async () => { throw new Error('请求超时,站点无响应'); }), /超时/ ); }); test('raceMirrors: 返回最快成功的结果', async () => { const m = freshMirror(); const r = await m.raceMirrors('r1', ['slow', 'fast'], async (x) => { if (x === 'slow') { await new Promise((s) => setTimeout(s, 200)); return 'slow'; } return 'fast'; }); assert.strictEqual(r, 'fast'); }); test('raceMirrors: 胜出后中止其余在途请求', async () => { const m = freshMirror(); let aborted = false; const r = await m.raceMirrors('r2', ['loser', 'winner'], async (x, signal) => { if (x === 'winner') return 'w'; return new Promise((_res, rej) => { signal.addEventListener('abort', () => { aborted = true; rej(new Error('请求已取消')); }); }); }); assert.strictEqual(r, 'w'); await new Promise((s) => setTimeout(s, 20)); assert.ok(aborted, '败者没有被中止'); }); test('raceMirrors: 跳过已拉黑镜像', async () => { const m = freshMirror(); // 先让 bad 因真实故障进黑名单 await m.raceMirrors('r3', ['bad', 'good'], async (x) => { if (x === 'bad') throw new Error('网络连接失败,请检查网络或代理设置'); return 'ok'; }); const tried = []; await m.raceMirrors('r3', ['bad', 'good'], async (x) => { tried.push(x); return 'ok'; }); assert.ok(!tried.includes('bad'), '黑名单在竞速模式下失效了'); }); test('raceMirrors: 全部失败时 reject 而不是挂起', async () => { const m = freshMirror(); await assert.rejects( m.raceMirrors('r4', ['a', 'b'], async () => { throw new Error('请求超时,站点无响应'); }), /超时/ ); }); test('raceMirrors: 内容级错误直接结束竞速', async () => { const m = freshMirror(); await assert.rejects( m.raceMirrors('r5', ['a', 'b'], async (x) => { if (x === 'a') throw m.contentError('页面结构无法识别'); await new Promise((s) => setTimeout(s, 500)); return 'late'; }), /页面结构无法识别/ ); });