CarbsStorageTests.swift 7.6 KB

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