GlucoseStoreTests.swift 41 KB

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