From 3942211af076355a86d995ec7ec3e86e7b615b14 Mon Sep 17 00:00:00 2001 From: Matt Jones Date: Mon, 28 Apr 2025 21:10:38 -0500 Subject: [PATCH 1/2] chore: separate unit/integration tests, run on PR --- .github/workflows/unittests.yml | 25 ++++++++ pom.xml | 55 +++++++++++++++++- .../switch_bot/SwitchBotDeviceApi.java | 3 +- .../switch_bot/SwitchBotApiUnitTest.java | 58 +++++++++++++++++++ .../SwitchBotApiIntegrationTest.java} | 48 ++++++--------- 5 files changed, 156 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/unittests.yml create mode 100644 src/test/java/com/bigboxer23/switch_bot/SwitchBotApiUnitTest.java rename src/test/java/com/bigboxer23/switch_bot/{SwitchBotApiTest.java => integration/SwitchBotApiIntegrationTest.java} (79%) diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml new file mode 100644 index 0000000..8dc1de7 --- /dev/null +++ b/.github/workflows/unittests.yml @@ -0,0 +1,25 @@ +name: Run Unit Tests + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + test: + name: Unit Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Run Unit Tests + run: mvn clean test diff --git a/pom.xml b/pom.xml index 1bc571a..32b4db1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.bigboxer23 switchbotapi-java - 1.2.1 + 1.2.2 switchbotapi-java https://github.com/bigboxer23/switchbotapi-java @@ -22,6 +22,12 @@ + + org.mockito + mockito-core + 5.17.0 + test + org.junit.jupiter junit-jupiter-api @@ -78,9 +84,56 @@ 17 + + org.apache.maven.plugins + maven-failsafe-plugin + 3.5.3 + + + **/*IT.java + **/*ITCase.java + **/*IntegrationTest.java + + methods + 4 + false + ${!integration} + 1 + + + + integration-tests + + integration-test + + integration-test + + + verify + + verify + + verify + + + maven-surefire-plugin 3.5.3 + + + **/*Test.java + **/*Tests.java + + + **/*IT.java + **/*ITCase.java + **/*IntegrationTest.java + + methods + 4 + false + maven-jar-plugin diff --git a/src/main/java/com/bigboxer23/switch_bot/SwitchBotDeviceApi.java b/src/main/java/com/bigboxer23/switch_bot/SwitchBotDeviceApi.java index 94886ac..92c60c8 100644 --- a/src/main/java/com/bigboxer23/switch_bot/SwitchBotDeviceApi.java +++ b/src/main/java/com/bigboxer23/switch_bot/SwitchBotDeviceApi.java @@ -22,11 +22,10 @@ public class SwitchBotDeviceApi { private Map deviceIdToNames; - private long deviceIdToNamesCacheTime = -1; + protected long deviceIdToNamesCacheTime = -1; protected SwitchBotDeviceApi(SwitchBotApi provider) { this.provider = provider; - refreshDeviceNameMap(); } public String getDeviceNameFromId(String deviceId) { diff --git a/src/test/java/com/bigboxer23/switch_bot/SwitchBotApiUnitTest.java b/src/test/java/com/bigboxer23/switch_bot/SwitchBotApiUnitTest.java new file mode 100644 index 0000000..8bbab63 --- /dev/null +++ b/src/test/java/com/bigboxer23/switch_bot/SwitchBotApiUnitTest.java @@ -0,0 +1,58 @@ +package com.bigboxer23.switch_bot; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.bigboxer23.switch_bot.data.Device; +import com.bigboxer23.switch_bot.data.DeviceCommand; +import com.bigboxer23.utils.time.ITimeConstants; +import java.io.IOException; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class SwitchBotApiUnitTest { + @Test + public void testSingletonBehavior() { + SwitchBotApi mockApi1 = SwitchBotApi.getInstance("token", "secret"); + SwitchBotApi mockApi2 = SwitchBotApi.getInstance("token", "secret"); + assertSame(mockApi1, mockApi2, "Should return the same singleton instance"); + } + + @Test + public void testDeviceCommandSerialization() throws IOException { + SwitchBotApi api = SwitchBotApi.getInstance("token", "secret"); + + DeviceCommand originalCommand = new DeviceCommand("turnOn", "default"); + + String json = api.getMoshi().adapter(DeviceCommand.class).toJson(originalCommand); + assertNotNull(json); + assertTrue(json.contains("turnOn")); + + DeviceCommand deserializedCommand = + api.getMoshi().adapter(DeviceCommand.class).fromJson(json); + assertNotNull(deserializedCommand); + assertEquals(originalCommand.getCommand(), deserializedCommand.getCommand()); + assertEquals(originalCommand.getParameter(), deserializedCommand.getParameter()); + assertEquals("command", deserializedCommand.getCommandType()); + } + + @Test + public void testDeviceNameCacheRefreshBehavior() throws IOException { + SwitchBotApi mockApi = mock(SwitchBotApi.class); + SwitchBotDeviceApi deviceApi = spy(new SwitchBotDeviceApi(mockApi)); + + deviceApi.deviceIdToNamesCacheTime = System.currentTimeMillis() - (ITimeConstants.HOUR * 2); + + Device dummyDevice = new Device(); + dummyDevice.setDeviceId("12345"); + dummyDevice.setDeviceName("Test Device"); + doReturn(List.of(dummyDevice)).when(deviceApi).getDevices(); + + String deviceName = deviceApi.getDeviceNameFromId("12345"); + assertEquals("Test Device", deviceName); + + reset(deviceApi); + deviceApi.getDeviceNameFromId("12345"); + verify(deviceApi, times(0)).getDevices(); + } +} diff --git a/src/test/java/com/bigboxer23/switch_bot/SwitchBotApiTest.java b/src/test/java/com/bigboxer23/switch_bot/integration/SwitchBotApiIntegrationTest.java similarity index 79% rename from src/test/java/com/bigboxer23/switch_bot/SwitchBotApiTest.java rename to src/test/java/com/bigboxer23/switch_bot/integration/SwitchBotApiIntegrationTest.java index 1c570ae..439faa9 100644 --- a/src/test/java/com/bigboxer23/switch_bot/SwitchBotApiTest.java +++ b/src/test/java/com/bigboxer23/switch_bot/integration/SwitchBotApiIntegrationTest.java @@ -1,26 +1,28 @@ -package com.bigboxer23.switch_bot; +package com.bigboxer23.switch_bot.integration; import static org.junit.jupiter.api.Assertions.*; +import com.bigboxer23.switch_bot.IDeviceCommands; +import com.bigboxer23.switch_bot.IDeviceTypes; +import com.bigboxer23.switch_bot.SwitchBotApi; import com.bigboxer23.switch_bot.data.Device; import com.bigboxer23.utils.command.Command; import com.bigboxer23.utils.properties.PropertyUtils; import java.io.IOException; import java.util.List; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -/** Need to define environment variables for SwitchBotToken/SwitchBotSecret to run tests */ -public class SwitchBotApiTest { +@Tag("integration") +public class SwitchBotApiIntegrationTest { private static final String token = PropertyUtils.getProperty("switchbot_token"); - private static final String secret = PropertyUtils.getProperty("switchbot_secret"); - private static final SwitchBotApi instance = SwitchBotApi.getInstance(token, secret); @Test public void testGetDevices() throws IOException { List devices = instance.getDeviceApi().getDevices(); - assertFalse(devices.isEmpty()); + assertFalse(devices.isEmpty(), "Device list should not be empty"); assertNotNull(devices.get(0).getDeviceId()); } @@ -37,9 +39,8 @@ public void getDeviceNameFromId() throws IOException { public void testDeviceStatus() throws IOException { try { instance.getDeviceApi().getDeviceStatus("123"); - fail(); - } catch (IOException e) { - + fail("Expected IOException for invalid device ID"); + } catch (IOException ignored) { } for (Device device : instance.getDeviceApi().getDevices()) { assertNotNull(device.getDeviceId()); @@ -55,17 +56,16 @@ public void testDeviceStatus() throws IOException { } case IDeviceTypes.CURTAIN -> { assertTrue(status.getSlidePosition() >= 0); - assertTrue(status.isMoving()); assertTrue(status.getBattery() >= 0); } case IDeviceTypes.PLUG_MINI -> { assertTrue("on".equals(status.getPower()) || "off".equals(status.getPower())); - assertTrue(status.getVoltage() > 0); + assertTrue(status.getVoltage() > -1); assertTrue(status.getWatts() > -1); assertTrue(status.getElectricityOfDay() >= 0); assertTrue(status.getElectricCurrent() > -1); } - case IDeviceTypes.WATER_DETECTOR -> assertTrue(status.isWet()); + case IDeviceTypes.WATER_DETECTOR -> assertTrue(status.isWet() || status.isDry()); case IDeviceTypes.METER_PRO_CO2 -> assertTrue(status.getCo2() > 0); } } @@ -80,25 +80,14 @@ public void testCurtainDeviceCommands() throws IOException, InterruptedException .orElse(null); assertNotNull(curtain); assertNotNull(curtain.getDeviceId()); - System.out.println("slide position " - + instance.getDeviceApi().getDeviceStatus(curtain.getDeviceId()).getSlidePosition()); instance.getDeviceApi().sendDeviceControlCommands(curtain.getDeviceId(), IDeviceCommands.CURTAIN_CLOSE); - pollForStatus(() -> { - int slidePosition = instance.getDeviceApi() - .getDeviceStatus(curtain.getDeviceId()) - .getSlidePosition(); - System.out.println("slide position " + slidePosition); - return slidePosition >= 90; - }); + pollForStatus(() -> + instance.getDeviceApi().getDeviceStatus(curtain.getDeviceId()).getSlidePosition() >= 90); + instance.getDeviceApi().sendDeviceControlCommands(curtain.getDeviceId(), IDeviceCommands.CURTAIN_OPEN); - pollForStatus(() -> { - int slidePosition = instance.getDeviceApi() - .getDeviceStatus(curtain.getDeviceId()) - .getSlidePosition(); - System.out.println("slide position " + slidePosition); - return slidePosition == 0; - }); + pollForStatus(() -> + instance.getDeviceApi().getDeviceStatus(curtain.getDeviceId()).getSlidePosition() == 0); } @Test @@ -120,8 +109,7 @@ public void testPlugDeviceCommands() throws IOException, InterruptedException { private void pollForStatus(Command command) throws IOException, InterruptedException { boolean result = command.execute(); - for (int ai = 0; ai < 10 && !result; ai++) { - System.out.println("sleeping " + ai); + for (int i = 0; i < 10 && !result; i++) { Thread.sleep(2000); result = command.execute(); } From f7c3bddd2af1370e6c46694a21316113edc49b6e Mon Sep 17 00:00:00 2001 From: Matt Jones Date: Mon, 28 Apr 2025 21:14:00 -0500 Subject: [PATCH 2/2] chore: GH token --- .github/workflows/unittests.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 8dc1de7..ea19b1a 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -20,6 +20,19 @@ jobs: with: distribution: 'temurin' java-version: '17' - + - name: Configure Maven for GitHub Packages + run: | + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml < + + + github + ${{ github.actor }} + ${{ secrets.GITHUB_TOKEN }} + + + + EOF - name: Run Unit Tests run: mvn clean test