InsulinDeliveryStoreTests.swift 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  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 InsulinDeliveryStoreTests: PersistenceControllerTestCase {
  12. private 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. private 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. private 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. private 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. override func setUp() {
  47. super.setUp()
  48. let semaphore = DispatchSemaphore(value: 0)
  49. cacheStore.onReady { error in
  50. XCTAssertNil(error)
  51. semaphore.signal()
  52. }
  53. semaphore.wait()
  54. healthStore = HKHealthStoreMock()
  55. insulinDeliveryStore = InsulinDeliveryStore(healthStore: healthStore,
  56. cacheStore: cacheStore,
  57. cacheLength: .hours(1),
  58. provenanceIdentifier: HKSource.default().bundleIdentifier)
  59. }
  60. override func tearDown() {
  61. let semaphore = DispatchSemaphore(value: 0)
  62. insulinDeliveryStore.purgeAllDoseEntries(healthKitPredicate: HKQuery.predicateForObjects(from: HKSource.default())) { error in
  63. XCTAssertNil(error)
  64. semaphore.signal()
  65. }
  66. semaphore.wait()
  67. insulinDeliveryStore = nil
  68. healthStore = nil
  69. super.tearDown()
  70. }
  71. // MARK: - HealthKitSampleStore
  72. func testHealthKitQueryAnchorPersistence() {
  73. var observerQuery: HKObserverQueryMock? = nil
  74. var anchoredObjectQuery: HKAnchoredObjectQueryMock? = nil
  75. insulinDeliveryStore.createObserverQuery = { (sampleType, predicate, updateHandler) -> HKObserverQuery in
  76. observerQuery = HKObserverQueryMock(sampleType: sampleType, predicate: predicate, updateHandler: updateHandler)
  77. return observerQuery!
  78. }
  79. let authorizationCompletion = expectation(description: "authorization completion")
  80. insulinDeliveryStore.authorize { (result) in
  81. authorizationCompletion.fulfill()
  82. }
  83. waitForExpectations(timeout: 10)
  84. XCTAssertNotNil(observerQuery)
  85. let anchoredObjectQueryCreationExpectation = expectation(description: "anchored object query creation")
  86. insulinDeliveryStore.createAnchoredObjectQuery = { (sampleType, predicate, anchor, limit, resultsHandler) -> HKAnchoredObjectQuery in
  87. anchoredObjectQuery = HKAnchoredObjectQueryMock(type: sampleType, predicate: predicate, anchor: anchor, limit: limit, resultsHandler: resultsHandler)
  88. anchoredObjectQueryCreationExpectation.fulfill()
  89. return anchoredObjectQuery!
  90. }
  91. let observerQueryCompletionExpectation = expectation(description: "observer query completion")
  92. let observerQueryCompletionHandler = {
  93. observerQueryCompletionExpectation.fulfill()
  94. }
  95. // This simulates a signal marking the arrival of new HK Data.
  96. observerQuery!.updateHandler(observerQuery!, observerQueryCompletionHandler, nil)
  97. wait(for: [anchoredObjectQueryCreationExpectation], timeout: 10)
  98. // Trigger results handler for anchored object query
  99. let returnedAnchor = HKQueryAnchor(fromValue: 5)
  100. anchoredObjectQuery!.resultsHandler(anchoredObjectQuery!, [], [], returnedAnchor, nil)
  101. // Wait for observerQueryCompletionExpectation
  102. waitForExpectations(timeout: 10)
  103. XCTAssertNotNil(insulinDeliveryStore.queryAnchor)
  104. cacheStore.managedObjectContext.performAndWait {}
  105. // Create a new glucose store, and ensure it uses the last query anchor
  106. let newInsulinDeliveryStore = InsulinDeliveryStore(healthStore: healthStore,
  107. cacheStore: cacheStore,
  108. provenanceIdentifier: HKSource.default().bundleIdentifier)
  109. let newAuthorizationCompletion = expectation(description: "authorization completion")
  110. observerQuery = nil
  111. newInsulinDeliveryStore.createObserverQuery = { (sampleType, predicate, updateHandler) -> HKObserverQuery in
  112. observerQuery = HKObserverQueryMock(sampleType: sampleType, predicate: predicate, updateHandler: updateHandler)
  113. return observerQuery!
  114. }
  115. newInsulinDeliveryStore.authorize { (result) in
  116. newAuthorizationCompletion.fulfill()
  117. }
  118. waitForExpectations(timeout: 10)
  119. anchoredObjectQuery = nil
  120. let newAnchoredObjectQueryCreationExpectation = expectation(description: "new anchored object query creation")
  121. newInsulinDeliveryStore.createAnchoredObjectQuery = { (sampleType, predicate, anchor, limit, resultsHandler) -> HKAnchoredObjectQuery in
  122. anchoredObjectQuery = HKAnchoredObjectQueryMock(type: sampleType, predicate: predicate, anchor: anchor, limit: limit, resultsHandler: resultsHandler)
  123. newAnchoredObjectQueryCreationExpectation.fulfill()
  124. return anchoredObjectQuery!
  125. }
  126. // This simulates a signal marking the arrival of new HK Data.
  127. observerQuery!.updateHandler(observerQuery!, {}, nil)
  128. waitForExpectations(timeout: 10)
  129. // Assert new glucose store is querying with the last anchor that our HealthKit mock returned
  130. XCTAssertEqual(returnedAnchor, anchoredObjectQuery?.anchor)
  131. anchoredObjectQuery!.resultsHandler(anchoredObjectQuery!, [], [], returnedAnchor, nil)
  132. }
  133. // MARK: - Fetching
  134. func testGetDoseEntries() {
  135. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  136. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  137. switch result {
  138. case .failure(let error):
  139. XCTFail("Unexpected failure: \(error)")
  140. case .success:
  141. break
  142. }
  143. addDoseEntriesCompletion.fulfill()
  144. }
  145. waitForExpectations(timeout: 10)
  146. let getDoseEntries1Completion = expectation(description: "getDoseEntries1")
  147. insulinDeliveryStore.getDoseEntries() { result in
  148. switch result {
  149. case .failure(let error):
  150. XCTFail("Unexpected failure: \(error)")
  151. case .success(let entries):
  152. XCTAssertEqual(entries.count, 3)
  153. XCTAssertEqual(entries[0].type, self.entry1.type)
  154. XCTAssertEqual(entries[0].startDate, self.entry1.startDate)
  155. XCTAssertEqual(entries[0].endDate, self.entry1.endDate)
  156. XCTAssertEqual(entries[0].value, 0.015)
  157. XCTAssertEqual(entries[0].unit, .units)
  158. XCTAssertNil(entries[0].deliveredUnits)
  159. XCTAssertEqual(entries[0].description, self.entry1.description)
  160. XCTAssertEqual(entries[0].syncIdentifier, self.entry1.syncIdentifier)
  161. XCTAssertEqual(entries[0].scheduledBasalRate, self.entry1.scheduledBasalRate)
  162. XCTAssertEqual(entries[1].type, self.entry3.type)
  163. XCTAssertEqual(entries[1].startDate, self.entry3.startDate)
  164. XCTAssertEqual(entries[1].endDate, self.entry3.endDate)
  165. XCTAssertEqual(entries[1].value, self.entry3.value)
  166. XCTAssertEqual(entries[1].unit, self.entry3.unit)
  167. XCTAssertEqual(entries[1].deliveredUnits, self.entry3.deliveredUnits)
  168. XCTAssertEqual(entries[1].description, self.entry3.description)
  169. XCTAssertEqual(entries[1].syncIdentifier, self.entry3.syncIdentifier)
  170. XCTAssertEqual(entries[1].scheduledBasalRate, self.entry3.scheduledBasalRate)
  171. XCTAssertEqual(entries[2].type, self.entry2.type)
  172. XCTAssertEqual(entries[2].startDate, self.entry2.startDate)
  173. XCTAssertEqual(entries[2].endDate, self.entry2.endDate)
  174. XCTAssertEqual(entries[2].value, self.entry2.value)
  175. XCTAssertEqual(entries[2].unit, self.entry2.unit)
  176. XCTAssertEqual(entries[2].deliveredUnits, self.entry2.deliveredUnits)
  177. XCTAssertEqual(entries[2].description, self.entry2.description)
  178. XCTAssertEqual(entries[2].syncIdentifier, self.entry2.syncIdentifier)
  179. XCTAssertEqual(entries[2].scheduledBasalRate, self.entry2.scheduledBasalRate)
  180. }
  181. getDoseEntries1Completion.fulfill()
  182. }
  183. waitForExpectations(timeout: 10)
  184. let getDoseEntries2Completion = expectation(description: "getDoseEntries2")
  185. insulinDeliveryStore.getDoseEntries(start: Date(timeIntervalSinceNow: -.minutes(5)), end: Date(timeIntervalSinceNow: -.minutes(3))) { result in
  186. switch result {
  187. case .failure(let error):
  188. XCTFail("Unexpected failure: \(error)")
  189. case .success(let entries):
  190. XCTAssertEqual(entries.count, 1)
  191. XCTAssertEqual(entries[0].type, self.entry3.type)
  192. XCTAssertEqual(entries[0].startDate, self.entry3.startDate)
  193. XCTAssertEqual(entries[0].endDate, self.entry3.endDate)
  194. XCTAssertEqual(entries[0].value, self.entry3.value)
  195. XCTAssertEqual(entries[0].unit, self.entry3.unit)
  196. XCTAssertEqual(entries[0].deliveredUnits, self.entry3.deliveredUnits)
  197. XCTAssertEqual(entries[0].description, self.entry3.description)
  198. XCTAssertEqual(entries[0].syncIdentifier, self.entry3.syncIdentifier)
  199. XCTAssertEqual(entries[0].scheduledBasalRate, self.entry3.scheduledBasalRate)
  200. }
  201. getDoseEntries2Completion.fulfill()
  202. }
  203. waitForExpectations(timeout: 10)
  204. let purgeCachedInsulinDeliveryObjectsCompletion = expectation(description: "purgeCachedInsulinDeliveryObjects")
  205. insulinDeliveryStore.purgeCachedInsulinDeliveryObjects() { error in
  206. XCTAssertNil(error)
  207. purgeCachedInsulinDeliveryObjectsCompletion.fulfill()
  208. }
  209. waitForExpectations(timeout: 10)
  210. let getDoseEntries3Completion = expectation(description: "getDoseEntries3")
  211. insulinDeliveryStore.getDoseEntries() { result in
  212. switch result {
  213. case .failure(let error):
  214. XCTFail("Unexpected failure: \(error)")
  215. case .success(let entries):
  216. XCTAssertEqual(entries.count, 0)
  217. }
  218. getDoseEntries3Completion.fulfill()
  219. }
  220. waitForExpectations(timeout: 10)
  221. }
  222. func testLastBasalEndDate() {
  223. let getLastBasalEndDate1Completion = expectation(description: "getLastBasalEndDate1")
  224. insulinDeliveryStore.getLastBasalEndDate() { result in
  225. switch result {
  226. case .failure(let error):
  227. XCTFail("Unexpected failure: \(error)")
  228. case .success(let lastBasalEndDate):
  229. XCTAssertEqual(lastBasalEndDate, .distantPast)
  230. }
  231. getLastBasalEndDate1Completion.fulfill()
  232. }
  233. waitForExpectations(timeout: 10)
  234. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  235. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  236. switch result {
  237. case .failure(let error):
  238. XCTFail("Unexpected failure: \(error)")
  239. case .success:
  240. break
  241. }
  242. addDoseEntriesCompletion.fulfill()
  243. }
  244. waitForExpectations(timeout: 10)
  245. let getLastBasalEndDate2Completion = expectation(description: "getLastBasalEndDate2")
  246. insulinDeliveryStore.getLastBasalEndDate() { result in
  247. switch result {
  248. case .failure(let error):
  249. XCTFail("Unexpected failure: \(error)")
  250. case .success(let lastBasalEndDate):
  251. XCTAssertEqual(lastBasalEndDate, self.entry2.endDate)
  252. }
  253. getLastBasalEndDate2Completion.fulfill()
  254. }
  255. waitForExpectations(timeout: 10)
  256. let purgeCachedInsulinDeliveryObjectsCompletion = expectation(description: "purgeCachedInsulinDeliveryObjects")
  257. insulinDeliveryStore.purgeCachedInsulinDeliveryObjects() { error in
  258. XCTAssertNil(error)
  259. purgeCachedInsulinDeliveryObjectsCompletion.fulfill()
  260. }
  261. waitForExpectations(timeout: 10)
  262. let getLastBasalEndDate3Completion = expectation(description: "getLastBasalEndDate3")
  263. insulinDeliveryStore.getLastBasalEndDate() { result in
  264. switch result {
  265. case .failure(let error):
  266. XCTFail("Unexpected failure: \(error)")
  267. case .success(let lastBasalEndDate):
  268. XCTAssertEqual(lastBasalEndDate, .distantPast)
  269. }
  270. getLastBasalEndDate3Completion.fulfill()
  271. }
  272. waitForExpectations(timeout: 10)
  273. }
  274. // MARK: - Modification
  275. func testAddDoseEntries() {
  276. let addDoseEntries1Completion = expectation(description: "addDoseEntries1")
  277. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  278. switch result {
  279. case .failure(let error):
  280. XCTFail("Unexpected failure: \(error)")
  281. case .success:
  282. break
  283. }
  284. addDoseEntries1Completion.fulfill()
  285. }
  286. waitForExpectations(timeout: 10)
  287. let getDoseEntries1Completion = expectation(description: "getDoseEntries1")
  288. insulinDeliveryStore.getDoseEntries() { result in
  289. switch result {
  290. case .failure(let error):
  291. XCTFail("Unexpected failure: \(error)")
  292. case .success(let entries):
  293. XCTAssertEqual(entries.count, 3)
  294. XCTAssertEqual(entries[0].type, self.entry1.type)
  295. XCTAssertEqual(entries[0].startDate, self.entry1.startDate)
  296. XCTAssertEqual(entries[0].endDate, self.entry1.endDate)
  297. XCTAssertEqual(entries[0].value, 0.015)
  298. XCTAssertEqual(entries[0].unit, .units)
  299. XCTAssertNil(entries[0].deliveredUnits)
  300. XCTAssertEqual(entries[0].description, self.entry1.description)
  301. XCTAssertEqual(entries[0].syncIdentifier, self.entry1.syncIdentifier)
  302. XCTAssertEqual(entries[0].scheduledBasalRate, self.entry1.scheduledBasalRate)
  303. XCTAssertEqual(entries[1].type, self.entry3.type)
  304. XCTAssertEqual(entries[1].startDate, self.entry3.startDate)
  305. XCTAssertEqual(entries[1].endDate, self.entry3.endDate)
  306. XCTAssertEqual(entries[1].value, self.entry3.value)
  307. XCTAssertEqual(entries[1].unit, self.entry3.unit)
  308. XCTAssertEqual(entries[1].deliveredUnits, self.entry3.deliveredUnits)
  309. XCTAssertEqual(entries[1].description, self.entry3.description)
  310. XCTAssertEqual(entries[1].syncIdentifier, self.entry3.syncIdentifier)
  311. XCTAssertEqual(entries[1].scheduledBasalRate, self.entry3.scheduledBasalRate)
  312. XCTAssertEqual(entries[2].type, self.entry2.type)
  313. XCTAssertEqual(entries[2].startDate, self.entry2.startDate)
  314. XCTAssertEqual(entries[2].endDate, self.entry2.endDate)
  315. XCTAssertEqual(entries[2].value, self.entry2.value)
  316. XCTAssertEqual(entries[2].unit, self.entry2.unit)
  317. XCTAssertEqual(entries[2].deliveredUnits, self.entry2.deliveredUnits)
  318. XCTAssertEqual(entries[2].description, self.entry2.description)
  319. XCTAssertEqual(entries[2].syncIdentifier, self.entry2.syncIdentifier)
  320. XCTAssertEqual(entries[2].scheduledBasalRate, self.entry2.scheduledBasalRate)
  321. }
  322. getDoseEntries1Completion.fulfill()
  323. }
  324. waitForExpectations(timeout: 10)
  325. let addDoseEntries2Completion = expectation(description: "addDoseEntries2")
  326. insulinDeliveryStore.addDoseEntries([entry3, entry1, entry2], from: device, syncVersion: 2) { result in
  327. switch result {
  328. case .failure(let error):
  329. XCTFail("Unexpected failure: \(error)")
  330. case .success:
  331. break
  332. }
  333. addDoseEntries2Completion.fulfill()
  334. }
  335. waitForExpectations(timeout: 10)
  336. let getDoseEntries2Completion = expectation(description: "getDoseEntries2Completion")
  337. insulinDeliveryStore.getDoseEntries() { result in
  338. switch result {
  339. case .failure(let error):
  340. XCTFail("Unexpected failure: \(error)")
  341. case .success(let entries):
  342. XCTAssertEqual(entries.count, 3)
  343. XCTAssertEqual(entries[0].type, self.entry1.type)
  344. XCTAssertEqual(entries[0].startDate, self.entry1.startDate)
  345. XCTAssertEqual(entries[0].endDate, self.entry1.endDate)
  346. XCTAssertEqual(entries[0].value, 0.015)
  347. XCTAssertEqual(entries[0].unit, .units)
  348. XCTAssertNil(entries[0].deliveredUnits)
  349. XCTAssertEqual(entries[0].description, self.entry1.description)
  350. XCTAssertEqual(entries[0].syncIdentifier, self.entry1.syncIdentifier)
  351. XCTAssertEqual(entries[0].scheduledBasalRate, self.entry1.scheduledBasalRate)
  352. XCTAssertEqual(entries[1].type, self.entry3.type)
  353. XCTAssertEqual(entries[1].startDate, self.entry3.startDate)
  354. XCTAssertEqual(entries[1].endDate, self.entry3.endDate)
  355. XCTAssertEqual(entries[1].value, self.entry3.value)
  356. XCTAssertEqual(entries[1].unit, self.entry3.unit)
  357. XCTAssertEqual(entries[1].deliveredUnits, self.entry3.deliveredUnits)
  358. XCTAssertEqual(entries[1].description, self.entry3.description)
  359. XCTAssertEqual(entries[1].syncIdentifier, self.entry3.syncIdentifier)
  360. XCTAssertEqual(entries[1].scheduledBasalRate, self.entry3.scheduledBasalRate)
  361. XCTAssertEqual(entries[2].type, self.entry2.type)
  362. XCTAssertEqual(entries[2].startDate, self.entry2.startDate)
  363. XCTAssertEqual(entries[2].endDate, self.entry2.endDate)
  364. XCTAssertEqual(entries[2].value, self.entry2.value)
  365. XCTAssertEqual(entries[2].unit, self.entry2.unit)
  366. XCTAssertEqual(entries[2].deliveredUnits, self.entry2.deliveredUnits)
  367. XCTAssertEqual(entries[2].description, self.entry2.description)
  368. XCTAssertEqual(entries[2].syncIdentifier, self.entry2.syncIdentifier)
  369. XCTAssertEqual(entries[2].scheduledBasalRate, self.entry2.scheduledBasalRate)
  370. }
  371. getDoseEntries2Completion.fulfill()
  372. }
  373. waitForExpectations(timeout: 10)
  374. }
  375. func testAddDoseEntriesEmpty() {
  376. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  377. insulinDeliveryStore.addDoseEntries([], from: device, syncVersion: 2) { result in
  378. switch result {
  379. case .failure(let error):
  380. XCTFail("Unexpected failure: \(error)")
  381. case .success:
  382. break
  383. }
  384. addDoseEntriesCompletion.fulfill()
  385. }
  386. waitForExpectations(timeout: 10)
  387. }
  388. func testAddDoseEntriesNotification() {
  389. let doseEntriesDidChangeCompletion = expectation(description: "doseEntriesDidChange")
  390. let observer = NotificationCenter.default.addObserver(forName: InsulinDeliveryStore.doseEntriesDidChange, object: insulinDeliveryStore, queue: nil) { notification in
  391. let updateSource = notification.userInfo?[HealthKitSampleStore.notificationUpdateSourceKey] as? Int
  392. XCTAssertEqual(updateSource, UpdateSource.changedInApp.rawValue)
  393. doseEntriesDidChangeCompletion.fulfill()
  394. }
  395. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  396. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  397. switch result {
  398. case .failure(let error):
  399. XCTFail("Unexpected failure: \(error)")
  400. case .success:
  401. break
  402. }
  403. addDoseEntriesCompletion.fulfill()
  404. }
  405. wait(for: [doseEntriesDidChangeCompletion, addDoseEntriesCompletion], timeout: 10, enforceOrder: true)
  406. NotificationCenter.default.removeObserver(observer)
  407. }
  408. // MARK: - Cache Management
  409. func testEarliestCacheDate() {
  410. XCTAssertEqual(insulinDeliveryStore.earliestCacheDate.timeIntervalSinceNow, -.hours(1), accuracy: 1)
  411. }
  412. func testPurgeAllDoseEntries() {
  413. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  414. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], 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. let getDoseEntries1Completion = expectation(description: "getDoseEntries1")
  425. insulinDeliveryStore.getDoseEntries() { result in
  426. switch result {
  427. case .failure(let error):
  428. XCTFail("Unexpected failure: \(error)")
  429. case .success(let entries):
  430. XCTAssertEqual(entries.count, 3)
  431. }
  432. getDoseEntries1Completion.fulfill()
  433. }
  434. waitForExpectations(timeout: 10)
  435. let purgeAllDoseEntriesCompletion = expectation(description: "purgeAllDoseEntries")
  436. insulinDeliveryStore.purgeAllDoseEntries(healthKitPredicate: HKQuery.predicateForObjects(from: HKSource.default())) { error in
  437. XCTAssertNil(error)
  438. purgeAllDoseEntriesCompletion.fulfill()
  439. }
  440. waitForExpectations(timeout: 10)
  441. let getDoseEntries2Completion = expectation(description: "getDoseEntries2")
  442. insulinDeliveryStore.getDoseEntries() { result in
  443. switch result {
  444. case .failure(let error):
  445. XCTFail("Unexpected failure: \(error)")
  446. case .success(let entries):
  447. XCTAssertEqual(entries.count, 0)
  448. }
  449. getDoseEntries2Completion.fulfill()
  450. }
  451. waitForExpectations(timeout: 10)
  452. }
  453. func testPurgeExpiredGlucoseObjects() {
  454. let expiredEntry = DoseEntry(type: .bolus,
  455. startDate: Date(timeIntervalSinceNow: -.hours(3)),
  456. endDate: Date(timeIntervalSinceNow: -.hours(2)),
  457. value: 3.0,
  458. unit: .units,
  459. deliveredUnits: nil,
  460. syncIdentifier: "7530B8CA-827A-4DE8-ADE3-9E10FF80A4A9",
  461. scheduledBasalRate: nil)
  462. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  463. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3, expiredEntry], from: device, syncVersion: 2) { result in
  464. switch result {
  465. case .failure(let error):
  466. XCTFail("Unexpected failure: \(error)")
  467. case .success:
  468. break
  469. }
  470. addDoseEntriesCompletion.fulfill()
  471. }
  472. waitForExpectations(timeout: 10)
  473. let getDoseEntriesCompletion = expectation(description: "getDoseEntries")
  474. insulinDeliveryStore.getDoseEntries() { result in
  475. switch result {
  476. case .failure(let error):
  477. XCTFail("Unexpected failure: \(error)")
  478. case .success(let entries):
  479. XCTAssertEqual(entries.count, 3)
  480. }
  481. getDoseEntriesCompletion.fulfill()
  482. }
  483. waitForExpectations(timeout: 10)
  484. }
  485. func testPurgeCachedInsulinDeliveryObjects() {
  486. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  487. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  488. switch result {
  489. case .failure(let error):
  490. XCTFail("Unexpected failure: \(error)")
  491. case .success:
  492. break
  493. }
  494. addDoseEntriesCompletion.fulfill()
  495. }
  496. waitForExpectations(timeout: 10)
  497. let getDoseEntries1Completion = expectation(description: "getDoseEntries1")
  498. insulinDeliveryStore.getDoseEntries() { result in
  499. switch result {
  500. case .failure(let error):
  501. XCTFail("Unexpected failure: \(error)")
  502. case .success(let entries):
  503. XCTAssertEqual(entries.count, 3)
  504. }
  505. getDoseEntries1Completion.fulfill()
  506. }
  507. waitForExpectations(timeout: 10)
  508. let purgeCachedInsulinDeliveryObjects1Completion = expectation(description: "purgeCachedInsulinDeliveryObjects1")
  509. insulinDeliveryStore.purgeCachedInsulinDeliveryObjects(before: Date(timeIntervalSinceNow: -.minutes(5))) { error in
  510. XCTAssertNil(error)
  511. purgeCachedInsulinDeliveryObjects1Completion.fulfill()
  512. }
  513. waitForExpectations(timeout: 10)
  514. let getDoseEntries2Completion = expectation(description: "getDoseEntries2")
  515. insulinDeliveryStore.getDoseEntries() { result in
  516. switch result {
  517. case .failure(let error):
  518. XCTFail("Unexpected failure: \(error)")
  519. case .success(let entries):
  520. XCTAssertEqual(entries.count, 2)
  521. }
  522. getDoseEntries2Completion.fulfill()
  523. }
  524. waitForExpectations(timeout: 10)
  525. let purgeCachedInsulinDeliveryObjects2Completion = expectation(description: "purgeCachedInsulinDeliveryObjects2")
  526. insulinDeliveryStore.purgeCachedInsulinDeliveryObjects() { error in
  527. XCTAssertNil(error)
  528. purgeCachedInsulinDeliveryObjects2Completion.fulfill()
  529. }
  530. waitForExpectations(timeout: 10)
  531. let getDoseEntries3Completion = expectation(description: "getDoseEntries3")
  532. insulinDeliveryStore.getDoseEntries() { result in
  533. switch result {
  534. case .failure(let error):
  535. XCTFail("Unexpected failure: \(error)")
  536. case .success(let entries):
  537. XCTAssertEqual(entries.count, 0)
  538. }
  539. getDoseEntries3Completion.fulfill()
  540. }
  541. waitForExpectations(timeout: 10)
  542. }
  543. func testPurgeCachedInsulinDeliveryObjectsNotification() {
  544. let addDoseEntriesCompletion = expectation(description: "addDoseEntries")
  545. insulinDeliveryStore.addDoseEntries([entry1, entry2, entry3], from: device, syncVersion: 2) { result in
  546. switch result {
  547. case .failure(let error):
  548. XCTFail("Unexpected failure: \(error)")
  549. case .success:
  550. break
  551. }
  552. addDoseEntriesCompletion.fulfill()
  553. }
  554. waitForExpectations(timeout: 10)
  555. let doseEntriesDidChangeCompletion = expectation(description: "doseEntriesDidChange")
  556. let observer = NotificationCenter.default.addObserver(forName: InsulinDeliveryStore.doseEntriesDidChange, object: insulinDeliveryStore, queue: nil) { notification in
  557. let updateSource = notification.userInfo?[HealthKitSampleStore.notificationUpdateSourceKey] as? Int
  558. XCTAssertEqual(updateSource, UpdateSource.changedInApp.rawValue)
  559. doseEntriesDidChangeCompletion.fulfill()
  560. }
  561. let purgeCachedInsulinDeliveryObjectsCompletion = expectation(description: "purgeCachedInsulinDeliveryObjects")
  562. insulinDeliveryStore.purgeCachedInsulinDeliveryObjects() { error in
  563. XCTAssertNil(error)
  564. purgeCachedInsulinDeliveryObjectsCompletion.fulfill()
  565. }
  566. wait(for: [doseEntriesDidChangeCompletion, purgeCachedInsulinDeliveryObjectsCompletion], timeout: 10, enforceOrder: true)
  567. NotificationCenter.default.removeObserver(observer)
  568. }
  569. }