Explorar o código

Extend and fix determination import unit tests

Deniz Cengiz hai 1 ano
pai
achega
398ce3b024
Modificáronse 2 ficheiros con 94 adicións e 32 borrados
  1. 1 2
      Model/JSONImporter.swift
  2. 93 30
      TrioTests/JSONImporterTests.swift

+ 1 - 2
Model/JSONImporter.swift

@@ -324,7 +324,6 @@ class JSONImporter {
         backgroundContext.parent = context
 
         try await backgroundContext.perform {
-            
             /// We know both determination entries are from within last 24 hrs via `checkDeterminationDate()` in the earlier `guard` clause
             /// If their `deliverAt` does not match, and if `suggestedDeliverAt` is newer, it is worth storing them both, as that represents
             /// a more recent algorithm run that did not cause a dosing enactment, e.g., a carb entry or a manual bolus.
@@ -611,7 +610,7 @@ extension Determination: Codable {
     /// Helper function to convert `Determination` to `OrefDetermination` while importing JSON glucose entries
     func store(in context: NSManagedObjectContext) throws {
         // TODO: some guards here ?!
-        
+
         let newOrefDetermination = OrefDetermination(context: context)
         newOrefDetermination.id = UUID()
         newOrefDetermination.insulinSensitivity = decimalToNSDecimalNumber(isf)

+ 93 - 30
TrioTests/JSONImporterTests.swift

@@ -17,7 +17,6 @@ class BundleReference {}
     var coreDataStack: CoreDataStack!
     var context: NSManagedObjectContext!
     var importer: JSONImporter!
-    @Injected() var determinationStorage: DeterminationStorage!
 
     init() async throws {
         // In-memory Core Data for tests
@@ -249,35 +248,99 @@ class BundleReference {}
         #expect(determination.threshold == Decimal(string: "3.7").map(NSDecimalNumber.init))
         #expect(determination.carbRatio == nil) // not present in JSON
 
-        // TODO: fix forecast testing
-//        let forecastHierarchy = try await determinationStorage.fetchForecastHierarchy(for: determination.objectID, in: context)
-//
-//        for entry in forecastHierarchy {
-//            let (_, forecast, values) = await determinationStorage.fetchForecastObjects(for: entry, in: context)
-//
-//            // Basic checks
-//            #expect(forecast != nil)
-//            #expect(!values.isEmpty)
-//
-//            if let forecast = forecast {
-//                let sortedValues = values.sorted { $0.index < $1.index }
-//                let prefix = sortedValues.prefix(5).compactMap(\.value)
-//                let type = forecast.type?.lowercased()
-//
-//                switch type {
-//                case "zt":
-//                    #expect(prefix == [85, 78, 71, 64, 58])
-//                case "iob":
-//                    #expect(prefix == [85, 89, 92, 95, 97])
-//                case "uam":
-//                    #expect(prefix == [85, 89, 93, 96, 99])
-//                case "cob":
-//                    #expect(prefix == [85, 90, 94, 99, 103])
-//                default:
-//                    break // Skip unknown forecast types silently
-//                }
-//            }
-//        }
+        let forecasts = try await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: Forecast.self,
+            onContext: context,
+            predicate: NSPredicate(format: "orefDetermination = %@", determination.objectID),
+            key: "type",
+            ascending: true,
+            relationshipKeyPathsForPrefetching: ["forecastValues"]
+        )
+
+        var forecastHierarchy: [(forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID])] = []
+
+        await context.perform {
+            if let forecasts = forecasts as? [Forecast] {
+                for forecast in forecasts {
+                    // Use the helper property that already sorts by index
+                    let sortedValues = forecast.forecastValuesArray
+                    forecastHierarchy.append((
+                        forecastID: forecast.objectID,
+                        forecastValueIDs: sortedValues.map(\.objectID)
+                    ))
+                }
+            }
+
+            for entry in forecastHierarchy {
+                var forecastValueTuple: (Forecast?, [ForecastValue]) = (nil, [])
+
+                var forecast: Forecast?
+                var forecastValues: [ForecastValue] = []
+
+                do {
+                    // Fetch the forecast object
+                    forecast = try context.existingObject(with: entry.forecastID) as? Forecast
+
+                    // Fetch the first 3h of forecast values
+                    for forecastValueID in entry.forecastValueIDs.prefix(36) {
+                        if let forecastValue = try context.existingObject(with: forecastValueID) as? ForecastValue {
+                            forecastValues.append(forecastValue)
+                        }
+                    }
+                } catch {
+                    debugPrint(
+                        "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch forecast Values with error: \(error.localizedDescription)"
+                    )
+                }
+                forecastValueTuple = (forecast, forecastValues)
+
+                // Basic checks
+                #expect(forecastValueTuple.0 != nil)
+                #expect(forecastValueTuple.1.isNotEmpty == true)
+
+                if let forecast = forecastValueTuple.0 {
+                    let sortedValues = forecastValueTuple.1.sorted { $0.index < $1.index }
+                    let prefix = sortedValues.prefix(5).compactMap(\.value)
+                    let type = forecast.type?.lowercased()
+
+                    switch type {
+                    case "zt":
+                        #expect(prefix == [85, 78, 71, 64, 58])
+                    case "iob":
+                        #expect(prefix == [85, 89, 92, 95, 97])
+                    case "uam":
+                        #expect(prefix == [85, 89, 93, 96, 99])
+                    case "cob":
+                        #expect(prefix == [85, 90, 94, 99, 103])
+                    default:
+                        break // Skip unknown forecast types silently
+                    }
+                }
+            }
+        }
+    }
+
+    @Test("Skip importing old determinations") func testSkipImportOldDeterminationData() async throws {
+        let testBundle = Bundle(for: BundleReference.self)
+        let enactedPath = testBundle.path(forResource: "enacted", ofType: "json")!
+        let enactedUrl = URL(filePath: enactedPath)
+        let suggestedPath = testBundle.path(forResource: "suggested", ofType: "json")!
+        let suggestedUrl = URL(filePath: suggestedPath)
+
+        // more than 24 hours in the future from the most recent entry
+        let now = Date("2025-04-29T22:00:00.000Z")!
+
+        try await importer.importOrefDetermination(enactedUrl: enactedUrl, suggestedUrl: suggestedUrl, now: now)
+
+        let determinations = try await coreDataStack.fetchEntitiesAsync(
+            ofType: OrefDetermination.self,
+            onContext: context,
+            predicate: NSPredicate(format: "TRUEPREDICATE"),
+            key: "deliverAt",
+            ascending: false
+        ) as? [OrefDetermination] ?? []
+
+        #expect(determinations.isEmpty)
     }
 }