package bridge import ( "encoding/json" "testing" "time" "github.com/AFASystems/presence/internal/pkg/common/appcontext" "github.com/AFASystems/presence/internal/pkg/model" ) // TestHelper provides utility functions for testing type TestHelper struct { t *testing.T appState *appcontext.AppState } // NewTestHelper creates a new test helper instance func NewTestHelper(t *testing.T) *TestHelper { return &TestHelper{ t: t, appState: appcontext.NewAppState(), } } // GetAppState returns the appState instance func (th *TestHelper) GetAppState() *appcontext.AppState { return th.appState } // AddTestBeacon adds a beacon with the given MAC and ID to the lookup func (th *TestHelper) AddTestBeacon(mac, id string) { th.appState.AddBeaconToLookup(mac, id) } // CreateRawReading creates a test RawReading with default values func (th *TestHelper) CreateRawReading(mac string, rssi int) model.RawReading { return model.RawReading{ Timestamp: time.Now().Format(time.RFC3339), Type: "BLE", MAC: mac, RSSI: rssi, RawData: "0201060302A0", } } // CreateRawReadingWithCustomData creates a test RawReading with custom raw data func (th *TestHelper) CreateRawReadingWithCustomData(mac string, rssi int, rawData string) model.RawReading { return model.RawReading{ Timestamp: time.Now().Format(time.RFC3339), Type: "BLE", MAC: mac, RSSI: rssi, RawData: rawData, } } // CreateGatewayReading creates a Gateway type reading func (th *TestHelper) CreateGatewayReading(mac string) model.RawReading { return model.RawReading{ Timestamp: time.Now().Format(time.RFC3339), Type: "Gateway", MAC: mac, RSSI: -50, RawData: "020106", } } // MarshalReadings marshals a slice of readings to JSON func (th *TestHelper) MarshalReadings(readings []model.RawReading) []byte { data, err := json.Marshal(readings) if err != nil { th.t.Fatalf("Failed to marshal readings: %v", err) } return data } // CreateMQTTMessage creates a complete MQTT message with readings func (th *TestHelper) CreateMQTTMessage(topic string, readings []model.RawReading) (string, []byte) { data := th.MarshalReadings(readings) return topic, data } // AssertBeaconAdvertisement asserts that a beacon advertisement matches expected values func (th *TestHelper) AssertBeaconAdvertisement(adv model.BeaconAdvertisement, expectedID, expectedHostname, expectedMAC string, expectedRSSI int64) { if adv.ID != expectedID { th.t.Errorf("Expected ID '%s', got '%s'", expectedID, adv.ID) } if adv.Hostname != expectedHostname { th.t.Errorf("Expected hostname '%s', got '%s'", expectedHostname, adv.Hostname) } if adv.MAC != expectedMAC { th.t.Errorf("Expected MAC '%s', got '%s'", expectedMAC, adv.MAC) } if adv.RSSI != expectedRSSI { th.t.Errorf("Expected RSSI %d, got %d", expectedRSSI, adv.RSSI) } } // GenerateTestMAC generates a test MAC address from an index func GenerateTestMAC(index int) string { return "AA:BB:CC:DD:" + toHex(index>>8) + ":" + toHex(index&0xFF) } // GenerateTestID generates a test beacon ID from an index func GenerateTestID(index int) string { return "test-beacon-" + toHex(index) } // toHex converts a number to a 2-digit hex string func toHex(n int) string { return formatInt(n, 16) } // Helper function to format int as hex string func formatInt(n, base int) string { const digits = "0123456789ABCDEF" if n == 0 { return "00" } result := "" for n > 0 { remainder := n % base result = string(digits[remainder]) + result n = n / base } // Pad to 2 digits for len(result) < 2 { result = "0" + result } return result } // CreateMockMessage creates a mock MQTT message for testing type MockMessage struct { topic string payload []byte } // NewMockMessage creates a new mock message func NewMockMessage(topic string, payload []byte) *MockMessage { return &MockMessage{ topic: topic, payload: payload, } } // Topic returns the message topic func (m *MockMessage) Topic() string { return m.topic } // Payload returns the message payload func (m *MockMessage) Payload() []byte { return m.payload } // Asserted returns a flag (not used in mock) func (m *MockMessage) Asserted() bool { return false } // Duplicate returns a flag (not used in mock) func (m *MockMessage) Duplicate() bool { return false } // QoS returns the QoS level (not used in mock) func (m *MockMessage) QoS() byte { return 0 } // Retained returns retained flag (not used in mock) func (m *MockMessage) Retained() bool { return false } // MessageID returns message ID (not used in mock) func (m *MockMessage) MessageID() uint16 { return 0 } // SetupTestBeacons configures the appState with a standard set of test beacons func SetupTestBeacons(appState *appcontext.AppState) { beacons := []struct { mac string id string }{ {"AA:BB:CC:DD:EE:FF", "beacon-1"}, {"11:22:33:44:55:66", "beacon-2"}, {"77:88:99:AA:BB:CC", "beacon-3"}, {"DD:EE:FF:00:11:22", "beacon-4"}, } for _, b := range beacons { appState.AddBeaconToLookup(b.mac, b.id) } } // CreateTestReadings creates a slice of test readings func CreateTestReadings(count int) []model.RawReading { readings := make([]model.RawReading, count) for i := 0; i < count; i++ { readings[i] = model.RawReading{ Timestamp: time.Now().Format(time.RFC3339), Type: "BLE", MAC: GenerateTestMAC(i), RSSI: -60 - i, RawData: "0201060302A0", } } return readings } // CleanupTestState cleans up the appState lookup func CleanupTestState(appState *appcontext.AppState) { appState.CleanLookup() } // AssertKafkaMessageCount asserts that the mock writer received the expected number of 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) }