| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- //
- // CarbStoreTests.swift
- // LoopKitTests
- //
- // Copyright © 2018 LoopKit Authors. All rights reserved.
- //
- import XCTest
- import HealthKit
- import CoreData
- @testable import LoopKit
- class CarbStoreTests: PersistenceControllerTestCase, CarbStoreSyncDelegate {
- var carbStore: CarbStore!
- var healthStore: HKHealthStoreMock!
- override func setUp() {
- super.setUp()
- healthStore = HKHealthStoreMock()
- carbStore = CarbStore(healthStore: healthStore, cacheStore: cacheStore)
- carbStore.testQueryStore = healthStore
- carbStore.syncDelegate = self
- }
- override func tearDown() {
- carbStore.syncDelegate = nil
- carbStore = nil
- healthStore = nil
- uploadMessages = []
- deleteMessages = []
- uploadHandler = nil
- deleteHandler = nil
- super.tearDown()
- }
- // MARK: - CarbStoreSyncDelegate
- var uploadMessages: [(entries: [StoredCarbEntry], completion: ([StoredCarbEntry]) -> Void)] = []
- var uploadHandler: ((_: [StoredCarbEntry], _: ([StoredCarbEntry]) -> Void) -> Void)?
- func carbStore(_ carbStore: CarbStore, hasEntriesNeedingUpload entries: [StoredCarbEntry], completion: @escaping ([StoredCarbEntry]) -> Void) {
- uploadMessages.append((entries: entries, completion: completion))
- uploadHandler?(entries, completion)
- }
- var deleteMessages: [(entries: [DeletedCarbEntry], completion: ([DeletedCarbEntry]) -> Void)] = []
- var deleteHandler: ((_: [DeletedCarbEntry], _: ([DeletedCarbEntry]) -> Void) -> Void)?
- func carbStore(_ carbStore: CarbStore, hasDeletedEntries entries: [DeletedCarbEntry], completion: @escaping ([DeletedCarbEntry]) -> Void) {
- deleteMessages.append((entries: entries, completion: completion))
- deleteHandler?(entries, completion)
- }
- // MARK: -
- /// Adds a new entry, validates its uploading/uploaded transition
- func testAddAndSyncSuccessful() {
- let entry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: nil, absorptionTime: .hours(3))
- let addCarb = expectation(description: "Add carb entry")
- let uploading = expectation(description: "Sync delegate: upload")
- let uploaded = expectation(description: "Sync delegate: completed")
- // 2. assert sync delegate called
- uploadHandler = { (entries, completion) in
- XCTAssertEqual(1, entries.count)
- // 3. assert entered in db as uploading
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(1, objects.count)
- XCTAssertEqual(.uploading, objects.first!.uploadState)
- }
- uploading.fulfill()
- var entry = entries.first!
- entry.externalID = "1234"
- entry.isUploaded = true
- completion([entry])
- // 4. call delegate completion
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- // 5. assert entered in db as uploaded
- XCTAssertEqual(.uploaded, objects.first!.uploadState)
- uploaded.fulfill()
- }
- }
- // 1. Add carb
- carbStore.addCarbEntry(entry) { (result) in
- addCarb.fulfill()
- }
- wait(for: [addCarb, uploading, uploaded], timeout: 2, enforceOrder: true)
- }
- /// Adds a new entry, validates its uploading/notUploaded transition
- func testAddAndSyncFailed() {
- let entry = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: nil, absorptionTime: .hours(3))
- let addCarb = expectation(description: "Add carb entry")
- let uploading = expectation(description: "Sync delegate: upload")
- let uploaded = expectation(description: "Sync delegate: completed")
- // 2. assert sync delegate called
- uploadHandler = { (entries, completion) in
- XCTAssertEqual(1, entries.count)
- // 3. assert entered in db as uploading
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(1, objects.count)
- XCTAssertEqual(.uploading, objects.first!.uploadState)
- }
- uploading.fulfill()
- completion(entries) // Not uploaded
- // 4. call delegate completion
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- // 5. assert entered in db as not uploaded
- XCTAssertEqual(.notUploaded, objects.first!.uploadState)
- uploaded.fulfill()
- }
- }
- // 1. Add carb
- carbStore.addCarbEntry(entry) { (result) in
- addCarb.fulfill()
- }
- wait(for: [addCarb, uploading, uploaded], timeout: 2, enforceOrder: true)
- }
- /// Adds two entries, validating their transition as the delegate calls completion out-of-order
- func testAddAndSyncInterleve() {
- let entry1 = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: nil, absorptionTime: .hours(3))
- let entry2 = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: nil, absorptionTime: .hours(3))
- let addCarb1 = expectation(description: "Add carb entry")
- let addCarb2 = expectation(description: "Add carb entry")
- let uploading1 = expectation(description: "Sync delegate: upload")
- let uploading2 = expectation(description: "Sync delegate: upload")
- let uploaded = expectation(description: "Sync delegate: completed")
- uploaded.expectedFulfillmentCount = 2
- // 2. assert sync delegate called
- uploadHandler = { (entries, completion) in
- XCTAssertEqual(1, entries.count)
- // 3. assert entered in db as uploading
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(self.uploadMessages.count, objects.count)
- for object in objects {
- XCTAssertEqual(.uploading, object.uploadState)
- }
- }
- switch self.uploadMessages.count {
- case 1:
- uploading1.fulfill()
- self.carbStore.addCarbEntry(entry2) { (result) in
- addCarb2.fulfill()
- }
- case 2:
- uploading2.fulfill()
- for index in (0...1).reversed() {
- var entry = self.uploadMessages[index].entries.first!
- entry.externalID = "\(index)"
- entry.isUploaded = true
- self.uploadMessages[index].completion([entry])
- // 4. call delegate completion
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- for object in objects {
- if object.externalID == "\(index)" {
- XCTAssertEqual(.uploaded, object.uploadState)
- }
- }
- uploaded.fulfill()
- }
- }
- default:
- XCTFail()
- }
- }
- // 1. Add carb 1
- carbStore.addCarbEntry(entry1) { (result) in
- addCarb1.fulfill()
- }
- wait(for: [addCarb1, uploading1, addCarb2, uploading2, uploaded], timeout: 2, enforceOrder: true)
- }
- /// Adds an entry with a failed upload, validates its requested again for sync on next entry
- func testAddAndSyncMultipleCallbacks() {
- let entry1 = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: nil, absorptionTime: .hours(3))
- let entry2 = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: nil, absorptionTime: .hours(3))
- let addCarb1 = expectation(description: "Add carb entry")
- let addCarb2 = expectation(description: "Add carb entry")
- let uploading1 = expectation(description: "Sync delegate: upload")
- let uploading2 = expectation(description: "Sync delegate: upload")
- let uploaded = expectation(description: "Sync delegate: completed")
- uploaded.expectedFulfillmentCount = 2
- // 2. assert sync delegate called
- uploadHandler = { (entries, completion) in
- // 3. assert entered in db as uploading
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(self.uploadMessages.count, objects.count)
- for object in objects {
- XCTAssertEqual(.uploading, object.uploadState)
- }
- }
- switch self.uploadMessages.count {
- case 1:
- XCTAssertEqual(1, entries.count)
- uploading1.fulfill()
- completion(entries) // Not uploaded
- self.carbStore.addCarbEntry(entry2) { (result) in
- addCarb2.fulfill()
- }
- case 2:
- XCTAssertEqual(2, entries.count)
- uploading2.fulfill()
- for index in (0...1).reversed() {
- var entry = self.uploadMessages[index].entries.first!
- entry.externalID = "\(index)"
- entry.isUploaded = true
- self.uploadMessages[index].completion([entry])
- // 4. call delegate completion
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- for object in objects {
- if object.externalID == "\(index)" {
- XCTAssertEqual(.uploaded, object.uploadState)
- }
- }
- uploaded.fulfill()
- }
- }
- default:
- XCTFail()
- }
- }
- // 1. Add carb 1
- carbStore.addCarbEntry(entry1) { (result) in
- addCarb1.fulfill()
- }
- wait(for: [addCarb1, uploading1, addCarb2, uploading2, uploaded], timeout: 2, enforceOrder: true)
- }
- /// Adds and uploads an entry, then modifies it and validates its re-upload
- func testModifyUploadedCarb() {
- let entry1 = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: nil, absorptionTime: .hours(3))
- let entry2 = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 15), startDate: Date(), foodType: nil, absorptionTime: .hours(3))
- let sample2 = HKQuantitySample(type: carbStore.sampleType as! HKQuantityType, quantity: entry2.quantity, start: entry2.startDate, end: entry2.endDate)
- let addCarb1 = expectation(description: "Add carb entry")
- let addCarb2 = expectation(description: "Add carb entry")
- let uploading1 = expectation(description: "Sync delegate: upload")
- let uploading2 = expectation(description: "Sync delegate: upload")
- let uploaded = expectation(description: "Sync delegate: completed")
- var lastUUID: UUID?
- // 2. assert sync delegate called
- uploadHandler = { (entries, completion) in
- // 3. assert entered in db as uploading
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(1, objects.count)
- XCTAssertEqual(.uploading, objects[0].uploadState)
- if let lastUUID = lastUUID {
- XCTAssertNotEqual(lastUUID, objects[0].uuid)
- }
- lastUUID = objects[0].uuid
- }
- switch self.uploadMessages.count {
- case 1:
- XCTAssertEqual(1, entries.count)
- uploading1.fulfill()
- var entry = entries.first!
- entry.externalID = "1234"
- entry.isUploaded = true
- completion([entry])
- self.healthStore.queryResults = (samples: [sample2], error: nil)
- self.carbStore.replaceCarbEntry(entries.first!, withEntry: entry2) { (result) in
- addCarb2.fulfill()
- self.healthStore.queryResults = nil
- }
- case 2:
- XCTAssertEqual(1, entries.count)
- XCTAssertEqual("1234", entries.first!.externalID)
- XCTAssertFalse(entries.first!.isUploaded)
- uploading2.fulfill()
- var entry = entries.first!
- entry.isUploaded = true
- completion([entry])
- // 4. call delegate completion
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(1, objects.count)
- for object in objects {
- XCTAssertEqual(.uploaded, object.uploadState)
- }
- uploaded.fulfill()
- }
- default:
- XCTFail()
- }
- }
- deleteHandler = { (entries, completion) in
- XCTFail()
- }
- // 1. Add carb 1
- carbStore.addCarbEntry(entry1) { (result) in
- addCarb1.fulfill()
- }
- wait(for: [addCarb1, uploading1, addCarb2, uploading2, uploaded], timeout: 2, enforceOrder: true)
- }
- /// Adds an entry, modifying it before upload completes, validating the delegate is then asked to delete it
- func testModifyNotUploadedCarb() {
- let entry1 = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 10), startDate: Date(), foodType: nil, absorptionTime: .hours(3))
- let entry2 = NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: 15), startDate: Date(), foodType: nil, absorptionTime: .hours(3))
- let sample2 = HKQuantitySample(type: carbStore.sampleType as! HKQuantityType, quantity: entry2.quantity, start: entry2.startDate, end: entry2.endDate)
- let addCarb1 = expectation(description: "Add carb entry")
- let addCarb2 = expectation(description: "Add carb entry")
- let uploading1 = expectation(description: "Sync delegate: upload")
- let uploading2 = expectation(description: "Sync delegate: upload")
- let uploaded = expectation(description: "Sync delegate: completed")
- let deleted = expectation(description: "Sync delegate: deleted")
- // 2. assert sync delegate called
- uploadHandler = { (entries, completion) in
- // 3. assert entered in db as uploading
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(1, objects.count)
- for object in objects {
- XCTAssertEqual(.uploading, object.uploadState)
- }
- }
- switch self.uploadMessages.count {
- case 1:
- XCTAssertEqual(1, entries.count)
- uploading1.fulfill()
- self.healthStore.queryResults = (samples: [sample2], error: nil)
- self.carbStore.replaceCarbEntry(entries.first!, withEntry: entry2) { (result) in
- addCarb2.fulfill()
- self.healthStore.queryResults = nil
- }
- case 2:
- XCTAssertEqual(1, entries.count)
- XCTAssertNil(entries.first!.externalID)
- XCTAssertFalse(entries.first!.isUploaded)
- uploading2.fulfill()
- var entry = entries.first!
- entry.externalID = "1234"
- entry.isUploaded = true
- completion([entry])
- entry = self.uploadMessages[0].entries.first!
- entry.externalID = "5678"
- entry.isUploaded = true
- self.uploadMessages[0].completion([entry])
- // 4. call delegate completion
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [CachedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(1, objects.count)
- for object in objects {
- XCTAssertEqual("1234", object.externalID)
- XCTAssertEqual(.uploaded, object.uploadState)
- }
- uploaded.fulfill()
- }
- default:
- XCTFail()
- }
- }
- deleteHandler = { (entries, completion) in
- XCTAssertEqual(1, entries.count)
- var entry = entries[0]
- XCTAssertEqual("5678", entry.externalID)
- XCTAssertFalse(entry.isUploaded)
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [DeletedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(1, objects.count)
- for object in objects {
- XCTAssertEqual("5678", object.externalID)
- XCTAssertEqual(.uploading, object.uploadState)
- }
- }
- entry.isUploaded = true
- completion([entry])
- self.cacheStore.managedObjectContext.performAndWait {
- let objects: [DeletedCarbObject] = self.cacheStore.managedObjectContext.all()
- XCTAssertEqual(0, objects.count)
- }
- deleted.fulfill()
- }
- // 1. Add carb 1
- carbStore.addCarbEntry(entry1) { (result) in
- addCarb1.fulfill()
- }
- wait(for: [addCarb1, uploading1, addCarb2, uploading2, uploaded, deleted], timeout: 2, enforceOrder: true)
- }
- }
|