| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- //
- // DoseStoreTests.swift
- // LoopKit
- //
- // Copyright © 2017 LoopKit Authors. All rights reserved.
- //
- import XCTest
- import CoreData
- import HealthKit
- @testable import LoopKit
- class DoseStoreTests: PersistenceControllerTestCase {
- func testPumpEventTypeDoseMigration() {
- cacheStore.managedObjectContext.performAndWait {
- let event = PumpEvent(entity: PumpEvent.entity(), insertInto: cacheStore.managedObjectContext)
- event.date = Date()
- event.duration = .minutes(30)
- event.unit = .unitsPerHour
- event.type = .tempBasal
- event.value = 0.5
- event.doseType = nil
- XCTAssertNotNil(event.dose)
- XCTAssertEqual(.tempBasal, event.dose!.type)
- }
- }
- func testDeduplication() {
- cacheStore.managedObjectContext.performAndWait {
- let bolus1 = PumpEvent(context: cacheStore.managedObjectContext)
- bolus1.date = DateFormatter.descriptionFormatter.date(from: "2018-04-30 02:12:42 +0000")
- bolus1.raw = Data(hexadecimalString: "0100a600a6001b006a0c335d12")!
- bolus1.type = PumpEventType.bolus
- bolus1.dose = DoseEntry(type: .bolus, startDate: bolus1.date!, value: 4.15, unit: .units, syncIdentifier: bolus1.raw?.hexadecimalString)
- let bolus2 = PumpEvent(context: cacheStore.managedObjectContext)
- bolus2.date = DateFormatter.descriptionFormatter.date(from: "2018-04-30 00:00:00 +0000")
- bolus2.raw = Data(hexadecimalString: "0100a600a6001b006a0c335d12")!
- bolus2.type = PumpEventType.bolus
- bolus2.dose = DoseEntry(type: .bolus, startDate: bolus2.date!, value: 0.15, unit: .units, syncIdentifier: bolus1.raw?.hexadecimalString)
- let request: NSFetchRequest<PumpEvent> = PumpEvent.fetchRequest()
- let eventsBeforeSave = try! cacheStore.managedObjectContext.fetch(request)
- XCTAssertEqual(2, eventsBeforeSave.count)
- try! cacheStore.managedObjectContext.save()
- let eventsAfterSave = try! cacheStore.managedObjectContext.fetch(request)
- XCTAssertEqual(1, eventsAfterSave.count)
- }
- }
- /// See https://github.com/LoopKit/Loop/issues/853
- func testOutOfOrderDosesSyncedToHealth() {
- let formatter = DateFormatter.descriptionFormatter
- let f = { (input) in
- return formatter.date(from: input)!
- }
- // 1. Create a DoseStore
- let healthStore = HKHealthStoreMock()
- let doseStore = DoseStore(
- healthStore: healthStore,
- cacheStore: cacheStore,
- observationEnabled: false,
- insulinModel: WalshInsulinModel(actionDuration: .hours(4)),
- 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]]]),
- 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]]]),
- syncVersion: 1,
- // Set the current date
- test_currentDate: f("2018-12-12 18:07:14 +0000")
- )
- // 2. Add a temp basal which has already ended. It should be saved to Health
- let pumpEvents1 = [
- NewPumpEvent(date: f("2018-12-12 17:35:58 +0000"), dose: nil, isMutable: false, 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),
- 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), isMutable: false, 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)
- ]
- doseStore.insulinDeliveryStore.test_lastBasalEndDate = f("2018-12-12 17:35:58 +0000")
- let addPumpEvents1 = expectation(description: "add pumpEvents1")
- addPumpEvents1.expectedFulfillmentCount = 3
- healthStore.setSaveHandler({ (objects, success, error) in
- XCTAssertEqual(1, objects.count)
- let sample = objects.first as! HKQuantitySample
- XCTAssertEqual(HKInsulinDeliveryReason.basal, sample.insulinDeliveryReason)
- XCTAssertNil(error)
- addPumpEvents1.fulfill()
- })
- doseStore.insulinDeliveryStore.test_lastBasalEndDateDidSet = {
- addPumpEvents1.fulfill()
- }
- doseStore.addPumpEvents(pumpEvents1, lastReconciliation: Date()) { (error) in
- XCTAssertNil(error)
- addPumpEvents1.fulfill()
- }
- waitForExpectations(timeout: 3)
- XCTAssertEqual(f("2018-12-12 18:05:58 +0000"), doseStore.insulinDeliveryStore.test_lastBasalEndDate)
- // 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)
- // Even though it is before lastBasalEndDate, it should be saved to HealthKit.
- doseStore.insulinDeliveryStore.test_currentDate = f("2018-12-12 18:16:23 +0000")
- let pumpEvents2 = [
- 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), isMutable: false, 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)
- ]
- let addPumpEvents2 = expectation(description: "add pumpEvents2")
- addPumpEvents2.expectedFulfillmentCount = 3
- healthStore.setSaveHandler({ (objects, success, error) in
- XCTAssertEqual(1, objects.count)
- let sample = objects.first as! HKQuantitySample
- XCTAssertEqual(HKInsulinDeliveryReason.bolus, sample.insulinDeliveryReason)
- XCTAssertEqual(5.0, sample.quantity.doubleValue(for: .internationalUnit()))
- XCTAssertEqual(f("2018-12-12 18:05:14 +0000"), sample.startDate)
- XCTAssertNil(error)
- addPumpEvents2.fulfill()
- })
- doseStore.insulinDeliveryStore.test_lastBasalEndDateDidSet = {
- addPumpEvents2.fulfill()
- }
- doseStore.addPumpEvents(pumpEvents2, lastReconciliation: Date()) { (error) in
- XCTAssertNil(error)
- addPumpEvents2.fulfill()
- }
- waitForExpectations(timeout: 3)
- XCTAssertEqual(f("2018-12-12 18:05:58 +0000"), doseStore.insulinDeliveryStore.test_lastBasalEndDate)
- // Add the next set of pump events, which haven't completed and shouldn't be saved to HealthKit
- doseStore.insulinDeliveryStore.test_currentDate = f("2018-12-12 18:21:22 +0000")
- let pumpEvents3 = [
- NewPumpEvent(date: f("2018-12-12 18:16:31 +0000"), dose: nil, isMutable: false, 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),
- 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), isMutable: false, 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),
- ]
- let addPumpEvents3 = expectation(description: "add pumpEvents3")
- addPumpEvents3.expectedFulfillmentCount = 1
- healthStore.setSaveHandler({ (objects, success, error) in
- XCTFail()
- })
- doseStore.insulinDeliveryStore.test_lastBasalEndDateDidSet = {
- XCTFail()
- }
- doseStore.addPumpEvents(pumpEvents3, lastReconciliation: Date()) { (error) in
- XCTAssertNil(error)
- addPumpEvents3.fulfill()
- }
- waitForExpectations(timeout: 3)
- XCTAssertEqual(f("2018-12-12 18:05:58 +0000"), doseStore.insulinDeliveryStore.test_lastBasalEndDate)
- }
- /// https://github.com/LoopKit/Loop/issues/852
- func testSplitBasalsSyncedToHealth() {
- let formatter = DateFormatter.descriptionFormatter
- let f = { (input) in
- return formatter.date(from: input)!
- }
- // Create a DoseStore
- let healthStore = HKHealthStoreMock()
- let doseStore = DoseStore(
- healthStore: healthStore,
- cacheStore: cacheStore,
- observationEnabled: false,
- insulinModel: WalshInsulinModel(actionDuration: .hours(4)),
- 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]]]),
- 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]]]),
- syncVersion: 1,
- // Set the current date (5 minutes later)
- test_currentDate: f("2018-11-29 11:04:27 +0000")
- )
- doseStore.pumpRecordsBasalProfileStartEvents = false
- doseStore.insulinDeliveryStore.test_lastBasalEndDate = f("2018-11-29 10:54:28 +0000")
- // Add a temp basal. It hasn't finished yet, and should not be saved to Health
- let pumpEvents1 = [
- NewPumpEvent(date: f("2018-11-29 10:59:28 +0000"), dose: nil, isMutable: false, 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),
- 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), isMutable: false, 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)
- ]
- let addPumpEvents1 = expectation(description: "add pumpEvents1")
- addPumpEvents1.expectedFulfillmentCount = 1
- healthStore.setSaveHandler({ (objects, success, error) in
- XCTFail()
- })
- doseStore.insulinDeliveryStore.test_lastBasalEndDateDidSet = {
- XCTFail()
- }
- doseStore.addPumpEvents(pumpEvents1, lastReconciliation: Date()) { (error) in
- XCTAssertNil(error)
- addPumpEvents1.fulfill()
- }
- waitForExpectations(timeout: 3)
- XCTAssertEqual(f("2018-11-29 10:54:28 +0000"), doseStore.insulinDeliveryStore.test_lastBasalEndDate)
- XCTAssertEqual(f("2018-11-29 10:59:28 +0000"), doseStore.pumpEventQueryAfterDate)
- // Add the next query of the same pump events (no new data) 5 minutes later. Expect the same result
- doseStore.insulinDeliveryStore.test_currentDate = f("2018-11-29 11:09:27 +0000")
- let addPumpEvents2 = expectation(description: "add pumpEvents2")
- addPumpEvents2.expectedFulfillmentCount = 1
- healthStore.setSaveHandler({ (objects, success, error) in
- XCTFail()
- })
- doseStore.insulinDeliveryStore.test_lastBasalEndDateDidSet = {
- XCTFail()
- }
- doseStore.addPumpEvents(pumpEvents1, lastReconciliation: Date()) { (error) in
- XCTAssertNil(error)
- addPumpEvents2.fulfill()
- }
- waitForExpectations(timeout: 3)
- XCTAssertEqual(f("2018-11-29 10:54:28 +0000"), doseStore.insulinDeliveryStore.test_lastBasalEndDate)
- XCTAssertEqual(f("2018-11-29 10:59:28 +0000"), doseStore.pumpEventQueryAfterDate)
- // Add the next set of pump events, including the last temp basal change.
- // The previous, completed basal entries should be saved to Health
- doseStore.insulinDeliveryStore.test_currentDate = f("2018-11-29 11:14:28 +0000")
- let pumpEvents3 = [
- NewPumpEvent(date: f("2018-11-29 11:09:27 +0000"), dose: nil, isMutable: false, 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),
- 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), isMutable: false, 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)
- ]
- let addPumpEvents3 = expectation(description: "add pumpEvents3")
- addPumpEvents3.expectedFulfillmentCount = 3
- healthStore.setSaveHandler({ (objects, success, error) in
- XCTAssertEqual(3, objects.count)
- let basal = objects[0] as! HKQuantitySample
- XCTAssertEqual(HKInsulinDeliveryReason.basal, basal.insulinDeliveryReason)
- XCTAssertEqual(f("2018-11-29 10:54:28 +0000"), basal.startDate)
- XCTAssertEqual(f("2018-11-29 10:59:28 +0000"), basal.endDate)
- XCTAssertEqual("BasalRateSchedule 2018-11-29T10:54:28Z 2018-11-29T10:59:28Z", basal.metadata![HKMetadataKeySyncIdentifier] as! String)
- let temp1 = objects[1] as! HKQuantitySample
- XCTAssertEqual(HKInsulinDeliveryReason.basal, temp1.insulinDeliveryReason)
- XCTAssertEqual(f("2018-11-29 10:59:28 +0000"), temp1.startDate)
- XCTAssertEqual(f("2018-11-29 11:00:00 +0000"), temp1.endDate)
- XCTAssertEqual("5bffc7cace53e48e87f7cfcb 1/2", temp1.metadata![HKMetadataKeySyncIdentifier] as! String)
- XCTAssertEqual(0.003, temp1.quantity.doubleValue(for: .internationalUnit()), accuracy: 0.01)
- let temp2 = objects[2] as! HKQuantitySample
- XCTAssertEqual(HKInsulinDeliveryReason.basal, temp2.insulinDeliveryReason)
- XCTAssertEqual(f("2018-11-29 11:00:00 +0000"), temp2.startDate)
- XCTAssertEqual(f("2018-11-29 11:09:27 +0000"), temp2.endDate)
- XCTAssertEqual("5bffc7cace53e48e87f7cfcb 2/2", temp2.metadata![HKMetadataKeySyncIdentifier] as! String)
- XCTAssertEqual(0.047, temp2.quantity.doubleValue(for: .internationalUnit()), accuracy: 0.01)
- XCTAssertNil(error)
- addPumpEvents3.fulfill()
- })
- doseStore.insulinDeliveryStore.test_lastBasalEndDateDidSet = {
- addPumpEvents3.fulfill()
- }
- doseStore.addPumpEvents(pumpEvents3, lastReconciliation: Date()) { (error) in
- XCTAssertNil(error)
- addPumpEvents3.fulfill()
- }
- waitForExpectations(timeout: 3)
- XCTAssertEqual(f("2018-11-29 11:09:27 +0000"), doseStore.insulinDeliveryStore.test_lastBasalEndDate)
- XCTAssertEqual(f("2018-11-29 11:09:27 +0000"), doseStore.pumpEventQueryAfterDate)
- // Add the next set of pump events, including the last temp basal cancel
- doseStore.insulinDeliveryStore.test_currentDate = f("2018-11-29 11:19:28 +0000")
- let pumpEvents4 = [
- NewPumpEvent(date: f("2018-11-29 11:14:28 +0000"), dose: nil, isMutable: false, 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),
- 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), isMutable: false, 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)
- ]
- let addPumpEvents4 = expectation(description: "add pumpEvents4")
- addPumpEvents4.expectedFulfillmentCount = 3
- healthStore.setSaveHandler({ (objects, success, error) in
- XCTAssertEqual(1, objects.count)
- let temp = objects[0] as! HKQuantitySample
- XCTAssertEqual(HKInsulinDeliveryReason.basal, temp.insulinDeliveryReason)
- XCTAssertEqual(f("2018-11-29 11:09:27 +0000"), temp.startDate)
- XCTAssertEqual(f("2018-11-29 11:14:28 +0000"), temp.endDate)
- XCTAssertEqual("5bffca22ce53e48e87f7d624", temp.metadata![HKMetadataKeySyncIdentifier] as! String)
- XCTAssertEqual(0.05, temp.quantity.doubleValue(for: .internationalUnit()), accuracy: 0.01)
- XCTAssertNil(error)
- addPumpEvents4.fulfill()
- })
- doseStore.insulinDeliveryStore.test_lastBasalEndDateDidSet = {
- addPumpEvents4.fulfill()
- }
- doseStore.addPumpEvents(pumpEvents4, lastReconciliation: Date()) { (error) in
- XCTAssertNil(error)
- addPumpEvents4.fulfill()
- }
- waitForExpectations(timeout: 3)
- XCTAssertEqual(f("2018-11-29 11:14:28 +0000"), doseStore.pumpEventQueryAfterDate)
- XCTAssertEqual(f("2018-11-29 11:14:28 +0000"), doseStore.insulinDeliveryStore.test_lastBasalEndDate)
- }
- }
|