CarbsStorageTests.swift 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import CoreData
  2. import Foundation
  3. import Swinject
  4. import Testing
  5. @testable import Trio
  6. @Suite("CarbsStorage Tests", .serialized) struct CarbsStorageTests: Injectable {
  7. @Injected() var storage: CarbsStorage!
  8. let resolver: Resolver
  9. let coreDataStack = CoreDataStack.createForTests()
  10. let testContext: NSManagedObjectContext
  11. init() {
  12. // Create test context
  13. testContext = coreDataStack.newTaskContext()
  14. // Create assembler with test assembly
  15. let assembler = Assembler([
  16. StorageAssembly(),
  17. ServiceAssembly(),
  18. APSAssembly(),
  19. NetworkAssembly(),
  20. UIAssembly(),
  21. SecurityAssembly(),
  22. TestAssembly(testContext: testContext)
  23. ])
  24. resolver = assembler.resolver
  25. injectServices(resolver)
  26. }
  27. @Test("Storage is correctly initialized") func testStorageInitialization() {
  28. #expect(storage != nil, "CarbsStorage should be injected")
  29. #expect(storage is BaseCarbsStorage, "Storage should be of type BaseCarbsStorage")
  30. #expect(storage.updatePublisher != nil, "Update publisher should be available")
  31. }
  32. @Test("Store and retrieve carbs entries") func testStoreAndRetrieveCarbs() async throws {
  33. // Given
  34. let testEntries = [
  35. CarbsEntry(
  36. id: UUID().uuidString,
  37. createdAt: Date(),
  38. actualDate: Date(),
  39. carbs: 20,
  40. fat: 0,
  41. protein: 0,
  42. note: "Test meal",
  43. enteredBy: "Test",
  44. isFPU: false,
  45. fpuID: nil
  46. )
  47. ]
  48. // When
  49. try await storage.storeCarbs(testEntries, areFetchedFromRemote: false)
  50. let recentEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
  51. ofType: CarbEntryStored.self,
  52. onContext: testContext,
  53. predicate: NSPredicate(format: "TRUEPREDICATE"),
  54. key: "date",
  55. ascending: false
  56. )
  57. guard let recentEntries = recentEntries as? [CarbEntryStored] else {
  58. throw TestError("Failed to get recent entries")
  59. }
  60. // Then
  61. #expect(!recentEntries.isEmpty, "Should have stored entries")
  62. #expect(recentEntries.count == 1, "Should have exactly one entry")
  63. #expect(recentEntries[0].carbs == 20, "Carbs value should match")
  64. #expect(recentEntries[0].fat == 0, "Fat value should match")
  65. #expect(recentEntries[0].protein == 0, "Protein value should match")
  66. #expect(recentEntries[0].note == "Test meal", "Note should match")
  67. }
  68. @Test("Delete carbs entry") func testDeleteCarbsEntry() async throws {
  69. // Given
  70. let testEntry = CarbsEntry(
  71. id: UUID().uuidString,
  72. createdAt: Date(),
  73. actualDate: Date(),
  74. carbs: 30,
  75. fat: nil,
  76. protein: nil,
  77. note: "Delete test",
  78. enteredBy: "Test",
  79. isFPU: false,
  80. fpuID: nil
  81. )
  82. // When
  83. try await storage.storeCarbs([testEntry], areFetchedFromRemote: false)
  84. // Get the stored entry's ObjectID
  85. let storedEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
  86. ofType: CarbEntryStored.self,
  87. onContext: testContext,
  88. predicate: NSPredicate(format: "carbs == 30"),
  89. key: "date",
  90. ascending: false
  91. ) as? [CarbEntryStored]
  92. guard let objectID = storedEntries?.first?.objectID else {
  93. throw TestError("Failed to get stored entry's ObjectID")
  94. }
  95. // Delete the entry
  96. await storage.deleteCarbsEntryStored(objectID)
  97. // Then - verify deletion
  98. let remainingEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
  99. ofType: CarbEntryStored.self,
  100. onContext: testContext,
  101. predicate: NSPredicate(format: "carbs == 30"),
  102. key: "date",
  103. ascending: false
  104. ) as? [CarbEntryStored]
  105. #expect(remainingEntries?.isEmpty == true, "Should have no entries after deletion")
  106. }
  107. @Test("Get carbs not yet uploaded to Nightscout") func testGetCarbsNotYetUploadedToNightscout() async throws {
  108. // Given
  109. let testEntry = CarbsEntry(
  110. id: UUID().uuidString,
  111. createdAt: Date(),
  112. actualDate: Date(),
  113. carbs: 40,
  114. fat: nil,
  115. protein: nil,
  116. note: "NS test",
  117. enteredBy: "Test",
  118. isFPU: false,
  119. fpuID: nil
  120. )
  121. // When
  122. try await storage.storeCarbs([testEntry], areFetchedFromRemote: false)
  123. let notUploadedEntries = try await storage.getCarbsNotYetUploadedToNightscout()
  124. // Then
  125. #expect(!notUploadedEntries.isEmpty, "Should have entries not uploaded to NS")
  126. #expect(notUploadedEntries[0].carbs == 40, "Carbs value should match")
  127. }
  128. @Test("Get FPUs not yet uploaded to Nightscout") func testGetFPUsNotYetUploadedToNightscout() async throws {
  129. // Given
  130. let fpuID = UUID().uuidString
  131. let testEntry = CarbsEntry(
  132. id: UUID().uuidString,
  133. createdAt: Date(),
  134. actualDate: Date(),
  135. carbs: 30,
  136. fat: 20,
  137. protein: 10,
  138. note: "FPU test",
  139. enteredBy: "Test",
  140. isFPU: false,
  141. fpuID: fpuID
  142. )
  143. // When
  144. try await storage.storeCarbs([testEntry], areFetchedFromRemote: false)
  145. // First verify all stored entries
  146. let allStoredEntries = try await CoreDataStack.shared.fetchEntitiesAsync(
  147. ofType: CarbEntryStored.self,
  148. onContext: testContext,
  149. predicate: NSPredicate(format: "fpuID == %@", fpuID),
  150. key: "date",
  151. ascending: true
  152. ) as? [CarbEntryStored]
  153. // Then verify the stored entries
  154. #expect(allStoredEntries?.isEmpty == false, "Should have stored entries")
  155. #expect(allStoredEntries?.count ?? 0 > 1, "Should have multiple entries due to FPU splitting")
  156. // Original carb-non-fpu entry should be stored with original fat and protein values and isFPU set to false
  157. let carbNonFpuEntry = allStoredEntries?.first(where: { $0.isFPU == false })
  158. #expect(carbNonFpuEntry != nil, "Should have one carb non-fpu entry")
  159. #expect(carbNonFpuEntry?.carbs == 30, "Original carbs should match")
  160. #expect(carbNonFpuEntry?.protein == 10, "Original carbs should match")
  161. #expect(carbNonFpuEntry?.fat == 20, "Original carbs should match")
  162. // Additional carb-fpu entries should be created for fat/protein with isFPU set to true and the carbs set to the amount of each carbEquivalent
  163. let carbFpuEntry = allStoredEntries?.filter { $0.isFPU == true }
  164. #expect(carbFpuEntry?.isEmpty == false, "Should have additional carb-fpu entries")
  165. // Now test the Nightscout upload function
  166. let notUploadedFPUs = try await storage.getFPUsNotYetUploadedToNightscout()
  167. // Then verify Nightscout entries
  168. #expect(!notUploadedFPUs.isEmpty, "Should have FPUs not uploaded to NS")
  169. let fpu = notUploadedFPUs[0]
  170. #expect(fpu.carbs ?? 0 < 30, "Original carbs value should match")
  171. #expect(fpu.protein == 0, "Protein value should match")
  172. #expect(fpu.fat == 0, "Fat value should match")
  173. // Verify all entries share the same fpuID
  174. #expect(
  175. allStoredEntries?.allSatisfy { $0.fpuID?.uuidString == fpuID } == true,
  176. "All entries should share the same fpuID"
  177. )
  178. }
  179. }