import assert from 'node:assert/strict' import { readFileSync, readdirSync } from 'node:fs' import { createRequire } from 'node:module' import { parse, compileScript, compileTemplate } from '@vue/compiler-sfc' import { fetchEventHistory, fetchHandlingEfficiency, fetchEventAnalysis } from '../../../src/modules/cockpit/services/warningResponseApi.js' import { eventTimeWindow } from '../../../src/modules/cockpit/domain/eventRange.js' import { trendBlock } from '../../../src/modules/cockpit/domain/panelBlocks.js' import { legendMatchesMarkers, legendVisuals, planPointMarkers, pointLegend } from '../../../src/modules/cockpit/domain/pointStatus.js' const require = createRequire(import.meta.url) const postcss = require('postcss') const root = new URL('../../../', import.meta.url) let components = 0 for (const name of ['CockpitShell.vue', ...readdirSync(new URL('src/modules/cockpit/components/', root)).filter((file) => file.endsWith('.vue')).map((file) => `components/${file}`)]) { const filename = `src/modules/cockpit/${name}` const source = readFileSync(new URL(filename, root), 'utf8') const result = parse(source, { filename }) assert.deepEqual(result.errors, [], filename) const descriptor = result.descriptor const script = descriptor.scriptSetup ? compileScript(descriptor, { id: filename }) : null if (descriptor.template) { const template = compileTemplate({ source: descriptor.template.content, filename, id: filename, compilerOptions: { bindingMetadata: script?.bindings || {} } }) assert.deepEqual(template.errors, [], filename) } for (const style of descriptor.styles) postcss.parse(style.content, { from: filename }) components++ } const tokens = readFileSync(new URL('src/modules/cockpit/styles/tokens.css', root), 'utf8') postcss.parse(tokens) // 2026-10-22 维护者要求两侧面板稍微调透(三档各降 0.08),断言跟随新基准。 assert.match(tokens, /--panel-top:\s*rgba\(2,\s*9,\s*27,\s*0\.8\)/) // Exercise actual request -> adaptation -> 90-day merge with a pinned clock and no live network. const config = { dataSource: 'real', api: { baseUrl: 'https://backend.example/api/cockpit/v1' } } const window = eventTimeWindow('LAST_90_DAYS', Date.parse('2026-10-13T12:34:00+08:00')) const requests = [] const payloadFor = (url) => { const params = new URL(url).searchParams const start = params.get('startTime') const end = params.get('endTime') assert.ok(start && end, 'backend must receive actual times') assert.ok(Date.parse(end) - Date.parse(start) < 31 * 86400000) const rows = [] for (let t = Math.floor(Date.parse(start) / 3600000) * 3600000; t <= Date.parse(end); t += 3600000) rows.push({ timeBucket: new Date(t).toISOString(), eventCount: 1 }) return { range: { type: 'TREND_WINDOW', startTime: start, endTime: end }, rows } } const respond = (body) => ({ ok: true, status: 200, json: async () => ({ code: 200, data: body }) }) const result = await fetchEventHistory({ ...window, rangeType: 'LAST_90_DAYS', mode: 'WARNING', config, fetchImpl: async (url) => { requests.push(url); return respond(payloadFor(url)) } }) assert.equal(result.state, 'success') assert.equal(result.data.trend.length, 2161) assert.equal(new Set(result.data.trend.map((row) => row.bucket)).size, 2161) assert.equal(result.data.range.start, window.startTime) assert.ok(requests.length > 1) const mismatch = await fetchEventHistory({ ...window, rangeType: 'LAST_90_DAYS', config, fetchImpl: async (url) => respond({ ...payloadFor(url), range: { startTime: '2026-01-01T00:00:00Z', endTime: '2026-01-02T00:00:00Z' } }) }) assert.equal(mismatch.state, 'failed', 'wrong echoed window must not pass') const partialFailure = await fetchEventHistory({ ...window, rangeType: 'LAST_90_DAYS', config, fetchImpl: async (url) => new URL(url).searchParams.get('startTime') === window.startTime ? { ok: false, status: 500 } : respond(payloadFor(url)) }) assert.equal(partialFailure.state, 'failed', 'one failed window must not be silently omitted') const mustNotFetch = () => { throw new Error('unsupported aggregate must not request backend') } assert.equal((await fetchHandlingEfficiency({ ...window, config, fetchImpl: mustNotFetch })).state, 'unavailable') assert.equal((await fetchEventAnalysis({ ...window, config, fetchImpl: mustNotFetch })).state, 'unavailable') const flat = trendBlock('flat', Array.from({ length: 90 }, (_, i) => ({ key: String(i), label: String(i), count: 0 })), { chart: 'line' }) assert.equal(flat.linePoints.filter((point) => point.annotate).length, 1) assert.equal(flat.axisIndices.length, 4) assert.deepEqual(flat.yTicks, [{ value: 0, pct: 0 }]) const oddPeak = trendBlock('odd', [{ key: 'a', count: 0 }, { key: 'b', count: 3 }], { chart: 'line' }) assert.equal(oddPeak.yTicks[1].value, 1.5) const visuals = legendVisuals() const objects = visuals.filter((visual) => visual.key !== 'UNASSOCIATED').map((visual) => ({ code: visual.key, kind: 'monitoring-point', status: visual.key, coordinate: { renderable: true }, longitude: 110, latitude: 28 })) objects.push({ code: 'DEVICE', kind: 'unassociated-equipment', coordinate: { renderable: true }, longitude: 110, latitude: 28 }) const plan = planPointMarkers(objects) const legend = pointLegend(plan.byStatus) assert.equal(legendMatchesMarkers(legend, plan.markers).ok, true) const corrupted = legend.map((row) => row.key === 'OFFLINE' ? { ...row, tone: 'lv4' } : row) assert.equal(legendMatchesMarkers(corrupted, plan.markers).ok, false, 'wrong color mapping must fail') console.log(`Final acceptance passed: ${components} Vue SFC syntax/templates/styles; history request/merge and negative cases; flat trend labels; map legend adversarial check.`)