BolusSafetyValidatorTests.swift 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import Foundation
  2. import Testing
  3. @testable import Trio
  4. @Suite("Bolus Safety Validator Tests") struct BolusSafetyValidatorTests: Injectable {
  5. @Injected() var validator: BolusSafetyValidator!
  6. let resolver = TrioApp().resolver
  7. init() {
  8. injectServices(resolver)
  9. }
  10. @Test("Validator resolves from the service container") func testValidatorResolves() {
  11. #expect(validator != nil, "BolusSafetyValidator should be registered in ServiceAssembly")
  12. #expect(validator is BaseBolusSafetyValidator, "Validator should be of type BaseBolusSafetyValidator")
  13. }
  14. @Test("Allows bolus when all inputs are within limits") func testAllowed() {
  15. let inputs = BolusSafetyInputs(
  16. maxBolus: 10,
  17. maxIOB: 10,
  18. currentIOB: 1,
  19. totalRecentBolus: 0
  20. )
  21. #expect(BolusSafetyEvaluator.evaluate(bolusAmount: 5, inputs: inputs) == .allowed)
  22. }
  23. @Test("Rejects when amount exceeds max bolus") func testExceedsMaxBolus() {
  24. let inputs = BolusSafetyInputs(
  25. maxBolus: 5,
  26. maxIOB: 10,
  27. currentIOB: 0,
  28. totalRecentBolus: 0
  29. )
  30. let result = BolusSafetyEvaluator.evaluate(bolusAmount: 6, inputs: inputs)
  31. #expect(result == .rejected(.exceedsMaxBolus(maxBolus: 5)))
  32. }
  33. @Test("Rejects when current IOB is unavailable") func testIOBUnavailable() {
  34. let inputs = BolusSafetyInputs(
  35. maxBolus: 10,
  36. maxIOB: 10,
  37. currentIOB: nil,
  38. totalRecentBolus: 0
  39. )
  40. let result = BolusSafetyEvaluator.evaluate(bolusAmount: 1, inputs: inputs)
  41. #expect(result == .rejected(.iobUnavailable))
  42. }
  43. @Test("Rejects when amount would exceed max IOB") func testExceedsMaxIOB() {
  44. let inputs = BolusSafetyInputs(
  45. maxBolus: 10,
  46. maxIOB: 5,
  47. currentIOB: 3,
  48. totalRecentBolus: 0
  49. )
  50. let result = BolusSafetyEvaluator.evaluate(bolusAmount: 2.5, inputs: inputs)
  51. #expect(result == .rejected(.exceedsMaxIOB(currentIOB: 3, maxIOB: 5)))
  52. }
  53. @Test("Rejects when recent bolus totals >= 20% of requested amount") func testRecentBolusWithinWindow() {
  54. let inputs = BolusSafetyInputs(
  55. maxBolus: 10,
  56. maxIOB: 10,
  57. currentIOB: 0,
  58. totalRecentBolus: 1.0
  59. )
  60. let result = BolusSafetyEvaluator.evaluate(bolusAmount: 5, inputs: inputs)
  61. #expect(result == .rejected(.recentBolusWithinWindow(totalRecent: 1.0)))
  62. }
  63. @Test("Allows when recent bolus total is below 20% threshold") func testRecentBolusBelowThreshold() {
  64. let inputs = BolusSafetyInputs(
  65. maxBolus: 10,
  66. maxIOB: 10,
  67. currentIOB: 0,
  68. totalRecentBolus: 0.99
  69. )
  70. #expect(BolusSafetyEvaluator.evaluate(bolusAmount: 5, inputs: inputs) == .allowed)
  71. }
  72. @Test("Max bolus check runs before IOB check") func testCheckOrdering() {
  73. let inputs = BolusSafetyInputs(
  74. maxBolus: 5,
  75. maxIOB: 10,
  76. currentIOB: nil,
  77. totalRecentBolus: 0
  78. )
  79. let result = BolusSafetyEvaluator.evaluate(bolusAmount: 6, inputs: inputs)
  80. #expect(result == .rejected(.exceedsMaxBolus(maxBolus: 5)))
  81. }
  82. @Test("Equal-to-max-bolus is allowed") func testEqualsMaxBolus() {
  83. let inputs = BolusSafetyInputs(
  84. maxBolus: 5,
  85. maxIOB: 10,
  86. currentIOB: 0,
  87. totalRecentBolus: 0
  88. )
  89. #expect(BolusSafetyEvaluator.evaluate(bolusAmount: 5, inputs: inputs) == .allowed)
  90. }
  91. @Test("Current IOB plus amount equal to max IOB is allowed") func testEqualToMaxIOB() {
  92. let inputs = BolusSafetyInputs(
  93. maxBolus: 10,
  94. maxIOB: 5,
  95. currentIOB: 3,
  96. totalRecentBolus: 0
  97. )
  98. #expect(BolusSafetyEvaluator.evaluate(bolusAmount: 2, inputs: inputs) == .allowed)
  99. }
  100. }