package decoder import ( "context" "encoding/json" "os" "testing" "time" "github.com/AFASystems/presence/internal/pkg/common/appcontext" "github.com/AFASystems/presence/internal/pkg/model" "github.com/segmentio/kafka-go" ) // TestIntegration_DecoderEndToEnd tests the complete decoder flow func TestIntegration_DecoderEndToEnd(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } // Check if Kafka is available kafkaURL := os.Getenv("KAFKA_URL") if kafkaURL == "" { kafkaURL = "localhost:9092" } // Create test topics rawTopic := "test-rawbeacons-" + time.Now().Format("20060102150405") alertTopic := "test-alertbeacons-" + time.Now().Format("20060102150405") // Setup appState := appcontext.NewAppState() parserRegistry := &model.ParserRegistry{} // Register a test parser config := model.Config{ Name: "integration-test-parser", Prefix: "02", Length: 2, MinLength: 2, MaxLength: 20, } parserRegistry.Register("integration-test-parser", config) // Create Kafka writer writer := kafka.NewWriter(kafka.WriterConfig{ Brokers: []string{kafkaURL}, Topic: alertTopic, }) defer writer.Close() // Create Kafka reader to verify messages reader := kafka.NewReader(kafka.ReaderConfig{ Brokers: []string{kafkaURL}, Topic: alertTopic, GroupID: "test-group-" + time.Now().Format("20060102150405"), }) defer reader.Close() // Create a test beacon advertisement adv := model.BeaconAdvertisement{ ID: "integration-test-beacon", Data: "020106", // Valid hex data } // Process the beacon err := decodeBeacon(adv, appState, writer, parserRegistry) if err != nil { t.Logf("Decode beacon returned error (may be expected if no parser matches): %v", err) } // Give Kafka time to propagate time.Sleep(1 * time.Second) // Verify event was stored in appState event, exists := appState.GetBeaconEvent("integration-test-beacon") if exists { t.Logf("Event stored in appState: %+v", event) } } // TestIntegration_ParserRegistryOperations tests parser registry with real Kafka func TestIntegration_ParserRegistryOperations(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } kafkaURL := os.Getenv("KAFKA_URL") if kafkaURL == "" { kafkaURL = "localhost:9092" } alertTopic := "test-alertbeacons-registry-" + time.Now().Format("20060102150405") // Setup appState := appcontext.NewAppState() parserRegistry := &model.ParserRegistry{} writer := kafka.NewWriter(kafka.WriterConfig{ Brokers: []string{kafkaURL}, Topic: alertTopic, }) defer writer.Close() // Test parser registration through Kafka message flow parserMsg := model.KafkaParser{ ID: "add", Name: "kafka-test-parser", Config: model.Config{ Name: "kafka-test-parser", Prefix: "02", Length: 2, MinLength: 2, MaxLength: 20, }, } // Simulate parser registry update switch parserMsg.ID { case "add": config := parserMsg.Config parserRegistry.Register(config.Name, config) case "delete": parserRegistry.Unregister(parserMsg.Name) case "update": config := parserMsg.Config parserRegistry.Register(config.Name, config) } // Verify parser was registered if len(parserRegistry.ParserList) != 1 { t.Errorf("Expected 1 parser in registry, got %d", len(parserRegistry.ParserList)) } if _, exists := parserRegistry.ParserList["kafka-test-parser"]; !exists { t.Error("Parser should exist in registry") } } // TestIntegration_MultipleBeaconsSequential tests processing multiple beacons func TestIntegration_MultipleBeaconsSequential(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } kafkaURL := os.Getenv("KAFKA_URL") if kafkaURL == "" { kafkaURL = "localhost:9092" } alertTopic := "test-alertbeacons-multi-" + time.Now().Format("20060102150405") // Setup appState := appcontext.NewAppState() parserRegistry := &model.ParserRegistry{} // Register parser config := model.Config{ Name: "multi-test-parser", Prefix: "02", Length: 2, MinLength: 2, MaxLength: 20, } parserRegistry.Register("multi-test-parser", config) writer := kafka.NewWriter(kafka.WriterConfig{ Brokers: []string{kafkaURL}, Topic: alertTopic, }) defer writer.Close() reader := kafka.NewReader(kafka.ReaderConfig{ Brokers: []string{kafkaURL}, Topic: alertTopic, GroupID: "test-group-multi-" + time.Now().Format("20060102150405"), MinBytes: 10e3, MaxBytes: 10e6, }) defer reader.Close() // Process multiple beacons beacons := []model.BeaconAdvertisement{ {ID: "beacon-1", Data: "020106"}, {ID: "beacon-2", Data: "020107"}, {ID: "beacon-3", Data: "020108"}, } for _, adv := range beacons { err := decodeBeacon(adv, appState, writer, parserRegistry) if err != nil { t.Logf("Processing beacon %s returned error: %v", adv.ID, err) } } // Give Kafka time to propagate time.Sleep(2 * time.Second) // Verify events in appState for _, adv := range beacons { event, exists := appState.GetBeaconEvent(adv.ID) if exists { t.Logf("Event for %s: %+v", adv.ID, event) } } } // TestIntegration_EventDeduplication tests that duplicate events are not published func TestIntegration_EventDeduplication(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } kafkaURL := os.Getenv("KAFKA_URL") if kafkaURL == "" { kafkaURL = "localhost:9092" } alertTopic := "test-alertbeacons-dedup-" + time.Now().Format("20060102150405") // Setup appState := appcontext.NewAppState() parserRegistry := &model.ParserRegistry{} // Register parser config := model.Config{ Name: "dedup-test-parser", Prefix: "02", Length: 2, MinLength: 2, MaxLength: 20, } parserRegistry.Register("dedup-test-parser", config) writer := kafka.NewWriter(kafka.WriterConfig{ Brokers: []string{kafkaURL}, Topic: alertTopic, }) defer writer.Close() reader := kafka.NewReader(kafka.ReaderConfig{ Brokers: []string{kafkaURL}, Topic: alertTopic, GroupID: "test-group-dedup-" + time.Now().Format("20060102150405"), }) defer reader.Close() // Create identical beacon advertisement adv := model.BeaconAdvertisement{ ID: "dedup-test-beacon", Data: "020106", } // Process first time err := decodeBeacon(adv, appState, writer, parserRegistry) if err != nil { t.Logf("First processing returned error: %v", err) } // Process second time with identical data err = decodeBeacon(adv, appState, writer, parserRegistry) if err != nil { t.Logf("Second processing returned error: %v", err) } // Give Kafka time to propagate time.Sleep(1 * time.Second) // Try to read from Kafka - should have at most 1 message due to deduplication ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() messageCount := 0 for { msg, err := reader.ReadMessage(ctx) if err != nil { break } messageCount++ t.Logf("Read message %d: %s", messageCount, string(msg.Value)) if messageCount > 1 { t.Error("Expected at most 1 message due to deduplication, got more") break } } t.Logf("Total messages read: %d", messageCount) } // TestIntegration_AppStatePersistence tests that events persist in AppState func TestIntegration_AppStatePersistence(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } kafkaURL := os.Getenv("KAFKA_URL") if kafkaURL == "" { kafkaURL = "localhost:9092" } alertTopic := "test-alertbeacons-persist-" + time.Now().Format("20060102150405") // Setup appState := appcontext.NewAppState() parserRegistry := &model.ParserRegistry{} config := model.Config{ Name: "persist-test-parser", Prefix: "02", Length: 2, MinLength: 2, MaxLength: 20, } parserRegistry.Register("persist-test-parser", config) writer := kafka.NewWriter(kafka.WriterConfig{ Brokers: []string{kafkaURL}, Topic: alertTopic, }) defer writer.Close() // Process beacon adv := model.BeaconAdvertisement{ ID: "persist-test-beacon", Data: "020106", } err := decodeBeacon(adv, appState, writer, parserRegistry) if err != nil { t.Logf("Processing returned error: %v", err) } // Verify event persists in AppState event, exists := appState.GetBeaconEvent("persist-test-beacon") if !exists { t.Error("Event should exist in AppState after processing") } else { t.Logf("Event persisted: ID=%s, Type=%s, Battery=%d", event.ID, event.Type, event.Battery) // Verify event can be serialized to JSON jsonData, err := event.ToJSON() if err != nil { t.Errorf("Failed to serialize event to JSON: %v", err) } else { t.Logf("Event JSON: %s", string(jsonData)) } } } // TestIntegration_ParserUpdateFlow tests updating parsers during runtime func TestIntegration_ParserUpdateFlow(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } kafkaURL := os.Getenv("KAFKA_URL") if kafkaURL == "" { kafkaURL = "localhost:9092" } alertTopic := "test-alertbeacons-update-" + time.Now().Format("20060102150405") // Setup appState := appcontext.NewAppState() parserRegistry := &model.ParserRegistry{} writer := kafka.NewWriter(kafka.WriterConfig{ Brokers: []string{kafkaURL}, Topic: alertTopic, }) defer writer.Close() // Initial parser config config1 := model.Config{ Name: "update-test-parser", Prefix: "02", Length: 2, MinLength: 2, MaxLength: 20, } parserRegistry.Register("update-test-parser", config1) // Process with initial config adv := model.BeaconAdvertisement{ ID: "update-test-beacon", Data: "020106", } err := decodeBeacon(adv, appState, writer, parserRegistry) t.Logf("First processing: %v", err) // Update parser config config2 := model.Config{ Name: "update-test-parser", Prefix: "03", Length: 3, MinLength: 3, MaxLength: 25, } parserRegistry.Register("update-test-parser", config2) // Process again with updated config adv2 := model.BeaconAdvertisement{ ID: "update-test-beacon-2", Data: "030107", } err = decodeBeacon(adv2, appState, writer, parserRegistry) t.Logf("Second processing with updated parser: %v", err) // Verify parser still exists if _, exists := parserRegistry.ParserList["update-test-parser"]; !exists { t.Error("Parser should exist after update") } }