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