| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- //
- // 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"))
- }
- }
|