| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 |
- import fs from 'node:fs/promises'
- import path from 'node:path'
- import { fetchMapSnapshot } from '../../../src/modules/cockpit/services/mapApi.js'
- import { fetchBaseArchives } from '../../../src/modules/cockpit/services/infrastructureApi.js'
- import { fetchCurrentEvents } from '../../../src/modules/cockpit/services/eventApi.js'
- const base = 'http://192.168.110.96:8301/api/cockpit/v1'
- const output = path.resolve('.scratch/cockpit-real-data-wayfinding/verify/artifacts/t18')
- const observations = []
- const checks = []
- async function get(route) {
- const started = Date.now()
- try {
- const response = await fetch(base + route, { signal: AbortSignal.timeout(15000) })
- const body = await response.json()
- observations.push({ route, http: response.status, code: body.code, ms: Date.now() - started,
- readOnly: body.data?.readOnly, keys: Object.keys(body.data || {}),
- error: body.code !== 200 ? String(body.msg || '').slice(0, 450) : undefined,
- counts: body.data?.counts, totals: body.data?.totals, pagination: body.data?.pagination,
- mapFilter: body.data?.mapFilter, freshness: body.data?.freshness, state: body.data?.state,
- sectionStates: Object.fromEntries(Object.entries(body.data?.sections || {}).map(([key, value]) => [key, value?.state])),
- canonicalCode: body.data?.deviceCode || body.data?.pointCode,
- workOrders: Array.isArray(body.data?.workOrders) ? body.data.workOrders.map(row => ({ code: row.workOrderCode, stateCode: row.stateCode, associatedWithCurrentEvent: row.associatedWithCurrentEvent })) : undefined,
- })
- return { http: response.status, ...body }
- } catch (error) {
- observations.push({ route, error: String(error.message) })
- return { code: -1 }
- }
- }
- function check(id, passed, evidence) { checks.push({ id, status: passed ? 'passed' : 'failed', evidence }) }
- const snapshot = await get('/map-snapshot?pageSize=1000')
- const points = snapshot.data?.monitoringPoints || []
- const devices = snapshot.data?.unassociatedEquipment || []
- check('map-total', snapshot.data?.total === points.length + devices.length, { total: snapshot.data?.total, points: points.length, unassociated: devices.length })
- check('map-eight-status-buckets', snapshot.data?.pointStatusDistribution?.length === 8, { actual: snapshot.data?.pointStatusDistribution?.length ?? 'missing' })
- check('no-unverified-rendering', [...points, ...devices].every(row => row.coordinateValidation?.state === 'VALID' || row.coordinateValidation?.renderable === false), { blocked: [...points, ...devices].filter(row => !row.coordinateValidation?.renderable).length })
- const first = await get('/map-snapshot?page=1&pageSize=2')
- const second = await get('/map-snapshot?page=2&pageSize=2&snapshotAt=' + encodeURIComponent(first.data?.snapshotAt || ''))
- check('direct-snapshot-page-two', second.code === 200 && first.data?.snapshotAt === second.data?.snapshotAt && first.data?.total === second.data?.total, { first: first.code, second: second.code, total: first.data?.total })
- const missing = await get('/map-snapshot?page=2&pageSize=2')
- check('missing-token-rejected', missing.http === 400 && missing.data?.category === 'SNAPSHOT_EXPIRED', { http: missing.http, category: missing.data?.category })
- for (const district of ['0', '1', '2', 'UNASSIGNED']) {
- const result = await get('/map-snapshot?pageSize=1000&district=' + district)
- check('district-' + district, result.code === 200 && result.data?.mapFilter?.districtFilter === (district === 'UNASSIGNED' ? 'UNASSIGNED' : 'ASSIGNED'), { total: result.data?.total, echo: result.data?.mapFilter })
- }
- for (const industry of ['GAS', 'WATER_SUPPLY', 'DRAINAGE', 'MANHOLE']) {
- for (const route of ['/map-snapshot?pageSize=1000&industry=', '/monitoring/overview?industry=', '/monitoring/distribution?pageSize=1000&industry=', '/infrastructure/base-archives?industry=', '/infrastructure/management-information?industry=', '/overview/monitoring?industry=']) {
- const result = await get(route + industry)
- check(route + industry, result.code === 200 && result.data?.readOnly === true, { http: result.http, code: result.code })
- }
- }
- for (const status of ['ALARM', 'WARNING', 'WORK_ORDER', 'NORMAL', 'OFFLINE', 'EMPTY', 'UNKNOWN', 'UNASSOCIATED', 'ALARM,OFFLINE']) {
- const result = await get('/map-snapshot?pageSize=1000&pointStatus=' + status)
- check('status-' + status, result.code === 200 && result.data?.mapFilter?.pointStatus != null, { total: result.data?.total, echo: result.data?.mapFilter?.pointStatus ?? 'missing' })
- }
- for (const route of ['/map-snapshot?district=9', '/map-snapshot?industry=BOGUS', '/map-snapshot?pointStatus=BOGUS', '/map-snapshot?page=0', '/map-snapshot?pageSize=1001', '/monitoring/distribution?pointStatus=UNKNOWN', '/monitoring/distribution?pointStatus=UNASSOCIATED', '/events/current?mode=BOGUS']) {
- const result = await get(route)
- check('invalid-' + route, route.includes('UNKNOWN') || route.includes('UNASSOCIATED') ? result.code === 200 : result.http === 400, { http: result.http, code: result.code, category: result.data?.category })
- }
- for (const point of points) {
- const result = await get('/monitoring-points/' + encodeURIComponent(point.pointCode) + '?snapshotAt=' + encodeURIComponent(snapshot.data.snapshotAt))
- check('point-' + point.pointCode, result.code === 200, { code: result.code, deviceCodes: result.data?.devices?.map(row => row.deviceCode), state: result.data?.pointStatus })
- }
- const orderCodes = new Set()
- for (const device of devices) {
- for (const suffix of ['archive', 'metrics/current', 'metrics/history', 'events/current', 'events/history', 'work-orders', 'detail-aggregate', 'freshness']) {
- const window = suffix.endsWith('/history') ? '?startTime=' + encodeURIComponent('2026-10-03T00:00:00+08:00') + '&endTime=' + encodeURIComponent('2026-10-10T23:59:59+08:00') : ''
- const result = await get('/equipments/' + encodeURIComponent(device.deviceCode) + '/' + suffix + window)
- check('device-' + device.deviceCode + '-' + suffix, result.code === 200, { http: result.http, code: result.code, state: result.data?.state })
- for (const order of Array.isArray(result.data?.workOrders) ? result.data.workOrders : []) if (order.workOrderCode) orderCodes.add(order.workOrderCode)
- }
- }
- for (const code of orderCodes) {
- const result = await get('/work-orders/' + encodeURIComponent(code) + '/timeline')
- check('timeline-' + code, result.code === 200, { code: result.code })
- }
- for (const route of ['/monitoring/overview', '/monitoring/distribution', '/monitoring/situation']) {
- const result = await get(route)
- check('monitoring-createdAt-' + route, result.code === 200 && Object.prototype.hasOwnProperty.call(result.data || {}, 'createdAt'), { code: result.code, createdAt: result.data?.createdAt ?? null })
- }
- const config = { dataSource: 'real', api: { baseUrl: base } }
- const rawArchive = await get('/infrastructure/base-archives')
- const adaptedArchive = await fetchBaseArchives({ config })
- check('frontend-envelope-archive', adaptedArchive.state === 'success' && adaptedArchive.data?.facilityCount === rawArchive.data?.totals?.facilityCount, { state: adaptedArchive.state, adapted: adaptedArchive.data?.facilityCount, raw: rawArchive.data?.totals?.facilityCount })
- const adaptedMap = await fetchMapSnapshot({ config, pageSize: 1000 })
- check('frontend-envelope-map', adaptedMap.data?.total === snapshot.data?.total && !!adaptedMap.data?.snapshotAt, { adaptedTotal: adaptedMap.data?.total, rawTotal: snapshot.data?.total, snapshot: adaptedMap.data?.snapshotAt })
- const rawEvents = await get('/events/current?mode=ALARM')
- const adaptedEvents = await fetchCurrentEvents({ config, mode: 'ALARM' })
- check('frontend-business-error-not-success', rawEvents.code === 200 || ['failed', 'unavailable'].includes(adaptedEvents.state), { rawCode: rawEvents.code, adaptedState: adaptedEvents.state })
- const report = { timestamp: new Date().toISOString(), base, readOnly: true, observations, checks,
- summary: { requests: observations.length, passed: checks.filter(row => row.status === 'passed').length, failed: checks.filter(row => row.status === 'failed').length },
- scope: 'Live GET contracts and frontend real adapters; no database writes, no mock responses, no human verdict.' }
- await fs.mkdir(output, { recursive: true })
- await fs.writeFile(path.join(output, 'live-audit-results.json'), JSON.stringify(report, null, 2))
- console.log(JSON.stringify(report.summary))
- for (const row of checks.filter(row => row.status === 'failed')) console.log('FAIL ' + row.id + ' ' + JSON.stringify(row.evidence))
- process.exitCode = report.summary.failed ? 1 : 0
|