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秒22\.23秒\/3<\/span>次/); assert.doesNotMatch(html, />成功失败 { 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分钟前 { 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, /