ソースを参照

fix: tests

master
Blaz Smehov 4日前
コミット
536c959d76
19個のファイルの変更140行の追加121行の削除
  1. +5
    -0
      internal/app/server/events.go
  2. +39
    -35
      internal/pkg/service/beacon_service.go
  3. バイナリ
      location
  4. バイナリ
      server
  5. +5
    -6
      tests/appcontext/appcontext_test.go
  6. +2
    -2
      tests/bridge/bridge_test.go
  7. +5
    -3
      tests/bridge/event_loop_test.go
  8. +2
    -2
      tests/bridge/integration_test.go
  9. +5
    -5
      tests/bridge/mqtt_handler_test.go
  10. +1
    -1
      tests/bridge/testutil.go
  11. +15
    -8
      tests/controller/controller_test.go
  12. +12
    -12
      tests/decoder/decode_test.go
  13. +3
    -3
      tests/decoder/decoder_test.go
  14. +7
    -7
      tests/decoder/event_loop_test.go
  15. +8
    -8
      tests/decoder/integration_test.go
  16. +17
    -17
      tests/decoder/testutil.go
  17. +2
    -2
      tests/location/location_test.go
  18. +6
    -5
      tests/model/model_test.go
  19. +6
    -5
      tests/utils/utils_test.go

+ 5
- 0
internal/app/server/events.go ファイルの表示

@@ -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)


+ 39
- 35
internal/pkg/service/beacon_service.go ファイルの表示

@@ -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)
} }
} }




バイナリ
location ファイルの表示


バイナリ
server ファイルの表示


+ 5
- 6
tests/appcontext/appcontext_test.go ファイルの表示

@@ -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),
}) })




+ 2
- 2
tests/bridge/bridge_test.go ファイルの表示

@@ -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,


+ 5
- 3
tests/bridge/event_loop_test.go ファイルの表示

@@ -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() {


+ 2
- 2
tests/bridge/integration_test.go ファイルの表示

@@ -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)


+ 5
- 5
tests/bridge/mqtt_handler_test.go ファイルの表示

@@ -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)


+ 1
- 1
tests/bridge/testutil.go ファイルの表示

@@ -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)
} }


+ 15
- 8
tests/controller/controller_test.go ファイルの表示

@@ -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()


+ 12
- 12
tests/decoder/decode_test.go ファイルの表示

@@ -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,
} }


+ 3
- 3
tests/decoder/decoder_test.go ファイルの表示

@@ -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


+ 7
- 7
tests/decoder/event_loop_test.go ファイルの表示

@@ -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",
} }


+ 8
- 8
tests/decoder/integration_test.go ファイルの表示

@@ -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",
} }


+ 17
- 17
tests/decoder/testutil.go ファイルの表示

@@ -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,


+ 2
- 2
tests/location/location_test.go ファイルの表示

@@ -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",
} }


+ 6
- 5
tests/model/model_test.go ファイルの表示

@@ -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)
} }


+ 6
- 5
tests/utils/utils_test.go ファイルの表示

@@ -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)
} }


読み込み中…
キャンセル
保存