package decoder import ( "context" "testing" "github.com/AFASystems/presence/internal/pkg/common/appcontext" "github.com/AFASystems/presence/internal/pkg/model" "github.com/segmentio/kafka-go" ) // MockKafkaWriter is a mock implementation of kafkaWriter for testing type MockKafkaWriter struct { Messages []kafka.Message } func (m *MockKafkaWriter) WriteMessages(ctx context.Context, msgs ...kafka.Message) error { m.Messages = append(m.Messages, msgs...) return nil } // TestHelper provides utility functions for decoder testing type TestHelper struct { t *testing.T appState *appcontext.AppState parserRegistry *model.ParserRegistry } // NewTestHelper creates a new test helper instance func NewTestHelper(t *testing.T) *TestHelper { return &TestHelper{ t: t, appState: appcontext.NewAppState(), parserRegistry: &model.ParserRegistry{}, } } // GetAppState returns the appState instance func (th *TestHelper) GetAppState() *appcontext.AppState { return th.appState } // GetParserRegistry returns the parser registry func (th *TestHelper) GetParserRegistry() *model.ParserRegistry { return th.parserRegistry } // RegisterTestParser registers a parser with default test configuration func (th *TestHelper) RegisterTestParser(name string) { config := model.Config{ Name: name, Min: 2, Max: 20, Pattern: []string{"02"}, Configs: map[string]model.ParserConfig{ "length": {Length: 2, Offset: 0, Order: "big"}, }, } th.parserRegistry.Register(name, config) } // CreateBeaconAdvertisement creates a test beacon advertisement func (th *TestHelper) CreateBeaconAdvertisement(id, data string) model.BeaconAdvertisement { return model.BeaconAdvertisement{ ID: id, Data: data, } } // CreateValidHexAdvertisement creates a beacon with valid hex data func (th *TestHelper) CreateValidHexAdvertisement(id string) model.BeaconAdvertisement { return model.BeaconAdvertisement{ ID: id, Data: "020106", } } // CreateInvalidHexAdvertisement creates a beacon with invalid hex data func (th *TestHelper) CreateInvalidHexAdvertisement(id string) model.BeaconAdvertisement { return model.BeaconAdvertisement{ ID: id, Data: "INVALID_HEX", } } // CreateEmptyAdvertisement creates a beacon with empty data func (th *TestHelper) CreateEmptyAdvertisement(id string) model.BeaconAdvertisement { return model.BeaconAdvertisement{ ID: id, Data: "", } } // AssertParserExists asserts that a parser exists in the registry func (th *TestHelper) AssertParserExists(name string) { if _, exists := th.parserRegistry.ParserList[name]; !exists { th.t.Errorf("Parser '%s' should exist in registry", name) } } // AssertParserNotExists asserts that a parser does not exist in the registry func (th *TestHelper) AssertParserNotExists(name string) { if _, exists := th.parserRegistry.ParserList[name]; exists { th.t.Errorf("Parser '%s' should not exist in registry", name) } } // AssertEventExists asserts that an event exists in appState func (th *TestHelper) AssertEventExists(id string) model.BeaconEvent { event, exists := th.appState.GetBeaconEvent(id) if !exists { th.t.Errorf("Event for beacon '%s' should exist in appState", id) return model.BeaconEvent{} } return event } // AssertEventNotExists asserts that an event does not exist in appState func (th *TestHelper) AssertEventNotExists(id string) { _, exists := th.appState.GetBeaconEvent(id) if exists { th.t.Errorf("Event for beacon '%s' should not exist in appState", id) } } // AssertParserCount asserts the number of parsers in the registry func (th *TestHelper) AssertParserCount(expected int) { if len(th.parserRegistry.ParserList) != expected { th.t.Errorf("Expected %d parsers in registry, got %d", expected, len(th.parserRegistry.ParserList)) } } // Helper functions for creating test configurations // CreateTestConfig creates a test parser configuration func CreateTestConfig(name string, min, max int, pattern []string) model.Config { return model.Config{ Name: name, Min: min, Max: max, Pattern: pattern, Configs: map[string]model.ParserConfig{ "length": {Length: 2, Offset: 0, Order: "big"}, }, } } // CreateKafkaParserMessage creates a Kafka parser message for testing func CreateKafkaParserMessage(id, name string, config model.Config) model.KafkaParser { return model.KafkaParser{ ID: id, Name: name, Config: config, } } // AssertNoError asserts that an error is nil func AssertNoError(t *testing.T, err error, msg string) { if err != nil { t.Errorf("%s: %v", msg, err) } } // AssertError asserts that an error is not nil func AssertError(t *testing.T, err error, msg string) { if err == nil { t.Errorf("%s: expected error but got nil", msg) } } // Common test data // Valid hex strings for testing var ValidHexStrings = []string{ "020106", // Simple AD structure "0201060302A0", // AD structure with flags "1AFF0C01", // iBeacon-like data "0201061AFF0C01", // Multiple AD structures } // Invalid hex strings for testing var InvalidHexStrings = []string{ "INVALID_HEX", "02016ZZZ", "GGGGGG", "NOT-HEX", } // Empty or whitespace data for testing var EmptyTestData = []string{ "", " ", "\t\n", } // CreateMockWriter creates a mock Kafka writer func CreateMockWriter() *MockKafkaWriter { return &MockKafkaWriter{Messages: []kafka.Message{}} } // Beacon event test helpers // AssertEventFields asserts that event fields match expected values func AssertEventFields(t *testing.T, event model.BeaconEvent, expectedID, expectedType string) { if event.ID != expectedID { t.Errorf("Expected event ID '%s', got '%s'", expectedID, event.ID) } if event.Type != expectedType { t.Errorf("Expected event type '%s', got '%s'", expectedType, event.Type) } } // SetupTestParsers registers a standard set of test parsers func SetupTestParsers(registry *model.ParserRegistry) { parsers := []model.Config{ {Name: "parser-1", Min: 2, Max: 20, Pattern: []string{"02"}}, {Name: "parser-2", Min: 3, Max: 25, Pattern: []string{"03"}}, {Name: "parser-3", Min: 4, Max: 30, Pattern: []string{"04"}}, } for _, p := range parsers { registry.Register(p.Name, p) } } // CleanupTestParsers removes all parsers from the registry func CleanupTestParsers(registry *model.ParserRegistry) { for name := range registry.ParserList { registry.Unregister(name) } } // CreateTestBeaconEvent creates a test beacon event func CreateTestBeaconEvent(id, eventType string) model.BeaconEvent { return model.BeaconEvent{ ID: id, Type: eventType, Battery: 100, Event: 1, AccX: 0, AccY: 0, AccZ: 0, } } // AssertKafkaMessageCount asserts the number of Kafka messages func AssertKafkaMessageCount(t *testing.T, writer *MockKafkaWriter, expected int) { if len(writer.Messages) != expected { t.Errorf("Expected %d Kafka message(s), got %d", expected, len(writer.Messages)) } } // AssertNoKafkaMessages asserts that no messages were written to Kafka func AssertNoKafkaMessages(t *testing.T, writer *MockKafkaWriter) { AssertKafkaMessageCount(t, writer, 0) } // Parser registry test helpers // SimulateEventLoopParserUpdate simulates the event loop's parser update logic func SimulateEventLoopParserUpdate(msg model.KafkaParser, registry *model.ParserRegistry) { switch msg.ID { case "add": config := msg.Config registry.Register(config.Name, config) case "delete": registry.Unregister(msg.Name) case "update": config := msg.Config registry.Register(config.Name, config) } } // CreateParserAddMessage creates a parser add message func CreateParserAddMessage(name string, min, max int) model.KafkaParser { return model.KafkaParser{ ID: "add", Name: name, Config: model.Config{ Name: name, Min: min, Max: max, Pattern: []string{"02"}, }, } } // CreateParserDeleteMessage creates a parser delete message func CreateParserDeleteMessage(name string) model.KafkaParser { return model.KafkaParser{ ID: "delete", Name: name, } } // CreateParserUpdateMessage creates a parser update message func CreateParserUpdateMessage(name string, min, max int) model.KafkaParser { return model.KafkaParser{ ID: "update", Name: name, Config: model.Config{ Name: name, Min: min, Max: max, Pattern: []string{"02"}, }, } } // GenerateTestBeaconID generates a test beacon ID func GenerateTestBeaconID(index int) string { return "test-beacon-" + string(rune('A'+index)) } // GenerateTestHexData generates test hex data func GenerateTestHexData(index int) string { prefix := "02" value := string(rune('6' + index)) return prefix + "01" + value }