package api import ( "encoding/json" "net/http/httptest" "testing" ) func TestNormalizeControlMode(t *testing.T) { cases := map[string]string{ "off": "off", "own": "own", "proxy": "proxy", "OWN": "own", " Proxy": "proxy", "": "", // unset — cascade continues to the next layer "bogus": "", // unknown — treated as unset } for in, want := range cases { if got := normalizeControlMode(in); got != want { t.Errorf("normalizeControlMode(%q) = %q, want %q", in, got, want) } } } func TestControlIndexSetUserInvalidatesOldToken(t *testing.T) { ci := newControlIndex() ci.setUser("u1", map[string]ankerChargerBinding{"SN1": {Token: "tok-old"}}) if _, ok := ci.lookup("tok-old"); !ok { t.Fatal("tok-old should resolve after first set") } // Regenerate: same charger, new token. The old token must stop resolving. ci.setUser("u1", map[string]ankerChargerBinding{"SN1": {Token: "tok-new"}}) if _, ok := ci.lookup("tok-old"); ok { t.Error("tok-old should be invalidated after regeneration") } b, ok := ci.lookup("tok-new") if !ok || b.UserID != "u1" || b.Serial != "SN1" { t.Errorf("tok-new resolves to %+v (ok=%v), want u1/SN1", b, ok) } } func TestControlIndexIsolatesUsers(t *testing.T) { ci := newControlIndex() ci.setUser("u1", map[string]ankerChargerBinding{"SN1": {Token: "a"}}) ci.setUser("u2", map[string]ankerChargerBinding{"SN2": {Token: "b"}}) // Re-setting u1 must not touch u2's token. ci.setUser("u1", map[string]ankerChargerBinding{"SN1": {Token: "a2"}}) if _, ok := ci.lookup("b"); !ok { t.Error("u2's token should be untouched when u1 is updated") } } func TestAnkerBindingFor(t *testing.T) { raw := json.RawMessage(`{"ankerSolix":{"controlChargers":{"SN1":{"token":"xyz"}}}}`) if got := ankerBindingFor(raw, "SN1").Token; got != "xyz" { t.Errorf("binding token = %q, want xyz", got) } if got := ankerBindingFor(raw, "other").Token; got != "" { t.Errorf("unknown charger should have empty token, got %q", got) } if got := ankerBindingFor(nil, "SN1").Token; got != "" { t.Errorf("nil settings should have empty token, got %q", got) } } func TestExtractWSURL(t *testing.T) { cases := map[string]string{ `{"data":{"ocppUrl":"wss://ocpp.anker.com/CP1"}}`: "wss://ocpp.anker.com/CP1", `{"a":{"b":[{"endpoint":"ws://x/y"}]}}`: "ws://x/y", `{"data":{"note":"https://not-a-ws-url"}}`: "", `{"empty":true}`: "", } for in, want := range cases { if got := extractWSURL(json.RawMessage(in)); got != want { t.Errorf("extractWSURL(%s) = %q, want %q", in, got, want) } } } func TestOCPPEndpoint(t *testing.T) { s := &Server{} // Plain HTTP request → ws:// r := httptest.NewRequest("GET", "http://host.example/x", nil) r.Host = "host.example" if got := s.ocppEndpoint(r, "SN 1"); got != "ws://host.example/ocpp/SN%201" { t.Errorf("endpoint = %q", got) } // Behind a TLS-terminating proxy → wss:// r2 := httptest.NewRequest("GET", "http://host.example/x", nil) r2.Host = "host.example" r2.Header.Set("X-Forwarded-Proto", "https") if got := s.ocppEndpoint(r2, "SN1"); got != "wss://host.example/ocpp/SN1" { t.Errorf("tls endpoint = %q", got) } }