| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- import assert from 'node:assert/strict'
- import { createPinia, setActivePinia } from 'pinia'
- import { useDetailStore } from '../../../src/modules/cockpit/stores/detailStore.js'
- import { metricRows, metricsSourceText, sectionHasContent, sectionPlaceholder } from '../../../src/modules/cockpit/domain/equipmentDetailView.js'
- import { TIME_LABELS } from '../../../src/modules/cockpit/domain/fields.js'
- // Literal API-contract fixture, not a reimplementation of the backend selection.
- const readings = [
- { metricCode: 'ENV_COD', metricName: 'COD', value: 32.5, unit: 'mg/L', sampledAt: '2026-10-29T10:00:00+08:00', timeSource: 'CREATED_AT', freshness: 'STALE' },
- { metricCode: 'ENV_PH', metricName: 'pH', value: 0, unit: '', sampledAt: '2026-10-28T09:00:00+08:00', timeSource: 'CREATED_AT', freshness: 'CURRENT' },
- { metricCode: 'ENV_TEMPERATURE', metricName: '温度', value: null, unit: '℃', sampledAt: '2026-10-29T10:00:00+08:00', timeSource: 'CREATED_AT', freshness: 'STALE' },
- ]
- function aggregate(code, metrics, state = 'STALE') {
- return {
- deviceCode: code, aggregateState: 'READY',
- archive: { state: 'SUCCESS', data: { deviceCode: code, deviceName: '仁科环境监测器', deviceType: '15', deviceTypeName: '仁科环境监测器' } },
- metrics: { state, data: { deviceCode: code, state, stateReason: state === 'STALE' ? 'SAMPLE_OUTDATED' : 'READY', metrics } },
- warnings: { state: 'SUCCESS', data: [] }, alarms: { state: 'SUCCESS', data: [] },
- workOrders: { state: 'EMPTY', data: null }, timelines: { state: 'EMPTY', data: null },
- alarmWarningRelations: { state: 'EMPTY', data: null }, latestImage: { state: 'EMPTY', data: null },
- }
- }
- const paths = []
- setActivePinia(createPinia())
- const store = useDetailStore()
- store.configure({
- config: { dataSource: 'real', api: { baseUrl: 'http://fixture.invalid/api/cockpit/v1' } },
- fetchImpl: async (url) => {
- paths.push(url)
- assert.ok(url.endsWith('/equipments/21119740/detail-aggregate'))
- return Response.json({ code: 200, data: aggregate('21119740', readings) })
- },
- })
- await store.openDevice('21119740')
- const metrics = store.sections.equipmentAggregate.data.sections.metrics
- assert.ok(sectionHasContent(metrics), sectionPlaceholder(metrics))
- assert.equal(metrics.state, 'stale')
- assert.equal(metrics.retained, true)
- assert.equal(paths.length, 1, 'Populated typed aggregate must not fan out to a guessed trend endpoint')
- const rows = metricRows(metrics.data)
- assert.deepEqual(rows.map(({ name, value, unit, sampledAt }) => ({ name, value, unit, sampledAt })), [
- { name: 'COD', value: '32.5', unit: 'mg/L', sampledAt: '2026-10-29T10:00:00+08:00' },
- { name: 'pH', value: '0', unit: '', sampledAt: '2026-10-28T09:00:00+08:00' },
- { name: '温度', value: '—', unit: '℃', sampledAt: '2026-10-29T10:00:00+08:00' },
- ])
- assert.equal(rows[0].sampledAtLabel, TIME_LABELS.ingested, 'A source create_time must not be labeled as physical sampling time')
- assert.equal(metricsSourceText(metrics.data), '指标记录已超过配置周期')
- assert.deepEqual(rows.map((row) => row.freshnessLabel), ['已过期', '', '已过期'], 'Mixed fresh/stale factors need their own stale labels')
- // A slow previous device response must never overwrite the newly selected device.
- let releaseA
- const lateA = new Promise((resolve) => { releaseA = resolve })
- store.closeDetail()
- store.configure({ fetchImpl: async (url) => {
- if (url.endsWith('/equipments/A/detail-aggregate')) return lateA
- assert.ok(url.endsWith('/equipments/B/detail-aggregate'))
- return Response.json({ code: 200, data: aggregate('B', [
- { metricCode: 'PRESSURE', metricName: '压力', value: 0.6, unit: 'MPa', sampledAt: '2026-11-02T10:00:00+08:00', timeSource: 'REPORTED_AT' },
- ], 'SUCCESS') })
- } })
- const openingA = store.openDevice('A')
- await store.openDevice('B')
- releaseA(Response.json({ code: 200, data: aggregate('A', readings) }))
- await openingA
- assert.equal(store.selection.deviceCode, 'B')
- assert.equal(store.sections.equipmentAggregate.data.deviceCode, 'B')
- const bRows = metricRows(store.sections.equipmentAggregate.data.sections.metrics.data)
- assert.equal(bRows[0].value, '0.6')
- assert.equal(bRows[0].sampledAtLabel, TIME_LABELS.sampled)
- store.configure({ fetchImpl: async () => Response.json({ code: 200, data: aggregate('D-failed', [], 'FAILED') }) })
- await store.openDevice('D-failed')
- const failed = store.sections.equipmentAggregate.data.sections
- assert.equal(failed.metrics.state, 'failed')
- assert.equal(failed.archive.state, 'success')
- assert.equal(failed.warnings.state, 'success')
- assert.equal(failed.alarms.state, 'success')
- console.log('PASS real store -> typed metric rows: per-factor time, stale values, zero/null, time source, device race and independent sections')
|