GlucoseAlertTests.swift 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import Foundation
  2. import Testing
  3. @testable import Trio
  4. @Suite("Trio Alerts: GlucoseAlert") struct GlucoseAlertTests {
  5. // MARK: - Group A: shouldEvaluate
  6. @Test("urgentLow evaluates even when isEnabled is false") func urgentLowDisabledStillEvaluates() {
  7. var a = GlucoseAlert(type: .urgentLow)
  8. a.isEnabled = false
  9. #expect(a.shouldEvaluate == true)
  10. }
  11. @Test("urgentLow evaluates when isEnabled is true") func urgentLowEnabledEvaluates() {
  12. var a = GlucoseAlert(type: .urgentLow)
  13. a.isEnabled = true
  14. #expect(a.shouldEvaluate == true)
  15. }
  16. @Test("low does not evaluate when disabled") func lowDisabledDoesNotEvaluate() {
  17. var a = GlucoseAlert(type: .low)
  18. a.isEnabled = false
  19. #expect(a.shouldEvaluate == false)
  20. }
  21. @Test("low evaluates when enabled") func lowEnabledEvaluates() {
  22. var a = GlucoseAlert(type: .low)
  23. a.isEnabled = true
  24. #expect(a.shouldEvaluate == true)
  25. }
  26. @Test("high does not evaluate when disabled") func highDisabledDoesNotEvaluate() {
  27. var a = GlucoseAlert(type: .high)
  28. a.isEnabled = false
  29. #expect(a.shouldEvaluate == false)
  30. }
  31. @Test("high evaluates when enabled") func highEnabledEvaluates() {
  32. var a = GlucoseAlert(type: .high)
  33. a.isEnabled = true
  34. #expect(a.shouldEvaluate == true)
  35. }
  36. @Test("forecastedLow does not evaluate when disabled") func forecastedLowDisabledDoesNotEvaluate() {
  37. var a = GlucoseAlert(type: .forecastedLow)
  38. a.isEnabled = false
  39. #expect(a.shouldEvaluate == false)
  40. }
  41. @Test("forecastedLow evaluates when enabled") func forecastedLowEnabledEvaluates() {
  42. var a = GlucoseAlert(type: .forecastedLow)
  43. a.isEnabled = true
  44. #expect(a.shouldEvaluate == true)
  45. }
  46. @Test("carbsRequired does not evaluate when disabled") func carbsRequiredDisabledDoesNotEvaluate() {
  47. var a = GlucoseAlert(type: .carbsRequired)
  48. a.isEnabled = false
  49. #expect(a.shouldEvaluate == false)
  50. }
  51. @Test("carbsRequired evaluates when enabled") func carbsRequiredEnabledEvaluates() {
  52. var a = GlucoseAlert(type: .carbsRequired)
  53. a.isEnabled = true
  54. #expect(a.shouldEvaluate == true)
  55. }
  56. // MARK: - Group A.5: type metadata
  57. @Test("carbsRequired defaults: threshold 10 g, bloop.caf, no override") func carbsRequiredDefaults() {
  58. let a = GlucoseAlert(type: .carbsRequired)
  59. #expect(a.thresholdMgDL == 10)
  60. #expect(a.soundFilename == "bloop.caf")
  61. #expect(a.overridesSilenceAndDND == false)
  62. }
  63. @Test("isReadingDriven: true for low family + high, false for forecast + carbs") func isReadingDriven() {
  64. #expect(GlucoseAlertType.urgentLow.isReadingDriven)
  65. #expect(GlucoseAlertType.low.isReadingDriven)
  66. #expect(GlucoseAlertType.high.isReadingDriven)
  67. #expect(!GlucoseAlertType.forecastedLow.isReadingDriven)
  68. #expect(!GlucoseAlertType.carbsRequired.isReadingDriven)
  69. }
  70. // MARK: - Group B: decoder defaults
  71. @Test("Omitted isEnabled defaults to true") func decodeOmittedIsEnabledDefaultsTrue() throws {
  72. let json = """
  73. {
  74. "id": "11111111-1111-1111-1111-111111111111",
  75. "type": "low",
  76. "name": "Low Glucose",
  77. "thresholdMgDL": 72
  78. }
  79. """
  80. let decoded = try JSONDecoder().decode(GlucoseAlert.self, from: Data(json.utf8))
  81. #expect(decoded.isEnabled == true)
  82. }
  83. @Test("urgentLow with isEnabled false decodes false but still evaluates") func decodeUrgentLowDisabledStillEvaluates() throws {
  84. let json = """
  85. {
  86. "id": "22222222-2222-2222-2222-222222222222",
  87. "type": "urgentLow",
  88. "name": "Urgent Low Glucose",
  89. "isEnabled": false,
  90. "thresholdMgDL": 54
  91. }
  92. """
  93. let decoded = try JSONDecoder().decode(GlucoseAlert.self, from: Data(json.utf8))
  94. #expect(decoded.isEnabled == false)
  95. #expect(decoded.shouldEvaluate == true)
  96. }
  97. @Test("high with optional fields omitted uses type defaults") func decodeHighDefaults() throws {
  98. let json = """
  99. {
  100. "id": "33333333-3333-3333-3333-333333333333",
  101. "type": "high",
  102. "name": "High Glucose",
  103. "thresholdMgDL": 270
  104. }
  105. """
  106. let decoded = try JSONDecoder().decode(GlucoseAlert.self, from: Data(json.utf8))
  107. #expect(decoded.soundFilename == GlucoseAlertType.high.defaultSoundFilename)
  108. #expect(decoded.playsSound == true)
  109. #expect(decoded.overridesSilenceAndDND == false)
  110. #expect(decoded.activeOption == .always)
  111. #expect(decoded.snoozedUntil == nil)
  112. #expect(decoded.isEnabled == true)
  113. }
  114. @Test("urgentLow with overridesSilenceAndDND omitted uses type defaults") func decodeUrgentLowOverrideDefault() throws {
  115. let json = """
  116. {
  117. "id": "44444444-4444-4444-4444-444444444444",
  118. "type": "urgentLow",
  119. "name": "Urgent Low Glucose",
  120. "thresholdMgDL": 54
  121. }
  122. """
  123. let decoded = try JSONDecoder().decode(GlucoseAlert.self, from: Data(json.utf8))
  124. #expect(decoded.overridesSilenceAndDND == true)
  125. #expect(decoded.soundFilename == GlucoseAlertType.urgentLow.defaultSoundFilename)
  126. }
  127. // MARK: - Group C: round-trip
  128. @Test("Fully-populated value survives encode/decode round-trip") func roundTrip() throws {
  129. var original = GlucoseAlert(type: .low)
  130. original.name = "Custom Low Alarm"
  131. original.isEnabled = false
  132. original.thresholdMgDL = 65
  133. original.soundFilename = "custom_sound.caf"
  134. original.playsSound = false
  135. original.overridesSilenceAndDND = true
  136. original.activeOption = .night
  137. original.snoozedUntil = Date(timeIntervalSinceReferenceDate: 1_000_000)
  138. let data = try JSONEncoder().encode(original)
  139. let decoded = try JSONDecoder().decode(GlucoseAlert.self, from: data)
  140. #expect(decoded == original)
  141. }
  142. }