package utils import ( "testing" "github.com/AFASystems/presence/internal/pkg/model" ) func TestParseADFast(t *testing.T) { tests := []struct { name string input []byte expected [][2]int }{ { name: "Empty input", input: []byte{}, expected: [][2]int{}, }, { name: "Single AD structure", input: []byte{0x02, 0x01, 0x06}, expected: [][2]int{{0, 2}}, }, { name: "Multiple AD structures", input: []byte{0x02, 0x01, 0x06, 0x03, 0x02, 0x01, 0x02}, expected: [][2]int{{0, 2}, {3, 6}}, }, { name: "Complex AD structures", input: []byte{0x02, 0x01, 0x06, 0x1A, 0xFF, 0x4C, 0x00, 0x02, 0x15, 0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2, 0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xC5}, expected: [][2]int{{0, 2}, {2, 28}}, }, { name: "Zero length AD structure", input: []byte{0x00, 0x01, 0x06, 0x03, 0x02, 0x01, 0x02}, expected: [][2]int{{2, 5}}, }, { name: "AD structure exceeding bounds", input: []byte{0x05, 0x01, 0x06}, expected: [][2]int{}, }, { name: "Incomplete AD structure", input: []byte{0x03, 0x01}, expected: [][2]int{}, }, { name: "Valid then invalid structure", input: []byte{0x02, 0x01, 0x06, 0xFF, 0x01, 0x06}, expected: [][2]int{{0, 2}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ParseADFast(tt.input) if len(result) != len(tt.expected) { t.Errorf("ParseADFast() length = %v, expected %v", len(result), len(tt.expected)) return } for i, r := range result { if r[0] != tt.expected[i][0] || r[1] != tt.expected[i][1] { t.Errorf("ParseADFast()[%d] = %v, expected %v", i, r, tt.expected[i]) } } }) } } func TestRemoveFlagBytes(t *testing.T) { tests := []struct { name string input []byte expected []byte }{ { name: "Empty input", input: []byte{}, expected: []byte{}, }, { name: "Single byte input", input: []byte{0x01}, expected: []byte{0x01}, }, { name: "No flag bytes", input: []byte{0x02, 0x01, 0x06, 0x03, 0x02, 0x01}, expected: []byte{0x02, 0x01, 0x06, 0x03, 0x02, 0x01}, }, { name: "With flag bytes", input: []byte{0x02, 0x01, 0x06, 0x1A, 0xFF, 0x4C, 0x00, 0x02}, expected: []byte{0x1A, 0xFF, 0x4C, 0x00, 0x02}, }, { name: "Flag type is 0x01", input: []byte{0x02, 0x01, 0x06, 0x05, 0x01, 0x02, 0x03, 0x04}, expected: []byte{0x05, 0x01, 0x02, 0x03, 0x04}, }, { name: "Flag type is not 0x01", input: []byte{0x02, 0x02, 0x06, 0x05, 0x01, 0x02, 0x03, 0x04}, expected: []byte{0x02, 0x02, 0x06, 0x05, 0x01, 0x02, 0x03, 0x04}, }, { name: "Length exceeds bounds", input: []byte{0xFF, 0x01, 0x06}, expected: []byte{0xFF, 0x01, 0x06}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := RemoveFlagBytes(tt.input) if len(result) != len(tt.expected) { t.Errorf("RemoveFlagBytes() length = %v, expected %v", len(result), len(tt.expected)) return } for i, b := range result { if b != tt.expected[i] { t.Errorf("RemoveFlagBytes()[%d] = %v, expected %v", i, b, tt.expected[i]) } } }) } } func TestIsValidADStructure(t *testing.T) { tests := []struct { name string data []byte expected bool }{ { name: "Empty data", data: []byte{}, expected: false, }, { name: "Single byte", data: []byte{0x01}, expected: false, }, { name: "Valid minimal structure", data: []byte{0x01, 0x01}, expected: true, }, { name: "Valid structure", data: []byte{0x02, 0x01, 0x06}, expected: true, }, { name: "Zero length", data: []byte{0x00, 0x01, 0x06}, expected: false, }, { name: "Length exceeds data", data: []byte{0x05, 0x01, 0x06}, expected: false, }, { name: "Length exactly matches", data: []byte{0x02, 0x01, 0x06}, expected: true, }, { name: "Large valid structure", data: []byte{0x1F, 0xFF, 0x4C, 0x00, 0x02, 0x15, 0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2, 0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xC5, 0x01, 0x02, 0x03, 0x04, 0x05}, expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := isValidADStructure(tt.data) if result != tt.expected { t.Errorf("isValidADStructure() = %v, expected %v", result, tt.expected) } }) } } func TestCheckIngics(t *testing.T) { tests := []struct { name string ad []byte expected bool }{ { name: "Valid Ingics beacon", ad: []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0x12, 0x34, 0x01}, expected: true, }, { name: "Invalid - too short", ad: []byte{0x05, 0xFF, 0x59, 0x00}, expected: false, }, { name: "Invalid - wrong manufacturer ID", ad: []byte{0x08, 0xFF, 0x59, 0x01, 0x80, 0xBC, 0x12, 0x34, 0x01}, expected: false, }, { name: "Invalid - wrong type", ad: []byte{0x08, 0xFE, 0x59, 0x00, 0x80, 0xBC, 0x12, 0x34, 0x01}, expected: false, }, { name: "Valid with minimum length", ad: []byte{0x06, 0xFF, 0x59, 0x00, 0x80, 0xBC}, expected: true, }, { name: "Empty data", ad: []byte{}, expected: false, }, { name: "Partial match only", ad: []byte{0x06, 0xFF, 0x59, 0x00, 0x80}, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := checkIngics(tt.ad) if result != tt.expected { t.Errorf("checkIngics() = %v, expected %v", result, tt.expected) } }) } } func TestParseIngicsState(t *testing.T) { tests := []struct { name string ad []byte expected model.BeaconEvent }{ { name: "Valid Ingics data", ad: []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0x34, 0x12, 0x05}, expected: model.BeaconEvent{ Battery: 0x1234, // 4660 in little endian Event: 0x05, Type: "Ingics", }, }, { name: "Zero battery", ad: []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0x00, 0x00, 0x00}, expected: model.BeaconEvent{ Battery: 0, Event: 0, Type: "Ingics", }, }, { name: "Max battery value", ad: []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0xFF, 0xFF, 0xFF}, expected: model.BeaconEvent{ Battery: 0xFFFF, Event: 0xFF, Type: "Ingics", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := parseIngicsState(tt.ad) if result.Battery != tt.expected.Battery { t.Errorf("parseIngicsState() Battery = %v, expected %v", result.Battery, tt.expected.Battery) } if result.Event != tt.expected.Event { t.Errorf("parseIngicsState() Event = %v, expected %v", result.Event, tt.expected.Event) } if result.Type != tt.expected.Type { t.Errorf("parseIngicsState() Type = %v, expected %v", result.Type, tt.expected.Type) } }) } } func TestCheckEddystoneTLM(t *testing.T) { tests := []struct { name string ad []byte expected bool }{ { name: "Valid Eddystone TLM", ad: []byte{0x12, 0x16, 0xAA, 0xFE, 0x20, 0x00, 0x01, 0x02, 0x03, 0x04}, expected: true, }, { name: "Invalid - too short", ad: []byte{0x03, 0x16, 0xAA}, expected: false, }, { name: "Invalid - wrong type", ad: []byte{0x12, 0x15, 0xAA, 0xFE, 0x20, 0x00, 0x01, 0x02, 0x03, 0x04}, expected: false, }, { name: "Invalid - wrong company ID", ad: []byte{0x12, 0x16, 0xAA, 0xFF, 0x20, 0x00, 0x01, 0x02, 0x03, 0x04}, expected: false, }, { name: "Invalid - wrong TLM type", ad: []byte{0x12, 0x16, 0xAA, 0xFE, 0x21, 0x00, 0x01, 0x02, 0x03, 0x04}, expected: false, }, { name: "Valid with minimum length", ad: []byte{0x04, 0x16, 0xAA, 0xFE, 0x20}, expected: true, }, { name: "Empty data", ad: []byte{}, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := checkEddystoneTLM(tt.ad) if result != tt.expected { t.Errorf("checkEddystoneTLM() = %v, expected %v", result, tt.expected) } }) } } func TestParseEddystoneState(t *testing.T) { tests := []struct { name string ad []byte expected model.BeaconEvent }{ { name: "Valid Eddystone TLM data", ad: []byte{0x12, 0x16, 0xAA, 0xFE, 0x20, 0x34, 0x12, 0x78, 0x56, 0x00}, expected: model.BeaconEvent{ Battery: 0x1234, // 4660 in big endian (note: different from Ingics) Type: "Eddystone", }, }, { name: "Zero battery", ad: []byte{0x12, 0x16, 0xAA, 0xFE, 0x20, 0x00, 0x00, 0x78, 0x56, 0x00}, expected: model.BeaconEvent{ Battery: 0, Type: "Eddystone", }, }, { name: "Max battery value", ad: []byte{0x12, 0x16, 0xAA, 0xFE, 0x20, 0xFF, 0xFF, 0x78, 0x56, 0x00}, expected: model.BeaconEvent{ Battery: 0xFFFF, Type: "Eddystone", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := parseEddystoneState(tt.ad) if result.Battery != tt.expected.Battery { t.Errorf("parseEddystoneState() Battery = %v, expected %v", result.Battery, tt.expected.Battery) } if result.Type != tt.expected.Type { t.Errorf("parseEddystoneState() Type = %v, expected %v", result.Type, tt.expected.Type) } }) } } func TestCheckMinewB7(t *testing.T) { tests := []struct { name string ad []byte expected bool }{ { name: "Valid Minew B7", ad: []byte{0x08, 0x16, 0xE1, 0xFF, 0x01, 0x02, 0x03, 0x04}, expected: true, }, { name: "Invalid - too short", ad: []byte{0x03, 0x16, 0xE1}, expected: false, }, { name: "Invalid - wrong type", ad: []byte{0x08, 0x15, 0xE1, 0xFF, 0x01, 0x02, 0x03, 0x04}, expected: false, }, { name: "Invalid - wrong company ID", ad: []byte{0x08, 0x16, 0xE1, 0xFE, 0x01, 0x02, 0x03, 0x04}, expected: false, }, { name: "Valid with minimum length", ad: []byte{0x04, 0x16, 0xE1, 0xFF}, expected: true, }, { name: "Empty data", ad: []byte{}, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := checkMinewB7(tt.ad) if result != tt.expected { t.Errorf("checkMinewB7() = %v, expected %v", result, tt.expected) } }) } } func TestLoopADStructures(t *testing.T) { tests := []struct { name string data []byte ranges [][2]int id string expected model.BeaconEvent }{ { name: "Ingics beacon found", data: []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0x34, 0x12, 0x05, 0x02, 0x01, 0x06}, ranges: [][2]int{{0, 8}, {8, 11}}, id: "test-beacon", expected: model.BeaconEvent{ ID: "test-beacon", Name: "test-beacon", Battery: 0x1234, Event: 0x05, Type: "Ingics", }, }, { name: "Eddystone beacon found", data: []byte{0x02, 0x01, 0x06, 0x12, 0x16, 0xAA, 0xFE, 0x20, 0x34, 0x12, 0x78, 0x56}, ranges: [][2]int{{0, 2}, {2, 14}}, id: "eddystone-test", expected: model.BeaconEvent{ ID: "eddystone-test", Name: "eddystone-test", Battery: 0x1234, Type: "Eddystone", }, }, { name: "Minew B7 beacon found", data: []byte{0x08, 0x16, 0xE1, 0xFF, 0x01, 0x02, 0x03, 0x04, 0x02, 0x01, 0x06}, ranges: [][2]int{{0, 8}, {8, 11}}, id: "minew-test", expected: model.BeaconEvent{ ID: "minew-test", Name: "minew-test", Type: "", // Minew B7 returns empty BeaconEvent }, }, { name: "No matching beacon type", data: []byte{0x02, 0x01, 0x06, 0x03, 0x02, 0x01, 0x02}, ranges: [][2]int{{0, 2}, {2, 5}}, id: "unknown-test", expected: model.BeaconEvent{}, }, { name: "Invalid AD structure", data: []byte{0x02, 0x01, 0x06, 0xFF, 0x01, 0x06}, ranges: [][2]int{{0, 2}, {2, 4}}, id: "invalid-test", expected: model.BeaconEvent{}, }, { name: "Empty data", data: []byte{}, ranges: [][2]int{}, id: "empty-test", expected: model.BeaconEvent{ ID: "empty-test", Name: "empty-test", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := LoopADStructures(tt.data, tt.ranges, tt.id) if result.ID != tt.expected.ID { t.Errorf("LoopADStructures() ID = %v, expected %v", result.ID, tt.expected.ID) } if result.Name != tt.expected.Name { t.Errorf("LoopADStructures() Name = %v, expected %v", result.Name, tt.expected.Name) } if result.Type != tt.expected.Type { t.Errorf("LoopADStructures() Type = %v, expected %v", result.Type, tt.expected.Type) } if result.Battery != tt.expected.Battery { t.Errorf("LoopADStructures() Battery = %v, expected %v", result.Battery, tt.expected.Battery) } }) } } func TestLoopADStructuresPriority(t *testing.T) { // Test that Ingics is checked first data := []byte{0x08, 0xFF, 0x59, 0x00, 0x80, 0xBC, 0x34, 0x12, 0x05, 0x12, 0x16, 0xAA, 0xFE, 0x20, 0x78, 0x56} ranges := [][2]int{{0, 8}, {8, 15}} result := LoopADStructures(data, ranges, "priority-test") // Should detect Ingics first, not Eddystone if result.Type != "Ingics" { t.Errorf("LoopADStructures() Type = %v, expected Ingics (priority test)", result.Type) } } // Benchmark tests func BenchmarkParseADFast(b *testing.B) { data := []byte{0x02, 0x01, 0x06, 0x1A, 0xFF, 0x4C, 0x00, 0x02, 0x15, 0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2, 0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xC5} for i := 0; i < b.N; i++ { ParseADFast(data) } } func BenchmarkRemoveFlagBytes(b *testing.B) { data := []byte{0x02, 0x01, 0x06, 0x1A, 0xFF, 0x4C, 0x00, 0x02, 0x15, 0xE2, 0xC5} for i := 0; i < b.N; i++ { RemoveFlagBytes(data) } }