JSONImporterTests.swift 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. //
  2. // JSONImporterTests.swift
  3. // Trio
  4. //
  5. // Created by Cengiz Deniz on 21.04.25.
  6. //
  7. import CoreData
  8. import Foundation
  9. import Swinject
  10. import Testing
  11. @testable import Trio
  12. class BundleReference {}
  13. @Suite("JSON Importer Tests", .serialized) struct JSONImporterTests: Injectable {
  14. var coreDataStack: CoreDataStack!
  15. var context: NSManagedObjectContext!
  16. var importer: JSONImporter!
  17. @Injected() var determinationStorage: DeterminationStorage!
  18. init() async throws {
  19. // In-memory Core Data for tests
  20. coreDataStack = try await CoreDataStack.createForTests()
  21. context = coreDataStack.newTaskContext()
  22. importer = JSONImporter(context: context, coreDataStack: coreDataStack)
  23. }
  24. @Test("Import glucose history with value checks") func testImportGlucoseHistoryDetails() async throws {
  25. let testBundle = Bundle(for: BundleReference.self)
  26. let path = testBundle.path(forResource: "glucose", ofType: "json")!
  27. let url = URL(filePath: path)
  28. let now = Date("2025-04-28T19:32:52.000Z")!
  29. try await importer.importGlucoseHistory(url: url, now: now)
  30. // run the import againt to check our deduplication logic
  31. try await importer.importGlucoseHistory(url: url, now: now)
  32. let allReadings = try await coreDataStack.fetchEntitiesAsync(
  33. ofType: GlucoseStored.self,
  34. onContext: context,
  35. predicate: NSPredicate(format: "TRUEPREDICATE"),
  36. key: "date",
  37. ascending: false
  38. ) as? [GlucoseStored] ?? []
  39. #expect(allReadings.count == 274)
  40. #expect(allReadings.first?.glucose == 115)
  41. #expect(allReadings.first?.date == Date("2025-04-28T19:32:51.727Z"))
  42. #expect(allReadings.last?.glucose == 127)
  43. #expect(allReadings.last?.date == Date("2025-04-27T19:37:50.327Z"))
  44. let manualCount = allReadings.filter({ $0.isManual }).count
  45. #expect(manualCount == 1)
  46. }
  47. @Test("Skip importing old glucose values") func testSkipImportOldGlucoseValues() async throws {
  48. let testBundle = Bundle(for: BundleReference.self)
  49. let path = testBundle.path(forResource: "glucose", ofType: "json")!
  50. let url = URL(filePath: path)
  51. // more than 24 hours in the future from the most recent entry
  52. let now = Date("2025-04-29T19:32:52.000Z")!
  53. try await importer.importGlucoseHistory(url: url, now: now)
  54. let allReadings = try await coreDataStack.fetchEntitiesAsync(
  55. ofType: GlucoseStored.self,
  56. onContext: context,
  57. predicate: NSPredicate(format: "TRUEPREDICATE"),
  58. key: "date",
  59. ascending: false
  60. ) as? [GlucoseStored] ?? []
  61. #expect(allReadings.isEmpty)
  62. }
  63. @Test("Import determination data with value checks") func testImportDeterminationDetails() async throws {
  64. let testBundle = Bundle(for: BundleReference.self)
  65. let enactedPath = testBundle.path(forResource: "enacted", ofType: "json")!
  66. let enactedUrl = URL(filePath: enactedPath)
  67. let suggestedPath = testBundle.path(forResource: "suggested", ofType: "json")!
  68. let suggestedUrl = URL(filePath: suggestedPath)
  69. let now = Date("2025-04-28T20:50:00.000Z")!
  70. try await importer.importOrefDetermination(enactedUrl: enactedUrl, suggestedUrl: suggestedUrl, now: now)
  71. // run the import againt to check our deduplication logic
  72. try await importer.importOrefDetermination(enactedUrl: enactedUrl, suggestedUrl: suggestedUrl, now: now)
  73. let determinations = try await coreDataStack.fetchEntitiesAsync(
  74. ofType: OrefDetermination.self,
  75. onContext: context,
  76. predicate: NSPredicate(format: "TRUEPREDICATE"),
  77. key: "deliverAt",
  78. ascending: false
  79. ) as? [OrefDetermination] ?? []
  80. #expect(determinations.count == 1) // single determination, as enacted.deliverAt and suggested.deliverAt match
  81. let determination = determinations.first!
  82. #expect(determination.deliverAt == Date("2025-04-28T19:41:43.564Z"))
  83. #expect(determination.timestamp == Date("2025-04-28T19:41:48.453Z"))
  84. #expect(determination.enacted == true)
  85. #expect(determination.reason?.starts(with: "Autosens ratio: 0.99") == true)
  86. #expect(determination.insulinReq == Decimal(string: "0.29").map(NSDecimalNumber.init))
  87. #expect(determination.eventualBG! == NSDecimalNumber(160))
  88. #expect(determination.sensitivityRatio == Decimal(string: "0.9863849810728643").map(NSDecimalNumber.init))
  89. #expect(determination.rate == Decimal(string: "0").map(NSDecimalNumber.init))
  90. #expect(determination.duration == NSDecimalNumber(60))
  91. #expect(determination.iob == Decimal(string: "1.249").map(NSDecimalNumber.init))
  92. #expect(determination.cob == 34)
  93. #expect(determination.temp == "absolute")
  94. #expect(determination.glucose == NSDecimalNumber(85))
  95. #expect(determination.reservoir == Decimal(string: "3735928559").map(NSDecimalNumber.init))
  96. #expect(determination.insulinSensitivity == Decimal(string: "4.6").map(NSDecimalNumber.init))
  97. #expect(determination.currentTarget == Decimal(string: "94").map(NSDecimalNumber.init))
  98. #expect(determination.insulinForManualBolus == Decimal(string: "0.8").map(NSDecimalNumber.init))
  99. #expect(determination.manualBolusErrorString == Decimal(string: "0").map(NSDecimalNumber.init))
  100. #expect(determination.minDelta == NSDecimalNumber(5))
  101. #expect(determination.expectedDelta == Decimal(string: "-5.9").map(NSDecimalNumber.init))
  102. #expect(determination.threshold == Decimal(string: "3.7").map(NSDecimalNumber.init))
  103. #expect(determination.carbRatio == nil) // not present in JSON
  104. // TODO: fix forecast testing
  105. // let forecastHierarchy = try await determinationStorage.fetchForecastHierarchy(for: determination.objectID, in: context)
  106. //
  107. // for entry in forecastHierarchy {
  108. // let (_, forecast, values) = await determinationStorage.fetchForecastObjects(for: entry, in: context)
  109. //
  110. // // Basic checks
  111. // #expect(forecast != nil)
  112. // #expect(!values.isEmpty)
  113. //
  114. // if let forecast = forecast {
  115. // let sortedValues = values.sorted { $0.index < $1.index }
  116. // let prefix = sortedValues.prefix(5).compactMap(\.value)
  117. // let type = forecast.type?.lowercased()
  118. //
  119. // switch type {
  120. // case "zt":
  121. // #expect(prefix == [85, 78, 71, 64, 58])
  122. // case "iob":
  123. // #expect(prefix == [85, 89, 92, 95, 97])
  124. // case "uam":
  125. // #expect(prefix == [85, 89, 93, 96, 99])
  126. // case "cob":
  127. // #expect(prefix == [85, 90, 94, 99, 103])
  128. // default:
  129. // break // Skip unknown forecast types silently
  130. // }
  131. // }
  132. // }
  133. }
  134. }