|
- package bridge
-
- import (
- "context"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/AFASystems/presence/internal/pkg/common/appcontext"
- "github.com/AFASystems/presence/internal/pkg/model"
- mqtt "github.com/eclipse/paho.mqtt.golang"
- )
-
- // MockMQTTClient is a mock implementation of mqtt.Client for testing
- type MockMQTTClient struct {
- PublishedMessages map[string][]byte
- }
-
- func NewMockMQTTClient() *MockMQTTClient {
- return &MockMQTTClient{
- PublishedMessages: make(map[string][]byte),
- }
- }
-
- func (m *MockMQTTClient) Publish(topic string, qos byte, retained bool, payload interface{}) mqtt.Token {
- // Convert payload to bytes
- var payloadBytes []byte
- if b, ok := payload.([]byte); ok {
- payloadBytes = b
- } else {
- payloadBytes, _ = json.Marshal(payload)
- }
- m.PublishedMessages[topic] = payloadBytes
- return &mockToken{}
- }
-
- func (m *MockMQTTClient) Subscribe(topic string, qos byte, handler mqtt.MessageHandler) mqtt.Token {
- return &mockToken{}
- }
-
- func (m *MockMQTTClient) Disconnect(quiesce uint) {
- // Mock implementation
- }
-
- type mockToken struct{}
-
- func (m *mockToken) Wait() bool {
- return true
- }
-
- func (m *mockToken) WaitTimeout(time.Duration) bool {
- return true
- }
-
- func (m *mockToken) Error() error {
- return nil
- }
-
- func (m *mockToken) Done() <-chan struct{} {
- ch := make(chan struct{})
- close(ch)
- return ch
- }
-
- func TestEventLoop_ApiUpdate_POST(t *testing.T) {
- // Setup
- appState := appcontext.NewAppState()
-
- chApi := make(chan model.ApiUpdate, 10)
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- // Create a POST message
- msg := model.ApiUpdate{
- Method: "POST",
- MAC: "AA:BB:CC:DD:EE:FF",
- ID: "beacon-123",
- }
-
- // Test channel send in a goroutine
- go func() {
- chApi <- msg
- time.Sleep(100 * time.Millisecond)
- cancel()
- }()
-
- // Simulate the event loop handling
- select {
- case <-ctx.Done():
- // Context canceled
- case msg := <-chApi:
- if msg.Method == "POST" {
- appState.AddBeaconToLookup(msg.MAC, msg.ID)
- }
- }
-
- // Assert
- beaconID, exists := appState.BeaconExists("AA:BB:CC:DD:EE:FF")
- if !exists {
- t.Error("Expected beacon to exist in lookup")
- }
-
- if beaconID != "beacon-123" {
- t.Errorf("Expected beacon ID 'beacon-123', got '%s'", beaconID)
- }
- }
-
- func TestEventLoop_ApiUpdate_DELETE(t *testing.T) {
- // Setup
- appState := appcontext.NewAppState()
- appState.AddBeaconToLookup("AA:BB:CC:DD:EE:FF", "beacon-123")
-
- chApi := make(chan model.ApiUpdate, 10)
-
- // Create a DELETE message
- msg := model.ApiUpdate{
- Method: "DELETE",
- MAC: "AA:BB:CC:DD:EE:FF",
- }
-
- // Simulate the event loop handling
- chApi <- msg
-
- select {
- case msg := <-chApi:
- if msg.Method == "DELETE" {
- appState.RemoveBeaconFromLookup(msg.MAC)
- }
- case <-time.After(1 * time.Second):
- t.Fatal("Timeout waiting for message")
- }
-
- // Assert
- _, exists := appState.BeaconExists("AA:BB:CC:DD:EE:FF")
- if exists {
- t.Error("Expected beacon to be removed from lookup")
- }
- }
-
- func TestEventLoop_ApiUpdate_DELETE_All(t *testing.T) {
- // Setup
- appState := appcontext.NewAppState()
- appState.AddBeaconToLookup("AA:BB:CC:DD:EE:FF", "beacon-1")
- appState.AddBeaconToLookup("11:22:33:44:55:66", "beacon-2")
-
- chApi := make(chan model.ApiUpdate, 10)
-
- // Create a DELETE all message
- msg := model.ApiUpdate{
- Method: "DELETE",
- MAC: "all",
- }
-
- // Simulate the event loop handling
- chApi <- msg
-
- select {
- case msg := <-chApi:
- if msg.Method == "DELETE" && msg.MAC == "all" {
- appState.CleanLookup()
- }
- case <-time.After(1 * time.Second):
- t.Fatal("Timeout waiting for message")
- }
-
- // Assert
- _, exists1 := appState.BeaconExists("AA:BB:CC:DD:EE:FF")
- _, exists2 := appState.BeaconExists("11:22:33:44:55:66")
-
- if exists1 || exists2 {
- t.Error("Expected all beacons to be removed from lookup")
- }
- }
-
- func TestEventLoop_AlertMessage(t *testing.T) {
- // Setup
- mockClient := NewMockMQTTClient()
-
- chAlert := make(chan model.Alert, 10)
-
- // Create an alert message
- msg := model.Alert{
- ID: "tracker-123",
- Type: "battery_low",
- Value: "15",
- }
-
- go func() {
- alert := <-chAlert
- p, _ := json.Marshal(alert)
- mockClient.Publish("/alerts", 0, true, p)
- }()
-
- chAlert <- msg
- time.Sleep(100 * time.Millisecond)
-
- // Assert
- if _, exists := mockClient.PublishedMessages["/alerts"]; !exists {
- t.Error("Expected message to be published to /alerts topic")
- }
-
- var publishedAlert model.Alert
- err := json.Unmarshal(mockClient.PublishedMessages["/alerts"], &publishedAlert)
- if err != nil {
- t.Fatalf("Failed to unmarshal published alert: %v", err)
- }
-
- if publishedAlert.ID != "tracker-123" {
- t.Errorf("Expected ID 'tracker-123', got '%s'", publishedAlert.ID)
- }
-
- if publishedAlert.Type != "battery_low" {
- t.Errorf("Expected Type 'battery_low', got '%s'", publishedAlert.Type)
- }
- }
-
- func TestEventLoop_TrackerMessage(t *testing.T) {
- // Setup
- mockClient := NewMockMQTTClient()
-
- chMqtt := make(chan []model.Tracker, 10)
-
- // Create tracker messages
- trackers := []model.Tracker{
- {
- ID: "tracker-1",
- Name: "Tracker One",
- MAC: "AA:BB:CC:DD:EE:FF",
- Status: "active",
- X: 10.5,
- Y: 20.3,
- },
- {
- ID: "tracker-2",
- Name: "Tracker Two",
- MAC: "11:22:33:44:55:66",
- Status: "inactive",
- X: 15.2,
- Y: 25.7,
- },
- }
-
- go func() {
- trackerMsg := <-chMqtt
- p, _ := json.Marshal(trackerMsg)
- mockClient.Publish("/trackers", 0, true, p)
- }()
-
- chMqtt <- trackers
- time.Sleep(100 * time.Millisecond)
-
- // Assert
- if _, exists := mockClient.PublishedMessages["/trackers"]; !exists {
- t.Error("Expected message to be published to /trackers topic")
- }
-
- var publishedTrackers []model.Tracker
- err := json.Unmarshal(mockClient.PublishedMessages["/trackers"], &publishedTrackers)
- if err != nil {
- t.Fatalf("Failed to unmarshal published trackers: %v", err)
- }
-
- if len(publishedTrackers) != 2 {
- t.Errorf("Expected 2 trackers, got %d", len(publishedTrackers))
- }
-
- if publishedTrackers[0].Name != "Tracker One" {
- t.Errorf("Expected tracker name 'Tracker One', got '%s'", publishedTrackers[0].Name)
- }
- }
-
- func TestEventLoop_ContextCancellation(t *testing.T) {
- // Setup
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- chApi := make(chan model.ApiUpdate, 10)
- chAlert := make(chan model.Alert, 10)
- chMqtt := make(chan []model.Tracker, 10)
-
- // Cancel context immediately
- cancel()
-
- // Simulate event loop
- select {
- case <-ctx.Done():
- // Expected - context was canceled
- return
- case msg := <-chApi:
- t.Errorf("Should not receive API messages after context cancellation, got: %+v", msg)
- case msg := <-chAlert:
- t.Errorf("Should not receive alert messages after context cancellation, got: %+v", msg)
- case msg := <-chMqtt:
- t.Errorf("Should not receive tracker messages after context cancellation, got: %+v", msg)
- case <-time.After(1 * time.Second):
- t.Error("Timeout - context cancellation should have been immediate")
- }
- }
|