| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 |
- 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)
- assert.match(tokens, /--panel-top:\s*rgba\(2,\s*9,\s*27,\s*0\.88\)/)
- // 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.`)
|