| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- import CoreData
- import Foundation
- import Swinject
- import Testing
- @testable import LoopKit
- @testable import Trio
- @Suite("PumpHistoryStorage Tests", .serialized) struct PumpHistoryStorageTests: Injectable {
- @Injected() var storage: PumpHistoryStorage!
- let resolver: Resolver
- var coreDataStack: CoreDataStack!
- var testContext: NSManagedObjectContext!
- typealias PumpEvent = PumpEventStored.EventType
- init() async throws {
- // Create test context
- coreDataStack = try await CoreDataStack.createForTests()
- testContext = coreDataStack.newTaskContext()
- // Create assembler with test assembly
- let assembler = Assembler([
- StorageAssembly(),
- ServiceAssembly(),
- APSAssembly(),
- NetworkAssembly(),
- UIAssembly(),
- SecurityAssembly(),
- TestAssembly(testContext: testContext) // Add our test assembly last to override PumpHistoryStorage
- ])
- resolver = assembler.resolver
- injectServices(resolver)
- }
- @Test("Storage is correctly initialized") func testStorageInitialization() {
- // Verify storage exists
- #expect(storage != nil, "PumpHistoryStorage should be injected")
- // Verify it's the correct type
- #expect(
- storage is BasePumpHistoryStorage, "Storage should be of type BasePumpHistoryStorage"
- )
- // Verify we can access the update publisher
- #expect(storage.updatePublisher != nil, "Update publisher should be available")
- }
- @Test("Test read and delete using generic CoreDataStack functions") func testFetchAndDeletePumpEvents() async throws {
- // Given
- let date = Date()
- // Insert mock entry
- let events: [LoopKit.NewPumpEvent] = [
- LoopKit.NewPumpEvent(
- date: date,
- dose: LoopKit.DoseEntry(
- type: .bolus,
- startDate: date,
- value: 0.5,
- unit: .units,
- deliveredUnits: nil,
- description: nil,
- syncIdentifier: nil,
- scheduledBasalRate: nil,
- insulinType: .lyumjev,
- automatic: false,
- manuallyEntered: false,
- isMutable: false
- ),
- raw: Data(),
- title: "Test Bolus for Fetch",
- type: .bolus
- )
- ]
- // Store test event
- try await storage.storePumpEvents(events)
- // When - Fetch events with our generic fetch function
- let fetchedEvents = try await coreDataStack.fetchEntitiesAsync(
- ofType: PumpEventStored.self,
- onContext: testContext,
- predicate: NSPredicate(
- format: "type == %@ AND timestamp == %@",
- PumpEvent.bolus.rawValue,
- date as NSDate
- ),
- key: "timestamp",
- ascending: false
- )
- guard let fetchedEvents = fetchedEvents as? [PumpEventStored] else { return }
- // Then
- #expect(fetchedEvents.count == 1, "Should have found exactly one event")
- let fetchedEvent = fetchedEvents.first
- #expect(fetchedEvent?.type == PumpEvent.bolus.rawValue, "Should be a bolus event")
- #expect(fetchedEvent?.bolus?.amount as? Decimal == 0.5, "Bolus amount should be 0.5")
- #expect(
- abs(fetchedEvent?.timestamp?.timeIntervalSince(date) ?? 1) < 1,
- "Timestamp should match"
- )
- // When - Delete event
- if let fetchedEvent = fetchedEvent {
- await coreDataStack.deleteObject(identifiedBy: fetchedEvent.objectID)
- }
- // Then - Verify deletion
- let eventsAfterDeletion = try await coreDataStack.fetchEntitiesAsync(
- ofType: PumpEventStored.self,
- onContext: testContext,
- predicate: NSPredicate(
- format: "type == %@ AND timestamp == %@",
- PumpEvent.bolus.rawValue,
- date as NSDate
- ),
- key: "timestamp",
- ascending: false
- )
- guard let eventsAfterDeletion = eventsAfterDeletion as? [PumpEventStored] else { return }
- #expect(eventsAfterDeletion.isEmpty, "Should have no events after deletion")
- }
- @Test("Test store function in PumpHistoryStorage") func testStorePumpEvents() async throws {
- // Given
- let date = Date()
- let tenMinAgo = date.addingTimeInterval(-10.minutes.timeInterval)
- let halfHourInFuture = date.addingTimeInterval(30.minutes.timeInterval)
- // Get initial entries to compare to final entries later
- let initialEntries = try await testContext.perform {
- try testContext.fetch(PumpEventStored.fetchRequest())
- }
- // Create 2 test events, 1 bolus + 1 temp basal event
- let events: [LoopKit.NewPumpEvent] = [
- // SMB
- LoopKit.NewPumpEvent(
- date: tenMinAgo,
- dose: LoopKit.DoseEntry(
- type: .bolus,
- startDate: tenMinAgo,
- value: 0.4,
- unit: .units,
- deliveredUnits: nil,
- description: nil,
- syncIdentifier: nil,
- scheduledBasalRate: nil,
- insulinType: .lyumjev,
- automatic: true,
- manuallyEntered: false,
- isMutable: false
- ),
- raw: Data(),
- title: "Test Bolus",
- type: .bolus
- ),
- // Temp Basal event
- LoopKit.NewPumpEvent(
- date: date,
- dose: LoopKit.DoseEntry(
- type: .tempBasal,
- startDate: date,
- endDate: halfHourInFuture,
- value: 1.2,
- unit: .unitsPerHour,
- deliveredUnits: nil,
- description: nil,
- syncIdentifier: nil,
- scheduledBasalRate: nil,
- insulinType: .lyumjev,
- automatic: true,
- manuallyEntered: false,
- isMutable: true
- ),
- raw: Data(),
- title: "Test Temp Basal",
- type: .tempBasal
- )
- ]
- // When
- // Store in our in-memory PumphistoryStorage
- try await storage.storePumpEvents(events)
- // Then
- // Fetch all events after storing
- let finalEntries = try await testContext.perform {
- try testContext.fetch(PumpEventStored.fetchRequest())
- }
- // Verify there were no initial entries
- #expect(initialEntries.isEmpty, "There should be no initial entries")
- // Verify count increased by 2
- #expect(finalEntries.count == initialEntries.count + 2, "Should have added 2 new events")
- // Verify bolus event
- let bolusEvent = finalEntries.first {
- $0.type == PumpEvent.bolus.rawValue &&
- abs($0.timestamp?.timeIntervalSince(tenMinAgo) ?? 1) < 1
- }
- #expect(bolusEvent != nil, "Should have found bolus event")
- #expect(bolusEvent?.bolus?.amount as? Decimal == 0.4, "Bolus amount should be 0.4")
- #expect(bolusEvent?.isUploadedToNS == false, "Should not be uploaded to NS")
- #expect(bolusEvent?.isUploadedToHealth == false, "Should not be uploaded to Health")
- #expect(bolusEvent?.isUploadedToTidepool == false, "Should not be uploaded to Tidepool")
- #expect(bolusEvent?.bolus?.isSMB == true, "Should be a SMB")
- #expect(bolusEvent?.bolus?.isExternal == false, "Should not be external insulin")
- // Verify temp basal event
- let tempBasalEvent = finalEntries.first {
- $0.type == PumpEvent.tempBasal.rawValue &&
- abs($0.timestamp?.timeIntervalSince(date) ?? 1) < 1
- }
- #expect(tempBasalEvent != nil, "Should have found temp basal event")
- #expect(tempBasalEvent?.tempBasal?.rate as? Decimal == 1.2, "Temp basal rate should be 1.2")
- #expect(tempBasalEvent?.tempBasal?.duration == 30, "Temp basal duration should be 30 minutes")
- #expect(tempBasalEvent?.isUploadedToNS == false, "Should not be uploaded to NS")
- #expect(tempBasalEvent?.isUploadedToHealth == false, "Should not be uploaded to Health")
- #expect(bolusEvent?.isUploadedToTidepool == false, "Should not be uploaded to Tidepool")
- }
- @Test("Test store function for manual boluses") func testStorePumpEventsWithManualBoluses() async throws {
- // Given
- let date = Date()
- // Insert mock entry
- let events: [LoopKit.NewPumpEvent] = [
- LoopKit.NewPumpEvent(
- date: date,
- dose: LoopKit.DoseEntry(
- type: .bolus,
- startDate: date,
- value: 4,
- unit: .units,
- deliveredUnits: nil,
- description: nil,
- syncIdentifier: nil,
- scheduledBasalRate: nil,
- insulinType: .lyumjev,
- automatic: false,
- manuallyEntered: false,
- isMutable: false
- ),
- raw: Data(),
- title: "Test Bolus",
- type: .bolus
- )
- ]
- // Store test event and wait for storage to complete the task
- try await storage.storePumpEvents(events)
- // When - Fetch events with our generic fetch function
- let fetchedEvents = try await coreDataStack.fetchEntitiesAsync(
- ofType: PumpEventStored.self,
- onContext: testContext,
- predicate: NSPredicate(
- format: "type == %@ AND timestamp == %@",
- PumpEvent.bolus.rawValue,
- date as NSDate
- ),
- key: "timestamp",
- ascending: false
- )
- guard let fetchedEvents = fetchedEvents as? [PumpEventStored] else { return }
- // Then
- #expect(fetchedEvents.count == 1, "Should have found exactly one event")
- let fetchedEvent = fetchedEvents.first
- #expect(fetchedEvent?.type == PumpEvent.bolus.rawValue, "Should be a bolus event")
- #expect(fetchedEvent?.bolus?.amount as? Decimal == 4, "Bolus amount should be 4 U")
- #expect(
- abs(fetchedEvent?.timestamp?.timeIntervalSince(date) ?? 1) < 1,
- "Timestamp should match"
- )
- #expect(fetchedEvent?.bolus?.isSMB == false, "Should not be a SMB")
- #expect(fetchedEvent?.bolus?.isExternal == false, "Should not be external Insulin")
- #expect(fetchedEvent?.isUploadedToNS == false, "Should not be uploaded to NS")
- #expect(fetchedEvent?.isUploadedToHealth == false, "Should not be uploaded to Health")
- #expect(fetchedEvent?.isUploadedToTidepool == false, "Should not be uploaded to Tidepool")
- }
- @Test("Test duplicates in PumpHistoryStorage") func testDuplicatePumpEvents() async throws {
- // Given
- let date = Date()
- let twoHoursAgo = date - 2.hours.timeInterval
- let oneMinuteAgo = date - 1.minutes.timeInterval
- // Get initial entries to compare to final entries later
- let initialEntries = try await testContext.perform {
- try testContext.fetch(PumpEventStored.fetchRequest())
- }
- // Create two suspend events and two resume events
- let events: [LoopKit.NewPumpEvent] = [
- LoopKit.NewPumpEvent(
- date: twoHoursAgo,
- dose: nil,
- raw: Data(),
- title: "Test Suspend",
- type: .suspend
- ),
- LoopKit.NewPumpEvent(
- date: twoHoursAgo,
- dose: nil,
- raw: Data(),
- title: "Test Suspend",
- type: .suspend
- ),
- LoopKit.NewPumpEvent(
- date: oneMinuteAgo,
- dose: nil,
- raw: Data(),
- title: "Test Resume",
- type: .resume
- ),
- LoopKit.NewPumpEvent(
- date: oneMinuteAgo,
- dose: nil,
- raw: Data(),
- title: "Test Resume",
- type: .resume
- )
- ]
- // When
- // Store in our in-memory PumphistoryStorage
- try await storage.storePumpEvents(events)
- // Then
- // Fetch all events after storing
- let finalEntriesUnsorted = try await testContext.perform {
- try testContext.fetch(PumpEventStored.fetchRequest())
- }
- let finalEntries = finalEntriesUnsorted.sorted { $0.timestamp! < $1.timestamp! }
- // Verify there were no initial entries
- #expect(initialEntries.isEmpty, "There should be no initial entries")
- // Verify count increased by 2
- #expect(finalEntries.count == initialEntries.count + 2, "Should have added 2 new events")
- #expect(finalEntries.first?.type == PumpEvent.pumpSuspend.rawValue)
- #expect(finalEntries.last?.type == PumpEvent.pumpResume.rawValue)
- }
- }
|