GlucoseStoreTests.swift 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. //
  2. // GlucoseStoreTests.swift
  3. // LoopKitHostedTests
  4. //
  5. // Created by Darin Krauss on 10/12/20.
  6. // Copyright © 2020 LoopKit Authors. All rights reserved.
  7. //
  8. import XCTest
  9. import HealthKit
  10. @testable import LoopKit
  11. class GlucoseStoreTestsBase: PersistenceControllerTestCase, GlucoseStoreDelegate {
  12. private static let device = HKDevice(name: "NAME", manufacturer: "MANUFACTURER", model: "MODEL", hardwareVersion: "HARDWAREVERSION", firmwareVersion: "FIRMWAREVERSION", softwareVersion: "SOFTWAREVERSION", localIdentifier: "LOCALIDENTIFIER", udiDeviceIdentifier: "UDIDEVICEIDENTIFIER")
  13. internal let sample1 = NewGlucoseSample(date: Date(timeIntervalSinceNow: -.minutes(6)),
  14. quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 123.4),
  15. condition: nil,
  16. trend: nil,
  17. trendRate: nil,
  18. isDisplayOnly: true,
  19. wasUserEntered: false,
  20. syncIdentifier: "1925558F-E98F-442F-BBA6-F6F75FB4FD91",
  21. syncVersion: 2,
  22. device: device)
  23. internal let sample2 = NewGlucoseSample(date: Date(timeIntervalSinceNow: -.minutes(2)),
  24. quantity: HKQuantity(unit: .millimolesPerLiter, doubleValue: 7.4),
  25. condition: nil,
  26. trend: .flat,
  27. trendRate: HKQuantity(unit: .millimolesPerLiterPerMinute, doubleValue: 0.0),
  28. isDisplayOnly: false,
  29. wasUserEntered: true,
  30. syncIdentifier: "535F103C-3DFE-48F2-B15A-47313191E7B7",
  31. syncVersion: 3,
  32. device: device)
  33. internal let sample3 = NewGlucoseSample(date: Date(timeIntervalSinceNow: -.minutes(4)),
  34. quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 400.0),
  35. condition: .aboveRange,
  36. trend: .upUpUp,
  37. trendRate: HKQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: 4.2),
  38. isDisplayOnly: false,
  39. wasUserEntered: false,
  40. syncIdentifier: "E1624D2B-A971-41B8-B8A0-3A8212AC3D71",
  41. syncVersion: 4,
  42. device: device)
  43. var healthStore: HKHealthStoreMock!
  44. var glucoseStore: GlucoseStore!
  45. var delegateCompletion: XCTestExpectation?
  46. var authorizationStatus: HKAuthorizationStatus = .notDetermined
  47. override func setUp() {
  48. super.setUp()
  49. let semaphore = DispatchSemaphore(value: 0)
  50. cacheStore.onReady { error in
  51. XCTAssertNil(error)
  52. semaphore.signal()
  53. }
  54. semaphore.wait()
  55. healthStore = HKHealthStoreMock()
  56. healthStore.authorizationStatus = authorizationStatus
  57. glucoseStore = GlucoseStore(healthStore: healthStore,
  58. cacheStore: cacheStore,
  59. cacheLength: .hours(1),
  60. observationInterval: .minutes(30),
  61. provenanceIdentifier: HKSource.default().bundleIdentifier)
  62. glucoseStore.delegate = self
  63. }
  64. override func tearDown() {
  65. let semaphore = DispatchSemaphore(value: 0)
  66. glucoseStore.purgeAllGlucoseSamples(healthKitPredicate: HKQuery.predicateForObjects(from: HKSource.default())) { error in
  67. XCTAssertNil(error)
  68. semaphore.signal()
  69. }
  70. semaphore.wait()
  71. delegateCompletion = nil
  72. glucoseStore = nil
  73. healthStore = nil
  74. super.tearDown()
  75. }
  76. // MARK: - GlucoseStoreDelegate
  77. func glucoseStoreHasUpdatedGlucoseData(_ glucoseStore: GlucoseStore) {
  78. delegateCompletion?.fulfill()
  79. }
  80. }
  81. class GlucoseStoreTestsAuthorizationRequired: GlucoseStoreTestsBase {
  82. func testObserverQueryStartup() {
  83. XCTAssert(glucoseStore.authorizationRequired);
  84. XCTAssertNil(glucoseStore.observerQuery);
  85. let observerQueryCreated = expectation(description: "observer query created")
  86. glucoseStore.createObserverQuery = { (sampleType, predicate, updateHandler) -> HKObserverQuery in
  87. let observerQuery = HKObserverQueryMock(sampleType: sampleType, predicate: predicate, updateHandler: updateHandler)
  88. observerQueryCreated.fulfill()
  89. return observerQuery
  90. }
  91. let authorizationCompletion = expectation(description: "authorization completion")
  92. glucoseStore.authorize { (result) in
  93. authorizationCompletion.fulfill()
  94. }
  95. waitForExpectations(timeout: 10)
  96. XCTAssertNotNil(glucoseStore.observerQuery);
  97. }
  98. }
  99. class GlucoseStoreStoreTestsAuthorized: GlucoseStoreTestsBase {
  100. override func setUp() {
  101. authorizationStatus = .sharingAuthorized
  102. super.setUp()
  103. }
  104. func testObserverQueryStartup() {
  105. // Check that an observer query is registered when authorization is already determined.
  106. XCTAssertFalse(glucoseStore.authorizationRequired);
  107. let observerQueryCreated = expectation(description: "observer query created")
  108. glucoseStore.createObserverQuery = { (sampleType, predicate, updateHandler) -> HKObserverQuery in
  109. let observerQuery = HKObserverQueryMock(sampleType: sampleType, predicate: predicate, updateHandler: updateHandler)
  110. observerQueryCreated.fulfill()
  111. return observerQuery
  112. }
  113. waitForExpectations(timeout: 2)
  114. }
  115. }
  116. class GlucoseStoreTests: GlucoseStoreTestsBase {
  117. override func setUp() {
  118. authorizationStatus = .sharingAuthorized
  119. super.setUp()
  120. }
  121. // MARK: - HealthKitSampleStore
  122. func testHealthKitQueryAnchorPersistence() {
  123. var observerQuery: HKObserverQueryMock? = nil
  124. var anchoredObjectQuery: HKAnchoredObjectQueryMock? = nil
  125. // Check that an observer query was registered even without authorize() being called.
  126. XCTAssertFalse(glucoseStore.authorizationRequired);
  127. let observerQueryCreated = expectation(description: "observer query created")
  128. glucoseStore.createObserverQuery = { (sampleType, predicate, updateHandler) -> HKObserverQuery in
  129. observerQuery = HKObserverQueryMock(sampleType: sampleType, predicate: predicate, updateHandler: updateHandler)
  130. observerQueryCreated.fulfill()
  131. return observerQuery!
  132. }
  133. let authorizationCompletion = expectation(description: "authorization completion")
  134. glucoseStore.authorize { (result) in
  135. authorizationCompletion.fulfill()
  136. }
  137. waitForExpectations(timeout: 10)
  138. XCTAssertNotNil(observerQuery)
  139. let anchoredObjectQueryCreationExpectation = expectation(description: "anchored object query creation")
  140. glucoseStore.createAnchoredObjectQuery = { (sampleType, predicate, anchor, limit, resultsHandler) -> HKAnchoredObjectQuery in
  141. anchoredObjectQuery = HKAnchoredObjectQueryMock(type: sampleType, predicate: predicate, anchor: anchor, limit: limit, resultsHandler: resultsHandler)
  142. anchoredObjectQueryCreationExpectation.fulfill()
  143. return anchoredObjectQuery!
  144. }
  145. let observerQueryCompletionExpectation = expectation(description: "observer query completion")
  146. let observerQueryCompletionHandler = {
  147. observerQueryCompletionExpectation.fulfill()
  148. }
  149. // This simulates a signal marking the arrival of new HK Data.
  150. observerQuery!.updateHandler(observerQuery!, observerQueryCompletionHandler, nil)
  151. wait(for: [anchoredObjectQueryCreationExpectation], timeout: 10)
  152. // Trigger results handler for anchored object query
  153. let returnedAnchor = HKQueryAnchor(fromValue: 5)
  154. anchoredObjectQuery!.resultsHandler(anchoredObjectQuery!, [], [], returnedAnchor, nil)
  155. // Wait for observerQueryCompletionExpectation
  156. waitForExpectations(timeout: 10)
  157. XCTAssertNotNil(glucoseStore.queryAnchor)
  158. cacheStore.managedObjectContext.performAndWait {}
  159. // Create a new glucose store, and ensure it uses the last query anchor
  160. let newGlucoseStore = GlucoseStore(healthStore: healthStore,
  161. cacheStore: cacheStore,
  162. provenanceIdentifier: HKSource.default().bundleIdentifier)
  163. let newAuthorizationCompletion = expectation(description: "authorization completion")
  164. observerQuery = nil
  165. let newObserverQueryCreated = expectation(description: "new observer query created")
  166. newGlucoseStore.createObserverQuery = { (sampleType, predicate, updateHandler) -> HKObserverQuery in
  167. observerQuery = HKObserverQueryMock(sampleType: sampleType, predicate: predicate, updateHandler: updateHandler)
  168. newObserverQueryCreated.fulfill()
  169. return observerQuery!
  170. }
  171. newGlucoseStore.authorize { (result) in
  172. newAuthorizationCompletion.fulfill()
  173. }
  174. waitForExpectations(timeout: 10)
  175. anchoredObjectQuery = nil
  176. let newAnchoredObjectQueryCreationExpectation = expectation(description: "new anchored object query creation")
  177. newGlucoseStore.createAnchoredObjectQuery = { (sampleType, predicate, anchor, limit, resultsHandler) -> HKAnchoredObjectQuery in
  178. anchoredObjectQuery = HKAnchoredObjectQueryMock(type: sampleType, predicate: predicate, anchor: anchor, limit: limit, resultsHandler: resultsHandler)
  179. newAnchoredObjectQueryCreationExpectation.fulfill()
  180. return anchoredObjectQuery!
  181. }
  182. // This simulates a signal marking the arrival of new HK Data.
  183. observerQuery!.updateHandler(observerQuery!, {}, nil)
  184. waitForExpectations(timeout: 10)
  185. // Assert new glucose store is querying with the last anchor that our HealthKit mock returned
  186. XCTAssertEqual(returnedAnchor, anchoredObjectQuery?.anchor)
  187. anchoredObjectQuery!.resultsHandler(anchoredObjectQuery!, [], [], returnedAnchor, nil)
  188. }
  189. // MARK: - Fetching
  190. func testGetGlucoseSamples() {
  191. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  192. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  193. switch result {
  194. case .failure(let error):
  195. XCTFail("Unexpected failure: \(error)")
  196. case .success(let samples):
  197. XCTAssertEqual(samples.count, 3)
  198. }
  199. addGlucoseSamplesCompletion.fulfill()
  200. }
  201. waitForExpectations(timeout: 10)
  202. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  203. glucoseStore.getGlucoseSamples() { result in
  204. switch result {
  205. case .failure(let error):
  206. XCTFail("Unexpected failure: \(error)")
  207. case .success(let samples):
  208. XCTAssertEqual(samples.count, 3)
  209. XCTAssertNotNil(samples[0].uuid)
  210. XCTAssertNil(samples[0].healthKitEligibleDate)
  211. assertEqualSamples(samples[0], self.sample1)
  212. XCTAssertNotNil(samples[1].uuid)
  213. XCTAssertNil(samples[1].healthKitEligibleDate)
  214. assertEqualSamples(samples[1], self.sample3)
  215. XCTAssertNotNil(samples[2].uuid)
  216. XCTAssertNil(samples[2].healthKitEligibleDate)
  217. assertEqualSamples(samples[2], self.sample2)
  218. }
  219. getGlucoseSamples1Completion.fulfill()
  220. }
  221. waitForExpectations(timeout: 10)
  222. let getGlucoseSamples2Completion = expectation(description: "getGlucoseSamples2")
  223. glucoseStore.getGlucoseSamples(start: Date(timeIntervalSinceNow: -.minutes(5)), end: Date(timeIntervalSinceNow: -.minutes(3))) { result in
  224. switch result {
  225. case .failure(let error):
  226. XCTFail("Unexpected failure: \(error)")
  227. case .success(let samples):
  228. XCTAssertEqual(samples.count, 1)
  229. XCTAssertNotNil(samples[0].uuid)
  230. XCTAssertNil(samples[0].healthKitEligibleDate)
  231. assertEqualSamples(samples[0], self.sample3)
  232. }
  233. getGlucoseSamples2Completion.fulfill()
  234. }
  235. waitForExpectations(timeout: 10)
  236. let purgeCachedGlucoseObjectsCompletion = expectation(description: "purgeCachedGlucoseObjects")
  237. glucoseStore.purgeCachedGlucoseObjects() { error in
  238. XCTAssertNil(error)
  239. purgeCachedGlucoseObjectsCompletion.fulfill()
  240. }
  241. waitForExpectations(timeout: 10)
  242. let getGlucoseSamples3Completion = expectation(description: "getGlucoseSamples3")
  243. glucoseStore.getGlucoseSamples() { result in
  244. switch result {
  245. case .failure(let error):
  246. XCTFail("Unexpected failure: \(error)")
  247. case .success(let samples):
  248. XCTAssertEqual(samples.count, 0)
  249. }
  250. getGlucoseSamples3Completion.fulfill()
  251. }
  252. waitForExpectations(timeout: 10)
  253. }
  254. enum Error: Swift.Error { case arbitrary }
  255. func testGetGlucoseSamplesDelayedHealthKitStorage() {
  256. glucoseStore.healthKitStorageDelay = .minutes(5)
  257. var hkobjects = [HKObject]()
  258. healthStore.setSaveHandler { o, _, _ in hkobjects = o }
  259. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  260. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  261. switch result {
  262. case .failure(let error):
  263. XCTFail("Unexpected failure: \(error)")
  264. case .success(let samples):
  265. XCTAssertEqual(samples.count, 3)
  266. }
  267. addGlucoseSamplesCompletion.fulfill()
  268. }
  269. waitForExpectations(timeout: 10)
  270. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  271. glucoseStore.getGlucoseSamples() { result in
  272. switch result {
  273. case .failure(let error):
  274. XCTFail("Unexpected failure: \(error)")
  275. case .success(let samples):
  276. XCTAssertEqual(samples.count, 3)
  277. // HealthKit storage is deferred, so the second 2 UUIDs are nil
  278. XCTAssertNotNil(samples[0].uuid)
  279. XCTAssertNil(samples[0].healthKitEligibleDate)
  280. assertEqualSamples(samples[0], self.sample1)
  281. XCTAssertNil(samples[1].uuid)
  282. XCTAssertNotNil(samples[1].healthKitEligibleDate)
  283. assertEqualSamples(samples[1], self.sample3)
  284. XCTAssertNil(samples[2].uuid)
  285. XCTAssertNotNil(samples[2].healthKitEligibleDate)
  286. assertEqualSamples(samples[2], self.sample2)
  287. }
  288. getGlucoseSamples1Completion.fulfill()
  289. }
  290. waitForExpectations(timeout: 10)
  291. let stored = hkobjects[0] as! HKQuantitySample
  292. XCTAssertEqual(sample1.quantitySample.quantity, stored.quantity)
  293. }
  294. func testGetGlucoseSamplesErrorHealthKitStorage() {
  295. healthStore.saveError = Error.arbitrary
  296. var hkobjects = [HKObject]()
  297. healthStore.setSaveHandler { o, _, _ in hkobjects = o }
  298. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  299. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  300. switch result {
  301. case .failure(let error):
  302. XCTFail("Unexpected failure: \(error)")
  303. case .success(let samples):
  304. XCTAssertEqual(samples.count, 3)
  305. }
  306. addGlucoseSamplesCompletion.fulfill()
  307. }
  308. waitForExpectations(timeout: 10)
  309. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  310. glucoseStore.getGlucoseSamples() { result in
  311. switch result {
  312. case .failure(let error):
  313. XCTFail("Unexpected failure: \(error)")
  314. case .success(let samples):
  315. XCTAssertEqual(samples.count, 3)
  316. // HealthKit storage is deferred, so the second 2 UUIDs are nil
  317. XCTAssertNil(samples[0].uuid)
  318. XCTAssertNotNil(samples[0].healthKitEligibleDate)
  319. assertEqualSamples(samples[0], self.sample1)
  320. XCTAssertNil(samples[1].uuid)
  321. XCTAssertNotNil(samples[1].healthKitEligibleDate)
  322. assertEqualSamples(samples[1], self.sample3)
  323. XCTAssertNil(samples[2].uuid)
  324. XCTAssertNotNil(samples[2].healthKitEligibleDate)
  325. assertEqualSamples(samples[2], self.sample2)
  326. }
  327. getGlucoseSamples1Completion.fulfill()
  328. }
  329. waitForExpectations(timeout: 10)
  330. XCTAssertEqual(3, hkobjects.count)
  331. }
  332. func testGetGlucoseSamplesDeniedHealthKitStorage() {
  333. healthStore.authorizationStatus = .sharingDenied
  334. var hkobjects = [HKObject]()
  335. healthStore.setSaveHandler { o, _, _ in hkobjects = o }
  336. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  337. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  338. switch result {
  339. case .failure(let error):
  340. XCTFail("Unexpected failure: \(error)")
  341. case .success(let samples):
  342. XCTAssertEqual(samples.count, 3)
  343. }
  344. addGlucoseSamplesCompletion.fulfill()
  345. }
  346. waitForExpectations(timeout: 10)
  347. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  348. glucoseStore.getGlucoseSamples() { result in
  349. switch result {
  350. case .failure(let error):
  351. XCTFail("Unexpected failure: \(error)")
  352. case .success(let samples):
  353. XCTAssertEqual(samples.count, 3)
  354. // HealthKit storage is denied, so all UUIDs are nil
  355. XCTAssertNil(samples[0].uuid)
  356. XCTAssertNil(samples[0].healthKitEligibleDate)
  357. assertEqualSamples(samples[0], self.sample1)
  358. XCTAssertNil(samples[1].uuid)
  359. XCTAssertNil(samples[1].healthKitEligibleDate)
  360. assertEqualSamples(samples[1], self.sample3)
  361. XCTAssertNil(samples[2].uuid)
  362. XCTAssertNil(samples[2].healthKitEligibleDate)
  363. assertEqualSamples(samples[2], self.sample2)
  364. }
  365. getGlucoseSamples1Completion.fulfill()
  366. }
  367. waitForExpectations(timeout: 10)
  368. XCTAssertTrue(hkobjects.isEmpty)
  369. }
  370. func testGetGlucoseSamplesSomeDeniedHealthKitStorage() {
  371. glucoseStore.healthKitStorageDelay = 0
  372. var hkobjects = [HKObject]()
  373. healthStore.setSaveHandler { o, _, _ in hkobjects = o }
  374. let addGlucoseSamples1Completion = expectation(description: "addGlucoseSamples1")
  375. // Authorized
  376. glucoseStore.addGlucoseSamples([sample1]) { result in
  377. switch result {
  378. case .failure(let error):
  379. XCTFail("Unexpected failure: \(error)")
  380. case .success(let samples):
  381. XCTAssertEqual(samples.count, 1)
  382. }
  383. addGlucoseSamples1Completion.fulfill()
  384. }
  385. waitForExpectations(timeout: 10)
  386. XCTAssertEqual(1, hkobjects.count)
  387. hkobjects = []
  388. healthStore.authorizationStatus = .sharingDenied
  389. let addGlucoseSamples2Completion = expectation(description: "addGlucoseSamples2")
  390. // Denied
  391. glucoseStore.addGlucoseSamples([sample2]) { result in
  392. switch result {
  393. case .failure(let error):
  394. XCTFail("Unexpected failure: \(error)")
  395. case .success(let samples):
  396. XCTAssertEqual(samples.count, 1)
  397. }
  398. addGlucoseSamples2Completion.fulfill()
  399. }
  400. waitForExpectations(timeout: 10)
  401. XCTAssertEqual(0, hkobjects.count)
  402. hkobjects = []
  403. healthStore.authorizationStatus = .sharingAuthorized
  404. let addGlucoseSamples3Completion = expectation(description: "addGlucoseSamples3")
  405. // Authorized
  406. glucoseStore.addGlucoseSamples([sample3]) { result in
  407. switch result {
  408. case .failure(let error):
  409. XCTFail("Unexpected failure: \(error)")
  410. case .success(let samples):
  411. XCTAssertEqual(samples.count, 1)
  412. }
  413. addGlucoseSamples3Completion.fulfill()
  414. }
  415. waitForExpectations(timeout: 10)
  416. XCTAssertEqual(1, hkobjects.count)
  417. hkobjects = []
  418. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  419. glucoseStore.getGlucoseSamples() { result in
  420. switch result {
  421. case .failure(let error):
  422. XCTFail("Unexpected failure: \(error)")
  423. case .success(let samples):
  424. XCTAssertEqual(samples.count, 3)
  425. XCTAssertNotNil(samples[0].uuid)
  426. XCTAssertNil(samples[0].healthKitEligibleDate)
  427. assertEqualSamples(samples[0], self.sample1)
  428. XCTAssertNotNil(samples[1].uuid)
  429. XCTAssertNil(samples[1].healthKitEligibleDate)
  430. assertEqualSamples(samples[1], self.sample3)
  431. XCTAssertNil(samples[2].uuid)
  432. XCTAssertNil(samples[2].healthKitEligibleDate)
  433. assertEqualSamples(samples[2], self.sample2)
  434. }
  435. getGlucoseSamples1Completion.fulfill()
  436. }
  437. waitForExpectations(timeout: 10)
  438. }
  439. func testLatestGlucose() {
  440. XCTAssertNil(glucoseStore.latestGlucose)
  441. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  442. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  443. switch result {
  444. case .failure(let error):
  445. XCTFail("Unexpected failure: \(error)")
  446. case .success(let samples):
  447. XCTAssertEqual(samples.count, 3)
  448. assertEqualSamples(samples[0], self.sample1)
  449. assertEqualSamples(samples[1], self.sample2)
  450. assertEqualSamples(samples[2], self.sample3)
  451. }
  452. addGlucoseSamplesCompletion.fulfill()
  453. }
  454. waitForExpectations(timeout: 10)
  455. XCTAssertNotNil(glucoseStore.latestGlucose)
  456. XCTAssertEqual(glucoseStore.latestGlucose?.startDate, sample2.date)
  457. XCTAssertEqual(glucoseStore.latestGlucose?.endDate, sample2.date)
  458. XCTAssertEqual(glucoseStore.latestGlucose?.quantity, sample2.quantity)
  459. XCTAssertEqual(glucoseStore.latestGlucose?.provenanceIdentifier, HKSource.default().bundleIdentifier)
  460. XCTAssertEqual(glucoseStore.latestGlucose?.isDisplayOnly, sample2.isDisplayOnly)
  461. XCTAssertEqual(glucoseStore.latestGlucose?.wasUserEntered, sample2.wasUserEntered)
  462. let purgeCachedGlucoseObjectsCompletion = expectation(description: "purgeCachedGlucoseObjects")
  463. glucoseStore.purgeCachedGlucoseObjects() { error in
  464. XCTAssertNil(error)
  465. purgeCachedGlucoseObjectsCompletion.fulfill()
  466. }
  467. waitForExpectations(timeout: 10)
  468. XCTAssertNil(glucoseStore.latestGlucose)
  469. }
  470. // MARK: - Modification
  471. func testAddGlucoseSamples() {
  472. let addGlucoseSamples1Completion = expectation(description: "addGlucoseSamples1")
  473. glucoseStore.addGlucoseSamples([sample1, sample2, sample3, sample1, sample2, sample3]) { result in
  474. switch result {
  475. case .failure(let error):
  476. XCTFail("Unexpected failure: \(error)")
  477. case .success(let samples):
  478. XCTAssertEqual(samples.count, 3)
  479. // Note: the HealthKit UUID is no longer updated before being returned as a result of addGlucoseSamples.
  480. XCTAssertNil(samples[0].uuid)
  481. XCTAssertNotNil(samples[0].healthKitEligibleDate)
  482. assertEqualSamples(samples[0], self.sample1)
  483. XCTAssertNil(samples[1].uuid)
  484. XCTAssertNotNil(samples[1].healthKitEligibleDate)
  485. assertEqualSamples(samples[1], self.sample2)
  486. XCTAssertNil(samples[2].uuid)
  487. XCTAssertNotNil(samples[2].healthKitEligibleDate)
  488. assertEqualSamples(samples[2], self.sample3)
  489. }
  490. addGlucoseSamples1Completion.fulfill()
  491. }
  492. waitForExpectations(timeout: 10)
  493. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  494. glucoseStore.getGlucoseSamples() { result in
  495. switch result {
  496. case .failure(let error):
  497. XCTFail("Unexpected failure: \(error)")
  498. case .success(let samples):
  499. XCTAssertEqual(samples.count, 3)
  500. XCTAssertNotNil(samples[0].uuid)
  501. XCTAssertNil(samples[0].healthKitEligibleDate)
  502. assertEqualSamples(samples[0], self.sample1)
  503. XCTAssertNotNil(samples[1].uuid)
  504. XCTAssertNil(samples[1].healthKitEligibleDate)
  505. assertEqualSamples(samples[1], self.sample3)
  506. XCTAssertNotNil(samples[2].uuid)
  507. XCTAssertNil(samples[2].healthKitEligibleDate)
  508. assertEqualSamples(samples[2], self.sample2)
  509. }
  510. getGlucoseSamples1Completion.fulfill()
  511. }
  512. waitForExpectations(timeout: 10)
  513. let addGlucoseSamples2Completion = expectation(description: "addGlucoseSamples2")
  514. glucoseStore.addGlucoseSamples([sample3, sample1, sample2]) { result in
  515. switch result {
  516. case .failure(let error):
  517. XCTFail("Unexpected failure: \(error)")
  518. case .success(let samples):
  519. XCTAssertEqual(samples.count, 0)
  520. }
  521. addGlucoseSamples2Completion.fulfill()
  522. }
  523. waitForExpectations(timeout: 10)
  524. let getGlucoseSamples2Completion = expectation(description: "getGlucoseSamples2Completion")
  525. glucoseStore.getGlucoseSamples() { result in
  526. switch result {
  527. case .failure(let error):
  528. XCTFail("Unexpected failure: \(error)")
  529. case .success(let samples):
  530. XCTAssertEqual(samples.count, 3)
  531. XCTAssertNotNil(samples[0].uuid)
  532. XCTAssertNil(samples[0].healthKitEligibleDate)
  533. assertEqualSamples(samples[0], self.sample1)
  534. XCTAssertNotNil(samples[1].uuid)
  535. XCTAssertNil(samples[1].healthKitEligibleDate)
  536. assertEqualSamples(samples[1], self.sample3)
  537. XCTAssertNotNil(samples[2].uuid)
  538. XCTAssertNil(samples[2].healthKitEligibleDate)
  539. assertEqualSamples(samples[2], self.sample2)
  540. }
  541. getGlucoseSamples2Completion.fulfill()
  542. }
  543. waitForExpectations(timeout: 10)
  544. }
  545. func testAddGlucoseSamplesEmpty() {
  546. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  547. glucoseStore.addGlucoseSamples([]) { result in
  548. switch result {
  549. case .failure(let error):
  550. XCTFail("Unexpected failure: \(error)")
  551. case .success(let samples):
  552. XCTAssertEqual(samples.count, 0)
  553. }
  554. addGlucoseSamplesCompletion.fulfill()
  555. }
  556. waitForExpectations(timeout: 10)
  557. }
  558. func testAddGlucoseSamplesNotification() {
  559. delegateCompletion = expectation(description: "delegate")
  560. let glucoseSamplesDidChangeCompletion = expectation(description: "glucoseSamplesDidChange")
  561. let observer = NotificationCenter.default.addObserver(forName: GlucoseStore.glucoseSamplesDidChange, object: glucoseStore, queue: nil) { notification in
  562. glucoseSamplesDidChangeCompletion.fulfill()
  563. }
  564. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  565. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  566. switch result {
  567. case .failure(let error):
  568. XCTFail("Unexpected failure: \(error)")
  569. case .success(let samples):
  570. XCTAssertEqual(samples.count, 3)
  571. }
  572. addGlucoseSamplesCompletion.fulfill()
  573. }
  574. wait(for: [glucoseSamplesDidChangeCompletion, delegateCompletion!, addGlucoseSamplesCompletion], timeout: 10, enforceOrder: true)
  575. NotificationCenter.default.removeObserver(observer)
  576. delegateCompletion = nil
  577. }
  578. // MARK: - Watch Synchronization
  579. func testSyncGlucoseSamples() {
  580. var syncGlucoseSamples: [StoredGlucoseSample] = []
  581. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  582. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  583. switch result {
  584. case .failure(let error):
  585. XCTFail("Unexpected failure: \(error)")
  586. case .success(let samples):
  587. XCTAssertEqual(samples.count, 3)
  588. }
  589. addGlucoseSamplesCompletion.fulfill()
  590. }
  591. waitForExpectations(timeout: 10)
  592. let getSyncGlucoseSamples1Completion = expectation(description: "getSyncGlucoseSamples1")
  593. glucoseStore.getSyncGlucoseSamples() { result in
  594. switch result {
  595. case .failure(let error):
  596. XCTFail("Unexpected failure: \(error)")
  597. case .success(let objects):
  598. XCTAssertEqual(objects.count, 3)
  599. XCTAssertNotNil(objects[0].uuid)
  600. assertEqualSamples(objects[0], self.sample1)
  601. XCTAssertNotNil(objects[1].uuid)
  602. assertEqualSamples(objects[1], self.sample3)
  603. XCTAssertNotNil(objects[2].uuid)
  604. assertEqualSamples(objects[2], self.sample2)
  605. syncGlucoseSamples = objects
  606. }
  607. getSyncGlucoseSamples1Completion.fulfill()
  608. }
  609. waitForExpectations(timeout: 10)
  610. let getSyncGlucoseSamples2Completion = expectation(description: "getSyncGlucoseSamples2")
  611. glucoseStore.getSyncGlucoseSamples(start: Date(timeIntervalSinceNow: -.minutes(5)), end: Date(timeIntervalSinceNow: -.minutes(3))) { result in
  612. switch result {
  613. case .failure(let error):
  614. XCTFail("Unexpected failure: \(error)")
  615. case .success(let objects):
  616. XCTAssertEqual(objects.count, 1)
  617. XCTAssertNotNil(objects[0].uuid)
  618. assertEqualSamples(objects[0], self.sample3)
  619. }
  620. getSyncGlucoseSamples2Completion.fulfill()
  621. }
  622. waitForExpectations(timeout: 10)
  623. let purgeCachedGlucoseObjectsCompletion = expectation(description: "purgeCachedGlucoseObjects")
  624. glucoseStore.purgeCachedGlucoseObjects() { error in
  625. XCTAssertNil(error)
  626. purgeCachedGlucoseObjectsCompletion.fulfill()
  627. }
  628. waitForExpectations(timeout: 10)
  629. let getSyncGlucoseSamples3Completion = expectation(description: "getSyncGlucoseSamples3")
  630. glucoseStore.getSyncGlucoseSamples() { result in
  631. switch result {
  632. case .failure(let error):
  633. XCTFail("Unexpected failure: \(error)")
  634. case .success(let samples):
  635. XCTAssertEqual(samples.count, 0)
  636. }
  637. getSyncGlucoseSamples3Completion.fulfill()
  638. }
  639. waitForExpectations(timeout: 10)
  640. let setSyncGlucoseSamplesCompletion = expectation(description: "setSyncGlucoseSamples")
  641. glucoseStore.setSyncGlucoseSamples(syncGlucoseSamples) { error in
  642. XCTAssertNil(error)
  643. setSyncGlucoseSamplesCompletion.fulfill()
  644. }
  645. waitForExpectations(timeout: 10)
  646. let getSyncGlucoseSamples4Completion = expectation(description: "getSyncGlucoseSamples4")
  647. glucoseStore.getSyncGlucoseSamples() { result in
  648. switch result {
  649. case .failure(let error):
  650. XCTFail("Unexpected failure: \(error)")
  651. case .success(let objects):
  652. XCTAssertEqual(objects.count, 3)
  653. XCTAssertNotNil(objects[0].uuid)
  654. assertEqualSamples(objects[0], self.sample1)
  655. XCTAssertNotNil(objects[1].uuid)
  656. assertEqualSamples(objects[1], self.sample3)
  657. XCTAssertNotNil(objects[2].uuid)
  658. assertEqualSamples(objects[2], self.sample2)
  659. syncGlucoseSamples = objects
  660. }
  661. getSyncGlucoseSamples4Completion.fulfill()
  662. }
  663. waitForExpectations(timeout: 10)
  664. }
  665. // MARK: - Cache Management
  666. func testEarliestCacheDate() {
  667. XCTAssertEqual(glucoseStore.earliestCacheDate.timeIntervalSinceNow, -.hours(1), accuracy: 1)
  668. }
  669. func testPurgeAllGlucoseSamples() {
  670. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  671. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  672. switch result {
  673. case .failure(let error):
  674. XCTFail("Unexpected failure: \(error)")
  675. case .success(let samples):
  676. XCTAssertEqual(samples.count, 3)
  677. }
  678. addGlucoseSamplesCompletion.fulfill()
  679. }
  680. waitForExpectations(timeout: 10)
  681. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  682. glucoseStore.getGlucoseSamples() { result in
  683. switch result {
  684. case .failure(let error):
  685. XCTFail("Unexpected failure: \(error)")
  686. case .success(let samples):
  687. XCTAssertEqual(samples.count, 3)
  688. }
  689. getGlucoseSamples1Completion.fulfill()
  690. }
  691. waitForExpectations(timeout: 10)
  692. let purgeAllGlucoseSamplesCompletion = expectation(description: "purgeAllGlucoseSamples")
  693. glucoseStore.purgeAllGlucoseSamples(healthKitPredicate: HKQuery.predicateForObjects(from: HKSource.default())) { error in
  694. XCTAssertNil(error)
  695. purgeAllGlucoseSamplesCompletion.fulfill()
  696. }
  697. waitForExpectations(timeout: 10)
  698. let getGlucoseSamples2Completion = expectation(description: "getGlucoseSamples2")
  699. glucoseStore.getGlucoseSamples() { result in
  700. switch result {
  701. case .failure(let error):
  702. XCTFail("Unexpected failure: \(error)")
  703. case .success(let samples):
  704. XCTAssertEqual(samples.count, 0)
  705. }
  706. getGlucoseSamples2Completion.fulfill()
  707. }
  708. waitForExpectations(timeout: 10)
  709. }
  710. func testPurgeExpiredGlucoseObjects() {
  711. let expiredSample = NewGlucoseSample(date: Date(timeIntervalSinceNow: -.hours(2)),
  712. quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 198.7),
  713. condition: nil,
  714. trend: nil,
  715. trendRate: nil,
  716. isDisplayOnly: false,
  717. wasUserEntered: false,
  718. syncIdentifier: "6AB8C7F3-A2CE-442F-98C4-3D0514626B5F",
  719. syncVersion: 3)
  720. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  721. glucoseStore.addGlucoseSamples([sample1, sample2, sample3, expiredSample]) { result in
  722. switch result {
  723. case .failure(let error):
  724. XCTFail("Unexpected failure: \(error)")
  725. case .success(let samples):
  726. XCTAssertEqual(samples.count, 4)
  727. }
  728. addGlucoseSamplesCompletion.fulfill()
  729. }
  730. waitForExpectations(timeout: 10)
  731. let getGlucoseSamplesCompletion = expectation(description: "getGlucoseSamples")
  732. glucoseStore.getGlucoseSamples() { result in
  733. switch result {
  734. case .failure(let error):
  735. XCTFail("Unexpected failure: \(error)")
  736. case .success(let samples):
  737. XCTAssertEqual(samples.count, 3)
  738. }
  739. getGlucoseSamplesCompletion.fulfill()
  740. }
  741. waitForExpectations(timeout: 10)
  742. }
  743. func testPurgeCachedGlucoseObjects() {
  744. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  745. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  746. switch result {
  747. case .failure(let error):
  748. XCTFail("Unexpected failure: \(error)")
  749. case .success(let samples):
  750. XCTAssertEqual(samples.count, 3)
  751. }
  752. addGlucoseSamplesCompletion.fulfill()
  753. }
  754. waitForExpectations(timeout: 10)
  755. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  756. glucoseStore.getGlucoseSamples() { result in
  757. switch result {
  758. case .failure(let error):
  759. XCTFail("Unexpected failure: \(error)")
  760. case .success(let samples):
  761. XCTAssertEqual(samples.count, 3)
  762. }
  763. getGlucoseSamples1Completion.fulfill()
  764. }
  765. waitForExpectations(timeout: 10)
  766. let purgeCachedGlucoseObjects1Completion = expectation(description: "purgeCachedGlucoseObjects1")
  767. glucoseStore.purgeCachedGlucoseObjects(before: Date(timeIntervalSinceNow: -.minutes(5))) { error in
  768. XCTAssertNil(error)
  769. purgeCachedGlucoseObjects1Completion.fulfill()
  770. }
  771. waitForExpectations(timeout: 10)
  772. let getGlucoseSamples2Completion = expectation(description: "getGlucoseSamples2")
  773. glucoseStore.getGlucoseSamples() { result in
  774. switch result {
  775. case .failure(let error):
  776. XCTFail("Unexpected failure: \(error)")
  777. case .success(let samples):
  778. XCTAssertEqual(samples.count, 2)
  779. }
  780. getGlucoseSamples2Completion.fulfill()
  781. }
  782. waitForExpectations(timeout: 10)
  783. let purgeCachedGlucoseObjects2Completion = expectation(description: "purgeCachedGlucoseObjects2")
  784. glucoseStore.purgeCachedGlucoseObjects() { error in
  785. XCTAssertNil(error)
  786. purgeCachedGlucoseObjects2Completion.fulfill()
  787. }
  788. waitForExpectations(timeout: 10)
  789. let getGlucoseSamples3Completion = expectation(description: "getGlucoseSamples3")
  790. glucoseStore.getGlucoseSamples() { result in
  791. switch result {
  792. case .failure(let error):
  793. XCTFail("Unexpected failure: \(error)")
  794. case .success(let samples):
  795. XCTAssertEqual(samples.count, 0)
  796. }
  797. getGlucoseSamples3Completion.fulfill()
  798. }
  799. waitForExpectations(timeout: 10)
  800. }
  801. func testPurgeCachedGlucoseObjectsNotification() {
  802. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  803. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  804. switch result {
  805. case .failure(let error):
  806. XCTFail("Unexpected failure: \(error)")
  807. case .success(let samples):
  808. XCTAssertEqual(samples.count, 3)
  809. }
  810. addGlucoseSamplesCompletion.fulfill()
  811. }
  812. waitForExpectations(timeout: 10)
  813. delegateCompletion = expectation(description: "delegate")
  814. let glucoseSamplesDidChangeCompletion = expectation(description: "glucoseSamplesDidChange")
  815. let observer = NotificationCenter.default.addObserver(forName: GlucoseStore.glucoseSamplesDidChange, object: glucoseStore, queue: nil) { notification in
  816. glucoseSamplesDidChangeCompletion.fulfill()
  817. }
  818. let purgeCachedGlucoseObjectsCompletion = expectation(description: "purgeCachedGlucoseObjects")
  819. glucoseStore.purgeCachedGlucoseObjects() { error in
  820. XCTAssertNil(error)
  821. purgeCachedGlucoseObjectsCompletion.fulfill()
  822. }
  823. wait(for: [glucoseSamplesDidChangeCompletion, delegateCompletion!, purgeCachedGlucoseObjectsCompletion], timeout: 10, enforceOrder: true)
  824. NotificationCenter.default.removeObserver(observer)
  825. delegateCompletion = nil
  826. }
  827. }
  828. fileprivate func assertEqualSamples(_ storedGlucoseSample: StoredGlucoseSample,
  829. _ newGlucoseSample: NewGlucoseSample,
  830. provenanceIdentifier: String = HKSource.default().bundleIdentifier,
  831. file: StaticString = #file,
  832. line: UInt = #line) {
  833. XCTAssertEqual(storedGlucoseSample.provenanceIdentifier, provenanceIdentifier, file: file, line: line)
  834. XCTAssertEqual(storedGlucoseSample.syncIdentifier, newGlucoseSample.syncIdentifier, file: file, line: line)
  835. XCTAssertEqual(storedGlucoseSample.syncVersion, newGlucoseSample.syncVersion, file: file, line: line)
  836. XCTAssertEqual(storedGlucoseSample.startDate, newGlucoseSample.date, file: file, line: line)
  837. XCTAssertEqual(storedGlucoseSample.quantity, newGlucoseSample.quantity, file: file, line: line)
  838. XCTAssertEqual(storedGlucoseSample.isDisplayOnly, newGlucoseSample.isDisplayOnly, file: file, line: line)
  839. XCTAssertEqual(storedGlucoseSample.wasUserEntered, newGlucoseSample.wasUserEntered, file: file, line: line)
  840. XCTAssertEqual(storedGlucoseSample.device, newGlucoseSample.device, file: file, line: line)
  841. XCTAssertEqual(storedGlucoseSample.condition, newGlucoseSample.condition, file: file, line: line)
  842. XCTAssertEqual(storedGlucoseSample.trend, newGlucoseSample.trend, file: file, line: line)
  843. XCTAssertEqual(storedGlucoseSample.trendRate, newGlucoseSample.trendRate, file: file, line: line)
  844. }