From 2538378c0cbd68465ca96df5e9cf0c665f17721b Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sat, 2 Sep 2023 14:09:33 +0200 Subject: [PATCH] BolusExtensionKtTest --- plugins/insulin/build.gradle | 2 + .../nightscout/androidaps/HardLimitsMock.kt | 83 +++++++++++++++++++ .../androidaps/TestBaseWithProfile.kt | 61 ++++++++++++++ .../nightscout/androidaps/TestPumpPlugin.kt | 72 ++++++++++++++++ .../core/extensions/BolusExtensionKtTest.kt | 44 ++++++++++ 5 files changed, 262 insertions(+) create mode 100644 plugins/insulin/src/test/java/info/nightscout/androidaps/HardLimitsMock.kt create mode 100644 plugins/insulin/src/test/java/info/nightscout/androidaps/TestBaseWithProfile.kt create mode 100644 plugins/insulin/src/test/java/info/nightscout/androidaps/TestPumpPlugin.kt create mode 100644 plugins/insulin/src/test/java/info/nightscout/core/extensions/BolusExtensionKtTest.kt diff --git a/plugins/insulin/build.gradle b/plugins/insulin/build.gradle index 79591ec2dc..0c6f411c5e 100644 --- a/plugins/insulin/build.gradle +++ b/plugins/insulin/build.gradle @@ -24,4 +24,6 @@ dependencies { implementation project(':core:utils') implementation project(':core:validators') implementation project(':database:entities') + + testImplementation project(':core:main') } \ No newline at end of file diff --git a/plugins/insulin/src/test/java/info/nightscout/androidaps/HardLimitsMock.kt b/plugins/insulin/src/test/java/info/nightscout/androidaps/HardLimitsMock.kt new file mode 100644 index 0000000000..288eaf084a --- /dev/null +++ b/plugins/insulin/src/test/java/info/nightscout/androidaps/HardLimitsMock.kt @@ -0,0 +1,83 @@ +package info.nightscout.androidaps + +import info.nightscout.interfaces.utils.HardLimits +import info.nightscout.shared.interfaces.ResourceHelper +import info.nightscout.shared.sharedPreferences.SP +import javax.inject.Inject +import kotlin.math.max +import kotlin.math.min + +class HardLimitsMock @Inject constructor( + private val sp: SP, + private val rh: ResourceHelper +) : HardLimits { + + companion object { + + private const val CHILD = 0 + private const val TEENAGE = 1 + private const val ADULT = 2 + private const val RESISTANT_ADULT = 3 + private const val PREGNANT = 4 + private val MAX_BOLUS = doubleArrayOf(5.0, 10.0, 17.0, 25.0, 60.0) + + // Very Hard Limits Ranges + // First value is the Lowest and second value is the Highest a Limit can define + val VERY_HARD_LIMIT_MIN_BG = doubleArrayOf(80.0, 180.0) + val VERY_HARD_LIMIT_MAX_BG = doubleArrayOf(90.0, 200.0) + val VERY_HARD_LIMIT_TARGET_BG = doubleArrayOf(80.0, 200.0) + + // Very Hard Limits Ranges for Temp Targets + val VERY_HARD_LIMIT_TEMP_MIN_BG = intArrayOf(72, 180) + val VERY_HARD_LIMIT_TEMP_MAX_BG = intArrayOf(72, 270) + val VERY_HARD_LIMIT_TEMP_TARGET_BG = intArrayOf(72, 200) + val MIN_DIA = doubleArrayOf(5.0, 5.0, 5.0, 5.0, 5.0) + val MAX_DIA = doubleArrayOf(9.0, 9.0, 9.0, 9.0, 10.0) + val MIN_IC = doubleArrayOf(2.0, 2.0, 2.0, 2.0, 0.3) + val MAX_IC = doubleArrayOf(100.0, 100.0, 100.0, 100.0, 100.0) + const val MIN_ISF = 2.0 // mgdl + const val MAX_ISF = 1000.0 // mgdl + val MAX_IOB_AMA = doubleArrayOf(3.0, 5.0, 7.0, 12.0, 25.0) + val MAX_IOB_SMB = doubleArrayOf(7.0, 13.0, 22.0, 30.0, 70.0) + val MAX_BASAL = doubleArrayOf(2.0, 5.0, 10.0, 12.0, 25.0) + + //LGS Hard limits + //No IOB at all + const val MAX_IOB_LGS = 0.0 + + } + + private fun loadAge(): Int = when (sp.getString(info.nightscout.core.utils.R.string.key_age, "")) { + rh.gs(info.nightscout.core.utils.R.string.key_child) -> CHILD + rh.gs(info.nightscout.core.utils.R.string.key_teenage) -> TEENAGE + rh.gs(info.nightscout.core.utils.R.string.key_adult) -> ADULT + rh.gs(info.nightscout.core.utils.R.string.key_resistantadult) -> RESISTANT_ADULT + rh.gs(info.nightscout.core.utils.R.string.key_pregnant) -> PREGNANT + else -> ADULT + } + + override fun maxBolus(): Double = MAX_BOLUS[loadAge()] + override fun maxIobAMA(): Double = MAX_IOB_AMA[loadAge()] + override fun maxIobSMB(): Double = MAX_IOB_SMB[loadAge()] + override fun maxBasal(): Double = MAX_BASAL[loadAge()] + override fun minDia(): Double = MIN_DIA[loadAge()] + override fun maxDia(): Double = MAX_DIA[loadAge()] + override fun minIC(): Double = MIN_IC[loadAge()] + override fun maxIC(): Double = MAX_IC[loadAge()] + + // safety checks + override fun checkHardLimits(value: Double, valueName: Int, lowLimit: Double, highLimit: Double): Boolean = + value == verifyHardLimits(value, valueName, lowLimit, highLimit) + + override fun isInRange(value: Double, lowLimit: Double, highLimit: Double): Boolean = + value in lowLimit..highLimit + + override fun verifyHardLimits(value: Double, valueName: Int, lowLimit: Double, highLimit: Double): Double { + var newValue = value + if (newValue < lowLimit || newValue > highLimit) { + newValue = max(newValue, lowLimit) + newValue = min(newValue, highLimit) + } + return newValue + } +} \ No newline at end of file diff --git a/plugins/insulin/src/test/java/info/nightscout/androidaps/TestBaseWithProfile.kt b/plugins/insulin/src/test/java/info/nightscout/androidaps/TestBaseWithProfile.kt new file mode 100644 index 0000000000..c3960ec89d --- /dev/null +++ b/plugins/insulin/src/test/java/info/nightscout/androidaps/TestBaseWithProfile.kt @@ -0,0 +1,61 @@ +package info.nightscout.androidaps + +import android.content.Context +import dagger.android.AndroidInjector +import dagger.android.HasAndroidInjector +import info.nightscout.core.extensions.pureProfileFromJson +import info.nightscout.core.profile.ProfileSealed +import info.nightscout.interfaces.Config +import info.nightscout.interfaces.plugin.ActivePlugin +import info.nightscout.interfaces.profile.DefaultValueHelper +import info.nightscout.interfaces.profile.Profile +import info.nightscout.interfaces.profile.ProfileFunction +import info.nightscout.interfaces.utils.HardLimits +import info.nightscout.rx.bus.RxBus +import info.nightscout.shared.interfaces.ResourceHelper +import info.nightscout.shared.sharedPreferences.SP +import info.nightscout.shared.utils.DateUtil +import org.json.JSONObject +import org.junit.jupiter.api.BeforeEach +import org.mockito.Mock +import org.mockito.Mockito.`when` + +@Suppress("SpellCheckingInspection") +open class TestBaseWithProfile : TestBase() { + + @Mock lateinit var activePluginProvider: ActivePlugin + @Mock lateinit var rh: ResourceHelper + @Mock lateinit var profileFunction: ProfileFunction + @Mock lateinit var defaultValueHelper: DefaultValueHelper + @Mock lateinit var dateUtil: DateUtil + @Mock lateinit var config: Config + @Mock lateinit var sp: SP + @Mock lateinit var context: Context + + lateinit var hardLimits: HardLimits + lateinit var testPumpPlugin: TestPumpPlugin + + val rxBus = RxBus(aapsSchedulers, aapsLogger) + + val profileInjector = HasAndroidInjector { AndroidInjector { } } + + private lateinit var invalidProfileJSON: String + private lateinit var validProfileJSON: String + lateinit var validProfile: Profile + lateinit var invalidProfile: Profile + @Suppress("PropertyName") val TESTPROFILENAME = "someProfile" + + @BeforeEach + fun prepareMock() { + invalidProfileJSON = "{\"dia\":\"1\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"3\"}," + + "{\"time\":\"2:00\",\"value\":\"3.4\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4.5\"}]," + + "\"target_high\":[{\"time\":\"00:00\",\"value\":\"7\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}" + validProfileJSON = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"3\"}," + + "{\"time\":\"2:00\",\"value\":\"3.4\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4.5\"}]," + + "\"target_high\":[{\"time\":\"00:00\",\"value\":\"7\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}" + validProfile = ProfileSealed.Pure(pureProfileFromJson(JSONObject(validProfileJSON), dateUtil)!!) + testPumpPlugin = TestPumpPlugin(profileInjector) + `when`(activePluginProvider.activePump).thenReturn(testPumpPlugin) + hardLimits = HardLimitsMock(sp, rh) + } +} diff --git a/plugins/insulin/src/test/java/info/nightscout/androidaps/TestPumpPlugin.kt b/plugins/insulin/src/test/java/info/nightscout/androidaps/TestPumpPlugin.kt new file mode 100644 index 0000000000..5ba01a09b6 --- /dev/null +++ b/plugins/insulin/src/test/java/info/nightscout/androidaps/TestPumpPlugin.kt @@ -0,0 +1,72 @@ +package info.nightscout.androidaps + +import dagger.android.HasAndroidInjector +import info.nightscout.interfaces.profile.Profile +import info.nightscout.interfaces.pump.DetailedBolusInfo +import info.nightscout.interfaces.pump.Pump +import info.nightscout.interfaces.pump.PumpEnactResult +import info.nightscout.interfaces.pump.PumpSync +import info.nightscout.interfaces.pump.defs.ManufacturerType +import info.nightscout.interfaces.pump.defs.PumpDescription +import info.nightscout.interfaces.pump.defs.PumpType +import info.nightscout.interfaces.utils.TimeChangeType +import org.json.JSONObject + +@Suppress("MemberVisibilityCanBePrivate") +class TestPumpPlugin(val injector: HasAndroidInjector) : Pump { + + var connected = false + var isProfileSet = true + + override fun isConnected() = connected + override fun isConnecting() = false + override fun isHandshakeInProgress() = false + val lastData = 0L + + val baseBasal = 0.0 + override var pumpDescription = PumpDescription() + + override fun isInitialized(): Boolean = true + override fun isSuspended(): Boolean = false + override fun isBusy(): Boolean = false + override fun connect(reason: String) { + connected = true + } + + override fun disconnect(reason: String) { + connected = false + } + + override fun stopConnecting() { + connected = false + } + + override fun waitForDisconnectionInSeconds(): Int = 0 + override fun getPumpStatus(reason: String) {} + override fun setNewBasalProfile(profile: Profile): PumpEnactResult = PumpEnactResult(injector) + override fun isThisProfileSet(profile: Profile): Boolean = isProfileSet + override fun lastDataTime(): Long = lastData + override val baseBasalRate: Double = baseBasal + override val reservoirLevel: Double = 0.0 + override val batteryLevel: Int = 0 + override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun stopBolusDelivering() {} + override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult = + PumpEnactResult(injector).success(true) + + override fun setTempBasalPercent(percent: Int, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult = + PumpEnactResult(injector).success(true) + + override fun setExtendedBolus(insulin: Double, durationInMinutes: Int): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun cancelExtendedBolus(): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun getJSONStatus(profile: Profile, profileName: String, version: String): JSONObject = JSONObject() + override fun manufacturer(): ManufacturerType = ManufacturerType.AAPS + override fun model(): PumpType = PumpType.GENERIC_AAPS + override fun serialNumber(): String = "1" + override fun shortStatus(veryShort: Boolean): String = "" + override val isFakingTempsByExtendedBoluses: Boolean = false + override fun loadTDDs(): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun canHandleDST(): Boolean = true + override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType) {} +} \ No newline at end of file diff --git a/plugins/insulin/src/test/java/info/nightscout/core/extensions/BolusExtensionKtTest.kt b/plugins/insulin/src/test/java/info/nightscout/core/extensions/BolusExtensionKtTest.kt new file mode 100644 index 0000000000..dd0935a35b --- /dev/null +++ b/plugins/insulin/src/test/java/info/nightscout/core/extensions/BolusExtensionKtTest.kt @@ -0,0 +1,44 @@ +package info.nightscout.core.extensions + +import info.nightscout.androidaps.TestBaseWithProfile +import info.nightscout.database.entities.Bolus +import info.nightscout.insulin.InsulinLyumjevPlugin +import info.nightscout.interfaces.insulin.Insulin +import info.nightscout.interfaces.plugin.ActivePlugin +import info.nightscout.interfaces.profile.ProfileFunction +import info.nightscout.interfaces.ui.UiInteraction +import info.nightscout.shared.utils.T +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mock +import org.mockito.Mockito + +class BolusExtensionKtTest : TestBaseWithProfile() { + + @Mock lateinit var activePlugin: ActivePlugin + @Mock lateinit var profileFunctions: ProfileFunction + @Mock lateinit var uiInteraction: UiInteraction + + private lateinit var insulin: Insulin + + private val now = 1000000L + private val dia = 7.0 + + @BeforeEach + fun setup() { + insulin = InsulinLyumjevPlugin(profileInjector, rh, profileFunctions, rxBus, aapsLogger, config, hardLimits, uiInteraction) + Mockito.`when`(activePlugin.activeInsulin).thenReturn(insulin) + } + + @Test + fun iobCalc() { + val bolus = Bolus(timestamp = now - 1, amount = 1.0, type = Bolus.Type.NORMAL) + // there should be almost full IOB after now + Assertions.assertEquals(1.0, bolus.iobCalc(activePlugin, now, dia).iobContrib, 0.01) + // there should be less that 5% after DIA -1 + Assertions.assertTrue(0.05 > bolus.iobCalc(activePlugin, now + T.hours(dia.toLong() - 1).msecs(), dia).iobContrib) + // there should be zero after DIA + Assertions.assertEquals(0.0, bolus.iobCalc(activePlugin, now + T.hours(dia.toLong() + 1).msecs(), dia).iobContrib) + } +} \ No newline at end of file