Test / test (push) Failing after 25m47s
Go port of the read path of pybyd 0.0.75 (the client behind jkaberg/hass-byd-vehicle): the bangcle white-box AES envelope, the signing/checkcode layer, login, vehicle list, and realtime readings. Scraping runs on a cron schedule rather than per Prometheus request because each reading wakes the car's T-Box and drains the traction battery; /metrics serves the last result. Every numeric field of the realtime payload becomes a gauge, so the exporter does not need a hand-maintained field list that goes stale when BYD changes the payload. Tests pin the wire format against pybyd byte-for-byte and drive the client end to end against a fake cloud that speaks the real protocol. Co-Authored-By: Claude Opus 5 <[email protected]>
110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
package byd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"regexp"
|
|
"strconv"
|
|
"testing"
|
|
)
|
|
|
|
// TestDeviceIsStable is the point of the whole seeded-derivation approach: a
|
|
// restart must not look like a new phone to BYD.
|
|
func TestDeviceIsStable(t *testing.T) {
|
|
a, err := NewDevice("[email protected]")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, err := NewDevice("[email protected]")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if a != b {
|
|
t.Errorf("same seed gave different devices:\n%+v\n%+v", a, b)
|
|
}
|
|
|
|
c, err := NewDevice("[email protected]")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.IMEI == a.IMEI {
|
|
t.Error("different seeds produced the same IMEI")
|
|
}
|
|
}
|
|
|
|
func TestDeviceLooksRealistic(t *testing.T) {
|
|
d, err := NewDevice("[email protected]")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !regexp.MustCompile(`^\d{15}$`).MatchString(d.IMEI) {
|
|
t.Errorf("IMEI %q is not 15 digits", d.IMEI)
|
|
}
|
|
if got, want := luhn(d.IMEI[:14]), string(d.IMEI[14]); got != want {
|
|
t.Errorf("IMEI %q fails the Luhn check (want check digit %s)", d.IMEI, got)
|
|
}
|
|
if !regexp.MustCompile(`^([0-9a-f]{2}:){5}[0-9a-f]{2}$`).MatchString(d.MAC) {
|
|
t.Errorf("MAC %q is malformed", d.MAC)
|
|
}
|
|
if !regexp.MustCompile(`^[0-9a-f]{32}$`).MatchString(d.IMEIMD5) {
|
|
t.Errorf("IMEIMD5 %q is not a lowercase MD5", d.IMEIMD5)
|
|
}
|
|
for name, v := range map[string]string{
|
|
"Model": d.Model, "MobileBrand": d.MobileBrand, "MobileModel": d.MobileModel,
|
|
"Mod": d.Mod, "SDK": d.SDK, "AndroidVer": d.AndroidVer,
|
|
} {
|
|
if v == "" {
|
|
t.Errorf("%s is empty", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDeviceMACIsLocallyAdministered keeps the generated address out of the
|
|
// space of real vendor MACs.
|
|
func TestDeviceMACIsLocallyAdministered(t *testing.T) {
|
|
for _, seed := range []string{"a", "b", "c", "d", "e"} {
|
|
d, err := NewDevice(seed)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
first, err := strconv.ParseInt(d.MAC[:2], 16, 16)
|
|
if err != nil {
|
|
t.Fatalf("MAC %q: %v", d.MAC, err)
|
|
}
|
|
if first&0x02 == 0 {
|
|
t.Errorf("MAC %q is not locally administered", d.MAC)
|
|
}
|
|
if first&0x01 != 0 {
|
|
t.Errorf("MAC %q is a multicast address", d.MAC)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDevicePoolIsUsable(t *testing.T) {
|
|
var pool []poolEntry
|
|
if err := json.Unmarshal(devicePoolJSON, &pool); err != nil {
|
|
t.Fatalf("device_pool.json is not valid JSON: %v", err)
|
|
}
|
|
if len(pool) < 2 {
|
|
t.Fatalf("device pool has %d entries; a pool of one defeats the point", len(pool))
|
|
}
|
|
for i, e := range pool {
|
|
if len(e.TACPrefix) != 8 {
|
|
t.Errorf("entry %d: TAC prefix %q is not 8 digits", i, e.TACPrefix)
|
|
}
|
|
if len(e.SDKOptions) == 0 || len(e.SDKOptions) != len(e.OSOptions) {
|
|
t.Errorf("entry %d: sdk_options and os_options must pair up (%d vs %d)",
|
|
i, len(e.SDKOptions), len(e.OSOptions))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLuhn(t *testing.T) {
|
|
// Known-good IMEIs; the 15th digit must be what luhn derives from the first 14.
|
|
for _, imei := range []string{"490154203237518", "356938035643809"} {
|
|
if got, want := luhn(imei[:14]), string(imei[14]); got != want {
|
|
t.Errorf("luhn(%s) = %s, want %s", imei[:14], got, want)
|
|
}
|
|
}
|
|
}
|