import Foundation import Testing @testable import Trio struct ProfileGeneratorTests { // Base test inputs that match the JavaScript test setup private func createBaseInputs() -> ( PumpSettings, BGTargets, [BasalProfileEntry], InsulinSensitivities, Preferences, CarbRatios, [TempTarget], String, TrioSettings ) { let pumpSettings = PumpSettings( insulinActionCurve: 10, maxBolus: 10, maxBasal: 2 ) let bgTargets = BGTargets( units: .mgdL, userPreferredUnits: .mgdL, targets: [ BGTargetEntry(low: 100, high: 120, start: "00:00", offset: 0) ] ) let basalProfile = [ BasalProfileEntry(start: "00:00", minutes: 0, rate: 1.0) ] let isf = InsulinSensitivities( units: .mgdL, userPreferredUnits: .mgdL, sensitivities: [ InsulinSensitivityEntry(sensitivity: 100, offset: 0, start: "00:00") ] ) let preferences = Preferences() let carbRatios = CarbRatios( units: .grams, schedule: [ CarbRatioEntry(start: "00:00", offset: 0, ratio: 20) ] ) let tempTargets: [TempTarget] = [] let model = "523" let trioSettings = TrioSettings() return (pumpSettings, bgTargets, basalProfile, isf, preferences, carbRatios, tempTargets, model, trioSettings) } @Test("Basic profile generation should create profile with correct values") func testBasicProfileGeneration() throws { let inputs = createBaseInputs() let profile = try ProfileGenerator.generate( pumpSettings: inputs.0, bgTargets: inputs.1, basalProfile: inputs.2, isf: inputs.3, preferences: inputs.4, carbRatios: inputs.5, tempTargets: inputs.6, model: inputs.7, clock: Date() ) #expect(profile.maxIob == 0) #expect(profile.dia == 10) #expect(profile.sens == 100) #expect(profile.currentBasal == 1) #expect(profile.maxBg == 100) #expect(profile.minBg == 100) #expect(profile.carbRatio == 20) } @Test("Profile with active temp target should use temp target values") func testProfileWithTempTarget() throws { var inputs = createBaseInputs() // Create temp target 5 minutes ago that lasts 20 minutes let currentTime = Date() let creationDate = currentTime.addingTimeInterval(-5 * 60) let tempTarget = TempTarget( name: "Eating Soon", createdAt: creationDate, targetTop: 80, targetBottom: 80, duration: 20, enteredBy: "Test", reason: "Eating Soon", isPreset: nil, enabled: nil, halfBasalTarget: nil ) inputs.6 = [tempTarget] let profile = try ProfileGenerator.generate( pumpSettings: inputs.0, bgTargets: inputs.1, basalProfile: inputs.2, isf: inputs.3, preferences: inputs.4, carbRatios: inputs.5, tempTargets: inputs.6, model: inputs.7, clock: currentTime ) #expect(profile.maxIob == 0) #expect(profile.dia == 10) #expect(profile.sens == 100) #expect(profile.currentBasal == 1) #expect(profile.maxBg == 80) #expect(profile.minBg == 80) #expect(profile.carbRatio == 20) #expect(profile.temptargetSet == true) } @Test("Profile with expired temp target should use default values") func testProfileWithExpiredTempTarget() throws { var inputs = createBaseInputs() // Create temp target 90 minutes ago let currentTime = Date() let creationDate = currentTime.addingTimeInterval(-90 * 60) let tempTarget = TempTarget( name: "Eating Soon", createdAt: creationDate, targetTop: 80, targetBottom: 80, duration: 20, enteredBy: "Test", reason: "Eating Soon", isPreset: nil, enabled: nil, halfBasalTarget: nil ) inputs.6 = [tempTarget] let profile = try ProfileGenerator.generate( pumpSettings: inputs.0, bgTargets: inputs.1, basalProfile: inputs.2, isf: inputs.3, preferences: inputs.4, carbRatios: inputs.5, tempTargets: inputs.6, model: inputs.7, clock: currentTime ) #expect(profile.maxIob == 0) #expect(profile.dia == 10) #expect(profile.sens == 100) #expect(profile.currentBasal == 1) #expect(profile.maxBg == 100) #expect(profile.minBg == 100) #expect(profile.carbRatio == 20) } @Test("Profile with zero duration temp target should use default values") func testProfileWithZeroDurationTempTarget() throws { var inputs = createBaseInputs() // Create temp target 5 minutes ago with 0 duration let currentTime = Date() let creationDate = currentTime.addingTimeInterval(-5 * 60) let tempTarget = TempTarget( name: "Eating Soon", createdAt: creationDate, targetTop: 80, targetBottom: 80, duration: 0, enteredBy: "Test", reason: "Eating Soon", isPreset: nil, enabled: nil, halfBasalTarget: nil ) inputs.6 = [tempTarget] let profile = try ProfileGenerator.generate( pumpSettings: inputs.0, bgTargets: inputs.1, basalProfile: inputs.2, isf: inputs.3, preferences: inputs.4, carbRatios: inputs.5, tempTargets: inputs.6, model: inputs.7, clock: currentTime ) #expect(profile.maxIob == 0) #expect(profile.dia == 10) #expect(profile.sens == 100) #expect(profile.currentBasal == 1) #expect(profile.maxBg == 100) #expect(profile.minBg == 100) #expect(profile.carbRatio == 20) } @Test("Profile generation with invalid DIA should throw error") func testInvalidDIA() throws { var inputs = createBaseInputs() inputs.0 = PumpSettings( insulinActionCurve: 1, maxBolus: 10, maxBasal: 2 ) #expect(throws: ProfileError.invalidDIA(value: 1)) { _ = try ProfileGenerator.generate( pumpSettings: inputs.0, bgTargets: inputs.1, basalProfile: inputs.2, isf: inputs.3, preferences: inputs.4, carbRatios: inputs.5, tempTargets: inputs.6, model: inputs.7, clock: Date() ) } } @Test("Profile generation with zero basal rate should throw error") func testCurrentBasalZero() throws { var inputs = createBaseInputs() inputs.2 = [ BasalProfileEntry(start: "00:00", minutes: 0, rate: 0.0) ] // the reason it throws this error is due to some complex logic // in Javascript around the handling of nil and 0 basal rate entries #expect(throws: ProfileError.invalidMaxDailyBasal(value: 0)) { _ = try ProfileGenerator.generate( pumpSettings: inputs.0, bgTargets: inputs.1, basalProfile: inputs.2, isf: inputs.3, preferences: inputs.4, carbRatios: inputs.5, tempTargets: inputs.6, model: inputs.7, clock: Date() ) } } @Test("Profile should store model string correctly") func testModelString() throws { var inputs = createBaseInputs() inputs.7 = "\"554\"\n" let profile = try ProfileGenerator.generate( pumpSettings: inputs.0, bgTargets: inputs.1, basalProfile: inputs.2, isf: inputs.3, preferences: inputs.4, carbRatios: inputs.5, tempTargets: inputs.6, model: inputs.7, clock: Date() ) #expect(profile.model == "554") } // @Test("Profile should use temptargetSet key in output json") func testTempTargetSetKey() async throws { // var inputs = createBaseInputs() // inputs.7 = "\"554\"\n" // let now = Date() // let tempTargets = [ // TempTarget( // name: nil, // createdAt: now - 1.hoursToSeconds, // targetTop: 100, // targetBottom: 80, // duration: 120, // enteredBy: nil, // reason: nil, // isPreset: nil, // enabled: nil, // halfBasalTarget: nil // ) // ] // // let openAps = OpenAPS(storage: BaseFileStorage(), tddStorage: MockTDDStorage()) // let jsResult = await openAps.makeProfileJavascript( // preferences: inputs.4, // pumpSettings: inputs.0, // bgTargets: inputs.1, // basalProfile: inputs.2, // isf: inputs.3, // carbRatio: inputs.5, // tempTargets: tempTargets, // model: inputs.7, // autotune: RawJSON.null, // trioSettings: inputs.8 // ) // // let (swiftResult, makeProfileInputs) = OpenAPSSwift.makeProfile( // preferences: inputs.4, // pumpSettings: inputs.0, // bgTargets: inputs.1, // basalProfile: inputs.2, // isf: inputs.3, // carbRatio: inputs.5, // tempTargets: tempTargets, // model: inputs.7, // trioSettings: inputs.8, // clock: now // ) // // let comparison = JSONCompare.createComparison( // function: .makeProfile, // swift: swiftResult, // swiftDuration: 1.0, // javascript: jsResult, // javascriptDuration: 1.0, // iobInputs: nil, // mealInputs: nil, // autosensInputs: nil, // determineBasalInputs: nil, // makeProfileInputs: makeProfileInputs // ) // // if comparison.resultType == .valueDifference { // print(comparison.differences!.prettyPrintedJSON!) // } // // #expect(comparison.resultType == .matching) // } }