| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115 |
- //
- // SettingsStoreTests.swift
- // LoopKitTests
- //
- // Created by Darin Krauss on 1/2/20.
- // Copyright © 2020 LoopKit Authors. All rights reserved.
- //
- import XCTest
- import HealthKit
- @testable import LoopKit
- class SettingsStorePersistenceTests: PersistenceControllerTestCase, SettingsStoreDelegate {
-
- var settingsStore: SettingsStore!
-
- override func setUp() {
- super.setUp()
-
- settingsStoreHasUpdatedSettingsDataHandler = nil
- settingsStore = SettingsStore(store: cacheStore, expireAfter: .hours(1))
- settingsStore.delegate = self
- }
-
- override func tearDown() {
- settingsStore.delegate = nil
- settingsStore = nil
- settingsStoreHasUpdatedSettingsDataHandler = nil
-
- super.tearDown()
- }
-
- // MARK: - SettingsStoreDelegate
-
- var settingsStoreHasUpdatedSettingsDataHandler: ((_ : SettingsStore) -> Void)?
-
- func settingsStoreHasUpdatedSettingsData(_ settingsStore: SettingsStore) {
- settingsStoreHasUpdatedSettingsDataHandler?(settingsStore)
- }
-
- // MARK: -
-
- func testStoreSettings() {
- let storeSettingsHandler = expectation(description: "Store settings handler")
- let storeSettingsCompletion = expectation(description: "Store settings completion")
-
- var handlerInvocation = 0
-
- settingsStoreHasUpdatedSettingsDataHandler = { settingsStore in
- handlerInvocation += 1
-
- switch handlerInvocation {
- case 1:
- storeSettingsHandler.fulfill()
- default:
- XCTFail("Unexpected handler invocation")
- }
- }
-
- settingsStore.storeSettings(StoredSettings()) { _ in
- storeSettingsCompletion.fulfill()
- }
-
- wait(for: [storeSettingsHandler, storeSettingsCompletion], timeout: 2, enforceOrder: true)
- }
-
- func testStoreSettingsMultiple() {
- let storeSettingsHandler1 = expectation(description: "Store settings handler 1")
- let storeSettingsHandler2 = expectation(description: "Store settings handler 2")
- let storeSettingsCompletion1 = expectation(description: "Store settings completion 1")
- let storeSettingsCompletion2 = expectation(description: "Store settings completion 2")
-
- var handlerInvocation = 0
-
- settingsStoreHasUpdatedSettingsDataHandler = { settingsStore in
- handlerInvocation += 1
-
- switch handlerInvocation {
- case 1:
- storeSettingsHandler1.fulfill()
- case 2:
- storeSettingsHandler2.fulfill()
- default:
- XCTFail("Unexpected handler invocation")
- }
- }
-
- settingsStore.storeSettings(StoredSettings()) { _ in
- storeSettingsCompletion1.fulfill()
- }
-
- settingsStore.storeSettings(StoredSettings()) { _ in
- storeSettingsCompletion2.fulfill()
- }
-
- wait(for: [storeSettingsHandler1, storeSettingsCompletion1, storeSettingsHandler2, storeSettingsCompletion2], timeout: 2, enforceOrder: true)
- }
-
- // MARK: -
- func testSettingsObjectEncodable() throws {
- cacheStore.managedObjectContext.performAndWait {
- do {
- let object = SettingsObject(context: cacheStore.managedObjectContext)
- object.data = try PropertyListEncoder().encode(StoredSettings.test)
- object.date = dateFormatter.date(from: "2100-01-02T03:03:00Z")!
- object.modificationCounter = 123
- try assertSettingsObjectEncodable(object, encodesJSON: """
- {
- "data" : {
- "automaticDosingStrategy" : 1,
- "basalRateSchedule" : {
- "items" : [
- {
- "startTime" : 0,
- "value" : 1
- },
- {
- "startTime" : 21600,
- "value" : 1.5
- },
- {
- "startTime" : 64800,
- "value" : 1.25
- }
- ],
- "referenceTimeInterval" : 0,
- "repeatInterval" : 86400,
- "timeZone" : {
- "identifier" : "GMT-0700"
- }
- },
- "bloodGlucoseUnit" : "mg/dL",
- "carbRatioSchedule" : {
- "unit" : "g",
- "valueSchedule" : {
- "items" : [
- {
- "startTime" : 0,
- "value" : 15
- },
- {
- "startTime" : 32400,
- "value" : 14
- },
- {
- "startTime" : 72000,
- "value" : 18
- }
- ],
- "referenceTimeInterval" : 0,
- "repeatInterval" : 86400,
- "timeZone" : {
- "identifier" : "GMT-0700"
- }
- }
- },
- "cgmDevice" : {
- "firmwareVersion" : "CGM Firmware Version",
- "hardwareVersion" : "CGM Hardware Version",
- "localIdentifier" : "CGM Local Identifier",
- "manufacturer" : "CGM Manufacturer",
- "model" : "CGM Model",
- "name" : "CGM Name",
- "softwareVersion" : "CGM Software Version",
- "udiDeviceIdentifier" : "CGM UDI Device Identifier"
- },
- "controllerDevice" : {
- "model" : "Controller Model",
- "modelIdentifier" : "Controller Model Identifier",
- "name" : "Controller Name",
- "systemName" : "Controller System Name",
- "systemVersion" : "Controller System Version"
- },
- "controllerTimeZone" : {
- "identifier" : "America/Los_Angeles"
- },
- "date" : "2020-05-14T22:48:15Z",
- "defaultRapidActingModel" : {
- "actionDuration" : 21600,
- "delay" : 600,
- "modelType" : "rapidAdult",
- "peakActivity" : 10800
- },
- "deviceToken" : "Device Token String",
- "dosingEnabled" : true,
- "glucoseTargetRangeSchedule" : {
- "override" : {
- "end" : "2020-05-14T14:48:15Z",
- "start" : "2020-05-14T12:48:15Z",
- "value" : {
- "maxValue" : 115,
- "minValue" : 105
- }
- },
- "rangeSchedule" : {
- "unit" : "mg/dL",
- "valueSchedule" : {
- "items" : [
- {
- "startTime" : 0,
- "value" : {
- "maxValue" : 110,
- "minValue" : 100
- }
- },
- {
- "startTime" : 25200,
- "value" : {
- "maxValue" : 100,
- "minValue" : 90
- }
- },
- {
- "startTime" : 75600,
- "value" : {
- "maxValue" : 120,
- "minValue" : 110
- }
- }
- ],
- "referenceTimeInterval" : 0,
- "repeatInterval" : 86400,
- "timeZone" : {
- "identifier" : "GMT-0700"
- }
- }
- }
- },
- "insulinSensitivitySchedule" : {
- "unit" : "mg/dL",
- "valueSchedule" : {
- "items" : [
- {
- "startTime" : 0,
- "value" : 45
- },
- {
- "startTime" : 10800,
- "value" : 40
- },
- {
- "startTime" : 54000,
- "value" : 50
- }
- ],
- "referenceTimeInterval" : 0,
- "repeatInterval" : 86400,
- "timeZone" : {
- "identifier" : "GMT-0700"
- }
- }
- },
- "insulinType" : 1,
- "maximumBasalRatePerHour" : 3.5,
- "maximumBolus" : 10,
- "notificationSettings" : {
- "alertSetting" : "disabled",
- "alertStyle" : "banner",
- "announcementSetting" : "enabled",
- "authorizationStatus" : "authorized",
- "badgeSetting" : "enabled",
- "carPlaySetting" : "notSupported",
- "criticalAlertSetting" : "enabled",
- "lockScreenSetting" : "disabled",
- "notificationCenterSetting" : "notSupported",
- "providesAppNotificationSettings" : true,
- "scheduledDeliverySetting" : "disabled",
- "showPreviewsSetting" : "whenAuthenticated",
- "soundSetting" : "enabled",
- "timeSensitiveSetting" : "enabled"
- },
- "overridePresets" : [
- {
- "duration" : {
- "finite" : {
- "duration" : 3600
- }
- },
- "id" : "2A67A303-5203-4CB8-8263-79498265368E",
- "name" : "Apple",
- "settings" : {
- "insulinNeedsScaleFactor" : 2,
- "targetRangeInMgdl" : {
- "maxValue" : 140,
- "minValue" : 130
- }
- },
- "symbol" : "🍎"
- }
- ],
- "preMealOverride" : {
- "actualEnd" : {
- "type" : "natural"
- },
- "context" : "preMeal",
- "duration" : "indefinite",
- "enactTrigger" : "local",
- "settings" : {
- "insulinNeedsScaleFactor" : 0.5,
- "targetRangeInMgdl" : {
- "maxValue" : 90,
- "minValue" : 80
- }
- },
- "startDate" : "2020-05-14T14:38:39Z",
- "syncIdentifier" : "2A67A303-5203-1234-8263-79498265368E"
- },
- "preMealTargetRange" : {
- "maxValue" : 90,
- "minValue" : 80
- },
- "pumpDevice" : {
- "firmwareVersion" : "Pump Firmware Version",
- "hardwareVersion" : "Pump Hardware Version",
- "localIdentifier" : "Pump Local Identifier",
- "manufacturer" : "Pump Manufacturer",
- "model" : "Pump Model",
- "name" : "Pump Name",
- "softwareVersion" : "Pump Software Version",
- "udiDeviceIdentifier" : "Pump UDI Device Identifier"
- },
- "scheduleOverride" : {
- "actualEnd" : {
- "type" : "natural"
- },
- "context" : "preMeal",
- "duration" : {
- "finite" : {
- "duration" : 3600
- }
- },
- "enactTrigger" : {
- "remote" : {
- "address" : "127.0.0.1"
- }
- },
- "settings" : {
- "insulinNeedsScaleFactor" : 1.5,
- "targetRangeInMgdl" : {
- "maxValue" : 120,
- "minValue" : 110
- }
- },
- "startDate" : "2020-05-14T14:48:19Z",
- "syncIdentifier" : "2A67A303-1234-4CB8-8263-79498265368E"
- },
- "suspendThreshold" : {
- "unit" : "mg/dL",
- "value" : 75
- },
- "syncIdentifier" : "2A67A303-1234-4CB8-1234-79498265368E",
- "workoutTargetRange" : {
- "maxValue" : 160,
- "minValue" : 150
- }
- },
- "date" : "2100-01-02T03:03:00Z",
- "modificationCounter" : 123
- }
- """
- )
- } catch let error {
- XCTFail("Unexpected failure: \(error)")
- }
- }
- }
- private func assertSettingsObjectEncodable(_ original: SettingsObject, encodesJSON string: String) throws {
- let data = try encoder.encode(original)
- XCTAssertEqual(String(data: data, encoding: .utf8), string)
- }
- private let dateFormatter = ISO8601DateFormatter()
- private let encoder: JSONEncoder = {
- let encoder = JSONEncoder()
- encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
- encoder.dateEncodingStrategy = .iso8601
- return encoder
- }()
- }
- class SettingsStoreQueryAnchorTests: XCTestCase {
-
- var rawValue: SettingsStore.QueryAnchor.RawValue = [
- "modificationCounter": Int64(123)
- ]
-
- func testInitializerDefault() {
- let queryAnchor = SettingsStore.QueryAnchor()
- XCTAssertEqual(queryAnchor.modificationCounter, 0)
- }
-
- func testInitializerRawValue() {
- let queryAnchor = SettingsStore.QueryAnchor(rawValue: rawValue)
- XCTAssertNotNil(queryAnchor)
- XCTAssertEqual(queryAnchor?.modificationCounter, 123)
- }
-
- func testInitializerRawValueMissingModificationCounter() {
- rawValue["modificationCounter"] = nil
- XCTAssertNil(SettingsStore.QueryAnchor(rawValue: rawValue))
- }
-
- func testInitializerRawValueInvalidModificationCounter() {
- rawValue["modificationCounter"] = "123"
- XCTAssertNil(SettingsStore.QueryAnchor(rawValue: rawValue))
- }
-
- func testRawValueWithDefault() {
- let rawValue = SettingsStore.QueryAnchor().rawValue
- XCTAssertEqual(rawValue.count, 1)
- XCTAssertEqual(rawValue["modificationCounter"] as? Int64, Int64(0))
- }
-
- func testRawValueWithNonDefault() {
- var queryAnchor = SettingsStore.QueryAnchor()
- queryAnchor.modificationCounter = 123
- let rawValue = queryAnchor.rawValue
- XCTAssertEqual(rawValue.count, 1)
- XCTAssertEqual(rawValue["modificationCounter"] as? Int64, Int64(123))
- }
-
- }
- class SettingsStoreQueryTests: PersistenceControllerTestCase {
-
- var settingsStore: SettingsStore!
- var completion: XCTestExpectation!
- var queryAnchor: SettingsStore.QueryAnchor!
- var limit: Int!
-
- override func setUp() {
- super.setUp()
-
- settingsStore = SettingsStore(store: cacheStore, expireAfter: .hours(1))
- completion = expectation(description: "Completion")
- queryAnchor = SettingsStore.QueryAnchor()
- limit = Int.max
- }
-
- override func tearDown() {
- limit = nil
- queryAnchor = nil
- completion = nil
- settingsStore = nil
-
- super.tearDown()
- }
-
- // MARK: -
-
- func testEmptyWithDefaultQueryAnchor() {
- settingsStore.executeSettingsQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let data):
- XCTAssertEqual(anchor.modificationCounter, 0)
- XCTAssertEqual(data.count, 0)
- }
- self.completion.fulfill()
- }
-
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
-
- func testEmptyWithMissingQueryAnchor() {
- queryAnchor = nil
-
- settingsStore.executeSettingsQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let data):
- XCTAssertEqual(anchor.modificationCounter, 0)
- XCTAssertEqual(data.count, 0)
- }
- self.completion.fulfill()
- }
-
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
-
- func testEmptyWithNonDefaultQueryAnchor() {
- queryAnchor.modificationCounter = 1
-
- settingsStore.executeSettingsQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let data):
- XCTAssertEqual(anchor.modificationCounter, 1)
- XCTAssertEqual(data.count, 0)
- }
- self.completion.fulfill()
- }
-
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
-
- func testDataWithUnusedQueryAnchor() {
- let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
-
- addData(withSyncIdentifiers: syncIdentifiers)
-
- settingsStore.executeSettingsQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let data):
- XCTAssertEqual(anchor.modificationCounter, 3)
- XCTAssertEqual(data.count, 3)
- for (index, syncIdentifier) in syncIdentifiers.enumerated() {
- XCTAssertEqual(data[index].syncIdentifier, syncIdentifier)
- }
- }
- self.completion.fulfill()
- }
-
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
-
- func testDataWithStaleQueryAnchor() {
- let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
-
- addData(withSyncIdentifiers: syncIdentifiers)
-
- queryAnchor.modificationCounter = 2
-
- settingsStore.executeSettingsQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let data):
- XCTAssertEqual(anchor.modificationCounter, 3)
- XCTAssertEqual(data.count, 1)
- XCTAssertEqual(data[0].syncIdentifier, syncIdentifiers[2])
- }
- self.completion.fulfill()
- }
-
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
-
- func testDataWithCurrentQueryAnchor() {
- let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
-
- addData(withSyncIdentifiers: syncIdentifiers)
-
- queryAnchor.modificationCounter = 3
-
- settingsStore.executeSettingsQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let data):
- XCTAssertEqual(anchor.modificationCounter, 3)
- XCTAssertEqual(data.count, 0)
- }
- self.completion.fulfill()
- }
-
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
-
- func testDataWithLimitZero() {
- let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
-
- addData(withSyncIdentifiers: syncIdentifiers)
-
- limit = 0
-
- settingsStore.executeSettingsQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let data):
- XCTAssertEqual(anchor.modificationCounter, 0)
- XCTAssertEqual(data.count, 0)
- }
- self.completion.fulfill()
- }
-
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
-
- func testDataWithLimitCoveredByData() {
- let syncIdentifiers = [generateSyncIdentifier(), generateSyncIdentifier(), generateSyncIdentifier()]
-
- addData(withSyncIdentifiers: syncIdentifiers)
-
- limit = 2
-
- settingsStore.executeSettingsQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
- switch result {
- case .failure(let error):
- XCTFail("Unexpected failure: \(error)")
- case .success(let anchor, let data):
- XCTAssertEqual(anchor.modificationCounter, 2)
- XCTAssertEqual(data.count, 2)
- XCTAssertEqual(data[0].syncIdentifier, syncIdentifiers[0])
- XCTAssertEqual(data[1].syncIdentifier, syncIdentifiers[1])
- }
- self.completion.fulfill()
- }
-
- wait(for: [completion], timeout: 2, enforceOrder: true)
- }
-
- private func addData(withSyncIdentifiers syncIdentifiers: [UUID]) {
- let semaphore = DispatchSemaphore(value: 0)
- for syncIdentifier in syncIdentifiers {
- self.settingsStore.storeSettings(StoredSettings(syncIdentifier: syncIdentifier)) { _ in semaphore.signal() }
- }
- for _ in syncIdentifiers { semaphore.wait() }
- }
-
- private func generateSyncIdentifier() -> UUID { UUID() }
- }
- class SettingsStoreCriticalEventLogTests: PersistenceControllerTestCase {
- var settingsStore: SettingsStore!
- var outputStream: MockOutputStream!
- var progress: Progress!
-
- override func setUp() {
- super.setUp()
- let settings = [StoredSettings(date: dateFormatter.date(from: "2100-01-02T03:08:00Z")!, controllerTimeZone: TimeZone(identifier: "America/Los_Angeles")!, syncIdentifier: UUID(uuidString: "18CF3948-0B3D-4B12-8BFE-14986B0E6784")!),
- StoredSettings(date: dateFormatter.date(from: "2100-01-02T03:10:00Z")!, controllerTimeZone: TimeZone(identifier: "America/Los_Angeles")!, syncIdentifier: UUID(uuidString: "C86DEB61-68E9-464E-9DD5-96A9CB445FD3")!),
- StoredSettings(date: dateFormatter.date(from: "2100-01-02T03:04:00Z")!, controllerTimeZone: TimeZone(identifier: "America/Los_Angeles")!, syncIdentifier: UUID(uuidString: "2B03D96C-6F5D-4140-99CD-80C3E64D6010")!),
- StoredSettings(date: dateFormatter.date(from: "2100-01-02T03:06:00Z")!, controllerTimeZone: TimeZone(identifier: "America/Los_Angeles")!, syncIdentifier: UUID(uuidString: "FF1C4F01-3558-4FB2-957E-FA1522C4735E")!),
- StoredSettings(date: dateFormatter.date(from: "2100-01-02T03:02:00Z")!, controllerTimeZone: TimeZone(identifier: "America/Los_Angeles")!, syncIdentifier: UUID(uuidString: "71B699D7-0E8F-4B13-B7A1-E7751EB78E74")!)]
- settingsStore = SettingsStore(store: cacheStore, expireAfter: .hours(1))
- let dispatchGroup = DispatchGroup()
- dispatchGroup.enter()
- settingsStore.addStoredSettings(settings: settings) { error in
- XCTAssertNil(error)
- dispatchGroup.leave()
- }
- dispatchGroup.wait()
- outputStream = MockOutputStream()
- progress = Progress()
- }
- override func tearDown() {
- settingsStore = nil
- super.tearDown()
- }
-
- func testExportProgressTotalUnitCount() {
- switch settingsStore.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 * 11)
- }
- }
-
- func testExportProgressTotalUnitCountEmpty() {
- switch settingsStore.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(settingsStore.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, """
- [
- {"data":{"automaticDosingStrategy":0,"bloodGlucoseUnit":"mg/dL","controllerTimeZone":{"identifier":"America/Los_Angeles"},"date":"2100-01-02T03:08:00.000Z","dosingEnabled":false,"syncIdentifier":"18CF3948-0B3D-4B12-8BFE-14986B0E6784"},"date":"2100-01-02T03:08:00.000Z","modificationCounter":1},
- {"data":{"automaticDosingStrategy":0,"bloodGlucoseUnit":"mg/dL","controllerTimeZone":{"identifier":"America/Los_Angeles"},"date":"2100-01-02T03:04:00.000Z","dosingEnabled":false,"syncIdentifier":"2B03D96C-6F5D-4140-99CD-80C3E64D6010"},"date":"2100-01-02T03:04:00.000Z","modificationCounter":3},
- {"data":{"automaticDosingStrategy":0,"bloodGlucoseUnit":"mg/dL","controllerTimeZone":{"identifier":"America/Los_Angeles"},"date":"2100-01-02T03:06:00.000Z","dosingEnabled":false,"syncIdentifier":"FF1C4F01-3558-4FB2-957E-FA1522C4735E"},"date":"2100-01-02T03:06:00.000Z","modificationCounter":4}
- ]
- """
- )
- XCTAssertEqual(progress.completedUnitCount, 3 * 11)
- }
- func testExportEmpty() {
- XCTAssertNil(settingsStore.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(settingsStore.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()
- }
- class StoredSettingsCodableTests: XCTestCase {
- func testStoredSettingsCodable() throws {
- try assertStoredSettingsCodable(StoredSettings.test, encodesJSON: """
- {
- "automaticDosingStrategy" : 1,
- "basalRateSchedule" : {
- "items" : [
- {
- "startTime" : 0,
- "value" : 1
- },
- {
- "startTime" : 21600,
- "value" : 1.5
- },
- {
- "startTime" : 64800,
- "value" : 1.25
- }
- ],
- "referenceTimeInterval" : 0,
- "repeatInterval" : 86400,
- "timeZone" : {
- "identifier" : "GMT-0700"
- }
- },
- "bloodGlucoseUnit" : "mg/dL",
- "carbRatioSchedule" : {
- "unit" : "g",
- "valueSchedule" : {
- "items" : [
- {
- "startTime" : 0,
- "value" : 15
- },
- {
- "startTime" : 32400,
- "value" : 14
- },
- {
- "startTime" : 72000,
- "value" : 18
- }
- ],
- "referenceTimeInterval" : 0,
- "repeatInterval" : 86400,
- "timeZone" : {
- "identifier" : "GMT-0700"
- }
- }
- },
- "cgmDevice" : {
- "firmwareVersion" : "CGM Firmware Version",
- "hardwareVersion" : "CGM Hardware Version",
- "localIdentifier" : "CGM Local Identifier",
- "manufacturer" : "CGM Manufacturer",
- "model" : "CGM Model",
- "name" : "CGM Name",
- "softwareVersion" : "CGM Software Version",
- "udiDeviceIdentifier" : "CGM UDI Device Identifier"
- },
- "controllerDevice" : {
- "model" : "Controller Model",
- "modelIdentifier" : "Controller Model Identifier",
- "name" : "Controller Name",
- "systemName" : "Controller System Name",
- "systemVersion" : "Controller System Version"
- },
- "controllerTimeZone" : {
- "identifier" : "America/Los_Angeles"
- },
- "date" : "2020-05-14T22:48:15Z",
- "defaultRapidActingModel" : {
- "actionDuration" : 21600,
- "delay" : 600,
- "modelType" : "rapidAdult",
- "peakActivity" : 10800
- },
- "deviceToken" : "Device Token String",
- "dosingEnabled" : true,
- "glucoseTargetRangeSchedule" : {
- "override" : {
- "end" : "2020-05-14T14:48:15Z",
- "start" : "2020-05-14T12:48:15Z",
- "value" : {
- "maxValue" : 115,
- "minValue" : 105
- }
- },
- "rangeSchedule" : {
- "unit" : "mg/dL",
- "valueSchedule" : {
- "items" : [
- {
- "startTime" : 0,
- "value" : {
- "maxValue" : 110,
- "minValue" : 100
- }
- },
- {
- "startTime" : 25200,
- "value" : {
- "maxValue" : 100,
- "minValue" : 90
- }
- },
- {
- "startTime" : 75600,
- "value" : {
- "maxValue" : 120,
- "minValue" : 110
- }
- }
- ],
- "referenceTimeInterval" : 0,
- "repeatInterval" : 86400,
- "timeZone" : {
- "identifier" : "GMT-0700"
- }
- }
- }
- },
- "insulinSensitivitySchedule" : {
- "unit" : "mg/dL",
- "valueSchedule" : {
- "items" : [
- {
- "startTime" : 0,
- "value" : 45
- },
- {
- "startTime" : 10800,
- "value" : 40
- },
- {
- "startTime" : 54000,
- "value" : 50
- }
- ],
- "referenceTimeInterval" : 0,
- "repeatInterval" : 86400,
- "timeZone" : {
- "identifier" : "GMT-0700"
- }
- }
- },
- "insulinType" : 1,
- "maximumBasalRatePerHour" : 3.5,
- "maximumBolus" : 10,
- "notificationSettings" : {
- "alertSetting" : "disabled",
- "alertStyle" : "banner",
- "announcementSetting" : "enabled",
- "authorizationStatus" : "authorized",
- "badgeSetting" : "enabled",
- "carPlaySetting" : "notSupported",
- "criticalAlertSetting" : "enabled",
- "lockScreenSetting" : "disabled",
- "notificationCenterSetting" : "notSupported",
- "providesAppNotificationSettings" : true,
- "scheduledDeliverySetting" : "disabled",
- "showPreviewsSetting" : "whenAuthenticated",
- "soundSetting" : "enabled",
- "timeSensitiveSetting" : "enabled"
- },
- "overridePresets" : [
- {
- "duration" : {
- "finite" : {
- "duration" : 3600
- }
- },
- "id" : "2A67A303-5203-4CB8-8263-79498265368E",
- "name" : "Apple",
- "settings" : {
- "insulinNeedsScaleFactor" : 2,
- "targetRangeInMgdl" : {
- "maxValue" : 140,
- "minValue" : 130
- }
- },
- "symbol" : "🍎"
- }
- ],
- "preMealOverride" : {
- "actualEnd" : {
- "type" : "natural"
- },
- "context" : "preMeal",
- "duration" : "indefinite",
- "enactTrigger" : "local",
- "settings" : {
- "insulinNeedsScaleFactor" : 0.5,
- "targetRangeInMgdl" : {
- "maxValue" : 90,
- "minValue" : 80
- }
- },
- "startDate" : "2020-05-14T14:38:39Z",
- "syncIdentifier" : "2A67A303-5203-1234-8263-79498265368E"
- },
- "preMealTargetRange" : {
- "maxValue" : 90,
- "minValue" : 80
- },
- "pumpDevice" : {
- "firmwareVersion" : "Pump Firmware Version",
- "hardwareVersion" : "Pump Hardware Version",
- "localIdentifier" : "Pump Local Identifier",
- "manufacturer" : "Pump Manufacturer",
- "model" : "Pump Model",
- "name" : "Pump Name",
- "softwareVersion" : "Pump Software Version",
- "udiDeviceIdentifier" : "Pump UDI Device Identifier"
- },
- "scheduleOverride" : {
- "actualEnd" : {
- "type" : "natural"
- },
- "context" : "preMeal",
- "duration" : {
- "finite" : {
- "duration" : 3600
- }
- },
- "enactTrigger" : {
- "remote" : {
- "address" : "127.0.0.1"
- }
- },
- "settings" : {
- "insulinNeedsScaleFactor" : 1.5,
- "targetRangeInMgdl" : {
- "maxValue" : 120,
- "minValue" : 110
- }
- },
- "startDate" : "2020-05-14T14:48:19Z",
- "syncIdentifier" : "2A67A303-1234-4CB8-8263-79498265368E"
- },
- "suspendThreshold" : {
- "unit" : "mg/dL",
- "value" : 75
- },
- "syncIdentifier" : "2A67A303-1234-4CB8-1234-79498265368E",
- "workoutTargetRange" : {
- "maxValue" : 160,
- "minValue" : 150
- }
- }
- """
- )
- }
-
- private func assertStoredSettingsCodable(_ original: StoredSettings, encodesJSON string: String) throws {
- let data = try encoder.encode(original)
- XCTAssertEqual(String(data: data, encoding: .utf8), string)
- let decoded = try decoder.decode(StoredSettings.self, from: data)
- XCTAssertEqual(decoded, original)
- }
-
- private let dateFormatter = ISO8601DateFormatter()
-
- private let encoder: JSONEncoder = {
- let encoder = JSONEncoder()
- encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
- encoder.dateEncodingStrategy = .iso8601
- return encoder
- }()
-
- private let decoder: JSONDecoder = {
- let decoder = JSONDecoder()
- decoder.dateDecodingStrategy = .iso8601
- return decoder
- }()
- }
- fileprivate extension StoredSettings {
- static var test: StoredSettings {
- let controllerTimeZone = TimeZone(identifier: "America/Los_Angeles")!
- let scheduleTimeZone = TimeZone(secondsFromGMT: TimeZone(identifier: "America/Phoenix")!.secondsFromGMT())!
- let dosingEnabled = true
- let glucoseTargetRangeSchedule = GlucoseRangeSchedule(rangeSchedule: DailyQuantitySchedule(unit: .milligramsPerDeciliter,
- dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: DoubleRange(minValue: 100.0, maxValue: 110.0)),
- RepeatingScheduleValue(startTime: .hours(7), value: DoubleRange(minValue: 90.0, maxValue: 100.0)),
- RepeatingScheduleValue(startTime: .hours(21), value: DoubleRange(minValue: 110.0, maxValue: 120.0))],
- timeZone: scheduleTimeZone)!,
- override: GlucoseRangeSchedule.Override(value: DoubleRange(minValue: 105.0, maxValue: 115.0),
- start: dateFormatter.date(from: "2020-05-14T12:48:15Z")!,
- end: dateFormatter.date(from: "2020-05-14T14:48:15Z")!))
- let preMealTargetRange = DoubleRange(minValue: 80.0, maxValue: 90.0).quantityRange(for: .milligramsPerDeciliter)
- let workoutTargetRange = DoubleRange(minValue: 150.0, maxValue: 160.0).quantityRange(for: .milligramsPerDeciliter)
- let overridePresets = [TemporaryScheduleOverridePreset(id: UUID(uuidString: "2A67A303-5203-4CB8-8263-79498265368E")!,
- symbol: "🍎",
- name: "Apple",
- settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter,
- targetRange: DoubleRange(minValue: 130.0, maxValue: 140.0),
- insulinNeedsScaleFactor: 2.0),
- duration: .finite(.minutes(60)))]
- let scheduleOverride = TemporaryScheduleOverride(context: .preMeal,
- settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter,
- targetRange: DoubleRange(minValue: 110.0, maxValue: 120.0),
- insulinNeedsScaleFactor: 1.5),
- startDate: dateFormatter.date(from: "2020-05-14T14:48:19Z")!,
- duration: .finite(.minutes(60)),
- enactTrigger: .remote("127.0.0.1"),
- syncIdentifier: UUID(uuidString: "2A67A303-1234-4CB8-8263-79498265368E")!)
- let preMealOverride = TemporaryScheduleOverride(context: .preMeal,
- settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter,
- targetRange: DoubleRange(minValue: 80.0, maxValue: 90.0),
- insulinNeedsScaleFactor: 0.5),
- startDate: dateFormatter.date(from: "2020-05-14T14:38:39Z")!,
- duration: .indefinite,
- enactTrigger: .local,
- syncIdentifier: UUID(uuidString: "2A67A303-5203-1234-8263-79498265368E")!)
- let maximumBasalRatePerHour = 3.5
- let maximumBolus = 10.0
- let suspendThreshold = GlucoseThreshold(unit: .milligramsPerDeciliter, value: 75.0)
- let deviceToken = "Device Token String"
- let insulinType = InsulinType.humalog
- let defaultRapidActingModel = StoredInsulinModel(modelType: .rapidAdult, delay: .minutes(10), actionDuration: .hours(6), peakActivity: .hours(3))
- let basalRateSchedule = BasalRateSchedule(dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: 1.0),
- RepeatingScheduleValue(startTime: .hours(6), value: 1.5),
- RepeatingScheduleValue(startTime: .hours(18), value: 1.25)],
- timeZone: scheduleTimeZone)
- let insulinSensitivitySchedule = InsulinSensitivitySchedule(unit: .milligramsPerDeciliter,
- dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: 45.0),
- RepeatingScheduleValue(startTime: .hours(3), value: 40.0),
- RepeatingScheduleValue(startTime: .hours(15), value: 50.0)],
- timeZone: scheduleTimeZone)
- let carbRatioSchedule = CarbRatioSchedule(unit: .gram(),
- dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: 15.0),
- RepeatingScheduleValue(startTime: .hours(9), value: 14.0),
- RepeatingScheduleValue(startTime: .hours(20), value: 18.0)],
- timeZone: scheduleTimeZone)
- let notificationSettings = NotificationSettings(authorizationStatus: .authorized,
- soundSetting: .enabled,
- badgeSetting: .enabled,
- alertSetting: .disabled,
- notificationCenterSetting: .notSupported,
- lockScreenSetting: .disabled,
- carPlaySetting: .notSupported,
- alertStyle: .banner,
- showPreviewsSetting: .whenAuthenticated,
- criticalAlertSetting: .enabled,
- providesAppNotificationSettings: true,
- announcementSetting: .enabled,
- timeSensitiveSetting: .enabled,
- scheduledDeliverySetting: .disabled)
- let controllerDevice = StoredSettings.ControllerDevice(name: "Controller Name",
- systemName: "Controller System Name",
- systemVersion: "Controller System Version",
- model: "Controller Model",
- modelIdentifier: "Controller Model Identifier")
- let cgmDevice = HKDevice(name: "CGM Name",
- manufacturer: "CGM Manufacturer",
- model: "CGM Model",
- hardwareVersion: "CGM Hardware Version",
- firmwareVersion: "CGM Firmware Version",
- softwareVersion: "CGM Software Version",
- localIdentifier: "CGM Local Identifier",
- udiDeviceIdentifier: "CGM UDI Device Identifier")
- let pumpDevice = HKDevice(name: "Pump Name",
- manufacturer: "Pump Manufacturer",
- model: "Pump Model",
- hardwareVersion: "Pump Hardware Version",
- firmwareVersion: "Pump Firmware Version",
- softwareVersion: "Pump Software Version",
- localIdentifier: "Pump Local Identifier",
- udiDeviceIdentifier: "Pump UDI Device Identifier")
- let bloodGlucoseUnit = HKUnit.milligramsPerDeciliter
- return StoredSettings(date: dateFormatter.date(from: "2020-05-14T22:48:15Z")!,
- controllerTimeZone: controllerTimeZone,
- dosingEnabled: dosingEnabled,
- glucoseTargetRangeSchedule: glucoseTargetRangeSchedule,
- preMealTargetRange: preMealTargetRange,
- workoutTargetRange: workoutTargetRange,
- overridePresets: overridePresets,
- scheduleOverride: scheduleOverride,
- preMealOverride: preMealOverride,
- maximumBasalRatePerHour: maximumBasalRatePerHour,
- maximumBolus: maximumBolus,
- suspendThreshold: suspendThreshold,
- deviceToken: deviceToken,
- insulinType: insulinType,
- defaultRapidActingModel: defaultRapidActingModel,
- basalRateSchedule: basalRateSchedule,
- insulinSensitivitySchedule: insulinSensitivitySchedule,
- carbRatioSchedule: carbRatioSchedule,
- notificationSettings: notificationSettings,
- controllerDevice: controllerDevice,
- cgmDevice: cgmDevice,
- pumpDevice: pumpDevice,
- bloodGlucoseUnit: bloodGlucoseUnit,
- automaticDosingStrategy: .automaticBolus,
- syncIdentifier: UUID(uuidString: "2A67A303-1234-4CB8-1234-79498265368E")!)
- }
- private static let dateFormatter = ISO8601DateFormatter()
- }
|