package metrics import ( "encoding/json" "strings" "testing" ) func TestSnake(t *testing.T) { tests := map[string]string{ "soc": "soc", "enduranceMileage": "endurance_mileage", "leftFrontTirepressure": "left_front_tirepressure", "mainSettingTempNew": "main_setting_temp_new", "evSOC": "ev_soc", "evSOCValue": "ev_soc_value", "tboxVersion": "tbox_version", "gear2State": "gear2_state", "": "", } for in, want := range tests { if got := Snake(in); got != want { t.Errorf("Snake(%q) = %q, want %q", in, got, want) } } } func TestValidName(t *testing.T) { valid := []string{"byd_soc", "_x", "a1"} invalid := []string{"", "1abc", "byd-soc", "byd soc", "byd.soc"} for _, s := range valid { if !ValidName(s) { t.Errorf("ValidName(%q) = false, want true", s) } } for _, s := range invalid { if ValidName(s) { t.Errorf("ValidName(%q) = true, want false", s) } } } // TestAddPayloadCoercion covers the shapes BYD actually sends: JSON numbers, // numbers quoted as strings, booleans, and strings that are not numbers at all. func TestAddPayloadCoercion(t *testing.T) { var payload map[string]any raw := `{"elecPercent":82,"speed":"13.5","charging":true,"vin":"LC0","nested":{"a":1},"missing":null}` if err := json.Unmarshal([]byte(raw), &payload); err != nil { t.Fatal(err) } s := New() s.AddPayload("byd_", Labels{"vin": "LC0"}, payload) want := map[string]float64{ "byd_elec_percent": 82, "byd_speed": 13.5, "byd_charging": 1, } for name, value := range want { samples := s.byName[name] if len(samples) != 1 { t.Errorf("%s: got %d samples, want 1", name, len(samples)) continue } if samples[0].value != value { t.Errorf("%s = %v, want %v", name, samples[0].value, value) } } for _, name := range []string{"byd_vin", "byd_nested", "byd_missing"} { if _, ok := s.byName[name]; ok { t.Errorf("%s should not have become a metric", name) } } } func TestStringFormat(t *testing.T) { s := New() s.Add("byd_soc", Labels{"vin": "B", "name": "Second"}, 50) s.Add("byd_soc", Labels{"vin": "A", "name": "First"}, 82) s.Add("byd_scrape_success", nil, 1) got := s.String() want := strings.Join([]string{ "# TYPE byd_scrape_success gauge", "byd_scrape_success 1", "# TYPE byd_soc gauge", `byd_soc{name="First",vin="A"} 82`, `byd_soc{name="Second",vin="B"} 50`, "", }, "\n") if got != want { t.Errorf("String() =\n%s\nwant\n%s", got, want) } } // TestStringEmitsOneTypeLinePerName guards the rule that repeating a TYPE line // for the same metric makes the exposition invalid for strict parsers. func TestStringEmitsOneTypeLinePerName(t *testing.T) { s := New() for _, vin := range []string{"A", "B", "C"} { s.Add("byd_soc", Labels{"vin": vin}, 1) } if n := strings.Count(s.String(), "# TYPE byd_soc gauge"); n != 1 { t.Errorf("got %d TYPE lines for byd_soc, want 1", n) } } func TestStringIsStable(t *testing.T) { build := func() string { s := New() s.AddPayload("byd_", Labels{"vin": "A"}, map[string]any{ "soc": 1.0, "range": 2.0, "speed": 3.0, "temp": 4.0, }) return s.String() } // Map iteration order differs per run; the rendered output must not. first := build() for i := 0; i < 20; i++ { if got := build(); got != first { t.Fatalf("output is not stable across runs:\n%s\nvs\n%s", got, first) } } } func TestLabelEscaping(t *testing.T) { s := New() s.Add("byd_x", Labels{"name": `He said "hi"\`, "empty": ""}, 1) got := s.String() if !strings.Contains(got, `name="He said \"hi\"\\"`) { t.Errorf("quotes and backslashes not escaped: %s", got) } if strings.Contains(got, "empty") { t.Errorf("empty labels should be dropped: %s", got) } } func TestLen(t *testing.T) { s := New() if s.Len() != 0 { t.Errorf("Len() = %d on a new set, want 0", s.Len()) } s.Add("a", nil, 1) s.Add("a", Labels{"vin": "x"}, 2) s.Add("b", nil, 3) if s.Len() != 3 { t.Errorf("Len() = %d, want 3", s.Len()) } }