You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

299 lines
6.7 KiB

  1. package bridge
  2. import (
  3. "context"
  4. "encoding/json"
  5. "testing"
  6. "time"
  7. "github.com/AFASystems/presence/internal/pkg/common/appcontext"
  8. "github.com/AFASystems/presence/internal/pkg/model"
  9. mqtt "github.com/eclipse/paho.mqtt.golang"
  10. )
  11. // MockMQTTClient is a mock implementation of mqtt.Client for testing
  12. type MockMQTTClient struct {
  13. PublishedMessages map[string][]byte
  14. }
  15. func NewMockMQTTClient() *MockMQTTClient {
  16. return &MockMQTTClient{
  17. PublishedMessages: make(map[string][]byte),
  18. }
  19. }
  20. func (m *MockMQTTClient) Publish(topic string, qos byte, retained bool, payload interface{}) mqtt.Token {
  21. // Convert payload to bytes
  22. var payloadBytes []byte
  23. if b, ok := payload.([]byte); ok {
  24. payloadBytes = b
  25. } else {
  26. payloadBytes, _ = json.Marshal(payload)
  27. }
  28. m.PublishedMessages[topic] = payloadBytes
  29. return &mockToken{}
  30. }
  31. func (m *MockMQTTClient) Subscribe(topic string, qos byte, handler mqtt.MessageHandler) mqtt.Token {
  32. return &mockToken{}
  33. }
  34. func (m *MockMQTTClient) Disconnect(quiesce uint) {
  35. // Mock implementation
  36. }
  37. type mockToken struct{}
  38. func (m *mockToken) Wait() bool {
  39. return true
  40. }
  41. func (m *mockToken) WaitTimeout(time.Duration) bool {
  42. return true
  43. }
  44. func (m *mockToken) Error() error {
  45. return nil
  46. }
  47. func (m *mockToken) Done() <-chan struct{} {
  48. ch := make(chan struct{})
  49. close(ch)
  50. return ch
  51. }
  52. func TestEventLoop_ApiUpdate_POST(t *testing.T) {
  53. // Setup
  54. appState := appcontext.NewAppState()
  55. chApi := make(chan model.ApiUpdate, 10)
  56. ctx, cancel := context.WithCancel(context.Background())
  57. defer cancel()
  58. // Create a POST message
  59. msg := model.ApiUpdate{
  60. Method: "POST",
  61. MAC: "AA:BB:CC:DD:EE:FF",
  62. ID: "beacon-123",
  63. }
  64. // Test channel send in a goroutine
  65. go func() {
  66. chApi <- msg
  67. time.Sleep(100 * time.Millisecond)
  68. cancel()
  69. }()
  70. // Simulate the event loop handling
  71. select {
  72. case <-ctx.Done():
  73. // Context canceled
  74. case msg := <-chApi:
  75. if msg.Method == "POST" {
  76. appState.AddBeaconToLookup(msg.MAC, msg.ID)
  77. }
  78. }
  79. // Assert
  80. beaconID, exists := appState.BeaconExists("AA:BB:CC:DD:EE:FF")
  81. if !exists {
  82. t.Error("Expected beacon to exist in lookup")
  83. }
  84. if beaconID != "beacon-123" {
  85. t.Errorf("Expected beacon ID 'beacon-123', got '%s'", beaconID)
  86. }
  87. }
  88. func TestEventLoop_ApiUpdate_DELETE(t *testing.T) {
  89. // Setup
  90. appState := appcontext.NewAppState()
  91. appState.AddBeaconToLookup("AA:BB:CC:DD:EE:FF", "beacon-123")
  92. chApi := make(chan model.ApiUpdate, 10)
  93. // Create a DELETE message
  94. msg := model.ApiUpdate{
  95. Method: "DELETE",
  96. MAC: "AA:BB:CC:DD:EE:FF",
  97. }
  98. // Simulate the event loop handling
  99. chApi <- msg
  100. select {
  101. case msg := <-chApi:
  102. if msg.Method == "DELETE" {
  103. appState.RemoveBeaconFromLookup(msg.MAC)
  104. }
  105. case <-time.After(1 * time.Second):
  106. t.Fatal("Timeout waiting for message")
  107. }
  108. // Assert
  109. _, exists := appState.BeaconExists("AA:BB:CC:DD:EE:FF")
  110. if exists {
  111. t.Error("Expected beacon to be removed from lookup")
  112. }
  113. }
  114. func TestEventLoop_ApiUpdate_DELETE_All(t *testing.T) {
  115. // Setup
  116. appState := appcontext.NewAppState()
  117. appState.AddBeaconToLookup("AA:BB:CC:DD:EE:FF", "beacon-1")
  118. appState.AddBeaconToLookup("11:22:33:44:55:66", "beacon-2")
  119. chApi := make(chan model.ApiUpdate, 10)
  120. // Create a DELETE all message
  121. msg := model.ApiUpdate{
  122. Method: "DELETE",
  123. MAC: "all",
  124. }
  125. // Simulate the event loop handling
  126. chApi <- msg
  127. select {
  128. case msg := <-chApi:
  129. if msg.Method == "DELETE" && msg.MAC == "all" {
  130. appState.CleanLookup()
  131. }
  132. case <-time.After(1 * time.Second):
  133. t.Fatal("Timeout waiting for message")
  134. }
  135. // Assert
  136. _, exists1 := appState.BeaconExists("AA:BB:CC:DD:EE:FF")
  137. _, exists2 := appState.BeaconExists("11:22:33:44:55:66")
  138. if exists1 || exists2 {
  139. t.Error("Expected all beacons to be removed from lookup")
  140. }
  141. }
  142. func TestEventLoop_AlertMessage(t *testing.T) {
  143. // Setup
  144. mockClient := NewMockMQTTClient()
  145. chAlert := make(chan model.Alert, 10)
  146. // Create an alert message
  147. msg := model.Alert{
  148. ID: "tracker-123",
  149. Type: "battery_low",
  150. Value: "15",
  151. }
  152. go func() {
  153. alert := <-chAlert
  154. p, _ := json.Marshal(alert)
  155. mockClient.Publish("/alerts", 0, true, p)
  156. }()
  157. chAlert <- msg
  158. time.Sleep(100 * time.Millisecond)
  159. // Assert
  160. if _, exists := mockClient.PublishedMessages["/alerts"]; !exists {
  161. t.Error("Expected message to be published to /alerts topic")
  162. }
  163. var publishedAlert model.Alert
  164. err := json.Unmarshal(mockClient.PublishedMessages["/alerts"], &publishedAlert)
  165. if err != nil {
  166. t.Fatalf("Failed to unmarshal published alert: %v", err)
  167. }
  168. if publishedAlert.ID != "tracker-123" {
  169. t.Errorf("Expected ID 'tracker-123', got '%s'", publishedAlert.ID)
  170. }
  171. if publishedAlert.Type != "battery_low" {
  172. t.Errorf("Expected Type 'battery_low', got '%s'", publishedAlert.Type)
  173. }
  174. }
  175. func TestEventLoop_TrackerMessage(t *testing.T) {
  176. // Setup
  177. mockClient := NewMockMQTTClient()
  178. chMqtt := make(chan []model.Tracker, 10)
  179. // Create tracker messages
  180. trackers := []model.Tracker{
  181. {
  182. ID: "tracker-1",
  183. Name: "Tracker One",
  184. MAC: "AA:BB:CC:DD:EE:FF",
  185. Status: "active",
  186. X: 10.5,
  187. Y: 20.3,
  188. },
  189. {
  190. ID: "tracker-2",
  191. Name: "Tracker Two",
  192. MAC: "11:22:33:44:55:66",
  193. Status: "inactive",
  194. X: 15.2,
  195. Y: 25.7,
  196. },
  197. }
  198. go func() {
  199. trackerMsg := <-chMqtt
  200. p, _ := json.Marshal(trackerMsg)
  201. mockClient.Publish("/trackers", 0, true, p)
  202. }()
  203. chMqtt <- trackers
  204. time.Sleep(100 * time.Millisecond)
  205. // Assert
  206. if _, exists := mockClient.PublishedMessages["/trackers"]; !exists {
  207. t.Error("Expected message to be published to /trackers topic")
  208. }
  209. var publishedTrackers []model.Tracker
  210. err := json.Unmarshal(mockClient.PublishedMessages["/trackers"], &publishedTrackers)
  211. if err != nil {
  212. t.Fatalf("Failed to unmarshal published trackers: %v", err)
  213. }
  214. if len(publishedTrackers) != 2 {
  215. t.Errorf("Expected 2 trackers, got %d", len(publishedTrackers))
  216. }
  217. if publishedTrackers[0].Name != "Tracker One" {
  218. t.Errorf("Expected tracker name 'Tracker One', got '%s'", publishedTrackers[0].Name)
  219. }
  220. }
  221. func TestEventLoop_ContextCancellation(t *testing.T) {
  222. // Setup
  223. ctx, cancel := context.WithCancel(context.Background())
  224. defer cancel()
  225. chApi := make(chan model.ApiUpdate, 10)
  226. chAlert := make(chan model.Alert, 10)
  227. chMqtt := make(chan []model.Tracker, 10)
  228. // Cancel context immediately
  229. cancel()
  230. // Simulate event loop
  231. select {
  232. case <-ctx.Done():
  233. // Expected - context was canceled
  234. return
  235. case msg := <-chApi:
  236. t.Errorf("Should not receive API messages after context cancellation, got: %+v", msg)
  237. case msg := <-chAlert:
  238. t.Errorf("Should not receive alert messages after context cancellation, got: %+v", msg)
  239. case msg := <-chMqtt:
  240. t.Errorf("Should not receive tracker messages after context cancellation, got: %+v", msg)
  241. case <-time.After(1 * time.Second):
  242. t.Error("Timeout - context cancellation should have been immediate")
  243. }
  244. }