GlucoseRangeScheduleTests.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. //
  2. // GlucoseRangeScheduleTests.swift
  3. // LoopKitTests
  4. //
  5. // Created by Nathaniel Hamming on 2021-03-09.
  6. // Copyright © 2021 LoopKit Authors. All rights reserved.
  7. //
  8. import XCTest
  9. import HealthKit
  10. @testable import LoopKit
  11. class GlucoseRangeScheduleTests: XCTestCase {
  12. func testInitializer() {
  13. let glucoseRangeSchedule = GlucoseRangeSchedule(
  14. unit: .milligramsPerDeciliter,
  15. dailyItems: [
  16. RepeatingScheduleValue(startTime: 0, value: DoubleRange(minValue: 75.0, maxValue: 90.0)),
  17. RepeatingScheduleValue(startTime: 3000, value: DoubleRange(minValue: 100.0, maxValue: 120.0)),
  18. RepeatingScheduleValue(startTime: 6000, value: DoubleRange(minValue: 130.0, maxValue: 150.0))
  19. ],
  20. timeZone: TimeZone(secondsFromGMT: -14400))
  21. XCTAssertNotNil(glucoseRangeSchedule)
  22. XCTAssertEqual(glucoseRangeSchedule!.unit, .milligramsPerDeciliter)
  23. XCTAssertEqual(glucoseRangeSchedule!.rangeSchedule.items.count, 3)
  24. XCTAssertEqual(glucoseRangeSchedule!.rangeSchedule.items[0].startTime, 0)
  25. XCTAssertEqual(glucoseRangeSchedule!.rangeSchedule.items[0].value, DoubleRange(minValue: 75.0, maxValue: 90.0))
  26. XCTAssertEqual(glucoseRangeSchedule!.rangeSchedule.items[1].startTime, 3000)
  27. XCTAssertEqual(glucoseRangeSchedule!.rangeSchedule.items[1].value, DoubleRange(minValue: 100.0, maxValue: 120.0))
  28. XCTAssertEqual(glucoseRangeSchedule!.rangeSchedule.items[2].startTime, 6000)
  29. XCTAssertEqual(glucoseRangeSchedule!.rangeSchedule.items[2].value, DoubleRange(minValue: 130.0, maxValue: 150.0))
  30. XCTAssertEqual(glucoseRangeSchedule!.timeZone, TimeZone(secondsFromGMT: -14400))
  31. XCTAssertEqual(glucoseRangeSchedule!.minLowerBound(), HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 75))
  32. XCTAssertEqual(glucoseRangeSchedule!.scheduleRange(), HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 75)...HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 150))
  33. }
  34. func testInitializerWithOverride() {
  35. let schedule = DailyQuantitySchedule(
  36. unit: .milligramsPerDeciliter,
  37. dailyItems: [
  38. RepeatingScheduleValue(startTime: 0, value: DoubleRange(minValue: 75.0, maxValue: 90.0)),
  39. RepeatingScheduleValue(startTime: 3000, value: DoubleRange(minValue: 100.0, maxValue: 120.0)),
  40. RepeatingScheduleValue(startTime: 6000, value: DoubleRange(minValue: 130.0, maxValue: 150.0))
  41. ])!
  42. let override = GlucoseRangeSchedule.Override(
  43. value: DoubleRange(minValue: 90.0, maxValue: 110.0),
  44. start: Date())
  45. let glucoseRangeSchedule = GlucoseRangeSchedule(
  46. rangeSchedule: schedule,
  47. override: override)
  48. XCTAssertNotNil(glucoseRangeSchedule)
  49. XCTAssertEqual(glucoseRangeSchedule.unit, .milligramsPerDeciliter)
  50. XCTAssertEqual(glucoseRangeSchedule.rangeSchedule.items.count, 3)
  51. XCTAssertEqual(glucoseRangeSchedule.rangeSchedule.items[0].startTime, 0)
  52. XCTAssertEqual(glucoseRangeSchedule.rangeSchedule.items[0].value, DoubleRange(minValue: 75.0, maxValue: 90.0))
  53. XCTAssertEqual(glucoseRangeSchedule.rangeSchedule.items[1].startTime, 3000)
  54. XCTAssertEqual(glucoseRangeSchedule.rangeSchedule.items[1].value, DoubleRange(minValue: 100.0, maxValue: 120.0))
  55. XCTAssertEqual(glucoseRangeSchedule.rangeSchedule.items[2].startTime, 6000)
  56. XCTAssertEqual(glucoseRangeSchedule.rangeSchedule.items[2].value, DoubleRange(minValue: 130.0, maxValue: 150.0))
  57. XCTAssertEqual(glucoseRangeSchedule.override, override)
  58. }
  59. func testRawValue() {
  60. let glucoseRangeSchedule = GlucoseRangeSchedule(
  61. unit: .milligramsPerDeciliter,
  62. dailyItems: [
  63. RepeatingScheduleValue(startTime: 0, value: DoubleRange(minValue: 75.0, maxValue: 90.0))
  64. ])
  65. XCTAssertNotNil(glucoseRangeSchedule)
  66. XCTAssertEqual(glucoseRangeSchedule!.rawValue["unit"] as! String, "mg/dL")
  67. XCTAssertEqual((glucoseRangeSchedule!.rawValue["items"] as! [[String: Any]]).count, 1)
  68. XCTAssertEqual((glucoseRangeSchedule!.rawValue["items"] as! [[String: Any]])[0]["value"] as! [Double], [75.0, 90.0])
  69. XCTAssertEqual((glucoseRangeSchedule!.rawValue["items"] as! [[String: Any]])[0]["startTime"] as! Double, 0.0)
  70. }
  71. func testInitializerWithRawValueValid() {
  72. let rawValue: GlucoseRangeSchedule.RawValue = [
  73. "timeZone": -14400,
  74. "unit": "mg/dL",
  75. "items": [
  76. [
  77. "startTime": 0.0,
  78. "value": [
  79. 75.0,
  80. 90.0
  81. ]
  82. ]
  83. ]
  84. ]
  85. let glucoseRangeSchedule = GlucoseRangeSchedule(rawValue: rawValue)
  86. XCTAssertNotNil(glucoseRangeSchedule)
  87. XCTAssertEqual(glucoseRangeSchedule!.items, [RepeatingScheduleValue(startTime: 0, value: DoubleRange(minValue: 75.0, maxValue: 90.0))])
  88. XCTAssertEqual(glucoseRangeSchedule!.unit, .milligramsPerDeciliter)
  89. XCTAssertEqual(glucoseRangeSchedule!.timeZone, TimeZone(secondsFromGMT: -14400))
  90. }
  91. func testInitializerWithRawValueWithValueMissing() {
  92. let rawValue: GlucoseRangeSchedule.RawValue = [
  93. "timeZone": -14400,
  94. "unit": "mg/dL"
  95. ]
  96. XCTAssertNil(GlucoseRangeSchedule(rawValue: rawValue))
  97. }
  98. func testInitializerWithRawValueWithWrongType() {
  99. let rawValue: GlucoseRangeSchedule.RawValue = [
  100. "units": "g",
  101. "timeZone": "g",
  102. "items": [
  103. [
  104. "startTime": 0,
  105. "value": [
  106. 75,
  107. 90
  108. ]
  109. ]
  110. ]
  111. ]
  112. XCTAssertNil(GlucoseRangeSchedule(rawValue: rawValue))
  113. }
  114. func testInitializerWithRawValueWithUnitMissing() {
  115. let rawValue: GlucoseRangeSchedule.RawValue = [
  116. "timeZone": -14400,
  117. "items": [
  118. [
  119. "startTime": 0.0,
  120. "value": [
  121. 75.0,
  122. 90.0
  123. ]
  124. ]
  125. ]
  126. ]
  127. XCTAssertNil(GlucoseRangeSchedule(rawValue: rawValue))
  128. }
  129. func testBetweenStartEnd() {
  130. let therapyTimeZone = TimeZone(secondsFromGMT: -4*60*60)!
  131. var calendar = Calendar(identifier: .gregorian)
  132. calendar.timeZone = therapyTimeZone
  133. let glucoseRangeSchedule = GlucoseRangeSchedule(
  134. unit: .milligramsPerDeciliter,
  135. dailyItems: [
  136. RepeatingScheduleValue(startTime: 0, value: DoubleRange(minValue: 75.0, maxValue: 90.0)),
  137. RepeatingScheduleValue(startTime: 3000, value: DoubleRange(minValue: 100.0, maxValue: 120.0)),
  138. RepeatingScheduleValue(startTime: 6000, value: DoubleRange(minValue: 130.0, maxValue: 150.0))
  139. ], timeZone: therapyTimeZone)
  140. let start = calendar.startOfDay(for: Date())
  141. let end = start.addingTimeInterval(TimeInterval.minutes(30))
  142. let expected = [AbsoluteScheduleValue(startDate: start, endDate: start.addingTimeInterval(TimeInterval.minutes(50)), value: DoubleRange(minValue: 75.0, maxValue: 90.0))]
  143. XCTAssertEqual(glucoseRangeSchedule!.between(start: start, end: end), expected)
  144. }
  145. func testQuantityBetweenStartEnd() {
  146. let therapyTimeZone = TimeZone(secondsFromGMT: -4*60*60)!
  147. var calendar = Calendar(identifier: .gregorian)
  148. calendar.timeZone = therapyTimeZone
  149. let glucoseRangeSchedule = GlucoseRangeSchedule(
  150. unit: .millimolesPerLiter,
  151. dailyItems: [
  152. RepeatingScheduleValue(startTime: 0, value: DoubleRange(minValue: 75.0, maxValue: 90.0)),
  153. RepeatingScheduleValue(startTime: 3000, value: DoubleRange(minValue: 100.0, maxValue: 120.0)),
  154. RepeatingScheduleValue(startTime: 6000, value: DoubleRange(minValue: 130.0, maxValue: 150.0))
  155. ], timeZone: therapyTimeZone)
  156. let start = calendar.startOfDay(for: Date())
  157. let end = start.addingTimeInterval(TimeInterval.minutes(30))
  158. let expected = [AbsoluteScheduleValue(startDate: start, endDate: start.addingTimeInterval(TimeInterval.minutes(50)), value: DoubleRange(minValue: 75.0, maxValue: 90.0).quantityRange(for: .millimolesPerLiter))]
  159. XCTAssertEqual(glucoseRangeSchedule!.quantityBetween(start: start, end: end), expected)
  160. }
  161. func testValueAtDate() {
  162. let therapyTimeZone = TimeZone(secondsFromGMT: -4*60*60)!
  163. var calendar = Calendar(identifier: .gregorian)
  164. calendar.timeZone = therapyTimeZone
  165. let glucoseRangeSchedule = GlucoseRangeSchedule(
  166. unit: .milligramsPerDeciliter,
  167. dailyItems: [
  168. RepeatingScheduleValue(startTime: 0, value: DoubleRange(minValue: 75.0, maxValue: 90.0)),
  169. RepeatingScheduleValue(startTime: 3000, value: DoubleRange(minValue: 100.0, maxValue: 120.0)),
  170. RepeatingScheduleValue(startTime: 6000, value: DoubleRange(minValue: 130.0, maxValue: 150.0))
  171. ], timeZone: therapyTimeZone)
  172. let inDay30Min = calendar.startOfDay(for: Date()).addingTimeInterval(TimeInterval.minutes(30))
  173. let inDay1Hour = inDay30Min.addingTimeInterval(TimeInterval.minutes(30))
  174. let inDay2Hours = inDay1Hour.addingTimeInterval(TimeInterval.minutes(60))
  175. XCTAssertEqual(glucoseRangeSchedule!.value(at: inDay30Min), DoubleRange(minValue: 75.0, maxValue: 90.0))
  176. XCTAssertEqual(glucoseRangeSchedule!.value(at: inDay1Hour), DoubleRange(minValue: 100.0, maxValue: 120.0))
  177. XCTAssertEqual(glucoseRangeSchedule!.value(at: inDay2Hours), DoubleRange(minValue: 130.0, maxValue: 150.0))
  178. }
  179. func testQuantityRangeAtDate() {
  180. let therapyTimeZone = TimeZone(secondsFromGMT: -4*60*60)!
  181. var calendar = Calendar(identifier: .gregorian)
  182. calendar.timeZone = therapyTimeZone
  183. let glucoseRangeSchedule = GlucoseRangeSchedule(
  184. unit: .milligramsPerDeciliter,
  185. dailyItems: [
  186. RepeatingScheduleValue(startTime: 0, value: DoubleRange(minValue: 75.0, maxValue: 90.0)),
  187. RepeatingScheduleValue(startTime: 3000, value: DoubleRange(minValue: 100.0, maxValue: 120.0)),
  188. RepeatingScheduleValue(startTime: 6000, value: DoubleRange(minValue: 130.0, maxValue: 150.0))
  189. ], timeZone: therapyTimeZone)
  190. let inDay30Min = calendar.startOfDay(for: Date()).addingTimeInterval(TimeInterval.minutes(30))
  191. let inDay1Hour = inDay30Min.addingTimeInterval(TimeInterval.minutes(30))
  192. let inDay2Hours = inDay1Hour.addingTimeInterval(TimeInterval.minutes(60))
  193. XCTAssertEqual(glucoseRangeSchedule!.quantityRange(at: inDay30Min), HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 75.0)...HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 90.0))
  194. XCTAssertEqual(glucoseRangeSchedule!.quantityRange(at: inDay1Hour), HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 100.0)...HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 120.0))
  195. XCTAssertEqual(glucoseRangeSchedule!.quantityRange(at: inDay2Hours), HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 130.0)...HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 150.0))
  196. }
  197. func testScheduleFor() {
  198. let therapyTimeZone = TimeZone(secondsFromGMT: -4*60*60)!
  199. var calendar = Calendar(identifier: .gregorian)
  200. calendar.timeZone = therapyTimeZone
  201. let glucoseRangeScheduleMGDL = GlucoseRangeSchedule(
  202. unit: .milligramsPerDeciliter,
  203. dailyItems: [
  204. RepeatingScheduleValue(startTime: 0, value: DoubleRange(minValue: 75.0, maxValue: 90.0))
  205. ], timeZone: therapyTimeZone)
  206. let glucoseRangeScheduleMMOLL = glucoseRangeScheduleMGDL?.schedule(for: .millimolesPerLiter)
  207. let expected = DoubleRange(minValue: 75.0, maxValue: 90.0).quantityRange(for: .milligramsPerDeciliter).doubleRange(for: .millimolesPerLiter)
  208. XCTAssertNotNil(glucoseRangeScheduleMMOLL)
  209. XCTAssertEqual(glucoseRangeScheduleMMOLL!.unit, .millimolesPerLiter)
  210. XCTAssertEqual(glucoseRangeScheduleMMOLL!.rangeSchedule.items[0].value, expected)
  211. }
  212. func testInitializeClosedHKQuantityRange() throws {
  213. let dailyItems = [
  214. RepeatingScheduleValue(startTime: 0, value: DoubleRange(minValue: 75.0, maxValue: 90.0)),
  215. RepeatingScheduleValue(startTime: 3000, value: DoubleRange(minValue: 100.0, maxValue: 120.0)),
  216. RepeatingScheduleValue(startTime: 6000, value: DoubleRange(minValue: 130.0, maxValue: 150.0))
  217. ]
  218. let dailyQuantities = dailyItems.map {
  219. RepeatingScheduleValue(startTime: $0.startTime,
  220. value: $0.value.quantityRange(for: .milligramsPerDeciliter))
  221. }
  222. let rangeSchedule = DailyQuantitySchedule(
  223. unit: .milligramsPerDeciliter,
  224. dailyQuantities: dailyQuantities)!
  225. let glucoseTargetRangeSchedule = GlucoseRangeSchedule(rangeSchedule: rangeSchedule)
  226. XCTAssertEqual(glucoseTargetRangeSchedule.rangeSchedule, rangeSchedule)
  227. }
  228. func testQuantityRanges() throws {
  229. let dailyItems = [
  230. RepeatingScheduleValue(startTime: 0, value: DoubleRange(minValue: 75.0, maxValue: 90.0)),
  231. RepeatingScheduleValue(startTime: 3000, value: DoubleRange(minValue: 100.0, maxValue: 120.0)),
  232. RepeatingScheduleValue(startTime: 6000, value: DoubleRange(minValue: 130.0, maxValue: 150.0))
  233. ]
  234. let dailyQuantities = dailyItems.map {
  235. RepeatingScheduleValue(startTime: $0.startTime,
  236. value: $0.value.quantityRange(for: .milligramsPerDeciliter))
  237. }
  238. let glucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: .milligramsPerDeciliter, dailyItems: dailyItems)!
  239. XCTAssertEqual(glucoseTargetRangeSchedule.quantityRanges, dailyQuantities)
  240. }
  241. }