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.
 
 
 
 

249 line
6.3 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. "github.com/segmentio/kafka-go"
  10. )
  11. // MockKafkaWriter is a mock implementation of kafkaWriter for testing
  12. type MockKafkaWriter struct {
  13. Messages []kafka.Message
  14. }
  15. func (m *MockKafkaWriter) WriteMessages(ctx context.Context, msgs ...kafka.Message) error {
  16. m.Messages = append(m.Messages, msgs...)
  17. return nil
  18. }
  19. // TestHelper provides utility functions for testing
  20. type TestHelper struct {
  21. t *testing.T
  22. appState *appcontext.AppState
  23. }
  24. // NewTestHelper creates a new test helper instance
  25. func NewTestHelper(t *testing.T) *TestHelper {
  26. return &TestHelper{
  27. t: t,
  28. appState: appcontext.NewAppState(),
  29. }
  30. }
  31. // GetAppState returns the appState instance
  32. func (th *TestHelper) GetAppState() *appcontext.AppState {
  33. return th.appState
  34. }
  35. // AddTestBeacon adds a beacon with the given MAC and ID to the lookup
  36. func (th *TestHelper) AddTestBeacon(mac, id string) {
  37. th.appState.AddBeaconToLookup(mac, id)
  38. }
  39. // CreateRawReading creates a test RawReading with default values
  40. func (th *TestHelper) CreateRawReading(mac string, rssi int) model.RawReading {
  41. return model.RawReading{
  42. Timestamp: time.Now().Format(time.RFC3339),
  43. Type: "BLE",
  44. MAC: mac,
  45. RSSI: rssi,
  46. RawData: "0201060302A0",
  47. }
  48. }
  49. // CreateRawReadingWithCustomData creates a test RawReading with custom raw data
  50. func (th *TestHelper) CreateRawReadingWithCustomData(mac string, rssi int, rawData string) model.RawReading {
  51. return model.RawReading{
  52. Timestamp: time.Now().Format(time.RFC3339),
  53. Type: "BLE",
  54. MAC: mac,
  55. RSSI: rssi,
  56. RawData: rawData,
  57. }
  58. }
  59. // CreateGatewayReading creates a Gateway type reading
  60. func (th *TestHelper) CreateGatewayReading(mac string) model.RawReading {
  61. return model.RawReading{
  62. Timestamp: time.Now().Format(time.RFC3339),
  63. Type: "Gateway",
  64. MAC: mac,
  65. RSSI: -50,
  66. RawData: "020106",
  67. }
  68. }
  69. // MarshalReadings marshals a slice of readings to JSON
  70. func (th *TestHelper) MarshalReadings(readings []model.RawReading) []byte {
  71. data, err := json.Marshal(readings)
  72. if err != nil {
  73. th.t.Fatalf("Failed to marshal readings: %v", err)
  74. }
  75. return data
  76. }
  77. // CreateMQTTMessage creates a complete MQTT message with readings
  78. func (th *TestHelper) CreateMQTTMessage(topic string, readings []model.RawReading) (string, []byte) {
  79. data := th.MarshalReadings(readings)
  80. return topic, data
  81. }
  82. // AssertBeaconAdvertisement asserts that a beacon advertisement matches expected values
  83. func (th *TestHelper) AssertBeaconAdvertisement(adv model.BeaconAdvertisement, expectedID, expectedHostname, expectedMAC string, expectedRSSI int64) {
  84. if adv.ID != expectedID {
  85. th.t.Errorf("Expected ID '%s', got '%s'", expectedID, adv.ID)
  86. }
  87. if adv.Hostname != expectedHostname {
  88. th.t.Errorf("Expected hostname '%s', got '%s'", expectedHostname, adv.Hostname)
  89. }
  90. if adv.MAC != expectedMAC {
  91. th.t.Errorf("Expected MAC '%s', got '%s'", expectedMAC, adv.MAC)
  92. }
  93. if adv.RSSI != expectedRSSI {
  94. th.t.Errorf("Expected RSSI %d, got %d", expectedRSSI, adv.RSSI)
  95. }
  96. }
  97. // GenerateTestMAC generates a test MAC address from an index
  98. func GenerateTestMAC(index int) string {
  99. return "AA:BB:CC:DD:" + toHex(index>>8) + ":" + toHex(index&0xFF)
  100. }
  101. // GenerateTestID generates a test beacon ID from an index
  102. func GenerateTestID(index int) string {
  103. return "test-beacon-" + toHex(index)
  104. }
  105. // toHex converts a number to a 2-digit hex string
  106. func toHex(n int) string {
  107. return formatInt(n, 16)
  108. }
  109. // Helper function to format int as hex string
  110. func formatInt(n, base int) string {
  111. const digits = "0123456789ABCDEF"
  112. if n == 0 {
  113. return "00"
  114. }
  115. result := ""
  116. for n > 0 {
  117. remainder := n % base
  118. result = string(digits[remainder]) + result
  119. n = n / base
  120. }
  121. // Pad to 2 digits
  122. for len(result) < 2 {
  123. result = "0" + result
  124. }
  125. return result
  126. }
  127. // CreateMockMessage creates a mock MQTT message for testing
  128. type MockMessage struct {
  129. topic string
  130. payload []byte
  131. }
  132. // NewMockMessage creates a new mock message
  133. func NewMockMessage(topic string, payload []byte) *MockMessage {
  134. return &MockMessage{
  135. topic: topic,
  136. payload: payload,
  137. }
  138. }
  139. // Topic returns the message topic
  140. func (m *MockMessage) Topic() string {
  141. return m.topic
  142. }
  143. // Payload returns the message payload
  144. func (m *MockMessage) Payload() []byte {
  145. return m.payload
  146. }
  147. // Asserted returns a flag (not used in mock)
  148. func (m *MockMessage) Asserted() bool {
  149. return false
  150. }
  151. // Duplicate returns a flag (not used in mock)
  152. func (m *MockMessage) Duplicate() bool {
  153. return false
  154. }
  155. // QoS returns the QoS level (not used in mock)
  156. func (m *MockMessage) QoS() byte {
  157. return 0
  158. }
  159. // Retained returns retained flag (not used in mock)
  160. func (m *MockMessage) Retained() bool {
  161. return false
  162. }
  163. // MessageID returns message ID (not used in mock)
  164. func (m *MockMessage) MessageID() uint16 {
  165. return 0
  166. }
  167. // SetupTestBeacons configures the appState with a standard set of test beacons
  168. func SetupTestBeacons(appState *appcontext.AppState) {
  169. beacons := []struct {
  170. mac string
  171. id string
  172. }{
  173. {"AA:BB:CC:DD:EE:FF", "beacon-1"},
  174. {"11:22:33:44:55:66", "beacon-2"},
  175. {"77:88:99:AA:BB:CC", "beacon-3"},
  176. {"DD:EE:FF:00:11:22", "beacon-4"},
  177. }
  178. for _, b := range beacons {
  179. appState.AddBeaconToLookup(b.mac, b.id)
  180. }
  181. }
  182. // CreateTestReadings creates a slice of test readings
  183. func CreateTestReadings(count int) []model.RawReading {
  184. readings := make([]model.RawReading, count)
  185. for i := 0; i < count; i++ {
  186. readings[i] = model.RawReading{
  187. Timestamp: time.Now().Format(time.RFC3339),
  188. Type: "BLE",
  189. MAC: GenerateTestMAC(i),
  190. RSSI: -60 - i,
  191. RawData: "0201060302A0",
  192. }
  193. }
  194. return readings
  195. }
  196. // CleanupTestState cleans up the appState lookup
  197. func CleanupTestState(appState *appcontext.AppState) {
  198. appState.CleanLookup()
  199. }
  200. // AssertKafkaMessageCount asserts that the mock writer received the expected number of messages
  201. func AssertKafkaMessageCount(t *testing.T, writer *MockKafkaWriter, expected int) {
  202. if len(writer.Messages) != expected {
  203. t.Errorf("Expected %d Kafka message(s), got %d", expected, len(writer.Messages))
  204. }
  205. }
  206. // AssertNoKafkaMessages asserts that no messages were written to Kafka
  207. func AssertNoKafkaMessages(t *testing.T, writer *MockKafkaWriter) {
  208. AssertKafkaMessageCount(t, writer, 0)
  209. }