CarbStoreTests.swift 71 KB

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