DoseStoreTests.swift 85 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577
  1. //
  2. // DoseStoreTests.swift
  3. // LoopKit
  4. //
  5. // Copyright © 2017 LoopKit Authors. All rights reserved.
  6. //
  7. import XCTest
  8. import CoreData
  9. import HealthKit
  10. @testable import LoopKit
  11. class DoseStoreTests: PersistenceControllerTestCase {
  12. func loadReservoirFixture(_ resourceName: String) -> [NewReservoirValue] {
  13. let fixture: [JSONDictionary] = loadFixture(resourceName)
  14. let dateFormatter = ISO8601DateFormatter.localTimeDate(timeZone: .utcTimeZone)
  15. return fixture.map {
  16. return NewReservoirValue(startDate: dateFormatter.date(from: $0["date"] as! String)!, unitVolume: $0["amount"] as! Double)
  17. }
  18. }
  19. func defaultStore(testingDate: Date? = nil) -> DoseStore {
  20. let healthStore = HKHealthStoreMock()
  21. let doseStore = DoseStore(
  22. healthStore: healthStore,
  23. cacheStore: cacheStore,
  24. observationEnabled: false,
  25. insulinModelProvider: StaticInsulinModelProvider(WalshInsulinModel(actionDuration: .hours(4))),
  26. longestEffectDuration: .hours(4),
  27. basalProfile: BasalRateSchedule(rawValue: ["timeZone": -28800, "items": [["value": 0.75, "startTime": 0.0], ["value": 0.8, "startTime": 10800.0], ["value": 0.85, "startTime": 32400.0], ["value": 1.0, "startTime": 68400.0]]]),
  28. insulinSensitivitySchedule: InsulinSensitivitySchedule(rawValue: ["unit": "mg/dL", "timeZone": -28800, "items": [["value": 40.0, "startTime": 0.0], ["value": 35.0, "startTime": 21600.0], ["value": 40.0, "startTime": 57600.0]]]),
  29. syncVersion: 1,
  30. provenanceIdentifier: Bundle.main.bundleIdentifier!,
  31. test_currentDate: testingDate
  32. )
  33. let semaphore = DispatchSemaphore(value: 0)
  34. cacheStore.onReady { (error) in
  35. semaphore.signal()
  36. }
  37. semaphore.wait()
  38. return doseStore
  39. }
  40. let testingDateFormatter = DateFormatter.descriptionFormatter
  41. func testingDate(_ input: String) -> Date {
  42. return testingDateFormatter.date(from: input)!
  43. }
  44. func testEmptyDoseStoreReturnsZeroInsulinOnBoard() {
  45. let doseStore = defaultStore()
  46. let queryFinishedExpectation = expectation(description: "query finished")
  47. doseStore.insulinOnBoard(at: Date()) { (result) in
  48. switch result {
  49. case .failure(let error):
  50. XCTFail("Unexpected error: \(error)")
  51. case .success(let value):
  52. XCTAssertEqual(0, value.value)
  53. }
  54. queryFinishedExpectation.fulfill()
  55. }
  56. waitForExpectations(timeout: 3)
  57. }
  58. func testGetNormalizedDoseEntriesUsingReservoir() {
  59. let now = testingDate("2022-09-05 02:04:00 +0000")
  60. let doseStore = defaultStore(testingDate: now)
  61. let reservoirReadings = loadReservoirFixture("reservoir_iob_test")
  62. let storageExpectations = expectation(description: "reservoir store finished")
  63. storageExpectations.expectedFulfillmentCount = reservoirReadings.count + 1
  64. for reading in reservoirReadings.reversed() {
  65. doseStore.addReservoirValue(reading.unitVolume, at: reading.startDate) { _, _, _, _ in storageExpectations.fulfill() }
  66. }
  67. let bolusStart = testingDate("2022-09-05 01:49:47 +0000")
  68. let bolusEnd = testingDate("2022-09-05 01:51:19 +0000")
  69. let bolus = DoseEntry(type: .bolus, startDate: bolusStart, endDate: bolusEnd, value: 2.3, unit: .units, isMutable: true)
  70. let pumpEvent = NewPumpEvent(date: bolus.startDate, dose: bolus, raw: Data(hexadecimalString: "0000")!, title: "Bolus 2.3U")
  71. doseStore.addPumpEvents([pumpEvent], lastReconciliation: testingDate("2022-09-05 01:50:18 +0000")) { error in
  72. storageExpectations.fulfill()
  73. }
  74. waitForExpectations(timeout: 2)
  75. let queryFinishedExpectation = expectation(description: "query finished")
  76. doseStore.insulinOnBoard(at: now) { (result) in
  77. switch result {
  78. case .failure(let error):
  79. XCTFail("Unexpected error: \(error)")
  80. case .success(let value):
  81. XCTAssertEqual(2.25, value.value, accuracy: 0.01)
  82. }
  83. queryFinishedExpectation.fulfill()
  84. }
  85. waitForExpectations(timeout: 3)
  86. }
  87. func testMutableDosesIncludedInIOB() {
  88. let now = testingDate("2023-01-08 17:11:14 +0000")
  89. let doseStore = defaultStore(testingDate: now)
  90. let reservoirReadings = loadReservoirFixture("reservoir_for_iob_missing")
  91. let storageExpectations = expectation(description: "reservoir store finished")
  92. storageExpectations.expectedFulfillmentCount = reservoirReadings.count + 1
  93. for reading in reservoirReadings.reversed() {
  94. doseStore.addReservoirValue(reading.unitVolume, at: reading.startDate) { _, _, _, _ in storageExpectations.fulfill() }
  95. }
  96. let lastReconciliation = testingDate("2023-01-08 17:08:27 +0000")
  97. // NewPumpEvent(date: 2023-01-08 17:04:58 +0000, dose: Optional(LoopKit.DoseEntry(type: LoopKit.DoseType.bolus, startDate: 2023-01-08 17:04:58 +0000, endDate: 2023-01-08 17:08:24 +0000, value: 5.15, unit: LoopKit.DoseUnit.units, deliveredUnits: Optional(5.15), description: nil, insulinType: Optional(LoopKit.InsulinType.novolog), automatic: Optional(false), manuallyEntered: false, syncIdentifier: Optional("464327afd390446786cced682f22448f"), isMutable: true, wasProgrammedByPumpUI: false, scheduledBasalRate: nil)), raw: 16 bytes, type: Optional(LoopKit.PumpEventType.bolus), title: "Bolus", alarmType: nil),
  98. // NewPumpEvent(date: 2023-01-08 17:02:35 +0000, dose: Optional(LoopKit.DoseEntry(type: LoopKit.DoseType.tempBasal, startDate: 2023-01-08 17:02:35 +0000, endDate: 2023-01-08 17:32:35 +0000, value: 0.575, unit: LoopKit.DoseUnit.unitsPerHour, deliveredUnits: nil, description: nil, insulinType: Optional(LoopKit.InsulinType.novolog), automatic: Optional(true), manuallyEntered: false, syncIdentifier: Optional("61487bd5d34f4ff49a7f0766066e7773"), isMutable: false, wasProgrammedByPumpUI: false, scheduledBasalRate: nil)), raw: 16 bytes, type: Optional(LoopKit.PumpEventType.tempBasal), title: "Temp Basal", alarmType: nil)]
  99. let bolusStart = testingDate("2023-01-08 17:04:58 +0000")
  100. let bolusEnd = testingDate("2023-01-08 17:08:24 +0000")
  101. let bolus = DoseEntry(type: .bolus, startDate: bolusStart, endDate: bolusEnd, value: 5.15, unit: .units, isMutable: true)
  102. let tempBasalStart = testingDate("2023-01-08 17:02:35 +0000")
  103. let tempBasalEnd = testingDate("2023-01-08 17:32:35 +0000")
  104. let tempBasal = DoseEntry(type: .tempBasal, startDate: tempBasalStart, endDate: tempBasalEnd, value:0.575, unit: .unitsPerHour, isMutable: false)
  105. let pumpEvents: [NewPumpEvent] = [
  106. NewPumpEvent(date: bolus.startDate, dose: bolus, raw: Data(hexadecimalString: "0000")!, title: "Bolus 5.15U"),
  107. NewPumpEvent(date: tempBasal.startDate, dose: tempBasal, raw: Data(hexadecimalString: "0001")!, title: "TempBasal 0.575 U/hr")
  108. ]
  109. doseStore.addPumpEvents(pumpEvents, lastReconciliation: lastReconciliation) { error in
  110. storageExpectations.fulfill()
  111. }
  112. waitForExpectations(timeout: 2)
  113. let queryFinishedExpectation = expectation(description: "query finished")
  114. doseStore.insulinOnBoard(at: now) { (result) in
  115. switch result {
  116. case .failure(let error):
  117. XCTFail("Unexpected error: \(error)")
  118. case .success(let value):
  119. XCTAssertEqual(5.07, value.value, accuracy: 0.01)
  120. }
  121. queryFinishedExpectation.fulfill()
  122. }
  123. waitForExpectations(timeout: 3)
  124. }
  125. func testPumpEventTypeDoseMigration() {
  126. cacheStore.managedObjectContext.performAndWait {
  127. let event = PumpEvent(entity: PumpEvent.entity(), insertInto: cacheStore.managedObjectContext)
  128. event.date = Date()
  129. event.duration = .minutes(30)
  130. event.unit = .unitsPerHour
  131. event.type = .tempBasal
  132. event.value = 0.5
  133. event.doseType = nil
  134. XCTAssertNotNil(event.dose)
  135. XCTAssertEqual(.tempBasal, event.dose!.type)
  136. }
  137. }
  138. func testDeduplication() {
  139. cacheStore.managedObjectContext.performAndWait {
  140. let bolus1 = PumpEvent(context: cacheStore.managedObjectContext)
  141. bolus1.date = DateFormatter.descriptionFormatter.date(from: "2018-04-30 02:12:42 +0000")
  142. bolus1.raw = Data(hexadecimalString: "0100a600a6001b006a0c335d12")!
  143. bolus1.type = PumpEventType.bolus
  144. bolus1.dose = DoseEntry(type: .bolus, startDate: bolus1.date!, value: 4.15, unit: .units, syncIdentifier: bolus1.raw?.hexadecimalString)
  145. let bolus2 = PumpEvent(context: cacheStore.managedObjectContext)
  146. bolus2.date = DateFormatter.descriptionFormatter.date(from: "2018-04-30 00:00:00 +0000")
  147. bolus2.raw = Data(hexadecimalString: "0100a600a6001b006a0c335d12")!
  148. bolus2.type = PumpEventType.bolus
  149. bolus2.dose = DoseEntry(type: .bolus, startDate: bolus2.date!, value: 0.15, unit: .units, syncIdentifier: bolus1.raw?.hexadecimalString)
  150. let request: NSFetchRequest<PumpEvent> = PumpEvent.fetchRequest()
  151. let eventsBeforeSave = try! cacheStore.managedObjectContext.fetch(request)
  152. XCTAssertEqual(2, eventsBeforeSave.count)
  153. try! cacheStore.managedObjectContext.save()
  154. let eventsAfterSave = try! cacheStore.managedObjectContext.fetch(request)
  155. XCTAssertEqual(1, eventsAfterSave.count)
  156. }
  157. }
  158. /// See https://github.com/LoopKit/Loop/issues/853
  159. func testOutOfOrderDosesSyncedToHealth() {
  160. let formatter = DateFormatter.descriptionFormatter
  161. let f = { (input) in
  162. return formatter.date(from: input)!
  163. }
  164. // 1. Create a DoseStore
  165. let healthStore = HKHealthStoreMock()
  166. let doseStoreInitialization = expectation(description: "Expect DoseStore to finish initialization")
  167. let doseStore = DoseStore(
  168. healthStore: healthStore,
  169. cacheStore: cacheStore,
  170. observationEnabled: false,
  171. insulinModelProvider: StaticInsulinModelProvider(WalshInsulinModel(actionDuration: .hours(4))),
  172. longestEffectDuration: .hours(4),
  173. basalProfile: BasalRateSchedule(rawValue: ["timeZone": -28800, "items": [ // Timezone = -0800
  174. ["value": 0.75, "startTime": 0.0], // 0000 - Midnight
  175. ["value": 0.8, "startTime": 10800.0], // 0300 - 3am
  176. ["value": 0.85, "startTime": 32400.0], // 0900 - 9am
  177. ["value": 1.0, "startTime": 68400.0]]]), // 1900 - 7pm
  178. insulinSensitivitySchedule: InsulinSensitivitySchedule(rawValue: ["unit": "mg/dL", "timeZone": -28800, "items": [["value": 40.0, "startTime": 0.0], ["value": 35.0, "startTime": 21600.0], ["value": 40.0, "startTime": 57600.0]]]),
  179. syncVersion: 1,
  180. provenanceIdentifier: Bundle.main.bundleIdentifier!,
  181. onReady: { _ in doseStoreInitialization.fulfill() },
  182. // Set the current date
  183. test_currentDate: f("2018-12-12 18:07:14 +0000")
  184. )
  185. waitForExpectations(timeout: 3)
  186. // 2. Add a temp basal which has already ended. It should be saved to Health
  187. let pumpEvents1 = [
  188. NewPumpEvent(date: f("2018-12-12 17:35:58 +0000"), dose: nil, raw: UUID().data, title: "TempBasalPumpEvent(length: 8, rawData: 8 bytes, rateType: MinimedKit.TempBasalPumpEvent.RateType.Absolute, rate: 2.125, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 9 minute: 35 second: 58 isLeapMonth: false )", type: nil),
  189. NewPumpEvent(date: f("2018-12-12 17:35:58 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-12-12 17:35:58 +0000"), endDate: f("2018-12-12 18:05:58 +0000"), value: 2.125, unit: .unitsPerHour), raw: Data(hexadecimalString: "1601fa23094c12")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 9 minute: 35 second: 58 isLeapMonth: false )", type: .tempBasal)
  190. ]
  191. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate = f("2018-12-12 17:35:58 +0000")
  192. let addPumpEvents1 = expectation(description: "add pumpEvents1")
  193. addPumpEvents1.expectedFulfillmentCount = 2
  194. healthStore.setSaveHandler({ (objects, success, error) in
  195. XCTAssertEqual(1, objects.count)
  196. let sample = objects.first as! HKQuantitySample
  197. XCTAssertEqual(HKInsulinDeliveryReason.basal, sample.insulinDeliveryReason)
  198. XCTAssertNil(error)
  199. addPumpEvents1.fulfill()
  200. })
  201. let lastBasalEndDateSetExpectation = expectation(description: "last basal end date set")
  202. lastBasalEndDateSetExpectation.assertForOverFulfill = false
  203. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDateDidSet = {
  204. lastBasalEndDateSetExpectation.fulfill()
  205. }
  206. doseStore.addPumpEvents(pumpEvents1, lastReconciliation: Date()) { (error) in
  207. XCTAssertNil(error)
  208. addPumpEvents1.fulfill()
  209. }
  210. waitForExpectations(timeout: 3)
  211. XCTAssertEqual(f("2018-12-12 18:05:58 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  212. // 3. Add a bolus a little later, which started before the last temp basal ends, but wasn't written to pump history until it completed (x22 pump behavior)
  213. // Even though it is before lastBasalEndDate, it should be saved to HealthKit.
  214. doseStore.insulinDeliveryStore.test_currentDate = f("2018-12-12 18:16:23 +0000")
  215. let pumpEvents2 = [
  216. NewPumpEvent(date: f("2018-12-12 18:05:14 +0000"), dose: DoseEntry(type: .bolus, startDate: f("2018-12-12 18:05:14 +0000"), endDate: f("2018-12-12 18:05:14 +0000"), value: 5.0, unit: .units), raw: Data(hexadecimalString: "01323200ce052a0c12")!, title: "BolusNormalPumpEvent(length: 9, rawData: 9 bytes, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 10 minute: 5 second: 14 isLeapMonth: false , unabsorbedInsulinRecord: nil, amount: 5.0, programmed: 5.0, unabsorbedInsulinTotal: 0.0, type: MinimedKit.BolusNormalPumpEvent.BolusType.normal, duration: 0.0, deliveryUnitsPerMinute: 1.5)", type: .bolus)
  217. ]
  218. let addPumpEvents2 = expectation(description: "add pumpEvents2")
  219. addPumpEvents2.expectedFulfillmentCount = 3
  220. healthStore.setSaveHandler({ (objects, success, error) in
  221. XCTAssertEqual(1, objects.count)
  222. let sample = objects.first as! HKQuantitySample
  223. XCTAssertEqual(HKInsulinDeliveryReason.bolus, sample.insulinDeliveryReason)
  224. XCTAssertEqual(5.0, sample.quantity.doubleValue(for: .internationalUnit()))
  225. XCTAssertEqual(f("2018-12-12 18:05:14 +0000"), sample.startDate)
  226. XCTAssertNil(error)
  227. addPumpEvents2.fulfill()
  228. })
  229. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDateDidSet = {
  230. addPumpEvents2.fulfill()
  231. }
  232. doseStore.addPumpEvents(pumpEvents2, lastReconciliation: Date()) { (error) in
  233. XCTAssertNil(error)
  234. addPumpEvents2.fulfill()
  235. }
  236. waitForExpectations(timeout: 3)
  237. XCTAssertEqual(f("2018-12-12 18:05:58 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  238. // Add the next set of pump events, which haven't completed and shouldn't be saved to HealthKit
  239. doseStore.insulinDeliveryStore.test_currentDate = f("2018-12-12 18:21:22 +0000")
  240. let pumpEvents3 = [
  241. NewPumpEvent(date: f("2018-12-12 18:16:31 +0000"), dose: nil, raw: UUID().data, title: "TempBasalPumpEvent(length: 8, rawData: 8 bytes, rateType: MinimedKit.TempBasalPumpEvent.RateType.Absolute, rate: 0.0, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 10 minute: 16 second: 31 isLeapMonth: false )", type: nil),
  242. NewPumpEvent(date: f("2018-12-12 18:16:31 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-12-12 18:16:31 +0000"), endDate: f("2018-12-12 18:46:31 +0000"), value: 0.0, unit: .unitsPerHour), raw: Data(hexadecimalString: "1601df100a4c12")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 10 minute: 16 second: 31 isLeapMonth: false )", type: .tempBasal),
  243. ]
  244. let addPumpEvents3 = expectation(description: "add pumpEvents3")
  245. addPumpEvents3.expectedFulfillmentCount = 1
  246. healthStore.setSaveHandler({ (objects, success, error) in
  247. XCTFail()
  248. })
  249. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDateDidSet = nil
  250. doseStore.addPumpEvents(pumpEvents3, lastReconciliation: Date()) { (error) in
  251. XCTAssertNil(error)
  252. addPumpEvents3.fulfill()
  253. }
  254. waitForExpectations(timeout: 3)
  255. XCTAssertEqual(f("2018-12-12 18:05:58 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  256. }
  257. /// https://github.com/LoopKit/Loop/issues/852
  258. func testSplitBasalsSyncedToHealth() {
  259. let formatter = DateFormatter.descriptionFormatter
  260. let f = { (input) in
  261. return formatter.date(from: input)!
  262. }
  263. // Create a DoseStore
  264. let healthStore = HKHealthStoreMock()
  265. let doseStoreInitialization = expectation(description: "Expect DoseStore to finish initialization")
  266. let doseStore = DoseStore(
  267. healthStore: healthStore,
  268. cacheStore: cacheStore,
  269. observationEnabled: false,
  270. insulinModelProvider: StaticInsulinModelProvider(WalshInsulinModel(actionDuration: .hours(4))),
  271. longestEffectDuration: .hours(4),
  272. basalProfile: BasalRateSchedule(rawValue: ["timeZone": -28800, "items": [["value": 0.75, "startTime": 0.0], ["value": 0.8, "startTime": 10800.0], ["value": 0.85, "startTime": 32400.0], ["value": 1.0, "startTime": 68400.0]]]),
  273. insulinSensitivitySchedule: InsulinSensitivitySchedule(rawValue: ["unit": "mg/dL", "timeZone": -28800, "items": [["value": 40.0, "startTime": 0.0], ["value": 35.0, "startTime": 21600.0], ["value": 40.0, "startTime": 57600.0]]]),
  274. syncVersion: 1,
  275. provenanceIdentifier: Bundle.main.bundleIdentifier!,
  276. onReady: { _ in doseStoreInitialization.fulfill() },
  277. // Set the current date (5 minutes later)
  278. test_currentDate: f("2018-11-29 11:04:27 +0000")
  279. )
  280. waitForExpectations(timeout: 3)
  281. doseStore.pumpRecordsBasalProfileStartEvents = false
  282. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate = f("2018-11-29 10:54:28 +0000")
  283. // Add a temp basal. It hasn't finished yet, and should not be saved to Health
  284. let pumpEvents1 = [
  285. NewPumpEvent(date: f("2018-11-29 10:59:28 +0000"), dose: nil, raw: UUID().data, title: "TempBasalPumpEvent(length: 8, rawData: 8 bytes, rateType: MinimedKit.TempBasalPumpEvent.RateType.Absolute, rate: 0.3, timestamp: calendar: gregorian (fixed) year: 2018 month: 11 day: 29 hour: 2 minute: 59 second: 28 isLeapMonth: false )", type: nil),
  286. NewPumpEvent(date: f("2018-11-29 10:59:28 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-11-29 10:59:28 +0000"), endDate: f("2018-11-29 11:29:28 +0000"), value: 0.3, unit: .unitsPerHour), raw: Data(hexadecimalString: "5bffc7cace53e48e87f7cfcb")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 11 day: 29 hour: 2 minute: 59 second: 28 isLeapMonth: false )", type: .tempBasal)
  287. ]
  288. let addPumpEvents1 = expectation(description: "add pumpEvents1")
  289. addPumpEvents1.expectedFulfillmentCount = 1
  290. healthStore.setSaveHandler({ (objects, success, error) in
  291. XCTFail()
  292. })
  293. doseStore.addPumpEvents(pumpEvents1, lastReconciliation: Date()) { (error) in
  294. XCTAssertNil(error)
  295. addPumpEvents1.fulfill()
  296. }
  297. waitForExpectations(timeout: 3)
  298. XCTAssertEqual(f("2018-11-29 10:54:28 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  299. XCTAssertEqual(f("2018-11-29 10:59:28 +0000"), doseStore.pumpEventQueryAfterDate)
  300. // Add the next query of the same pump events (no new data) 5 minutes later. Expect the same result
  301. doseStore.insulinDeliveryStore.test_currentDate = f("2018-11-29 11:09:27 +0000")
  302. let addPumpEvents2 = expectation(description: "add pumpEvents2")
  303. addPumpEvents2.expectedFulfillmentCount = 1
  304. healthStore.setSaveHandler({ (objects, success, error) in
  305. XCTFail()
  306. })
  307. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDateDidSet = {
  308. XCTFail()
  309. }
  310. doseStore.addPumpEvents(pumpEvents1, lastReconciliation: Date()) { (error) in
  311. XCTAssertNil(error)
  312. addPumpEvents2.fulfill()
  313. }
  314. waitForExpectations(timeout: 3)
  315. XCTAssertEqual(f("2018-11-29 10:54:28 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  316. XCTAssertEqual(f("2018-11-29 10:59:28 +0000"), doseStore.pumpEventQueryAfterDate)
  317. // Add the next set of pump events, including the last temp basal change.
  318. // The previous, completed basal entries should be saved to Health
  319. doseStore.insulinDeliveryStore.test_currentDate = f("2018-11-29 11:14:28 +0000")
  320. let pumpEvents3 = [
  321. NewPumpEvent(date: f("2018-11-29 11:09:27 +0000"), dose: nil, raw: UUID().data, title: "TempBasalPumpEvent(length: 8, rawData: 8 bytes, rateType: MinimedKit.TempBasalPumpEvent.RateType.Absolute, rate: 0.325, timestamp: calendar: gregorian (fixed) year: 2018 month: 11 day: 29 hour: 3 minute: 9 second: 27 isLeapMonth: false )", type: nil),
  322. NewPumpEvent(date: f("2018-11-29 11:09:27 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-11-29 11:09:27 +0000"), endDate: f("2018-11-29 11:39:27 +0000"), value: 0.325, unit: .unitsPerHour), raw: Data(hexadecimalString: "5bffca22ce53e48e87f7d624")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 11 day: 29 hour: 3 minute: 9 second: 27 isLeapMonth: false )", type: .tempBasal)
  323. ]
  324. let addPumpEvents3 = expectation(description: "add pumpEvents3")
  325. addPumpEvents3.expectedFulfillmentCount = 3
  326. healthStore.setSaveHandler({ (objects, success, error) in
  327. XCTAssertEqual(3, objects.count)
  328. let basal = objects[0] as! HKQuantitySample
  329. XCTAssertEqual(HKInsulinDeliveryReason.basal, basal.insulinDeliveryReason)
  330. XCTAssertEqual(f("2018-11-29 10:54:28 +0000"), basal.startDate)
  331. XCTAssertEqual(f("2018-11-29 10:59:28 +0000"), basal.endDate)
  332. XCTAssertEqual("BasalRateSchedule 2018-11-29T10:54:28Z 2018-11-29T10:59:28Z", basal.metadata![HKMetadataKeySyncIdentifier] as! String)
  333. let temp1 = objects[1] as! HKQuantitySample
  334. XCTAssertEqual(HKInsulinDeliveryReason.basal, temp1.insulinDeliveryReason)
  335. XCTAssertEqual(f("2018-11-29 10:59:28 +0000"), temp1.startDate)
  336. XCTAssertEqual(f("2018-11-29 11:00:00 +0000"), temp1.endDate)
  337. XCTAssertEqual("5bffc7cace53e48e87f7cfcb 1/2", temp1.metadata![HKMetadataKeySyncIdentifier] as! String)
  338. XCTAssertEqual(0.003, temp1.quantity.doubleValue(for: .internationalUnit()), accuracy: 0.01)
  339. let temp2 = objects[2] as! HKQuantitySample
  340. XCTAssertEqual(HKInsulinDeliveryReason.basal, temp2.insulinDeliveryReason)
  341. XCTAssertEqual(f("2018-11-29 11:00:00 +0000"), temp2.startDate)
  342. XCTAssertEqual(f("2018-11-29 11:09:27 +0000"), temp2.endDate)
  343. XCTAssertEqual("5bffc7cace53e48e87f7cfcb 2/2", temp2.metadata![HKMetadataKeySyncIdentifier] as! String)
  344. XCTAssertEqual(0.047, temp2.quantity.doubleValue(for: .internationalUnit()), accuracy: 0.01)
  345. XCTAssertNil(error)
  346. addPumpEvents3.fulfill()
  347. })
  348. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDateDidSet = {
  349. addPumpEvents3.fulfill()
  350. }
  351. doseStore.addPumpEvents(pumpEvents3, lastReconciliation: Date()) { (error) in
  352. XCTAssertNil(error)
  353. addPumpEvents3.fulfill()
  354. }
  355. waitForExpectations(timeout: 3)
  356. XCTAssertEqual(f("2018-11-29 11:09:27 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  357. XCTAssertEqual(f("2018-11-29 11:09:27 +0000"), doseStore.pumpEventQueryAfterDate)
  358. // Add the next set of pump events, including the last immutable temp basal cancel
  359. doseStore.insulinDeliveryStore.test_currentDate = f("2018-11-29 11:19:28 +0000")
  360. let pumpEvents4 = [
  361. NewPumpEvent(date: f("2018-11-29 11:14:28 +0000"), dose: nil, raw: UUID().data, title: "TempBasalPumpEvent(length: 8, rawData: 8 bytes, rateType: MinimedKit.TempBasalPumpEvent.RateType.Absolute, rate: 0, timestamp: calendar: gregorian (fixed) year: 2018 month: 11 day: 29 hour: 3 minute: 14 second: 28 isLeapMonth: false )", type: nil),
  362. NewPumpEvent(date: f("2018-11-29 11:14:28 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-11-29 11:14:28 +0000"), endDate: f("2018-11-29 11:14:28 +0000"), value: 0.0, unit: .unitsPerHour), raw: Data(hexadecimalString: "5bffced1ce53e48e87f7e33b")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 11 day: 29 hour: 3 minute: 14 second: 28 isLeapMonth: false )", type: .tempBasal)
  363. ]
  364. let addPumpEvents4 = expectation(description: "add pumpEvents4")
  365. addPumpEvents4.expectedFulfillmentCount = 3
  366. healthStore.setSaveHandler({ (objects, success, error) in
  367. XCTAssertEqual(1, objects.count)
  368. let temp = objects[0] as! HKQuantitySample
  369. XCTAssertEqual(HKInsulinDeliveryReason.basal, temp.insulinDeliveryReason)
  370. XCTAssertEqual(f("2018-11-29 11:09:27 +0000"), temp.startDate)
  371. XCTAssertEqual(f("2018-11-29 11:14:28 +0000"), temp.endDate)
  372. XCTAssertEqual("5bffca22ce53e48e87f7d624", temp.metadata![HKMetadataKeySyncIdentifier] as! String)
  373. XCTAssertEqual(0.05, temp.quantity.doubleValue(for: .internationalUnit()), accuracy: 0.01)
  374. XCTAssertNil(error)
  375. addPumpEvents4.fulfill()
  376. })
  377. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDateDidSet = {
  378. addPumpEvents4.fulfill()
  379. }
  380. doseStore.addPumpEvents(pumpEvents4, lastReconciliation: Date()) { (error) in
  381. XCTAssertNil(error)
  382. addPumpEvents4.fulfill()
  383. }
  384. waitForExpectations(timeout: 3)
  385. XCTAssertEqual(f("2018-11-29 11:14:28 +0000"), doseStore.pumpEventQueryAfterDate)
  386. XCTAssertEqual(f("2018-11-29 11:14:28 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  387. // Add the final mutable pump event, it should NOT be synced to HealthKit
  388. doseStore.insulinDeliveryStore.test_currentDate = f("2018-11-29 11:24:28 +0000")
  389. let pumpEvents5 = [
  390. NewPumpEvent(date: f("2018-11-29 11:14:28 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-11-29 11:14:28 +0000"), endDate: f("2018-11-29 11:44:28 +0000"), value: 1.0, unit: .unitsPerHour, isMutable: true), raw: Data(hexadecimalString: "e48e87f7e33b5bffced1ce53")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 11 day: 29 hour: 3 minute: 14 second: 28 isLeapMonth: false )", type: .tempBasal)
  391. ]
  392. let addPumpEvents5 = expectation(description: "add pumpEvents5")
  393. addPumpEvents5.expectedFulfillmentCount = 2
  394. healthStore.setSaveHandler({ (objects, success, error) in
  395. XCTFail()
  396. })
  397. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDateDidSet = {
  398. addPumpEvents5.fulfill()
  399. }
  400. doseStore.addPumpEvents(pumpEvents5, lastReconciliation: Date()) { (error) in
  401. XCTAssertNil(error)
  402. addPumpEvents5.fulfill()
  403. }
  404. waitForExpectations(timeout: 3)
  405. XCTAssertEqual(f("2018-11-29 11:14:28 +0000"), doseStore.pumpEventQueryAfterDate)
  406. XCTAssertEqual(f("2018-11-29 11:14:28 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  407. }
  408. func testAddPumpEventsProgrammedByPumpUI() {
  409. let formatter = DateFormatter.descriptionFormatter
  410. let f = { (input) in
  411. return formatter.date(from: input)!
  412. }
  413. let doseStoreInitialization = expectation(description: "Expect DoseStore to finish initialization")
  414. // 1. Create a DoseStore
  415. let doseStore = DoseStore(
  416. healthStore: HKHealthStoreMock(),
  417. cacheStore: cacheStore,
  418. observationEnabled: false,
  419. insulinModelProvider: StaticInsulinModelProvider(WalshInsulinModel(actionDuration: .hours(4))),
  420. longestEffectDuration: .hours(4),
  421. basalProfile: BasalRateSchedule(rawValue: ["timeZone": -28800, "items": [["value": 0.75, "startTime": 0.0], ["value": 0.8, "startTime": 10800.0], ["value": 0.85, "startTime": 32400.0], ["value": 1.0, "startTime": 37800.0]]]),
  422. insulinSensitivitySchedule: InsulinSensitivitySchedule(rawValue: ["unit": "mg/dL", "timeZone": -28800, "items": [["value": 40.0, "startTime": 0.0], ["value": 35.0, "startTime": 21600.0], ["value": 40.0, "startTime": 57600.0]]]),
  423. syncVersion: 1,
  424. provenanceIdentifier: Bundle.main.bundleIdentifier!,
  425. onReady: { _ in doseStoreInitialization.fulfill() },
  426. // Set the current date
  427. test_currentDate: f("2018-12-12 18:07:14 +0000")
  428. )
  429. waitForExpectations(timeout: 3)
  430. // 2. Add a temp basal which has already ended. It should persist in InsulinDeliveryStore.
  431. let pumpEvents1 = [
  432. NewPumpEvent(date: f("2018-12-12 17:35:00 +0000"), dose: nil, raw: UUID().data, title: "TempBasalPumpEvent(length: 8, rawData: 8 bytes, rateType: MinimedKit.TempBasalPumpEvent.RateType.Absolute, rate: 2.125, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 9 minute: 35 second: 0 isLeapMonth: false )", type: nil),
  433. NewPumpEvent(date: f("2018-12-12 17:35:00 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-12-12 17:35:00 +0000"), endDate: f("2018-12-12 18:05:00 +0000"), value: 2.125, unit: .unitsPerHour, wasProgrammedByPumpUI: true), raw: Data(hexadecimalString: "1601fa23094c12")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 9 minute: 35 second: 0 isLeapMonth: false )", type: .tempBasal)
  434. ]
  435. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate = f("2018-12-12 17:35:00 +0000")
  436. doseStore.insulinDeliveryStore.test_currentDate = f("2018-12-12 18:07:14 +0000")
  437. let addPumpEvents1 = expectation(description: "addPumpEvents1")
  438. addPumpEvents1.expectedFulfillmentCount = 2
  439. doseStore.addPumpEvents(pumpEvents1, lastReconciliation: Date()) { (error) in
  440. XCTAssertNil(error)
  441. doseStore.insulinDeliveryStore.getDoseEntries { result in
  442. switch result {
  443. case .failure:
  444. XCTFail()
  445. case .success(let doseEntries):
  446. XCTAssertEqual(doseEntries.count, 1)
  447. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  448. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  449. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  450. XCTAssertEqual(doseEntries[0].value, 2.125)
  451. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  452. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  453. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  454. XCTAssertFalse(doseEntries[0].isMutable)
  455. }
  456. addPumpEvents1.fulfill();
  457. }
  458. doseStore.insulinDeliveryStore.getDoseEntries(includeMutable: true) { result in
  459. switch result {
  460. case .failure:
  461. XCTFail()
  462. case .success(let doseEntries):
  463. XCTAssertEqual(doseEntries.count, 1)
  464. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  465. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  466. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  467. XCTAssertEqual(doseEntries[0].value, 2.125)
  468. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  469. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  470. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  471. XCTAssertFalse(doseEntries[0].isMutable)
  472. XCTAssertTrue(doseEntries[0].wasProgrammedByPumpUI)
  473. }
  474. addPumpEvents1.fulfill();
  475. }
  476. }
  477. waitForExpectations(timeout: 3)
  478. XCTAssertEqual(f("2018-12-12 18:05:00 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  479. }
  480. func testBasalInsertionBetweenTempBasals() {
  481. let start = testingDate("2018-12-12 17:00:00 +0000")
  482. let now = start.addingTimeInterval(.minutes(20))
  483. let doseStore = defaultStore(testingDate: now)
  484. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate = start
  485. // 2. Add a temp basal which has already ended. It should persist in InsulinDeliveryStore.
  486. let pumpEvents1 = [
  487. NewPumpEvent(
  488. date: start,
  489. dose: DoseEntry(
  490. type: .tempBasal,
  491. startDate: start,
  492. endDate: start.addingTimeInterval(.minutes(5)),
  493. value: 1.5,
  494. unit: .unitsPerHour,
  495. automatic: true),
  496. raw: Data(hexadecimalString: "01")!,
  497. title: "First Temp",
  498. type: .tempBasal),
  499. NewPumpEvent(
  500. date: start.addingTimeInterval(.minutes(10)),
  501. dose: DoseEntry(
  502. type: .tempBasal,
  503. startDate: start.addingTimeInterval(.minutes(10)),
  504. endDate: start.addingTimeInterval(.minutes(15)),
  505. value: 1.5,
  506. unit: .unitsPerHour,
  507. automatic: true),
  508. raw: Data(hexadecimalString: "02")!,
  509. title: "Second Temp",
  510. type: .tempBasal)
  511. ]
  512. let addPumpEvents = expectation(description: "addPumpEvents")
  513. doseStore.addPumpEvents(pumpEvents1, lastReconciliation: now) { (error) in
  514. XCTAssertNil(error)
  515. doseStore.insulinDeliveryStore.getDoseEntries(start: start, end: start.addingTimeInterval(.minutes(20))) { result in
  516. switch result {
  517. case .failure:
  518. XCTFail()
  519. case .success(let doseEntries):
  520. XCTAssertEqual(doseEntries.count, 3)
  521. XCTAssertTrue(doseEntries[0].automatic!)
  522. XCTAssertTrue(doseEntries[1].automatic!)
  523. XCTAssertTrue(doseEntries[2].automatic!)
  524. }
  525. addPumpEvents.fulfill();
  526. }
  527. }
  528. waitForExpectations(timeout: 3)
  529. }
  530. func testAddPumpEventsPurgesMutableDosesFromInsulinDeliveryStore() {
  531. let formatter = DateFormatter.descriptionFormatter
  532. let f = { (input) in
  533. return formatter.date(from: input)!
  534. }
  535. let doseStoreInitialization = expectation(description: "Expect DoseStore to finish initialization")
  536. // 1. Create a DoseStore
  537. let doseStore = DoseStore(
  538. healthStore: HKHealthStoreMock(),
  539. cacheStore: cacheStore,
  540. observationEnabled: false,
  541. insulinModelProvider: StaticInsulinModelProvider(WalshInsulinModel(actionDuration: .hours(4))),
  542. longestEffectDuration: .hours(4),
  543. basalProfile: BasalRateSchedule(rawValue: ["timeZone": -28800, "items": [["value": 0.75, "startTime": 0.0], ["value": 0.8, "startTime": 10800.0], ["value": 0.85, "startTime": 32400.0], ["value": 1.0, "startTime": 37800.0]]]),
  544. insulinSensitivitySchedule: InsulinSensitivitySchedule(rawValue: ["unit": "mg/dL", "timeZone": -28800, "items": [["value": 40.0, "startTime": 0.0], ["value": 35.0, "startTime": 21600.0], ["value": 40.0, "startTime": 57600.0]]]),
  545. syncVersion: 1,
  546. provenanceIdentifier: Bundle.main.bundleIdentifier!,
  547. onReady: { _ in doseStoreInitialization.fulfill() },
  548. // Set the current date
  549. test_currentDate: f("2018-12-12 18:07:14 +0000")
  550. )
  551. waitForExpectations(timeout: 3)
  552. // 2. Add a temp basal which has already ended. It should persist in InsulinDeliveryStore.
  553. let pumpEvents1 = [
  554. NewPumpEvent(date: f("2018-12-12 17:35:00 +0000"), dose: nil, raw: UUID().data, title: "TempBasalPumpEvent(length: 8, rawData: 8 bytes, rateType: MinimedKit.TempBasalPumpEvent.RateType.Absolute, rate: 2.125, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 9 minute: 35 second: 0 isLeapMonth: false )", type: nil),
  555. NewPumpEvent(date: f("2018-12-12 17:35:00 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-12-12 17:35:00 +0000"), endDate: f("2018-12-12 18:05:00 +0000"), value: 2.125, unit: .unitsPerHour), raw: Data(hexadecimalString: "1601fa23094c12")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 9 minute: 35 second: 0 isLeapMonth: false )", type: .tempBasal)
  556. ]
  557. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate = f("2018-12-12 17:35:00 +0000")
  558. doseStore.insulinDeliveryStore.test_currentDate = f("2018-12-12 18:07:14 +0000")
  559. let addPumpEvents1 = expectation(description: "addPumpEvents1")
  560. addPumpEvents1.expectedFulfillmentCount = 2
  561. doseStore.addPumpEvents(pumpEvents1, lastReconciliation: Date()) { (error) in
  562. XCTAssertNil(error)
  563. doseStore.insulinDeliveryStore.getDoseEntries { result in
  564. switch result {
  565. case .failure:
  566. XCTFail()
  567. case .success(let doseEntries):
  568. XCTAssertEqual(doseEntries.count, 1)
  569. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  570. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  571. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  572. XCTAssertEqual(doseEntries[0].value, 2.125)
  573. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  574. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  575. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  576. XCTAssertFalse(doseEntries[0].isMutable)
  577. }
  578. addPumpEvents1.fulfill();
  579. }
  580. doseStore.insulinDeliveryStore.getDoseEntries(includeMutable: true) { result in
  581. switch result {
  582. case .failure:
  583. XCTFail()
  584. case .success(let doseEntries):
  585. XCTAssertEqual(doseEntries.count, 1)
  586. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  587. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  588. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  589. XCTAssertEqual(doseEntries[0].value, 2.125)
  590. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  591. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  592. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  593. XCTAssertFalse(doseEntries[0].isMutable)
  594. }
  595. addPumpEvents1.fulfill();
  596. }
  597. }
  598. waitForExpectations(timeout: 3)
  599. XCTAssertEqual(f("2018-12-12 18:05:00 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  600. // 3. Add a mutable temp basal. It should persist in InsulinDeliveryStore.
  601. let pumpEvents2 = [
  602. NewPumpEvent(date: f("2018-12-12 18:05:00 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-12-12 18:05:00 +0000"), endDate: f("2018-12-12 18:25:00 +0000"), value: 1.375, unit: .unitsPerHour, isMutable: true, wasProgrammedByPumpUI: true), raw: Data(hexadecimalString: "3094c121601fa2")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 10 minute: 05 second: 0 isLeapMonth: false )", type: .tempBasal)
  603. ]
  604. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate = f("2018-12-12 18:05:00 +0000")
  605. doseStore.insulinDeliveryStore.test_currentDate = f("2018-12-12 18:07:14 +0000")
  606. let addPumpEvents2 = expectation(description: "addPumpEvents2")
  607. addPumpEvents2.expectedFulfillmentCount = 2
  608. doseStore.addPumpEvents(pumpEvents2, lastReconciliation: Date()) { (error) in
  609. XCTAssertNil(error)
  610. doseStore.insulinDeliveryStore.getDoseEntries { result in
  611. switch result {
  612. case .failure:
  613. XCTFail()
  614. case .success(let doseEntries):
  615. XCTAssertEqual(doseEntries.count, 1)
  616. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  617. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  618. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  619. XCTAssertEqual(doseEntries[0].value, 2.125)
  620. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  621. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  622. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  623. XCTAssertFalse(doseEntries[0].isMutable)
  624. XCTAssertFalse(doseEntries[0].wasProgrammedByPumpUI)
  625. }
  626. addPumpEvents2.fulfill();
  627. }
  628. doseStore.insulinDeliveryStore.getDoseEntries(includeMutable: true) { result in
  629. switch result {
  630. case .failure:
  631. XCTFail()
  632. case .success(let doseEntries):
  633. XCTAssertEqual(doseEntries.count, 2)
  634. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  635. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  636. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  637. XCTAssertEqual(doseEntries[0].value, 2.125)
  638. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  639. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  640. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  641. XCTAssertFalse(doseEntries[0].isMutable)
  642. XCTAssertEqual(doseEntries[1].type, .tempBasal)
  643. XCTAssertEqual(doseEntries[1].startDate, f("2018-12-12 18:05:00 +0000"))
  644. XCTAssertEqual(doseEntries[1].endDate, f("2018-12-12 18:25:00 +0000"))
  645. XCTAssertEqual(doseEntries[1].value, 1.375)
  646. XCTAssertNil(doseEntries[1].deliveredUnits)
  647. XCTAssertEqual(doseEntries[1].syncIdentifier, "3094c121601fa2")
  648. XCTAssertEqual(doseEntries[1].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  649. XCTAssertTrue(doseEntries[1].isMutable)
  650. XCTAssertTrue(doseEntries[1].wasProgrammedByPumpUI)
  651. }
  652. addPumpEvents2.fulfill();
  653. }
  654. }
  655. waitForExpectations(timeout: 3)
  656. XCTAssertEqual(f("2018-12-12 18:05:00 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  657. // 4. Update the mutable temp basal that crossing scheduled basal boundary. It should persist in InsulinDeliveryStore.
  658. let pumpEvents3 = [
  659. NewPumpEvent(date: f("2018-12-12 18:05:00 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-12-12 18:05:00 +0000"), endDate: f("2018-12-12 18:35:00 +0000"), value: 0.875, unit: .unitsPerHour, isMutable: true), raw: Data(hexadecimalString: "3094c121601fa2")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 10 minute: 05 second: 0 isLeapMonth: false )", type: .tempBasal)
  660. ]
  661. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate = f("2018-12-12 18:05:00 +0000")
  662. doseStore.insulinDeliveryStore.test_currentDate = f("2018-12-12 18:07:14 +0000")
  663. let addPumpEvents3 = expectation(description: "addPumpEvents3")
  664. addPumpEvents3.expectedFulfillmentCount = 2
  665. doseStore.addPumpEvents(pumpEvents3, lastReconciliation: Date()) { (error) in
  666. XCTAssertNil(error)
  667. doseStore.insulinDeliveryStore.getDoseEntries { result in
  668. switch result {
  669. case .failure:
  670. XCTFail()
  671. case .success(let doseEntries):
  672. XCTAssertEqual(doseEntries.count, 1)
  673. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  674. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  675. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  676. XCTAssertEqual(doseEntries[0].value, 2.125)
  677. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  678. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  679. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  680. XCTAssertFalse(doseEntries[0].isMutable)
  681. }
  682. addPumpEvents3.fulfill();
  683. }
  684. doseStore.insulinDeliveryStore.getDoseEntries(includeMutable: true) { result in
  685. switch result {
  686. case .failure:
  687. XCTFail()
  688. case .success(let doseEntries):
  689. XCTAssertEqual(doseEntries.count, 3)
  690. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  691. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  692. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  693. XCTAssertEqual(doseEntries[0].value, 2.125)
  694. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  695. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  696. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  697. XCTAssertFalse(doseEntries[0].isMutable)
  698. XCTAssertEqual(doseEntries[1].type, .tempBasal)
  699. XCTAssertEqual(doseEntries[1].startDate, f("2018-12-12 18:05:00 +0000"))
  700. XCTAssertEqual(doseEntries[1].endDate, f("2018-12-12 18:30:00 +0000"))
  701. XCTAssertEqual(doseEntries[1].value, 0.875)
  702. XCTAssertNil(doseEntries[1].deliveredUnits)
  703. XCTAssertEqual(doseEntries[1].syncIdentifier, "3094c121601fa2 1/2")
  704. XCTAssertEqual(doseEntries[1].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  705. XCTAssertTrue(doseEntries[1].isMutable)
  706. XCTAssertEqual(doseEntries[2].type, .tempBasal)
  707. XCTAssertEqual(doseEntries[2].startDate, f("2018-12-12 18:30:00 +0000"))
  708. XCTAssertEqual(doseEntries[2].endDate, f("2018-12-12 18:35:00 +0000"))
  709. XCTAssertEqual(doseEntries[2].value, 0.875)
  710. XCTAssertNil(doseEntries[2].deliveredUnits)
  711. XCTAssertEqual(doseEntries[2].syncIdentifier, "3094c121601fa2 2/2")
  712. XCTAssertEqual(doseEntries[2].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 1.0))
  713. XCTAssertTrue(doseEntries[2].isMutable)
  714. }
  715. addPumpEvents3.fulfill();
  716. }
  717. }
  718. waitForExpectations(timeout: 3)
  719. XCTAssertEqual(f("2018-12-12 18:05:00 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  720. // 5. Add a different mutable temp basal that crossing scheduled basal boundary. It should persist in InsulinDeliveryStore. A scheduled basal should be added.
  721. let pumpEvents4 = [
  722. NewPumpEvent(date: f("2018-12-12 18:15:00 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-12-12 18:15:00 +0000"), endDate: f("2018-12-12 18:45:00 +0000"), value: 0.5, unit: .unitsPerHour, isMutable: true), raw: Data(hexadecimalString: "121601f3094ca2")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 10 minute: 15 second: 0 isLeapMonth: false )", type: .tempBasal)
  723. ]
  724. var basalDoseEntry: DoseEntry? = nil
  725. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate = f("2018-12-12 18:05:00 +0000")
  726. doseStore.insulinDeliveryStore.test_currentDate = f("2018-12-12 18:17:14 +0000")
  727. let addPumpEvents4 = expectation(description: "addPumpEvents4")
  728. addPumpEvents4.expectedFulfillmentCount = 2
  729. doseStore.addPumpEvents(pumpEvents4, lastReconciliation: Date()) { (error) in
  730. XCTAssertNil(error)
  731. doseStore.insulinDeliveryStore.getDoseEntries { result in
  732. switch result {
  733. case .failure:
  734. XCTFail()
  735. case .success(let doseEntries):
  736. XCTAssertEqual(doseEntries.count, 2)
  737. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  738. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  739. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  740. XCTAssertEqual(doseEntries[0].value, 2.125)
  741. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  742. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  743. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  744. XCTAssertFalse(doseEntries[0].isMutable)
  745. XCTAssertEqual(doseEntries[1].type, .basal)
  746. XCTAssertEqual(doseEntries[1].startDate, f("2018-12-12 18:05:00 +0000"))
  747. XCTAssertEqual(doseEntries[1].endDate, f("2018-12-12 18:15:00 +0000"))
  748. XCTAssertEqual(doseEntries[1].value, 0.15)
  749. XCTAssertNil(doseEntries[1].deliveredUnits)
  750. XCTAssertEqual(doseEntries[1].syncIdentifier, "BasalRateSchedule 2018-12-12T18:05:00Z 2018-12-12T18:15:00Z")
  751. XCTAssertEqual(doseEntries[1].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  752. XCTAssertFalse(doseEntries[1].isMutable)
  753. basalDoseEntry = doseEntries[1]
  754. }
  755. addPumpEvents4.fulfill();
  756. }
  757. doseStore.insulinDeliveryStore.getDoseEntries(includeMutable: true) { result in
  758. switch result {
  759. case .failure:
  760. XCTFail()
  761. case .success(let doseEntries):
  762. XCTAssertEqual(doseEntries.count, 4)
  763. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  764. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  765. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  766. XCTAssertEqual(doseEntries[0].value, 2.125)
  767. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  768. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  769. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  770. XCTAssertFalse(doseEntries[0].isMutable)
  771. XCTAssertEqual(doseEntries[1].type, .basal)
  772. XCTAssertEqual(doseEntries[1].startDate, f("2018-12-12 18:05:00 +0000"))
  773. XCTAssertEqual(doseEntries[1].endDate, f("2018-12-12 18:15:00 +0000"))
  774. XCTAssertEqual(doseEntries[1].value, 0.15)
  775. XCTAssertNil(doseEntries[1].deliveredUnits)
  776. XCTAssertEqual(doseEntries[1].syncIdentifier, "BasalRateSchedule 2018-12-12T18:05:00Z 2018-12-12T18:15:00Z")
  777. XCTAssertEqual(doseEntries[1].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  778. XCTAssertFalse(doseEntries[1].isMutable)
  779. XCTAssertEqual(doseEntries[2].type, .tempBasal)
  780. XCTAssertEqual(doseEntries[2].startDate, f("2018-12-12 18:15:00 +0000"))
  781. XCTAssertEqual(doseEntries[2].endDate, f("2018-12-12 18:30:00 +0000"))
  782. XCTAssertEqual(doseEntries[2].value, 0.5)
  783. XCTAssertNil(doseEntries[2].deliveredUnits)
  784. XCTAssertEqual(doseEntries[2].syncIdentifier, "121601f3094ca2 1/2")
  785. XCTAssertEqual(doseEntries[2].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  786. XCTAssertTrue(doseEntries[2].isMutable)
  787. XCTAssertEqual(doseEntries[3].type, .tempBasal)
  788. XCTAssertEqual(doseEntries[3].startDate, f("2018-12-12 18:30:00 +0000"))
  789. XCTAssertEqual(doseEntries[3].endDate, f("2018-12-12 18:45:00 +0000"))
  790. XCTAssertEqual(doseEntries[3].value, 0.5)
  791. XCTAssertNil(doseEntries[3].deliveredUnits)
  792. XCTAssertEqual(doseEntries[3].syncIdentifier, "121601f3094ca2 2/2")
  793. XCTAssertEqual(doseEntries[3].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 1.0))
  794. XCTAssertTrue(doseEntries[3].isMutable)
  795. }
  796. addPumpEvents4.fulfill();
  797. }
  798. }
  799. waitForExpectations(timeout: 3)
  800. XCTAssertEqual(f("2018-12-12 18:15:00 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  801. // 6. Deleted scheduled basal dose entry. Tombstones entry so should not be returned again.
  802. let addPumpEvents5 = expectation(description: "addPumpEvents5")
  803. doseStore.deleteDose(basalDoseEntry!) { result in
  804. XCTAssertNil(result)
  805. addPumpEvents5.fulfill();
  806. }
  807. waitForExpectations(timeout: 3)
  808. XCTAssertEqual(f("2018-12-12 18:05:00 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  809. // 7. Add an immutable temp basal that crossing scheduled basal boundary. It should persist in InsulinDeliveryStore.
  810. let pumpEvents6 = [
  811. NewPumpEvent(date: f("2018-12-12 18:25:00 +0000"), dose: DoseEntry(type: .tempBasal, startDate: f("2018-12-12 18:25:00 +0000"), endDate: f("2018-12-12 18:40:00 +0000"), value: 0.75, unit: .unitsPerHour), raw: Data(hexadecimalString: "1201f3094c16a2")!, title: "TempBasalDurationPumpEvent(length: 7, rawData: 7 bytes, duration: 30, timestamp: calendar: gregorian (fixed) year: 2018 month: 12 day: 12 hour: 10 minute: 25 second: 0 isLeapMonth: false )", type: .tempBasal)
  812. ]
  813. doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate = f("2018-12-12 18:05:00 +0000")
  814. doseStore.insulinDeliveryStore.test_currentDate = f("2018-12-12 18:41:14 +0000")
  815. let addPumpEvents6 = expectation(description: "addPumpEvents6")
  816. addPumpEvents6.expectedFulfillmentCount = 2
  817. doseStore.addPumpEvents(pumpEvents6, lastReconciliation: Date()) { (error) in
  818. XCTAssertNil(error)
  819. doseStore.insulinDeliveryStore.getDoseEntries { result in
  820. switch result {
  821. case .failure:
  822. XCTFail()
  823. case .success(let doseEntries):
  824. XCTAssertEqual(doseEntries.count, 4)
  825. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  826. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  827. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  828. XCTAssertEqual(doseEntries[0].value, 2.125)
  829. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  830. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  831. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  832. XCTAssertFalse(doseEntries[0].isMutable)
  833. XCTAssertEqual(doseEntries[1].type, .basal)
  834. XCTAssertEqual(doseEntries[1].startDate, f("2018-12-12 18:05:00 +0000"))
  835. XCTAssertEqual(doseEntries[1].endDate, f("2018-12-12 18:25:00 +0000"))
  836. XCTAssertEqual(doseEntries[1].value, 0.3)
  837. XCTAssertNil(doseEntries[1].deliveredUnits)
  838. XCTAssertEqual(doseEntries[1].syncIdentifier, "BasalRateSchedule 2018-12-12T18:05:00Z 2018-12-12T18:25:00Z")
  839. XCTAssertEqual(doseEntries[1].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  840. XCTAssertFalse(doseEntries[1].isMutable)
  841. XCTAssertEqual(doseEntries[2].type, .tempBasal)
  842. XCTAssertEqual(doseEntries[2].startDate, f("2018-12-12 18:25:00 +0000"))
  843. XCTAssertEqual(doseEntries[2].endDate, f("2018-12-12 18:30:00 +0000"))
  844. XCTAssertEqual(doseEntries[2].value, 0.75)
  845. XCTAssertEqual(doseEntries[2].deliveredUnits, 0.06666666666666667)
  846. XCTAssertEqual(doseEntries[2].syncIdentifier, "1201f3094c16a2 1/2")
  847. XCTAssertEqual(doseEntries[2].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  848. XCTAssertFalse(doseEntries[2].isMutable)
  849. XCTAssertEqual(doseEntries[3].type, .tempBasal)
  850. XCTAssertEqual(doseEntries[3].startDate, f("2018-12-12 18:30:00 +0000"))
  851. XCTAssertEqual(doseEntries[3].endDate, f("2018-12-12 18:40:00 +0000"))
  852. XCTAssertEqual(doseEntries[3].value, 0.75)
  853. XCTAssertEqual(doseEntries[3].deliveredUnits, 0.13333333333333333)
  854. XCTAssertEqual(doseEntries[3].syncIdentifier, "1201f3094c16a2 2/2")
  855. XCTAssertEqual(doseEntries[3].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 1.0))
  856. XCTAssertFalse(doseEntries[3].isMutable)
  857. }
  858. addPumpEvents6.fulfill();
  859. }
  860. doseStore.insulinDeliveryStore.getDoseEntries(includeMutable: true) { result in
  861. switch result {
  862. case .failure:
  863. XCTFail()
  864. case .success(let doseEntries):
  865. XCTAssertEqual(doseEntries.count, 4)
  866. XCTAssertEqual(doseEntries[0].type, .tempBasal)
  867. XCTAssertEqual(doseEntries[0].startDate, f("2018-12-12 17:35:00 +0000"))
  868. XCTAssertEqual(doseEntries[0].endDate, f("2018-12-12 18:05:00 +0000"))
  869. XCTAssertEqual(doseEntries[0].value, 2.125)
  870. XCTAssertEqual(doseEntries[0].deliveredUnits, 1.05)
  871. XCTAssertEqual(doseEntries[0].syncIdentifier, "1601fa23094c12")
  872. XCTAssertEqual(doseEntries[0].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  873. XCTAssertFalse(doseEntries[0].isMutable)
  874. XCTAssertEqual(doseEntries[1].type, .basal)
  875. XCTAssertEqual(doseEntries[1].startDate, f("2018-12-12 18:05:00 +0000"))
  876. XCTAssertEqual(doseEntries[1].endDate, f("2018-12-12 18:25:00 +0000"))
  877. XCTAssertEqual(doseEntries[1].value, 0.3)
  878. XCTAssertNil(doseEntries[1].deliveredUnits)
  879. XCTAssertEqual(doseEntries[1].syncIdentifier, "BasalRateSchedule 2018-12-12T18:05:00Z 2018-12-12T18:25:00Z")
  880. XCTAssertEqual(doseEntries[1].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  881. XCTAssertFalse(doseEntries[1].isMutable)
  882. XCTAssertEqual(doseEntries[2].type, .tempBasal)
  883. XCTAssertEqual(doseEntries[2].startDate, f("2018-12-12 18:25:00 +0000"))
  884. XCTAssertEqual(doseEntries[2].endDate, f("2018-12-12 18:30:00 +0000"))
  885. XCTAssertEqual(doseEntries[2].value, 0.75)
  886. XCTAssertEqual(doseEntries[2].deliveredUnits, 0.06666666666666667)
  887. XCTAssertEqual(doseEntries[2].syncIdentifier, "1201f3094c16a2 1/2")
  888. XCTAssertEqual(doseEntries[2].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 0.85))
  889. XCTAssertFalse(doseEntries[2].isMutable)
  890. XCTAssertEqual(doseEntries[3].type, .tempBasal)
  891. XCTAssertEqual(doseEntries[3].startDate, f("2018-12-12 18:30:00 +0000"))
  892. XCTAssertEqual(doseEntries[3].endDate, f("2018-12-12 18:40:00 +0000"))
  893. XCTAssertEqual(doseEntries[3].value, 0.75)
  894. XCTAssertEqual(doseEntries[3].deliveredUnits, 0.13333333333333333)
  895. XCTAssertEqual(doseEntries[3].syncIdentifier, "1201f3094c16a2 2/2")
  896. XCTAssertEqual(doseEntries[3].scheduledBasalRate, HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 1.0))
  897. XCTAssertFalse(doseEntries[3].isMutable)
  898. }
  899. addPumpEvents6.fulfill();
  900. }
  901. }
  902. waitForExpectations(timeout: 3)
  903. XCTAssertEqual(f("2018-12-12 18:40:00 +0000"), doseStore.insulinDeliveryStore.test_lastImmutableBasalEndDate)
  904. }
  905. }
  906. class DoseStoreQueryAnchorTests: XCTestCase {
  907. var rawValue: DoseStore.QueryAnchor.RawValue = [
  908. "modificationCounter": Int64(123)
  909. ]
  910. func testInitializerDefault() {
  911. let queryAnchor = DoseStore.QueryAnchor()
  912. XCTAssertEqual(queryAnchor.modificationCounter, 0)
  913. }
  914. func testInitializerRawValue() {
  915. let queryAnchor = DoseStore.QueryAnchor(rawValue: rawValue)
  916. XCTAssertNotNil(queryAnchor)
  917. XCTAssertEqual(queryAnchor?.modificationCounter, 123)
  918. }
  919. func testInitializerRawValueMissingModificationCounter() {
  920. rawValue["modificationCounter"] = nil
  921. XCTAssertNil(DoseStore.QueryAnchor(rawValue: rawValue))
  922. }
  923. func testInitializerRawValueInvalidModificationCounter() {
  924. rawValue["modificationCounter"] = "123"
  925. XCTAssertNil(DoseStore.QueryAnchor(rawValue: rawValue))
  926. }
  927. func testRawValueWithDefault() {
  928. let rawValue = DoseStore.QueryAnchor().rawValue
  929. XCTAssertEqual(rawValue.count, 1)
  930. XCTAssertEqual(rawValue["modificationCounter"] as? Int64, Int64(0))
  931. }
  932. func testRawValueWithNonDefault() {
  933. var queryAnchor = DoseStore.QueryAnchor()
  934. queryAnchor.modificationCounter = 123
  935. let rawValue = queryAnchor.rawValue
  936. XCTAssertEqual(rawValue.count, 1)
  937. XCTAssertEqual(rawValue["modificationCounter"] as? Int64, Int64(123))
  938. }
  939. }
  940. class DoseStoreQueryTests: PersistenceControllerTestCase {
  941. let insulinModel = WalshInsulinModel(actionDuration: .hours(4))
  942. let basalProfile = BasalRateSchedule(rawValue: ["timeZone": -28800, "items": [["value": 0.75, "startTime": 0.0], ["value": 0.8, "startTime": 10800.0], ["value": 0.85, "startTime": 32400.0], ["value": 1.0, "startTime": 68400.0]]])
  943. let insulinSensitivitySchedule = InsulinSensitivitySchedule(rawValue: ["unit": "mg/dL", "timeZone": -28800, "items": [["value": 40.0, "startTime": 0.0], ["value": 35.0, "startTime": 21600.0], ["value": 40.0, "startTime": 57600.0]]])
  944. var doseStore: DoseStore!
  945. var completion: XCTestExpectation!
  946. var queryAnchor: DoseStore.QueryAnchor!
  947. var limit: Int!
  948. override func setUp() {
  949. super.setUp()
  950. doseStore = DoseStore(healthStore: HKHealthStoreMock(),
  951. cacheStore: cacheStore,
  952. observationEnabled: false,
  953. insulinModelProvider: StaticInsulinModelProvider(insulinModel),
  954. longestEffectDuration: insulinModel.effectDuration,
  955. basalProfile: basalProfile,
  956. insulinSensitivitySchedule: insulinSensitivitySchedule,
  957. provenanceIdentifier: Bundle.main.bundleIdentifier!)
  958. let semaphore = DispatchSemaphore(value: 0)
  959. cacheStore.onReady { (error) in
  960. semaphore.signal()
  961. }
  962. semaphore.wait()
  963. completion = expectation(description: "Completion")
  964. queryAnchor = DoseStore.QueryAnchor()
  965. limit = Int.max
  966. }
  967. override func tearDown() {
  968. limit = nil
  969. queryAnchor = nil
  970. completion = nil
  971. doseStore = nil
  972. super.tearDown()
  973. }
  974. func testPumpEventEmptyWithDefaultQueryAnchor() {
  975. doseStore.executePumpEventQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  976. switch result {
  977. case .failure(let error):
  978. XCTFail("Unexpected failure: \(error)")
  979. case .success(let anchor, let data):
  980. XCTAssertEqual(anchor.modificationCounter, 0)
  981. XCTAssertEqual(data.count, 0)
  982. }
  983. self.completion.fulfill()
  984. }
  985. wait(for: [completion], timeout: 2, enforceOrder: true)
  986. }
  987. func testPumpEventEmptyWithMissingQueryAnchor() {
  988. queryAnchor = nil
  989. doseStore.executePumpEventQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  990. switch result {
  991. case .failure(let error):
  992. XCTFail("Unexpected failure: \(error)")
  993. case .success(let anchor, let data):
  994. XCTAssertEqual(anchor.modificationCounter, 0)
  995. XCTAssertEqual(data.count, 0)
  996. }
  997. self.completion.fulfill()
  998. }
  999. wait(for: [completion], timeout: 2, enforceOrder: true)
  1000. }
  1001. func testPumpEventEmptyWithNonDefaultQueryAnchor() {
  1002. queryAnchor.modificationCounter = 1
  1003. doseStore.executePumpEventQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  1004. switch result {
  1005. case .failure(let error):
  1006. XCTFail("Unexpected failure: \(error)")
  1007. case .success(let anchor, let data):
  1008. XCTAssertEqual(anchor.modificationCounter, 1)
  1009. XCTAssertEqual(data.count, 0)
  1010. }
  1011. self.completion.fulfill()
  1012. }
  1013. wait(for: [completion], timeout: 2, enforceOrder: true)
  1014. }
  1015. func testPumpEventDataWithUnusedQueryAnchor() {
  1016. let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
  1017. addPumpEventData(withSyncIdentifiers: syncIdentifiers)
  1018. doseStore.executePumpEventQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  1019. switch result {
  1020. case .failure(let error):
  1021. XCTFail("Unexpected failure: \(error)")
  1022. case .success(let anchor, let data):
  1023. XCTAssertEqual(anchor.modificationCounter, 3)
  1024. XCTAssertEqual(data.count, 3)
  1025. for (index, syncIdentifier) in syncIdentifiers.enumerated() {
  1026. XCTAssertEqual(data[index].raw?.hexadecimalString, syncIdentifier)
  1027. }
  1028. }
  1029. self.completion.fulfill()
  1030. }
  1031. wait(for: [completion], timeout: 2, enforceOrder: true)
  1032. }
  1033. func testPumpEventDataWithStaleQueryAnchor() {
  1034. let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
  1035. addPumpEventData(withSyncIdentifiers: syncIdentifiers)
  1036. queryAnchor.modificationCounter = 2
  1037. doseStore.executePumpEventQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  1038. switch result {
  1039. case .failure(let error):
  1040. XCTFail("Unexpected failure: \(error)")
  1041. case .success(let anchor, let data):
  1042. XCTAssertEqual(anchor.modificationCounter, 3)
  1043. XCTAssertEqual(data.count, 1)
  1044. XCTAssertEqual(data[0].raw?.hexadecimalString, syncIdentifiers[2])
  1045. }
  1046. self.completion.fulfill()
  1047. }
  1048. wait(for: [completion], timeout: 2, enforceOrder: true)
  1049. }
  1050. func testPumpEventDataWithCurrentQueryAnchor() {
  1051. let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
  1052. addPumpEventData(withSyncIdentifiers: syncIdentifiers)
  1053. queryAnchor.modificationCounter = 3
  1054. doseStore.executePumpEventQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  1055. switch result {
  1056. case .failure(let error):
  1057. XCTFail("Unexpected failure: \(error)")
  1058. case .success(let anchor, let data):
  1059. XCTAssertEqual(anchor.modificationCounter, 3)
  1060. XCTAssertEqual(data.count, 0)
  1061. }
  1062. self.completion.fulfill()
  1063. }
  1064. wait(for: [completion], timeout: 2, enforceOrder: true)
  1065. }
  1066. func testPumpEventDataWithLimitCoveredByData() {
  1067. let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
  1068. addPumpEventData(withSyncIdentifiers: syncIdentifiers)
  1069. limit = 2
  1070. doseStore.executePumpEventQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  1071. switch result {
  1072. case .failure(let error):
  1073. XCTFail("Unexpected failure: \(error)")
  1074. case .success(let anchor, let data):
  1075. XCTAssertEqual(anchor.modificationCounter, 2)
  1076. XCTAssertEqual(data.count, 2)
  1077. XCTAssertEqual(data[0].raw?.hexadecimalString, syncIdentifiers[0])
  1078. XCTAssertEqual(data[1].raw?.hexadecimalString, syncIdentifiers[1])
  1079. }
  1080. self.completion.fulfill()
  1081. }
  1082. wait(for: [completion], timeout: 2, enforceOrder: true)
  1083. }
  1084. private func addPumpEventData(withSyncIdentifiers syncIdentifiers: [String]) {
  1085. cacheStore.managedObjectContext.performAndWait {
  1086. for syncIdentifier in syncIdentifiers {
  1087. let pumpEvent = PumpEvent(context: self.cacheStore.managedObjectContext)
  1088. pumpEvent.date = Date()
  1089. pumpEvent.type = PumpEventType.allCases.randomElement()!
  1090. pumpEvent.raw = Data(hexadecimalString: syncIdentifier)
  1091. self.cacheStore.save()
  1092. }
  1093. }
  1094. }
  1095. private func generateSyncIdentifier() -> String {
  1096. return UUID().data.hexadecimalString
  1097. }
  1098. }
  1099. class DoseStoreCriticalEventLogTests: PersistenceControllerTestCase {
  1100. let insulinModel = WalshInsulinModel(actionDuration: .hours(4))
  1101. let basalProfile = BasalRateSchedule(rawValue: ["timeZone": -28800, "items": [["value": 0.75, "startTime": 0.0], ["value": 0.8, "startTime": 10800.0], ["value": 0.85, "startTime": 32400.0], ["value": 1.0, "startTime": 68400.0]]])
  1102. let insulinSensitivitySchedule = InsulinSensitivitySchedule(rawValue: ["unit": "mg/dL", "timeZone": -28800, "items": [["value": 40.0, "startTime": 0.0], ["value": 35.0, "startTime": 21600.0], ["value": 40.0, "startTime": 57600.0]]])
  1103. var doseStore: DoseStore!
  1104. var outputStream: MockOutputStream!
  1105. var progress: Progress!
  1106. override func setUp() {
  1107. super.setUp()
  1108. let persistedDate = dateFormatter.date(from: "2100-01-02T03:00:00Z")!
  1109. let url = URL(string: "http://a.b.com")!
  1110. let events = [PersistedPumpEvent(date: dateFormatter.date(from: "2100-01-02T03:08:00Z")!, persistedDate: persistedDate, dose: nil, isUploaded: false, objectIDURL: url, raw: nil, title: nil, type: nil),
  1111. PersistedPumpEvent(date: dateFormatter.date(from: "2100-01-02T03:10:00Z")!, persistedDate: persistedDate, dose: nil, isUploaded: false, objectIDURL: url, raw: nil, title: nil, type: nil),
  1112. PersistedPumpEvent(date: dateFormatter.date(from: "2100-01-02T03:04:00Z")!, persistedDate: persistedDate, dose: nil, isUploaded: false, objectIDURL: url, raw: nil, title: nil, type: nil),
  1113. PersistedPumpEvent(date: dateFormatter.date(from: "2100-01-02T03:06:00Z")!, persistedDate: persistedDate, dose: nil, isUploaded: false, objectIDURL: url, raw: nil, title: nil, type: nil),
  1114. PersistedPumpEvent(date: dateFormatter.date(from: "2100-01-02T03:02:00Z")!, persistedDate: persistedDate, dose: nil, isUploaded: false, objectIDURL: url, raw: nil, title: nil, type: nil)]
  1115. doseStore = DoseStore(healthStore: HKHealthStoreMock(),
  1116. cacheStore: cacheStore,
  1117. observationEnabled: false,
  1118. insulinModelProvider: StaticInsulinModelProvider(insulinModel),
  1119. longestEffectDuration: insulinModel.effectDuration,
  1120. basalProfile: basalProfile,
  1121. insulinSensitivitySchedule: insulinSensitivitySchedule,
  1122. provenanceIdentifier: Bundle.main.bundleIdentifier!)
  1123. XCTAssertNil(doseStore.addPumpEvents(events: events))
  1124. outputStream = MockOutputStream()
  1125. progress = Progress()
  1126. }
  1127. override func tearDown() {
  1128. doseStore = nil
  1129. super.tearDown()
  1130. }
  1131. func testExportProgressTotalUnitCount() {
  1132. switch doseStore.exportProgressTotalUnitCount(startDate: dateFormatter.date(from: "2100-01-02T03:03:00Z")!,
  1133. endDate: dateFormatter.date(from: "2100-01-02T03:09:00Z")!) {
  1134. case .failure(let error):
  1135. XCTFail("Unexpected failure: \(error)")
  1136. case .success(let progressTotalUnitCount):
  1137. XCTAssertEqual(progressTotalUnitCount, 3 * 1)
  1138. }
  1139. }
  1140. func testExportProgressTotalUnitCountEmpty() {
  1141. switch doseStore.exportProgressTotalUnitCount(startDate: dateFormatter.date(from: "2100-01-02T03:00:00Z")!,
  1142. endDate: dateFormatter.date(from: "2100-01-02T03:01:00Z")!) {
  1143. case .failure(let error):
  1144. XCTFail("Unexpected failure: \(error)")
  1145. case .success(let progressTotalUnitCount):
  1146. XCTAssertEqual(progressTotalUnitCount, 0)
  1147. }
  1148. }
  1149. func testExport() {
  1150. XCTAssertNil(doseStore.export(startDate: dateFormatter.date(from: "2100-01-02T03:03:00Z")!,
  1151. endDate: dateFormatter.date(from: "2100-01-02T03:09:00Z")!,
  1152. to: outputStream,
  1153. progress: progress))
  1154. XCTAssertEqual(outputStream.string, """
  1155. [
  1156. {"createdAt":"2100-01-02T03:00:00.000Z","date":"2100-01-02T03:08:00.000Z","duration":0,"insulinType":0,"modificationCounter":1,"mutable":false,"uploaded":false,"wasProgrammedByPumpUI":false},
  1157. {"createdAt":"2100-01-02T03:00:00.000Z","date":"2100-01-02T03:04:00.000Z","duration":0,"insulinType":0,"modificationCounter":3,"mutable":false,"uploaded":false,"wasProgrammedByPumpUI":false},
  1158. {"createdAt":"2100-01-02T03:00:00.000Z","date":"2100-01-02T03:06:00.000Z","duration":0,"insulinType":0,"modificationCounter":4,"mutable":false,"uploaded":false,"wasProgrammedByPumpUI":false}
  1159. ]
  1160. """
  1161. )
  1162. XCTAssertEqual(progress.completedUnitCount, 3 * 1)
  1163. }
  1164. func testExportEmpty() {
  1165. XCTAssertNil(doseStore.export(startDate: dateFormatter.date(from: "2100-01-02T03:00:00Z")!,
  1166. endDate: dateFormatter.date(from: "2100-01-02T03:01:00Z")!,
  1167. to: outputStream,
  1168. progress: progress))
  1169. XCTAssertEqual(outputStream.string, "[]")
  1170. XCTAssertEqual(progress.completedUnitCount, 0)
  1171. }
  1172. func testExportCancelled() {
  1173. progress.cancel()
  1174. XCTAssertEqual(doseStore.export(startDate: dateFormatter.date(from: "2100-01-02T03:03:00Z")!,
  1175. endDate: dateFormatter.date(from: "2100-01-02T03:09:00Z")!,
  1176. to: outputStream,
  1177. progress: progress) as? CriticalEventLogError, CriticalEventLogError.cancelled)
  1178. }
  1179. private let dateFormatter = ISO8601DateFormatter()
  1180. }
  1181. class DoseStoreEffectTests: PersistenceControllerTestCase {
  1182. var doseStore: DoseStore!
  1183. var insulinSensitivitySchedule: InsulinSensitivitySchedule {
  1184. return InsulinSensitivitySchedule(unit: HKUnit.milligramsPerDeciliter, dailyItems: [RepeatingScheduleValue(startTime: 0.0, value: 40.0)], timeZone: .currentFixed)!
  1185. }
  1186. let dateFormatter = ISO8601DateFormatter.localTimeDate()
  1187. override func setUp() {
  1188. super.setUp()
  1189. let healthStore = HKHealthStoreMock()
  1190. let exponentialInsulinModel: InsulinModel = ExponentialInsulinModelPreset.rapidActingAdult
  1191. let startDate = dateFormatter.date(from: "2015-07-13T12:00:00")!
  1192. doseStore = DoseStore(
  1193. healthStore: healthStore,
  1194. observeHealthKitSamplesFromOtherApps: false,
  1195. cacheStore: cacheStore,
  1196. observationEnabled: false,
  1197. insulinModelProvider: StaticInsulinModelProvider(exponentialInsulinModel),
  1198. longestEffectDuration: exponentialInsulinModel.effectDuration,
  1199. basalProfile: BasalRateSchedule(dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: 1.0)]),
  1200. insulinSensitivitySchedule: insulinSensitivitySchedule,
  1201. overrideHistory: TemporaryScheduleOverrideHistory(),
  1202. provenanceIdentifier: Bundle.main.bundleIdentifier!,
  1203. test_currentDate: startDate
  1204. )
  1205. }
  1206. override func tearDown() {
  1207. doseStore = nil
  1208. super.tearDown()
  1209. }
  1210. func loadGlucoseEffectFixture(_ resourceName: String) -> [GlucoseEffect] {
  1211. let fixture: [JSONDictionary] = loadFixture(resourceName)
  1212. let dateFormatter = ISO8601DateFormatter.localTimeDate()
  1213. return fixture.map {
  1214. return GlucoseEffect(startDate: dateFormatter.date(from: $0["date"] as! String)!, quantity: HKQuantity(unit: HKUnit(from: $0["unit"] as! String), doubleValue:$0["amount"] as! Double))
  1215. }
  1216. }
  1217. func loadDoseFixture(_ resourceName: String) -> [DoseEntry] {
  1218. let fixture: [JSONDictionary] = loadFixture(resourceName)
  1219. let dateFormatter = ISO8601DateFormatter.localTimeDate()
  1220. return fixture.compactMap {
  1221. guard let unit = DoseUnit(rawValue: $0["unit"] as! String),
  1222. let pumpType = PumpEventType(rawValue: $0["type"] as! String),
  1223. let type = DoseType(pumpEventType: pumpType)
  1224. else {
  1225. return nil
  1226. }
  1227. var scheduledBasalRate: HKQuantity? = nil
  1228. if let scheduled = $0["scheduled"] as? Double {
  1229. scheduledBasalRate = HKQuantity(unit: unit.unit, doubleValue: scheduled)
  1230. }
  1231. return DoseEntry(
  1232. type: type,
  1233. startDate: dateFormatter.date(from: $0["start_at"] as! String)!,
  1234. endDate: dateFormatter.date(from: $0["end_at"] as! String)!,
  1235. value: $0["amount"] as! Double,
  1236. unit: unit,
  1237. description: $0["description"] as? String,
  1238. syncIdentifier: $0["raw"] as? String,
  1239. scheduledBasalRate: scheduledBasalRate
  1240. )
  1241. }
  1242. }
  1243. func injectDoseEvents(from fixture: String) {
  1244. let events = loadDoseFixture(fixture).map {
  1245. NewPumpEvent(
  1246. date: $0.startDate,
  1247. dose: $0,
  1248. raw: Data(UUID().uuidString.utf8),
  1249. title: "",
  1250. type: $0.type.pumpEventType
  1251. )
  1252. }
  1253. let updateGroup = DispatchGroup()
  1254. updateGroup.enter()
  1255. doseStore.addPumpEvents(events, lastReconciliation: nil) { error in
  1256. if error != nil {
  1257. XCTFail("Doses should be added successfully to dose store")
  1258. }
  1259. updateGroup.leave()
  1260. }
  1261. updateGroup.wait()
  1262. }
  1263. func testGlucoseEffectFromTempBasal() {
  1264. injectDoseEvents(from: "basal_dose")
  1265. let output = loadGlucoseEffectFixture("effect_from_basal_output_exponential")
  1266. var insulinEffects: [GlucoseEffect]!
  1267. let startDate = dateFormatter.date(from: "2015-07-13T12:00:00")!
  1268. let updateGroup = DispatchGroup()
  1269. updateGroup.enter()
  1270. doseStore.getGlucoseEffects(start: startDate) { (result) -> Void in
  1271. switch result {
  1272. case .failure(let error):
  1273. print(error)
  1274. XCTFail("Mock should always return success")
  1275. case .success(let effects):
  1276. insulinEffects = effects
  1277. }
  1278. updateGroup.leave()
  1279. }
  1280. updateGroup.wait()
  1281. XCTAssertEqual(output.count, insulinEffects.count)
  1282. for (expected, calculated) in zip(output, insulinEffects) {
  1283. XCTAssertEqual(expected.startDate, calculated.startDate)
  1284. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: 1.0, String(describing: expected.startDate))
  1285. }
  1286. }
  1287. func testGlucoseEffectFromTempBasalWithOldDoses() {
  1288. injectDoseEvents(from: "basal_dose_with_expired")
  1289. let output = loadGlucoseEffectFixture("effect_from_basal_output_exponential")
  1290. var insulinEffects: [GlucoseEffect]!
  1291. let startDate = dateFormatter.date(from: "2015-07-13T12:00:00")!
  1292. let updateGroup = DispatchGroup()
  1293. updateGroup.enter()
  1294. doseStore.getGlucoseEffects(start: startDate) { (result) -> Void in
  1295. switch result {
  1296. case .failure(let error):
  1297. print(error)
  1298. XCTFail("Mock should always return success")
  1299. case .success(let effects):
  1300. insulinEffects = effects
  1301. }
  1302. updateGroup.leave()
  1303. }
  1304. updateGroup.wait()
  1305. XCTAssertEqual(output.count, insulinEffects.count)
  1306. for (expected, calculated) in zip(output, insulinEffects) {
  1307. XCTAssertEqual(expected.startDate, calculated.startDate)
  1308. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: 1.0, String(describing: expected.startDate))
  1309. }
  1310. }
  1311. func testGlucoseEffectFromHistory() {
  1312. injectDoseEvents(from: "dose_history_with_delivered_units")
  1313. let output = loadGlucoseEffectFixture("effect_from_history_exponential_delivered_units_output")
  1314. var insulinEffects: [GlucoseEffect]!
  1315. let startDate = dateFormatter.date(from: "2016-01-30T15:40:49")!
  1316. let updateGroup = DispatchGroup()
  1317. updateGroup.enter()
  1318. doseStore.getGlucoseEffects(start: startDate) { (result) -> Void in
  1319. switch result {
  1320. case .failure(let error):
  1321. print(error)
  1322. XCTFail("Mock should always return success")
  1323. case .success(let effects):
  1324. insulinEffects = effects
  1325. }
  1326. updateGroup.leave()
  1327. }
  1328. updateGroup.wait()
  1329. XCTAssertEqual(output.count, insulinEffects.count)
  1330. for (expected, calculated) in zip(output, insulinEffects) {
  1331. XCTAssertEqual(expected.startDate, calculated.startDate)
  1332. XCTAssertEqual(expected.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), calculated.quantity.doubleValue(for: HKUnit.milligramsPerDeciliter), accuracy: 1.0, String(describing: expected.startDate))
  1333. }
  1334. }
  1335. }