From bd0cb116b5039b09792bf414c8fe37d7333f0f25 Mon Sep 17 00:00:00 2001 From: Brian Bazurto <124537281+ba-00001@users.noreply.github.com> Date: Fri, 26 Jun 2026 22:13:12 -0500 Subject: [PATCH 1/3] Verify device is opened and claimed before health check (#10) Health endpoints previously failed when called before a device was opened and claimed. getHealth() now checks device state first: if the device is not both opened and claimed it immediately returns NOT_READY and skips the health check; only an opened+claimed device proceeds to the health check. - DynamicDevice: add isOpened()/isClaimed(); isConnected() composes them - All device classes expose isOpened()/isClaimed() - All device managers gate getHealth() on opened+claimed - Add DynamicDeviceTest and per-manager gate tests --- .../devicemanager/common/DynamicDevice.java | 41 +++++-- .../cashdrawer/CashDrawerDevice.java | 14 +++ .../cashdrawer/CashDrawerManager.java | 6 +- .../components/check/MicrDevice.java | 14 +++ .../components/check/MicrManager.java | 6 +- .../linedisplay/LineDisplayDevice.java | 14 +++ .../linedisplay/LineDisplayManager.java | 6 +- .../components/printer/PrinterDevice.java | 14 +++ .../components/printer/PrinterManager.java | 6 +- .../components/scale/ScaleDevice.java | 16 +++ .../components/scale/ScaleManager.java | 6 +- .../components/scanner/ScannerDevice.java | 16 +++ .../components/scanner/ScannerManager.java | 24 ++-- .../common/DynamicDeviceTest.java | 104 ++++++++++++++++++ .../cashdrawer/CashDrawerManagerTest.java | 33 ++++++ .../components/check/MicrManagerTest.java | 33 ++++++ .../linedisplay/LineDisplayManagerTest.java | 33 ++++++ .../printer/PrinterManagerTest.java | 35 ++++++ .../components/scale/ScaleManagerTest.java | 33 ++++++ .../scanner/ScannerManagerTest.java | 41 +++++++ 20 files changed, 469 insertions(+), 26 deletions(-) create mode 100644 src/test/java/com/target/devicemanager/common/DynamicDeviceTest.java diff --git a/src/main/java/com/target/devicemanager/common/DynamicDevice.java b/src/main/java/com/target/devicemanager/common/DynamicDevice.java index 9a8db48..ebfd7f4 100644 --- a/src/main/java/com/target/devicemanager/common/DynamicDevice.java +++ b/src/main/java/com/target/devicemanager/common/DynamicDevice.java @@ -74,24 +74,43 @@ public void disconnect() { } } - public boolean isConnected() { + /** + * Whether the underlying device has been opened (its JavaPOS state is no + * longer CLOSED). A claimed device is always opened, but an opened device + * is not necessarily claimed. + */ + public boolean isOpened() { synchronized (device) { int deviceState = device.getState(); - if (deviceState != JposConst.JPOS_S_IDLE && deviceState != JposConst.JPOS_S_BUSY) { - return false; - } + return deviceState == JposConst.JPOS_S_IDLE || deviceState == JposConst.JPOS_S_BUSY; + } + } + + /** + * Whether this process has claimed (taken exclusive ownership of) the + * device. Returns false if the device is not opened or the claim cannot be + * read. + */ + public boolean isClaimed() { + synchronized (device) { try { - if (!device.getClaimed()) { - return false; - } - int powerState = devicePower.getPowerState(device); - if (powerState != JposConst.JPOS_PS_ONLINE && powerState != JposConst.JPOS_PS_UNKNOWN) { - return false; - } + return device.getClaimed(); } catch (JposException jposException) { return false; } } + } + + public boolean isConnected() { + synchronized (device) { + if (!isOpened() || !isClaimed()) { + return false; + } + int powerState = devicePower.getPowerState(device); + if (powerState != JposConst.JPOS_PS_ONLINE && powerState != JposConst.JPOS_PS_UNKNOWN) { + return false; + } + } return true; } diff --git a/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerDevice.java b/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerDevice.java index fe9cee2..6868b35 100644 --- a/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerDevice.java +++ b/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerDevice.java @@ -230,6 +230,20 @@ public String getDeviceName() { */ public boolean isConnected() { return deviceConnected; } + /** + * Shows if the device is opened. + */ + public boolean isOpened() { + return dynamicCashDrawer.isOpened(); + } + + /** + * Shows if the device is claimed. + */ + public boolean isClaimed() { + return dynamicCashDrawer.isClaimed(); + } + /** * Attaches an event listener and adding it to a new instance. */ diff --git a/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerManager.java b/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerManager.java index 80cb923..d2e8724 100644 --- a/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerManager.java +++ b/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerManager.java @@ -99,7 +99,11 @@ public void openCashDrawer() throws DeviceException { public DeviceHealthResponse getHealth() { DeviceHealthResponse deviceHealthResponse; - if (cashDrawerDevice.isConnected()) { + // Issue #10: only run the health check when the device is opened AND claimed; + // otherwise immediately report NOT_READY without checking health. + if (!cashDrawerDevice.isOpened() || !cashDrawerDevice.isClaimed()) { + deviceHealthResponse = new DeviceHealthResponse(cashDrawerDevice.getDeviceName(), DeviceHealth.NOTREADY); + } else if (cashDrawerDevice.isConnected()) { deviceHealthResponse = new DeviceHealthResponse(cashDrawerDevice.getDeviceName(), DeviceHealth.READY); } else { deviceHealthResponse = new DeviceHealthResponse(cashDrawerDevice.getDeviceName(), DeviceHealth.NOTREADY); diff --git a/src/main/java/com/target/devicemanager/components/check/MicrDevice.java b/src/main/java/com/target/devicemanager/components/check/MicrDevice.java index 339d784..60329c4 100644 --- a/src/main/java/com/target/devicemanager/components/check/MicrDevice.java +++ b/src/main/java/com/target/devicemanager/components/check/MicrDevice.java @@ -165,6 +165,20 @@ public boolean isConnected() { return dynamicMicr.isConnected(); } + /** + * @return Device is opened. + */ + public boolean isOpened() { + return dynamicMicr.isOpened(); + } + + /** + * @return Device is claimed. + */ + public boolean isClaimed() { + return dynamicMicr.isClaimed(); + } + /** * begins check insertion process. */ diff --git a/src/main/java/com/target/devicemanager/components/check/MicrManager.java b/src/main/java/com/target/devicemanager/components/check/MicrManager.java index bc53b33..be12355 100644 --- a/src/main/java/com/target/devicemanager/components/check/MicrManager.java +++ b/src/main/java/com/target/devicemanager/components/check/MicrManager.java @@ -143,7 +143,11 @@ public void connectionEventOccurred(ConnectionEvent connectionEvent) { public DeviceHealthResponse getHealth() { DeviceHealthResponse deviceHealthResponse; - if (micrDevice.isConnected()) { + // Issue #10: only run the health check when the device is opened AND claimed; + // otherwise immediately report NOT_READY without checking health. + if (!micrDevice.isOpened() || !micrDevice.isClaimed()) { + deviceHealthResponse = new DeviceHealthResponse(micrDevice.getDeviceName(), DeviceHealth.NOTREADY); + } else if (micrDevice.isConnected()) { deviceHealthResponse = new DeviceHealthResponse(micrDevice.getDeviceName(), DeviceHealth.READY); } else { deviceHealthResponse = new DeviceHealthResponse(micrDevice.getDeviceName(), DeviceHealth.NOTREADY); diff --git a/src/main/java/com/target/devicemanager/components/linedisplay/LineDisplayDevice.java b/src/main/java/com/target/devicemanager/components/linedisplay/LineDisplayDevice.java index 9e6063c..c2422c6 100644 --- a/src/main/java/com/target/devicemanager/components/linedisplay/LineDisplayDevice.java +++ b/src/main/java/com/target/devicemanager/components/linedisplay/LineDisplayDevice.java @@ -117,6 +117,20 @@ public boolean isConnected() { return dynamicLineDisplay.isConnected(); } + /** + * @return Device is opened. + */ + public boolean isOpened() { + return dynamicLineDisplay.isOpened(); + } + + /** + * @return Device is claimed. + */ + public boolean isClaimed() { + return dynamicLineDisplay.isClaimed(); + } + /** * Makes sure it displays the lines on device. * @param line1Text displays the first line text. diff --git a/src/main/java/com/target/devicemanager/components/linedisplay/LineDisplayManager.java b/src/main/java/com/target/devicemanager/components/linedisplay/LineDisplayManager.java index 86c4211..7265b2d 100644 --- a/src/main/java/com/target/devicemanager/components/linedisplay/LineDisplayManager.java +++ b/src/main/java/com/target/devicemanager/components/linedisplay/LineDisplayManager.java @@ -100,7 +100,11 @@ private String formatLineText(String lineText) { public DeviceHealthResponse getHealth() { DeviceHealthResponse deviceHealthResponse; - if (lineDisplayDevice.isConnected()) { + // Issue #10: only run the health check when the device is opened AND claimed; + // otherwise immediately report NOT_READY without checking health. + if (!lineDisplayDevice.isOpened() || !lineDisplayDevice.isClaimed()) { + deviceHealthResponse = new DeviceHealthResponse(lineDisplayDevice.getDeviceName(), DeviceHealth.NOTREADY); + } else if (lineDisplayDevice.isConnected()) { deviceHealthResponse = new DeviceHealthResponse(lineDisplayDevice.getDeviceName(), DeviceHealth.READY); } else { deviceHealthResponse = new DeviceHealthResponse(lineDisplayDevice.getDeviceName(), DeviceHealth.NOTREADY); diff --git a/src/main/java/com/target/devicemanager/components/printer/PrinterDevice.java b/src/main/java/com/target/devicemanager/components/printer/PrinterDevice.java index 46a8179..ecf3a4c 100644 --- a/src/main/java/com/target/devicemanager/components/printer/PrinterDevice.java +++ b/src/main/java/com/target/devicemanager/components/printer/PrinterDevice.java @@ -422,6 +422,20 @@ public boolean isConnected() { return deviceConnected; } + /** + * Whether the printer has been opened. + */ + public boolean isOpened() { + return dynamicPrinter.isOpened(); + } + + /** + * Whether the printer has been claimed. + */ + public boolean isClaimed() { + return dynamicPrinter.isClaimed(); + } + public boolean getIsCheckInserted() { return isCheckInserted; } diff --git a/src/main/java/com/target/devicemanager/components/printer/PrinterManager.java b/src/main/java/com/target/devicemanager/components/printer/PrinterManager.java index fc17bda..118eca5 100644 --- a/src/main/java/com/target/devicemanager/components/printer/PrinterManager.java +++ b/src/main/java/com/target/devicemanager/components/printer/PrinterManager.java @@ -162,7 +162,11 @@ public void frankCheck(List contents) throws PrinterException { public DeviceHealthResponse getHealth() { DeviceHealthResponse deviceHealthResponse; - if (printerDevice.isConnected()) { + // Issue #10: only run the health check when the device is opened AND claimed; + // otherwise immediately report NOT_READY without checking health. + if (!printerDevice.isOpened() || !printerDevice.isClaimed()) { + deviceHealthResponse = new DeviceHealthResponse(printerDevice.getDeviceName(), DeviceHealth.NOTREADY); + } else if (printerDevice.isConnected()) { deviceHealthResponse = new DeviceHealthResponse(printerDevice.getDeviceName(), DeviceHealth.READY); } else { deviceHealthResponse = new DeviceHealthResponse(printerDevice.getDeviceName(), DeviceHealth.NOTREADY); diff --git a/src/main/java/com/target/devicemanager/components/scale/ScaleDevice.java b/src/main/java/com/target/devicemanager/components/scale/ScaleDevice.java index 26b70fd..d3d489d 100644 --- a/src/main/java/com/target/devicemanager/components/scale/ScaleDevice.java +++ b/src/main/java/com/target/devicemanager/components/scale/ScaleDevice.java @@ -324,6 +324,22 @@ public boolean isConnected() { return deviceConnected; } + /** + * Checks to see if scale is opened. + * @return opened status. + */ + public boolean isOpened() { + return dynamicScale.isOpened(); + } + + /** + * Checks to see if scale is claimed. + * @return claimed status. + */ + public boolean isClaimed() { + return dynamicScale.isClaimed(); + } + /** * Lock the current resource. * @return diff --git a/src/main/java/com/target/devicemanager/components/scale/ScaleManager.java b/src/main/java/com/target/devicemanager/components/scale/ScaleManager.java index 76f3fdd..29a9c76 100644 --- a/src/main/java/com/target/devicemanager/components/scale/ScaleManager.java +++ b/src/main/java/com/target/devicemanager/components/scale/ScaleManager.java @@ -187,7 +187,11 @@ public boolean isScaleReady() { public DeviceHealthResponse getHealth() { DeviceHealthResponse deviceHealthResponse; - if (isScaleReady()) { + // Issue #10: only run the health check when the device is opened AND claimed; + // otherwise immediately report NOT_READY without checking health. + if (!scaleDevice.isOpened() || !scaleDevice.isClaimed()) { + deviceHealthResponse = new DeviceHealthResponse(scaleDevice.getDeviceName(), DeviceHealth.NOTREADY); + } else if (isScaleReady()) { deviceHealthResponse = new DeviceHealthResponse(scaleDevice.getDeviceName(), DeviceHealth.READY); } else { deviceHealthResponse = new DeviceHealthResponse(scaleDevice.getDeviceName(), DeviceHealth.NOTREADY); diff --git a/src/main/java/com/target/devicemanager/components/scanner/ScannerDevice.java b/src/main/java/com/target/devicemanager/components/scanner/ScannerDevice.java index 62c83d8..0b7bcda 100644 --- a/src/main/java/com/target/devicemanager/components/scanner/ScannerDevice.java +++ b/src/main/java/com/target/devicemanager/components/scanner/ScannerDevice.java @@ -203,6 +203,22 @@ public boolean isConnected() { return dynamicScanner.isConnected(); } + /** + * Whether the scanner has been opened. + * @return Opened status. + */ + public boolean isOpened() { + return dynamicScanner.isOpened(); + } + + /** + * Whether the scanner has been claimed. + * @return Claimed status. + */ + public boolean isClaimed() { + return dynamicScanner.isClaimed(); + } + public void setIsTest(boolean isTest) { this.isTest = isTest; diff --git a/src/main/java/com/target/devicemanager/components/scanner/ScannerManager.java b/src/main/java/com/target/devicemanager/components/scanner/ScannerManager.java index 88107ec..4703e0e 100644 --- a/src/main/java/com/target/devicemanager/components/scanner/ScannerManager.java +++ b/src/main/java/com/target/devicemanager/components/scanner/ScannerManager.java @@ -193,19 +193,11 @@ public List getHealth(ScannerType scannerType) { case "FLATBED": case "HANDHELD": if(scanner.getScannerType().equals(scannerType.name())) { - if (scanner.isConnected()) { - response.add(new DeviceHealthResponse(scanner.getDeviceName(), DeviceHealth.READY)); - } else { - response.add(new DeviceHealthResponse(scanner.getDeviceName(), DeviceHealth.NOTREADY)); - } + response.add(getScannerHealth(scanner)); } break; default: - if (scanner.isConnected()) { - response.add(new DeviceHealthResponse(scanner.getDeviceName(), DeviceHealth.READY)); - } else { - response.add(new DeviceHealthResponse(scanner.getDeviceName(), DeviceHealth.NOTREADY)); - } + response.add(getScannerHealth(scanner)); } } try { @@ -217,6 +209,18 @@ public List getHealth(ScannerType scannerType) { return response; } + /** + * Issue #10: a scanner reports READY only when it is opened AND claimed and + * its health check passes; otherwise the health check is skipped and it + * reports NOT_READY. + */ + private DeviceHealthResponse getScannerHealth(ScannerDevice scanner) { + if (scanner.isOpened() && scanner.isClaimed() && scanner.isConnected()) { + return new DeviceHealthResponse(scanner.getDeviceName(), DeviceHealth.READY); + } + return new DeviceHealthResponse(scanner.getDeviceName(), DeviceHealth.NOTREADY); + } + public List getStatus() { try { if (cacheManager != null && Objects.requireNonNull(cacheManager.getCache("scannerHealth")).get("health") != null) { diff --git a/src/test/java/com/target/devicemanager/common/DynamicDeviceTest.java b/src/test/java/com/target/devicemanager/common/DynamicDeviceTest.java new file mode 100644 index 0000000..0c34f47 --- /dev/null +++ b/src/test/java/com/target/devicemanager/common/DynamicDeviceTest.java @@ -0,0 +1,104 @@ +package com.target.devicemanager.common; + +import jpos.BaseJposControl; +import jpos.JposConst; +import jpos.JposException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class DynamicDeviceTest { + + private DynamicDevice dynamicDevice; + @Mock + private BaseJposControl mockDevice; + @Mock + private DevicePower mockDevicePower; + @Mock + private DeviceConnector mockDeviceConnector; + + @BeforeEach + public void setup() { + dynamicDevice = new DynamicDevice<>(mockDevice, mockDevicePower, mockDeviceConnector); + } + + @Test + public void isOpened_WhenStateIdle_ReturnsTrue() { + when(mockDevice.getState()).thenReturn(JposConst.JPOS_S_IDLE); + assertTrue(dynamicDevice.isOpened()); + } + + @Test + public void isOpened_WhenStateBusy_ReturnsTrue() { + when(mockDevice.getState()).thenReturn(JposConst.JPOS_S_BUSY); + assertTrue(dynamicDevice.isOpened()); + } + + @Test + public void isOpened_WhenStateClosed_ReturnsFalse() { + when(mockDevice.getState()).thenReturn(JposConst.JPOS_S_CLOSED); + assertFalse(dynamicDevice.isOpened()); + } + + @Test + public void isClaimed_WhenClaimed_ReturnsTrue() throws JposException { + when(mockDevice.getClaimed()).thenReturn(true); + assertTrue(dynamicDevice.isClaimed()); + } + + @Test + public void isClaimed_WhenNotClaimed_ReturnsFalse() throws JposException { + when(mockDevice.getClaimed()).thenReturn(false); + assertFalse(dynamicDevice.isClaimed()); + } + + @Test + public void isClaimed_WhenJposExceptionThrown_ReturnsFalse() throws JposException { + when(mockDevice.getClaimed()).thenThrow(new JposException(JposConst.JPOS_E_FAILURE)); + assertFalse(dynamicDevice.isClaimed()); + } + + @Test + public void isConnected_WhenOpenedClaimedAndOnline_ReturnsTrue() throws JposException { + when(mockDevice.getState()).thenReturn(JposConst.JPOS_S_IDLE); + when(mockDevice.getClaimed()).thenReturn(true); + when(mockDevicePower.getPowerState(mockDevice)).thenReturn(JposConst.JPOS_PS_ONLINE); + assertTrue(dynamicDevice.isConnected()); + } + + @Test + public void isConnected_WhenNotOpened_ReturnsFalseAndSkipsPowerCheck() { + when(mockDevice.getState()).thenReturn(JposConst.JPOS_S_CLOSED); + assertFalse(dynamicDevice.isConnected()); + verify(mockDevicePower, never()).getPowerState(any()); + } + + @Test + public void isConnected_WhenNotClaimed_ReturnsFalseAndSkipsPowerCheck() throws JposException { + when(mockDevice.getState()).thenReturn(JposConst.JPOS_S_IDLE); + when(mockDevice.getClaimed()).thenReturn(false); + assertFalse(dynamicDevice.isConnected()); + verify(mockDevicePower, never()).getPowerState(any()); + } + + @Test + public void isConnected_WhenPowerOffline_ReturnsFalse() throws JposException { + when(mockDevice.getState()).thenReturn(JposConst.JPOS_S_IDLE); + when(mockDevice.getClaimed()).thenReturn(true); + when(mockDevicePower.getPowerState(mockDevice)).thenReturn(JposConst.JPOS_PS_OFFLINE); + assertFalse(dynamicDevice.isConnected()); + } +} diff --git a/src/test/java/com/target/devicemanager/components/cashdrawer/CashDrawerManagerTest.java b/src/test/java/com/target/devicemanager/components/cashdrawer/CashDrawerManagerTest.java index ebf7f8d..bf0596b 100644 --- a/src/test/java/com/target/devicemanager/components/cashdrawer/CashDrawerManagerTest.java +++ b/src/test/java/com/target/devicemanager/components/cashdrawer/CashDrawerManagerTest.java @@ -93,6 +93,9 @@ public void clear() { public void testInitialize() { cashDrawerManager = new CashDrawerManager(mockCashDrawerDevice, mockCashDrawerLock); cashDrawerManagerCache = new CashDrawerManager(mockCashDrawerDevice, mockCashDrawerLock, mockCacheManager); + // Issue #10: default the device to opened + claimed so existing health tests reach the health check. + when(mockCashDrawerDevice.isOpened()).thenReturn(true); + when(mockCashDrawerDevice.isClaimed()).thenReturn(true); } @Test @@ -314,6 +317,36 @@ public void getHealth_WhenDeviceOffline_ShouldReturnNotReadyHealthResponse() { assertEquals(expected.toString(), testCache.get("health").get().toString()); } + @Test + public void getHealth_WhenNotClaimed_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockCashDrawerDevice.isClaimed()).thenReturn(false); + when(mockCashDrawerDevice.getDeviceName()).thenReturn("cashDrawer"); + when(mockCacheManager.getCache("cashDrawerHealth")).thenReturn(testCache); + + //act + DeviceHealthResponse deviceHealthResponse = cashDrawerManagerCache.getHealth(); + + //assert + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponse.getHealthStatus()); + verify(mockCashDrawerDevice, never()).isConnected(); + } + + @Test + public void getHealth_WhenNotOpened_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockCashDrawerDevice.isOpened()).thenReturn(false); + when(mockCashDrawerDevice.getDeviceName()).thenReturn("cashDrawer"); + when(mockCacheManager.getCache("cashDrawerHealth")).thenReturn(testCache); + + //act + DeviceHealthResponse deviceHealthResponse = cashDrawerManagerCache.getHealth(); + + //assert + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponse.getHealthStatus()); + verify(mockCashDrawerDevice, never()).isConnected(); + } + @Test public void getHealth_WhenDeviceOnline_ShouldReturnReadyHealthResponse() { //arrange diff --git a/src/test/java/com/target/devicemanager/components/check/MicrManagerTest.java b/src/test/java/com/target/devicemanager/components/check/MicrManagerTest.java index e8cd482..cb45a83 100644 --- a/src/test/java/com/target/devicemanager/components/check/MicrManagerTest.java +++ b/src/test/java/com/target/devicemanager/components/check/MicrManagerTest.java @@ -95,6 +95,9 @@ public void clear() { public void testInitialize() { micrManager = new MicrManager(mockMicrDevice); micrManagerCacheClient = new MicrManager(mockMicrDevice, mockCacheManager, mockFutureClient); + // Issue #10: default the device to opened + claimed so existing health tests reach the health check. + when(mockMicrDevice.isOpened()).thenReturn(true); + when(mockMicrDevice.isClaimed()).thenReturn(true); } @Test @@ -376,6 +379,36 @@ public void getHealth_WhenDeviceOffline_ShouldReturnNotReadyHealthResponse() { assertEquals(expected.toString(), testCache.get("health").get().toString()); } + @Test + public void getHealth_WhenNotClaimed_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockMicrDevice.isClaimed()).thenReturn(false); + when(mockMicrDevice.getDeviceName()).thenReturn("micr"); + when(mockCacheManager.getCache("micrHealth")).thenReturn(testCache); + + //act + DeviceHealthResponse deviceHealthResponse = micrManagerCacheClient.getHealth(); + + //assert + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponse.getHealthStatus()); + verify(mockMicrDevice, never()).isConnected(); + } + + @Test + public void getHealth_WhenNotOpened_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockMicrDevice.isOpened()).thenReturn(false); + when(mockMicrDevice.getDeviceName()).thenReturn("micr"); + when(mockCacheManager.getCache("micrHealth")).thenReturn(testCache); + + //act + DeviceHealthResponse deviceHealthResponse = micrManagerCacheClient.getHealth(); + + //assert + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponse.getHealthStatus()); + verify(mockMicrDevice, never()).isConnected(); + } + @Test public void getHealth_WhenDeviceOnline_ShouldReturnReadyHealthResponse() { //arrange diff --git a/src/test/java/com/target/devicemanager/components/linedisplay/LineDisplayManagerTest.java b/src/test/java/com/target/devicemanager/components/linedisplay/LineDisplayManagerTest.java index 23cb2fe..686ec66 100644 --- a/src/test/java/com/target/devicemanager/components/linedisplay/LineDisplayManagerTest.java +++ b/src/test/java/com/target/devicemanager/components/linedisplay/LineDisplayManagerTest.java @@ -89,6 +89,9 @@ public void clear() { public void testInitialize() { lineDisplayManager = new LineDisplayManager(mockLineDisplayDevice); lineDisplayManagerCache = new LineDisplayManager(mockLineDisplayDevice, mockCacheManager); + // Issue #10: default the device to opened + claimed so existing health tests reach the health check. + when(mockLineDisplayDevice.isOpened()).thenReturn(true); + when(mockLineDisplayDevice.isClaimed()).thenReturn(true); } @Test @@ -287,6 +290,36 @@ public void getHealth_WhenDeviceOffline_ShouldReturnNotReadyHealthResponse() { assertEquals(expected.toString(), testCache.get("health").get().toString()); } + @Test + public void getHealth_WhenNotClaimed_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockLineDisplayDevice.isClaimed()).thenReturn(false); + when(mockLineDisplayDevice.getDeviceName()).thenReturn("lineDisplay"); + when(mockCacheManager.getCache("lineDisplayHealth")).thenReturn(testCache); + + //act + DeviceHealthResponse deviceHealthResponse = lineDisplayManagerCache.getHealth(); + + //assert + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponse.getHealthStatus()); + verify(mockLineDisplayDevice, never()).isConnected(); + } + + @Test + public void getHealth_WhenNotOpened_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockLineDisplayDevice.isOpened()).thenReturn(false); + when(mockLineDisplayDevice.getDeviceName()).thenReturn("lineDisplay"); + when(mockCacheManager.getCache("lineDisplayHealth")).thenReturn(testCache); + + //act + DeviceHealthResponse deviceHealthResponse = lineDisplayManagerCache.getHealth(); + + //assert + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponse.getHealthStatus()); + verify(mockLineDisplayDevice, never()).isConnected(); + } + @Test public void getHealth_WhenDeviceOnline_ShouldReturnReadyHealthResponse() { //arrange diff --git a/src/test/java/com/target/devicemanager/components/printer/PrinterManagerTest.java b/src/test/java/com/target/devicemanager/components/printer/PrinterManagerTest.java index aca7a2f..ca1cb50 100644 --- a/src/test/java/com/target/devicemanager/components/printer/PrinterManagerTest.java +++ b/src/test/java/com/target/devicemanager/components/printer/PrinterManagerTest.java @@ -99,6 +99,9 @@ public void clear() { public void testInitialize() { printerManager = new PrinterManager(mockPrinterDevice, mockPrinterLock); printerManagerCacheFuture = new PrinterManager(mockPrinterDevice, mockPrinterLock, mockCacheManager, mockFuture, true); + // Issue #10: default the device to opened + claimed so existing health tests reach the health check. + when(mockPrinterDevice.isOpened()).thenReturn(true); + when(mockPrinterDevice.isClaimed()).thenReturn(true); } @Test @@ -501,6 +504,38 @@ public void getHealth_WhenDeviceOnline_ShouldReturnReadyHealthResponse() { assertEquals(expected.toString(), testCache.get("health").get().toString()); } + @Test + public void getHealth_WhenNotClaimed_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockPrinterDevice.isClaimed()).thenReturn(false); + when(mockPrinterDevice.getDeviceName()).thenReturn("printer"); + when(mockCacheManager.getCache("printerHealth")).thenReturn(testCache); + + //act + DeviceHealthResponse deviceHealthResponse = printerManagerCacheFuture.getHealth(); + + //assert + assertEquals("printer", deviceHealthResponse.getDeviceName()); + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponse.getHealthStatus()); + verify(mockPrinterDevice, never()).isConnected(); + } + + @Test + public void getHealth_WhenNotOpened_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockPrinterDevice.isOpened()).thenReturn(false); + when(mockPrinterDevice.getDeviceName()).thenReturn("printer"); + when(mockCacheManager.getCache("printerHealth")).thenReturn(testCache); + + //act + DeviceHealthResponse deviceHealthResponse = printerManagerCacheFuture.getHealth(); + + //assert + assertEquals("printer", deviceHealthResponse.getDeviceName()); + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponse.getHealthStatus()); + verify(mockPrinterDevice, never()).isConnected(); + } + @Test public void getHealth_CacheNull_ShouldReturnReadyHealthResponse() { //arrange diff --git a/src/test/java/com/target/devicemanager/components/scale/ScaleManagerTest.java b/src/test/java/com/target/devicemanager/components/scale/ScaleManagerTest.java index 1179f36..ea819aa 100644 --- a/src/test/java/com/target/devicemanager/components/scale/ScaleManagerTest.java +++ b/src/test/java/com/target/devicemanager/components/scale/ScaleManagerTest.java @@ -115,6 +115,9 @@ public void testInitialize() { sseEmitterList.add(mockSseEmitter); scaleManager = new ScaleManager(mockScaleDevice, mockSseEmitterList, mockCompletableFutureFormattedWeightList); scaleManagerListCacheEmitter = new ScaleManager(mockScaleDevice, sseEmitterList, completableFutureFormattedWeightList, mockCacheManager, mockSseEmitterList); + // Issue #10: default the device to opened + claimed so existing health tests reach the health check. + when(mockScaleDevice.isOpened()).thenReturn(true); + when(mockScaleDevice.isClaimed()).thenReturn(true); } @Test @@ -568,6 +571,36 @@ public void getHealth_WhenDeviceOffline_ShouldReturnNotReadyHealthResponse() { assertEquals(expected.toString(), testCache.get("health").get().toString()); } + @Test + public void getHealth_WhenNotClaimed_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockScaleDevice.isClaimed()).thenReturn(false); + when(mockScaleDevice.getDeviceName()).thenReturn("scale"); + when(mockCacheManager.getCache("scaleHealth")).thenReturn(testCache); + + //act + DeviceHealthResponse deviceHealthResponse = scaleManagerListCacheEmitter.getHealth(); + + //assert + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponse.getHealthStatus()); + verify(mockScaleDevice, never()).isConnected(); + } + + @Test + public void getHealth_WhenNotOpened_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockScaleDevice.isOpened()).thenReturn(false); + when(mockScaleDevice.getDeviceName()).thenReturn("scale"); + when(mockCacheManager.getCache("scaleHealth")).thenReturn(testCache); + + //act + DeviceHealthResponse deviceHealthResponse = scaleManagerListCacheEmitter.getHealth(); + + //assert + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponse.getHealthStatus()); + verify(mockScaleDevice, never()).isConnected(); + } + @Test public void getHealth_WhenDeviceOnline_ShouldReturnReadyHealthResponse() { //arrange diff --git a/src/test/java/com/target/devicemanager/components/scanner/ScannerManagerTest.java b/src/test/java/com/target/devicemanager/components/scanner/ScannerManagerTest.java index e97c344..4d06430 100644 --- a/src/test/java/com/target/devicemanager/components/scanner/ScannerManagerTest.java +++ b/src/test/java/com/target/devicemanager/components/scanner/ScannerManagerTest.java @@ -110,6 +110,11 @@ public void testInitialize() { reconnectResults.add(mockFuture); scannerManager = new ScannerManager(scannerDevices, mockScannerLock); scannerManagerCache = Mockito.spy(new ScannerManager(scannerDevices, mockScannerLock, mockCacheManager, mockExecutor, reconnectResults, true)); + // Issue #10: default scanners to opened + claimed so existing health tests reach the health check. + when(mockHandheldScannerDevice.isOpened()).thenReturn(true); + when(mockHandheldScannerDevice.isClaimed()).thenReturn(true); + when(mockFlatbedScannerDevice.isOpened()).thenReturn(true); + when(mockFlatbedScannerDevice.isClaimed()).thenReturn(true); } @Test @@ -408,6 +413,42 @@ public void getHealth_WhenFlatbedDeviceOffline_ShouldReturnNotReadyHealthRespons assertEquals(expectedList.toString(), testCache.get("health").get().toString()); } + @Test + public void getHealth_WhenScannerNotClaimed_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockHandheldScannerDevice.isClaimed()).thenReturn(false); + when(mockHandheldScannerDevice.getDeviceName()).thenReturn("HANDHELD"); + when(mockFlatbedScannerDevice.isConnected()).thenReturn(true); + when(mockFlatbedScannerDevice.getDeviceName()).thenReturn("FLATBED"); + when(mockCacheManager.getCache("scannerHealth")).thenReturn(testCache); + + //act + List deviceHealthResponseList = scannerManagerCache.getHealth(ScannerType.BOTH); + + //assert + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponseList.get(0).getHealthStatus()); + assertEquals(DeviceHealth.READY, deviceHealthResponseList.get(1).getHealthStatus()); + verify(mockHandheldScannerDevice, never()).isConnected(); + } + + @Test + public void getHealth_WhenScannerNotOpened_ShouldReturnNotReadyWithoutCheckingConnection() { + //arrange + when(mockHandheldScannerDevice.isOpened()).thenReturn(false); + when(mockHandheldScannerDevice.getDeviceName()).thenReturn("HANDHELD"); + when(mockFlatbedScannerDevice.isConnected()).thenReturn(true); + when(mockFlatbedScannerDevice.getDeviceName()).thenReturn("FLATBED"); + when(mockCacheManager.getCache("scannerHealth")).thenReturn(testCache); + + //act + List deviceHealthResponseList = scannerManagerCache.getHealth(ScannerType.BOTH); + + //assert + assertEquals(DeviceHealth.NOTREADY, deviceHealthResponseList.get(0).getHealthStatus()); + assertEquals(DeviceHealth.READY, deviceHealthResponseList.get(1).getHealthStatus()); + verify(mockHandheldScannerDevice, never()).isConnected(); + } + @Test public void getHealth_WhenBothDevicesOnline_ShouldReturnReadyHealthResponse() { //arrange From bf1ba547428870c7d380d1d3bc59a093dc3e172e Mon Sep 17 00:00:00 2001 From: Brian Bazurto <124537281+ba-00001@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:50:48 -0500 Subject: [PATCH 2/3] Keep simulated devices opened+claimed for health gate (#10) SimulatedDynamicDevice overrides isConnected() but not the new isOpened()/isClaimed(), so in simulation mode the health gate read the underlying JavaPOS control (never opened/claimed) and forced NOT_READY for scanner/printer/scale/cash drawer. Override isOpened()/isClaimed() to track the simulated connection state. --- .../common/SimulatedDynamicDevice.java | 16 +++++++ .../common/SimulatedDynamicDeviceTest.java | 47 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/test/java/com/target/devicemanager/common/SimulatedDynamicDeviceTest.java diff --git a/src/main/java/com/target/devicemanager/common/SimulatedDynamicDevice.java b/src/main/java/com/target/devicemanager/common/SimulatedDynamicDevice.java index 40a1264..388e71b 100644 --- a/src/main/java/com/target/devicemanager/common/SimulatedDynamicDevice.java +++ b/src/main/java/com/target/devicemanager/common/SimulatedDynamicDevice.java @@ -25,4 +25,20 @@ public ConnectionResult connect() { public boolean isConnected() { return simulatedDevice.getState() == JposConst.JPOS_S_IDLE; } + + /** + * A simulated device does not go through the real open/claim handshake, so + * opened/claimed track its simulated connection state instead of the + * underlying JavaPOS control (whose getClaimed() would otherwise report not + * claimed). + */ + @Override + public boolean isOpened() { + return isConnected(); + } + + @Override + public boolean isClaimed() { + return isConnected(); + } } \ No newline at end of file diff --git a/src/test/java/com/target/devicemanager/common/SimulatedDynamicDeviceTest.java b/src/test/java/com/target/devicemanager/common/SimulatedDynamicDeviceTest.java new file mode 100644 index 0000000..0f064ec --- /dev/null +++ b/src/test/java/com/target/devicemanager/common/SimulatedDynamicDeviceTest.java @@ -0,0 +1,47 @@ +package com.target.devicemanager.common; + +import jpos.BaseJposControl; +import jpos.JposConst; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class SimulatedDynamicDeviceTest { + + private SimulatedDynamicDevice simulatedDynamicDevice; + @Mock + private BaseJposControl mockDevice; + @Mock + private DevicePower mockDevicePower; + @Mock + private DeviceConnector mockDeviceConnector; + + @BeforeEach + public void setup() { + simulatedDynamicDevice = new SimulatedDynamicDevice<>(mockDevice, mockDevicePower, mockDeviceConnector); + } + + @Test + public void isOpenedAndIsClaimed_WhenSimulatedDeviceOnline_ReturnTrue() { + when(mockDevice.getState()).thenReturn(JposConst.JPOS_S_IDLE); + assertTrue(simulatedDynamicDevice.isOpened()); + assertTrue(simulatedDynamicDevice.isClaimed()); + } + + @Test + public void isOpenedAndIsClaimed_WhenSimulatedDeviceOffline_ReturnFalse() { + when(mockDevice.getState()).thenReturn(JposConst.JPOS_S_CLOSED); + assertFalse(simulatedDynamicDevice.isOpened()); + assertFalse(simulatedDynamicDevice.isClaimed()); + } +} From 04a572de6b50c0a066222c0b486a6ac823eccfc3 Mon Sep 17 00:00:00 2001 From: Brian Bazurto <124537281+ba-00001@users.noreply.github.com> Date: Sat, 27 Jun 2026 17:25:29 -0500 Subject: [PATCH 3/3] Keep health gate and check on one source of truth (#10) For printer, scale, and cash drawer, isConnected() returns a cached connection flag while isOpened()/isClaimed() read live JPOS state, so a transient getClaimed() failure could flip a healthy device to NOT_READY. These devices only track a single connection flag (set after a successful open + claim + enable), so make isOpened()/isClaimed() mirror isConnected(). Scanner, line display, and MICR keep delegating to the dynamic device, where all three already read the same live state. --- .../components/cashdrawer/CashDrawerDevice.java | 11 +++++------ .../components/printer/PrinterDevice.java | 11 +++++------ .../devicemanager/components/scale/ScaleDevice.java | 13 +++++-------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerDevice.java b/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerDevice.java index 6868b35..64cc7b4 100644 --- a/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerDevice.java +++ b/src/main/java/com/target/devicemanager/components/cashdrawer/CashDrawerDevice.java @@ -231,17 +231,16 @@ public String getDeviceName() { public boolean isConnected() { return deviceConnected; } /** - * Shows if the device is opened. + * The cash drawer tracks a single cached connection flag (set only after a + * successful open + claim + enable), so opened/claimed mirror isConnected() + * to keep the health gate and the health check on one source of truth. */ public boolean isOpened() { - return dynamicCashDrawer.isOpened(); + return isConnected(); } - /** - * Shows if the device is claimed. - */ public boolean isClaimed() { - return dynamicCashDrawer.isClaimed(); + return isConnected(); } /** diff --git a/src/main/java/com/target/devicemanager/components/printer/PrinterDevice.java b/src/main/java/com/target/devicemanager/components/printer/PrinterDevice.java index ecf3a4c..5ff2617 100644 --- a/src/main/java/com/target/devicemanager/components/printer/PrinterDevice.java +++ b/src/main/java/com/target/devicemanager/components/printer/PrinterDevice.java @@ -423,17 +423,16 @@ public boolean isConnected() { } /** - * Whether the printer has been opened. + * The printer tracks a single cached connection flag (set only after a + * successful open + claim + enable), so opened/claimed mirror isConnected() + * to keep the health gate and the health check on one source of truth. */ public boolean isOpened() { - return dynamicPrinter.isOpened(); + return isConnected(); } - /** - * Whether the printer has been claimed. - */ public boolean isClaimed() { - return dynamicPrinter.isClaimed(); + return isConnected(); } public boolean getIsCheckInserted() { diff --git a/src/main/java/com/target/devicemanager/components/scale/ScaleDevice.java b/src/main/java/com/target/devicemanager/components/scale/ScaleDevice.java index d3d489d..0788c62 100644 --- a/src/main/java/com/target/devicemanager/components/scale/ScaleDevice.java +++ b/src/main/java/com/target/devicemanager/components/scale/ScaleDevice.java @@ -325,19 +325,16 @@ public boolean isConnected() { } /** - * Checks to see if scale is opened. - * @return opened status. + * The scale tracks a single cached connection flag (set only after a + * successful open + claim + enable), so opened/claimed mirror isConnected() + * to keep the health gate and the health check on one source of truth. */ public boolean isOpened() { - return dynamicScale.isOpened(); + return isConnected(); } - /** - * Checks to see if scale is claimed. - * @return claimed status. - */ public boolean isClaimed() { - return dynamicScale.isClaimed(); + return isConnected(); } /**