GlucoseAlertCoordinatorTests.swift 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import Foundation
  2. import Testing
  3. @testable import Trio
  4. @Suite("Trio Alerts: GlucoseAlertCoordinator breach/retract") struct GlucoseAlertCoordinatorTests {
  5. @Test("low breaches at or below threshold 70") func lowBreach() {
  6. #expect(GlucoseAlertCoordinator.breached(type: .low, latestMgDL: 70, thresholdMgDL: 70))
  7. #expect(!GlucoseAlertCoordinator.breached(type: .low, latestMgDL: 71, thresholdMgDL: 70))
  8. }
  9. @Test("low retracts only at threshold + margin (70 + 5)") func lowRetract() {
  10. #expect(!GlucoseAlertCoordinator.shouldRetract(
  11. type: .low, latestMgDL: 74, thresholdMgDL: 70, recoveryMarginMgDL: 5
  12. ))
  13. #expect(GlucoseAlertCoordinator.shouldRetract(
  14. type: .low, latestMgDL: 75, thresholdMgDL: 70, recoveryMarginMgDL: 5
  15. ))
  16. }
  17. @Test("urgentLow breaches at or below threshold 54") func urgentLowBreach() {
  18. #expect(GlucoseAlertCoordinator.breached(type: .urgentLow, latestMgDL: 54, thresholdMgDL: 54))
  19. #expect(!GlucoseAlertCoordinator.breached(type: .urgentLow, latestMgDL: 55, thresholdMgDL: 54))
  20. }
  21. @Test("high breaches at or above threshold 270") func highBreach() {
  22. #expect(GlucoseAlertCoordinator.breached(type: .high, latestMgDL: 270, thresholdMgDL: 270))
  23. #expect(!GlucoseAlertCoordinator.breached(type: .high, latestMgDL: 269, thresholdMgDL: 270))
  24. }
  25. @Test("high retracts only at threshold - margin (270 - 5)") func highRetract() {
  26. #expect(!GlucoseAlertCoordinator.shouldRetract(
  27. type: .high, latestMgDL: 266, thresholdMgDL: 270, recoveryMarginMgDL: 5
  28. ))
  29. #expect(GlucoseAlertCoordinator.shouldRetract(
  30. type: .high, latestMgDL: 265, thresholdMgDL: 270, recoveryMarginMgDL: 5
  31. ))
  32. }
  33. @Test("type priority order: urgentLow < low < forecastedLow < high < carbsRequired") func priorityOrder() {
  34. #expect(GlucoseAlertType.urgentLow.priority == 0)
  35. #expect(GlucoseAlertType.low.priority == 1)
  36. #expect(GlucoseAlertType.forecastedLow.priority == 2)
  37. #expect(GlucoseAlertType.high.priority == 3)
  38. #expect(GlucoseAlertType.carbsRequired.priority == 4)
  39. #expect(GlucoseAlertType.urgentLow.priority < GlucoseAlertType.low.priority)
  40. }
  41. @Test("shouldRetract uses the default 5 mg/dL margin when omitted") func defaultMarginWiring() {
  42. #expect(GlucoseAlertCoordinator.shouldRetract(type: .low, latestMgDL: 75, thresholdMgDL: 70))
  43. #expect(!GlucoseAlertCoordinator.shouldRetract(type: .low, latestMgDL: 74, thresholdMgDL: 70))
  44. }
  45. // MARK: - Carbs Required predicates (mg/dL-based gates are no-ops)
  46. /// Carbs Required is determination-driven (`evaluateCarbsRequired(_:)`),
  47. /// not reading-driven. The shared mg/dL `breached` predicate must never
  48. /// return true for it, regardless of value / threshold combination.
  49. @Test("carbsRequired never breaches via the mg/dL predicate") func carbsRequiredNeverBreaches() {
  50. #expect(!GlucoseAlertCoordinator.breached(type: .carbsRequired, latestMgDL: 0, thresholdMgDL: 0))
  51. #expect(!GlucoseAlertCoordinator.breached(type: .carbsRequired, latestMgDL: 200, thresholdMgDL: 10))
  52. #expect(!GlucoseAlertCoordinator.breached(type: .carbsRequired, latestMgDL: 10, thresholdMgDL: 200))
  53. }
  54. @Test("carbsRequired never retracts via the mg/dL predicate") func carbsRequiredNeverRetractsViaMgDL() {
  55. #expect(!GlucoseAlertCoordinator.shouldRetract(
  56. type: .carbsRequired, latestMgDL: 100, thresholdMgDL: 10, recoveryMarginMgDL: 5
  57. ))
  58. #expect(!GlucoseAlertCoordinator.shouldRetract(
  59. type: .carbsRequired, latestMgDL: 0, thresholdMgDL: 50, recoveryMarginMgDL: 5
  60. ))
  61. }
  62. }