GlucoseStorageTests.swift 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import CoreData
  2. import Foundation
  3. import Swinject
  4. import Testing
  5. @testable import Trio
  6. @Suite("GlucoseStorage Tests", .serialized) struct GlucoseStorageTests: Injectable {
  7. @Injected() var storage: GlucoseStorage!
  8. let resolver: Resolver
  9. let coreDataStack = CoreDataStack.createForTests()
  10. let testContext: NSManagedObjectContext
  11. init() {
  12. // Create test context
  13. // As we are only using this single test context to initialize our in-memory DeterminationStorage we need to perform the Unit Tests serialized
  14. testContext = coreDataStack.newTaskContext()
  15. // Create assembler with test assembly
  16. let assembler = Assembler([
  17. StorageAssembly(),
  18. ServiceAssembly(),
  19. APSAssembly(),
  20. NetworkAssembly(),
  21. UIAssembly(),
  22. SecurityAssembly(),
  23. TestAssembly(testContext: testContext) // Add our test assembly last to override Storage
  24. ])
  25. resolver = assembler.resolver
  26. injectServices(resolver)
  27. }
  28. @Test("Storage is correctly initialized") func testStorageInitialization() {
  29. // Verify storage exists
  30. #expect(storage != nil, "GlucoseStorage should be injected")
  31. // Verify it's the correct type
  32. #expect(storage is BaseGlucoseStorage, "Storage should be of type BaseGlucoseStorage")
  33. }
  34. @Test("Store and retrieve glucose entries") func testStoreAndRetrieveGlucose() async throws {
  35. // Given
  36. let testGlucose = [
  37. BloodGlucose(direction: BloodGlucose.Direction.flat, date: 123, dateString: Date(), glucose: 126)
  38. ]
  39. // When
  40. try await storage.storeGlucose(testGlucose)
  41. // Then verify stored entries
  42. let storedEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
  43. ofType: GlucoseStored.self,
  44. onContext: testContext,
  45. predicate: NSPredicate(format: "glucose == 126"),
  46. key: "date",
  47. ascending: false
  48. ) as? [GlucoseStored]
  49. #expect(storedEntries?.isEmpty == false, "Should have stored entries")
  50. #expect(storedEntries?.count == 1, "Should have exactly one entry")
  51. #expect(storedEntries?[0].glucose == 126, "Glucose value should match")
  52. #expect(storedEntries?[0].direction == "Flat", "Direction should match")
  53. }
  54. @Test("Delete glucose entry") func testDeleteGlucose() async throws {
  55. // Given
  56. let testGlucose = [
  57. BloodGlucose(direction: BloodGlucose.Direction.flat, date: 123, dateString: Date(), glucose: 140)
  58. ]
  59. try await storage.storeGlucose(testGlucose)
  60. // Get the stored entry's ObjectID
  61. let storedEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
  62. ofType: GlucoseStored.self,
  63. onContext: testContext,
  64. predicate: NSPredicate(format: "glucose == 140"),
  65. key: "date",
  66. ascending: false
  67. ) as? [GlucoseStored]
  68. guard let objectID = storedEntries?.first?.objectID else {
  69. throw TestError("Failed to get stored entry's ObjectID")
  70. }
  71. #expect(storedEntries.isNotNilNotEmpty == true, "Should have exactly one (test) entry")
  72. // When
  73. await storage.deleteGlucose(objectID)
  74. // Then verify deletion
  75. let remainingEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
  76. ofType: GlucoseStored.self,
  77. onContext: testContext,
  78. predicate: NSPredicate(format: "glucose == 140"),
  79. key: "date",
  80. ascending: false
  81. ) as? [GlucoseStored]
  82. #expect(remainingEntries?.isEmpty == true, "Should have no entries after deletion")
  83. }
  84. @Test("Get glucose not yet uploaded to Nightscout") func testGetGlucoseNotYetUploadedToNightscout() async throws {
  85. // Given
  86. let testGlucose = [
  87. BloodGlucose(direction: BloodGlucose.Direction.flat, date: 123, dateString: Date(), glucose: 160)
  88. ]
  89. try await storage.storeGlucose(testGlucose)
  90. // When
  91. let notUploadedEntries = try await storage.getGlucoseNotYetUploadedToNightscout()
  92. // Then
  93. #expect(!notUploadedEntries.isEmpty, "Should have entries not uploaded to NS")
  94. #expect(notUploadedEntries[0].glucose == 160, "Glucose value should match")
  95. }
  96. @Test("Get manual glucose not yet uploaded to Nightscout") func testGetManualGlucoseNotYetUploadedToNightscout() async throws {
  97. // Given
  98. storage.addManualGlucose(glucose: 180)
  99. // When
  100. let notUploadedEntries = try await storage.getManualGlucoseNotYetUploadedToNightscout()
  101. // Then
  102. #expect(!notUploadedEntries.isEmpty, "Should have manual entries not uploaded to NS")
  103. let entry = notUploadedEntries[0]
  104. #expect(entry.glucose == "180", "Glucose value should match")
  105. #expect(entry.glucoseType == "Manual", "Type should be mbg for manual entries")
  106. #expect(entry.eventType == .capillaryGlucose, "Type should be capillaryGlucose")
  107. }
  108. @Test("Test glucose alarms") func testGlucoseAlarms() async throws {
  109. // Given
  110. let lowGlucose = [
  111. BloodGlucose(direction: BloodGlucose.Direction.flat, date: 123, dateString: Date(), glucose: 55)
  112. ]
  113. let highGlucose = [
  114. BloodGlucose(direction: BloodGlucose.Direction.flat, date: 123, dateString: Date(), glucose: 271)
  115. ]
  116. let normalGlucose = [
  117. BloodGlucose(direction: BloodGlucose.Direction.flat, date: 123, dateString: Date(), glucose: 100)
  118. ]
  119. // When - Test low glucose
  120. try await storage.storeGlucose(lowGlucose)
  121. var storedEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
  122. ofType: GlucoseStored.self,
  123. onContext: testContext,
  124. predicate: NSPredicate(format: "glucose == 55"),
  125. key: "date",
  126. ascending: false
  127. ) as? [GlucoseStored]
  128. // Then
  129. #expect(storedEntries?.first?.glucose == 55, "Low glucose value should match")
  130. #expect(storage.alarm == .low, "Should trigger low glucose alarm") // default low limit is 72 mg/dL
  131. // When - Test high glucose
  132. try await storage.storeGlucose(highGlucose)
  133. storedEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
  134. ofType: GlucoseStored.self,
  135. onContext: testContext,
  136. predicate: NSPredicate(format: "glucose == 271"),
  137. key: "date",
  138. ascending: false
  139. ) as? [GlucoseStored]
  140. // Then
  141. #expect(storedEntries?.first?.glucose == 271, "High glucose value should match")
  142. #expect(storage.alarm == .high, "Should trigger high glucose alarm") // default high limit is 270 mg/dL
  143. // When - Test normal glucose
  144. try await storage.storeGlucose(normalGlucose)
  145. storedEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
  146. ofType: GlucoseStored.self,
  147. onContext: testContext,
  148. predicate: NSPredicate(format: "glucose == 100"),
  149. key: "date",
  150. ascending: false
  151. ) as? [GlucoseStored]
  152. // Then
  153. #expect(storedEntries?.first?.glucose == 100, "Normal glucose value should match")
  154. #expect(storage.alarm == nil, "Should not trigger any alarm")
  155. }
  156. }