Skip to content

Commit 0790483

Browse files
authored
LOOP-1686: add higher level tests to Loop algorithm (#200)
LOOP-1686: add higher level tests to Loop algorithm
1 parent 636c5e4 commit 0790483

File tree

63 files changed

+9926
-207
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+9926
-207
lines changed

Loop.xcodeproj/project.pbxproj

Lines changed: 268 additions & 0 deletions
Large diffs are not rendered by default.

Loop/Extensions/DeviceDataManager+DeviceStatus.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ extension DeviceDataManager {
6969
{
7070
return .openAppURL(url)
7171
} else if let cgmManagerUI = (cgmManager as? CGMManagerUI),
72-
let unit = loopManager.glucoseStore.preferredUnit
72+
let unit = glucoseStore.preferredUnit
7373
{
7474
return .presentViewController(cgmManagerUI.settingsViewController(for: unit, glucoseTintColor: .glucoseTintColor, guidanceColors: .default))
7575
} else {

Loop/Managers/DeviceDataManager.swift

Lines changed: 130 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,46 @@ final class DeviceDataManager {
6767
UserDefaults.appGroup?.pumpManagerRawValue = pumpManager?.rawValue
6868
}
6969
}
70+
71+
// MARK: Stores
72+
let healthStore: HKHealthStore
73+
74+
let carbStore: CarbStore
75+
76+
let doseStore: DoseStore
77+
78+
let glucoseStore: GlucoseStore
79+
80+
private let cacheStore: PersistenceController
81+
82+
let dosingDecisionStore: DosingDecisionStore
83+
84+
let settingsStore: SettingsStore
85+
86+
/// All the HealthKit types to be read and shared by stores
87+
private var sampleTypes: Set<HKSampleType> {
88+
return Set([
89+
glucoseStore.sampleType,
90+
carbStore.sampleType,
91+
doseStore.sampleType,
92+
].compactMap { $0 })
93+
}
94+
95+
/// True if any stores require HealthKit authorization
96+
var authorizationRequired: Bool {
97+
return glucoseStore.authorizationRequired ||
98+
carbStore.authorizationRequired ||
99+
doseStore.authorizationRequired
100+
}
101+
102+
/// True if the user has explicitly denied access to any stores' HealthKit types
103+
private var sharingDenied: Bool {
104+
return glucoseStore.sharingDenied ||
105+
carbStore.sharingDenied ||
106+
doseStore.sharingDenied
107+
}
108+
109+
// MARK: Services
70110

71111
private(set) var servicesManager: ServicesManager!
72112

@@ -115,6 +155,49 @@ final class DeviceDataManager {
115155
self.pluginManager = pluginManager
116156
self.alertManager = alertManager
117157

158+
self.healthStore = HKHealthStore()
159+
self.cacheStore = PersistenceController.controllerInAppGroupDirectory()
160+
161+
let absorptionTimes = LoopSettings.defaultCarbAbsorptionTimes
162+
let sensitivitySchedule = UserDefaults.appGroup?.insulinSensitivitySchedule
163+
let overrideHistory = UserDefaults.appGroup?.overrideHistory ?? TemporaryScheduleOverrideHistory.init()
164+
165+
self.carbStore = CarbStore(
166+
healthStore: healthStore,
167+
observeHealthKitSamplesFromOtherApps: FeatureFlags.observeHealthKitSamplesFromOtherApps,
168+
cacheStore: cacheStore,
169+
cacheLength: localCacheDuration,
170+
defaultAbsorptionTimes: absorptionTimes,
171+
observationInterval: absorptionTimes.slow * 2,
172+
carbRatioSchedule: UserDefaults.appGroup?.carbRatioSchedule,
173+
insulinSensitivitySchedule: sensitivitySchedule,
174+
overrideHistory: overrideHistory,
175+
carbAbsorptionModel: FeatureFlags.nonlinearCarbModelEnabled ? .nonlinear : .linear
176+
)
177+
178+
self.doseStore = DoseStore(
179+
healthStore: healthStore,
180+
observeHealthKitSamplesFromOtherApps: FeatureFlags.observeHealthKitSamplesFromOtherApps,
181+
cacheStore: cacheStore,
182+
cacheLength: localCacheDuration,
183+
insulinModel: UserDefaults.appGroup?.insulinModelSettings?.model,
184+
basalProfile: UserDefaults.appGroup?.basalRateSchedule,
185+
insulinSensitivitySchedule: sensitivitySchedule,
186+
overrideHistory: overrideHistory,
187+
lastPumpEventsReconciliation: pumpManager?.lastReconciliation
188+
)
189+
190+
self.glucoseStore = GlucoseStore(
191+
healthStore: healthStore,
192+
observeHealthKitSamplesFromOtherApps: FeatureFlags.observeHealthKitSamplesFromOtherApps,
193+
cacheStore: cacheStore,
194+
cacheLength: localCacheDuration,
195+
observationInterval: .hours(24)
196+
)
197+
198+
self.dosingDecisionStore = DosingDecisionStore(store: cacheStore, expireAfter: localCacheDuration)
199+
self.settingsStore = SettingsStore(store: cacheStore, expireAfter: localCacheDuration)
200+
118201
bluetoothStateManager.addBluetoothStateObserver(self)
119202

120203
if let pumpManagerRawValue = UserDefaults.appGroup?.pumpManagerRawValue {
@@ -136,16 +219,23 @@ final class DeviceDataManager {
136219
basalDeliveryState: pumpManager?.status.basalDeliveryState,
137220
lastPumpEventsReconciliation: pumpManager?.lastReconciliation,
138221
analyticsServicesManager: analyticsServicesManager,
139-
localCacheDuration: localCacheDuration
222+
localCacheDuration: localCacheDuration,
223+
doseStore: doseStore,
224+
glucoseStore: glucoseStore,
225+
carbStore: carbStore,
226+
dosingDecisionStore: dosingDecisionStore,
227+
settingsStore: settingsStore
140228
)
229+
cacheStore.delegate = loopManager
230+
141231
watchManager = WatchDataManager(deviceManager: self)
142232

143233
let remoteDataServicesManager = RemoteDataServicesManager(
144-
carbStore: loopManager.carbStore,
145-
doseStore: loopManager.doseStore,
146-
dosingDecisionStore: loopManager.dosingDecisionStore,
147-
glucoseStore: loopManager.glucoseStore,
148-
settingsStore: loopManager.settingsStore
234+
carbStore: carbStore,
235+
doseStore: doseStore,
236+
dosingDecisionStore: dosingDecisionStore,
237+
glucoseStore: glucoseStore,
238+
settingsStore: settingsStore
149239
)
150240

151241
servicesManager = ServicesManager(
@@ -156,18 +246,17 @@ final class DeviceDataManager {
156246
dataManager: loopManager
157247
)
158248

159-
160249
if FeatureFlags.scenariosEnabled {
161250
testingScenariosManager = LocalTestingScenariosManager(deviceManager: self)
162251
}
163252

164253
loopManager.delegate = self
165254

166-
loopManager.carbStore.delegate = self
167-
loopManager.doseStore.delegate = self
168-
loopManager.dosingDecisionStore.delegate = self
169-
loopManager.glucoseStore.delegate = self
170-
loopManager.settingsStore.delegate = self
255+
carbStore.delegate = self
256+
doseStore.delegate = self
257+
dosingDecisionStore.delegate = self
258+
glucoseStore.delegate = self
259+
settingsStore.delegate = self
171260

172261
setupPump()
173262
setupCGM()
@@ -257,6 +346,21 @@ final class DeviceDataManager {
257346

258347
return Manager.init(rawState: rawState) as? CGMManagerUI
259348
}
349+
350+
// Get HealthKit authorization for all of the stores
351+
func authorize(_ completion: @escaping () -> Void) {
352+
// Authorize all types at once for simplicity
353+
healthStore.requestAuthorization(toShare: sampleTypes, read: sampleTypes) { (success, error) in
354+
if success {
355+
// Call the individual authorization methods to trigger query creation
356+
self.carbStore.authorize(toShare: true, { _ in })
357+
self.doseStore.insulinDeliveryStore.authorize(toShare: true, { _ in })
358+
self.glucoseStore.authorize(toShare: true, { _ in })
359+
}
360+
361+
completion()
362+
}
363+
}
260364

261365
func generateDiagnosticReport(_ completion: @escaping (_ report: String) -> Void) {
262366
self.loopManager.generateDiagnosticReport { (loopReport) in
@@ -288,6 +392,8 @@ final class DeviceDataManager {
288392
"* lastError: \(String(describing: self.lastError))",
289393
"* lastBLEDrivenUpdate: \(self.lastBLEDrivenUpdate)",
290394
"",
395+
"cacheStore: \(String(reflecting: self.cacheStore))",
396+
"",
291397
self.cgmManager != nil ? String(reflecting: self.cgmManager!) : "cgmManager: nil",
292398
"",
293399
self.pumpManager != nil ? String(reflecting: self.pumpManager!) : "pumpManager: nil",
@@ -317,7 +423,7 @@ private extension DeviceDataManager {
317423
cgmManager?.cgmManagerDelegate = self
318424
cgmManager?.delegateQueue = queue
319425

320-
loopManager.glucoseStore.managedDataInterval = cgmManager?.managedDataInterval
426+
glucoseStore.managedDataInterval = cgmManager?.managedDataInterval
321427

322428
updatePumpManagerBLEHeartbeatPreference()
323429
if let cgmManager = cgmManager {
@@ -334,12 +440,12 @@ private extension DeviceDataManager {
334440
pumpManager?.pumpManagerDelegate = self
335441
pumpManager?.delegateQueue = queue
336442

337-
loopManager.doseStore.device = pumpManager?.status.device
443+
doseStore.device = pumpManager?.status.device
338444
pumpManagerHUDProvider = pumpManager?.hudProvider(insulinTintColor: .insulinTintColor, guidanceColors: .default)
339445

340446
// Proliferate PumpModel preferences to DoseStore
341447
if let pumpRecordsBasalProfileStartEvents = pumpManager?.pumpRecordsBasalProfileStartEvents {
342-
loopManager?.doseStore.pumpRecordsBasalProfileStartEvents = pumpRecordsBasalProfileStartEvents
448+
doseStore.pumpRecordsBasalProfileStartEvents = pumpRecordsBasalProfileStartEvents
343449
}
344450
if let pumpManager = pumpManager {
345451
alertManager?.addAlertResponder(managerIdentifier: pumpManager.managerIdentifier,
@@ -453,7 +559,7 @@ extension DeviceDataManager: CGMManagerDelegate {
453559

454560
func startDateToFilterNewData(for manager: CGMManager) -> Date? {
455561
dispatchPrecondition(condition: .onQueue(queue))
456-
return loopManager.glucoseStore.latestGlucose?.startDate
562+
return glucoseStore.latestGlucose?.startDate
457563
}
458564

459565
func cgmManagerDidUpdateState(_ manager: CGMManager) {
@@ -542,7 +648,7 @@ extension DeviceDataManager: PumpManagerDelegate {
542648
dispatchPrecondition(condition: .onQueue(queue))
543649
log.default("PumpManager:%{public}@ did update status: %{public}@", String(describing: type(of: pumpManager)), String(describing: status))
544650

545-
loopManager.doseStore.device = status.device
651+
doseStore.device = status.device
546652

547653
if let newBatteryValue = status.pumpBatteryChargeRemaining {
548654
if newBatteryValue == 0 {
@@ -569,7 +675,7 @@ extension DeviceDataManager: PumpManagerDelegate {
569675

570676
log.default("PumpManager:%{public}@ will deactivate", String(describing: type(of: pumpManager)))
571677

572-
loopManager.doseStore.resetPumpData()
678+
doseStore.resetPumpData(completion: nil)
573679
DispatchQueue.main.async {
574680
self.pumpManager = nil
575681
}
@@ -579,15 +685,15 @@ extension DeviceDataManager: PumpManagerDelegate {
579685
dispatchPrecondition(condition: .onQueue(queue))
580686
log.default("PumpManager:%{public}@ did update pumpRecordsBasalProfileStartEvents to %{public}@", String(describing: type(of: pumpManager)), String(describing: pumpRecordsBasalProfileStartEvents))
581687

582-
loopManager.doseStore.pumpRecordsBasalProfileStartEvents = pumpRecordsBasalProfileStartEvents
688+
doseStore.pumpRecordsBasalProfileStartEvents = pumpRecordsBasalProfileStartEvents
583689
}
584690

585691
func pumpManager(_ pumpManager: PumpManager, didError error: PumpManagerError) {
586692
dispatchPrecondition(condition: .onQueue(queue))
587693
log.error("PumpManager:%{public}@ did error: %{public}@", String(describing: type(of: pumpManager)), String(describing: error))
588694

589695
setLastError(error: error)
590-
loopManager.storeDosingDecision(withError: error)
696+
loopManager.storeDosingDecision(withDate: Date(), withError: error)
591697
}
592698

593699
func pumpManager(_ pumpManager: PumpManager, hasNewPumpEvents events: [NewPumpEvent], lastReconciliation: Date?, completion: @escaping (_ error: Error?) -> Void) {
@@ -653,7 +759,7 @@ extension DeviceDataManager: PumpManagerDelegate {
653759

654760
func startDateToFilterNewPumpEvents(for manager: PumpManager) -> Date {
655761
dispatchPrecondition(condition: .onQueue(queue))
656-
return loopManager.doseStore.pumpEventQueryAfterDate
762+
return doseStore.pumpEventQueryAfterDate
657763
}
658764
}
659765

@@ -721,16 +827,16 @@ extension DeviceDataManager {
721827
}
722828

723829
let devicePredicate = HKQuery.predicateForObjects(from: [testingPumpManager.testingDevice])
724-
let doseStore = loopManager.doseStore
725830
let insulinDeliveryStore = doseStore.insulinDeliveryStore
831+
726832
let healthStore = insulinDeliveryStore.healthStore
727833
doseStore.resetPumpData { doseStoreError in
728834
guard doseStoreError == nil else {
729835
completion?(doseStoreError!)
730836
return
731837
}
732838

733-
healthStore.deleteObjects(of: doseStore.sampleType!, predicate: devicePredicate) { success, deletedObjectCount, error in
839+
healthStore.deleteObjects(of: self.doseStore.sampleType!, predicate: devicePredicate) { success, deletedObjectCount, error in
734840
if success {
735841
insulinDeliveryStore.test_lastBasalEndDate = nil
736842
}
@@ -750,7 +856,7 @@ extension DeviceDataManager {
750856
}
751857

752858
let predicate = HKQuery.predicateForObjects(from: [testingCGMManager.testingDevice])
753-
loopManager.glucoseStore.purgeGlucoseSamples(matchingCachePredicate: nil, healthKitPredicate: predicate) { success, count, error in
859+
glucoseStore.purgeGlucoseSamples(matchingCachePredicate: nil, healthKitPredicate: predicate) { success, count, error in
754860
completion?(error)
755861
}
756862
}

0 commit comments

Comments
 (0)