| @@ -3,6 +3,7 @@ package server | |||||
| import ( | import ( | ||||
| "context" | "context" | ||||
| "encoding/json" | "encoding/json" | ||||
| "fmt" | |||||
| "log/slog" | "log/slog" | ||||
| "time" | "time" | ||||
| @@ -55,6 +56,10 @@ func RunEventLoop(ctx context.Context, a *ServerApp) { | |||||
| slog.Error("saving decoder event for beacon", "id", id, "err", err) | slog.Error("saving decoder event for beacon", "id", id, "err", err) | ||||
| continue | continue | ||||
| } | } | ||||
| if msg.Type == "Eddystone" && msg.Battery < 3000 { | |||||
| fmt.Printf("Sending alert for battery low: %+v\n", msg) | |||||
| service.SendAlert(id, "BatteryLow", a.KafkaManager.GetWriter("alert"), ctx, a.DB) | |||||
| } | |||||
| case <-beaconTicker.C: | case <-beaconTicker.C: | ||||
| var list []model.Tracker | var list []model.Tracker | ||||
| a.DB.Find(&list) | a.DB.Find(&list) | ||||
| @@ -88,7 +88,7 @@ func LocationToBeaconService(msg model.HTTPLocation, db *gorm.DB, writer *kafka. | |||||
| return | return | ||||
| } | } | ||||
| sendAlert(gw.ID, msg.ID, writer, ctx, allowedZones, db) | |||||
| sendRestrictedZoneAlert(gw.ID, msg.ID, writer, ctx, allowedZones, db) | |||||
| } | } | ||||
| func LocationToBeaconServiceAI(msg model.HTTPLocation, db *gorm.DB, writer *kafka.Writer, ctx context.Context) { | func LocationToBeaconServiceAI(msg model.HTTPLocation, db *gorm.DB, writer *kafka.Writer, ctx context.Context) { | ||||
| @@ -126,48 +126,52 @@ func LocationToBeaconServiceAI(msg model.HTTPLocation, db *gorm.DB, writer *kafk | |||||
| return | return | ||||
| } | } | ||||
| sendAlert(gw.ID, tracker.ID, writer, ctx, allowedZones, db) | |||||
| sendRestrictedZoneAlert(gw.ID, tracker.ID, writer, ctx, allowedZones, db) | |||||
| } | } | ||||
| func sendAlert(gwId, trackerId string, writer *kafka.Writer, ctx context.Context, allowedZones []string, db *gorm.DB) { | |||||
| if len(allowedZones) != 0 && !slices.Contains(allowedZones, gwId) { | |||||
| var existingAlert model.Alert | |||||
| result := db.Select("status").Where("tracker_id = ? AND type = ?", trackerId, "Restricted zone").Order("timestamp DESC").First(&existingAlert) | |||||
| if result.Error == gorm.ErrRecordNotFound || existingAlert.Status == "resolved" { | |||||
| alert := model.Alert{ | |||||
| ID: uuid.New().String(), | |||||
| TrackerID: trackerId, | |||||
| Type: "Restricted zone", | |||||
| Status: "new", | |||||
| Timestamp: time.Now(), | |||||
| } | |||||
| func SendAlert(trackerId, alertType string, writer *kafka.Writer, ctx context.Context, db *gorm.DB) { | |||||
| var existingAlert model.Alert | |||||
| result := db.Select("status").Where("tracker_id = ? AND type = ?", trackerId, alertType).Order("timestamp DESC").First(&existingAlert) | |||||
| if result.Error == gorm.ErrRecordNotFound || existingAlert.Status == "resolved" { | |||||
| alert := model.Alert{ | |||||
| ID: uuid.New().String(), | |||||
| TrackerID: trackerId, | |||||
| Type: alertType, | |||||
| Status: "new", | |||||
| Timestamp: time.Now(), | |||||
| } | |||||
| if err := InsertAlert(alert, db, ctx); err != nil { | |||||
| msg := fmt.Sprintf("Error in inserting alert: %v", err) | |||||
| slog.Error(msg) | |||||
| return | |||||
| } | |||||
| if err := InsertAlert(alert, db, ctx); err != nil { | |||||
| msg := fmt.Sprintf("Error in inserting alert: %v", err) | |||||
| slog.Error(msg) | |||||
| return | |||||
| } | |||||
| eMsg, err := json.Marshal(alert) | |||||
| if err != nil { | |||||
| msg := "Error in marshaling" | |||||
| eMsg, err := json.Marshal(alert) | |||||
| if err != nil { | |||||
| msg := "Error in marshaling" | |||||
| slog.Error(msg) | |||||
| return | |||||
| } else { | |||||
| msg := kafka.Message{ | |||||
| Value: eMsg, | |||||
| } | |||||
| if err := kafkaclient.Write(ctx, writer, msg); err != nil { | |||||
| msg := fmt.Sprintf("Error in writing message: %v", err) | |||||
| slog.Error(msg) | slog.Error(msg) | ||||
| return | return | ||||
| } else { | |||||
| msg := kafka.Message{ | |||||
| Value: eMsg, | |||||
| } | |||||
| if err := kafkaclient.Write(ctx, writer, msg); err != nil { | |||||
| msg := fmt.Sprintf("Error in writing message: %v", err) | |||||
| slog.Error(msg) | |||||
| return | |||||
| } | |||||
| } | } | ||||
| return | |||||
| } else { | |||||
| return | |||||
| } | } | ||||
| return | |||||
| } else { | |||||
| return | |||||
| } | |||||
| } | |||||
| func sendRestrictedZoneAlert(gwId, trackerId string, writer *kafka.Writer, ctx context.Context, allowedZones []string, db *gorm.DB) { | |||||
| if len(allowedZones) != 0 && !slices.Contains(allowedZones, gwId) { | |||||
| SendAlert(trackerId, "Restricted zone", writer, ctx, db) | |||||
| } | } | ||||
| } | } | ||||
| @@ -5,7 +5,6 @@ import ( | |||||
| "testing" | "testing" | ||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | "github.com/AFASystems/presence/internal/pkg/common/appcontext" | ||||
| "github.com/AFASystems/presence/internal/pkg/model" | |||||
| ) | ) | ||||
| func TestNewAppState(t *testing.T) { | func TestNewAppState(t *testing.T) { | ||||
| @@ -64,7 +63,7 @@ func TestBeaconLookup_CleanLookup(t *testing.T) { | |||||
| func TestBeacon_GetAndUpdate(t *testing.T) { | func TestBeacon_GetAndUpdate(t *testing.T) { | ||||
| state := appcontext.NewAppState() | state := appcontext.NewAppState() | ||||
| beacon := model.Beacon{ | |||||
| beacon := appcontext.Beacon{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Name: "Test", | Name: "Test", | ||||
| } | } | ||||
| @@ -81,7 +80,7 @@ func TestBeacon_GetAndUpdate(t *testing.T) { | |||||
| func TestBeaconEvent_GetAndUpdate(t *testing.T) { | func TestBeaconEvent_GetAndUpdate(t *testing.T) { | ||||
| state := appcontext.NewAppState() | state := appcontext.NewAppState() | ||||
| event := model.BeaconEvent{ | |||||
| event := appcontext.BeaconEvent{ | |||||
| ID: "beacon-1", | ID: "beacon-1", | ||||
| Type: "iBeacon", | Type: "iBeacon", | ||||
| Battery: 85, | Battery: 85, | ||||
| @@ -99,8 +98,8 @@ func TestBeaconEvent_GetAndUpdate(t *testing.T) { | |||||
| func TestGetAllBeacons(t *testing.T) { | func TestGetAllBeacons(t *testing.T) { | ||||
| state := appcontext.NewAppState() | state := appcontext.NewAppState() | ||||
| state.UpdateBeacon("b1", model.Beacon{ID: "b1"}) | |||||
| state.UpdateBeacon("b2", model.Beacon{ID: "b2"}) | |||||
| state.UpdateBeacon("b1", appcontext.Beacon{ID: "b1"}) | |||||
| state.UpdateBeacon("b2", appcontext.Beacon{ID: "b2"}) | |||||
| all := state.GetAllBeacons() | all := state.GetAllBeacons() | ||||
| if len(all) != 2 { | if len(all) != 2 { | ||||
| @@ -111,7 +110,7 @@ func TestGetAllBeacons(t *testing.T) { | |||||
| func TestUpdateSettings(t *testing.T) { | func TestUpdateSettings(t *testing.T) { | ||||
| state := appcontext.NewAppState() | state := appcontext.NewAppState() | ||||
| state.UpdateSettings(map[string]any{ | state.UpdateSettings(map[string]any{ | ||||
| "current_algorithm": "ai", | |||||
| "current_algorithm": "ai", | |||||
| "location_confidence": int64(5), | "location_confidence": int64(5), | ||||
| }) | }) | ||||
| @@ -9,8 +9,8 @@ import ( | |||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | "github.com/AFASystems/presence/internal/pkg/common/appcontext" | ||||
| "github.com/AFASystems/presence/internal/pkg/model" | "github.com/AFASystems/presence/internal/pkg/model" | ||||
| "github.com/segmentio/kafka-go" | |||||
| mqtt "github.com/eclipse/paho.mqtt.golang" | mqtt "github.com/eclipse/paho.mqtt.golang" | ||||
| "github.com/segmentio/kafka-go" | |||||
| ) | ) | ||||
| // mqtthandler is extracted from main.go for testing purposes | // mqtthandler is extracted from main.go for testing purposes | ||||
| @@ -36,7 +36,7 @@ func mqtthandler(writer kafkaWriter, topic string, message []byte, appState *app | |||||
| continue | continue | ||||
| } | } | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: val, | ID: val, | ||||
| Hostname: hostname, | Hostname: hostname, | ||||
| MAC: reading.MAC, | MAC: reading.MAC, | ||||
| @@ -180,9 +180,11 @@ func TestEventLoop_AlertMessage(t *testing.T) { | |||||
| // Create an alert message | // Create an alert message | ||||
| msg := model.Alert{ | msg := model.Alert{ | ||||
| ID: "tracker-123", | |||||
| Type: "battery_low", | |||||
| Value: "15", | |||||
| ID: "tracker-123", | |||||
| Type: "battery_low", | |||||
| Status: "new", | |||||
| Timestamp: time.Now(), | |||||
| TrackerID: "tracker-123", | |||||
| } | } | ||||
| go func() { | go func() { | ||||
| @@ -75,7 +75,7 @@ func TestIntegration_EndToEnd(t *testing.T) { | |||||
| } | } | ||||
| // Verify | // Verify | ||||
| var adv model.BeaconAdvertisement | |||||
| var adv appcontext.BeaconAdvertisement | |||||
| err = json.Unmarshal(msg.Value, &adv) | err = json.Unmarshal(msg.Value, &adv) | ||||
| if err != nil { | if err != nil { | ||||
| t.Fatalf("Failed to unmarshal beacon advertisement: %v", err) | t.Fatalf("Failed to unmarshal beacon advertisement: %v", err) | ||||
| @@ -155,7 +155,7 @@ func TestIntegration_MultipleMessages(t *testing.T) { | |||||
| t.Fatalf("Failed to read message %d from Kafka: %v", i+1, err) | t.Fatalf("Failed to read message %d from Kafka: %v", i+1, err) | ||||
| } | } | ||||
| var adv model.BeaconAdvertisement | |||||
| var adv appcontext.BeaconAdvertisement | |||||
| err = json.Unmarshal(msg.Value, &adv) | err = json.Unmarshal(msg.Value, &adv) | ||||
| if err != nil { | if err != nil { | ||||
| t.Fatalf("Failed to unmarshal beacon advertisement %d: %v", i+1, err) | t.Fatalf("Failed to unmarshal beacon advertisement %d: %v", i+1, err) | ||||
| @@ -35,7 +35,7 @@ func TestMQTTHandler_SingleReading(t *testing.T) { | |||||
| t.Errorf("Expected 1 message, got %d", len(mockWriter.Messages)) | t.Errorf("Expected 1 message, got %d", len(mockWriter.Messages)) | ||||
| } | } | ||||
| var adv model.BeaconAdvertisement | |||||
| var adv appcontext.BeaconAdvertisement | |||||
| err := json.Unmarshal(mockWriter.Messages[0].Value, &adv) | err := json.Unmarshal(mockWriter.Messages[0].Value, &adv) | ||||
| if err != nil { | if err != nil { | ||||
| t.Fatalf("Failed to unmarshal beacon advertisement: %v", err) | t.Fatalf("Failed to unmarshal beacon advertisement: %v", err) | ||||
| @@ -175,8 +175,8 @@ func TestMQTTHandler_HostnameExtraction(t *testing.T) { | |||||
| // Test various topic formats | // Test various topic formats | ||||
| testCases := []struct { | testCases := []struct { | ||||
| topic string | |||||
| expectedHost string | |||||
| topic string | |||||
| expectedHost string | |||||
| }{ | }{ | ||||
| {"publish_out/gateway-1", "gateway-1"}, | {"publish_out/gateway-1", "gateway-1"}, | ||||
| {"publish_out/gateway-prod-02", "gateway-prod-02"}, | {"publish_out/gateway-prod-02", "gateway-prod-02"}, | ||||
| @@ -202,7 +202,7 @@ func TestMQTTHandler_HostnameExtraction(t *testing.T) { | |||||
| t.Fatalf("Expected 1 message, got %d", len(mockWriter.Messages)) | t.Fatalf("Expected 1 message, got %d", len(mockWriter.Messages)) | ||||
| } | } | ||||
| var adv model.BeaconAdvertisement | |||||
| var adv appcontext.BeaconAdvertisement | |||||
| err := json.Unmarshal(mockWriter.Messages[0].Value, &adv) | err := json.Unmarshal(mockWriter.Messages[0].Value, &adv) | ||||
| if err != nil { | if err != nil { | ||||
| t.Fatalf("Failed to unmarshal beacon advertisement: %v", err) | t.Fatalf("Failed to unmarshal beacon advertisement: %v", err) | ||||
| @@ -242,7 +242,7 @@ func TestMQTTHandler_PreservesRawData(t *testing.T) { | |||||
| t.Fatalf("Expected 1 message, got %d", len(mockWriter.Messages)) | t.Fatalf("Expected 1 message, got %d", len(mockWriter.Messages)) | ||||
| } | } | ||||
| var adv model.BeaconAdvertisement | |||||
| var adv appcontext.BeaconAdvertisement | |||||
| err := json.Unmarshal(mockWriter.Messages[0].Value, &adv) | err := json.Unmarshal(mockWriter.Messages[0].Value, &adv) | ||||
| if err != nil { | if err != nil { | ||||
| t.Fatalf("Failed to unmarshal beacon advertisement: %v", err) | t.Fatalf("Failed to unmarshal beacon advertisement: %v", err) | ||||
| @@ -94,7 +94,7 @@ func (th *TestHelper) CreateMQTTMessage(topic string, readings []model.RawReadin | |||||
| } | } | ||||
| // AssertBeaconAdvertisement asserts that a beacon advertisement matches expected values | // AssertBeaconAdvertisement asserts that a beacon advertisement matches expected values | ||||
| func (th *TestHelper) AssertBeaconAdvertisement(adv model.BeaconAdvertisement, expectedID, expectedHostname, expectedMAC string, expectedRSSI int64) { | |||||
| func (th *TestHelper) AssertBeaconAdvertisement(adv appcontext.BeaconAdvertisement, expectedID, expectedHostname, expectedMAC string, expectedRSSI int64) { | |||||
| if adv.ID != expectedID { | if adv.ID != expectedID { | ||||
| th.t.Errorf("Expected ID '%s', got '%s'", expectedID, adv.ID) | th.t.Errorf("Expected ID '%s', got '%s'", expectedID, adv.ID) | ||||
| } | } | ||||
| @@ -2,11 +2,13 @@ package controller | |||||
| import ( | import ( | ||||
| "bytes" | "bytes" | ||||
| "context" | |||||
| "encoding/json" | "encoding/json" | ||||
| "net/http" | "net/http" | ||||
| "net/http/httptest" | "net/http/httptest" | ||||
| "testing" | "testing" | ||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | |||||
| "github.com/AFASystems/presence/internal/pkg/controller" | "github.com/AFASystems/presence/internal/pkg/controller" | ||||
| "github.com/AFASystems/presence/internal/pkg/model" | "github.com/AFASystems/presence/internal/pkg/model" | ||||
| "github.com/gorilla/mux" | "github.com/gorilla/mux" | ||||
| @@ -19,7 +21,7 @@ func setupTestDB(t *testing.T) *gorm.DB { | |||||
| if err != nil { | if err != nil { | ||||
| t.Fatalf("Failed to open test DB: %v", err) | t.Fatalf("Failed to open test DB: %v", err) | ||||
| } | } | ||||
| if err := db.AutoMigrate(&model.Gateway{}, &model.Zone{}, &model.Tracker{}, &model.TrackerZones{}, &model.Config{}, &model.Settings{}, &model.Tracks{}); err != nil { | |||||
| if err := db.AutoMigrate(&model.Gateway{}, &model.Zone{}, &model.Tracker{}, &model.TrackerZones{}, &model.Config{}, appcontext.Settings{}, &model.Tracks{}); err != nil { | |||||
| t.Fatalf("Failed to migrate: %v", err) | t.Fatalf("Failed to migrate: %v", err) | ||||
| } | } | ||||
| return db | return db | ||||
| @@ -27,7 +29,8 @@ func setupTestDB(t *testing.T) *gorm.DB { | |||||
| func TestGatewayListController_Empty(t *testing.T) { | func TestGatewayListController_Empty(t *testing.T) { | ||||
| db := setupTestDB(t) | db := setupTestDB(t) | ||||
| handler := controller.GatewayListController(db) | |||||
| context := context.Background() | |||||
| handler := controller.GatewayListController(db, context) | |||||
| req := httptest.NewRequest(http.MethodGet, "/gateways", nil) | req := httptest.NewRequest(http.MethodGet, "/gateways", nil) | ||||
| rec := httptest.NewRecorder() | rec := httptest.NewRecorder() | ||||
| @@ -47,7 +50,8 @@ func TestGatewayListController_Empty(t *testing.T) { | |||||
| func TestGatewayAddController(t *testing.T) { | func TestGatewayAddController(t *testing.T) { | ||||
| db := setupTestDB(t) | db := setupTestDB(t) | ||||
| handler := controller.GatewayAddController(db) | |||||
| context := context.Background() | |||||
| handler := controller.GatewayAddController(db, context) | |||||
| gateway := model.Gateway{ | gateway := model.Gateway{ | ||||
| ID: "gw-1", | ID: "gw-1", | ||||
| @@ -77,11 +81,11 @@ func TestGatewayAddController(t *testing.T) { | |||||
| func TestGatewayDeleteController(t *testing.T) { | func TestGatewayDeleteController(t *testing.T) { | ||||
| db := setupTestDB(t) | db := setupTestDB(t) | ||||
| db.Create(&model.Gateway{ID: "gw-1", Name: "G1", MAC: "AA:BB:CC:DD:EE:FF"}) | db.Create(&model.Gateway{ID: "gw-1", Name: "G1", MAC: "AA:BB:CC:DD:EE:FF"}) | ||||
| context := context.Background() | |||||
| req := httptest.NewRequest(http.MethodDelete, "/gateways/gw-1", nil) | req := httptest.NewRequest(http.MethodDelete, "/gateways/gw-1", nil) | ||||
| req = mux.SetURLVars(req, map[string]string{"id": "gw-1"}) | req = mux.SetURLVars(req, map[string]string{"id": "gw-1"}) | ||||
| rec := httptest.NewRecorder() | rec := httptest.NewRecorder() | ||||
| controller.GatewayDeleteController(db).ServeHTTP(rec, req) | |||||
| controller.GatewayDeleteController(db, context).ServeHTTP(rec, req) | |||||
| if rec.Code != http.StatusOK { | if rec.Code != http.StatusOK { | ||||
| t.Errorf("Expected 200, got %d", rec.Code) | t.Errorf("Expected 200, got %d", rec.Code) | ||||
| @@ -96,7 +100,8 @@ func TestGatewayDeleteController(t *testing.T) { | |||||
| func TestTrackerListController_Empty(t *testing.T) { | func TestTrackerListController_Empty(t *testing.T) { | ||||
| db := setupTestDB(t) | db := setupTestDB(t) | ||||
| handler := controller.TrackerList(db) | |||||
| context := context.Background() | |||||
| handler := controller.TrackerList(db, context) | |||||
| req := httptest.NewRequest(http.MethodGet, "/trackers", nil) | req := httptest.NewRequest(http.MethodGet, "/trackers", nil) | ||||
| rec := httptest.NewRecorder() | rec := httptest.NewRecorder() | ||||
| @@ -116,7 +121,8 @@ func TestTrackerListController_Empty(t *testing.T) { | |||||
| func TestZoneListController_Empty(t *testing.T) { | func TestZoneListController_Empty(t *testing.T) { | ||||
| db := setupTestDB(t) | db := setupTestDB(t) | ||||
| handler := controller.ZoneListController(db) | |||||
| context := context.Background() | |||||
| handler := controller.ZoneListController(db, context) | |||||
| req := httptest.NewRequest(http.MethodGet, "/zones", nil) | req := httptest.NewRequest(http.MethodGet, "/zones", nil) | ||||
| rec := httptest.NewRecorder() | rec := httptest.NewRecorder() | ||||
| @@ -129,7 +135,8 @@ func TestZoneListController_Empty(t *testing.T) { | |||||
| func TestSettingsListController(t *testing.T) { | func TestSettingsListController(t *testing.T) { | ||||
| db := setupTestDB(t) | db := setupTestDB(t) | ||||
| handler := controller.SettingsListController(db) | |||||
| context := context.Background() | |||||
| handler := controller.SettingsListController(db, context) | |||||
| req := httptest.NewRequest(http.MethodGet, "/settings", nil) | req := httptest.NewRequest(http.MethodGet, "/settings", nil) | ||||
| rec := httptest.NewRecorder() | rec := httptest.NewRecorder() | ||||
| @@ -15,7 +15,7 @@ func TestDecodeBeacon_EmptyData(t *testing.T) { | |||||
| mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} | mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} | ||||
| parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "", // Empty data | Data: "", // Empty data | ||||
| } | } | ||||
| @@ -39,7 +39,7 @@ func TestDecodeBeacon_WhitespaceOnly(t *testing.T) { | |||||
| mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} | mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} | ||||
| parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: " ", // Whitespace only | Data: " ", // Whitespace only | ||||
| } | } | ||||
| @@ -63,7 +63,7 @@ func TestDecodeBeacon_InvalidHex(t *testing.T) { | |||||
| mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} | mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} | ||||
| parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "INVALID_HEX_DATA!!!", | Data: "INVALID_HEX_DATA!!!", | ||||
| } | } | ||||
| @@ -88,7 +88,7 @@ func TestDecodeBeacon_ValidHexNoParser(t *testing.T) { | |||||
| parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} // No parsers registered | 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) | // Valid hex but no matching parser (03 02 FF 06 - type 0x02, no parser registered for it) | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "0302FF06", // Valid AD structure | Data: "0302FF06", // Valid AD structure | ||||
| } | } | ||||
| @@ -126,7 +126,7 @@ func TestDecodeBeacon_Deduplication(t *testing.T) { | |||||
| parserRegistry.Register("test-parser", config) | parserRegistry.Register("test-parser", config) | ||||
| // Create an event that will be parsed (03 02 FF 06 - not removed by RemoveFlagBytes) | // Create an event that will be parsed (03 02 FF 06 - not removed by RemoveFlagBytes) | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "0302FF06", | Data: "0302FF06", | ||||
| } | } | ||||
| @@ -171,7 +171,7 @@ func TestDecodeBeacon_DifferentDataPublishes(t *testing.T) { | |||||
| parserRegistry.Register("test-parser", config) | parserRegistry.Register("test-parser", config) | ||||
| // First processing | // First processing | ||||
| adv1 := model.BeaconAdvertisement{ | |||||
| adv1 := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "020106", | Data: "020106", | ||||
| } | } | ||||
| @@ -184,7 +184,7 @@ func TestDecodeBeacon_DifferentDataPublishes(t *testing.T) { | |||||
| firstMessageCount := len(mockWriter.Messages) | firstMessageCount := len(mockWriter.Messages) | ||||
| // Second processing with different data - should publish again | // Second processing with different data - should publish again | ||||
| adv2 := model.BeaconAdvertisement{ | |||||
| adv2 := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "0302FF07", // Different data | Data: "0302FF07", // Different data | ||||
| } | } | ||||
| @@ -220,7 +220,7 @@ func TestDecodeBeacon_WithFlagBytes(t *testing.T) { | |||||
| parserRegistry.Register("test-parser", config) | parserRegistry.Register("test-parser", config) | ||||
| // Data with flag bytes first (02 01 06), then our structure (03 02 FF 08) | // Data with flag bytes first (02 01 06), then our structure (03 02 FF 08) | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "0201060302FF08", // Flags removed, then 03 02 FF 08 remains | Data: "0201060302FF08", // Flags removed, then 03 02 FF 08 remains | ||||
| } | } | ||||
| @@ -254,7 +254,7 @@ func TestDecodeBeacon_MultipleBeacons(t *testing.T) { | |||||
| parserRegistry.Register("test-parser", config) | parserRegistry.Register("test-parser", config) | ||||
| // Process multiple different beacons (03 02 FF xx - not removed by RemoveFlagBytes) | // Process multiple different beacons (03 02 FF xx - not removed by RemoveFlagBytes) | ||||
| beacons := []model.BeaconAdvertisement{ | |||||
| beacons := []appcontext.BeaconAdvertisement{ | |||||
| {ID: "beacon-1", Data: "0302FF06"}, | {ID: "beacon-1", Data: "0302FF06"}, | ||||
| {ID: "beacon-2", Data: "0302FF07"}, | {ID: "beacon-2", Data: "0302FF07"}, | ||||
| {ID: "beacon-3", Data: "0302FF08"}, | {ID: "beacon-3", Data: "0302FF08"}, | ||||
| @@ -280,7 +280,7 @@ func TestProcessIncoming_ErrorHandling(t *testing.T) { | |||||
| parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | ||||
| // Invalid data that will cause an error | // Invalid data that will cause an error | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "INVALID_HEX", | Data: "INVALID_HEX", | ||||
| } | } | ||||
| @@ -313,7 +313,7 @@ func TestDecodeBeacon_EventHashing(t *testing.T) { | |||||
| } | } | ||||
| parserRegistry.Register("test-parser", config) | parserRegistry.Register("test-parser", config) | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "0302FF06", | Data: "0302FF06", | ||||
| } | } | ||||
| @@ -369,7 +369,7 @@ func TestDecodeBeacon_VariousHexFormats(t *testing.T) { | |||||
| for _, tc := range testCases { | for _, tc := range testCases { | ||||
| t.Run(tc.name, func(t *testing.T) { | t.Run(tc.name, func(t *testing.T) { | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: tc.hexData, | Data: tc.hexData, | ||||
| } | } | ||||
| @@ -14,7 +14,7 @@ import ( | |||||
| ) | ) | ||||
| // processIncoming processes incoming beacon advertisements | // processIncoming processes incoming beacon advertisements | ||||
| func processIncoming(adv model.BeaconAdvertisement, appState *appcontext.AppState, writer kafkaWriter, parserRegistry *model.ParserRegistry) { | |||||
| func processIncoming(adv appcontext.BeaconAdvertisement, appState *appcontext.AppState, writer kafkaWriter, parserRegistry *model.ParserRegistry) { | |||||
| err := decodeBeacon(adv, appState, writer, parserRegistry) | err := decodeBeacon(adv, appState, writer, parserRegistry) | ||||
| if err != nil { | if err != nil { | ||||
| eMsg := fmt.Sprintf("Error in decoding: %v", err) | eMsg := fmt.Sprintf("Error in decoding: %v", err) | ||||
| @@ -24,7 +24,7 @@ func processIncoming(adv model.BeaconAdvertisement, appState *appcontext.AppStat | |||||
| } | } | ||||
| // decodeBeacon decodes beacon data and publishes events | // decodeBeacon decodes beacon data and publishes events | ||||
| func decodeBeacon(adv model.BeaconAdvertisement, appState *appcontext.AppState, writer kafkaWriter, parserRegistry *model.ParserRegistry) error { | |||||
| func decodeBeacon(adv appcontext.BeaconAdvertisement, appState *appcontext.AppState, writer kafkaWriter, parserRegistry *model.ParserRegistry) error { | |||||
| beacon := strings.TrimSpace(adv.Data) | beacon := strings.TrimSpace(adv.Data) | ||||
| id := adv.ID | id := adv.ID | ||||
| if beacon == "" { | if beacon == "" { | ||||
| @@ -39,7 +39,7 @@ func decodeBeacon(adv model.BeaconAdvertisement, appState *appcontext.AppState, | |||||
| b = utils.RemoveFlagBytes(b) | b = utils.RemoveFlagBytes(b) | ||||
| indeces := utils.ParseADFast(b) | indeces := utils.ParseADFast(b) | ||||
| event := utils.LoopADStructures(b, indeces, id, parserRegistry) | |||||
| event := utils.LoopADStructures(b, indeces, id, parserRegistry, "") | |||||
| if event.ID == "" { | if event.ID == "" { | ||||
| return nil | return nil | ||||
| @@ -16,12 +16,12 @@ func TestEventLoop_RawMessageProcessing(t *testing.T) { | |||||
| mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} | mockWriter := &MockKafkaWriter{Messages: []kafka.Message{}} | ||||
| parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | parserRegistry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | ||||
| chRaw := make(chan model.BeaconAdvertisement, 10) | |||||
| chRaw := make(chan appcontext.BeaconAdvertisement, 10) | |||||
| ctx, cancel := context.WithCancel(context.Background()) | ctx, cancel := context.WithCancel(context.Background()) | ||||
| defer cancel() | defer cancel() | ||||
| // Create a test message | // Create a test message | ||||
| msg := model.BeaconAdvertisement{ | |||||
| msg := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "020106", | Data: "020106", | ||||
| } | } | ||||
| @@ -242,7 +242,7 @@ func TestEventLoop_ContextCancellation(t *testing.T) { | |||||
| ctx, cancel := context.WithCancel(context.Background()) | ctx, cancel := context.WithCancel(context.Background()) | ||||
| defer cancel() | defer cancel() | ||||
| chRaw := make(chan model.BeaconAdvertisement, 10) | |||||
| chRaw := make(chan appcontext.BeaconAdvertisement, 10) | |||||
| chParser := make(chan model.KafkaParser, 10) | chParser := make(chan model.KafkaParser, 10) | ||||
| // Cancel immediately | // Cancel immediately | ||||
| @@ -264,7 +264,7 @@ func TestEventLoop_ContextCancellation(t *testing.T) { | |||||
| func TestEventLoop_ChannelBuffering(t *testing.T) { | func TestEventLoop_ChannelBuffering(t *testing.T) { | ||||
| // Setup - create buffered channels (like in main) | // Setup - create buffered channels (like in main) | ||||
| chRaw := make(chan model.BeaconAdvertisement, 2000) | |||||
| chRaw := make(chan appcontext.BeaconAdvertisement, 2000) | |||||
| chParser := make(chan model.KafkaParser, 200) | chParser := make(chan model.KafkaParser, 200) | ||||
| _, cancel := context.WithCancel(context.Background()) | _, cancel := context.WithCancel(context.Background()) | ||||
| @@ -272,7 +272,7 @@ func TestEventLoop_ChannelBuffering(t *testing.T) { | |||||
| // Send multiple messages without blocking | // Send multiple messages without blocking | ||||
| for i := 0; i < 100; i++ { | for i := 0; i < 100; i++ { | ||||
| msg := model.BeaconAdvertisement{ | |||||
| msg := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "020106", | Data: "020106", | ||||
| } | } | ||||
| @@ -310,14 +310,14 @@ func TestEventLoop_ChannelBuffering(t *testing.T) { | |||||
| func TestEventLoop_ParserAndRawChannels(t *testing.T) { | func TestEventLoop_ParserAndRawChannels(t *testing.T) { | ||||
| // Setup | // Setup | ||||
| chRaw := make(chan model.BeaconAdvertisement, 10) | |||||
| chRaw := make(chan appcontext.BeaconAdvertisement, 10) | |||||
| chParser := make(chan model.KafkaParser, 10) | chParser := make(chan model.KafkaParser, 10) | ||||
| _, cancel := context.WithCancel(context.Background()) | _, cancel := context.WithCancel(context.Background()) | ||||
| defer cancel() | defer cancel() | ||||
| // Send both raw and parser messages | // Send both raw and parser messages | ||||
| rawMsg := model.BeaconAdvertisement{ | |||||
| rawMsg := appcontext.BeaconAdvertisement{ | |||||
| ID: "test-beacon", | ID: "test-beacon", | ||||
| Data: "020106", | Data: "020106", | ||||
| } | } | ||||
| @@ -56,7 +56,7 @@ func TestIntegration_DecoderEndToEnd(t *testing.T) { | |||||
| defer reader.Close() | defer reader.Close() | ||||
| // Create a test beacon advertisement | // Create a test beacon advertisement | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "integration-test-beacon", | ID: "integration-test-beacon", | ||||
| Data: "020106", // Valid hex data | Data: "020106", // Valid hex data | ||||
| } | } | ||||
| @@ -104,7 +104,7 @@ func TestIntegration_ParserRegistryOperations(t *testing.T) { | |||||
| ID: "add", | ID: "add", | ||||
| Name: "kafka-test-parser", | Name: "kafka-test-parser", | ||||
| Config: model.Config{ | Config: model.Config{ | ||||
| Name: "kafka-test-parser", | |||||
| Name: "kafka-test-parser", | |||||
| Min: 2, | Min: 2, | ||||
| Max: 20, | Max: 20, | ||||
| Pattern: []string{"0x02", "0x01"}, | Pattern: []string{"0x02", "0x01"}, | ||||
| @@ -177,7 +177,7 @@ func TestIntegration_MultipleBeaconsSequential(t *testing.T) { | |||||
| defer reader.Close() | defer reader.Close() | ||||
| // Process multiple beacons | // Process multiple beacons | ||||
| beacons := []model.BeaconAdvertisement{ | |||||
| beacons := []appcontext.BeaconAdvertisement{ | |||||
| {ID: "beacon-1", Data: "020106"}, | {ID: "beacon-1", Data: "020106"}, | ||||
| {ID: "beacon-2", Data: "020107"}, | {ID: "beacon-2", Data: "020107"}, | ||||
| {ID: "beacon-3", Data: "020108"}, | {ID: "beacon-3", Data: "020108"}, | ||||
| @@ -243,7 +243,7 @@ func TestIntegration_EventDeduplication(t *testing.T) { | |||||
| defer reader.Close() | defer reader.Close() | ||||
| // Create identical beacon advertisement | // Create identical beacon advertisement | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "dedup-test-beacon", | ID: "dedup-test-beacon", | ||||
| Data: "020106", | Data: "020106", | ||||
| } | } | ||||
| @@ -319,7 +319,7 @@ func TestIntegration_AppStatePersistence(t *testing.T) { | |||||
| defer writer.Close() | defer writer.Close() | ||||
| // Process beacon | // Process beacon | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "persist-test-beacon", | ID: "persist-test-beacon", | ||||
| Data: "020106", | Data: "020106", | ||||
| } | } | ||||
| @@ -381,7 +381,7 @@ func TestIntegration_ParserUpdateFlow(t *testing.T) { | |||||
| parserRegistry.Register("update-test-parser", config1) | parserRegistry.Register("update-test-parser", config1) | ||||
| // Process with initial config | // Process with initial config | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: "update-test-beacon", | ID: "update-test-beacon", | ||||
| Data: "020106", | Data: "020106", | ||||
| } | } | ||||
| @@ -391,7 +391,7 @@ func TestIntegration_ParserUpdateFlow(t *testing.T) { | |||||
| // Update parser config | // Update parser config | ||||
| config2 := model.Config{ | config2 := model.Config{ | ||||
| Name: "update-test-parser", | |||||
| Name: "update-test-parser", | |||||
| Min: 3, | Min: 3, | ||||
| Max: 25, | Max: 25, | ||||
| Pattern: []string{"0x03"}, | Pattern: []string{"0x03"}, | ||||
| @@ -400,7 +400,7 @@ func TestIntegration_ParserUpdateFlow(t *testing.T) { | |||||
| parserRegistry.Register("update-test-parser", config2) | parserRegistry.Register("update-test-parser", config2) | ||||
| // Process again with updated config | // Process again with updated config | ||||
| adv2 := model.BeaconAdvertisement{ | |||||
| adv2 := appcontext.BeaconAdvertisement{ | |||||
| ID: "update-test-beacon-2", | ID: "update-test-beacon-2", | ||||
| Data: "030107", | Data: "030107", | ||||
| } | } | ||||
| @@ -60,32 +60,32 @@ func (th *TestHelper) RegisterTestParser(name string) { | |||||
| } | } | ||||
| // CreateBeaconAdvertisement creates a test beacon advertisement | // CreateBeaconAdvertisement creates a test beacon advertisement | ||||
| func (th *TestHelper) CreateBeaconAdvertisement(id, data string) model.BeaconAdvertisement { | |||||
| return model.BeaconAdvertisement{ | |||||
| func (th *TestHelper) CreateBeaconAdvertisement(id, data string) appcontext.BeaconAdvertisement { | |||||
| return appcontext.BeaconAdvertisement{ | |||||
| ID: id, | ID: id, | ||||
| Data: data, | Data: data, | ||||
| } | } | ||||
| } | } | ||||
| // CreateValidHexAdvertisement creates a beacon with valid hex data | // CreateValidHexAdvertisement creates a beacon with valid hex data | ||||
| func (th *TestHelper) CreateValidHexAdvertisement(id string) model.BeaconAdvertisement { | |||||
| return model.BeaconAdvertisement{ | |||||
| func (th *TestHelper) CreateValidHexAdvertisement(id string) appcontext.BeaconAdvertisement { | |||||
| return appcontext.BeaconAdvertisement{ | |||||
| ID: id, | ID: id, | ||||
| Data: "020106", | Data: "020106", | ||||
| } | } | ||||
| } | } | ||||
| // CreateInvalidHexAdvertisement creates a beacon with invalid hex data | // CreateInvalidHexAdvertisement creates a beacon with invalid hex data | ||||
| func (th *TestHelper) CreateInvalidHexAdvertisement(id string) model.BeaconAdvertisement { | |||||
| return model.BeaconAdvertisement{ | |||||
| func (th *TestHelper) CreateInvalidHexAdvertisement(id string) appcontext.BeaconAdvertisement { | |||||
| return appcontext.BeaconAdvertisement{ | |||||
| ID: id, | ID: id, | ||||
| Data: "INVALID_HEX", | Data: "INVALID_HEX", | ||||
| } | } | ||||
| } | } | ||||
| // CreateEmptyAdvertisement creates a beacon with empty data | // CreateEmptyAdvertisement creates a beacon with empty data | ||||
| func (th *TestHelper) CreateEmptyAdvertisement(id string) model.BeaconAdvertisement { | |||||
| return model.BeaconAdvertisement{ | |||||
| func (th *TestHelper) CreateEmptyAdvertisement(id string) appcontext.BeaconAdvertisement { | |||||
| return appcontext.BeaconAdvertisement{ | |||||
| ID: id, | ID: id, | ||||
| Data: "", | Data: "", | ||||
| } | } | ||||
| @@ -106,11 +106,11 @@ func (th *TestHelper) AssertParserNotExists(name string) { | |||||
| } | } | ||||
| // AssertEventExists asserts that an event exists in appState | // AssertEventExists asserts that an event exists in appState | ||||
| func (th *TestHelper) AssertEventExists(id string) model.BeaconEvent { | |||||
| func (th *TestHelper) AssertEventExists(id string) appcontext.BeaconEvent { | |||||
| event, exists := th.appState.GetBeaconEvent(id) | event, exists := th.appState.GetBeaconEvent(id) | ||||
| if !exists { | if !exists { | ||||
| th.t.Errorf("Event for beacon '%s' should exist in appState", id) | th.t.Errorf("Event for beacon '%s' should exist in appState", id) | ||||
| return model.BeaconEvent{} | |||||
| return appcontext.BeaconEvent{} | |||||
| } | } | ||||
| return event | return event | ||||
| } | } | ||||
| @@ -172,10 +172,10 @@ func AssertError(t *testing.T, err error, msg string) { | |||||
| // Valid hex strings for testing | // Valid hex strings for testing | ||||
| var ValidHexStrings = []string{ | var ValidHexStrings = []string{ | ||||
| "020106", // Simple AD structure | |||||
| "0201060302A0", // AD structure with flags | |||||
| "1AFF0C01", // iBeacon-like data | |||||
| "0201061AFF0C01", // Multiple AD structures | |||||
| "020106", // Simple AD structure | |||||
| "0201060302A0", // AD structure with flags | |||||
| "1AFF0C01", // iBeacon-like data | |||||
| "0201061AFF0C01", // Multiple AD structures | |||||
| } | } | ||||
| // Invalid hex strings for testing | // Invalid hex strings for testing | ||||
| @@ -201,7 +201,7 @@ func CreateMockWriter() *MockKafkaWriter { | |||||
| // Beacon event test helpers | // Beacon event test helpers | ||||
| // AssertEventFields asserts that event fields match expected values | // AssertEventFields asserts that event fields match expected values | ||||
| func AssertEventFields(t *testing.T, event model.BeaconEvent, expectedID, expectedType string) { | |||||
| func AssertEventFields(t *testing.T, event appcontext.BeaconEvent, expectedID, expectedType string) { | |||||
| if event.ID != expectedID { | if event.ID != expectedID { | ||||
| t.Errorf("Expected event ID '%s', got '%s'", expectedID, event.ID) | t.Errorf("Expected event ID '%s', got '%s'", expectedID, event.ID) | ||||
| } | } | ||||
| @@ -232,8 +232,8 @@ func CleanupTestParsers(registry *model.ParserRegistry) { | |||||
| } | } | ||||
| // CreateTestBeaconEvent creates a test beacon event | // CreateTestBeaconEvent creates a test beacon event | ||||
| func CreateTestBeaconEvent(id, eventType string) model.BeaconEvent { | |||||
| return model.BeaconEvent{ | |||||
| func CreateTestBeaconEvent(id, eventType string) appcontext.BeaconEvent { | |||||
| return appcontext.BeaconEvent{ | |||||
| ID: id, | ID: id, | ||||
| Type: eventType, | Type: eventType, | ||||
| Battery: 100, | Battery: 100, | ||||
| @@ -3,8 +3,8 @@ package location | |||||
| import ( | import ( | ||||
| "testing" | "testing" | ||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | |||||
| "github.com/AFASystems/presence/internal/pkg/common/utils" | "github.com/AFASystems/presence/internal/pkg/common/utils" | ||||
| "github.com/AFASystems/presence/internal/pkg/model" | |||||
| ) | ) | ||||
| // Test location algorithm scoring formula: seenW + (rssiW * (1.0 - (rssi / -100.0))) | // Test location algorithm scoring formula: seenW + (rssiW * (1.0 - (rssi / -100.0))) | ||||
| @@ -30,7 +30,7 @@ func TestLocationScoringFormula(t *testing.T) { | |||||
| } | } | ||||
| func TestCalculateDistance_ForLocation(t *testing.T) { | func TestCalculateDistance_ForLocation(t *testing.T) { | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| RSSI: -65, | RSSI: -65, | ||||
| TXPower: "C5", | TXPower: "C5", | ||||
| } | } | ||||
| @@ -4,11 +4,12 @@ import ( | |||||
| "encoding/json" | "encoding/json" | ||||
| "testing" | "testing" | ||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | |||||
| "github.com/AFASystems/presence/internal/pkg/model" | "github.com/AFASystems/presence/internal/pkg/model" | ||||
| ) | ) | ||||
| func TestBeaconEvent_Hash(t *testing.T) { | func TestBeaconEvent_Hash(t *testing.T) { | ||||
| e := model.BeaconEvent{ | |||||
| e := appcontext.BeaconEvent{ | |||||
| ID: "beacon-1", | ID: "beacon-1", | ||||
| Name: "beacon-1", | Name: "beacon-1", | ||||
| Type: "iBeacon", | Type: "iBeacon", | ||||
| @@ -28,8 +29,8 @@ func TestBeaconEvent_Hash(t *testing.T) { | |||||
| } | } | ||||
| func TestBeaconEvent_Hash_BatteryRounded(t *testing.T) { | func TestBeaconEvent_Hash_BatteryRounded(t *testing.T) { | ||||
| e1 := model.BeaconEvent{ID: "1", Battery: 84, Event: 1} | |||||
| e2 := model.BeaconEvent{ID: "1", Battery: 89, Event: 1} | |||||
| e1 := appcontext.BeaconEvent{ID: "1", Battery: 84, Event: 1} | |||||
| e2 := appcontext.BeaconEvent{ID: "1", Battery: 89, Event: 1} | |||||
| hash1 := e1.Hash() | hash1 := e1.Hash() | ||||
| hash2 := e2.Hash() | hash2 := e2.Hash() | ||||
| // Battery is rounded to nearest 10, so 84 and 89 should produce same hash | // Battery is rounded to nearest 10, so 84 and 89 should produce same hash | ||||
| @@ -39,7 +40,7 @@ func TestBeaconEvent_Hash_BatteryRounded(t *testing.T) { | |||||
| } | } | ||||
| func TestBeaconEvent_ToJSON(t *testing.T) { | func TestBeaconEvent_ToJSON(t *testing.T) { | ||||
| e := model.BeaconEvent{ | |||||
| e := appcontext.BeaconEvent{ | |||||
| ID: "beacon-1", | ID: "beacon-1", | ||||
| Name: "Test", | Name: "Test", | ||||
| Type: "iBeacon", | Type: "iBeacon", | ||||
| @@ -49,7 +50,7 @@ func TestBeaconEvent_ToJSON(t *testing.T) { | |||||
| if err != nil { | if err != nil { | ||||
| t.Fatalf("ToJSON failed: %v", err) | t.Fatalf("ToJSON failed: %v", err) | ||||
| } | } | ||||
| var decoded model.BeaconEvent | |||||
| var decoded appcontext.BeaconEvent | |||||
| if err := json.Unmarshal(data, &decoded); err != nil { | if err := json.Unmarshal(data, &decoded); err != nil { | ||||
| t.Fatalf("Failed to unmarshal: %v", err) | t.Fatalf("Failed to unmarshal: %v", err) | ||||
| } | } | ||||
| @@ -3,6 +3,7 @@ package utils | |||||
| import ( | import ( | ||||
| "testing" | "testing" | ||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | |||||
| "github.com/AFASystems/presence/internal/pkg/common/utils" | "github.com/AFASystems/presence/internal/pkg/common/utils" | ||||
| "github.com/AFASystems/presence/internal/pkg/model" | "github.com/AFASystems/presence/internal/pkg/model" | ||||
| ) | ) | ||||
| @@ -80,17 +81,17 @@ func TestRemoveFlagBytes_TooShort(t *testing.T) { | |||||
| func TestCalculateDistance(t *testing.T) { | func TestCalculateDistance(t *testing.T) { | ||||
| tests := []struct { | tests := []struct { | ||||
| name string | |||||
| rssi int64 | |||||
| name string | |||||
| rssi int64 | |||||
| txPower string | txPower string | ||||
| }{ | }{ | ||||
| {"typical beacon", -65, "C5"}, // -59 in two's complement | |||||
| {"typical beacon", -65, "C5"}, // -59 in two's complement | |||||
| {"weak signal", -90, "C5"}, | {"weak signal", -90, "C5"}, | ||||
| {"strong signal", -40, "C5"}, | {"strong signal", -40, "C5"}, | ||||
| } | } | ||||
| for _, tt := range tests { | for _, tt := range tests { | ||||
| t.Run(tt.name, func(t *testing.T) { | t.Run(tt.name, func(t *testing.T) { | ||||
| adv := model.BeaconAdvertisement{RSSI: tt.rssi, TXPower: tt.txPower} | |||||
| adv := appcontext.BeaconAdvertisement{RSSI: tt.rssi, TXPower: tt.txPower} | |||||
| d := utils.CalculateDistance(adv) | d := utils.CalculateDistance(adv) | ||||
| if d < 0 { | if d < 0 { | ||||
| t.Errorf("Distance should be non-negative, got %f", d) | t.Errorf("Distance should be non-negative, got %f", d) | ||||
| @@ -103,7 +104,7 @@ func TestLoopADStructures_NoParsers(t *testing.T) { | |||||
| registry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | registry := &model.ParserRegistry{ParserList: make(map[string]model.BeaconParser)} | ||||
| data := []byte{0x02, 0x01, 0x06} | data := []byte{0x02, 0x01, 0x06} | ||||
| indices := utils.ParseADFast(data) | indices := utils.ParseADFast(data) | ||||
| event := utils.LoopADStructures(data, indices, "beacon-1", registry) | |||||
| event := utils.LoopADStructures(data, indices, "beacon-1", registry, "") | |||||
| if event.ID != "" { | if event.ID != "" { | ||||
| t.Errorf("Expected empty event with no parsers, got ID %s", event.ID) | t.Errorf("Expected empty event with no parsers, got ID %s", event.ID) | ||||
| } | } | ||||