package decoder import ( "bytes" "testing" "github.com/AFASystems/presence/internal/pkg/common/appcontext" "github.com/AFASystems/presence/internal/pkg/model" "github.com/segmentio/kafka-go" ) func TestDecodeBeacon_EmptyData(t *testing.T) { // Setup appState := appcontext.NewAppState() mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} adv := model.BeaconAdvertisement{ ID: "test-beacon", Data: "", // Empty data } // Execute err := decodeBeacon(adv, appState, mockWriter, parserRegistry) // Assert if err != nil { t.Errorf("Expected no error for empty data, got %v", err) } if len(mockWriter.Messages) != 0 { t.Errorf("Expected no messages for empty data, got %d", len(mockWriter.Messages)) } } func TestDecodeBeacon_WhitespaceOnly(t *testing.T) { // Setup appState := appcontext.NewAppState() mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} adv := model.BeaconAdvertisement{ ID: "test-beacon", Data: " ", // Whitespace only } // Execute err := decodeBeacon(adv, appState, mockWriter, parserRegistry) // Assert if err != nil { t.Errorf("Expected no error for whitespace-only data, got %v", err) } if len(mockWriter.Messages) != 0 { t.Errorf("Expected no messages for whitespace-only data, got %d", len(mockWriter.Messages)) } } func TestDecodeBeacon_InvalidHex(t *testing.T) { // Setup appState := appcontext.NewAppState() mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} adv := model.BeaconAdvertisement{ ID: "test-beacon", Data: "INVALID_HEX_DATA!!!", } // Execute err := decodeBeacon(adv, appState, mockWriter, parserRegistry) // Assert if err == nil { t.Error("Expected error for invalid hex data, got nil") } if len(mockWriter.Messages) != 0 { t.Errorf("Expected no messages for invalid hex, got %d", len(mockWriter.Messages)) } } func TestDecodeBeacon_ValidHexNoParser(t *testing.T) { // Setup appState := appcontext.NewAppState() mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} // No parsers registered // Valid hex but no matching parser (03 02 FF 06 - type 0x02, no parser registered for it) adv := model.BeaconAdvertisement{ ID: "test-beacon", Data: "0302FF06", // Valid AD structure } // Execute err := decodeBeacon(adv, appState, mockWriter, parserRegistry) // Assert if err != nil { t.Errorf("Expected no error when no parser matches, got %v", err) } if len(mockWriter.Messages) != 0 { t.Errorf("Expected no messages when no parser matches, got %d", len(mockWriter.Messages)) } } func TestDecodeBeacon_Deduplication(t *testing.T) { // Setup appState := appcontext.NewAppState() mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} // Register a test parser // Use pattern 0x02 - AD structure 03 02 FF 06 (len 3, type 0x02) - not removed by RemoveFlagBytes config := model.Config{ Name: "test-parser", Min: 3, Max: 10, Pattern: []string{"0x02"}, Configs: map[string]model.ParserConfig{ "battery": {Length: 1, Offset: 2, Order: "littleendian"}, }, } parserRegistry.Register("test-parser", config) // Create an event that will be parsed (03 02 FF 06 - not removed by RemoveFlagBytes) adv := model.BeaconAdvertisement{ ID: "test-beacon", Data: "0302FF06", } // First processing - should publish err := decodeBeacon(adv, appState, mockWriter, parserRegistry) if err != nil { t.Fatalf("First processing failed: %v", err) } firstMessageCount := len(mockWriter.Messages) // Second processing with identical data - should deduplicate err = decodeBeacon(adv, appState, mockWriter, parserRegistry) if err != nil { t.Fatalf("Second processing failed: %v", err) } // Assert - message count should not have changed if len(mockWriter.Messages) != firstMessageCount { t.Errorf("Expected deduplication, got %d messages (should be %d)", len(mockWriter.Messages), firstMessageCount) } } func TestDecodeBeacon_DifferentDataPublishes(t *testing.T) { // Setup appState := appcontext.NewAppState() mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} // Register a test parser // Use pattern 0x02 - AD structure 03 02 FF 06 (len 3, type 0x02) - not removed by RemoveFlagBytes config := model.Config{ Name: "test-parser", Min: 3, Max: 10, Pattern: []string{"0x02"}, Configs: map[string]model.ParserConfig{ "battery": {Length: 1, Offset: 2, Order: "littleendian"}, }, } parserRegistry.Register("test-parser", config) // First processing adv1 := model.BeaconAdvertisement{ ID: "test-beacon", Data: "020106", } err := decodeBeacon(adv1, appState, mockWriter, parserRegistry) if err != nil { t.Fatalf("First processing failed: %v", err) } firstMessageCount := len(mockWriter.Messages) // Second processing with different data - should publish again adv2 := model.BeaconAdvertisement{ ID: "test-beacon", Data: "0302FF07", // Different data } err = decodeBeacon(adv2, appState, mockWriter, parserRegistry) if err != nil { t.Fatalf("Second processing failed: %v", err) } // Assert - message count should have increased if len(mockWriter.Messages) != firstMessageCount+1 { t.Errorf("Expected new message for different data, got %d messages (expected %d)", len(mockWriter.Messages), firstMessageCount+1) } } func TestDecodeBeacon_WithFlagBytes(t *testing.T) { // Setup appState := appcontext.NewAppState() mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} // Register a test parser // Use pattern 0x02 - AD structure 03 02 FF 06 (len 3, type 0x02) - not removed by RemoveFlagBytes config := model.Config{ Name: "test-parser", Min: 3, Max: 10, Pattern: []string{"0x02"}, Configs: map[string]model.ParserConfig{ "battery": {Length: 1, Offset: 2, Order: "littleendian"}, }, } parserRegistry.Register("test-parser", config) // Data with flag bytes first (02 01 06), then our structure (03 02 FF 08) adv := model.BeaconAdvertisement{ ID: "test-beacon", Data: "0201060302FF08", // Flags removed, then 03 02 FF 08 remains } // Execute err := decodeBeacon(adv, appState, mockWriter, parserRegistry) // Assert - should process successfully after flag removal if err != nil { t.Errorf("Expected no error with flag bytes, got %v", err) } } func TestDecodeBeacon_MultipleBeacons(t *testing.T) { // Setup appState := appcontext.NewAppState() mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} // Register a test parser // Use pattern 0x02 - AD structure 03 02 FF 06 (len 3, type 0x02) - not removed by RemoveFlagBytes config := model.Config{ Name: "test-parser", Min: 3, Max: 10, Pattern: []string{"0x02"}, Configs: map[string]model.ParserConfig{ "battery": {Length: 1, Offset: 2, Order: "littleendian"}, }, } parserRegistry.Register("test-parser", config) // Process multiple different beacons (03 02 FF xx - not removed by RemoveFlagBytes) beacons := []model.BeaconAdvertisement{ {ID: "beacon-1", Data: "0302FF06"}, {ID: "beacon-2", Data: "0302FF07"}, {ID: "beacon-3", Data: "0302FF08"}, } for _, adv := range beacons { err := decodeBeacon(adv, appState, mockWriter, parserRegistry) if err != nil { t.Errorf("Failed to process beacon %s: %v", adv.ID, err) } } // Each unique beacon should produce a message if len(mockWriter.Messages) != len(beacons) { t.Errorf("Expected %d messages, got %d", len(beacons), len(mockWriter.Messages)) } } func TestProcessIncoming_ErrorHandling(t *testing.T) { // Setup appState := appcontext.NewAppState() mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} // Invalid data that will cause an error adv := model.BeaconAdvertisement{ ID: "test-beacon", Data: "INVALID_HEX", } // Execute - should not panic, just handle error processIncoming(adv, appState, mockWriter, parserRegistry) // Assert - no messages should be written if len(mockWriter.Messages) != 0 { t.Errorf("Expected no messages on error, got %d", len(mockWriter.Messages)) } } func TestDecodeBeacon_EventHashing(t *testing.T) { // Setup appState := appcontext.NewAppState() mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} // Register a test parser that creates consistent events // Use pattern 0x02 - AD structure 03 02 FF 06 (len 3, type 0x02) - not removed by RemoveFlagBytes config := model.Config{ Name: "test-parser", Min: 3, Max: 10, Pattern: []string{"0x02"}, Configs: map[string]model.ParserConfig{ "battery": {Length: 1, Offset: 2, Order: "littleendian"}, }, } parserRegistry.Register("test-parser", config) adv := model.BeaconAdvertisement{ ID: "test-beacon", Data: "0302FF06", } // First processing err := decodeBeacon(adv, appState, mockWriter, parserRegistry) if err != nil { t.Fatalf("First processing failed: %v", err) } // Get the event from appState event, exists := appState.GetBeaconEvent("test-beacon") if !exists { t.Fatal("Event should exist in appState") } // Verify hash is created hash := event.Hash() if hash == nil || len(hash) == 0 { t.Error("Expected non-empty hash") } // Second processing should be deduplicated based on hash err = decodeBeacon(adv, appState, mockWriter, parserRegistry) if err != nil { t.Fatalf("Second processing failed: %v", err) } // Should still have only one message if len(mockWriter.Messages) != 1 { t.Errorf("Expected 1 message after deduplication, got %d", len(mockWriter.Messages)) } } func TestDecodeBeacon_VariousHexFormats(t *testing.T) { // Setup appState := appcontext.NewAppState() mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} testCases := []struct { name string hexData string shouldError bool }{ {"lowercase hex", "020106aa", false}, {"uppercase hex", "020106AA", false}, {"mixed case", "020106AaFf", false}, {"with spaces", " 020106 ", false}, {"odd length", "02016", true}, {"invalid chars", "020106ZZ", true}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { adv := model.BeaconAdvertisement{ ID: "test-beacon", Data: tc.hexData, } err := decodeBeacon(adv, appState, mockWriter, parserRegistry) if tc.shouldError && err == nil { t.Errorf("Expected error for %s, got nil", tc.name) } if !tc.shouldError && err != nil && !bytes.Contains([]byte(err.Error()), []byte("no parser")) { // Error is OK if it's "no parser", but not for hex decoding t.Logf("Got expected error for %s: %v", tc.name, err) } }) } }