| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 |
- import assert from 'node:assert/strict'
- import { fetchEquipmentDetailAggregate } from '../../../src/modules/cockpit/services/detailApi.js'
- import { metricRows, sectionHasContent, sectionPlaceholder } from '../../../src/modules/cockpit/domain/equipmentDetailView.js'
- const baseUrl = process.argv[2]
- assert.ok(baseUrl, 'Pass the existing cockpit read-only API base URL')
- const deviceCode = process.argv[3] || '21119740'
- const request = (url, options) => fetch(url, { ...options, signal: AbortSignal.timeout(15000) })
- // Independent existing source API, rather than copying the cockpit adapter's query.
- const factors = [
- { node: 1, code: 'ENV_SUSPENDED_SOLIDS', name: '悬浮物', field: 'floatValue', unit: 'mg/L' },
- { node: 2, code: 'ENV_COD', name: 'COD', field: 'hum', unit: 'mg/L' },
- { node: 3, code: 'ENV_AMMONIA_NITROGEN', name: '氨氮', field: 'hum', unit: 'mg/L' },
- { node: 4, code: 'ENV_CONDUCTIVITY', name: '电导率', field: 'hum', unit: 'μS/cm' },
- { node: 5, code: 'ENV_PH', name: 'pH', field: 'hum', unit: '' },
- ]
- const sources = deviceCode === '21119740' ? await Promise.all(factors.map(async (factor) => {
- const url = new URL('/api/env/visualization/latest', baseUrl)
- url.searchParams.set('deviceId', deviceCode)
- url.searchParams.set('nodeId', String(factor.node))
- const response = await request(url)
- assert.ok(response.ok, `Source node ${factor.node}: HTTP ${response.status}`)
- const body = await response.json()
- assert.equal(body.code, 200, `Source node ${factor.node} must succeed independently`)
- const record = body.data || null
- if (record) {
- assert.equal(record.deviceId, Number(deviceCode), 'Source device identity mismatch')
- assert.equal(record.nodeId, factor.node, 'Source node identity mismatch')
- }
- return { factor, record }
- })) : []
- const result = await fetchEquipmentDetailAggregate(deviceCode, {
- config: { dataSource: 'real', api: { baseUrl } }, fetchImpl: request,
- })
- const metrics = result.data?.sections?.metrics
- const rows = metricRows(metrics?.data)
- console.log(JSON.stringify({ deviceCode, aggregate: result.state, metrics: metrics?.state,
- placeholder: sectionPlaceholder(metrics), rows,
- sourceNodes: sources.map(({ factor, record }) => ({ node: factor.node, recordId: record?.id,
- value: record?.[factor.field], temperature: record?.tem, createdAt: record?.createTime })),
- }, null, 2))
- assert.ok(sectionHasContent(metrics), `${deviceCode}: latest metrics must render content, not ${sectionPlaceholder(metrics)}`)
- assert.ok(rows.some((row) => row.value !== '—'), `${deviceCode}: expected at least one real reading`)
- assert.ok(rows.every((row) => row.name && row.sampledAt), 'Every metric must retain its name and source time')
- function assertReading(code, name, value, unit, sourceTime) {
- const row = rows.find((item) => item.code === code)
- assert.ok(row, `Missing real source metric ${code}`)
- assert.equal(row.name, name)
- assert.equal(row.unit, unit, `${code}: source unit mismatch`)
- const missing = value === null || value === undefined || String(value).trim() === '' || !Number.isFinite(Number(value))
- if (missing) assert.equal(row.value, '—', `${code}: missing source value must not become zero`)
- else assert.equal(Number(row.value), Number(value), `${code}: source and cockpit readings differ`)
- assert.equal(row.sampledAtLabel, '数据入库时间', `${code}: create_time must not be called sampling time`)
- const sourceAt = /(?:Z|[+-]\d{2}:\d{2})$/.test(sourceTime) ? sourceTime : `${sourceTime}+08:00`
- const sourceMillis = Date.parse(sourceAt)
- const metricMillis = Date.parse(row.sampledAt)
- assert.ok(Number.isFinite(sourceMillis), `${code}: invalid source timestamp`)
- assert.ok(Number.isFinite(metricMillis), `${code}: invalid cockpit timestamp`)
- assert.equal(metricMillis, sourceMillis, `${code}: latest node source time differs`)
- }
- for (const { factor, record } of sources) {
- if (!record) {
- assert.ok(!rows.some((row) => row.code === factor.code), `Node ${factor.node}: no source must not produce a reading`)
- continue
- }
- assertReading(factor.code, factor.name, record[factor.field], factor.unit, record.createTime)
- assertReading(`ENV_NODE_${factor.node}_TEMPERATURE`, `${factor.name}节点温度`, record.tem, '℃', record.createTime)
- }
- console.log('PASS real detail API -> latest metric rows, independently checked against each available source node')
|