const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
function loadRenderSandbox(overrides = {}) {
const protocolSource = fs.readFileSync(path.join(__dirname, 'channels-protocols.js'), 'utf8');
const source = fs.readFileSync(path.join(__dirname, 'channels-render.js'), 'utf8');
const sandbox = {
window: {
t(key, params = {}) {
if (key === 'channels.table.priority') return '优先级';
if (key === 'channels.stats.healthScoreLabel') return '健康度';
if (key === 'channels.stats.successRate') return `成功率 ${params.rate}`;
if (key === 'channels.priorityUpdateSuccess') return '优先级已更新';
if (key === 'channels.priorityUpdateFailed') return '优先级更新失败';
if (key === 'channels.stats.firstByte') return '首字';
if (key === 'channels.stats.calls') return '调用';
if (key === 'channels.table.lastSuccess') return '最后成功';
if (key === 'channels.lastSuccess.noRequests') return '暂无请求';
if (key === 'channels.lastSuccess.never') return '从未成功';
if (key === 'channels.lastSuccess.secondsAgo') return `${params.count}秒前`;
if (key === 'channels.lastSuccess.minutesAgo') return `${params.count}分钟前`;
if (key === 'channels.lastSuccess.hoursAgo') return `${params.count}小时前`;
if (key === 'channels.lastSuccess.daysAgo') return `${params.count}天前`;
if (key === 'channels.lastSuccess.failedStatus') return `失败代码:${params.status}`;
if (key === 'channels.lastSuccess.failedAt') return `失败于:${params.time}`;
if (key === 'channels.lastSuccess.failedNoMessage') return '无失败日志';
if (key === 'channels.lastSuccess.lastSuccessPrefix') return `最后成功 ${params.time}`;
if (key === 'channels.lastSuccess.detail') return '详情';
if (key === 'stats.tooltipDuration') return '耗时';
if (key === 'stats.unitTimes') return '次';
if (key === 'common.success') return '成功';
if (key === 'common.failed') return '失败';
if (key === 'common.copy') return '复制';
if (key === 'common.seconds') return '秒';
if (key === 'channels.batchRefreshStatus.processing') return '刷新中';
if (key === 'channels.batchRefreshStatus.updated') return '已更新';
if (key === 'channels.batchRefreshStatus.unchanged') return '未变化';
if (key === 'channels.batchRefreshStatus.failed') return '失败';
if (key === 'channels.batchRefreshRowProcessing') return '正在获取模型列表...';
if (key === 'channels.batchRefreshRowUpdatedMerge') return `获取 ${params.fetched},新增 ${params.added},总计 ${params.total}`;
if (key === 'channels.batchRefreshRowUpdatedReplace') return `获取 ${params.fetched},移除 ${params.removed},总计 ${params.total}`;
if (key === 'channels.batchRefreshRowUnchanged') return `获取 ${params.fetched},总计 ${params.total}`;
if (key === 'channels.batchRefreshRowFailed') return `${params.error}`;
if (key === 'channels.batchRefreshDetail') return '展开详情';
if (key === 'channels.batchRefreshClear') return '清除';
if (key === 'channels.batchRefreshCopied') return '已复制';
return key;
}
},
TemplateEngine: {
render(_templateId, data = {}) {
return data;
}
},
channelStatsById: {},
formatMetricNumber(value) {
return String(value ?? '');
},
buildCostStackHtml(standard, effective) {
return `${standard ?? ''}/${effective ?? ''}`;
},
buildCornerMultiplierBadge(multiplier) {
if (!multiplier || Math.abs(Number(multiplier) - 1) < 1e-9) return '';
return `${multiplier}x`;
},
getCostDisplayInfo(standard, effective) {
const standardCost = Number(standard) || 0;
const effectiveCost = effective === undefined || effective === null ? standardCost : (Number(effective) || 0);
return {
standardCost,
effectiveCost,
hasMultiplier: Math.abs(effectiveCost - standardCost) >= 1e-9,
multiplier: standardCost > 0 ? effectiveCost / standardCost : 1
};
},
humanizeMS(ms) {
return `${ms}ms`;
},
setTimeout(fn) {
fn();
return 1;
},
clearTimeout() {},
console
};
Object.assign(sandbox, overrides);
vm.createContext(sandbox);
vm.runInContext(`${protocolSource}\n${source}`, sandbox);
return sandbox;
}
function loadRenderHelpers() {
return loadRenderSandbox();
}
test('buildEffectivePriorityHtml 不渲染优先级和健康度标签', () => {
const { buildEffectivePriorityHtml } = loadRenderHelpers();
const html = buildEffectivePriorityHtml({
priority: 110,
effective_priority: 105,
success_rate: 0.991
});
assert.ok(!html.includes('ch-priority-label'));
assert.match(html, /class="ch-priority-input"[^>]*value="110"/);
assert.ok(html.includes('>105<'));
});
test('buildEffectivePriorityHtml 在健康度等于优先级时只显示一次优先级', () => {
const { buildEffectivePriorityHtml } = loadRenderHelpers();
const html = buildEffectivePriorityHtml({
priority: 100,
effective_priority: 100
});
assert.equal((html.match(/ch-priority-row/g) || []).length, 1);
assert.match(html, /class="ch-priority-input"[^>]*value="100"/);
assert.ok(!html.includes('ch-priority-health'));
});
test('buildEffectivePriorityHtml 渲染可直接编辑的优先级控件', () => {
const { buildEffectivePriorityHtml } = loadRenderHelpers();
const html = buildEffectivePriorityHtml({
id: 42,
priority: 7
});
assert.match(html, /ch-priority-editor-wrap/);
assert.match(html, /ch-priority-editor/);
assert.match(html, /class="ch-priority-input"[^>]*data-channel-id="42"[^>]*data-original-priority="7"/);
assert.doesNotMatch(html, /ch-priority-controls/);
assert.doesNotMatch(html, /data-action="priority-step"/);
});
test('saveInlineChannelPriority 只调用优先级接口并更新本地状态,不重新拉 channels 或 filter-options', async () => {
const fetchCalls = [];
let filterCalls = 0;
let reloadCalls = 0;
const channels = [{ id: 7, priority: 10, effective_priority: 11 }];
const filteredChannels = [{ id: 7, priority: 10, effective_priority: 11 }];
const { saveInlineChannelPriority } = loadRenderSandbox({
channels,
filteredChannels,
fetchDataWithAuth: async (url, options = {}) => {
fetchCalls.push({ url, body: JSON.parse(options.body) });
return {};
},
clearChannelsCache() {},
filterChannels() {
filterCalls += 1;
},
reloadChannelsList() {
reloadCalls += 1;
throw new Error('priority update must not reload channel list');
},
window: {
t(key) {
if (key === 'channels.priorityUpdateSuccess') return '优先级已更新';
if (key === 'channels.priorityUpdateFailed') return '优先级更新失败';
return key;
},
showSuccess() {},
showError(error) {
throw new Error(error);
}
}
});
const input = {
value: '20',
dataset: { channelId: '7', originalPriority: '10' },
classList: { remove() {} },
closest() {
return {
classList: { toggle() {} },
querySelectorAll() { return []; }
};
}
};
await saveInlineChannelPriority(input);
assert.deepEqual(fetchCalls, [{
url: '/admin/channels/batch-priority',
body: { updates: [{ id: 7, priority: 20 }] }
}]);
assert.equal(channels[0].priority, 20);
assert.equal(channels[0].effective_priority, 21);
assert.equal(filteredChannels[0].priority, 20);
assert.equal(filteredChannels[0].effective_priority, 21);
assert.equal(filterCalls, 1);
assert.equal(reloadCalls, 0);
});
test('buildChannelTimingHtml 渲染耗时和带单位的调用汇总', () => {
const { buildChannelTimingHtml } = loadRenderHelpers();
const html = buildChannelTimingHtml({
avgFirstByteTimeSeconds: 2.3,
avgDurationSeconds: 22.23,
success: 17,
error: 3
});
assert.match(html, /首字/);
assert.match(html, /耗时/);
assert.match(html, /调用/);
assert.match(html, />2\.30秒);
assert.match(html, />22\.23秒);
assert.match(html, /17<\/span>\/3<\/span>次/);
assert.doesNotMatch(html, />成功);
assert.doesNotMatch(html, />失败);
});
test('buildChannelLastSuccessHtml 显示最近成功的相对时间', () => {
const { buildChannelLastSuccessHtml } = loadRenderHelpers();
const now = Date.now();
const html = buildChannelLastSuccessHtml({
lastSuccessAt: now - 3 * 60 * 1000,
lastRequestAt: now - 3 * 60 * 1000,
lastRequestStatus: 200
});
assert.match(html, /ch-last-status--ok/);
assert.match(html, />3分钟前);
});
test('buildChannelLastRequestFailureHtml 在最后一次请求失败时生成紧凑详情并转义内容', () => {
const { buildChannelLastRequestFailureHtml } = loadRenderHelpers();
const now = Date.now();
const html = buildChannelLastRequestFailureHtml({
lastSuccessAt: now - 2 * 60 * 60 * 1000,
lastRequestAt: now - 45 * 1000,
lastRequestStatus: 429,
lastRequestMessage: '第一行\n'
});
assert.match(html, /ch-last-request/);
assert.match(html, /失败代码:429/);
assert.match(html, /失败于:45秒前/);
assert.match(html, /详情<\/summary>/);
assert.match(html, /ch-last-request__panel/);
assert.match(html, /data-action="copy-last-request-failure"/);
assert.match(html, />复制<\/button>/);
assert.doesNotMatch(html, /ch-last-request__message/);
assert.doesNotMatch(html, /ch-last-request__main/);
assert.doesNotMatch(html, /ch-last-request__actions/);
assert.doesNotMatch(html, /最后成功 2小时前/);
assert.doesNotMatch(html, /从未成功/);
assert.match(html, /<script>alert\(1\)<\/script>/);
assert.doesNotMatch(html, /'
});
assert.match(html, /channel-refresh-result--failed/);
assert.match(html, /失败<\/span>/);
assert.match(html, /HTTP 401 <bad>/);
assert.doesNotMatch(html, /失败:HTTP 401 <bad>/);
assert.match(html, /展开详情<\/summary>/);
assert.match(html, /完整错误 <script>alert\(1\)<\/script>/);
assert.doesNotMatch(html, /data-action="copy-batch-refresh-error"/);
assert.doesNotMatch(html, /复制错误/);
assert.match(html, /data-action="clear-batch-refresh-result"/);
assert.doesNotMatch(html, /