CarbStoreTests.swift 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357
  1. //
  2. // CarbStoreTests.swift
  3. // LoopKitTests
  4. //
  5. // Copyright © 2018 LoopKit Authors. All rights reserved.
  6. //
  7. import XCTest
  8. import HealthKit
  9. import CoreData
  10. @testable import LoopKit
  11. class CarbStorePersistenceTests: PersistenceControllerTestCase, CarbStoreDelegate {
  12. var healthStore: HKHealthStoreMock!
  13. var carbStore: CarbStore!
  14. override func setUp() {
  15. super.setUp()
  16. healthStore = HKHealthStoreMock()
  17. let hkSampleStore = HealthKitSampleStore(healthStore: healthStore, type: HealthKitSampleStore.carbType)
  18. carbStore = CarbStore(
  19. healthKitSampleStore: hkSampleStore,
  20. cacheStore: cacheStore,
  21. cacheLength: .hours(24),
  22. defaultAbsorptionTimes: (fast: .minutes(30), medium: .hours(3), slow: .hours(5)),
  23. provenanceIdentifier: Bundle.main.bundleIdentifier!)
  24. carbStore.delegate = self
  25. let semaphore = DispatchSemaphore(value: 0)
  26. cacheStore.onReady { (error) in
  27. semaphore.signal()
  28. }
  29. semaphore.wait()
  30. }
  31. override func tearDown() {
  32. carbStore.delegate = nil
  33. carbStore = nil
  34. healthStore = nil
  35. carbStoreHasUpdatedCarbDataHandler = nil
  36. super.tearDown()
  37. }
  38. // MARK: - CarbStoreDelegate
  39. var carbStoreHasUpdatedCarbDataHandler: ((_ : CarbStore) -> Void)?
  40. func carbStoreHasUpdatedCarbData(_ carbStore: CarbStore) {
  41. carbStoreHasUpdatedCarbDataHandler?(carbStore)
  42. }
  43. func carbStore(_ carbStore: CarbStore, didError error: CarbStore.CarbStoreError) {}
  44. // MARK: -
  45. func testGetCarbEntriesAfterAdd() {
  46. let firstCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(timeIntervalSinceNow: -1), foodType: "First", absorptionTime: .hours(5))
  47. let secondCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 20), startDate: Date(), foodType: "Second", absorptionTime: .hours(3))
  48. let thirdCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 30), startDate: Date(timeIntervalSinceNow: 1), foodType: "Third", absorptionTime: .minutes(30))
  49. let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
  50. carbStore.addCarbEntry(firstCarbEntry) { (_) in
  51. DispatchQueue.main.async {
  52. self.carbStore.addCarbEntry(secondCarbEntry) { (_) in
  53. DispatchQueue.main.async {
  54. self.carbStore.addCarbEntry(thirdCarbEntry) { (_) in
  55. DispatchQueue.main.async {
  56. self.carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
  57. getCarbEntriesCompletion.fulfill()
  58. switch result {
  59. case .failure(let error):
  60. XCTFail("Unexpected failure: \(error)")
  61. case .success(let entries):
  62. XCTAssertEqual(entries.count, 3)
  63. // First
  64. XCTAssertNotNil(entries[0].uuid)
  65. XCTAssertEqual(entries[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  66. XCTAssertNotNil(entries[0].syncIdentifier)
  67. XCTAssertEqual(entries[0].syncVersion, 1)
  68. XCTAssertEqual(entries[0].startDate, firstCarbEntry.startDate)
  69. XCTAssertEqual(entries[0].quantity, firstCarbEntry.quantity)
  70. XCTAssertEqual(entries[0].foodType, firstCarbEntry.foodType)
  71. XCTAssertEqual(entries[0].absorptionTime, firstCarbEntry.absorptionTime)
  72. XCTAssertEqual(entries[0].createdByCurrentApp, true)
  73. XCTAssertEqual(entries[0].userCreatedDate, firstCarbEntry.date)
  74. XCTAssertNil(entries[0].userUpdatedDate)
  75. // Second
  76. XCTAssertNotNil(entries[1].uuid)
  77. XCTAssertEqual(entries[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  78. XCTAssertNotNil(entries[1].syncIdentifier)
  79. XCTAssertEqual(entries[1].syncVersion, 1)
  80. XCTAssertEqual(entries[1].startDate, secondCarbEntry.startDate)
  81. XCTAssertEqual(entries[1].quantity, secondCarbEntry.quantity)
  82. XCTAssertEqual(entries[1].foodType, secondCarbEntry.foodType)
  83. XCTAssertEqual(entries[1].absorptionTime, secondCarbEntry.absorptionTime)
  84. XCTAssertEqual(entries[1].createdByCurrentApp, true)
  85. XCTAssertEqual(entries[1].userCreatedDate, secondCarbEntry.date)
  86. XCTAssertNil(entries[1].userUpdatedDate)
  87. // Third
  88. XCTAssertNotNil(entries[2].uuid)
  89. XCTAssertEqual(entries[2].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  90. XCTAssertNotNil(entries[2].syncIdentifier)
  91. XCTAssertEqual(entries[2].syncVersion, 1)
  92. XCTAssertEqual(entries[2].startDate, thirdCarbEntry.startDate)
  93. XCTAssertEqual(entries[2].quantity, thirdCarbEntry.quantity)
  94. XCTAssertEqual(entries[2].foodType, thirdCarbEntry.foodType)
  95. XCTAssertEqual(entries[2].absorptionTime, thirdCarbEntry.absorptionTime)
  96. XCTAssertEqual(entries[2].createdByCurrentApp, true)
  97. XCTAssertEqual(entries[2].userCreatedDate, thirdCarbEntry.date)
  98. XCTAssertNil(entries[2].userUpdatedDate)
  99. }
  100. }
  101. }
  102. }
  103. }
  104. }
  105. }
  106. }
  107. wait(for: [getCarbEntriesCompletion], timeout: 2, enforceOrder: true)
  108. }
  109. // MARK: -
  110. func testAddCarbEntry() {
  111. let addCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: "Add", absorptionTime: .hours(3))
  112. let addHealthStoreHandler = expectation(description: "Add health store handler")
  113. let addCarbEntryCompletion = expectation(description: "Add carb entry completion")
  114. let addCarbEntryHandler = expectation(description: "Add carb entry handler")
  115. let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
  116. var handlerInvocation = 0
  117. var addUUID: UUID?
  118. var addSyncIdentifier: String?
  119. healthStore.setSaveHandler({ (objects, success, error) in
  120. XCTAssertEqual(1, objects.count)
  121. let sample = objects.first as! HKQuantitySample
  122. XCTAssertEqual(sample.absorptionTime, addCarbEntry.absorptionTime)
  123. XCTAssertEqual(sample.foodType, addCarbEntry.foodType)
  124. XCTAssertEqual(sample.quantity, addCarbEntry.quantity)
  125. XCTAssertEqual(sample.startDate, addCarbEntry.startDate)
  126. XCTAssertNotNil(sample.syncIdentifier)
  127. XCTAssertEqual(sample.syncVersion, 1)
  128. XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
  129. XCTAssertNil(sample.userUpdatedDate)
  130. addUUID = sample.uuid
  131. addSyncIdentifier = sample.syncIdentifier
  132. addHealthStoreHandler.fulfill()
  133. })
  134. carbStoreHasUpdatedCarbDataHandler = { (carbStore) in
  135. handlerInvocation += 1
  136. self.cacheStore.managedObjectContext.performAndWait {
  137. switch handlerInvocation {
  138. case 1:
  139. addCarbEntryHandler.fulfill()
  140. let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
  141. XCTAssertEqual(objects.count, 1)
  142. // Added object
  143. XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
  144. XCTAssertEqual(objects[0].createdByCurrentApp, true)
  145. XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
  146. XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
  147. XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
  148. XCTAssertEqual(objects[0].uuid, addUUID)
  149. XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  150. XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
  151. XCTAssertEqual(objects[0].syncVersion, 1)
  152. XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
  153. XCTAssertNil(objects[0].userUpdatedDate)
  154. XCTAssertNil(objects[0].userDeletedDate)
  155. XCTAssertEqual(objects[0].operation, .create)
  156. XCTAssertNotNil(objects[0].addedDate)
  157. XCTAssertNil(objects[0].supercededDate)
  158. XCTAssertGreaterThan(objects[0].anchorKey, 0)
  159. DispatchQueue.main.async {
  160. carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
  161. getCarbEntriesCompletion.fulfill()
  162. switch result {
  163. case .failure(let error):
  164. XCTFail("Unexpected failure: \(error)")
  165. case .success(let entries):
  166. XCTAssertEqual(entries.count, 1)
  167. // Added sample
  168. XCTAssertEqual(entries[0].uuid, addUUID)
  169. XCTAssertEqual(entries[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  170. XCTAssertEqual(entries[0].syncIdentifier, addSyncIdentifier)
  171. XCTAssertEqual(entries[0].syncVersion, 1)
  172. XCTAssertEqual(entries[0].startDate, addCarbEntry.startDate)
  173. XCTAssertEqual(entries[0].quantity, addCarbEntry.quantity)
  174. XCTAssertEqual(entries[0].foodType, addCarbEntry.foodType)
  175. XCTAssertEqual(entries[0].absorptionTime, addCarbEntry.absorptionTime)
  176. XCTAssertEqual(entries[0].createdByCurrentApp, true)
  177. XCTAssertEqual(entries[0].userCreatedDate, addCarbEntry.date)
  178. XCTAssertNil(entries[0].userUpdatedDate)
  179. }
  180. }
  181. }
  182. default:
  183. XCTFail("Unexpected handler invocation")
  184. }
  185. }
  186. }
  187. carbStore.addCarbEntry(addCarbEntry) { (result) in
  188. addCarbEntryCompletion.fulfill()
  189. }
  190. wait(for: [addHealthStoreHandler, addCarbEntryCompletion, addCarbEntryHandler, getCarbEntriesCompletion], timeout: 10, enforceOrder: true)
  191. }
  192. func testAddAndReplaceCarbEntry() {
  193. let addCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: "Add", absorptionTime: .hours(3))
  194. let replaceCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 15), startDate: Date(), foodType: "Replace", absorptionTime: .hours(4))
  195. let addHealthStoreHandler = expectation(description: "Add health store handler")
  196. let addCarbEntryCompletion = expectation(description: "Add carb entry completion")
  197. let addCarbEntryHandler = expectation(description: "Add carb entry handler")
  198. let updateHealthStoreHandler = expectation(description: "Update health store handler")
  199. let updateCarbEntryCompletion = expectation(description: "Update carb entry completion")
  200. let updateCarbEntryHandler = expectation(description: "Update carb entry handler")
  201. let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
  202. var handlerInvocation = 0
  203. var addUUID: UUID?
  204. var addSyncIdentifier: String?
  205. var addAnchorKey: Int64?
  206. var updateUUID: UUID?
  207. healthStore.setSaveHandler({ (objects, success, error) in
  208. XCTAssertEqual(1, objects.count)
  209. let sample = objects.first as! HKQuantitySample
  210. XCTAssertEqual(sample.absorptionTime, addCarbEntry.absorptionTime)
  211. XCTAssertEqual(sample.foodType, addCarbEntry.foodType)
  212. XCTAssertEqual(sample.quantity, addCarbEntry.quantity)
  213. XCTAssertEqual(sample.startDate, addCarbEntry.startDate)
  214. XCTAssertNotNil(sample.syncIdentifier)
  215. XCTAssertEqual(sample.syncVersion, 1)
  216. XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
  217. XCTAssertNil(sample.userUpdatedDate)
  218. addUUID = sample.uuid
  219. addSyncIdentifier = sample.syncIdentifier
  220. addHealthStoreHandler.fulfill()
  221. })
  222. carbStoreHasUpdatedCarbDataHandler = { (carbStore) in
  223. handlerInvocation += 1
  224. self.cacheStore.managedObjectContext.performAndWait {
  225. switch handlerInvocation {
  226. case 1:
  227. addCarbEntryHandler.fulfill()
  228. let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
  229. XCTAssertEqual(objects.count, 1)
  230. // Added object
  231. XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
  232. XCTAssertEqual(objects[0].createdByCurrentApp, true)
  233. XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
  234. XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
  235. XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
  236. XCTAssertEqual(objects[0].uuid, addUUID)
  237. XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  238. XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
  239. XCTAssertEqual(objects[0].syncVersion, 1)
  240. XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
  241. XCTAssertNil(objects[0].userUpdatedDate)
  242. XCTAssertNil(objects[0].userDeletedDate)
  243. XCTAssertEqual(objects[0].operation, .create)
  244. XCTAssertNotNil(objects[0].addedDate)
  245. XCTAssertNil(objects[0].supercededDate)
  246. XCTAssertGreaterThan(objects[0].anchorKey, 0)
  247. addAnchorKey = objects[0].anchorKey
  248. self.healthStore.setSaveHandler({ (objects, success, error) in
  249. XCTAssertEqual(1, objects.count)
  250. let sample = objects.first as! HKQuantitySample
  251. XCTAssertNotEqual(sample.uuid, addUUID)
  252. XCTAssertEqual(sample.absorptionTime, replaceCarbEntry.absorptionTime)
  253. XCTAssertEqual(sample.foodType, replaceCarbEntry.foodType)
  254. XCTAssertEqual(sample.quantity, replaceCarbEntry.quantity)
  255. XCTAssertEqual(sample.startDate, replaceCarbEntry.startDate)
  256. XCTAssertEqual(sample.syncIdentifier, addSyncIdentifier)
  257. XCTAssertEqual(sample.syncVersion, 2)
  258. XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
  259. XCTAssertEqual(sample.userUpdatedDate, replaceCarbEntry.date)
  260. updateUUID = sample.uuid
  261. updateHealthStoreHandler.fulfill()
  262. })
  263. self.carbStore.replaceCarbEntry(StoredCarbEntry(managedObject: objects[0]), withEntry: replaceCarbEntry) { (result) in
  264. updateCarbEntryCompletion.fulfill()
  265. }
  266. case 2:
  267. updateCarbEntryHandler.fulfill()
  268. let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all().sorted { $0.syncVersion! < $1.syncVersion! }
  269. XCTAssertEqual(objects.count, 2)
  270. // Added object, superceded
  271. XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
  272. XCTAssertEqual(objects[0].createdByCurrentApp, true)
  273. XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
  274. XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
  275. XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
  276. XCTAssertEqual(objects[0].uuid, addUUID)
  277. XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  278. XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
  279. XCTAssertEqual(objects[0].syncVersion, 1)
  280. XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
  281. XCTAssertNil(objects[0].userUpdatedDate)
  282. XCTAssertNil(objects[0].userDeletedDate)
  283. XCTAssertEqual(objects[0].operation, .create)
  284. XCTAssertNotNil(objects[0].addedDate)
  285. XCTAssertNotNil(objects[0].supercededDate)
  286. XCTAssertEqual(objects[0].anchorKey, addAnchorKey)
  287. // Updated object
  288. XCTAssertEqual(objects[1].absorptionTime, replaceCarbEntry.absorptionTime)
  289. XCTAssertEqual(objects[1].createdByCurrentApp, true)
  290. XCTAssertEqual(objects[1].foodType, replaceCarbEntry.foodType)
  291. XCTAssertEqual(objects[1].grams, replaceCarbEntry.quantity.doubleValue(for: .gram()))
  292. XCTAssertEqual(objects[1].startDate, replaceCarbEntry.startDate)
  293. XCTAssertEqual(objects[1].uuid, updateUUID)
  294. XCTAssertEqual(objects[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  295. XCTAssertEqual(objects[1].syncIdentifier, addSyncIdentifier)
  296. XCTAssertEqual(objects[1].syncVersion, 2)
  297. XCTAssertEqual(objects[1].userCreatedDate, addCarbEntry.date)
  298. XCTAssertEqual(objects[1].userUpdatedDate, replaceCarbEntry.date)
  299. XCTAssertNil(objects[1].userDeletedDate)
  300. XCTAssertEqual(objects[1].operation, .update)
  301. XCTAssertNotNil(objects[1].addedDate)
  302. XCTAssertNil(objects[1].supercededDate)
  303. XCTAssertGreaterThan(objects[1].anchorKey, addAnchorKey!)
  304. DispatchQueue.main.async {
  305. carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
  306. getCarbEntriesCompletion.fulfill()
  307. switch result {
  308. case .failure(let error):
  309. XCTFail("Unexpected failure: \(error)")
  310. case .success(let entries):
  311. XCTAssertEqual(entries.count, 1)
  312. // Updated sample
  313. XCTAssertEqual(entries[0].uuid, updateUUID)
  314. XCTAssertEqual(entries[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  315. XCTAssertEqual(entries[0].syncIdentifier, addSyncIdentifier)
  316. XCTAssertEqual(entries[0].syncVersion, 2)
  317. XCTAssertEqual(entries[0].startDate, replaceCarbEntry.startDate)
  318. XCTAssertEqual(entries[0].quantity, replaceCarbEntry.quantity)
  319. XCTAssertEqual(entries[0].foodType, replaceCarbEntry.foodType)
  320. XCTAssertEqual(entries[0].absorptionTime, replaceCarbEntry.absorptionTime)
  321. XCTAssertEqual(entries[0].createdByCurrentApp, true)
  322. XCTAssertEqual(entries[0].userCreatedDate, addCarbEntry.date)
  323. XCTAssertEqual(entries[0].userUpdatedDate, replaceCarbEntry.date)
  324. }
  325. }
  326. }
  327. default:
  328. XCTFail("Unexpected handler invocation")
  329. }
  330. }
  331. }
  332. carbStore.addCarbEntry(addCarbEntry) { (result) in
  333. addCarbEntryCompletion.fulfill()
  334. }
  335. wait(for: [addHealthStoreHandler, addCarbEntryCompletion, addCarbEntryHandler, updateHealthStoreHandler, updateCarbEntryCompletion, updateCarbEntryHandler, getCarbEntriesCompletion], timeout: 10, enforceOrder: true)
  336. }
  337. func testAddAndDeleteCarbEntry() {
  338. let addCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: "Add", absorptionTime: .hours(3))
  339. let addHealthStoreHandler = expectation(description: "Add health store handler")
  340. let addCarbEntryCompletion = expectation(description: "Add carb entry completion")
  341. let addCarbEntryHandler = expectation(description: "Add carb entry handler")
  342. let deleteHealthStoreHandler = expectation(description: "Delete health store handler")
  343. let deleteCarbEntryCompletion = expectation(description: "Delete carb entry completion")
  344. let deleteCarbEntryHandler = expectation(description: "Delete carb entry handler")
  345. let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
  346. var handlerInvocation = 0
  347. var addUUID: UUID?
  348. var addSyncIdentifier: String?
  349. var addAnchorKey: Int64?
  350. healthStore.setSaveHandler({ (objects, success, error) in
  351. XCTAssertEqual(1, objects.count)
  352. let sample = objects.first as! HKQuantitySample
  353. XCTAssertEqual(sample.absorptionTime, addCarbEntry.absorptionTime)
  354. XCTAssertEqual(sample.foodType, addCarbEntry.foodType)
  355. XCTAssertEqual(sample.quantity, addCarbEntry.quantity)
  356. XCTAssertEqual(sample.startDate, addCarbEntry.startDate)
  357. XCTAssertNotNil(sample.syncIdentifier)
  358. XCTAssertEqual(sample.syncVersion, 1)
  359. XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
  360. XCTAssertNil(sample.userUpdatedDate)
  361. addUUID = sample.uuid
  362. addSyncIdentifier = sample.syncIdentifier
  363. addHealthStoreHandler.fulfill()
  364. })
  365. carbStoreHasUpdatedCarbDataHandler = { (carbStore) in
  366. handlerInvocation += 1
  367. self.cacheStore.managedObjectContext.performAndWait {
  368. switch handlerInvocation {
  369. case 1:
  370. addCarbEntryHandler.fulfill()
  371. let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
  372. XCTAssertEqual(objects.count, 1)
  373. // Added object
  374. XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
  375. XCTAssertEqual(objects[0].createdByCurrentApp, true)
  376. XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
  377. XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
  378. XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
  379. XCTAssertEqual(objects[0].uuid, addUUID)
  380. XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  381. XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
  382. XCTAssertEqual(objects[0].syncVersion, 1)
  383. XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
  384. XCTAssertNil(objects[0].userUpdatedDate)
  385. XCTAssertNil(objects[0].userDeletedDate)
  386. XCTAssertEqual(objects[0].operation, .create)
  387. XCTAssertNotNil(objects[0].addedDate)
  388. XCTAssertNil(objects[0].supercededDate)
  389. XCTAssertGreaterThan(objects[0].anchorKey, 0)
  390. addUUID = objects[0].uuid
  391. addSyncIdentifier = objects[0].syncIdentifier
  392. addAnchorKey = objects[0].anchorKey
  393. self.healthStore.setDeletedObjectsHandler({ (objectType, predicate, success, count, error) in
  394. XCTAssertEqual(objectType, HealthKitSampleStore.carbType)
  395. XCTAssertEqual(predicate.predicateFormat, "UUID == \(addUUID!)")
  396. deleteHealthStoreHandler.fulfill()
  397. })
  398. self.carbStore.deleteCarbEntry(StoredCarbEntry(managedObject: objects[0])) { (result) in
  399. deleteCarbEntryCompletion.fulfill()
  400. }
  401. case 2:
  402. deleteCarbEntryHandler.fulfill()
  403. let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
  404. XCTAssertEqual(objects.count, 2)
  405. // Added object, superceded
  406. XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
  407. XCTAssertEqual(objects[0].createdByCurrentApp, true)
  408. XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
  409. XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
  410. XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
  411. XCTAssertEqual(objects[0].uuid, addUUID)
  412. XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  413. XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
  414. XCTAssertEqual(objects[0].syncVersion, 1)
  415. XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
  416. XCTAssertNil(objects[0].userUpdatedDate)
  417. XCTAssertNil(objects[0].userDeletedDate)
  418. XCTAssertEqual(objects[0].operation, .create)
  419. XCTAssertNotNil(objects[0].addedDate)
  420. XCTAssertNotNil(objects[0].supercededDate)
  421. XCTAssertEqual(objects[0].anchorKey, addAnchorKey)
  422. // Deleted object
  423. XCTAssertEqual(objects[1].absorptionTime, addCarbEntry.absorptionTime)
  424. XCTAssertEqual(objects[1].createdByCurrentApp, true)
  425. XCTAssertEqual(objects[1].foodType, addCarbEntry.foodType)
  426. XCTAssertEqual(objects[1].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
  427. XCTAssertEqual(objects[1].startDate, addCarbEntry.startDate)
  428. XCTAssertNil(objects[1].uuid)
  429. XCTAssertEqual(objects[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  430. XCTAssertEqual(objects[1].syncIdentifier, addSyncIdentifier)
  431. XCTAssertEqual(objects[1].syncVersion, 1)
  432. XCTAssertEqual(objects[1].userCreatedDate, addCarbEntry.date)
  433. XCTAssertNil(objects[1].userUpdatedDate)
  434. XCTAssertNotNil(objects[1].userDeletedDate)
  435. XCTAssertEqual(objects[1].operation, .delete)
  436. XCTAssertNotNil(objects[1].addedDate)
  437. XCTAssertNil(objects[1].supercededDate)
  438. XCTAssertGreaterThan(objects[1].anchorKey, addAnchorKey!)
  439. DispatchQueue.main.async {
  440. carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
  441. getCarbEntriesCompletion.fulfill()
  442. switch result {
  443. case .failure(let error):
  444. XCTFail("Unexpected failure: \(error)")
  445. case .success(let entries):
  446. XCTAssertEqual(entries.count, 0)
  447. }
  448. }
  449. }
  450. default:
  451. XCTFail("Unexpected handler invocation")
  452. }
  453. }
  454. }
  455. carbStore.addCarbEntry(addCarbEntry) { (result) in
  456. addCarbEntryCompletion.fulfill()
  457. }
  458. wait(for: [addHealthStoreHandler, addCarbEntryCompletion, addCarbEntryHandler, deleteHealthStoreHandler, deleteCarbEntryCompletion, deleteCarbEntryHandler, getCarbEntriesCompletion], timeout: 10, enforceOrder: true)
  459. }
  460. func testAddAndReplaceAndDeleteCarbEntry() {
  461. let addCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: "Add", absorptionTime: .hours(3))
  462. let replaceCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 15), startDate: Date(), foodType: "Replace", absorptionTime: .hours(4))
  463. let addHealthStoreHandler = expectation(description: "Add health store handler")
  464. let addCarbEntryCompletion = expectation(description: "Add carb entry completion")
  465. let addCarbEntryHandler = expectation(description: "Add carb entry handler")
  466. let updateHealthStoreHandler = expectation(description: "Update health store handler")
  467. let updateCarbEntryCompletion = expectation(description: "Update carb entry completion")
  468. let updateCarbEntryHandler = expectation(description: "Update carb entry handler")
  469. let deleteHealthStoreHandler = expectation(description: "Delete health store handler")
  470. let deleteCarbEntryCompletion = expectation(description: "Delete carb entry completion")
  471. let deleteCarbEntryHandler = expectation(description: "Delete carb entry handler")
  472. let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
  473. var handlerInvocation = 0
  474. var addUUID: UUID?
  475. var addSyncIdentifier: String?
  476. var addAnchorKey: Int64?
  477. var updateUUID: UUID?
  478. var updateAnchorKey: Int64?
  479. healthStore.setSaveHandler({ (objects, success, error) in
  480. XCTAssertEqual(1, objects.count)
  481. let sample = objects.first as! HKQuantitySample
  482. XCTAssertEqual(sample.absorptionTime, addCarbEntry.absorptionTime)
  483. XCTAssertEqual(sample.foodType, addCarbEntry.foodType)
  484. XCTAssertEqual(sample.quantity, addCarbEntry.quantity)
  485. XCTAssertEqual(sample.startDate, addCarbEntry.startDate)
  486. XCTAssertNotNil(sample.syncIdentifier)
  487. XCTAssertEqual(sample.syncVersion, 1)
  488. XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
  489. XCTAssertNil(sample.userUpdatedDate)
  490. addUUID = sample.uuid
  491. addSyncIdentifier = sample.syncIdentifier
  492. addHealthStoreHandler.fulfill()
  493. })
  494. carbStoreHasUpdatedCarbDataHandler = { (carbStore) in
  495. handlerInvocation += 1
  496. self.cacheStore.managedObjectContext.performAndWait {
  497. switch handlerInvocation {
  498. case 1:
  499. addCarbEntryHandler.fulfill()
  500. let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
  501. XCTAssertEqual(objects.count, 1)
  502. // Added object
  503. XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
  504. XCTAssertEqual(objects[0].createdByCurrentApp, true)
  505. XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
  506. XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
  507. XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
  508. XCTAssertEqual(objects[0].uuid, addUUID)
  509. XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  510. XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
  511. XCTAssertEqual(objects[0].syncVersion, 1)
  512. XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
  513. XCTAssertNil(objects[0].userUpdatedDate)
  514. XCTAssertNil(objects[0].userDeletedDate)
  515. XCTAssertEqual(objects[0].operation, .create)
  516. XCTAssertNotNil(objects[0].addedDate)
  517. XCTAssertNil(objects[0].supercededDate)
  518. XCTAssertGreaterThan(objects[0].anchorKey, 0)
  519. addAnchorKey = objects[0].anchorKey
  520. self.healthStore.setSaveHandler({ (objects, success, error) in
  521. XCTAssertEqual(1, objects.count)
  522. let sample = objects.first as! HKQuantitySample
  523. XCTAssertNotEqual(sample.uuid, addUUID)
  524. XCTAssertEqual(sample.absorptionTime, replaceCarbEntry.absorptionTime)
  525. XCTAssertEqual(sample.foodType, replaceCarbEntry.foodType)
  526. XCTAssertEqual(sample.quantity, replaceCarbEntry.quantity)
  527. XCTAssertEqual(sample.startDate, replaceCarbEntry.startDate)
  528. XCTAssertEqual(sample.syncIdentifier, addSyncIdentifier)
  529. XCTAssertEqual(sample.syncVersion, 2)
  530. XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
  531. XCTAssertEqual(sample.userUpdatedDate, replaceCarbEntry.date)
  532. updateUUID = sample.uuid
  533. updateHealthStoreHandler.fulfill()
  534. })
  535. self.carbStore.replaceCarbEntry(StoredCarbEntry(managedObject: objects[0]), withEntry: replaceCarbEntry) { (result) in
  536. updateCarbEntryCompletion.fulfill()
  537. }
  538. case 2:
  539. updateCarbEntryHandler.fulfill()
  540. let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all().sorted { $0.syncVersion! < $1.syncVersion! }
  541. XCTAssertEqual(objects.count, 2)
  542. // Added object, superceded
  543. XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
  544. XCTAssertEqual(objects[0].createdByCurrentApp, true)
  545. XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
  546. XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
  547. XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
  548. XCTAssertEqual(objects[0].uuid, addUUID)
  549. XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  550. XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
  551. XCTAssertEqual(objects[0].syncVersion, 1)
  552. XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
  553. XCTAssertNil(objects[0].userUpdatedDate)
  554. XCTAssertNil(objects[0].userDeletedDate)
  555. XCTAssertEqual(objects[0].operation, .create)
  556. XCTAssertNotNil(objects[0].addedDate)
  557. XCTAssertNotNil(objects[0].supercededDate)
  558. XCTAssertEqual(objects[0].anchorKey, addAnchorKey)
  559. // Updated object
  560. XCTAssertEqual(objects[1].absorptionTime, replaceCarbEntry.absorptionTime)
  561. XCTAssertEqual(objects[1].createdByCurrentApp, true)
  562. XCTAssertEqual(objects[1].foodType, replaceCarbEntry.foodType)
  563. XCTAssertEqual(objects[1].grams, replaceCarbEntry.quantity.doubleValue(for: .gram()))
  564. XCTAssertEqual(objects[1].startDate, replaceCarbEntry.startDate)
  565. XCTAssertEqual(objects[1].uuid, updateUUID)
  566. XCTAssertEqual(objects[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  567. XCTAssertEqual(objects[1].syncIdentifier, addSyncIdentifier)
  568. XCTAssertEqual(objects[1].syncVersion, 2)
  569. XCTAssertEqual(objects[1].userCreatedDate, addCarbEntry.date)
  570. XCTAssertEqual(objects[1].userUpdatedDate, replaceCarbEntry.date)
  571. XCTAssertNil(objects[1].userDeletedDate)
  572. XCTAssertEqual(objects[1].operation, .update)
  573. XCTAssertNotNil(objects[1].addedDate)
  574. XCTAssertNil(objects[1].supercededDate)
  575. XCTAssertGreaterThan(objects[1].anchorKey, addAnchorKey!)
  576. updateAnchorKey = objects[1].anchorKey
  577. self.healthStore.setDeletedObjectsHandler({ (objectType, predicate, success, count, error) in
  578. XCTAssertEqual(objectType, HealthKitSampleStore.carbType)
  579. XCTAssertEqual(predicate.predicateFormat, "UUID == \(updateUUID!)")
  580. deleteHealthStoreHandler.fulfill()
  581. })
  582. self.carbStore.deleteCarbEntry(StoredCarbEntry(managedObject: objects[1])) { (result) in
  583. deleteCarbEntryCompletion.fulfill()
  584. }
  585. case 3:
  586. deleteCarbEntryHandler.fulfill()
  587. let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all().sorted { $0.syncVersion! < $1.syncVersion! }
  588. XCTAssertEqual(objects.count, 3)
  589. // Added object, superceded
  590. XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
  591. XCTAssertEqual(objects[0].createdByCurrentApp, true)
  592. XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
  593. XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
  594. XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
  595. XCTAssertEqual(objects[0].uuid, addUUID)
  596. XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  597. XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
  598. XCTAssertEqual(objects[0].syncVersion, 1)
  599. XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
  600. XCTAssertNil(objects[0].userUpdatedDate)
  601. XCTAssertNil(objects[0].userDeletedDate)
  602. XCTAssertEqual(objects[0].operation, .create)
  603. XCTAssertNotNil(objects[0].addedDate)
  604. XCTAssertNotNil(objects[0].supercededDate)
  605. XCTAssertEqual(objects[0].anchorKey, addAnchorKey)
  606. // Updated object, superceded
  607. XCTAssertEqual(objects[1].absorptionTime, replaceCarbEntry.absorptionTime)
  608. XCTAssertEqual(objects[1].createdByCurrentApp, true)
  609. XCTAssertEqual(objects[1].foodType, replaceCarbEntry.foodType)
  610. XCTAssertEqual(objects[1].grams, replaceCarbEntry.quantity.doubleValue(for: .gram()))
  611. XCTAssertEqual(objects[1].startDate, replaceCarbEntry.startDate)
  612. XCTAssertEqual(objects[1].uuid, updateUUID)
  613. XCTAssertEqual(objects[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  614. XCTAssertEqual(objects[1].syncIdentifier, addSyncIdentifier)
  615. XCTAssertEqual(objects[1].syncVersion, 2)
  616. XCTAssertEqual(objects[1].userCreatedDate, addCarbEntry.date)
  617. XCTAssertEqual(objects[1].userUpdatedDate, replaceCarbEntry.date)
  618. XCTAssertNil(objects[1].userDeletedDate)
  619. XCTAssertEqual(objects[1].operation, .update)
  620. XCTAssertNotNil(objects[1].addedDate)
  621. XCTAssertNotNil(objects[1].supercededDate)
  622. XCTAssertEqual(objects[1].anchorKey, updateAnchorKey)
  623. // Deleted object
  624. XCTAssertEqual(objects[2].absorptionTime, replaceCarbEntry.absorptionTime)
  625. XCTAssertEqual(objects[2].createdByCurrentApp, true)
  626. XCTAssertEqual(objects[2].foodType, replaceCarbEntry.foodType)
  627. XCTAssertEqual(objects[2].grams, replaceCarbEntry.quantity.doubleValue(for: .gram()))
  628. XCTAssertEqual(objects[2].startDate, replaceCarbEntry.startDate)
  629. XCTAssertNil(objects[2].uuid)
  630. XCTAssertEqual(objects[2].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  631. XCTAssertEqual(objects[2].syncIdentifier, addSyncIdentifier)
  632. XCTAssertEqual(objects[2].syncVersion, 2)
  633. XCTAssertEqual(objects[2].userCreatedDate, addCarbEntry.date)
  634. XCTAssertEqual(objects[2].userUpdatedDate, replaceCarbEntry.date)
  635. XCTAssertNotNil(objects[2].userDeletedDate)
  636. XCTAssertEqual(objects[2].operation, .delete)
  637. XCTAssertNotNil(objects[2].addedDate)
  638. XCTAssertNil(objects[2].supercededDate)
  639. XCTAssertGreaterThan(objects[2].anchorKey, updateAnchorKey!)
  640. DispatchQueue.main.async {
  641. carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
  642. getCarbEntriesCompletion.fulfill()
  643. switch result {
  644. case .failure(let error):
  645. XCTFail("Unexpected failure: \(error)")
  646. case .success(let entries):
  647. XCTAssertEqual(entries.count, 0)
  648. }
  649. }
  650. }
  651. default:
  652. XCTFail("Unexpected handler invocation")
  653. }
  654. }
  655. }
  656. carbStore.addCarbEntry(addCarbEntry) { (result) in
  657. addCarbEntryCompletion.fulfill()
  658. }
  659. wait(for: [addHealthStoreHandler, addCarbEntryCompletion, addCarbEntryHandler, updateHealthStoreHandler, updateCarbEntryCompletion, updateCarbEntryHandler, deleteHealthStoreHandler, deleteCarbEntryCompletion, deleteCarbEntryHandler, getCarbEntriesCompletion], timeout: 10, enforceOrder: true)
  660. }
  661. // MARK: -
  662. func testGetSyncCarbObjects() {
  663. let firstCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(timeIntervalSinceNow: -1), foodType: "First", absorptionTime: .hours(5))
  664. let secondCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 20), startDate: Date(), foodType: "Second", absorptionTime: .hours(3))
  665. let thirdCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 30), startDate: Date(timeIntervalSinceNow: 1), foodType: "Third", absorptionTime: .minutes(30))
  666. let getSyncCarbObjectsCompletion = expectation(description: "Get sync carb objects completion")
  667. carbStore.addCarbEntry(firstCarbEntry) { (_) in
  668. DispatchQueue.main.async {
  669. self.carbStore.addCarbEntry(secondCarbEntry) { (_) in
  670. DispatchQueue.main.async {
  671. self.carbStore.addCarbEntry(thirdCarbEntry) { (_) in
  672. DispatchQueue.main.async {
  673. self.carbStore.getSyncCarbObjects(start: Date().addingTimeInterval(-.minutes(1))) { result in
  674. getSyncCarbObjectsCompletion.fulfill()
  675. switch result {
  676. case .failure(let error):
  677. XCTFail("Unexpected failure: \(error)")
  678. case .success(let objects):
  679. XCTAssertEqual(objects.count, 3)
  680. // First
  681. XCTAssertEqual(objects[0].absorptionTime, firstCarbEntry.absorptionTime)
  682. XCTAssertEqual(objects[0].createdByCurrentApp, true)
  683. XCTAssertEqual(objects[0].foodType, firstCarbEntry.foodType)
  684. XCTAssertEqual(objects[0].grams, firstCarbEntry.quantity.doubleValue(for: .gram()))
  685. XCTAssertEqual(objects[0].startDate, firstCarbEntry.startDate)
  686. XCTAssertNotNil(objects[0].uuid)
  687. XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  688. XCTAssertNotNil(objects[0].syncIdentifier)
  689. XCTAssertEqual(objects[0].syncVersion, 1)
  690. XCTAssertEqual(objects[0].userCreatedDate, firstCarbEntry.date)
  691. XCTAssertNil(objects[0].userUpdatedDate)
  692. XCTAssertNil(objects[0].userDeletedDate)
  693. XCTAssertEqual(objects[0].operation, .create)
  694. XCTAssertNotNil(objects[0].addedDate)
  695. XCTAssertNil(objects[0].supercededDate)
  696. // Second
  697. XCTAssertEqual(objects[1].absorptionTime, secondCarbEntry.absorptionTime)
  698. XCTAssertEqual(objects[1].createdByCurrentApp, true)
  699. XCTAssertEqual(objects[1].foodType, secondCarbEntry.foodType)
  700. XCTAssertEqual(objects[1].grams, secondCarbEntry.quantity.doubleValue(for: .gram()))
  701. XCTAssertEqual(objects[1].startDate, secondCarbEntry.startDate)
  702. XCTAssertNotNil(objects[1].uuid)
  703. XCTAssertEqual(objects[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  704. XCTAssertNotNil(objects[1].syncIdentifier)
  705. XCTAssertEqual(objects[1].syncVersion, 1)
  706. XCTAssertEqual(objects[1].userCreatedDate, secondCarbEntry.date)
  707. XCTAssertNil(objects[1].userUpdatedDate)
  708. XCTAssertNil(objects[1].userDeletedDate)
  709. XCTAssertEqual(objects[1].operation, .create)
  710. XCTAssertNotNil(objects[1].addedDate)
  711. XCTAssertNil(objects[1].supercededDate)
  712. // Third
  713. XCTAssertEqual(objects[2].absorptionTime, thirdCarbEntry.absorptionTime)
  714. XCTAssertEqual(objects[2].createdByCurrentApp, true)
  715. XCTAssertEqual(objects[2].foodType, thirdCarbEntry.foodType)
  716. XCTAssertEqual(objects[2].grams, thirdCarbEntry.quantity.doubleValue(for: .gram()))
  717. XCTAssertEqual(objects[2].startDate, thirdCarbEntry.startDate)
  718. XCTAssertNotNil(objects[2].uuid)
  719. XCTAssertEqual(objects[2].provenanceIdentifier, Bundle.main.bundleIdentifier!)
  720. XCTAssertNotNil(objects[2].syncIdentifier)
  721. XCTAssertEqual(objects[2].syncVersion, 1)
  722. XCTAssertEqual(objects[2].userCreatedDate, thirdCarbEntry.date)
  723. XCTAssertNil(objects[2].userUpdatedDate)
  724. XCTAssertNil(objects[2].userDeletedDate)
  725. XCTAssertEqual(objects[2].operation, .create)
  726. XCTAssertNotNil(objects[2].addedDate)
  727. XCTAssertNil(objects[2].supercededDate)
  728. }
  729. }
  730. }
  731. }
  732. }
  733. }
  734. }
  735. }
  736. wait(for: [getSyncCarbObjectsCompletion], timeout: 2, enforceOrder: true)
  737. }
  738. func testSetSyncCarbObjects() {
  739. let carbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(timeIntervalSinceNow: -1), foodType: "First", absorptionTime: .hours(5))
  740. let syncCarbObjects = [SyncCarbObject(absorptionTime: .hours(5),
  741. createdByCurrentApp: true,
  742. foodType: "Pizza",
  743. grams: 45,
  744. startDate: Date(timeIntervalSinceNow: -30),
  745. uuid: UUID(),
  746. provenanceIdentifier: "com.loopkit.Loop",
  747. syncIdentifier: UUID().uuidString,
  748. syncVersion: 4,
  749. userCreatedDate: Date(timeIntervalSinceNow: -35),
  750. userUpdatedDate: Date(timeIntervalSinceNow: -34),
  751. userDeletedDate: nil,
  752. operation: .update,
  753. addedDate: Date(timeIntervalSinceNow: -34),
  754. supercededDate: nil),
  755. SyncCarbObject(absorptionTime: .hours(3),
  756. createdByCurrentApp: false,
  757. foodType: "Pasta",
  758. grams: 25,
  759. startDate: Date(timeIntervalSinceNow: -15),
  760. uuid: UUID(),
  761. provenanceIdentifier: "com.abc.Example",
  762. syncIdentifier: UUID().uuidString,
  763. syncVersion: 1,
  764. userCreatedDate: Date(timeIntervalSinceNow: -16),
  765. userUpdatedDate: nil,
  766. userDeletedDate: nil,
  767. operation: .create,
  768. addedDate: Date(timeIntervalSinceNow: -16),
  769. supercededDate: nil),
  770. SyncCarbObject(absorptionTime: .minutes(30),
  771. createdByCurrentApp: true,
  772. foodType: "Sugar",
  773. grams: 15,
  774. startDate: Date(timeIntervalSinceNow: 0),
  775. uuid: UUID(),
  776. provenanceIdentifier: "com.loopkit.Loop",
  777. syncIdentifier: UUID().uuidString,
  778. syncVersion: 1,
  779. userCreatedDate: Date(timeIntervalSinceNow: -1),
  780. userUpdatedDate: nil,
  781. userDeletedDate: nil,
  782. operation: .create,
  783. addedDate: Date(timeIntervalSinceNow: -1),
  784. supercededDate: nil)
  785. ]
  786. let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
  787. // Add a carb entry first, that will be purged when setSyncCarbObjects is invoked
  788. carbStore.addCarbEntry(carbEntry) { (_) in
  789. DispatchQueue.main.async {
  790. self.carbStore.setSyncCarbObjects(syncCarbObjects) { (error) in
  791. XCTAssertNil(error)
  792. DispatchQueue.main.async {
  793. self.carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
  794. getCarbEntriesCompletion.fulfill()
  795. switch result {
  796. case .failure(let error):
  797. XCTFail("Unexpected failure: \(error)")
  798. case .success(let entries):
  799. XCTAssertEqual(entries.count, 3)
  800. for index in 0..<3 {
  801. XCTAssertEqual(entries[index].uuid, syncCarbObjects[index].uuid)
  802. XCTAssertEqual(entries[index].provenanceIdentifier, syncCarbObjects[index].provenanceIdentifier)
  803. XCTAssertEqual(entries[index].syncIdentifier, syncCarbObjects[index].syncIdentifier)
  804. XCTAssertEqual(entries[index].syncVersion, syncCarbObjects[index].syncVersion)
  805. XCTAssertEqual(entries[index].startDate, syncCarbObjects[index].startDate)
  806. XCTAssertEqual(entries[index].quantity, syncCarbObjects[index].quantity)
  807. XCTAssertEqual(entries[index].foodType, syncCarbObjects[index].foodType)
  808. XCTAssertEqual(entries[index].absorptionTime, syncCarbObjects[index].absorptionTime)
  809. XCTAssertEqual(entries[index].createdByCurrentApp, syncCarbObjects[index].createdByCurrentApp)
  810. XCTAssertEqual(entries[index].userCreatedDate, syncCarbObjects[index].userCreatedDate)
  811. XCTAssertEqual(entries[index].userCreatedDate, syncCarbObjects[index].userCreatedDate)
  812. }
  813. }
  814. }
  815. }
  816. }
  817. }
  818. }
  819. wait(for: [getCarbEntriesCompletion], timeout: 2, enforceOrder: true)
  820. }
  821. // MARK: -
  822. private func generateSyncIdentifier() -> String {
  823. return UUID().uuidString
  824. }
  825. }
  826. class CarbStoreQueryAnchorTests: XCTestCase {
  827. var rawValue: CarbStore.QueryAnchor.RawValue = [
  828. "anchorKey": Int64(123)
  829. ]
  830. func testInitializerDefault() {
  831. let queryAnchor = CarbStore.QueryAnchor()
  832. XCTAssertEqual(queryAnchor.anchorKey, 0)
  833. }
  834. func testInitializerRawValue() {
  835. let queryAnchor = CarbStore.QueryAnchor(rawValue: rawValue)
  836. XCTAssertNotNil(queryAnchor)
  837. XCTAssertEqual(queryAnchor?.anchorKey, 123)
  838. }
  839. func testInitializerRawValueMissingAnchorKey() {
  840. rawValue["anchorKey"] = nil
  841. XCTAssertNil(CarbStore.QueryAnchor(rawValue: rawValue))
  842. }
  843. func testInitializerRawValueInvalidAnchorKey() {
  844. rawValue["anchorKey"] = "123"
  845. XCTAssertNil(CarbStore.QueryAnchor(rawValue: rawValue))
  846. }
  847. func testInitializerRawValueIgnoresDeprecatedStoredModificationCounter() {
  848. rawValue["storedModificationCounter"] = Int64(456)
  849. let queryAnchor = CarbStore.QueryAnchor(rawValue: rawValue)
  850. XCTAssertNotNil(queryAnchor)
  851. XCTAssertEqual(queryAnchor?.anchorKey, 123)
  852. }
  853. func testInitializerRawValueUsesDeprecatedStoredModificationCounter() {
  854. rawValue["anchorKey"] = nil
  855. rawValue["storedModificationCounter"] = Int64(456)
  856. let queryAnchor = CarbStore.QueryAnchor(rawValue: rawValue)
  857. XCTAssertNotNil(queryAnchor)
  858. XCTAssertEqual(queryAnchor?.anchorKey, 456)
  859. }
  860. func testRawValueWithDefault() {
  861. let rawValue = CarbStore.QueryAnchor().rawValue
  862. XCTAssertEqual(rawValue.count, 1)
  863. XCTAssertEqual(rawValue["anchorKey"] as? Int64, Int64(0))
  864. }
  865. func testRawValueWithNonDefault() {
  866. var queryAnchor = CarbStore.QueryAnchor()
  867. queryAnchor.anchorKey = 123
  868. let rawValue = queryAnchor.rawValue
  869. XCTAssertEqual(rawValue.count, 1)
  870. XCTAssertEqual(rawValue["anchorKey"] as? Int64, Int64(123))
  871. }
  872. }
  873. class CarbStoreQueryTests: PersistenceControllerTestCase {
  874. var carbStore: CarbStore!
  875. var completion: XCTestExpectation!
  876. var queryAnchor: CarbStore.QueryAnchor!
  877. var limit: Int!
  878. override func setUp() {
  879. super.setUp()
  880. let healthStore = HKHealthStoreMock()
  881. let hkSampleStore = HealthKitSampleStore(healthStore: healthStore, type: HealthKitSampleStore.carbType)
  882. carbStore = CarbStore(
  883. healthKitSampleStore: hkSampleStore,
  884. cacheStore: cacheStore,
  885. cacheLength: .hours(24),
  886. defaultAbsorptionTimes: (fast: .minutes(30), medium: .hours(3), slow: .hours(5)),
  887. provenanceIdentifier: Bundle.main.bundleIdentifier!)
  888. let semaphore = DispatchSemaphore(value: 0)
  889. cacheStore.onReady { (error) in
  890. semaphore.signal()
  891. }
  892. semaphore.wait()
  893. completion = expectation(description: "Completion")
  894. queryAnchor = CarbStore.QueryAnchor()
  895. limit = Int.max
  896. }
  897. override func tearDown() {
  898. limit = nil
  899. queryAnchor = nil
  900. completion = nil
  901. carbStore = nil
  902. super.tearDown()
  903. }
  904. func testEmptyWithDefaultQueryAnchor() {
  905. carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  906. switch result {
  907. case .failure(let error):
  908. XCTFail("Unexpected failure: \(error)")
  909. case .success(let anchor, let created, let updated, let deleted):
  910. XCTAssertEqual(anchor.anchorKey, 0)
  911. XCTAssertEqual(created.count, 0)
  912. XCTAssertEqual(updated.count, 0)
  913. XCTAssertEqual(deleted.count, 0)
  914. }
  915. self.completion.fulfill()
  916. }
  917. wait(for: [completion], timeout: 2, enforceOrder: true)
  918. }
  919. func testEmptyWithMissingQueryAnchor() {
  920. queryAnchor = nil
  921. carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  922. switch result {
  923. case .failure(let error):
  924. XCTFail("Unexpected failure: \(error)")
  925. case .success(let anchor, let created, let updated, let deleted):
  926. XCTAssertEqual(anchor.anchorKey, 0)
  927. XCTAssertEqual(created.count, 0)
  928. XCTAssertEqual(updated.count, 0)
  929. XCTAssertEqual(deleted.count, 0)
  930. }
  931. self.completion.fulfill()
  932. }
  933. wait(for: [completion], timeout: 2, enforceOrder: true)
  934. }
  935. func testEmptyWithNonDefaultQueryAnchor() {
  936. queryAnchor.anchorKey = 1
  937. carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  938. switch result {
  939. case .failure(let error):
  940. XCTFail("Unexpected failure: \(error)")
  941. case .success(let anchor, let created, let updated, let deleted):
  942. XCTAssertEqual(anchor.anchorKey, 1)
  943. XCTAssertEqual(created.count, 0)
  944. XCTAssertEqual(updated.count, 0)
  945. XCTAssertEqual(deleted.count, 0)
  946. }
  947. self.completion.fulfill()
  948. }
  949. wait(for: [completion], timeout: 2, enforceOrder: true)
  950. }
  951. func testDataWithUnusedQueryAnchor() {
  952. let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
  953. addData(withSyncIdentifiers: syncIdentifiers)
  954. carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  955. switch result {
  956. case .failure(let error):
  957. XCTFail("Unexpected failure: \(error)")
  958. case .success(let anchor, let created, let updated, let deleted):
  959. XCTAssertEqual(anchor.anchorKey, 3)
  960. XCTAssertEqual(created.count, 1)
  961. XCTAssertEqual(created[0].syncIdentifier, syncIdentifiers[0])
  962. XCTAssertEqual(created[0].syncVersion, 0)
  963. XCTAssertEqual(updated.count, 1)
  964. XCTAssertEqual(updated[0].syncIdentifier, syncIdentifiers[1])
  965. XCTAssertEqual(updated[0].syncVersion, 1)
  966. XCTAssertEqual(deleted.count, 1)
  967. XCTAssertEqual(deleted[0].syncIdentifier, syncIdentifiers[2])
  968. XCTAssertEqual(deleted[0].syncVersion, 2)
  969. }
  970. self.completion.fulfill()
  971. }
  972. wait(for: [completion], timeout: 2, enforceOrder: true)
  973. }
  974. func testDataWithStaleQueryAnchor() {
  975. let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
  976. addData(withSyncIdentifiers: syncIdentifiers)
  977. queryAnchor.anchorKey = 2
  978. carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  979. switch result {
  980. case .failure(let error):
  981. XCTFail("Unexpected failure: \(error)")
  982. case .success(let anchor, let created, let updated, let deleted):
  983. XCTAssertEqual(anchor.anchorKey, 3)
  984. XCTAssertEqual(created.count, 0)
  985. XCTAssertEqual(updated.count, 0)
  986. XCTAssertEqual(deleted.count, 1)
  987. XCTAssertEqual(deleted[0].syncIdentifier, syncIdentifiers[2])
  988. XCTAssertEqual(deleted[0].syncVersion, 2)
  989. }
  990. self.completion.fulfill()
  991. }
  992. wait(for: [completion], timeout: 2, enforceOrder: true)
  993. }
  994. func testDataWithCurrentQueryAnchor() {
  995. let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
  996. addData(withSyncIdentifiers: syncIdentifiers)
  997. queryAnchor.anchorKey = 3
  998. carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  999. switch result {
  1000. case .failure(let error):
  1001. XCTFail("Unexpected failure: \(error)")
  1002. case .success(let anchor, let created, let updated, let deleted):
  1003. XCTAssertEqual(anchor.anchorKey, 3)
  1004. XCTAssertEqual(created.count, 0)
  1005. XCTAssertEqual(updated.count, 0)
  1006. XCTAssertEqual(deleted.count, 0)
  1007. }
  1008. self.completion.fulfill()
  1009. }
  1010. wait(for: [completion], timeout: 2, enforceOrder: true)
  1011. }
  1012. func testDataWithLimitZero() {
  1013. let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
  1014. addData(withSyncIdentifiers: syncIdentifiers)
  1015. limit = 0
  1016. carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  1017. switch result {
  1018. case .failure(let error):
  1019. XCTFail("Unexpected failure: \(error)")
  1020. case .success(let anchor, let created, let updated, let deleted):
  1021. XCTAssertEqual(anchor.anchorKey, 0)
  1022. XCTAssertEqual(created.count, 0)
  1023. XCTAssertEqual(updated.count, 0)
  1024. XCTAssertEqual(deleted.count, 0)
  1025. }
  1026. self.completion.fulfill()
  1027. }
  1028. wait(for: [completion], timeout: 2, enforceOrder: true)
  1029. }
  1030. func testDataWithLimitCoveredByData() {
  1031. let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
  1032. addData(withSyncIdentifiers: syncIdentifiers)
  1033. limit = 2
  1034. carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  1035. switch result {
  1036. case .failure(let error):
  1037. XCTFail("Unexpected failure: \(error)")
  1038. case .success(let anchor, let created, let updated, let deleted):
  1039. XCTAssertEqual(anchor.anchorKey, 2)
  1040. XCTAssertEqual(created.count, 1)
  1041. XCTAssertEqual(created[0].syncIdentifier, syncIdentifiers[0])
  1042. XCTAssertEqual(created[0].syncVersion, 0)
  1043. XCTAssertEqual(updated.count, 1)
  1044. XCTAssertEqual(updated[0].syncIdentifier, syncIdentifiers[1])
  1045. XCTAssertEqual(updated[0].syncVersion, 1)
  1046. XCTAssertEqual(deleted.count, 0)
  1047. }
  1048. self.completion.fulfill()
  1049. }
  1050. wait(for: [completion], timeout: 2, enforceOrder: true)
  1051. }
  1052. private func addData(withSyncIdentifiers syncIdentifiers: [String]) {
  1053. cacheStore.managedObjectContext.performAndWait {
  1054. for (index, syncIdentifier) in syncIdentifiers.enumerated() {
  1055. let cachedCarbObject = CachedCarbObject(context: self.cacheStore.managedObjectContext)
  1056. cachedCarbObject.createdByCurrentApp = true
  1057. cachedCarbObject.startDate = Date()
  1058. cachedCarbObject.uuid = UUID()
  1059. cachedCarbObject.syncIdentifier = syncIdentifier
  1060. cachedCarbObject.syncVersion = index
  1061. cachedCarbObject.operation = Operation(rawValue: index % Operation.allCases.count)!
  1062. cachedCarbObject.addedDate = Date()
  1063. self.cacheStore.save()
  1064. }
  1065. }
  1066. }
  1067. private func generateSyncIdentifier() -> String {
  1068. return UUID().uuidString
  1069. }
  1070. }
  1071. class CarbStoreCriticalEventLogTests: PersistenceControllerTestCase {
  1072. var carbStore: CarbStore!
  1073. var outputStream: MockOutputStream!
  1074. var progress: Progress!
  1075. override func setUp() {
  1076. super.setUp()
  1077. let objects = [SyncCarbObject(absorptionTime: nil, createdByCurrentApp: true, foodType: nil, grams: 11, startDate: dateFormatter.date(from: "2100-01-02T03:08:00Z")!, uuid: nil, provenanceIdentifier: Bundle.main.bundleIdentifier!, syncIdentifier: nil, syncVersion: nil, userCreatedDate: nil, userUpdatedDate: nil, userDeletedDate: nil, operation: .create, addedDate: dateFormatter.date(from: "2100-01-02T03:08:00Z")!, supercededDate: nil),
  1078. SyncCarbObject(absorptionTime: nil, createdByCurrentApp: true, foodType: nil, grams: 12, startDate: dateFormatter.date(from: "2100-01-02T03:10:00Z")!, uuid: nil, provenanceIdentifier: Bundle.main.bundleIdentifier!, syncIdentifier: nil, syncVersion: nil, userCreatedDate: nil, userUpdatedDate: nil, userDeletedDate: nil, operation: .create, addedDate: dateFormatter.date(from: "2100-01-02T03:10:00Z")!, supercededDate: nil),
  1079. SyncCarbObject(absorptionTime: nil, createdByCurrentApp: true, foodType: nil, grams: 13, startDate: dateFormatter.date(from: "2100-01-02T03:04:00Z")!, uuid: nil, provenanceIdentifier: Bundle.main.bundleIdentifier!, syncIdentifier: nil, syncVersion: nil, userCreatedDate: nil, userUpdatedDate: nil, userDeletedDate: nil, operation: .create, addedDate: dateFormatter.date(from: "2100-01-02T02:04:00Z")!, supercededDate: dateFormatter.date(from: "2100-01-02T03:04:00Z")!),
  1080. SyncCarbObject(absorptionTime: nil, createdByCurrentApp: true, foodType: nil, grams: 14, startDate: dateFormatter.date(from: "2100-01-02T03:06:00Z")!, uuid: nil, provenanceIdentifier: Bundle.main.bundleIdentifier!, syncIdentifier: nil, syncVersion: nil, userCreatedDate: nil, userUpdatedDate: nil, userDeletedDate: nil, operation: .create, addedDate: dateFormatter.date(from: "2100-01-02T03:06:00Z")!, supercededDate: nil),
  1081. SyncCarbObject(absorptionTime: nil, createdByCurrentApp: true, foodType: nil, grams: 15, startDate: dateFormatter.date(from: "2100-01-02T03:02:00Z")!, uuid: nil, provenanceIdentifier: Bundle.main.bundleIdentifier!, syncIdentifier: nil, syncVersion: nil, userCreatedDate: nil, userUpdatedDate: nil, userDeletedDate: nil, operation: .create, addedDate: dateFormatter.date(from: "2100-01-02T03:02:00Z")!, supercededDate: nil)]
  1082. let healthStore = HKHealthStoreMock()
  1083. let hkSampleStore = HealthKitSampleStore(
  1084. healthStore: healthStore,
  1085. type: HealthKitSampleStore.carbType,
  1086. observationEnabled: false)
  1087. carbStore = CarbStore(
  1088. healthKitSampleStore: hkSampleStore,
  1089. cacheStore: cacheStore,
  1090. cacheLength: .hours(24),
  1091. defaultAbsorptionTimes: (fast: .minutes(30), medium: .hours(3), slow: .hours(5)),
  1092. provenanceIdentifier: Bundle.main.bundleIdentifier!)
  1093. let dispatchGroup = DispatchGroup()
  1094. dispatchGroup.enter()
  1095. carbStore.setSyncCarbObjects(objects) { error in
  1096. XCTAssertNil(error)
  1097. dispatchGroup.leave()
  1098. }
  1099. dispatchGroup.wait()
  1100. outputStream = MockOutputStream()
  1101. progress = Progress()
  1102. }
  1103. override func tearDown() {
  1104. carbStore = nil
  1105. super.tearDown()
  1106. }
  1107. func testExportProgressTotalUnitCount() {
  1108. switch carbStore.exportProgressTotalUnitCount(startDate: dateFormatter.date(from: "2100-01-02T03:03:00Z")!,
  1109. endDate: dateFormatter.date(from: "2100-01-02T03:09:00Z")!) {
  1110. case .failure(let error):
  1111. XCTFail("Unexpected failure: \(error)")
  1112. case .success(let progressTotalUnitCount):
  1113. XCTAssertEqual(progressTotalUnitCount, 3 * 1)
  1114. }
  1115. }
  1116. func testExportProgressTotalUnitCountEmpty() {
  1117. switch carbStore.exportProgressTotalUnitCount(startDate: dateFormatter.date(from: "2100-01-02T03:00:00Z")!,
  1118. endDate: dateFormatter.date(from: "2100-01-02T03:01:00Z")!) {
  1119. case .failure(let error):
  1120. XCTFail("Unexpected failure: \(error)")
  1121. case .success(let progressTotalUnitCount):
  1122. XCTAssertEqual(progressTotalUnitCount, 0)
  1123. }
  1124. }
  1125. func testExport() {
  1126. XCTAssertNil(carbStore.export(startDate: dateFormatter.date(from: "2100-01-02T03:03:00Z")!,
  1127. endDate: dateFormatter.date(from: "2100-01-02T03:09:00Z")!,
  1128. to: outputStream,
  1129. progress: progress))
  1130. XCTAssertEqual(outputStream.string, """
  1131. [
  1132. {"addedDate":"2100-01-02T03:08:00.000Z","anchorKey":1,"createdByCurrentApp":true,"grams":11,"operation":0,"provenanceIdentifier":"com.apple.dt.xctest.tool","startDate":"2100-01-02T03:08:00.000Z"},
  1133. {"addedDate":"2100-01-02T02:04:00.000Z","anchorKey":3,"createdByCurrentApp":true,"grams":13,"operation":0,"provenanceIdentifier":"com.apple.dt.xctest.tool","startDate":"2100-01-02T03:04:00.000Z","supercededDate":"2100-01-02T03:04:00.000Z"},
  1134. {"addedDate":"2100-01-02T03:06:00.000Z","anchorKey":4,"createdByCurrentApp":true,"grams":14,"operation":0,"provenanceIdentifier":"com.apple.dt.xctest.tool","startDate":"2100-01-02T03:06:00.000Z"}
  1135. ]
  1136. """
  1137. )
  1138. XCTAssertEqual(progress.completedUnitCount, 3 * 1)
  1139. }
  1140. func testExportEmpty() {
  1141. XCTAssertNil(carbStore.export(startDate: dateFormatter.date(from: "2100-01-02T03:00:00Z")!,
  1142. endDate: dateFormatter.date(from: "2100-01-02T03:01:00Z")!,
  1143. to: outputStream,
  1144. progress: progress))
  1145. XCTAssertEqual(outputStream.string, "[]")
  1146. XCTAssertEqual(progress.completedUnitCount, 0)
  1147. }
  1148. func testExportCancelled() {
  1149. progress.cancel()
  1150. XCTAssertEqual(carbStore.export(startDate: dateFormatter.date(from: "2100-01-02T03:03:00Z")!,
  1151. endDate: dateFormatter.date(from: "2100-01-02T03:09:00Z")!,
  1152. to: outputStream,
  1153. progress: progress) as? CriticalEventLogError, CriticalEventLogError.cancelled)
  1154. }
  1155. private let dateFormatter = ISO8601DateFormatter()
  1156. }