173 lines
5.9 KiB
Go
173 lines
5.9 KiB
Go
package realtime
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
|
)
|
|
|
|
func TestRealtimeKVFieldsFromGB32960ParsedDomains(t *testing.T) {
|
|
rows := realtimeKVFields(envelope.FrameEnvelope{
|
|
Protocol: envelope.ProtocolGB32960,
|
|
VIN: "VIN001",
|
|
EventTimeMS: 1000,
|
|
ReceivedAtMS: 1100,
|
|
EventID: "event-1",
|
|
}, map[string]any{
|
|
"header": map[string]any{"vin": "VIN001", "command": "0x02"},
|
|
"data_units": []any{
|
|
map[string]any{"type": "0x01", "name": "vehicle", "value": map[string]any{"soc_percent": 88.0}},
|
|
map[string]any{"type": "0x30", "name": "gd_fc_stack", "value": map[string]any{
|
|
"stack_count": 1,
|
|
"summaries": []any{
|
|
map[string]any{"stack_water_outlet_temp_c": 63, "hydrogen_inlet_pressure_kpa": 130},
|
|
},
|
|
}},
|
|
},
|
|
})
|
|
|
|
values := kvMap(rows)
|
|
if values["gb32960.header/vin"] != "VIN001" || values["gb32960.header/command"] != "0x02" {
|
|
t.Fatalf("gb32960 header kv missing: %#v", values)
|
|
}
|
|
if values["gb32960.vehicle/type"] != "0x01" || values["gb32960.vehicle/soc_percent"] != "88" {
|
|
t.Fatalf("vehicle soc kv missing: %#v", values)
|
|
}
|
|
if values["gb32960.gd_fc_stack/stack_water_outlet_temp_c"] != "63" ||
|
|
values["gb32960.gd_fc_stack/hydrogen_inlet_pressure_kpa"] != "130" {
|
|
t.Fatalf("stack pressure kv missing: %#v", values)
|
|
}
|
|
}
|
|
|
|
func TestBuildFieldsEnvelopeKeepsMappedAndCanonicalStatFields(t *testing.T) {
|
|
fieldsEnv, ok := BuildFieldsEnvelope(envelope.FrameEnvelope{
|
|
Protocol: envelope.ProtocolGB32960,
|
|
VIN: "VIN001",
|
|
MessageID: "0x02",
|
|
EventTimeMS: 1000,
|
|
ReceivedAtMS: 1100,
|
|
EventID: "event-1",
|
|
Fields: map[string]any{
|
|
"charge_status": 1,
|
|
envelope.FieldSOCPercent: 88.0,
|
|
envelope.FieldTotalMileageKM: 100.1,
|
|
},
|
|
Parsed: map[string]any{
|
|
"data_units": []any{
|
|
map[string]any{"type": "0x01", "name": "vehicle", "value": map[string]any{
|
|
"charge_status": 1,
|
|
"soc_percent": 88.0,
|
|
"total_mileage_km": 100.1,
|
|
}},
|
|
},
|
|
},
|
|
})
|
|
if !ok {
|
|
t.Fatal("BuildFieldsEnvelope() should emit mapped fields")
|
|
}
|
|
for _, bareKey := range []string{"charge_status"} {
|
|
if _, exists := fieldsEnv.Fields[bareKey]; exists {
|
|
t.Fatalf("fields envelope should not expose non-canonical bare env.Fields key %q: %#v", bareKey, fieldsEnv.Fields)
|
|
}
|
|
}
|
|
for _, mappedKey := range []string{
|
|
"gb32960.vehicle.charge_status",
|
|
"gb32960.vehicle.soc_percent",
|
|
"gb32960.vehicle.total_mileage_km",
|
|
} {
|
|
if _, exists := fieldsEnv.Fields[mappedKey]; !exists {
|
|
t.Fatalf("fields envelope missing mapped key %q: %#v", mappedKey, fieldsEnv.Fields)
|
|
}
|
|
}
|
|
for _, canonicalKey := range []string{envelope.FieldSOCPercent, envelope.FieldTotalMileageKM} {
|
|
if _, exists := fieldsEnv.Fields[canonicalKey]; !exists {
|
|
t.Fatalf("fields envelope should keep canonical stat key %q: %#v", canonicalKey, fieldsEnv.Fields)
|
|
}
|
|
if _, exists := fieldsEnv.ParsedFields[canonicalKey]; exists {
|
|
t.Fatalf("parsed fields should stay protocol-mapped without canonical duplicate %q: %#v", canonicalKey, fieldsEnv.ParsedFields)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRealtimeKVFieldsFromJT808ParsedFields(t *testing.T) {
|
|
jtRows := realtimeKVFields(envelope.FrameEnvelope{
|
|
Protocol: envelope.ProtocolJT808,
|
|
VIN: "VIN001",
|
|
Fields: map[string]any{
|
|
envelope.FieldLongitude: 121.1,
|
|
envelope.FieldLatitude: 30.2,
|
|
envelope.FieldTotalMileageKM: 10241.2,
|
|
},
|
|
}, map[string]any{
|
|
"header": map[string]any{"message_id": "0x0200", "phone": "13307795425"},
|
|
"location": map[string]any{
|
|
"longitude": 121.1,
|
|
"latitude": 30.2,
|
|
"total_mileage_km": 10241.2,
|
|
"additional": []any{map[string]any{"id": "0x01", "value_hex": "00077235"}},
|
|
},
|
|
})
|
|
jtValues := kvMap(jtRows)
|
|
if jtValues["jt808.header/message_id"] != "0x0200" || jtValues["jt808.header/phone"] != "13307795425" {
|
|
t.Fatalf("jt808 header kv missing: %#v", jtValues)
|
|
}
|
|
if jtValues["jt808.location/total_mileage_km"] != "10241.2" ||
|
|
jtValues["jt808.location/longitude"] != "121.1" ||
|
|
jtValues["jt808.location/additional.additional_1.id"] != "0x01" {
|
|
t.Fatalf("jt808 location kv missing: %#v", jtValues)
|
|
}
|
|
if jtValues["jt808.location/soc_percent"] != "" {
|
|
t.Fatalf("jt808 kv should not include standardized env.Fields-only values: %#v", jtValues)
|
|
}
|
|
}
|
|
|
|
func TestRealtimeKVFieldsFromYutongMQTTParsedFields(t *testing.T) {
|
|
mqttRows := realtimeKVFields(envelope.FrameEnvelope{
|
|
Protocol: envelope.ProtocolYutongMQTT,
|
|
VIN: "VIN002",
|
|
Fields: map[string]any{
|
|
envelope.FieldSOCPercent: 76,
|
|
"gear": 3,
|
|
},
|
|
}, map[string]any{
|
|
"endpoint": "yutong",
|
|
"topic": "/ytforward/shln/3",
|
|
"data": map[string]any{
|
|
"ACC_PEDAL_APT": 14,
|
|
"BATTERY_CAPACITY_SOC": 78.4,
|
|
"CURRENT_OF_FC": 54.9,
|
|
"HYDROGEN_LOW_PRESSURE": 1.41,
|
|
"TOTAL_MILEAGE": 119925000,
|
|
"fuelCellCoolInTempt": 59,
|
|
},
|
|
"root": map[string]any{
|
|
"device": "LMRKH9AC2R1004087",
|
|
"version": "1.0",
|
|
},
|
|
})
|
|
mqttValues := kvMap(mqttRows)
|
|
if mqttValues["yutong_mqtt.data/acc_pedal_apt"] != "14" ||
|
|
mqttValues["yutong_mqtt.data/current_of_fc"] != "54.9" ||
|
|
mqttValues["yutong_mqtt.data/hydrogen_low_pressure"] != "1.41" ||
|
|
mqttValues["yutong_mqtt.data/total_mileage"] != "119925000" ||
|
|
mqttValues["yutong_mqtt.data/fuelcellcoolintempt"] != "59" {
|
|
t.Fatalf("mqtt raw data kv missing: %#v", mqttValues)
|
|
}
|
|
if mqttValues["yutong_mqtt.root/device"] != "LMRKH9AC2R1004087" ||
|
|
mqttValues["yutong_mqtt.metadata/endpoint"] != "yutong" ||
|
|
mqttValues["yutong_mqtt.metadata/topic"] != "/ytforward/shln/3" {
|
|
t.Fatalf("mqtt metadata kv missing: %#v", mqttValues)
|
|
}
|
|
if mqttValues["yutong_mqtt.data/soc_percent"] != "" || mqttValues["yutong_mqtt.data/gear"] != "" {
|
|
t.Fatalf("mqtt kv should not include standardized env.Fields-only values: %#v", mqttValues)
|
|
}
|
|
}
|
|
|
|
func kvMap(rows []RealtimeKVField) map[string]string {
|
|
out := map[string]string{}
|
|
for _, row := range rows {
|
|
out[row.Domain+"/"+row.Field] = row.Value
|
|
}
|
|
return out
|
|
}
|