Преглед изворни кода

Added migration to Onboarding; add unit tests WIP

Deniz Cengiz пре 1 година
родитељ
комит
678033de51

Model/MigrationScript.swift → Model/JSONImporter.swift


+ 8 - 4
Trio.xcodeproj/project.pbxproj

@@ -378,9 +378,9 @@
 		BD54A9732D281ABC00F9C1EE /* TempTargetPresetWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD54A9722D281A9C00F9C1EE /* TempTargetPresetWatch.swift */; };
 		BD54A9742D281AEF00F9C1EE /* TempTargetPresetWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD54A9722D281A9C00F9C1EE /* TempTargetPresetWatch.swift */; };
 		BD6EB2D62C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */; };
-		BD793CC52CE8ABAD00D669AC /* MigrationScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CC42CE8ABA700D669AC /* MigrationScript.swift */; };
 		BD793CB02CE7C61500D669AC /* OverrideRunStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */; };
 		BD793CB22CE8033500D669AC /* TempTargetRunStored.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CB12CE8032E00D669AC /* TempTargetRunStored.swift */; };
+		BD793CC52CE8ABAD00D669AC /* JSONImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CC42CE8ABA700D669AC /* JSONImporter.swift */; };
 		BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */; };
 		BD7DA9A72AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */; };
 		BD7DA9A92AE06E9200601B20 /* BolusCalculatorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */; };
@@ -539,6 +539,7 @@
 		DD1745502C55CA5500211FAC /* UnitsLimitsSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17454F2C55CA5500211FAC /* UnitsLimitsSettingsProvider.swift */; };
 		DD1745522C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745512C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift */; };
 		DD1745552C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745542C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift */; };
+		DD1D0BC72DB6BCDE005BD5A1 /* JSONImporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1D0BC62DB6BCDE005BD5A1 /* JSONImporterTests.swift */; };
 		DD1DB7CC2BECCA1F0048B367 /* BuildDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */; };
 		DD1E53592D273F26008F32A4 /* LoopStatusHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1E53582D273F20008F32A4 /* LoopStatusHelpView.swift */; };
 		DD21FCB52C6952AD00AF2C25 /* DecimalPickerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */; };
@@ -1171,9 +1172,9 @@
 		BD54A9702D281A7A00F9C1EE /* TempTargetPresetsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetPresetsView.swift; sourceTree = "<group>"; };
 		BD54A9722D281A9C00F9C1EE /* TempTargetPresetWatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetPresetWatch.swift; sourceTree = "<group>"; };
 		BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityWidgetConfiguration.swift; sourceTree = "<group>"; };
-		BD793CC42CE8ABA700D669AC /* MigrationScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScript.swift; sourceTree = "<group>"; };
 		BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideRunStored+helper.swift"; sourceTree = "<group>"; };
 		BD793CB12CE8032E00D669AC /* TempTargetRunStored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetRunStored.swift; sourceTree = "<group>"; };
+		BD793CC42CE8ABA700D669AC /* JSONImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONImporter.swift; sourceTree = "<group>"; };
 		BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigDataFlow.swift; sourceTree = "<group>"; };
 		BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigProvider.swift; sourceTree = "<group>"; };
 		BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorStateModel.swift; sourceTree = "<group>"; };
@@ -1339,6 +1340,7 @@
 		DD17454F2C55CA5500211FAC /* UnitsLimitsSettingsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsLimitsSettingsProvider.swift; sourceTree = "<group>"; };
 		DD1745512C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsLimitsSettingsStateModel.swift; sourceTree = "<group>"; };
 		DD1745542C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsLimitsSettingsRootView.swift; sourceTree = "<group>"; };
+		DD1D0BC62DB6BCDE005BD5A1 /* JSONImporterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONImporterTests.swift; sourceTree = "<group>"; };
 		DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = "<group>"; };
 		DD1E53582D273F20008F32A4 /* LoopStatusHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatusHelpView.swift; sourceTree = "<group>"; };
 		DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalPickerSettings.swift; sourceTree = "<group>"; };
@@ -2524,6 +2526,7 @@
 		38FCF3EE25E9028E0078B0D1 /* TrioTests */ = {
 			isa = PBXGroup;
 			children = (
+				DD1D0BC62DB6BCDE005BD5A1 /* JSONImporterTests.swift */,
 				BD8FC05C2D6618BE00B95AED /* BolusCalculatorTests */,
 				BD8FC0552D66187700B95AED /* CoreDataTests */,
 				38FCF3F125E9028E0078B0D1 /* Info.plist */,
@@ -2629,7 +2632,7 @@
 		587A54C82BCDCE0F009D38E2 /* Model */ = {
 			isa = PBXGroup;
 			children = (
-				BD793CC42CE8ABA700D669AC /* MigrationScript.swift */,
+				BD793CC42CE8ABA700D669AC /* JSONImporter.swift */,
 				3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */,
 				BDF34F8F2C10CF8C00D51995 /* CoreDataStack.swift */,
 				BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */,
@@ -3987,7 +3990,7 @@
 				BD47FDDD2D8B65B10043966B /* GlucoseTargetStepView.swift in Sources */,
 				19D466A329AA2B80004D5F33 /* MealSettingsDataFlow.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
-				BD793CC52CE8ABAD00D669AC /* MigrationScript.swift in Sources */,
+				BD793CC52CE8ABAD00D669AC /* JSONImporter.swift in Sources */,
 				38AEE73D25F0200C0013F05B /* TrioSettings.swift in Sources */,
 				38FCF3FD25E997A80078B0D1 /* PumpHistoryStorage.swift in Sources */,
 				58645BA72CA2D390008AFCE7 /* ChartAxisSetup.swift in Sources */,
@@ -4551,6 +4554,7 @@
 				CEE9A65E2BBC9F6500EB5194 /* CalibrationsTests.swift in Sources */,
 				BD8FC0622D6619E600B95AED /* OverrideStorageTests.swift in Sources */,
 				BD8FC0592D66189700B95AED /* TestAssembly.swift in Sources */,
+				DD1D0BC72DB6BCDE005BD5A1 /* JSONImporterTests.swift in Sources */,
 				BD8FC0662D661A0000B95AED /* GlucoseStorageTests.swift in Sources */,
 				BD8FC05B2D6618AF00B95AED /* DeterminationStorageTests.swift in Sources */,
 				CE1F6DD92BADF4620064EB8D /* PluginManagerTests.swift in Sources */,

+ 17 - 0
Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift

@@ -16,6 +16,7 @@ extension Onboarding {
         @ObservationIgnored @Injected() var notificationsManager: UserNotificationsManager!
         @ObservationIgnored @Injected() var bluetoothManager: BluetoothStateManager!
 
+        private let coreDataStack = CoreDataStack.shared
         private let settingsProvider = PickerSettingsProvider.shared
 
         // MARK: - App Diagnostics
@@ -515,6 +516,7 @@ extension Onboarding {
             saveBasalProfile()
             saveCarbRatios()
             saveISFValues()
+            migrateDataFromJSON()
         }
 
         /// Persists the current diagnostics sharing option to UserDefaults as a boolean.
@@ -574,6 +576,21 @@ extension Onboarding {
             let pumpSettings = PumpSettings(insulinActionCurve: defaultDIA, maxBolus: maxBolus, maxBasal: maxBasal)
             fileStorage.save(pumpSettings, as: OpenAPS.Settings.settings)
         }
+
+        func migrateDataFromJSON() {
+            Task {
+                let importer = JSONImporter(context: coreDataStack.newTaskContext())
+                async let importPumpHistory: () = importer.importPumpHistoryIfNeeded()
+                async let importCarbHistory: () = importer.importCarbHistoryIfNeeded()
+                async let importGlucoseHistory: () = importer.importGlucoseHistoryIfNeeded()
+                async let importDeterminationHistory: () = importer.importDeterminationHistoryIfNeeded()
+
+                await importPumpHistory
+                await importCarbHistory
+                await importGlucoseHistory
+                await importDeterminationHistory
+            }
+        }
     }
 }
 

+ 4 - 2
TrioTests/BolusCalculatorTests/BolusCalculatorTests.swift

@@ -332,13 +332,15 @@ import Testing
 
     @Test("Calculate insulin with zero carbs") func testZeroCarbsCalculation() async throws {
         // Given
-        let carbs: Decimal = 0
+        let carbs: Decimal = 0.0
+        let minPredBG: Decimal = 80.0
 
         // When
         let result = await calculator.handleBolusCalculation(
             carbs: carbs,
             useFattyMealCorrection: false,
-            useSuperBolus: false
+            useSuperBolus: false,
+            minPredBG: minPredBG,
         )
 
         // Then

+ 488 - 0
TrioTests/JSONImporterTests.swift

@@ -0,0 +1,488 @@
+//
+//  JSONImporterTests.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 21.04.25.
+//
+import CoreData
+import Foundation
+import Swinject
+import Testing
+
+@testable import Trio
+
+@Suite("JSON Importer Tests") struct JSONImporterTests: Injectable {
+    let resolver: Resolver = TrioApp().resolver
+    var coreDataStack: CoreDataStack!
+    var context: NSManagedObjectContext!
+    var importer: JSONImporter!
+    let fileManager = FileManager.default
+    let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
+    @Injected() var fileStorage: FileStorage!
+
+    init() async throws {
+        injectServices(resolver)
+
+        // In-memory Core Data for tests
+        coreDataStack = try await CoreDataStack.createForTests()
+        context = coreDataStack.newTaskContext()
+        importer = JSONImporter(context: context)
+
+        // Clear import flags and remove fixtures
+        let flags = [
+            "pumpHistoryImported",
+            "carbHistoryImported",
+            "glucoseHistoryImported",
+            "enactedHistoryImported"
+        ]
+        flags.forEach { UserDefaults.standard.removeObject(forKey: $0) }
+        let comps = [
+            OpenAPS.Monitor.pumpHistory,
+            OpenAPS.Monitor.carbHistory,
+            OpenAPS.Monitor.glucose,
+            OpenAPS.Enact.enacted
+        ]
+        comps.forEach { try? fileManager.removeItem(at: documentsURL.appendingPathComponent($0)) }
+    }
+
+    private let iso8601WithFractionalSecondsFormatter: ISO8601DateFormatter = {
+        let formatter = ISO8601DateFormatter()
+        // ensure it parses the full internet date+time with milliseconds
+        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
+        return formatter
+    }()
+
+    /// Parses an ISO‑8601 string (e.g. "2025-04-17T10:00:00.000Z") into a `Date`.
+    /// - Parameter isoString: the ISO‑8601 date string.
+    /// - Returns: a `Date` if parsing succeeds, or `nil` otherwise.
+    func dateFromISOString(_ isoString: String) -> Date? {
+        iso8601WithFractionalSecondsFormatter.date(from: isoString)
+    }
+
+    @Test("Import pump history with value checks") func testImportPumpHistoryDetails() async throws {
+        let pumpHistory = [
+            PumpHistoryEvent(
+                id: "9DDAA42F-465C-4812-9422-9933FB1CC290",
+                type: .bolus,
+                timestamp: dateFromISOString("2025-04-17T10:00:00.000Z") ?? Date(),
+                amount: 1.0,
+                duration: 0,
+                isSMB: false,
+                isExternal: true
+            ),
+            PumpHistoryEvent(
+                id: "F958F9A5-78F3-4B6C-AF6C-5B580BBB8A29",
+                type: .bolus,
+                timestamp: dateFromISOString("2025-04-17T10:01:00.000Z") ?? Date(),
+                amount: 2.0,
+                duration: 0,
+                isSMB: false,
+                isExternal: false
+            ),
+            PumpHistoryEvent(
+                id: "CCBE1CDA-EE13-4D7C-8CCC-7361EC9C979D",
+                type: .bolus,
+                timestamp: dateFromISOString("2025-04-17T10:02:00.000Z") ?? Date(),
+                amount: 3.0,
+                duration: 0,
+                isSMB: true,
+                isExternal: false
+            ),
+            PumpHistoryEvent(
+                id: "0FB76585-B6A4-4659-BDD2-B673BE6DD549",
+                type: .tempBasalDuration,
+                timestamp: dateFromISOString("2025-04-17T10:05:00.000Z") ?? Date(),
+                duration: 30
+            ),
+            PumpHistoryEvent(
+                id: "_0FB76585-B6A4-4659-BDD2-B673BE6DD549",
+                type: .tempBasal,
+                timestamp: dateFromISOString("2025-04-17T10:05:00.000Z") ?? Date(),
+                amount: 1.5,
+                duration: 0,
+                temp: .absolute
+            ),
+            PumpHistoryEvent(
+                id: "24909A93-0BC7-46D0-837F-9B2028E22BFC",
+                type: .pumpSuspend,
+                timestamp: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date()
+            ),
+            PumpHistoryEvent(
+                id: "BDEF7F55-48FE-447D-876C-19260ADE5ECA",
+                type: .pumpResume,
+                timestamp: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date()
+            ),
+            PumpHistoryEvent(
+                id: "1CAEEFA3-D740-4EA0-83B4-D28860991639",
+                type: .rewind,
+                timestamp: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date()
+            ),
+            PumpHistoryEvent(
+                id: "CD019C44-57F0-4CB0-BBDF-8B6C40A48E99",
+                type: .prime,
+                timestamp: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date()
+            )
+        ]
+
+        fileStorage.save(pumpHistory, as: OpenAPS.Monitor.pumpHistory)
+
+        // Import
+        await importer.importPumpHistoryIfNeeded()
+
+        // Fetch all imported events
+        let events = try await coreDataStack.fetchEntitiesAsync(
+            ofType: PumpEventStored.self,
+            onContext: context,
+            predicate: NSPredicate(format: "TRUEPREDICATE"),
+            key: "timestamp",
+            ascending: true
+        ) as? [PumpEventStored] ?? []
+
+        // Verify total count
+        #expect(events.count == 8, "Should import all 8 pump events") // TBR should be combination of TB duration and TBR, so 8, not 9
+
+        let iso8601 = ISO8601DateFormatter()
+        iso8601.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
+
+        // Verify the three Boluses
+        let bolusEvents = events.filter { $0.type == PumpEventStored.EventType.bolus.rawValue }
+        #expect(bolusEvents.count == 3, "Three bolus events")
+        let extDate = iso8601.date(from: "2025-04-17T10:00:00.000Z")!
+        let nonExtDate = iso8601.date(from: "2025-04-17T10:01:00.000Z")!
+        let smbDate = iso8601.date(from: "2025-04-17T10:02:00.000Z")!
+
+        // external bolus
+        #expect(
+            bolusEvents.contains {
+                abs($0.timestamp!.timeIntervalSince(extDate)) < 0.001 &&
+                    $0.bolus?.amount == NSDecimalNumber(value: 1.0) &&
+                    $0.bolus?.isExternal == true &&
+                    $0.bolus?.isSMB == false
+            },
+            "External bolus (1.0 U) at 10:00"
+        )
+        // non‑external
+        #expect(
+            bolusEvents.contains {
+                abs($0.timestamp!.timeIntervalSince(nonExtDate)) < 0.001 &&
+                    $0.bolus?.amount == NSDecimalNumber(value: 2.0) &&
+                    $0.bolus?.isExternal == false &&
+                    $0.bolus?.isSMB == false
+            },
+            "Non‑external bolus (2.0 U) at 10:01"
+        )
+        // SMB
+        #expect(
+            bolusEvents.contains {
+                abs($0.timestamp!.timeIntervalSince(smbDate)) < 0.001 &&
+                    $0.bolus?.amount == NSDecimalNumber(value: 3.0) &&
+                    $0.bolus?.isExternal == false &&
+                    $0.bolus?.isSMB == true
+            },
+            "SMB bolus (3.0 U) at 10:02"
+        )
+
+        // Verify TempBasalDuration + TempBasal
+        let durDate = iso8601.date(from: "2025-04-17T10:05:00.000Z")!
+        let durEvt = events.first {
+            abs($0.timestamp!.timeIntervalSince(durDate)) < 0.001 &&
+                $0.tempBasal?.duration == 30 &&
+                $0.tempBasal?.rate == NSDecimalNumber(value: 1.5)
+        }
+        #expect(durEvt != nil, "TempBasalRate at 10:05 for 30 min with rate 1.5 U/h")
+
+        // Verify the four “marker” events
+        let markers: [(type: PumpEventStored.EventType, ts: String)] = [
+            (.pumpSuspend, "2025-04-17T10:10:00.000Z"),
+            (.pumpResume, "2025-04-17T10:15:00.000Z"),
+            (.rewind, "2025-04-17T10:20:00.000Z"),
+            (.prime, "2025-04-17T10:25:00.000Z")
+        ]
+
+        for (eventType, tsString) in markers {
+            let date = iso8601.date(from: tsString)!
+            #expect(
+                events.contains {
+                    $0.type == eventType.rawValue &&
+                        abs($0.timestamp!.timeIntervalSince(date)) < 0.001
+                },
+                "\(eventType) at \(tsString)"
+            )
+        }
+
+        // Ensure file cleaned up and flag set
+        let url = documentsURL.appendingPathComponent(OpenAPS.Monitor.pumpHistory)
+        #expect(!fileManager.fileExists(atPath: url.path), "Pump JSON should be removed")
+        #expect(UserDefaults.standard.bool(forKey: "pumpHistoryImported"))
+    }
+
+
+    @Test("Import carb history with property checks") func testImportCarbHistoryDetails() async throws {
+        let carbHistory = [
+            CarbsEntry(
+                id: "CF9BE626-5B4F-421C-825F-BDEB873FF385",
+                createdAt: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date(),
+                actualDate: dateFromISOString("2025-04-17T10:10:00.000Z") ?? Date(),
+                carbs: 2,
+                fat: 0,
+                protein: 0,
+                note: "",
+                enteredBy: "Trio",
+                isFPU: true,
+                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
+            ),
+            CarbsEntry(
+                id: "6FFED023-DE5C-4042-8E4D-D876C37F528C",
+                createdAt: dateFromISOString("2025-04-21T21:58:25.452Z") ?? Date(),
+                actualDate: dateFromISOString("2025-04-21T21:58:25.452Z") ?? Date(),
+                carbs: 2,
+                fat: 0,
+                protein: 0,
+                note: "",
+                enteredBy: "Trio",
+                isFPU: true,
+                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
+            ),
+            CarbsEntry(
+                id: "32859426-03FC-4CF7-B9A1-16E122C04889",
+                createdAt: dateFromISOString("2025-04-21T21:28:25.452Z") ?? Date(),
+                actualDate: dateFromISOString("2025-04-21T21:28:25.452Z") ?? Date(),
+                carbs: 2,
+                fat: 0,
+                protein: 0,
+                note: "",
+                enteredBy: "Trio",
+                isFPU: true,
+                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
+            ),
+            CarbsEntry(
+                id: "F9AA11B6-8B2E-4FCA-9E3C-EFE582786CFD",
+                createdAt: dateFromISOString("2025-04-21T20:58:25.452Z") ?? Date(),
+                actualDate: dateFromISOString("2025-04-21T20:58:25.452Z") ?? Date(),
+                carbs: 2,
+                fat: 0,
+                protein: 0,
+                note: "",
+                enteredBy: "Trio",
+                isFPU: true,
+                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
+            ),
+            CarbsEntry(
+                id: "D2986C75-8EEF-4ACB-AE62-35B5391D437D",
+                createdAt: dateFromISOString("2025-04-21T20:28:25.452Z") ?? Date(),
+                actualDate: dateFromISOString("2025-04-21T20:28:25.452Z") ?? Date(),
+                carbs: 2,
+                fat: 0,
+                protein: 0,
+                note: "",
+                enteredBy: "Trio",
+                isFPU: true,
+                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
+            ),
+            CarbsEntry(
+                id: "E2F186B2-6A8F-4BC4-A038-3C104F988A78",
+                createdAt: dateFromISOString("2025-04-21T19:58:25.452Z") ?? Date(),
+                actualDate: dateFromISOString("2025-04-21T19:58:25.452Z") ?? Date(),
+                carbs: 2,
+                fat: 0,
+                protein: 0,
+                note: "",
+                enteredBy: "Trio",
+                isFPU: true,
+                fpuID: "EF2A99A8-3D96-4F92-8412-2D43C8CF6859"
+            ),
+            CarbsEntry(
+                id: "EEE7F9A0-490C-4E2B-8F04-ED8A77FC7867",
+                createdAt: dateFromISOString("2025-04-21T18:58:25.452Z") ?? Date(),
+                actualDate: dateFromISOString("2025-04-21T18:58:25.452Z") ?? Date(),
+                carbs: 45,
+                fat: 15,
+                protein: 25,
+                note: "",
+                enteredBy: "Trio",
+                isFPU: true,
+                fpuID: nil
+            ),
+            CarbsEntry(
+                id: "283155A7-5AF0-486E-BD3B-F9F8E2354845",
+                createdAt: dateFromISOString("2025-04-21T16:50:02.104Z") ?? Date(),
+                actualDate: dateFromISOString("2025-04-21T16:50:02.104Z") ?? Date(),
+                carbs: 30,
+                fat: 0,
+                protein: 0,
+                note: "",
+                enteredBy: "Trio",
+                isFPU: true,
+                fpuID: nil
+            )
+        ]
+        
+        fileStorage.save(carbHistory, as: OpenAPS.Monitor.carbHistory)
+
+        await importer.importCarbHistoryIfNeeded()
+
+        // Fetch all imported events
+        let entries = try await coreDataStack.fetchEntitiesAsync(
+            ofType: CarbEntryStored.self,
+            onContext: context,
+            predicate: NSPredicate(format: "TRUEPREDICATE"),
+            key: "date",
+            ascending: false
+        ) as? [CarbEntryStored] ?? []
+
+        #expect(entries.count == 8, "Should import 8 carb entries")
+        
+        // TODO: add distinct tests
+
+        let url = documentsURL.appendingPathComponent(OpenAPS.Monitor.carbHistory)
+        #expect(!fileManager.fileExists(atPath: url.path))
+        #expect(UserDefaults.standard.bool(forKey: "carbHistoryImported"))
+    }
+
+    @Test("Import glucose history with manual flag checks") func testImportGlucoseHistoryDetails() async throws {
+        let glucoseReadings = [
+            BloodGlucose(
+                _id: "A2BDFCE8-1978-4E12-9B29-BD11DB44A739",
+                sgv: 107,
+                direction: .flat,
+                date: 1733677520950,
+                dateString: dateFromISOString("2024-08-23T20:24:07.950Z") ?? Date(),
+                unfiltered: 107,
+                filtered: nil,
+                noise: nil,
+                glucose: 107,
+                type: "sgv",
+                transmitterID: "ABC123"
+            ),
+            BloodGlucose(
+                _id: "A2BDFCE8-1978-4E12-9B29-BD11DB44A739",
+                sgv: 112,
+                direction: .fortyFiveUp,
+                date: 1733676920294,
+                dateString: dateFromISOString("2024-12-08T16:55:20.295Z") ?? Date(),
+                unfiltered: 112,
+                filtered: nil,
+                noise: nil,
+                glucose: 112,
+                type: "sgv",
+                transmitterID: "ABC123"
+            ),
+            BloodGlucose(
+                _id: "A2BDFCE8-1978-4E12-9B29-BD11DB44A739",
+                sgv: 97,
+                direction: .fortyFiveDown,
+                date: 1733676620784,
+                dateString: dateFromISOString("2024-12-08T16:50:20.784Z") ?? Date(),
+                unfiltered: 97,
+                filtered: nil,
+                noise: nil,
+                glucose: 97,
+                type: "sgv",
+                transmitterID: "ABC123"
+            ),
+            BloodGlucose(
+                _id: "A2BDFCE8-1978-4E12-9B29-BD11DB44A739",
+                sgv: 70,
+                direction: .doubleDown,
+                date: 1733676320525,
+                dateString: dateFromISOString("2024-12-08T16:45:20.525Z") ?? Date(),
+                unfiltered: 70,
+                filtered: nil,
+                noise: nil,
+                glucose: 70,
+                type: "sgv",
+                transmitterID: "ABC123"
+            ),
+            BloodGlucose(
+                _id: "A2BDFCE8-1978-4E12-9B29-BD11DB44A739",
+                sgv: 188,
+                direction: .doubleUp,
+                date: 1733676020918,
+                dateString: dateFromISOString("2024-12-08T16:40:20.919Z") ?? Date(),
+                unfiltered: 188,
+                filtered: nil,
+                noise: nil,
+                glucose: 188,
+                type: "sgv",
+                transmitterID: "ABC123"
+            )
+        ]
+
+        // Fetch all GlucoseStored entries sorted by date
+        let allReadings = try await coreDataStack.fetchEntitiesAsync(
+            ofType: GlucoseStored.self,
+            onContext: context,
+            predicate: NSPredicate(format: "TRUEPREDICATE"),
+            key: "date",
+            ascending: true
+        ) as? [GlucoseStored] ?? []
+
+        #expect(allReadings.count == 5, "Should have imported 5 glucose readings")
+
+        // TODO: add distinct tests
+        
+        let url = documentsURL.appendingPathComponent(OpenAPS.Monitor.glucose)
+        #expect(!fileManager.fileExists(atPath: url.path))
+        #expect(UserDefaults.standard.bool(forKey: "glucoseHistoryImported"))
+    }
+
+    @Test("Import determination history with nested predBGs and values") func testImportDeterminationHistoryDetails() async throws {
+        let iobValues: [Int] = [
+            153, 149, 144, 139, 134, 129, 124, 119, 114, 109,
+            103,  98,  92,  87,  81,  76,  71,  65,  60,  55,
+             50,  44,  39
+        ]
+
+        let ztValues: [Int] = [
+            153, 147, 140, 134, 128, 121, 115, 109, 104,  98,
+             93,  87,  83,  78,  74,  70,  66,  63,  60,  57,
+             55,  53,  52,  51,  51,  51,  51,  52,  53,  54,
+             56,  58,  60,  62,  65,  67,  70,  73,  76,  78,
+             81,  84
+        ]
+
+        let uamValues: [Int] = [
+            153, 147, 140, 134, 127, 121, 115, 108, 102,  96,
+             89,  83,  77,  71,  65,  58,  52,  45,  39
+        ]
+
+        let determination = Determination(
+            id: UUID(),
+            reason: "Autosens ratio: 0.94, ISF: 45→48, COB: 0, Dev: 13, BGI: -6, CR: 7.8→8.3, Target: 85, minPredBG 45, minGuardBG -53, IOBpredBG 39, UAMpredBG 39, TDD: 42.2 U, 89% Bolus 11% Basal, Dynamic ISF/CR: On/On, Logarithmic formula, AF: 0.8, Basal ratio: 1.01; minGuardBG -53<70",
+            units: nil,
+            insulinReq: Decimal(0),
+            eventualBG: 46,
+            sensitivityRatio: Decimal(0.9430005356061704),
+            rate: Decimal(0),
+            duration: Decimal(120),
+            iob: Decimal(2.52),
+            cob: Decimal(0),
+            predictions: Predictions(iob: iobValues, zt: ztValues, cob: nil, uam: uamValues),
+            deliverAt:    dateFromISOString("2024-08-01T09:42:08.734Z") ?? Date(),
+            carbsReq:     nil,
+            temp:         TempType(rawValue: "absolute"),
+            bg:           Decimal(153),
+            reservoir:    Decimal(3735928559),
+            isf:          Decimal(48),
+            timestamp:    dateFromISOString("2024-08-01T09:42:09.371Z") ?? Date(),
+            current_target: Decimal(85),
+            insulinForManualBolus: nil,
+            manualBolusErrorString: Decimal(2),
+            minDelta:     Decimal(-4.28),
+            expectedDelta: Decimal(-4.7),
+            minGuardBG:   Decimal(-53),
+            minPredBG:    nil,
+            threshold:    Decimal(70),
+            carbRatio:    Decimal(8.3),
+            received:     true
+        )
+        
+        fileStorage.save(determination, as: OpenAPS.Enact.enacted)
+
+        // TODO: add distinct tests
+
+        let url = documentsURL.appendingPathComponent(OpenAPS.Enact.enacted)
+        #expect(!fileManager.fileExists(atPath: url.path))
+        #expect(UserDefaults.standard.bool(forKey: "enactedHistoryImported"))
+    }
+}