InsulinDeliveryStoreTests.swift 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  1. //
  2. // InsulinDeliveryStoreTests.swift
  3. // LoopKitHostedTests
  4. //
  5. // Created by Darin Krauss on 10/22/20.
  6. // Copyright © 2020 LoopKit Authors. All rights reserved.
  7. //
  8. import XCTest
  9. import HealthKit
  10. @testable import LoopKit
  11. class InsulinDeliveryStoreTestsBase: PersistenceControllerTestCase {
  12. internal let entry1 = DoseEntry(type: .basal,
  13. startDate: Date(timeIntervalSinceNow: -.minutes(6)),
  14. endDate: Date(timeIntervalSinceNow: -.minutes(5.5)),
  15. value: 1.8,
  16. unit: .unitsPerHour,
  17. deliveredUnits: 0.015,
  18. syncIdentifier: "4B14522E-A7B5-4E73-B76B-5043CD7176B0",
  19. scheduledBasalRate: nil)
  20. internal let entry2 = DoseEntry(type: .tempBasal,
  21. startDate: Date(timeIntervalSinceNow: -.minutes(2)),
  22. endDate: Date(timeIntervalSinceNow: -.minutes(1.5)),
  23. value: 2.4,
  24. unit: .unitsPerHour,
  25. deliveredUnits: 0.02,
  26. syncIdentifier: "A1F8E29B-33D6-4B38-B4CD-D84F14744871",
  27. scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 1.8))
  28. internal let entry3 = DoseEntry(type: .bolus,
  29. startDate: Date(timeIntervalSinceNow: -.minutes(4)),
  30. endDate: Date(timeIntervalSinceNow: -.minutes(3.5)),
  31. value: 1.0,
  32. unit: .units,
  33. deliveredUnits: nil,
  34. syncIdentifier: "1A1D6192-1521-4469-B962-1B82C4534BB1",
  35. scheduledBasalRate: nil)
  36. internal let device = HKDevice(name: UUID().uuidString,
  37. manufacturer: UUID().uuidString,
  38. model: UUID().uuidString,
  39. hardwareVersion: UUID().uuidString,
  40. firmwareVersion: UUID().uuidString,
  41. softwareVersion: UUID().uuidString,
  42. localIdentifier: UUID().uuidString,
  43. udiDeviceIdentifier: UUID().uuidString)
  44. var healthStore: HKHealthStoreMock!
  45. var insulinDeliveryStore: InsulinDeliveryStore!
  46. var authorizationStatus: HKAuthorizationStatus = .notDetermined
  47. override func setUp() {
  48. super.setUp()
  49. healthStore = HKHealthStoreMock()
  50. healthStore.authorizationStatus = authorizationStatus
  51. insulinDeliveryStore = InsulinDeliveryStore(healthStore: healthStore,
  52. cacheStore: cacheStore,
  53. cacheLength: .hours(1),
  54. provenanceIdentifier: HKSource.default().bundleIdentifier)
  55. let semaphore = DispatchSemaphore(value: 0)
  56. cacheStore.onReady { error in
  57. XCTAssertNil(error)
  58. semaphore.signal()
  59. }
  60. semaphore.wait()
  61. }
  62. override func tearDown() {
  63. let semaphore = DispatchSemaphore(value: 0)
  64. insulinDeliveryStore.purgeAllDoseEntries(healthKitPredicate: HKQuery.predicateForObjects(from: HKSource.default())) { error in
  65. XCTAssertNil(error)
  66. semaphore.signal()
  67. }
  68. semaphore.wait()
  69. insulinDeliveryStore = nil
  70. healthStore = nil
  71. super.tearDown()
  72. }
  73. }
  74. class InsulinDeliveryStoreTestsAuthorized: InsulinDeliveryStoreTestsBase {
  75. override func setUp() {
  76. authorizationStatus = .sharingAuthorized
  77. super.setUp()
  78. }
  79. func testObserverQueryStartup() {
  80. // Check that an observer query is registered when authorization is already determined.
  81. XCTAssertFalse(insulinDeliveryStore.authorizationRequired);
  82. let observerQueryCreated = expectation(description: "observer query created")
  83. insulinDeliveryStore.createObserverQuery = { (sampleType, predicate, updateHandler) -> HKObserverQuery in
  84. let observerQuery = HKObserverQueryMock(sampleType: sampleType, predicate: predicate, updateHandler: updateHandler)
  85. observerQueryCreated.fulfill()
  86. return observerQuery
  87. }
  88. waitForExpectations(timeout: 2)
  89. }
  90. }
  91. class InsulinDeliveryStoreTests: InsulinDeliveryStoreTestsBase {
  92. // MARK: - HealthKitSampleStore
  93. func testHealthKitQueryAnchorPersistence() {
  94. var observerQuery: HKObserverQueryMock? = nil
  95. var anchoredObjectQuery: HKAnchoredObjectQueryMock? = nil
  96. XCTAssert(insulinDeliveryStore.authorizationRequired);
  97. XCTAssertNil(insulinDeliveryStore.observerQuery);
  98. let observerQueryCreated = expectation(description: "observer query created")
  99. insulinDeliveryStore.createObserverQuery = { (sampleType, predicate, updateHandler) -> HKObserverQuery in
  100. observerQuery = HKObserverQueryMock(sampleType: sampleType, predicate: predicate, updateHandler: updateHandler)
  101. observerQueryCreated.fulfill()
  102. return observerQuery!
  103. }
  104. let authorizationCompletion = expectation(description: "authorization completion")
  105. insulinDeliveryStore.authorize { (result) in
  106. authorizationCompletion.fulfill()
  107. }
  108. waitForExpectations(timeout: 2)
  109. XCTAssertNotNil(observerQuery)
  110. let anchoredObjectQueryCreationExpectation = expectation(description: "anchored object query creation")
  111. insulinDeliveryStore.createAnchoredObjectQuery = { (sampleType, predicate, anchor, limit, resultsHandler) -> HKAnchoredObjectQuery in
  112. anchoredObjectQuery = HKAnchoredObjectQueryMock(type: sampleType, predicate: predicate, anchor: anchor, limit: limit, resultsHandler: resultsHandler)
  113. anchoredObjectQueryCreationExpectation.fulfill()
  114. return anchoredObjectQuery!
  115. }
  116. let observerQueryCompletionExpectation = expectation(description: "observer query completion")
  117. let observerQueryCompletionHandler = {
  118. observerQueryCompletionExpectation.fulfill()
  119. }
  120. // This simulates a signal marking the arrival of new HK Data.
  121. observerQuery!.updateHandler(observerQuery!, observerQueryCompletionHandler, nil)
  122. wait(for: [anchoredObjectQueryCreationExpectation], timeout: 10)
  123. // Trigger results handler for anchored object query
  124. let returnedAnchor = HKQueryAnchor(fromValue: 5)
  125. anchoredObjectQuery!.resultsHandler(anchoredObjectQuery!, [], [], returnedAnchor, nil)
  126. // Wait for observerQueryCompletionExpectation
  127. waitForExpectations(timeout: 10)
  128. XCTAssertNotNil(insulinDeliveryStore.queryAnchor)
  129. // Allow any managedObjectContext tasks to complete, like storing the anchor
  130. cacheStore.managedObjectContext.performAndWait {}
  131. // Create a new glucose store, and ensure it uses the last query anchor
  132. let newInsulinDeliveryStore = InsulinDeliveryStore(healthStore: healthStore,
  133. cacheStore: cacheStore,
  134. provenanceIdentifier: HKSource.default().bundleIdentifier)
  135. let newAuthorizationCompletion = expectation(description: "authorization completion")
  136. observerQuery = nil
  137. let newObserverQueryCreationExpectation = expectation(description: "new observer query created")
  138. newInsulinDeliveryStore.createObserverQuery = { (sampleType, predicate, updateHandler) -> HKObserverQuery in
  139. observerQuery = HKObserverQueryMock(sampleType: sampleType, predicate: predicate, updateHandler: updateHandler)
  140. newObserverQueryCreationExpectation.fulfill()
  141. return observerQuery!
  142. }
  143. newInsulinDeliveryStore.authorize { (result) in
  144. newAuthorizationCompletion.fulfill()
  145. }
  146. waitForExpectations(timeout: 10)
  147. anchoredObjectQuery = nil
  148. let newAnchoredObjectQueryCreationExpectation = expectation(description: "new anchored object query creation")
  149. newInsulinDeliveryStore.createAnchoredObjectQuery = { (sampleType, predicate, anchor, limit, resultsHandler) -> HKAnchoredObjectQuery in
  150. anchoredObjectQuery = HKAnchoredObjectQueryMock(type: sampleType, predicate: predicate, anchor: anchor, limit: limit, resultsHandler: resultsHandler)
  151. newAnchoredObjectQueryCreationExpectation.fulfill()
  152. return anchoredObjectQuery!
  153. }
  154. // This simulates a signal marking the arrival of new HK Data.
  155. observerQuery!.updateHandler(observerQuery!, {}, nil)
  156. waitForExpectations(timeout: 10)
  157. // Assert new glucose store is querying with the last anchor that our HealthKit mock returned
  158. XCTAssertEqual(returnedAnchor, anchoredObjectQuery?.anchor)
  159. anchoredObjectQuery!.resultsHandler(anchoredObjectQuery!, [], [], returnedAnchor, nil)
  160. }
  161. // MARK: - Fetching
  162. func testGetDoseEntries() {
  163. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  164. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  165. switch result {
  166. case .failure(let error):
  167. XCTFail("Unexpected failure: \(error)")
  168. case .success:
  169. break
  170. }
  171. addDoseEntriesCompletion.fulfill()
  172. }
  173. waitForExpectations(timeout: 10)
  174. let getDoseEntries1Completion = expectation(description: "getDoseEntries1")
  175. insulinDeliveryStore.getDoseEntries() { result in
  176. switch result {
  177. case .failure(let error):
  178. XCTFail("Unexpected failure: \(error)")
  179. case .success(let entries):
  180. XCTAssertEqual(entries.count, 3)
  181. XCTAssertEqual(entries[0].type, self.entry1.type)
  182. XCTAssertEqual(entries[0].startDate, self.entry1.startDate)
  183. XCTAssertEqual(entries[0].endDate, self.entry1.endDate)
  184. XCTAssertEqual(entries[0].value, 0.015)
  185. XCTAssertEqual(entries[0].unit, .units)
  186. XCTAssertNil(entries[0].deliveredUnits)
  187. XCTAssertEqual(entries[0].description, self.entry1.description)
  188. XCTAssertEqual(entries[0].syncIdentifier, self.entry1.syncIdentifier)
  189. XCTAssertEqual(entries[0].scheduledBasalRate, self.entry1.scheduledBasalRate)
  190. XCTAssertEqual(entries[1].type, self.entry3.type)
  191. XCTAssertEqual(entries[1].startDate, self.entry3.startDate)
  192. XCTAssertEqual(entries[1].endDate, self.entry3.endDate)
  193. XCTAssertEqual(entries[1].value, self.entry3.value)
  194. XCTAssertEqual(entries[1].unit, self.entry3.unit)
  195. XCTAssertEqual(entries[1].deliveredUnits, self.entry3.deliveredUnits)
  196. XCTAssertEqual(entries[1].description, self.entry3.description)
  197. XCTAssertEqual(entries[1].syncIdentifier, self.entry3.syncIdentifier)
  198. XCTAssertEqual(entries[1].scheduledBasalRate, self.entry3.scheduledBasalRate)
  199. XCTAssertEqual(entries[2].type, self.entry2.type)
  200. XCTAssertEqual(entries[2].startDate, self.entry2.startDate)
  201. XCTAssertEqual(entries[2].endDate, self.entry2.endDate)
  202. XCTAssertEqual(entries[2].value, self.entry2.value)
  203. XCTAssertEqual(entries[2].unit, self.entry2.unit)
  204. XCTAssertEqual(entries[2].deliveredUnits, self.entry2.deliveredUnits)
  205. XCTAssertEqual(entries[2].description, self.entry2.description)
  206. XCTAssertEqual(entries[2].syncIdentifier, self.entry2.syncIdentifier)
  207. XCTAssertEqual(entries[2].scheduledBasalRate, self.entry2.scheduledBasalRate)
  208. }
  209. getDoseEntries1Completion.fulfill()
  210. }
  211. waitForExpectations(timeout: 10)
  212. let getDoseEntries2Completion = expectation(description: "getDoseEntries2")
  213. insulinDeliveryStore.getDoseEntries(start: Date(timeIntervalSinceNow: -.minutes(3.75)), end: Date(timeIntervalSinceNow: -.minutes(1.75))) { result in
  214. switch result {
  215. case .failure(let error):
  216. XCTFail("Unexpected failure: \(error)")
  217. case .success(let entries):
  218. XCTAssertEqual(entries.count, 2)
  219. XCTAssertEqual(entries[0].type, self.entry3.type)
  220. XCTAssertEqual(entries[0].startDate, self.entry3.startDate)
  221. XCTAssertEqual(entries[0].endDate, self.entry3.endDate)
  222. XCTAssertEqual(entries[0].value, self.entry3.value)
  223. XCTAssertEqual(entries[0].unit, self.entry3.unit)
  224. XCTAssertEqual(entries[0].deliveredUnits, self.entry3.deliveredUnits)
  225. XCTAssertEqual(entries[0].description, self.entry3.description)
  226. XCTAssertEqual(entries[0].syncIdentifier, self.entry3.syncIdentifier)
  227. XCTAssertEqual(entries[0].scheduledBasalRate, self.entry3.scheduledBasalRate)
  228. XCTAssertEqual(entries[1].type, self.entry2.type)
  229. XCTAssertEqual(entries[1].startDate, self.entry2.startDate)
  230. XCTAssertEqual(entries[1].endDate, self.entry2.endDate)
  231. XCTAssertEqual(entries[1].value, self.entry2.value)
  232. XCTAssertEqual(entries[1].unit, self.entry2.unit)
  233. XCTAssertEqual(entries[1].deliveredUnits, self.entry2.deliveredUnits)
  234. XCTAssertEqual(entries[1].description, self.entry2.description)
  235. XCTAssertEqual(entries[1].syncIdentifier, self.entry2.syncIdentifier)
  236. XCTAssertEqual(entries[1].scheduledBasalRate, self.entry2.scheduledBasalRate)
  237. }
  238. getDoseEntries2Completion.fulfill()
  239. }
  240. waitForExpectations(timeout: 10)
  241. let purgeCachedInsulinDeliveryObjectsCompletion = expectation(description: "purgeCachedInsulinDeliveryObjects")
  242. insulinDeliveryStore.purgeCachedInsulinDeliveryObjects() { error in
  243. XCTAssertNil(error)
  244. purgeCachedInsulinDeliveryObjectsCompletion.fulfill()
  245. }
  246. waitForExpectations(timeout: 10)
  247. let getDoseEntries3Completion = expectation(description: "getDoseEntries3")
  248. insulinDeliveryStore.getDoseEntries() { result in
  249. switch result {
  250. case .failure(let error):
  251. XCTFail("Unexpected failure: \(error)")
  252. case .success(let entries):
  253. XCTAssertEqual(entries.count, 0)
  254. }
  255. getDoseEntries3Completion.fulfill()
  256. }
  257. waitForExpectations(timeout: 10)
  258. }
  259. func testLastBasalEndDate() {
  260. let getLastImmutableBasalEndDate1Completion = expectation(description: "getLastImmutableBasalEndDate1")
  261. insulinDeliveryStore.getLastImmutableBasalEndDate() { result in
  262. switch result {
  263. case .failure(let error):
  264. XCTFail("Unexpected failure: \(error)")
  265. case .success(let lastBasalEndDate):
  266. XCTAssertEqual(lastBasalEndDate, .distantPast)
  267. }
  268. getLastImmutableBasalEndDate1Completion.fulfill()
  269. }
  270. waitForExpectations(timeout: 10)
  271. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  272. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  273. switch result {
  274. case .failure(let error):
  275. XCTFail("Unexpected failure: \(error)")
  276. case .success:
  277. break
  278. }
  279. addDoseEntriesCompletion.fulfill()
  280. }
  281. waitForExpectations(timeout: 10)
  282. let getLastImmutableBasalEndDate2Completion = expectation(description: "getLastImmutableBasalEndDate2")
  283. insulinDeliveryStore.getLastImmutableBasalEndDate() { result in
  284. switch result {
  285. case .failure(let error):
  286. XCTFail("Unexpected failure: \(error)")
  287. case .success(let lastBasalEndDate):
  288. XCTAssertEqual(lastBasalEndDate, self.entry2.endDate)
  289. }
  290. getLastImmutableBasalEndDate2Completion.fulfill()
  291. }
  292. waitForExpectations(timeout: 10)
  293. let purgeCachedInsulinDeliveryObjectsCompletion = expectation(description: "purgeCachedInsulinDeliveryObjects")
  294. insulinDeliveryStore.purgeCachedInsulinDeliveryObjects() { error in
  295. XCTAssertNil(error)
  296. purgeCachedInsulinDeliveryObjectsCompletion.fulfill()
  297. }
  298. waitForExpectations(timeout: 10)
  299. let getLastImmutableBasalEndDate3Completion = expectation(description: "getLastImmutableBasalEndDate3")
  300. insulinDeliveryStore.getLastImmutableBasalEndDate() { result in
  301. switch result {
  302. case .failure(let error):
  303. XCTFail("Unexpected failure: \(error)")
  304. case .success(let lastBasalEndDate):
  305. XCTAssertEqual(lastBasalEndDate, .distantPast)
  306. }
  307. getLastImmutableBasalEndDate3Completion.fulfill()
  308. }
  309. waitForExpectations(timeout: 10)
  310. }
  311. // MARK: - Modification
  312. func testAddDoseEntries() {
  313. let addDoseEntries1Completion = expectation(description: "addDoseEntries1")
  314. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  315. switch result {
  316. case .failure(let error):
  317. XCTFail("Unexpected failure: \(error)")
  318. case .success:
  319. break
  320. }
  321. addDoseEntries1Completion.fulfill()
  322. }
  323. waitForExpectations(timeout: 10)
  324. let getDoseEntries1Completion = expectation(description: "getDoseEntries1")
  325. insulinDeliveryStore.getDoseEntries() { result in
  326. switch result {
  327. case .failure(let error):
  328. XCTFail("Unexpected failure: \(error)")
  329. case .success(let entries):
  330. XCTAssertEqual(entries.count, 3)
  331. XCTAssertEqual(entries[0].type, self.entry1.type)
  332. XCTAssertEqual(entries[0].startDate, self.entry1.startDate)
  333. XCTAssertEqual(entries[0].endDate, self.entry1.endDate)
  334. XCTAssertEqual(entries[0].value, 0.015)
  335. XCTAssertEqual(entries[0].unit, .units)
  336. XCTAssertNil(entries[0].deliveredUnits)
  337. XCTAssertEqual(entries[0].description, self.entry1.description)
  338. XCTAssertEqual(entries[0].syncIdentifier, self.entry1.syncIdentifier)
  339. XCTAssertEqual(entries[0].scheduledBasalRate, self.entry1.scheduledBasalRate)
  340. XCTAssertEqual(entries[1].type, self.entry3.type)
  341. XCTAssertEqual(entries[1].startDate, self.entry3.startDate)
  342. XCTAssertEqual(entries[1].endDate, self.entry3.endDate)
  343. XCTAssertEqual(entries[1].value, self.entry3.value)
  344. XCTAssertEqual(entries[1].unit, self.entry3.unit)
  345. XCTAssertEqual(entries[1].deliveredUnits, self.entry3.deliveredUnits)
  346. XCTAssertEqual(entries[1].description, self.entry3.description)
  347. XCTAssertEqual(entries[1].syncIdentifier, self.entry3.syncIdentifier)
  348. XCTAssertEqual(entries[1].scheduledBasalRate, self.entry3.scheduledBasalRate)
  349. XCTAssertEqual(entries[2].type, self.entry2.type)
  350. XCTAssertEqual(entries[2].startDate, self.entry2.startDate)
  351. XCTAssertEqual(entries[2].endDate, self.entry2.endDate)
  352. XCTAssertEqual(entries[2].value, self.entry2.value)
  353. XCTAssertEqual(entries[2].unit, self.entry2.unit)
  354. XCTAssertEqual(entries[2].deliveredUnits, self.entry2.deliveredUnits)
  355. XCTAssertEqual(entries[2].description, self.entry2.description)
  356. XCTAssertEqual(entries[2].syncIdentifier, self.entry2.syncIdentifier)
  357. XCTAssertEqual(entries[2].scheduledBasalRate, self.entry2.scheduledBasalRate)
  358. }
  359. getDoseEntries1Completion.fulfill()
  360. }
  361. waitForExpectations(timeout: 10)
  362. let addDoseEntries2Completion = expectation(description: "addDoseEntries2")
  363. insulinDeliveryStore.addDoseEntries([entry3, entry1, entry2], from: device, syncVersion: 2) { result in
  364. switch result {
  365. case .failure(let error):
  366. XCTFail("Unexpected failure: \(error)")
  367. case .success:
  368. break
  369. }
  370. addDoseEntries2Completion.fulfill()
  371. }
  372. waitForExpectations(timeout: 10)
  373. let getDoseEntries2Completion = expectation(description: "getDoseEntries2Completion")
  374. insulinDeliveryStore.getDoseEntries() { result in
  375. switch result {
  376. case .failure(let error):
  377. XCTFail("Unexpected failure: \(error)")
  378. case .success(let entries):
  379. XCTAssertEqual(entries.count, 3)
  380. XCTAssertEqual(entries[0].type, self.entry1.type)
  381. XCTAssertEqual(entries[0].startDate, self.entry1.startDate)
  382. XCTAssertEqual(entries[0].endDate, self.entry1.endDate)
  383. XCTAssertEqual(entries[0].value, 0.015)
  384. XCTAssertEqual(entries[0].unit, .units)
  385. XCTAssertNil(entries[0].deliveredUnits)
  386. XCTAssertEqual(entries[0].description, self.entry1.description)
  387. XCTAssertEqual(entries[0].syncIdentifier, self.entry1.syncIdentifier)
  388. XCTAssertEqual(entries[0].scheduledBasalRate, self.entry1.scheduledBasalRate)
  389. XCTAssertEqual(entries[1].type, self.entry3.type)
  390. XCTAssertEqual(entries[1].startDate, self.entry3.startDate)
  391. XCTAssertEqual(entries[1].endDate, self.entry3.endDate)
  392. XCTAssertEqual(entries[1].value, self.entry3.value)
  393. XCTAssertEqual(entries[1].unit, self.entry3.unit)
  394. XCTAssertEqual(entries[1].deliveredUnits, self.entry3.deliveredUnits)
  395. XCTAssertEqual(entries[1].description, self.entry3.description)
  396. XCTAssertEqual(entries[1].syncIdentifier, self.entry3.syncIdentifier)
  397. XCTAssertEqual(entries[1].scheduledBasalRate, self.entry3.scheduledBasalRate)
  398. XCTAssertEqual(entries[2].type, self.entry2.type)
  399. XCTAssertEqual(entries[2].startDate, self.entry2.startDate)
  400. XCTAssertEqual(entries[2].endDate, self.entry2.endDate)
  401. XCTAssertEqual(entries[2].value, self.entry2.value)
  402. XCTAssertEqual(entries[2].unit, self.entry2.unit)
  403. XCTAssertEqual(entries[2].deliveredUnits, self.entry2.deliveredUnits)
  404. XCTAssertEqual(entries[2].description, self.entry2.description)
  405. XCTAssertEqual(entries[2].syncIdentifier, self.entry2.syncIdentifier)
  406. XCTAssertEqual(entries[2].scheduledBasalRate, self.entry2.scheduledBasalRate)
  407. }
  408. getDoseEntries2Completion.fulfill()
  409. }
  410. waitForExpectations(timeout: 10)
  411. }
  412. func testAddDoseEntriesEmpty() {
  413. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  414. insulinDeliveryStore.addDoseEntries([], from: device, syncVersion: 2) { result in
  415. switch result {
  416. case .failure(let error):
  417. XCTFail("Unexpected failure: \(error)")
  418. case .success:
  419. break
  420. }
  421. addDoseEntriesCompletion.fulfill()
  422. }
  423. waitForExpectations(timeout: 10)
  424. }
  425. func testAddDoseEntriesNotification() {
  426. let doseEntriesDidChangeCompletion = expectation(description: "doseEntriesDidChange")
  427. let observer = NotificationCenter.default.addObserver(forName: InsulinDeliveryStore.doseEntriesDidChange, object: insulinDeliveryStore, queue: nil) { notification in
  428. doseEntriesDidChangeCompletion.fulfill()
  429. }
  430. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  431. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  432. switch result {
  433. case .failure(let error):
  434. XCTFail("Unexpected failure: \(error)")
  435. case .success:
  436. break
  437. }
  438. addDoseEntriesCompletion.fulfill()
  439. }
  440. wait(for: [doseEntriesDidChangeCompletion, addDoseEntriesCompletion], timeout: 10, enforceOrder: true)
  441. NotificationCenter.default.removeObserver(observer)
  442. }
  443. func testManuallyEnteredDoses() {
  444. let manualEntry = DoseEntry(type: .bolus,
  445. startDate: Date(timeIntervalSinceNow: -.minutes(15)),
  446. endDate: Date(timeIntervalSinceNow: -.minutes(10)),
  447. value: 3.0,
  448. unit: .units,
  449. syncIdentifier: "C0AB1CBD-6B36-4113-9D49-709A022B2451",
  450. manuallyEntered: true)
  451. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  452. insulinDeliveryStore.addDoseEntries([entry1, manualEntry, entry2, entry3], from: device, syncVersion: 2) { result in
  453. switch result {
  454. case .failure(let error):
  455. XCTFail("Unexpected failure: \(error)")
  456. case .success:
  457. break
  458. }
  459. addDoseEntriesCompletion.fulfill()
  460. }
  461. waitForExpectations(timeout: 10)
  462. let getManuallyEnteredDoses1Completion = expectation(description: "getManuallyEnteredDoses1")
  463. insulinDeliveryStore.getManuallyEnteredDoses(since: .distantPast) { result in
  464. switch result {
  465. case .failure(let error):
  466. XCTFail("Unexpected failure: \(error)")
  467. case .success(let entries):
  468. XCTAssertEqual(entries.count, 1)
  469. XCTAssertEqual(entries[0], manualEntry)
  470. }
  471. getManuallyEnteredDoses1Completion.fulfill()
  472. }
  473. waitForExpectations(timeout: 10)
  474. let getManuallyEnteredDoses2Completion = expectation(description: "getManuallyEnteredDoses2")
  475. insulinDeliveryStore.getManuallyEnteredDoses(since: Date(timeIntervalSinceNow: -.minutes(12))) { result in
  476. switch result {
  477. case .failure(let error):
  478. XCTFail("Unexpected failure: \(error)")
  479. case .success(let entries):
  480. XCTAssertEqual(entries.count, 0)
  481. }
  482. getManuallyEnteredDoses2Completion.fulfill()
  483. }
  484. waitForExpectations(timeout: 10)
  485. let deleteAllManuallyEnteredDoses1Completion = expectation(description: "deleteAllManuallyEnteredDoses1")
  486. insulinDeliveryStore.deleteAllManuallyEnteredDoses(since: Date(timeIntervalSinceNow: -.minutes(12))) { error in
  487. XCTAssertNil(error)
  488. deleteAllManuallyEnteredDoses1Completion.fulfill()
  489. }
  490. waitForExpectations(timeout: 10)
  491. let getManuallyEnteredDoses3Completion = expectation(description: "getManuallyEnteredDoses3")
  492. insulinDeliveryStore.getManuallyEnteredDoses(since: .distantPast) { result in
  493. switch result {
  494. case .failure(let error):
  495. XCTFail("Unexpected failure: \(error)")
  496. case .success(let entries):
  497. XCTAssertEqual(entries.count, 1)
  498. XCTAssertEqual(entries[0], manualEntry)
  499. }
  500. getManuallyEnteredDoses3Completion.fulfill()
  501. }
  502. waitForExpectations(timeout: 10)
  503. let deleteAllManuallyEnteredDose2Completion = expectation(description: "deleteAllManuallyEnteredDose2")
  504. insulinDeliveryStore.deleteAllManuallyEnteredDoses(since: Date(timeIntervalSinceNow: -.minutes(20))) { error in
  505. XCTAssertNil(error)
  506. deleteAllManuallyEnteredDose2Completion.fulfill()
  507. }
  508. waitForExpectations(timeout: 10)
  509. let getManuallyEnteredDoses4Completion = expectation(description: "getManuallyEnteredDoses4")
  510. insulinDeliveryStore.getManuallyEnteredDoses(since: .distantPast) { result in
  511. switch result {
  512. case .failure(let error):
  513. XCTFail("Unexpected failure: \(error)")
  514. case .success(let entries):
  515. XCTAssertEqual(entries.count, 0)
  516. }
  517. getManuallyEnteredDoses4Completion.fulfill()
  518. }
  519. waitForExpectations(timeout: 10)
  520. }
  521. // MARK: - Cache Management
  522. func testEarliestCacheDate() {
  523. XCTAssertEqual(insulinDeliveryStore.earliestCacheDate.timeIntervalSinceNow, -.hours(1), accuracy: 1)
  524. }
  525. func testPurgeAllDoseEntries() {
  526. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  527. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  528. switch result {
  529. case .failure(let error):
  530. XCTFail("Unexpected failure: \(error)")
  531. case .success:
  532. break
  533. }
  534. addDoseEntriesCompletion.fulfill()
  535. }
  536. waitForExpectations(timeout: 10)
  537. let getDoseEntries1Completion = expectation(description: "getDoseEntries1")
  538. insulinDeliveryStore.getDoseEntries() { result in
  539. switch result {
  540. case .failure(let error):
  541. XCTFail("Unexpected failure: \(error)")
  542. case .success(let entries):
  543. XCTAssertEqual(entries.count, 3)
  544. }
  545. getDoseEntries1Completion.fulfill()
  546. }
  547. waitForExpectations(timeout: 10)
  548. let purgeAllDoseEntriesCompletion = expectation(description: "purgeAllDoseEntries")
  549. insulinDeliveryStore.purgeAllDoseEntries(healthKitPredicate: HKQuery.predicateForObjects(from: HKSource.default())) { error in
  550. XCTAssertNil(error)
  551. purgeAllDoseEntriesCompletion.fulfill()
  552. }
  553. waitForExpectations(timeout: 10)
  554. let getDoseEntries2Completion = expectation(description: "getDoseEntries2")
  555. insulinDeliveryStore.getDoseEntries() { result in
  556. switch result {
  557. case .failure(let error):
  558. XCTFail("Unexpected failure: \(error)")
  559. case .success(let entries):
  560. XCTAssertEqual(entries.count, 0)
  561. }
  562. getDoseEntries2Completion.fulfill()
  563. }
  564. waitForExpectations(timeout: 10)
  565. }
  566. func testPurgeExpiredGlucoseObjects() {
  567. let expiredEntry = DoseEntry(type: .bolus,
  568. startDate: Date(timeIntervalSinceNow: -.hours(3)),
  569. endDate: Date(timeIntervalSinceNow: -.hours(2)),
  570. value: 3.0,
  571. unit: .units,
  572. deliveredUnits: nil,
  573. syncIdentifier: "7530B8CA-827A-4DE8-ADE3-9E10FF80A4A9",
  574. scheduledBasalRate: nil)
  575. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  576. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3, expiredEntry], from: device, syncVersion: 2) { result in
  577. switch result {
  578. case .failure(let error):
  579. XCTFail("Unexpected failure: \(error)")
  580. case .success:
  581. break
  582. }
  583. addDoseEntriesCompletion.fulfill()
  584. }
  585. waitForExpectations(timeout: 10)
  586. let getDoseEntriesCompletion = expectation(description: "getDoseEntries")
  587. insulinDeliveryStore.getDoseEntries() { result in
  588. switch result {
  589. case .failure(let error):
  590. XCTFail("Unexpected failure: \(error)")
  591. case .success(let entries):
  592. XCTAssertEqual(entries.count, 3)
  593. }
  594. getDoseEntriesCompletion.fulfill()
  595. }
  596. waitForExpectations(timeout: 10)
  597. }
  598. func testPurgeCachedInsulinDeliveryObjects() {
  599. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  600. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  601. switch result {
  602. case .failure(let error):
  603. XCTFail("Unexpected failure: \(error)")
  604. case .success:
  605. break
  606. }
  607. addDoseEntriesCompletion.fulfill()
  608. }
  609. waitForExpectations(timeout: 10)
  610. let getDoseEntries1Completion = expectation(description: "getDoseEntries1")
  611. insulinDeliveryStore.getDoseEntries() { result in
  612. switch result {
  613. case .failure(let error):
  614. XCTFail("Unexpected failure: \(error)")
  615. case .success(let entries):
  616. XCTAssertEqual(entries.count, 3)
  617. }
  618. getDoseEntries1Completion.fulfill()
  619. }
  620. waitForExpectations(timeout: 10)
  621. let purgeCachedInsulinDeliveryObjects1Completion = expectation(description: "purgeCachedInsulinDeliveryObjects1")
  622. insulinDeliveryStore.purgeCachedInsulinDeliveryObjects(before: Date(timeIntervalSinceNow: -.minutes(5))) { error in
  623. XCTAssertNil(error)
  624. purgeCachedInsulinDeliveryObjects1Completion.fulfill()
  625. }
  626. waitForExpectations(timeout: 10)
  627. let getDoseEntries2Completion = expectation(description: "getDoseEntries2")
  628. insulinDeliveryStore.getDoseEntries() { result in
  629. switch result {
  630. case .failure(let error):
  631. XCTFail("Unexpected failure: \(error)")
  632. case .success(let entries):
  633. XCTAssertEqual(entries.count, 2)
  634. }
  635. getDoseEntries2Completion.fulfill()
  636. }
  637. waitForExpectations(timeout: 10)
  638. let purgeCachedInsulinDeliveryObjects2Completion = expectation(description: "purgeCachedInsulinDeliveryObjects2")
  639. insulinDeliveryStore.purgeCachedInsulinDeliveryObjects() { error in
  640. XCTAssertNil(error)
  641. purgeCachedInsulinDeliveryObjects2Completion.fulfill()
  642. }
  643. waitForExpectations(timeout: 10)
  644. let getDoseEntries3Completion = expectation(description: "getDoseEntries3")
  645. insulinDeliveryStore.getDoseEntries() { result in
  646. switch result {
  647. case .failure(let error):
  648. XCTFail("Unexpected failure: \(error)")
  649. case .success(let entries):
  650. XCTAssertEqual(entries.count, 0)
  651. }
  652. getDoseEntries3Completion.fulfill()
  653. }
  654. waitForExpectations(timeout: 10)
  655. }
  656. func testPurgeCachedInsulinDeliveryObjectsNotification() {
  657. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  658. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  659. switch result {
  660. case .failure(let error):
  661. XCTFail("Unexpected failure: \(error)")
  662. case .success:
  663. break
  664. }
  665. addDoseEntriesCompletion.fulfill()
  666. }
  667. waitForExpectations(timeout: 10)
  668. let doseEntriesDidChangeCompletion = expectation(description: "doseEntriesDidChange")
  669. let observer = NotificationCenter.default.addObserver(forName: InsulinDeliveryStore.doseEntriesDidChange, object: insulinDeliveryStore, queue: nil) { notification in
  670. doseEntriesDidChangeCompletion.fulfill()
  671. }
  672. let purgeCachedInsulinDeliveryObjectsCompletion = expectation(description: "purgeCachedInsulinDeliveryObjects")
  673. insulinDeliveryStore.purgeCachedInsulinDeliveryObjects() { error in
  674. XCTAssertNil(error)
  675. purgeCachedInsulinDeliveryObjectsCompletion.fulfill()
  676. }
  677. wait(for: [doseEntriesDidChangeCompletion, purgeCachedInsulinDeliveryObjectsCompletion], timeout: 10, enforceOrder: true)
  678. NotificationCenter.default.removeObserver(observer)
  679. }
  680. }
  681. class InsulinDeliveryStoreQueryAnchorTests: XCTestCase {
  682. var rawValue: InsulinDeliveryStore.QueryAnchor.RawValue = [
  683. "modificationCounter": Int64(123)
  684. ]
  685. func testInitializerDefault() {
  686. let queryAnchor = InsulinDeliveryStore.QueryAnchor()
  687. XCTAssertEqual(queryAnchor.modificationCounter, 0)
  688. }
  689. func testInitializerRawValue() {
  690. let queryAnchor = InsulinDeliveryStore.QueryAnchor(rawValue: rawValue)
  691. XCTAssertNotNil(queryAnchor)
  692. XCTAssertEqual(queryAnchor?.modificationCounter, 123)
  693. }
  694. func testInitializerRawValueMissingModificationCounter() {
  695. rawValue["modificationCounter"] = nil
  696. XCTAssertNil(InsulinDeliveryStore.QueryAnchor(rawValue: rawValue))
  697. }
  698. func testInitializerRawValueInvalidModificationCounter() {
  699. rawValue["modificationCounter"] = "123"
  700. XCTAssertNil(InsulinDeliveryStore.QueryAnchor(rawValue: rawValue))
  701. }
  702. func testRawValueWithDefault() {
  703. let rawValue = InsulinDeliveryStore.QueryAnchor().rawValue
  704. XCTAssertEqual(rawValue.count, 1)
  705. XCTAssertEqual(rawValue["modificationCounter"] as? Int64, Int64(0))
  706. }
  707. func testRawValueWithNonDefault() {
  708. var queryAnchor = InsulinDeliveryStore.QueryAnchor()
  709. queryAnchor.modificationCounter = 123
  710. let rawValue = queryAnchor.rawValue
  711. XCTAssertEqual(rawValue.count, 1)
  712. XCTAssertEqual(rawValue["modificationCounter"] as? Int64, Int64(123))
  713. }
  714. }
  715. class InsulinDeliveryStoreQueryTests: PersistenceControllerTestCase {
  716. let insulinModel = WalshInsulinModel(actionDuration: .hours(4))
  717. 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]]])
  718. 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]]])
  719. var insulinDeliveryStore: InsulinDeliveryStore!
  720. var completion: XCTestExpectation!
  721. var queryAnchor: InsulinDeliveryStore.QueryAnchor!
  722. var limit: Int!
  723. override func setUp() {
  724. super.setUp()
  725. insulinDeliveryStore = InsulinDeliveryStore(healthStore: HKHealthStoreMock(),
  726. storeSamplesToHealthKit: false,
  727. cacheStore: cacheStore,
  728. observationEnabled: false,
  729. provenanceIdentifier: HKSource.default().bundleIdentifier)
  730. completion = expectation(description: "Completion")
  731. queryAnchor = InsulinDeliveryStore.QueryAnchor()
  732. limit = Int.max
  733. }
  734. override func tearDown() {
  735. limit = nil
  736. queryAnchor = nil
  737. completion = nil
  738. insulinDeliveryStore = nil
  739. super.tearDown()
  740. }
  741. func testDoseEmptyWithDefaultQueryAnchor() {
  742. insulinDeliveryStore.executeDoseQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  743. switch result {
  744. case .failure(let error):
  745. XCTFail("Unexpected failure: \(error)")
  746. case .success(let anchor, let created, let deleted):
  747. XCTAssertEqual(anchor.modificationCounter, 0)
  748. XCTAssertEqual(created.count, 0)
  749. XCTAssertEqual(deleted.count, 0)
  750. }
  751. self.completion.fulfill()
  752. }
  753. wait(for: [completion], timeout: 2, enforceOrder: true)
  754. }
  755. func testDoseEmptyWithMissingQueryAnchor() {
  756. queryAnchor = nil
  757. insulinDeliveryStore.executeDoseQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  758. switch result {
  759. case .failure(let error):
  760. XCTFail("Unexpected failure: \(error)")
  761. case .success(let anchor, let created, let deleted):
  762. XCTAssertEqual(anchor.modificationCounter, 0)
  763. XCTAssertEqual(created.count, 0)
  764. XCTAssertEqual(deleted.count, 0)
  765. }
  766. self.completion.fulfill()
  767. }
  768. wait(for: [completion], timeout: 2, enforceOrder: true)
  769. }
  770. func testDoseEmptyWithNonDefaultQueryAnchor() {
  771. queryAnchor.modificationCounter = 1
  772. insulinDeliveryStore.executeDoseQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  773. switch result {
  774. case .failure(let error):
  775. XCTFail("Unexpected failure: \(error)")
  776. case .success(let anchor, let created, let deleted):
  777. XCTAssertEqual(anchor.modificationCounter, 1)
  778. XCTAssertEqual(created.count, 0)
  779. XCTAssertEqual(deleted.count, 0)
  780. }
  781. self.completion.fulfill()
  782. }
  783. wait(for: [completion], timeout: 2, enforceOrder: true)
  784. }
  785. func testDoseDataWithUnusedQueryAnchor() {
  786. let doseData = [DoseDatum(), DoseDatum(deleted: true), DoseDatum()]
  787. addDoseData(doseData)
  788. insulinDeliveryStore.executeDoseQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  789. switch result {
  790. case .failure(let error):
  791. XCTFail("Unexpected failure: \(error)")
  792. case .success(let anchor, let created, let deleted):
  793. XCTAssertEqual(anchor.modificationCounter, 3)
  794. XCTAssertEqual(created.count, 2)
  795. XCTAssertEqual(deleted.count, 1)
  796. if created.count >= 2 {
  797. XCTAssertEqual(created[0].syncIdentifier, doseData[0].syncIdentifier)
  798. XCTAssertEqual(created[1].syncIdentifier, doseData[2].syncIdentifier)
  799. }
  800. if deleted.count >= 1 {
  801. XCTAssertEqual(deleted[0].syncIdentifier, doseData[1].syncIdentifier)
  802. }
  803. }
  804. self.completion.fulfill()
  805. }
  806. wait(for: [completion], timeout: 2, enforceOrder: true)
  807. }
  808. func testDoseDataWithStaleQueryAnchor() {
  809. let doseData = [DoseDatum(), DoseDatum(deleted: true), DoseDatum()]
  810. addDoseData(doseData)
  811. queryAnchor.modificationCounter = 2
  812. insulinDeliveryStore.executeDoseQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  813. switch result {
  814. case .failure(let error):
  815. XCTFail("Unexpected failure: \(error)")
  816. case .success(let anchor, let created, let deleted):
  817. XCTAssertEqual(anchor.modificationCounter, 3)
  818. XCTAssertEqual(created.count, 1)
  819. XCTAssertEqual(deleted.count, 0)
  820. if created.count >= 1 {
  821. XCTAssertEqual(created[0].syncIdentifier, doseData[2].syncIdentifier)
  822. }
  823. }
  824. self.completion.fulfill()
  825. }
  826. wait(for: [completion], timeout: 2, enforceOrder: true)
  827. }
  828. func testDoseDataWithCurrentQueryAnchor() {
  829. let doseData = [DoseDatum(), DoseDatum(deleted: true), DoseDatum()]
  830. addDoseData(doseData)
  831. queryAnchor.modificationCounter = 3
  832. insulinDeliveryStore.executeDoseQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  833. switch result {
  834. case .failure(let error):
  835. XCTFail("Unexpected failure: \(error)")
  836. case .success(let anchor, let created, let deleted):
  837. XCTAssertEqual(anchor.modificationCounter, 3)
  838. XCTAssertEqual(created.count, 0)
  839. XCTAssertEqual(deleted.count, 0)
  840. }
  841. self.completion.fulfill()
  842. }
  843. wait(for: [completion], timeout: 2, enforceOrder: true)
  844. }
  845. func testDoseDataWithLimitCoveredByData() {
  846. let doseData = [DoseDatum(), DoseDatum(deleted: true), DoseDatum()]
  847. addDoseData(doseData)
  848. limit = 2
  849. insulinDeliveryStore.executeDoseQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  850. switch result {
  851. case .failure(let error):
  852. XCTFail("Unexpected failure: \(error)")
  853. case .success(let anchor, let created, let deleted):
  854. XCTAssertEqual(anchor.modificationCounter, 2)
  855. XCTAssertEqual(created.count, 1)
  856. XCTAssertEqual(deleted.count, 1)
  857. if created.count >= 1 {
  858. XCTAssertEqual(created[0].syncIdentifier, doseData[0].syncIdentifier)
  859. }
  860. if deleted.count >= 1 {
  861. XCTAssertEqual(deleted[0].syncIdentifier, doseData[1].syncIdentifier)
  862. }
  863. }
  864. self.completion.fulfill()
  865. }
  866. wait(for: [completion], timeout: 2, enforceOrder: true)
  867. }
  868. private func addDoseData(_ doseData: [DoseDatum]) {
  869. cacheStore.managedObjectContext.performAndWait {
  870. for doseDatum in doseData {
  871. let object = CachedInsulinDeliveryObject(context: self.cacheStore.managedObjectContext)
  872. object.provenanceIdentifier = HKSource.default().bundleIdentifier
  873. object.hasLoopKitOrigin = true
  874. object.startDate = Date().addingTimeInterval(-.minutes(15))
  875. object.endDate = Date().addingTimeInterval(-.minutes(5))
  876. object.syncIdentifier = doseDatum.syncIdentifier
  877. object.value = 1.0
  878. object.reason = .bolus
  879. object.createdAt = Date()
  880. object.deletedAt = doseDatum.deleted ? Date() : nil
  881. object.manuallyEntered = false
  882. object.isSuspend = false
  883. self.cacheStore.save()
  884. }
  885. }
  886. }
  887. private struct DoseDatum {
  888. let syncIdentifier: String
  889. let deleted: Bool
  890. init(deleted: Bool = false) {
  891. self.syncIdentifier = UUID().uuidString
  892. self.deleted = deleted
  893. }
  894. }
  895. }