| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126 |
- //
- // 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",
- "temporaryMuteAlertsSetting" : {
- "disabled" : {
- }
- },
- "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",
- "temporaryMuteAlertsSetting" : {
- "disabled" : {
- }
- },
- "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,
- temporaryMuteAlertsSetting: .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()
- }
|