Files
DriverVault/API Server/internal/plugins/builtin/ankersolix/rfidcards_test.go
T
tajniak81andClaude Opus 5 245870a96a The add and remove buttons, and the read that checks them
Anker documents neither rfid write, so the bodies are inferred from the
field names get_device_cards answers with, and every write re-reads the
list: what the card shows is what the account holds, never what an
undocumented endpoint claimed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 10:50:00 +02:00

100 lines
3.5 KiB
Go

package ankersolix
import (
"context"
"encoding/json"
"strings"
"testing"
)
// A card number typed with punctuation is the same card as the one the account
// stores bare — anything else deletes nothing and adds a duplicate.
func TestNormalizeCardNumber(t *testing.T) {
for _, tc := range []struct{ in, want string }{
{"4754DC2A", "4754DC2A"},
{"47:54:dc:2a", "4754DC2A"},
{" 47-54 dc 2a ", "4754DC2A"},
{"04dfe672151a90", "04DFE672151A90"},
{"", ""},
{" :- ", ""},
} {
if got := normalizeCardNumber(tc.in); got != tc.want {
t.Errorf("normalizeCardNumber(%q) = %q, want %q", tc.in, got, tc.want)
}
}
}
// The name a card gets when it is added without one, in the shape the Anker app
// writes so a card added here does not stand out in it.
func TestRfidCardLabel(t *testing.T) {
if got := rfidCardLabel("4754DC2A"); got != "RFID DC2A" {
t.Errorf("rfidCardLabel = %q, want %q", got, "RFID DC2A")
}
if got := rfidCardLabel("2A"); got != "RFID 2A" {
t.Errorf("short number: rfidCardLabel = %q, want %q", got, "RFID 2A")
}
}
func TestParseRfidCards(t *testing.T) {
body := []byte(`{"code":0,"data":{"list":[
{"alias_name":"RFID DC2A","card_number":"4754DC2A","create_time":1788187299},
{"alias_name":"RFID B93F","card_number":"DA7CB93F","create_time":1788187315}]}}`)
cards, err := parseRfidCards(body)
if err != nil {
t.Fatalf("parseRfidCards: %v", err)
}
if len(cards) != 2 {
t.Fatalf("got %d cards, want 2", len(cards))
}
if cards[0].Name != "RFID DC2A" || cards[0].Number != "4754DC2A" {
t.Fatalf("first card = %+v", cards[0])
}
if string(cards[0].Added) != "1788187299" {
t.Fatalf("create_time = %q, want it relayed as it arrived", cards[0].Added)
}
// A charger with no cards answers with an empty document, which is an answer
// and not a failure.
cards, err = parseRfidCards([]byte(`{"code":0,"data":{}}`))
if err != nil || len(cards) != 0 {
t.Fatalf("empty list: got %d cards, err %v", len(cards), err)
}
}
// Presence is what a write is judged by, and it is judged on the number rather
// than on how either side punctuated it.
func TestCardPresent(t *testing.T) {
cards := []rfidCard{{Number: "4754DC2A"}, {Number: "DA7CB93F"}}
if !cardPresent(cards, "47:54:dc:2a") {
t.Error("punctuated number: want present")
}
if cardPresent(cards, "DEADBEEF") {
t.Error("unknown number: want absent")
}
if cardPresent(nil, "4754DC2A") {
t.Error("empty charger: want absent")
}
}
// Neither write may reach the cloud without knowing which charger and which
// card: an endpoint nobody has documented is not one to send half a request to.
func TestCardWritesRefuseIncompleteRequests(t *testing.T) {
p := &Plugin{}
_ = p.Init(context.Background(), map[string]string{"email": "u@example.com", "password": "p"})
for _, action := range []string{"rfid-card-save", "rfid-card-delete"} {
if _, err := p.Invoke(context.Background(), action, nil); err == nil ||
!strings.Contains(err.Error(), "requires an sn") {
t.Errorf("%s without a serial: got %v, want an sn-required error", action, err)
}
// A serial but no card, and no card number to be found in punctuation
// either: both stop before anything is sent.
for _, params := range []string{`{"sn":"EVSN1"}`, `{"sn":"EVSN1","cardNumber":" :- "}`} {
_, err := p.Invoke(context.Background(), action, json.RawMessage(params))
if err == nil || !strings.Contains(err.Error(), "card number is required") {
t.Errorf("%s with %s: got %v, want a card-number-required error", action, params, err)
}
}
}
}