From 15b3393603e15e5895004b041e240078674a2fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Wed, 24 Jun 2026 20:15:42 +0200 Subject: [PATCH] Add isAPNSProduction to ProfileSet Serialize a top-level isAPNSProduction flag in the profile so followers like LoopFollow can read which APNS environment to use. Matches how Trio uploads the same value. --- Sources/NightscoutKit/Models/ProfileSet.swift | 15 +++++++++++---- .../NightscoutKitTests/NightscoutKitTests.swift | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Sources/NightscoutKit/Models/ProfileSet.swift b/Sources/NightscoutKit/Models/ProfileSet.swift index e59baf4..d58ea9c 100644 --- a/Sources/NightscoutKit/Models/ProfileSet.swift +++ b/Sources/NightscoutKit/Models/ProfileSet.swift @@ -127,8 +127,9 @@ public class ProfileSet { public let store: ProfileStore public let settings: LoopSettings public let syncIdentifier: String - - public init(startDate: Date, units: String, enteredBy: String, defaultProfile: String, store: ProfileStore, settings: LoopSettings, syncIdentifier: String) { + public let isAPNSProduction: Bool? + + public init(startDate: Date, units: String, enteredBy: String, defaultProfile: String, store: ProfileStore, settings: LoopSettings, syncIdentifier: String, isAPNSProduction: Bool? = nil) { self.startDate = startDate self.units = units self.enteredBy = enteredBy @@ -136,6 +137,7 @@ public class ProfileSet { self.store = store self.settings = settings self.syncIdentifier = syncIdentifier + self.isAPNSProduction = isAPNSProduction } public var dictionaryRepresentation: [String: Any] { @@ -145,7 +147,7 @@ public class ProfileSet { let dictProfiles = Dictionary(uniqueKeysWithValues: store.map { key, value in (key, value.dictionaryRepresentation) }) - let rval : [String: Any] = [ + var rval : [String: Any] = [ "defaultProfile": defaultProfile, "startDate": dateFormatter.string(from: startDate), "mills": mills, @@ -154,7 +156,11 @@ public class ProfileSet { "loopSettings": settings.dictionaryRepresentation, "store": dictProfiles ] - + + if let isAPNSProduction = isAPNSProduction { + rval["isAPNSProduction"] = isAPNSProduction + } + return rval } @@ -180,5 +186,6 @@ public class ProfileSet { self.store = storeRaw.compactMapValues { Profile(rawValue: $0) } self.settings = settings self.syncIdentifier = syncIdentifier + self.isAPNSProduction = rawValue["isAPNSProduction"] as? Bool } } diff --git a/Tests/NightscoutKitTests/NightscoutKitTests.swift b/Tests/NightscoutKitTests/NightscoutKitTests.swift index 52dfcc7..55e212a 100644 --- a/Tests/NightscoutKitTests/NightscoutKitTests.swift +++ b/Tests/NightscoutKitTests/NightscoutKitTests.swift @@ -18,4 +18,21 @@ final class NightscoutKitTests: XCTestCase { XCTAssertEqual("ETC/GMT+5", json["timezone"] as? String) } + + private func makeProfileSet(isAPNSProduction: Bool?) -> ProfileSet { + let timeZone = TimeZone(secondsFromGMT: 0)! + let schedule = [ProfileSet.ScheduleItem(offset: .hours(0), value: 1)] + let profile = ProfileSet.Profile(timezone: timeZone, dia: .hours(6), sensitivity: schedule, carbratio: schedule, basal: schedule, targetLow: schedule, targetHigh: schedule, units: "mg/dL") + let settings = LoopSettings(dosingEnabled: true, overridePresets: [], scheduleOverride: nil, minimumBGGuard: nil, preMealTargetRange: nil, maximumBasalRatePerHour: nil, maximumBolus: nil, deviceToken: "token", bundleIdentifier: "com.example.loop", dosingStrategy: "automaticBolus") + return ProfileSet(startDate: Date(), units: "mg/dL", enteredBy: "Loop", defaultProfile: "Default", store: ["Default": profile], settings: settings, syncIdentifier: "sync", isAPNSProduction: isAPNSProduction) + } + + func testAPNSProductionEnvironmentIsSerializedAtTopLevel() { + XCTAssertEqual(true, makeProfileSet(isAPNSProduction: true).dictionaryRepresentation["isAPNSProduction"] as? Bool) + XCTAssertEqual(false, makeProfileSet(isAPNSProduction: false).dictionaryRepresentation["isAPNSProduction"] as? Bool) + } + + func testAPNSProductionEnvironmentOmittedWhenUnset() { + XCTAssertNil(makeProfileSet(isAPNSProduction: nil).dictionaryRepresentation["isAPNSProduction"]) + } }