GlucoseStorageTests.swift 7.3 KB

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