t56-latest-live.mjs 4.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import assert from 'node:assert/strict'
  2. import { fetchEquipmentDetailAggregate } from '../../../src/modules/cockpit/services/detailApi.js'
  3. import { metricRows, sectionHasContent, sectionPlaceholder } from '../../../src/modules/cockpit/domain/equipmentDetailView.js'
  4. const baseUrl = process.argv[2]
  5. assert.ok(baseUrl, 'Pass the existing cockpit read-only API base URL')
  6. const deviceCode = process.argv[3] || '21119740'
  7. const request = (url, options) => fetch(url, { ...options, signal: AbortSignal.timeout(15000) })
  8. // Independent existing source API, rather than copying the cockpit adapter's query.
  9. const factors = [
  10. { node: 1, code: 'ENV_SUSPENDED_SOLIDS', name: '悬浮物', field: 'floatValue', unit: 'mg/L' },
  11. { node: 2, code: 'ENV_COD', name: 'COD', field: 'hum', unit: 'mg/L' },
  12. { node: 3, code: 'ENV_AMMONIA_NITROGEN', name: '氨氮', field: 'hum', unit: 'mg/L' },
  13. { node: 4, code: 'ENV_CONDUCTIVITY', name: '电导率', field: 'hum', unit: 'μS/cm' },
  14. { node: 5, code: 'ENV_PH', name: 'pH', field: 'hum', unit: '' },
  15. ]
  16. const sources = deviceCode === '21119740' ? await Promise.all(factors.map(async (factor) => {
  17. const url = new URL('/api/env/visualization/latest', baseUrl)
  18. url.searchParams.set('deviceId', deviceCode)
  19. url.searchParams.set('nodeId', String(factor.node))
  20. const response = await request(url)
  21. assert.ok(response.ok, `Source node ${factor.node}: HTTP ${response.status}`)
  22. const body = await response.json()
  23. assert.equal(body.code, 200, `Source node ${factor.node} must succeed independently`)
  24. const record = body.data || null
  25. if (record) {
  26. assert.equal(record.deviceId, Number(deviceCode), 'Source device identity mismatch')
  27. assert.equal(record.nodeId, factor.node, 'Source node identity mismatch')
  28. }
  29. return { factor, record }
  30. })) : []
  31. const result = await fetchEquipmentDetailAggregate(deviceCode, {
  32. config: { dataSource: 'real', api: { baseUrl } }, fetchImpl: request,
  33. })
  34. const metrics = result.data?.sections?.metrics
  35. const rows = metricRows(metrics?.data)
  36. console.log(JSON.stringify({ deviceCode, aggregate: result.state, metrics: metrics?.state,
  37. placeholder: sectionPlaceholder(metrics), rows,
  38. sourceNodes: sources.map(({ factor, record }) => ({ node: factor.node, recordId: record?.id,
  39. value: record?.[factor.field], temperature: record?.tem, createdAt: record?.createTime })),
  40. }, null, 2))
  41. assert.ok(sectionHasContent(metrics), `${deviceCode}: latest metrics must render content, not ${sectionPlaceholder(metrics)}`)
  42. assert.ok(rows.some((row) => row.value !== '—'), `${deviceCode}: expected at least one real reading`)
  43. assert.ok(rows.every((row) => row.name && row.sampledAt), 'Every metric must retain its name and source time')
  44. function assertReading(code, name, value, unit, sourceTime) {
  45. const row = rows.find((item) => item.code === code)
  46. assert.ok(row, `Missing real source metric ${code}`)
  47. assert.equal(row.name, name)
  48. assert.equal(row.unit, unit, `${code}: source unit mismatch`)
  49. const missing = value === null || value === undefined || String(value).trim() === '' || !Number.isFinite(Number(value))
  50. if (missing) assert.equal(row.value, '—', `${code}: missing source value must not become zero`)
  51. else assert.equal(Number(row.value), Number(value), `${code}: source and cockpit readings differ`)
  52. assert.equal(row.sampledAtLabel, '数据入库时间', `${code}: create_time must not be called sampling time`)
  53. const sourceAt = /(?:Z|[+-]\d{2}:\d{2})$/.test(sourceTime) ? sourceTime : `${sourceTime}+08:00`
  54. const sourceMillis = Date.parse(sourceAt)
  55. const metricMillis = Date.parse(row.sampledAt)
  56. assert.ok(Number.isFinite(sourceMillis), `${code}: invalid source timestamp`)
  57. assert.ok(Number.isFinite(metricMillis), `${code}: invalid cockpit timestamp`)
  58. assert.equal(metricMillis, sourceMillis, `${code}: latest node source time differs`)
  59. }
  60. for (const { factor, record } of sources) {
  61. if (!record) {
  62. assert.ok(!rows.some((row) => row.code === factor.code), `Node ${factor.node}: no source must not produce a reading`)
  63. continue
  64. }
  65. assertReading(factor.code, factor.name, record[factor.field], factor.unit, record.createTime)
  66. assertReading(`ENV_NODE_${factor.node}_TEMPERATURE`, `${factor.name}节点温度`, record.tem, '℃', record.createTime)
  67. }
  68. console.log('PASS real detail API -> latest metric rows, independently checked against each available source node')