From 0bcdf30a7ef5efed5ac82b792c4e55ee013e6bb1 Mon Sep 17 00:00:00 2001 From: Bart Sopers Date: Fri, 20 Dec 2019 23:07:38 +0100 Subject: [PATCH] Improve basal schedule validation and add profile to basal schedule mapping tests --- .../omnipod/defs/schedule/BasalSchedule.java | 6 +- .../defs/schedule/BasalScheduleEntry.java | 6 +- .../driver/comm/AapsOmnipodManager.java | 27 ++- .../CommandInitializationException.java | 4 + .../driver/comm/AapsOmnipodManagerTest.java | 163 ++++++++++++++++++ 5 files changed, 198 insertions(+), 8 deletions(-) create mode 100644 app/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManagerTest.java diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalSchedule.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalSchedule.java index 61e487e7bd..140c584c0b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalSchedule.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalSchedule.java @@ -10,8 +10,10 @@ public class BasalSchedule { private final List entries; public BasalSchedule(List entries) { - if (entries == null) { - throw new IllegalArgumentException("Entries cannot be null"); + if (entries == null || entries.size() == 0) { + throw new IllegalArgumentException("Entries can not be empty"); + } else if (!entries.get(0).getStartTime().isEqual(Duration.ZERO)) { + throw new IllegalArgumentException("First basal schedule entry should have 0 offset"); } this.entries = entries; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalScheduleEntry.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalScheduleEntry.java index 6591e0c825..c872199471 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalScheduleEntry.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalScheduleEntry.java @@ -9,10 +9,14 @@ public class BasalScheduleEntry { private final Duration startTime; public BasalScheduleEntry(double rate, Duration startTime) { - if (rate < 0D) { + if (startTime.isLongerThan(Duration.standardHours(24).minus(Duration.standardSeconds(1))) || startTime.isShorterThan(Duration.ZERO)) { + throw new IllegalArgumentException("Invalid start time"); + } else if (rate < 0D) { throw new IllegalArgumentException("Rate should be >= 0"); } else if (rate > OmnipodConst.MAX_BASAL_RATE) { throw new IllegalArgumentException("Rate exceeds max basal rate"); + } else if (rate % OmnipodConst.POD_PULSE_SIZE > 0.000001 && rate % OmnipodConst.POD_PULSE_SIZE - OmnipodConst.POD_PULSE_SIZE < -0.000001) { + throw new IllegalArgumentException("Unsupported basal rate precision"); } this.rate = rate; this.startTime = startTime; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java index a728ce15a5..909629fe3e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java @@ -173,7 +173,13 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface } } else if (PodInitActionType.FillCannulaSetBasalProfileWizardStep.equals(podInitActionType)) { try { - Disposable disposable = delegate.insertCannula(mapProfileToBasalSchedule(profile)).subscribe(res -> // + BasalSchedule basalSchedule; + try { + basalSchedule = mapProfileToBasalSchedule(profile); + } catch (Exception ex) { + throw new CommandInitializationException("Basal profile mapping failed", ex); + } + Disposable disposable = delegate.insertCannula(basalSchedule).subscribe(res -> // handleSetupActionResult(podInitActionType, podInitReceiver, res, time)); return new PumpEnactResult().success(true).enacted(true); } catch (Exception ex) { @@ -222,11 +228,17 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface } @Override - public PumpEnactResult setBasalProfile(Profile basalProfile) { + public PumpEnactResult setBasalProfile(Profile profile) { long time = System.currentTimeMillis(); try { - delegate.setBasalSchedule(mapProfileToBasalSchedule(basalProfile), isBasalBeepsEnabled()); - addSuccessToHistory(time, PodHistoryEntryType.SetBasalSchedule, basalProfile); + BasalSchedule basalSchedule; + try { + basalSchedule = mapProfileToBasalSchedule(profile); + } catch (Exception ex) { + throw new CommandInitializationException("Basal profile mapping failed", ex); + } + delegate.setBasalSchedule(basalSchedule, isBasalBeepsEnabled()); + addSuccessToHistory(time, PodHistoryEntryType.SetBasalSchedule, profile); } catch (Exception ex) { if ((ex instanceof OmnipodException) && !((OmnipodException) ex).isCertainFailure()) { addToHistory(time, PodHistoryEntryType.SetBasalSchedule, "Uncertain failure", false); @@ -623,9 +635,14 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface return L.isEnabled(L.PUMP); } - // TODO add tests static BasalSchedule mapProfileToBasalSchedule(Profile profile) { + if (profile == null) { + throw new IllegalArgumentException("Profile can not be null"); + } Profile.ProfileValue[] basalValues = profile.getBasalValues(); + if(basalValues == null) { + throw new IllegalArgumentException("Basal values can not be null"); + } List entries = new ArrayList<>(); for (Profile.ProfileValue basalValue : basalValues) { entries.add(new BasalScheduleEntry(basalValue.value, Duration.standardSeconds(basalValue.timeAsSeconds))); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CommandInitializationException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CommandInitializationException.java index 1d46ef2e82..79e54934ca 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CommandInitializationException.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CommandInitializationException.java @@ -4,4 +4,8 @@ public class CommandInitializationException extends OmnipodException { public CommandInitializationException(String message) { super(message, true); } + + public CommandInitializationException(String message, Throwable cause) { + super(message, cause, true); + } } diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManagerTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManagerTest.java new file mode 100644 index 0000000000..9d57b3ac45 --- /dev/null +++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManagerTest.java @@ -0,0 +1,163 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.driver.comm; + +import org.joda.time.Duration; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.List; + +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalScheduleEntry; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.powermock.api.mockito.PowerMockito.when; + +public class AapsOmnipodManagerTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void validProfile() { + Profile profile = mock(Profile.class); + + Profile.ProfileValue value1 = mock(Profile.ProfileValue.class); + value1.timeAsSeconds = 0; + value1.value = 0.5D; + + Profile.ProfileValue value2 = mock(Profile.ProfileValue.class); + value2.timeAsSeconds = 18000; + value2.value = 1.0D; + + Profile.ProfileValue value3 = mock(Profile.ProfileValue.class); + value3.timeAsSeconds = 50400; + value3.value = 3.05D; + + when(profile.getBasalValues()).thenReturn(new Profile.ProfileValue[]{ + value1, + value2, + value3 + }); + + BasalSchedule basalSchedule = AapsOmnipodManager.mapProfileToBasalSchedule(profile); + + List entries = basalSchedule.getEntries(); + assertEquals(3, entries.size()); + + BasalScheduleEntry entry1 = entries.get(0); + assertEquals(Duration.standardSeconds(0), entry1.getStartTime()); + assertEquals(0.5D, entry1.getRate(), 0.000001); + + BasalScheduleEntry entry2 = entries.get(1); + assertEquals(Duration.standardSeconds(18000), entry2.getStartTime()); + assertEquals(1.0D, entry2.getRate(), 0.000001); + + BasalScheduleEntry entry3 = entries.get(2); + assertEquals(Duration.standardSeconds(50400), entry3.getStartTime()); + assertEquals(3.05D, entry3.getRate(), 0.000001); + } + + @Test + public void invalidProfileNullProfile() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Profile can not be null"); + AapsOmnipodManager.mapProfileToBasalSchedule(null); + } + + @Test + public void invalidProfileNullEntries() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Basal values can not be null"); + AapsOmnipodManager.mapProfileToBasalSchedule(mock(Profile.class)); + } + + @Test + public void invalidProfileZeroEntries() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Entries can not be empty"); + Profile profile = mock(Profile.class); + + when(profile.getBasalValues()).thenReturn(new Profile.ProfileValue[0]); + + AapsOmnipodManager.mapProfileToBasalSchedule(profile); + } + + @Test + public void invalidProfileNonZeroOffset() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("First basal schedule entry should have 0 offset"); + + Profile profile = mock(Profile.class); + + Profile.ProfileValue value = mock(Profile.ProfileValue.class); + value.timeAsSeconds = 500; + value.value = 0.5D; + + when(profile.getBasalValues()).thenReturn(new Profile.ProfileValue[]{ + value, + }); + + AapsOmnipodManager.mapProfileToBasalSchedule(profile); + } + + @Test + public void invalidProfileMoreThan24Hours() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Invalid start time"); + + Profile profile = mock(Profile.class); + + Profile.ProfileValue value1 = mock(Profile.ProfileValue.class); + value1.timeAsSeconds = 0; + value1.value = 0.5D; + + Profile.ProfileValue value2 = mock(Profile.ProfileValue.class); + value2.timeAsSeconds = 86400; + value2.value = 0.5D; + + when(profile.getBasalValues()).thenReturn(new Profile.ProfileValue[]{ + value1, + value2 + }); + + AapsOmnipodManager.mapProfileToBasalSchedule(profile); + } + + @Test + public void invalidProfileNegativeOffset() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Invalid start time"); + + Profile profile = mock(Profile.class); + + Profile.ProfileValue value = mock(Profile.ProfileValue.class); + value.timeAsSeconds = -1; + value.value = 0.5D; + + when(profile.getBasalValues()).thenReturn(new Profile.ProfileValue[]{ + value, + }); + + AapsOmnipodManager.mapProfileToBasalSchedule(profile); + } + + @Test + public void invalidProfileUnsupportedPrecision() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Unsupported basal rate precision"); + + Profile profile = mock(Profile.class); + + Profile.ProfileValue value = mock(Profile.ProfileValue.class); + value.timeAsSeconds = 500; + value.value = 0.04D; + + when(profile.getBasalValues()).thenReturn(new Profile.ProfileValue[]{ + value, + }); + + AapsOmnipodManager.mapProfileToBasalSchedule(profile); + } +} \ No newline at end of file