import { Client } from 'pg'; import config from '../../src/config/config.js'; import { DatabaseManager } from '../../src/db/databaseManager.js'; async function main() { const client = new Client({ host: config.db.host, port: config.db.port, user: config.db.user, password: config.db.password, database: config.db.database, }); await client.connect(); console.log('=== G4 Hot Smoke Test ===\n'); // 1. 检查新表是否存在 const tableExists = await client.query( `SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'heartbeat' AND c.relname = 'heartbeat_events_g4_hot'` ); if (tableExists.rowCount === 0) { console.error('FAIL: heartbeat.heartbeat_events_g4_hot 表不存在'); process.exit(1); } console.log('[OK] 新表 heartbeat_events_g4_hot 已存在'); // 2. 检查分区表类型 const parentKind = await client.query( `SELECT c.relkind AS kind FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'heartbeat' AND c.relname = 'heartbeat_events_g4_hot'` ); const kind = parentKind.rows?.[0]?.kind; console.log('[INFO] 父表 relkind:', kind, kind === 'p' ? '(分区表)' : '(非分区表)'); // 3. 列出已有分区 const partitions = await client.query( `SELECT c.relname AS partition FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid JOIN pg_class p ON p.oid = i.inhparent JOIN pg_namespace n ON n.oid = p.relnamespace WHERE n.nspname = 'heartbeat' AND p.relname = 'heartbeat_events_g4_hot' ORDER BY c.relname` ); console.log('[INFO] 现有分区:', partitions.rows.map((r) => r.partition)); // 4. 检查唯一索引 const indexes = await client.query( `SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = 'heartbeat' AND tablename = 'heartbeat_events_g4_hot' ORDER BY indexname` ); console.log('[INFO] 索引:'); for (const r of indexes.rows) { console.log(' -', r.indexname); } // 5. 使用 DatabaseManager 验证 COPY 写入 const dm = new DatabaseManager({ ...config.db, maxConnections: 1, g4HotHeartbeatEnabled: true }); await dm.connect(); const ts = Date.now(); const testEvent = { ts_ms: ts, hotel_id: 1, room_id: '101', device_id: 'smoke-g4hot-test', ip: '10.0.0.1', power_state: 1, guest_type: 0, cardless_state: 0, service_mask: 7, // bits 0,1,2 pms_state: 1, carbon_state: 0, device_count: 2, comm_seq: 1, insert_card: null, bright_g: 50, version: 1, elec_address: ['e1', 'e2'], air_address: ['ac1', 'ac2', 'ac3'], voltage: [220.1, 220.2], ampere: [1.1, 1.2], power: [242.0, 264.0], phase: ['A', 'B'], energy: [10.5, 11.5], sum_energy: [100.1, 200.2], state: [1, 2, 3], model: [1, 1, 2], speed: [3, 3, 1], set_temp: [26, 25, 24], now_temp: [27, 26, 25], solenoid_valve: [1, 0, 1], extra: { source: 'smoke-g4hot', power_carbon_on: 1.5, power_person_exist: 2.5 }, }; try { const result = await dm._insertEventsToTarget([testEvent], { tableName: config.db.g4HotTable ?? 'heartbeat.heartbeat_events_g4_hot', columns: dm._getG4HotColumns(), toRowValues: (e) => dm._g4HotToRowValues(e), ensurePartitions: false, logPrefix: '[g4hot-smoke]', missingPartitionTable: null, }); if (result.success) { console.log(`[OK] G4 Hot COPY 写入成功: ${result.insertedCount} 条`); } else { console.error('[FAIL] G4 Hot COPY 写入失败:', result.error?.message); if (result.error?.message?.includes('no partition')) { console.error(' 说明: 新表当日分区尚未创建,请先创建分区'); } } } catch (err) { console.error('[FAIL] G4 Hot 写入异常:', err.message); } // 6. 验证写入的数据 try { const readback = await client.query( `SELECT svc_01, svc_02, svc_03, svc_04, air_address_1, air_address_2, air_address_residual, elec_address_1, elec_address_2, power_carbon_on, power_person_exist, guid FROM heartbeat.heartbeat_events_g4_hot WHERE device_id = 'smoke-g4hot-test' AND ts_ms = $1`, [ts] ); if (readback.rowCount > 0) { const row = readback.rows[0]; console.log('[OK] 数据回读成功:'); console.log(' svc_01:', row.svc_01, '(期望 true)'); console.log(' svc_02:', row.svc_02, '(期望 true)'); console.log(' svc_03:', row.svc_03, '(期望 true)'); console.log(' svc_04:', row.svc_04, '(期望 false)'); console.log(' air_address_1:', row.air_address_1, '(期望 ac1)'); console.log(' air_address_2:', row.air_address_2, '(期望 ac2)'); console.log(' air_address_residual:', row.air_address_residual, '(期望 [ac3])'); console.log(' elec_address_1:', row.elec_address_1, '(期望 e1)'); console.log(' elec_address_2:', row.elec_address_2, '(期望 e2)'); console.log(' power_carbon_on:', row.power_carbon_on, '(期望 1.5)'); console.log(' power_person_exist:', row.power_person_exist, '(期望 2.5)'); console.log(' guid:', row.guid, '(期望 32 位无连接符小写)'); } else { console.warn('[WARN] 未读回数据(可能因分区缺失而写入失败)'); } } catch (err) { console.warn('[WARN] 数据回读失败:', err.message); } // 7. 清理测试数据 try { await client.query( `DELETE FROM heartbeat.heartbeat_events_g4_hot WHERE device_id = 'smoke-g4hot-test' AND ts_ms = $1`, [ts] ); console.log('[OK] 测试数据已清理'); } catch (err) { console.warn('[WARN] 清理失败:', err.message); } // 8. 验证配置摘要 console.log('\n=== 当前配置 ==='); console.log(' legacyHeartbeatEnabled:', config.db.legacyHeartbeatEnabled); console.log(' g4HotHeartbeatEnabled:', config.db.g4HotHeartbeatEnabled); console.log(' roomStatusEnabled:', config.db.roomStatusEnabled); console.log(' legacyTable:', config.db.legacyTable); console.log(' g4HotTable:', config.db.g4HotTable); await dm.disconnect(); await client.end(); console.log('\n=== Smoke Test 完成 ==='); } main().catch((err) => { console.error('smoke test failed:', err); process.exit(1); });