GlucoseStoreTests.swift 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  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. XCTAssertEqual([sample1.quantitySample.description], hkobjects.map { $0.description })
  266. }
  267. func testGetGlucoseSamplesErrorHealthKitStorage() {
  268. healthStore.saveError = Error.arbitrary
  269. var hkobjects = [HKObject]()
  270. healthStore.setSaveHandler { o, _, _ in hkobjects = o }
  271. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  272. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  273. switch result {
  274. case .failure(let error):
  275. XCTFail("Unexpected failure: \(error)")
  276. case .success(let samples):
  277. XCTAssertEqual(samples.count, 3)
  278. }
  279. addGlucoseSamplesCompletion.fulfill()
  280. }
  281. waitForExpectations(timeout: 10)
  282. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  283. glucoseStore.getGlucoseSamples() { result in
  284. switch result {
  285. case .failure(let error):
  286. XCTFail("Unexpected failure: \(error)")
  287. case .success(let samples):
  288. XCTAssertEqual(samples.count, 3)
  289. // HealthKit storage is deferred, so the second 2 UUIDs are nil
  290. XCTAssertNil(samples[0].uuid)
  291. XCTAssertNotNil(samples[0].healthKitEligibleDate)
  292. assertEqualSamples(samples[0], self.sample1)
  293. XCTAssertNil(samples[1].uuid)
  294. XCTAssertNotNil(samples[1].healthKitEligibleDate)
  295. assertEqualSamples(samples[1], self.sample3)
  296. XCTAssertNil(samples[2].uuid)
  297. XCTAssertNotNil(samples[2].healthKitEligibleDate)
  298. assertEqualSamples(samples[2], self.sample2)
  299. }
  300. getGlucoseSamples1Completion.fulfill()
  301. }
  302. waitForExpectations(timeout: 10)
  303. XCTAssertEqual(3, hkobjects.count)
  304. }
  305. func testGetGlucoseSamplesDeniedHealthKitStorage() {
  306. healthStore.authorizationStatus = .sharingDenied
  307. var hkobjects = [HKObject]()
  308. healthStore.setSaveHandler { o, _, _ in hkobjects = o }
  309. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  310. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { 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. }
  317. addGlucoseSamplesCompletion.fulfill()
  318. }
  319. waitForExpectations(timeout: 10)
  320. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  321. glucoseStore.getGlucoseSamples() { result in
  322. switch result {
  323. case .failure(let error):
  324. XCTFail("Unexpected failure: \(error)")
  325. case .success(let samples):
  326. XCTAssertEqual(samples.count, 3)
  327. // HealthKit storage is denied, so all UUIDs are nil
  328. XCTAssertNil(samples[0].uuid)
  329. XCTAssertNil(samples[0].healthKitEligibleDate)
  330. assertEqualSamples(samples[0], self.sample1)
  331. XCTAssertNil(samples[1].uuid)
  332. XCTAssertNil(samples[1].healthKitEligibleDate)
  333. assertEqualSamples(samples[1], self.sample3)
  334. XCTAssertNil(samples[2].uuid)
  335. XCTAssertNil(samples[2].healthKitEligibleDate)
  336. assertEqualSamples(samples[2], self.sample2)
  337. }
  338. getGlucoseSamples1Completion.fulfill()
  339. }
  340. waitForExpectations(timeout: 10)
  341. XCTAssertTrue(hkobjects.isEmpty)
  342. }
  343. func testGetGlucoseSamplesSomeDeniedHealthKitStorage() {
  344. glucoseStore.healthKitStorageDelay = 0
  345. var hkobjects = [HKObject]()
  346. healthStore.setSaveHandler { o, _, _ in hkobjects = o }
  347. let addGlucoseSamples1Completion = expectation(description: "addGlucoseSamples1")
  348. // Authorized
  349. glucoseStore.addGlucoseSamples([sample1]) { result in
  350. switch result {
  351. case .failure(let error):
  352. XCTFail("Unexpected failure: \(error)")
  353. case .success(let samples):
  354. XCTAssertEqual(samples.count, 1)
  355. }
  356. addGlucoseSamples1Completion.fulfill()
  357. }
  358. waitForExpectations(timeout: 10)
  359. XCTAssertEqual(1, hkobjects.count)
  360. hkobjects = []
  361. healthStore.authorizationStatus = .sharingDenied
  362. let addGlucoseSamples2Completion = expectation(description: "addGlucoseSamples2")
  363. // Denied
  364. glucoseStore.addGlucoseSamples([sample2]) { result in
  365. switch result {
  366. case .failure(let error):
  367. XCTFail("Unexpected failure: \(error)")
  368. case .success(let samples):
  369. XCTAssertEqual(samples.count, 1)
  370. }
  371. addGlucoseSamples2Completion.fulfill()
  372. }
  373. waitForExpectations(timeout: 10)
  374. XCTAssertEqual(0, hkobjects.count)
  375. hkobjects = []
  376. healthStore.authorizationStatus = .sharingAuthorized
  377. let addGlucoseSamples3Completion = expectation(description: "addGlucoseSamples3")
  378. // Authorized
  379. glucoseStore.addGlucoseSamples([sample3]) { result in
  380. switch result {
  381. case .failure(let error):
  382. XCTFail("Unexpected failure: \(error)")
  383. case .success(let samples):
  384. XCTAssertEqual(samples.count, 1)
  385. }
  386. addGlucoseSamples3Completion.fulfill()
  387. }
  388. waitForExpectations(timeout: 10)
  389. XCTAssertEqual(1, hkobjects.count)
  390. hkobjects = []
  391. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  392. glucoseStore.getGlucoseSamples() { result in
  393. switch result {
  394. case .failure(let error):
  395. XCTFail("Unexpected failure: \(error)")
  396. case .success(let samples):
  397. XCTAssertEqual(samples.count, 3)
  398. XCTAssertNotNil(samples[0].uuid)
  399. XCTAssertNil(samples[0].healthKitEligibleDate)
  400. assertEqualSamples(samples[0], self.sample1)
  401. XCTAssertNotNil(samples[1].uuid)
  402. XCTAssertNil(samples[1].healthKitEligibleDate)
  403. assertEqualSamples(samples[1], self.sample3)
  404. XCTAssertNil(samples[2].uuid)
  405. XCTAssertNil(samples[2].healthKitEligibleDate)
  406. assertEqualSamples(samples[2], self.sample2)
  407. }
  408. getGlucoseSamples1Completion.fulfill()
  409. }
  410. waitForExpectations(timeout: 10)
  411. }
  412. func testLatestGlucose() {
  413. XCTAssertNil(glucoseStore.latestGlucose)
  414. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  415. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  416. switch result {
  417. case .failure(let error):
  418. XCTFail("Unexpected failure: \(error)")
  419. case .success(let samples):
  420. XCTAssertEqual(samples.count, 3)
  421. assertEqualSamples(samples[0], self.sample1)
  422. assertEqualSamples(samples[1], self.sample2)
  423. assertEqualSamples(samples[2], self.sample3)
  424. }
  425. addGlucoseSamplesCompletion.fulfill()
  426. }
  427. waitForExpectations(timeout: 10)
  428. XCTAssertNotNil(glucoseStore.latestGlucose)
  429. XCTAssertEqual(glucoseStore.latestGlucose?.startDate, sample2.date)
  430. XCTAssertEqual(glucoseStore.latestGlucose?.endDate, sample2.date)
  431. XCTAssertEqual(glucoseStore.latestGlucose?.quantity, sample2.quantity)
  432. XCTAssertEqual(glucoseStore.latestGlucose?.provenanceIdentifier, HKSource.default().bundleIdentifier)
  433. XCTAssertEqual(glucoseStore.latestGlucose?.isDisplayOnly, sample2.isDisplayOnly)
  434. XCTAssertEqual(glucoseStore.latestGlucose?.wasUserEntered, sample2.wasUserEntered)
  435. let purgeCachedGlucoseObjectsCompletion = expectation(description: "purgeCachedGlucoseObjects")
  436. glucoseStore.purgeCachedGlucoseObjects() { error in
  437. XCTAssertNil(error)
  438. purgeCachedGlucoseObjectsCompletion.fulfill()
  439. }
  440. waitForExpectations(timeout: 10)
  441. XCTAssertNil(glucoseStore.latestGlucose)
  442. }
  443. // MARK: - Modification
  444. func testAddGlucoseSamples() {
  445. let addGlucoseSamples1Completion = expectation(description: "addGlucoseSamples1")
  446. glucoseStore.addGlucoseSamples([sample1, sample2, sample3, sample1, sample2, sample3]) { result in
  447. switch result {
  448. case .failure(let error):
  449. XCTFail("Unexpected failure: \(error)")
  450. case .success(let samples):
  451. XCTAssertEqual(samples.count, 3)
  452. // Note: the HealthKit UUID is no longer updated before being returned as a result of addGlucoseSamples.
  453. XCTAssertNil(samples[0].uuid)
  454. XCTAssertNotNil(samples[0].healthKitEligibleDate)
  455. assertEqualSamples(samples[0], self.sample1)
  456. XCTAssertNil(samples[1].uuid)
  457. XCTAssertNotNil(samples[1].healthKitEligibleDate)
  458. assertEqualSamples(samples[1], self.sample2)
  459. XCTAssertNil(samples[2].uuid)
  460. XCTAssertNotNil(samples[2].healthKitEligibleDate)
  461. assertEqualSamples(samples[2], self.sample3)
  462. }
  463. addGlucoseSamples1Completion.fulfill()
  464. }
  465. waitForExpectations(timeout: 10)
  466. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  467. glucoseStore.getGlucoseSamples() { result in
  468. switch result {
  469. case .failure(let error):
  470. XCTFail("Unexpected failure: \(error)")
  471. case .success(let samples):
  472. XCTAssertEqual(samples.count, 3)
  473. XCTAssertNotNil(samples[0].uuid)
  474. XCTAssertNil(samples[0].healthKitEligibleDate)
  475. assertEqualSamples(samples[0], self.sample1)
  476. XCTAssertNotNil(samples[1].uuid)
  477. XCTAssertNil(samples[1].healthKitEligibleDate)
  478. assertEqualSamples(samples[1], self.sample3)
  479. XCTAssertNotNil(samples[2].uuid)
  480. XCTAssertNil(samples[2].healthKitEligibleDate)
  481. assertEqualSamples(samples[2], self.sample2)
  482. }
  483. getGlucoseSamples1Completion.fulfill()
  484. }
  485. waitForExpectations(timeout: 10)
  486. let addGlucoseSamples2Completion = expectation(description: "addGlucoseSamples2")
  487. glucoseStore.addGlucoseSamples([sample3, sample1, sample2]) { result in
  488. switch result {
  489. case .failure(let error):
  490. XCTFail("Unexpected failure: \(error)")
  491. case .success(let samples):
  492. XCTAssertEqual(samples.count, 0)
  493. }
  494. addGlucoseSamples2Completion.fulfill()
  495. }
  496. waitForExpectations(timeout: 10)
  497. let getGlucoseSamples2Completion = expectation(description: "getGlucoseSamples2Completion")
  498. glucoseStore.getGlucoseSamples() { result in
  499. switch result {
  500. case .failure(let error):
  501. XCTFail("Unexpected failure: \(error)")
  502. case .success(let samples):
  503. XCTAssertEqual(samples.count, 3)
  504. XCTAssertNotNil(samples[0].uuid)
  505. XCTAssertNil(samples[0].healthKitEligibleDate)
  506. assertEqualSamples(samples[0], self.sample1)
  507. XCTAssertNotNil(samples[1].uuid)
  508. XCTAssertNil(samples[1].healthKitEligibleDate)
  509. assertEqualSamples(samples[1], self.sample3)
  510. XCTAssertNotNil(samples[2].uuid)
  511. XCTAssertNil(samples[2].healthKitEligibleDate)
  512. assertEqualSamples(samples[2], self.sample2)
  513. }
  514. getGlucoseSamples2Completion.fulfill()
  515. }
  516. waitForExpectations(timeout: 10)
  517. }
  518. func testAddGlucoseSamplesEmpty() {
  519. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  520. glucoseStore.addGlucoseSamples([]) { result in
  521. switch result {
  522. case .failure(let error):
  523. XCTFail("Unexpected failure: \(error)")
  524. case .success(let samples):
  525. XCTAssertEqual(samples.count, 0)
  526. }
  527. addGlucoseSamplesCompletion.fulfill()
  528. }
  529. waitForExpectations(timeout: 10)
  530. }
  531. func testAddGlucoseSamplesNotification() {
  532. delegateCompletion = expectation(description: "delegate")
  533. let glucoseSamplesDidChangeCompletion = expectation(description: "glucoseSamplesDidChange")
  534. let observer = NotificationCenter.default.addObserver(forName: GlucoseStore.glucoseSamplesDidChange, object: glucoseStore, queue: nil) { notification in
  535. glucoseSamplesDidChangeCompletion.fulfill()
  536. }
  537. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  538. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  539. switch result {
  540. case .failure(let error):
  541. XCTFail("Unexpected failure: \(error)")
  542. case .success(let samples):
  543. XCTAssertEqual(samples.count, 3)
  544. }
  545. addGlucoseSamplesCompletion.fulfill()
  546. }
  547. wait(for: [glucoseSamplesDidChangeCompletion, delegateCompletion!, addGlucoseSamplesCompletion], timeout: 10, enforceOrder: true)
  548. NotificationCenter.default.removeObserver(observer)
  549. delegateCompletion = nil
  550. }
  551. // MARK: - Watch Synchronization
  552. func testSyncGlucoseSamples() {
  553. var syncGlucoseSamples: [StoredGlucoseSample] = []
  554. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  555. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  556. switch result {
  557. case .failure(let error):
  558. XCTFail("Unexpected failure: \(error)")
  559. case .success(let samples):
  560. XCTAssertEqual(samples.count, 3)
  561. }
  562. addGlucoseSamplesCompletion.fulfill()
  563. }
  564. waitForExpectations(timeout: 10)
  565. let getSyncGlucoseSamples1Completion = expectation(description: "getSyncGlucoseSamples1")
  566. glucoseStore.getSyncGlucoseSamples() { result in
  567. switch result {
  568. case .failure(let error):
  569. XCTFail("Unexpected failure: \(error)")
  570. case .success(let objects):
  571. XCTAssertEqual(objects.count, 3)
  572. XCTAssertNotNil(objects[0].uuid)
  573. assertEqualSamples(objects[0], self.sample1)
  574. XCTAssertNotNil(objects[1].uuid)
  575. assertEqualSamples(objects[1], self.sample3)
  576. XCTAssertNotNil(objects[2].uuid)
  577. assertEqualSamples(objects[2], self.sample2)
  578. syncGlucoseSamples = objects
  579. }
  580. getSyncGlucoseSamples1Completion.fulfill()
  581. }
  582. waitForExpectations(timeout: 10)
  583. let getSyncGlucoseSamples2Completion = expectation(description: "getSyncGlucoseSamples2")
  584. glucoseStore.getSyncGlucoseSamples(start: Date(timeIntervalSinceNow: -.minutes(5)), end: Date(timeIntervalSinceNow: -.minutes(3))) { result in
  585. switch result {
  586. case .failure(let error):
  587. XCTFail("Unexpected failure: \(error)")
  588. case .success(let objects):
  589. XCTAssertEqual(objects.count, 1)
  590. XCTAssertNotNil(objects[0].uuid)
  591. assertEqualSamples(objects[0], self.sample3)
  592. }
  593. getSyncGlucoseSamples2Completion.fulfill()
  594. }
  595. waitForExpectations(timeout: 10)
  596. let purgeCachedGlucoseObjectsCompletion = expectation(description: "purgeCachedGlucoseObjects")
  597. glucoseStore.purgeCachedGlucoseObjects() { error in
  598. XCTAssertNil(error)
  599. purgeCachedGlucoseObjectsCompletion.fulfill()
  600. }
  601. waitForExpectations(timeout: 10)
  602. let getSyncGlucoseSamples3Completion = expectation(description: "getSyncGlucoseSamples3")
  603. glucoseStore.getSyncGlucoseSamples() { result in
  604. switch result {
  605. case .failure(let error):
  606. XCTFail("Unexpected failure: \(error)")
  607. case .success(let samples):
  608. XCTAssertEqual(samples.count, 0)
  609. }
  610. getSyncGlucoseSamples3Completion.fulfill()
  611. }
  612. waitForExpectations(timeout: 10)
  613. let setSyncGlucoseSamplesCompletion = expectation(description: "setSyncGlucoseSamples")
  614. glucoseStore.setSyncGlucoseSamples(syncGlucoseSamples) { error in
  615. XCTAssertNil(error)
  616. setSyncGlucoseSamplesCompletion.fulfill()
  617. }
  618. waitForExpectations(timeout: 10)
  619. let getSyncGlucoseSamples4Completion = expectation(description: "getSyncGlucoseSamples4")
  620. glucoseStore.getSyncGlucoseSamples() { result in
  621. switch result {
  622. case .failure(let error):
  623. XCTFail("Unexpected failure: \(error)")
  624. case .success(let objects):
  625. XCTAssertEqual(objects.count, 3)
  626. XCTAssertNotNil(objects[0].uuid)
  627. assertEqualSamples(objects[0], self.sample1)
  628. XCTAssertNotNil(objects[1].uuid)
  629. assertEqualSamples(objects[1], self.sample3)
  630. XCTAssertNotNil(objects[2].uuid)
  631. assertEqualSamples(objects[2], self.sample2)
  632. syncGlucoseSamples = objects
  633. }
  634. getSyncGlucoseSamples4Completion.fulfill()
  635. }
  636. waitForExpectations(timeout: 10)
  637. }
  638. // MARK: - Cache Management
  639. func testEarliestCacheDate() {
  640. XCTAssertEqual(glucoseStore.earliestCacheDate.timeIntervalSinceNow, -.hours(1), accuracy: 1)
  641. }
  642. func testPurgeAllGlucoseSamples() {
  643. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  644. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  645. switch result {
  646. case .failure(let error):
  647. XCTFail("Unexpected failure: \(error)")
  648. case .success(let samples):
  649. XCTAssertEqual(samples.count, 3)
  650. }
  651. addGlucoseSamplesCompletion.fulfill()
  652. }
  653. waitForExpectations(timeout: 10)
  654. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  655. glucoseStore.getGlucoseSamples() { result in
  656. switch result {
  657. case .failure(let error):
  658. XCTFail("Unexpected failure: \(error)")
  659. case .success(let samples):
  660. XCTAssertEqual(samples.count, 3)
  661. }
  662. getGlucoseSamples1Completion.fulfill()
  663. }
  664. waitForExpectations(timeout: 10)
  665. let purgeAllGlucoseSamplesCompletion = expectation(description: "purgeAllGlucoseSamples")
  666. glucoseStore.purgeAllGlucoseSamples(healthKitPredicate: HKQuery.predicateForObjects(from: HKSource.default())) { error in
  667. XCTAssertNil(error)
  668. purgeAllGlucoseSamplesCompletion.fulfill()
  669. }
  670. waitForExpectations(timeout: 10)
  671. let getGlucoseSamples2Completion = expectation(description: "getGlucoseSamples2")
  672. glucoseStore.getGlucoseSamples() { result in
  673. switch result {
  674. case .failure(let error):
  675. XCTFail("Unexpected failure: \(error)")
  676. case .success(let samples):
  677. XCTAssertEqual(samples.count, 0)
  678. }
  679. getGlucoseSamples2Completion.fulfill()
  680. }
  681. waitForExpectations(timeout: 10)
  682. }
  683. func testPurgeExpiredGlucoseObjects() {
  684. let expiredSample = NewGlucoseSample(date: Date(timeIntervalSinceNow: -.hours(2)),
  685. quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 198.7),
  686. condition: nil,
  687. trend: nil,
  688. trendRate: nil,
  689. isDisplayOnly: false,
  690. wasUserEntered: false,
  691. syncIdentifier: "6AB8C7F3-A2CE-442F-98C4-3D0514626B5F",
  692. syncVersion: 3)
  693. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  694. glucoseStore.addGlucoseSamples([sample1, sample2, sample3, expiredSample]) { result in
  695. switch result {
  696. case .failure(let error):
  697. XCTFail("Unexpected failure: \(error)")
  698. case .success(let samples):
  699. XCTAssertEqual(samples.count, 4)
  700. }
  701. addGlucoseSamplesCompletion.fulfill()
  702. }
  703. waitForExpectations(timeout: 10)
  704. let getGlucoseSamplesCompletion = expectation(description: "getGlucoseSamples")
  705. glucoseStore.getGlucoseSamples() { result in
  706. switch result {
  707. case .failure(let error):
  708. XCTFail("Unexpected failure: \(error)")
  709. case .success(let samples):
  710. XCTAssertEqual(samples.count, 3)
  711. }
  712. getGlucoseSamplesCompletion.fulfill()
  713. }
  714. waitForExpectations(timeout: 10)
  715. }
  716. func testPurgeCachedGlucoseObjects() {
  717. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  718. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  719. switch result {
  720. case .failure(let error):
  721. XCTFail("Unexpected failure: \(error)")
  722. case .success(let samples):
  723. XCTAssertEqual(samples.count, 3)
  724. }
  725. addGlucoseSamplesCompletion.fulfill()
  726. }
  727. waitForExpectations(timeout: 10)
  728. let getGlucoseSamples1Completion = expectation(description: "getGlucoseSamples1")
  729. glucoseStore.getGlucoseSamples() { result in
  730. switch result {
  731. case .failure(let error):
  732. XCTFail("Unexpected failure: \(error)")
  733. case .success(let samples):
  734. XCTAssertEqual(samples.count, 3)
  735. }
  736. getGlucoseSamples1Completion.fulfill()
  737. }
  738. waitForExpectations(timeout: 10)
  739. let purgeCachedGlucoseObjects1Completion = expectation(description: "purgeCachedGlucoseObjects1")
  740. glucoseStore.purgeCachedGlucoseObjects(before: Date(timeIntervalSinceNow: -.minutes(5))) { error in
  741. XCTAssertNil(error)
  742. purgeCachedGlucoseObjects1Completion.fulfill()
  743. }
  744. waitForExpectations(timeout: 10)
  745. let getGlucoseSamples2Completion = expectation(description: "getGlucoseSamples2")
  746. glucoseStore.getGlucoseSamples() { result in
  747. switch result {
  748. case .failure(let error):
  749. XCTFail("Unexpected failure: \(error)")
  750. case .success(let samples):
  751. XCTAssertEqual(samples.count, 2)
  752. }
  753. getGlucoseSamples2Completion.fulfill()
  754. }
  755. waitForExpectations(timeout: 10)
  756. let purgeCachedGlucoseObjects2Completion = expectation(description: "purgeCachedGlucoseObjects2")
  757. glucoseStore.purgeCachedGlucoseObjects() { error in
  758. XCTAssertNil(error)
  759. purgeCachedGlucoseObjects2Completion.fulfill()
  760. }
  761. waitForExpectations(timeout: 10)
  762. let getGlucoseSamples3Completion = expectation(description: "getGlucoseSamples3")
  763. glucoseStore.getGlucoseSamples() { result in
  764. switch result {
  765. case .failure(let error):
  766. XCTFail("Unexpected failure: \(error)")
  767. case .success(let samples):
  768. XCTAssertEqual(samples.count, 0)
  769. }
  770. getGlucoseSamples3Completion.fulfill()
  771. }
  772. waitForExpectations(timeout: 10)
  773. }
  774. func testPurgeCachedGlucoseObjectsNotification() {
  775. let addGlucoseSamplesCompletion = expectation(description: "addGlucoseSamples")
  776. glucoseStore.addGlucoseSamples([sample1, sample2, sample3]) { result in
  777. switch result {
  778. case .failure(let error):
  779. XCTFail("Unexpected failure: \(error)")
  780. case .success(let samples):
  781. XCTAssertEqual(samples.count, 3)
  782. }
  783. addGlucoseSamplesCompletion.fulfill()
  784. }
  785. waitForExpectations(timeout: 10)
  786. delegateCompletion = expectation(description: "delegate")
  787. let glucoseSamplesDidChangeCompletion = expectation(description: "glucoseSamplesDidChange")
  788. let observer = NotificationCenter.default.addObserver(forName: GlucoseStore.glucoseSamplesDidChange, object: glucoseStore, queue: nil) { notification in
  789. glucoseSamplesDidChangeCompletion.fulfill()
  790. }
  791. let purgeCachedGlucoseObjectsCompletion = expectation(description: "purgeCachedGlucoseObjects")
  792. glucoseStore.purgeCachedGlucoseObjects() { error in
  793. XCTAssertNil(error)
  794. purgeCachedGlucoseObjectsCompletion.fulfill()
  795. }
  796. wait(for: [glucoseSamplesDidChangeCompletion, delegateCompletion!, purgeCachedGlucoseObjectsCompletion], timeout: 10, enforceOrder: true)
  797. NotificationCenter.default.removeObserver(observer)
  798. delegateCompletion = nil
  799. }
  800. }
  801. fileprivate func assertEqualSamples(_ storedGlucoseSample: StoredGlucoseSample,
  802. _ newGlucoseSample: NewGlucoseSample,
  803. provenanceIdentifier: String = HKSource.default().bundleIdentifier,
  804. file: StaticString = #file,
  805. line: UInt = #line) {
  806. XCTAssertEqual(storedGlucoseSample.provenanceIdentifier, provenanceIdentifier, file: file, line: line)
  807. XCTAssertEqual(storedGlucoseSample.syncIdentifier, newGlucoseSample.syncIdentifier, file: file, line: line)
  808. XCTAssertEqual(storedGlucoseSample.syncVersion, newGlucoseSample.syncVersion, file: file, line: line)
  809. XCTAssertEqual(storedGlucoseSample.startDate, newGlucoseSample.date, file: file, line: line)
  810. XCTAssertEqual(storedGlucoseSample.quantity, newGlucoseSample.quantity, file: file, line: line)
  811. XCTAssertEqual(storedGlucoseSample.isDisplayOnly, newGlucoseSample.isDisplayOnly, file: file, line: line)
  812. XCTAssertEqual(storedGlucoseSample.wasUserEntered, newGlucoseSample.wasUserEntered, file: file, line: line)
  813. XCTAssertEqual(storedGlucoseSample.device, newGlucoseSample.device, file: file, line: line)
  814. XCTAssertEqual(storedGlucoseSample.condition, newGlucoseSample.condition, file: file, line: line)
  815. XCTAssertEqual(storedGlucoseSample.trend, newGlucoseSample.trend, file: file, line: line)
  816. XCTAssertEqual(storedGlucoseSample.trendRate, newGlucoseSample.trendRate, file: file, line: line)
  817. }