| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347 |
- //
- // CarbStoreTests.swift
- // LoopKitTests
- //
- // Copyright © 2018 LoopKit Authors. All rights reserved.
- //
- import XCTest
- import HealthKit
- import CoreData
- @testable import LoopKit
- class CarbStorePersistenceTests: PersistenceControllerTestCase, CarbStoreDelegate {
-
- var healthStore: HKHealthStoreMock!
- var carbStore: CarbStore!
-
- override func setUp() {
- super.setUp()
- healthStore = HKHealthStoreMock()
- carbStore = CarbStore(
- healthStore: healthStore,
- cacheStore: cacheStore,
- cacheLength: .hours(24),
- defaultAbsorptionTimes: (fast: .minutes(30), medium: .hours(3), slow: .hours(5)),
- observationInterval: 0,
- provenanceIdentifier: Bundle.main.bundleIdentifier!)
- carbStore.testQueryStore = healthStore
- carbStore.delegate = self
- let semaphore = DispatchSemaphore(value: 0)
- cacheStore.onReady { (error) in
- semaphore.signal()
- }
- semaphore.wait()
- }
-
- override func tearDown() {
- carbStore.delegate = nil
- carbStore = nil
- healthStore = nil
-
- carbStoreHasUpdatedCarbDataHandler = nil
-
- super.tearDown()
- }
-
- // MARK: - CarbStoreDelegate
-
- var carbStoreHasUpdatedCarbDataHandler: ((_ : CarbStore) -> Void)?
-
- func carbStoreHasUpdatedCarbData(_ carbStore: CarbStore) {
- carbStoreHasUpdatedCarbDataHandler?(carbStore)
- }
-
- func carbStore(_ carbStore: CarbStore, didError error: CarbStore.CarbStoreError) {}
-
- // MARK: -
-
- func testGetCarbEntriesAfterAdd() {
- let firstCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(timeIntervalSinceNow: -1), foodType: "First", absorptionTime: .hours(5))
- let secondCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 20), startDate: Date(), foodType: "Second", absorptionTime: .hours(3))
- let thirdCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 30), startDate: Date(timeIntervalSinceNow: 1), foodType: "Third", absorptionTime: .minutes(30))
- let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
- carbStore.addCarbEntry(firstCarbEntry) { (_) in
- DispatchQueue.main.async {
- self.carbStore.addCarbEntry(secondCarbEntry) { (_) in
- DispatchQueue.main.async {
- self.carbStore.addCarbEntry(thirdCarbEntry) { (_) in
- DispatchQueue.main.async {
- self.carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
- getCarbEntriesCompletion.fulfill()
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let entries):
- XCTAssertEqual(entries.count, 3)
- // First
- XCTAssertNotNil(entries[0].uuid)
- XCTAssertEqual(entries[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertNotNil(entries[0].syncIdentifier)
- XCTAssertEqual(entries[0].syncVersion, 1)
- XCTAssertEqual(entries[0].startDate, firstCarbEntry.startDate)
- XCTAssertEqual(entries[0].quantity, firstCarbEntry.quantity)
- XCTAssertEqual(entries[0].foodType, firstCarbEntry.foodType)
- XCTAssertEqual(entries[0].absorptionTime, firstCarbEntry.absorptionTime)
- XCTAssertEqual(entries[0].createdByCurrentApp, true)
- XCTAssertEqual(entries[0].userCreatedDate, firstCarbEntry.date)
- XCTAssertNil(entries[0].userUpdatedDate)
- // Second
- XCTAssertNotNil(entries[1].uuid)
- XCTAssertEqual(entries[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertNotNil(entries[1].syncIdentifier)
- XCTAssertEqual(entries[1].syncVersion, 1)
- XCTAssertEqual(entries[1].startDate, secondCarbEntry.startDate)
- XCTAssertEqual(entries[1].quantity, secondCarbEntry.quantity)
- XCTAssertEqual(entries[1].foodType, secondCarbEntry.foodType)
- XCTAssertEqual(entries[1].absorptionTime, secondCarbEntry.absorptionTime)
- XCTAssertEqual(entries[1].createdByCurrentApp, true)
- XCTAssertEqual(entries[1].userCreatedDate, secondCarbEntry.date)
- XCTAssertNil(entries[1].userUpdatedDate)
- // Third
- XCTAssertNotNil(entries[2].uuid)
- XCTAssertEqual(entries[2].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertNotNil(entries[2].syncIdentifier)
- XCTAssertEqual(entries[2].syncVersion, 1)
- XCTAssertEqual(entries[2].startDate, thirdCarbEntry.startDate)
- XCTAssertEqual(entries[2].quantity, thirdCarbEntry.quantity)
- XCTAssertEqual(entries[2].foodType, thirdCarbEntry.foodType)
- XCTAssertEqual(entries[2].absorptionTime, thirdCarbEntry.absorptionTime)
- XCTAssertEqual(entries[2].createdByCurrentApp, true)
- XCTAssertEqual(entries[2].userCreatedDate, thirdCarbEntry.date)
- XCTAssertNil(entries[2].userUpdatedDate)
- }
- }
- }
- }
- }
- }
- }
- }
- wait(for: [getCarbEntriesCompletion], timeout: 2, enforceOrder: true)
- }
- // MARK: -
- func testAddCarbEntry() {
- let addCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: "Add", absorptionTime: .hours(3))
- let addHealthStoreHandler = expectation(description: "Add health store handler")
- let addCarbEntryCompletion = expectation(description: "Add carb entry completion")
- let addCarbEntryHandler = expectation(description: "Add carb entry handler")
- let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
- var handlerInvocation = 0
- var addUUID: UUID?
- var addSyncIdentifier: String?
- healthStore.setSaveHandler({ (objects, success, error) in
- XCTAssertEqual(1, objects.count)
- let sample = objects.first as! HKQuantitySample
- XCTAssertEqual(sample.absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(sample.foodType, addCarbEntry.foodType)
- XCTAssertEqual(sample.quantity, addCarbEntry.quantity)
- XCTAssertEqual(sample.startDate, addCarbEntry.startDate)
- XCTAssertNotNil(sample.syncIdentifier)
- XCTAssertEqual(sample.syncVersion, 1)
- XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
- XCTAssertNil(sample.userUpdatedDate)
- addUUID = sample.uuid
- addSyncIdentifier = sample.syncIdentifier
- addHealthStoreHandler.fulfill()
- })
- carbStoreHasUpdatedCarbDataHandler = { (carbStore) in
- handlerInvocation += 1
- self.cacheStore.managedObjectContext.performAndWait {
- switch handlerInvocation {
- case 1:
- addCarbEntryHandler.fulfill()
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(objects.count, 1)
- // Added object
- XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(objects[0].createdByCurrentApp, true)
- XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
- XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
- XCTAssertEqual(objects[0].uuid, addUUID)
- XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[0].syncVersion, 1)
- XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
- XCTAssertNil(objects[0].userUpdatedDate)
- XCTAssertNil(objects[0].userDeletedDate)
- XCTAssertEqual(objects[0].operation, .create)
- XCTAssertNotNil(objects[0].addedDate)
- XCTAssertNil(objects[0].supercededDate)
- XCTAssertGreaterThan(objects[0].anchorKey, 0)
- DispatchQueue.main.async {
- carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
- getCarbEntriesCompletion.fulfill()
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let entries):
- XCTAssertEqual(entries.count, 1)
- // Added sample
- XCTAssertEqual(entries[0].uuid, addUUID)
- XCTAssertEqual(entries[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(entries[0].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(entries[0].syncVersion, 1)
- XCTAssertEqual(entries[0].startDate, addCarbEntry.startDate)
- XCTAssertEqual(entries[0].quantity, addCarbEntry.quantity)
- XCTAssertEqual(entries[0].foodType, addCarbEntry.foodType)
- XCTAssertEqual(entries[0].absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(entries[0].createdByCurrentApp, true)
- XCTAssertEqual(entries[0].userCreatedDate, addCarbEntry.date)
- XCTAssertNil(entries[0].userUpdatedDate)
- }
- }
- }
- default:
- XCTFail("Unexpected handler invocation")
- }
- }
- }
- carbStore.addCarbEntry(addCarbEntry) { (result) in
- addCarbEntryCompletion.fulfill()
- }
- wait(for: [addHealthStoreHandler, addCarbEntryCompletion, addCarbEntryHandler, getCarbEntriesCompletion], timeout: 10, enforceOrder: true)
- }
- func testAddAndReplaceCarbEntry() {
- let addCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: "Add", absorptionTime: .hours(3))
- let replaceCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 15), startDate: Date(), foodType: "Replace", absorptionTime: .hours(4))
- let addHealthStoreHandler = expectation(description: "Add health store handler")
- let addCarbEntryCompletion = expectation(description: "Add carb entry completion")
- let addCarbEntryHandler = expectation(description: "Add carb entry handler")
- let updateHealthStoreHandler = expectation(description: "Update health store handler")
- let updateCarbEntryCompletion = expectation(description: "Update carb entry completion")
- let updateCarbEntryHandler = expectation(description: "Update carb entry handler")
- let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
- var handlerInvocation = 0
- var addUUID: UUID?
- var addSyncIdentifier: String?
- var addAnchorKey: Int64?
- var updateUUID: UUID?
- healthStore.setSaveHandler({ (objects, success, error) in
- XCTAssertEqual(1, objects.count)
- let sample = objects.first as! HKQuantitySample
- XCTAssertEqual(sample.absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(sample.foodType, addCarbEntry.foodType)
- XCTAssertEqual(sample.quantity, addCarbEntry.quantity)
- XCTAssertEqual(sample.startDate, addCarbEntry.startDate)
- XCTAssertNotNil(sample.syncIdentifier)
- XCTAssertEqual(sample.syncVersion, 1)
- XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
- XCTAssertNil(sample.userUpdatedDate)
- addUUID = sample.uuid
- addSyncIdentifier = sample.syncIdentifier
- addHealthStoreHandler.fulfill()
- })
- carbStoreHasUpdatedCarbDataHandler = { (carbStore) in
- handlerInvocation += 1
- self.cacheStore.managedObjectContext.performAndWait {
- switch handlerInvocation {
- case 1:
- addCarbEntryHandler.fulfill()
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(objects.count, 1)
- // Added object
- XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(objects[0].createdByCurrentApp, true)
- XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
- XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
- XCTAssertEqual(objects[0].uuid, addUUID)
- XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[0].syncVersion, 1)
- XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
- XCTAssertNil(objects[0].userUpdatedDate)
- XCTAssertNil(objects[0].userDeletedDate)
- XCTAssertEqual(objects[0].operation, .create)
- XCTAssertNotNil(objects[0].addedDate)
- XCTAssertNil(objects[0].supercededDate)
- XCTAssertGreaterThan(objects[0].anchorKey, 0)
- addAnchorKey = objects[0].anchorKey
- self.healthStore.setSaveHandler({ (objects, success, error) in
- XCTAssertEqual(1, objects.count)
- let sample = objects.first as! HKQuantitySample
- XCTAssertNotEqual(sample.uuid, addUUID)
- XCTAssertEqual(sample.absorptionTime, replaceCarbEntry.absorptionTime)
- XCTAssertEqual(sample.foodType, replaceCarbEntry.foodType)
- XCTAssertEqual(sample.quantity, replaceCarbEntry.quantity)
- XCTAssertEqual(sample.startDate, replaceCarbEntry.startDate)
- XCTAssertEqual(sample.syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(sample.syncVersion, 2)
- XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
- XCTAssertEqual(sample.userUpdatedDate, replaceCarbEntry.date)
- updateUUID = sample.uuid
- updateHealthStoreHandler.fulfill()
- })
- self.carbStore.replaceCarbEntry(StoredCarbEntry(managedObject: objects[0]), withEntry: replaceCarbEntry) { (result) in
- updateCarbEntryCompletion.fulfill()
- }
- case 2:
- updateCarbEntryHandler.fulfill()
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all().sorted { $0.syncVersion! < $1.syncVersion! }
- XCTAssertEqual(objects.count, 2)
- // Added object, superceded
- XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(objects[0].createdByCurrentApp, true)
- XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
- XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
- XCTAssertEqual(objects[0].uuid, addUUID)
- XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[0].syncVersion, 1)
- XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
- XCTAssertNil(objects[0].userUpdatedDate)
- XCTAssertNil(objects[0].userDeletedDate)
- XCTAssertEqual(objects[0].operation, .create)
- XCTAssertNotNil(objects[0].addedDate)
- XCTAssertNotNil(objects[0].supercededDate)
- XCTAssertEqual(objects[0].anchorKey, addAnchorKey)
- // Updated object
- XCTAssertEqual(objects[1].absorptionTime, replaceCarbEntry.absorptionTime)
- XCTAssertEqual(objects[1].createdByCurrentApp, true)
- XCTAssertEqual(objects[1].foodType, replaceCarbEntry.foodType)
- XCTAssertEqual(objects[1].grams, replaceCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[1].startDate, replaceCarbEntry.startDate)
- XCTAssertEqual(objects[1].uuid, updateUUID)
- XCTAssertEqual(objects[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[1].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[1].syncVersion, 2)
- XCTAssertEqual(objects[1].userCreatedDate, addCarbEntry.date)
- XCTAssertEqual(objects[1].userUpdatedDate, replaceCarbEntry.date)
- XCTAssertNil(objects[1].userDeletedDate)
- XCTAssertEqual(objects[1].operation, .update)
- XCTAssertNotNil(objects[1].addedDate)
- XCTAssertNil(objects[1].supercededDate)
- XCTAssertGreaterThan(objects[1].anchorKey, addAnchorKey!)
- DispatchQueue.main.async {
- carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
- getCarbEntriesCompletion.fulfill()
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let entries):
- XCTAssertEqual(entries.count, 1)
- // Updated sample
- XCTAssertEqual(entries[0].uuid, updateUUID)
- XCTAssertEqual(entries[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(entries[0].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(entries[0].syncVersion, 2)
- XCTAssertEqual(entries[0].startDate, replaceCarbEntry.startDate)
- XCTAssertEqual(entries[0].quantity, replaceCarbEntry.quantity)
- XCTAssertEqual(entries[0].foodType, replaceCarbEntry.foodType)
- XCTAssertEqual(entries[0].absorptionTime, replaceCarbEntry.absorptionTime)
- XCTAssertEqual(entries[0].createdByCurrentApp, true)
- XCTAssertEqual(entries[0].userCreatedDate, addCarbEntry.date)
- XCTAssertEqual(entries[0].userUpdatedDate, replaceCarbEntry.date)
- }
- }
- }
- default:
- XCTFail("Unexpected handler invocation")
- }
- }
- }
- carbStore.addCarbEntry(addCarbEntry) { (result) in
- addCarbEntryCompletion.fulfill()
- }
- wait(for: [addHealthStoreHandler, addCarbEntryCompletion, addCarbEntryHandler, updateHealthStoreHandler, updateCarbEntryCompletion, updateCarbEntryHandler, getCarbEntriesCompletion], timeout: 10, enforceOrder: true)
- }
- func testAddAndDeleteCarbEntry() {
- let addCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: "Add", absorptionTime: .hours(3))
- let addHealthStoreHandler = expectation(description: "Add health store handler")
- let addCarbEntryCompletion = expectation(description: "Add carb entry completion")
- let addCarbEntryHandler = expectation(description: "Add carb entry handler")
- let deleteHealthStoreHandler = expectation(description: "Delete health store handler")
- let deleteCarbEntryCompletion = expectation(description: "Delete carb entry completion")
- let deleteCarbEntryHandler = expectation(description: "Delete carb entry handler")
- let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
- var handlerInvocation = 0
- var addUUID: UUID?
- var addSyncIdentifier: String?
- var addAnchorKey: Int64?
- healthStore.setSaveHandler({ (objects, success, error) in
- XCTAssertEqual(1, objects.count)
- let sample = objects.first as! HKQuantitySample
- XCTAssertEqual(sample.absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(sample.foodType, addCarbEntry.foodType)
- XCTAssertEqual(sample.quantity, addCarbEntry.quantity)
- XCTAssertEqual(sample.startDate, addCarbEntry.startDate)
- XCTAssertNotNil(sample.syncIdentifier)
- XCTAssertEqual(sample.syncVersion, 1)
- XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
- XCTAssertNil(sample.userUpdatedDate)
- addUUID = sample.uuid
- addSyncIdentifier = sample.syncIdentifier
- addHealthStoreHandler.fulfill()
- })
- carbStoreHasUpdatedCarbDataHandler = { (carbStore) in
- handlerInvocation += 1
- self.cacheStore.managedObjectContext.performAndWait {
- switch handlerInvocation {
- case 1:
- addCarbEntryHandler.fulfill()
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(objects.count, 1)
- // Added object
- XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(objects[0].createdByCurrentApp, true)
- XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
- XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
- XCTAssertEqual(objects[0].uuid, addUUID)
- XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[0].syncVersion, 1)
- XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
- XCTAssertNil(objects[0].userUpdatedDate)
- XCTAssertNil(objects[0].userDeletedDate)
- XCTAssertEqual(objects[0].operation, .create)
- XCTAssertNotNil(objects[0].addedDate)
- XCTAssertNil(objects[0].supercededDate)
- XCTAssertGreaterThan(objects[0].anchorKey, 0)
- addUUID = objects[0].uuid
- addSyncIdentifier = objects[0].syncIdentifier
- addAnchorKey = objects[0].anchorKey
- self.healthStore.setDeletedObjectsHandler({ (objectType, predicate, success, count, error) in
- XCTAssertEqual(objectType, HKObjectType.quantityType(forIdentifier: .dietaryCarbohydrates))
- XCTAssertEqual(predicate.predicateFormat, "UUID == \(addUUID!)")
- deleteHealthStoreHandler.fulfill()
- })
- self.carbStore.deleteCarbEntry(StoredCarbEntry(managedObject: objects[0])) { (result) in
- deleteCarbEntryCompletion.fulfill()
- }
- case 2:
- deleteCarbEntryHandler.fulfill()
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(objects.count, 2)
- // Added object, superceded
- XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(objects[0].createdByCurrentApp, true)
- XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
- XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
- XCTAssertEqual(objects[0].uuid, addUUID)
- XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[0].syncVersion, 1)
- XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
- XCTAssertNil(objects[0].userUpdatedDate)
- XCTAssertNil(objects[0].userDeletedDate)
- XCTAssertEqual(objects[0].operation, .create)
- XCTAssertNotNil(objects[0].addedDate)
- XCTAssertNotNil(objects[0].supercededDate)
- XCTAssertEqual(objects[0].anchorKey, addAnchorKey)
- // Deleted object
- XCTAssertEqual(objects[1].absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(objects[1].createdByCurrentApp, true)
- XCTAssertEqual(objects[1].foodType, addCarbEntry.foodType)
- XCTAssertEqual(objects[1].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[1].startDate, addCarbEntry.startDate)
- XCTAssertNil(objects[1].uuid)
- XCTAssertEqual(objects[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[1].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[1].syncVersion, 1)
- XCTAssertEqual(objects[1].userCreatedDate, addCarbEntry.date)
- XCTAssertNil(objects[1].userUpdatedDate)
- XCTAssertNotNil(objects[1].userDeletedDate)
- XCTAssertEqual(objects[1].operation, .delete)
- XCTAssertNotNil(objects[1].addedDate)
- XCTAssertNil(objects[1].supercededDate)
- XCTAssertGreaterThan(objects[1].anchorKey, addAnchorKey!)
- DispatchQueue.main.async {
- carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
- getCarbEntriesCompletion.fulfill()
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let entries):
- XCTAssertEqual(entries.count, 0)
- }
- }
- }
- default:
- XCTFail("Unexpected handler invocation")
- }
- }
- }
- carbStore.addCarbEntry(addCarbEntry) { (result) in
- addCarbEntryCompletion.fulfill()
- }
- wait(for: [addHealthStoreHandler, addCarbEntryCompletion, addCarbEntryHandler, deleteHealthStoreHandler, deleteCarbEntryCompletion, deleteCarbEntryHandler, getCarbEntriesCompletion], timeout: 10, enforceOrder: true)
- }
- func testAddAndReplaceAndDeleteCarbEntry() {
- let addCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: "Add", absorptionTime: .hours(3))
- let replaceCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 15), startDate: Date(), foodType: "Replace", absorptionTime: .hours(4))
- let addHealthStoreHandler = expectation(description: "Add health store handler")
- let addCarbEntryCompletion = expectation(description: "Add carb entry completion")
- let addCarbEntryHandler = expectation(description: "Add carb entry handler")
- let updateHealthStoreHandler = expectation(description: "Update health store handler")
- let updateCarbEntryCompletion = expectation(description: "Update carb entry completion")
- let updateCarbEntryHandler = expectation(description: "Update carb entry handler")
- let deleteHealthStoreHandler = expectation(description: "Delete health store handler")
- let deleteCarbEntryCompletion = expectation(description: "Delete carb entry completion")
- let deleteCarbEntryHandler = expectation(description: "Delete carb entry handler")
- let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
- var handlerInvocation = 0
- var addUUID: UUID?
- var addSyncIdentifier: String?
- var addAnchorKey: Int64?
- var updateUUID: UUID?
- var updateAnchorKey: Int64?
- healthStore.setSaveHandler({ (objects, success, error) in
- XCTAssertEqual(1, objects.count)
- let sample = objects.first as! HKQuantitySample
- XCTAssertEqual(sample.absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(sample.foodType, addCarbEntry.foodType)
- XCTAssertEqual(sample.quantity, addCarbEntry.quantity)
- XCTAssertEqual(sample.startDate, addCarbEntry.startDate)
- XCTAssertNotNil(sample.syncIdentifier)
- XCTAssertEqual(sample.syncVersion, 1)
- XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
- XCTAssertNil(sample.userUpdatedDate)
- addUUID = sample.uuid
- addSyncIdentifier = sample.syncIdentifier
- addHealthStoreHandler.fulfill()
- })
- carbStoreHasUpdatedCarbDataHandler = { (carbStore) in
- handlerInvocation += 1
- self.cacheStore.managedObjectContext.performAndWait {
- switch handlerInvocation {
- case 1:
- addCarbEntryHandler.fulfill()
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(objects.count, 1)
- // Added object
- XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(objects[0].createdByCurrentApp, true)
- XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
- XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
- XCTAssertEqual(objects[0].uuid, addUUID)
- XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[0].syncVersion, 1)
- XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
- XCTAssertNil(objects[0].userUpdatedDate)
- XCTAssertNil(objects[0].userDeletedDate)
- XCTAssertEqual(objects[0].operation, .create)
- XCTAssertNotNil(objects[0].addedDate)
- XCTAssertNil(objects[0].supercededDate)
- XCTAssertGreaterThan(objects[0].anchorKey, 0)
- addAnchorKey = objects[0].anchorKey
- self.healthStore.setSaveHandler({ (objects, success, error) in
- XCTAssertEqual(1, objects.count)
- let sample = objects.first as! HKQuantitySample
- XCTAssertNotEqual(sample.uuid, addUUID)
- XCTAssertEqual(sample.absorptionTime, replaceCarbEntry.absorptionTime)
- XCTAssertEqual(sample.foodType, replaceCarbEntry.foodType)
- XCTAssertEqual(sample.quantity, replaceCarbEntry.quantity)
- XCTAssertEqual(sample.startDate, replaceCarbEntry.startDate)
- XCTAssertEqual(sample.syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(sample.syncVersion, 2)
- XCTAssertEqual(sample.userCreatedDate, addCarbEntry.date)
- XCTAssertEqual(sample.userUpdatedDate, replaceCarbEntry.date)
- updateUUID = sample.uuid
- updateHealthStoreHandler.fulfill()
- })
- self.carbStore.replaceCarbEntry(StoredCarbEntry(managedObject: objects[0]), withEntry: replaceCarbEntry) { (result) in
- updateCarbEntryCompletion.fulfill()
- }
- case 2:
- updateCarbEntryHandler.fulfill()
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all().sorted { $0.syncVersion! < $1.syncVersion! }
- XCTAssertEqual(objects.count, 2)
- // Added object, superceded
- XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(objects[0].createdByCurrentApp, true)
- XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
- XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
- XCTAssertEqual(objects[0].uuid, addUUID)
- XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[0].syncVersion, 1)
- XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
- XCTAssertNil(objects[0].userUpdatedDate)
- XCTAssertNil(objects[0].userDeletedDate)
- XCTAssertEqual(objects[0].operation, .create)
- XCTAssertNotNil(objects[0].addedDate)
- XCTAssertNotNil(objects[0].supercededDate)
- XCTAssertEqual(objects[0].anchorKey, addAnchorKey)
- // Updated object
- XCTAssertEqual(objects[1].absorptionTime, replaceCarbEntry.absorptionTime)
- XCTAssertEqual(objects[1].createdByCurrentApp, true)
- XCTAssertEqual(objects[1].foodType, replaceCarbEntry.foodType)
- XCTAssertEqual(objects[1].grams, replaceCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[1].startDate, replaceCarbEntry.startDate)
- XCTAssertEqual(objects[1].uuid, updateUUID)
- XCTAssertEqual(objects[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[1].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[1].syncVersion, 2)
- XCTAssertEqual(objects[1].userCreatedDate, addCarbEntry.date)
- XCTAssertEqual(objects[1].userUpdatedDate, replaceCarbEntry.date)
- XCTAssertNil(objects[1].userDeletedDate)
- XCTAssertEqual(objects[1].operation, .update)
- XCTAssertNotNil(objects[1].addedDate)
- XCTAssertNil(objects[1].supercededDate)
- XCTAssertGreaterThan(objects[1].anchorKey, addAnchorKey!)
- updateAnchorKey = objects[1].anchorKey
- self.healthStore.setDeletedObjectsHandler({ (objectType, predicate, success, count, error) in
- XCTAssertEqual(objectType, HKObjectType.quantityType(forIdentifier: .dietaryCarbohydrates))
- XCTAssertEqual(predicate.predicateFormat, "UUID == \(updateUUID!)")
- deleteHealthStoreHandler.fulfill()
- })
- self.carbStore.deleteCarbEntry(StoredCarbEntry(managedObject: objects[1])) { (result) in
- deleteCarbEntryCompletion.fulfill()
- }
- case 3:
- deleteCarbEntryHandler.fulfill()
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all().sorted { $0.syncVersion! < $1.syncVersion! }
- XCTAssertEqual(objects.count, 3)
- // Added object, superceded
- XCTAssertEqual(objects[0].absorptionTime, addCarbEntry.absorptionTime)
- XCTAssertEqual(objects[0].createdByCurrentApp, true)
- XCTAssertEqual(objects[0].foodType, addCarbEntry.foodType)
- XCTAssertEqual(objects[0].grams, addCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[0].startDate, addCarbEntry.startDate)
- XCTAssertEqual(objects[0].uuid, addUUID)
- XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[0].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[0].syncVersion, 1)
- XCTAssertEqual(objects[0].userCreatedDate, addCarbEntry.date)
- XCTAssertNil(objects[0].userUpdatedDate)
- XCTAssertNil(objects[0].userDeletedDate)
- XCTAssertEqual(objects[0].operation, .create)
- XCTAssertNotNil(objects[0].addedDate)
- XCTAssertNotNil(objects[0].supercededDate)
- XCTAssertEqual(objects[0].anchorKey, addAnchorKey)
- // Updated object, superceded
- XCTAssertEqual(objects[1].absorptionTime, replaceCarbEntry.absorptionTime)
- XCTAssertEqual(objects[1].createdByCurrentApp, true)
- XCTAssertEqual(objects[1].foodType, replaceCarbEntry.foodType)
- XCTAssertEqual(objects[1].grams, replaceCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[1].startDate, replaceCarbEntry.startDate)
- XCTAssertEqual(objects[1].uuid, updateUUID)
- XCTAssertEqual(objects[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[1].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[1].syncVersion, 2)
- XCTAssertEqual(objects[1].userCreatedDate, addCarbEntry.date)
- XCTAssertEqual(objects[1].userUpdatedDate, replaceCarbEntry.date)
- XCTAssertNil(objects[1].userDeletedDate)
- XCTAssertEqual(objects[1].operation, .update)
- XCTAssertNotNil(objects[1].addedDate)
- XCTAssertNotNil(objects[1].supercededDate)
- XCTAssertEqual(objects[1].anchorKey, updateAnchorKey)
- // Deleted object
- XCTAssertEqual(objects[2].absorptionTime, replaceCarbEntry.absorptionTime)
- XCTAssertEqual(objects[2].createdByCurrentApp, true)
- XCTAssertEqual(objects[2].foodType, replaceCarbEntry.foodType)
- XCTAssertEqual(objects[2].grams, replaceCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[2].startDate, replaceCarbEntry.startDate)
- XCTAssertNil(objects[2].uuid)
- XCTAssertEqual(objects[2].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertEqual(objects[2].syncIdentifier, addSyncIdentifier)
- XCTAssertEqual(objects[2].syncVersion, 2)
- XCTAssertEqual(objects[2].userCreatedDate, addCarbEntry.date)
- XCTAssertEqual(objects[2].userUpdatedDate, replaceCarbEntry.date)
- XCTAssertNotNil(objects[2].userDeletedDate)
- XCTAssertEqual(objects[2].operation, .delete)
- XCTAssertNotNil(objects[2].addedDate)
- XCTAssertNil(objects[2].supercededDate)
- XCTAssertGreaterThan(objects[2].anchorKey, updateAnchorKey!)
- DispatchQueue.main.async {
- carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
- getCarbEntriesCompletion.fulfill()
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let entries):
- XCTAssertEqual(entries.count, 0)
- }
- }
- }
- default:
- XCTFail("Unexpected handler invocation")
- }
- }
- }
- carbStore.addCarbEntry(addCarbEntry) { (result) in
- addCarbEntryCompletion.fulfill()
- }
- wait(for: [addHealthStoreHandler, addCarbEntryCompletion, addCarbEntryHandler, updateHealthStoreHandler, updateCarbEntryCompletion, updateCarbEntryHandler, deleteHealthStoreHandler, deleteCarbEntryCompletion, deleteCarbEntryHandler, getCarbEntriesCompletion], timeout: 10, enforceOrder: true)
- }
- // MARK: -
- func testGetSyncCarbObjects() {
- let firstCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(timeIntervalSinceNow: -1), foodType: "First", absorptionTime: .hours(5))
- let secondCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 20), startDate: Date(), foodType: "Second", absorptionTime: .hours(3))
- let thirdCarbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 30), startDate: Date(timeIntervalSinceNow: 1), foodType: "Third", absorptionTime: .minutes(30))
- let getSyncCarbObjectsCompletion = expectation(description: "Get sync carb objects completion")
- carbStore.addCarbEntry(firstCarbEntry) { (_) in
- DispatchQueue.main.async {
- self.carbStore.addCarbEntry(secondCarbEntry) { (_) in
- DispatchQueue.main.async {
- self.carbStore.addCarbEntry(thirdCarbEntry) { (_) in
- DispatchQueue.main.async {
- self.carbStore.getSyncCarbObjects(start: Date().addingTimeInterval(-.minutes(1))) { result in
- getSyncCarbObjectsCompletion.fulfill()
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let objects):
- XCTAssertEqual(objects.count, 3)
- // First
- XCTAssertEqual(objects[0].absorptionTime, firstCarbEntry.absorptionTime)
- XCTAssertEqual(objects[0].createdByCurrentApp, true)
- XCTAssertEqual(objects[0].foodType, firstCarbEntry.foodType)
- XCTAssertEqual(objects[0].grams, firstCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[0].startDate, firstCarbEntry.startDate)
- XCTAssertNotNil(objects[0].uuid)
- XCTAssertEqual(objects[0].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertNotNil(objects[0].syncIdentifier)
- XCTAssertEqual(objects[0].syncVersion, 1)
- XCTAssertEqual(objects[0].userCreatedDate, firstCarbEntry.date)
- XCTAssertNil(objects[0].userUpdatedDate)
- XCTAssertNil(objects[0].userDeletedDate)
- XCTAssertEqual(objects[0].operation, .create)
- XCTAssertNotNil(objects[0].addedDate)
- XCTAssertNil(objects[0].supercededDate)
- // Second
- XCTAssertEqual(objects[1].absorptionTime, secondCarbEntry.absorptionTime)
- XCTAssertEqual(objects[1].createdByCurrentApp, true)
- XCTAssertEqual(objects[1].foodType, secondCarbEntry.foodType)
- XCTAssertEqual(objects[1].grams, secondCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[1].startDate, secondCarbEntry.startDate)
- XCTAssertNotNil(objects[1].uuid)
- XCTAssertEqual(objects[1].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertNotNil(objects[1].syncIdentifier)
- XCTAssertEqual(objects[1].syncVersion, 1)
- XCTAssertEqual(objects[1].userCreatedDate, secondCarbEntry.date)
- XCTAssertNil(objects[1].userUpdatedDate)
- XCTAssertNil(objects[1].userDeletedDate)
- XCTAssertEqual(objects[1].operation, .create)
- XCTAssertNotNil(objects[1].addedDate)
- XCTAssertNil(objects[1].supercededDate)
- // Third
- XCTAssertEqual(objects[2].absorptionTime, thirdCarbEntry.absorptionTime)
- XCTAssertEqual(objects[2].createdByCurrentApp, true)
- XCTAssertEqual(objects[2].foodType, thirdCarbEntry.foodType)
- XCTAssertEqual(objects[2].grams, thirdCarbEntry.quantity.doubleValue(for: .gram()))
- XCTAssertEqual(objects[2].startDate, thirdCarbEntry.startDate)
- XCTAssertNotNil(objects[2].uuid)
- XCTAssertEqual(objects[2].provenanceIdentifier, Bundle.main.bundleIdentifier!)
- XCTAssertNotNil(objects[2].syncIdentifier)
- XCTAssertEqual(objects[2].syncVersion, 1)
- XCTAssertEqual(objects[2].userCreatedDate, thirdCarbEntry.date)
- XCTAssertNil(objects[2].userUpdatedDate)
- XCTAssertNil(objects[2].userDeletedDate)
- XCTAssertEqual(objects[2].operation, .create)
- XCTAssertNotNil(objects[2].addedDate)
- XCTAssertNil(objects[2].supercededDate)
- }
- }
- }
- }
- }
- }
- }
- }
- wait(for: [getSyncCarbObjectsCompletion], timeout: 2, enforceOrder: true)
- }
- func testSetSyncCarbObjects() {
- let carbEntry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(timeIntervalSinceNow: -1), foodType: "First", absorptionTime: .hours(5))
- let syncCarbObjects = [SyncCarbObject(absorptionTime: .hours(5),
- createdByCurrentApp: true,
- foodType: "Pizza",
- grams: 45,
- startDate: Date(timeIntervalSinceNow: -30),
- uuid: UUID(),
- provenanceIdentifier: "com.loopkit.Loop",
- syncIdentifier: UUID().uuidString,
- syncVersion: 4,
- userCreatedDate: Date(timeIntervalSinceNow: -35),
- userUpdatedDate: Date(timeIntervalSinceNow: -34),
- userDeletedDate: nil,
- operation: .update,
- addedDate: Date(timeIntervalSinceNow: -34),
- supercededDate: nil),
- SyncCarbObject(absorptionTime: .hours(3),
- createdByCurrentApp: false,
- foodType: "Pasta",
- grams: 25,
- startDate: Date(timeIntervalSinceNow: -15),
- uuid: UUID(),
- provenanceIdentifier: "com.abc.Example",
- syncIdentifier: UUID().uuidString,
- syncVersion: 1,
- userCreatedDate: Date(timeIntervalSinceNow: -16),
- userUpdatedDate: nil,
- userDeletedDate: nil,
- operation: .create,
- addedDate: Date(timeIntervalSinceNow: -16),
- supercededDate: nil),
- SyncCarbObject(absorptionTime: .minutes(30),
- createdByCurrentApp: true,
- foodType: "Sugar",
- grams: 15,
- startDate: Date(timeIntervalSinceNow: 0),
- uuid: UUID(),
- provenanceIdentifier: "com.loopkit.Loop",
- syncIdentifier: UUID().uuidString,
- syncVersion: 1,
- userCreatedDate: Date(timeIntervalSinceNow: -1),
- userUpdatedDate: nil,
- userDeletedDate: nil,
- operation: .create,
- addedDate: Date(timeIntervalSinceNow: -1),
- supercededDate: nil)
- ]
- let getCarbEntriesCompletion = expectation(description: "Get carb entries completion")
- // Add a carb entry first, that will be purged when setSyncCarbObjects is invoked
- carbStore.addCarbEntry(carbEntry) { (_) in
- DispatchQueue.main.async {
- self.carbStore.setSyncCarbObjects(syncCarbObjects) { (error) in
- XCTAssertNil(error)
- DispatchQueue.main.async {
- self.carbStore.getCarbEntries(start: Date().addingTimeInterval(-.minutes(1))) { result in
- getCarbEntriesCompletion.fulfill()
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let entries):
- XCTAssertEqual(entries.count, 3)
- for index in 0..<3 {
- XCTAssertEqual(entries[index].uuid, syncCarbObjects[index].uuid)
- XCTAssertEqual(entries[index].provenanceIdentifier, syncCarbObjects[index].provenanceIdentifier)
- XCTAssertEqual(entries[index].syncIdentifier, syncCarbObjects[index].syncIdentifier)
- XCTAssertEqual(entries[index].syncVersion, syncCarbObjects[index].syncVersion)
- XCTAssertEqual(entries[index].startDate, syncCarbObjects[index].startDate)
- XCTAssertEqual(entries[index].quantity, syncCarbObjects[index].quantity)
- XCTAssertEqual(entries[index].foodType, syncCarbObjects[index].foodType)
- XCTAssertEqual(entries[index].absorptionTime, syncCarbObjects[index].absorptionTime)
- XCTAssertEqual(entries[index].createdByCurrentApp, syncCarbObjects[index].createdByCurrentApp)
- XCTAssertEqual(entries[index].userCreatedDate, syncCarbObjects[index].userCreatedDate)
- XCTAssertEqual(entries[index].userCreatedDate, syncCarbObjects[index].userCreatedDate)
- }
- }
- }
- }
- }
- }
- }
- wait(for: [getCarbEntriesCompletion], timeout: 2, enforceOrder: true)
- }
- // MARK: -
- private func generateSyncIdentifier() -> String {
- return UUID().uuidString
- }
- }
- class CarbStoreQueryAnchorTests: XCTestCase {
- var rawValue: CarbStore.QueryAnchor.RawValue = [
- "anchorKey": Int64(123)
- ]
- func testInitializerDefault() {
- let queryAnchor = CarbStore.QueryAnchor()
- XCTAssertEqual(queryAnchor.anchorKey, 0)
- }
- func testInitializerRawValue() {
- let queryAnchor = CarbStore.QueryAnchor(rawValue: rawValue)
- XCTAssertNotNil(queryAnchor)
- XCTAssertEqual(queryAnchor?.anchorKey, 123)
- }
- func testInitializerRawValueMissingAnchorKey() {
- rawValue["anchorKey"] = nil
- XCTAssertNil(CarbStore.QueryAnchor(rawValue: rawValue))
- }
- func testInitializerRawValueInvalidAnchorKey() {
- rawValue["anchorKey"] = "123"
- XCTAssertNil(CarbStore.QueryAnchor(rawValue: rawValue))
- }
- func testInitializerRawValueIgnoresDeprecatedStoredModificationCounter() {
- rawValue["storedModificationCounter"] = Int64(456)
- let queryAnchor = CarbStore.QueryAnchor(rawValue: rawValue)
- XCTAssertNotNil(queryAnchor)
- XCTAssertEqual(queryAnchor?.anchorKey, 123)
- }
- func testInitializerRawValueUsesDeprecatedStoredModificationCounter() {
- rawValue["anchorKey"] = nil
- rawValue["storedModificationCounter"] = Int64(456)
- let queryAnchor = CarbStore.QueryAnchor(rawValue: rawValue)
- XCTAssertNotNil(queryAnchor)
- XCTAssertEqual(queryAnchor?.anchorKey, 456)
- }
- func testRawValueWithDefault() {
- let rawValue = CarbStore.QueryAnchor().rawValue
- XCTAssertEqual(rawValue.count, 1)
- XCTAssertEqual(rawValue["anchorKey"] as? Int64, Int64(0))
- }
- func testRawValueWithNonDefault() {
- var queryAnchor = CarbStore.QueryAnchor()
- queryAnchor.anchorKey = 123
- let rawValue = queryAnchor.rawValue
- XCTAssertEqual(rawValue.count, 1)
- XCTAssertEqual(rawValue["anchorKey"] as? Int64, Int64(123))
- }
- }
- class CarbStoreQueryTests: PersistenceControllerTestCase {
-
- var carbStore: CarbStore!
- var completion: XCTestExpectation!
- var queryAnchor: CarbStore.QueryAnchor!
- var limit: Int!
-
- override func setUp() {
- super.setUp()
-
- carbStore = CarbStore(
- healthStore: HKHealthStoreMock(),
- cacheStore: cacheStore,
- cacheLength: .hours(24),
- defaultAbsorptionTimes: (fast: .minutes(30), medium: .hours(3), slow: .hours(5)),
- observationInterval: 0,
- provenanceIdentifier: Bundle.main.bundleIdentifier!)
- let semaphore = DispatchSemaphore(value: 0)
- cacheStore.onReady { (error) in
- semaphore.signal()
- }
- semaphore.wait()
- completion = expectation(description: "Completion")
- queryAnchor = CarbStore.QueryAnchor()
- limit = Int.max
- }
-
- override func tearDown() {
- limit = nil
- queryAnchor = nil
- completion = nil
- carbStore = nil
- super.tearDown()
- }
- func testEmptyWithDefaultQueryAnchor() {
- carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let created, let updated, let deleted):
- XCTAssertEqual(anchor.anchorKey, 0)
- XCTAssertEqual(created.count, 0)
- XCTAssertEqual(updated.count, 0)
- XCTAssertEqual(deleted.count, 0)
- }
- self.completion.fulfill()
- }
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
- func testEmptyWithMissingQueryAnchor() {
- queryAnchor = nil
- carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let created, let updated, let deleted):
- XCTAssertEqual(anchor.anchorKey, 0)
- XCTAssertEqual(created.count, 0)
- XCTAssertEqual(updated.count, 0)
- XCTAssertEqual(deleted.count, 0)
- }
- self.completion.fulfill()
- }
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
- func testEmptyWithNonDefaultQueryAnchor() {
- queryAnchor.anchorKey = 1
- carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let created, let updated, let deleted):
- XCTAssertEqual(anchor.anchorKey, 1)
- XCTAssertEqual(created.count, 0)
- XCTAssertEqual(updated.count, 0)
- XCTAssertEqual(deleted.count, 0)
- }
- self.completion.fulfill()
- }
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
- func testDataWithUnusedQueryAnchor() {
- let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
- addData(withSyncIdentifiers: syncIdentifiers)
- carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let created, let updated, let deleted):
- XCTAssertEqual(anchor.anchorKey, 3)
- XCTAssertEqual(created.count, 1)
- XCTAssertEqual(created[0].syncIdentifier, syncIdentifiers[0])
- XCTAssertEqual(created[0].syncVersion, 0)
- XCTAssertEqual(updated.count, 1)
- XCTAssertEqual(updated[0].syncIdentifier, syncIdentifiers[1])
- XCTAssertEqual(updated[0].syncVersion, 1)
- XCTAssertEqual(deleted.count, 1)
- XCTAssertEqual(deleted[0].syncIdentifier, syncIdentifiers[2])
- XCTAssertEqual(deleted[0].syncVersion, 2)
- }
- self.completion.fulfill()
- }
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
- func testDataWithStaleQueryAnchor() {
- let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
- addData(withSyncIdentifiers: syncIdentifiers)
- queryAnchor.anchorKey = 2
- carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let created, let updated, let deleted):
- XCTAssertEqual(anchor.anchorKey, 3)
- XCTAssertEqual(created.count, 0)
- XCTAssertEqual(updated.count, 0)
- XCTAssertEqual(deleted.count, 1)
- XCTAssertEqual(deleted[0].syncIdentifier, syncIdentifiers[2])
- XCTAssertEqual(deleted[0].syncVersion, 2)
- }
- self.completion.fulfill()
- }
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
- func testDataWithCurrentQueryAnchor() {
- let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
- addData(withSyncIdentifiers: syncIdentifiers)
- queryAnchor.anchorKey = 3
- carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let created, let updated, let deleted):
- XCTAssertEqual(anchor.anchorKey, 3)
- XCTAssertEqual(created.count, 0)
- XCTAssertEqual(updated.count, 0)
- XCTAssertEqual(deleted.count, 0)
- }
- self.completion.fulfill()
- }
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
- func testDataWithLimitZero() {
- let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
- addData(withSyncIdentifiers: syncIdentifiers)
- limit = 0
- carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let created, let updated, let deleted):
- XCTAssertEqual(anchor.anchorKey, 0)
- XCTAssertEqual(created.count, 0)
- XCTAssertEqual(updated.count, 0)
- XCTAssertEqual(deleted.count, 0)
- }
- self.completion.fulfill()
- }
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
- func testDataWithLimitCoveredByData() {
- let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
- addData(withSyncIdentifiers: syncIdentifiers)
- limit = 2
- carbStore.executeCarbQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let created, let updated, let deleted):
- XCTAssertEqual(anchor.anchorKey, 2)
- XCTAssertEqual(created.count, 1)
- XCTAssertEqual(created[0].syncIdentifier, syncIdentifiers[0])
- XCTAssertEqual(created[0].syncVersion, 0)
- XCTAssertEqual(updated.count, 1)
- XCTAssertEqual(updated[0].syncIdentifier, syncIdentifiers[1])
- XCTAssertEqual(updated[0].syncVersion, 1)
- XCTAssertEqual(deleted.count, 0)
- }
- self.completion.fulfill()
- }
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
- private func addData(withSyncIdentifiers syncIdentifiers: [String]) {
- cacheStore.managedObjectContext.performAndWait {
- for (index, syncIdentifier) in syncIdentifiers.enumerated() {
- let cachedCarbObject = CachedCarbObject(context: self.cacheStore.managedObjectContext)
- cachedCarbObject.createdByCurrentApp = true
- cachedCarbObject.startDate = Date()
- cachedCarbObject.uuid = UUID()
- cachedCarbObject.syncIdentifier = syncIdentifier
- cachedCarbObject.syncVersion = index
- cachedCarbObject.operation = Operation(rawValue: index % Operation.allCases.count)!
- cachedCarbObject.addedDate = Date()
- self.cacheStore.save()
- }
- }
- }
- private func generateSyncIdentifier() -> String {
- return UUID().uuidString
- }
-
- }
- class CarbStoreCriticalEventLogTests: PersistenceControllerTestCase {
- var carbStore: CarbStore!
- var outputStream: MockOutputStream!
- var progress: Progress!
-
- override func setUp() {
- super.setUp()
- 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),
- 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),
- 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")!),
- 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),
- 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)]
- carbStore = CarbStore(
- healthStore: HKHealthStoreMock(),
- cacheStore: cacheStore,
- cacheLength: .hours(24),
- defaultAbsorptionTimes: (fast: .minutes(30), medium: .hours(3), slow: .hours(5)),
- observationInterval: 0,
- provenanceIdentifier: Bundle.main.bundleIdentifier!)
- let dispatchGroup = DispatchGroup()
- dispatchGroup.enter()
- carbStore.setSyncCarbObjects(objects) { error in
- XCTAssertNil(error)
- dispatchGroup.leave()
- }
- dispatchGroup.wait()
- outputStream = MockOutputStream()
- progress = Progress()
- }
- override func tearDown() {
- carbStore = nil
- super.tearDown()
- }
-
- func testExportProgressTotalUnitCount() {
- switch carbStore.exportProgressTotalUnitCount(startDate: dateFormatter.date(from: "2100-01-02T03:03:00Z")!,
- endDate: dateFormatter.date(from: "2100-01-02T03:09:00Z")!) {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let progressTotalUnitCount):
- XCTAssertEqual(progressTotalUnitCount, 3 * 1)
- }
- }
-
- func testExportProgressTotalUnitCountEmpty() {
- switch carbStore.exportProgressTotalUnitCount(startDate: dateFormatter.date(from: "2100-01-02T03:00:00Z")!,
- endDate: dateFormatter.date(from: "2100-01-02T03:01:00Z")!) {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let progressTotalUnitCount):
- XCTAssertEqual(progressTotalUnitCount, 0)
- }
- }
- func testExport() {
- XCTAssertNil(carbStore.export(startDate: dateFormatter.date(from: "2100-01-02T03:03:00Z")!,
- endDate: dateFormatter.date(from: "2100-01-02T03:09:00Z")!,
- to: outputStream,
- progress: progress))
- XCTAssertEqual(outputStream.string, """
- [
- {"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"},
- {"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"},
- {"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"}
- ]
- """
- )
- XCTAssertEqual(progress.completedUnitCount, 3 * 1)
- }
- func testExportEmpty() {
- XCTAssertNil(carbStore.export(startDate: dateFormatter.date(from: "2100-01-02T03:00:00Z")!,
- endDate: dateFormatter.date(from: "2100-01-02T03:01:00Z")!,
- to: outputStream,
- progress: progress))
- XCTAssertEqual(outputStream.string, "[]")
- XCTAssertEqual(progress.completedUnitCount, 0)
- }
- func testExportCancelled() {
- progress.cancel()
- XCTAssertEqual(carbStore.export(startDate: dateFormatter.date(from: "2100-01-02T03:03:00Z")!,
- endDate: dateFormatter.date(from: "2100-01-02T03:09:00Z")!,
- to: outputStream,
- progress: progress) as? CriticalEventLogError, CriticalEventLogError.cancelled)
- }
- private let dateFormatter = ISO8601DateFormatter()
- }
|