package jmri.server.json.layoutblock; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.DataOutputStream; import java.io.IOException; import java.util.Locale; import jmri.InstanceManager; import jmri.JmriException; import jmri.jmrit.display.layoutEditor.LayoutBlock; import jmri.jmrit.display.layoutEditor.LayoutBlockManager; import jmri.server.json.JSON; import jmri.server.json.JsonException; import jmri.server.json.JsonMockConnection; import jmri.server.json.JsonRequest; import jmri.util.JUnitUtil; import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Randall Wood Copyright 2018 */ public class JsonLayoutBlockSocketServiceTest { private final Locale locale = Locale.ENGLISH; @BeforeEach public void setUp() { JUnitUtil.setUp(); JUnitUtil.resetProfileManager(); JUnitUtil.initLayoutBlockManager(); } @AfterEach public void tearDown() { JUnitUtil.deregisterBlockManagerShutdownTask(); JUnitUtil.tearDown(); } /** * Test property change listener on LayoutBlocks. * * @throws java.io.IOException if unexpectedly unable to write to * connection * @throws jmri.JmriException on unexpected error handling * LayoutBlock * @throws jmri.server.json.JsonException on unexpected error handling JSON */ @Test public void testBlockChange() throws IOException, JmriException, JsonException { JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null); JsonLayoutBlockSocketService instance = new JsonLayoutBlockSocketService(connection); LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, "LayoutBlock1"); assertNotNull( lb, "Required LayoutBlock not created"); JsonNode message = connection.getObjectMapper().createObjectNode().put(JSON.NAME, lb.getSystemName()); assertEquals( 1, lb.getNumPropertyChangeListeners(), "Block has only one listener"); instance.onMessage(JsonLayoutBlock.LAYOUTBLOCK, message, new JsonRequest(locale, JSON.V5, JSON.POST, 42)); assertEquals( 2, lb.getNumPropertyChangeListeners(), "Block is being listened to by service"); connection.sendMessage(null, 0); lb.redrawLayoutBlockPanels(); JsonNode result = connection.getMessage(); assertNotNull(result); assertEquals( lb.getSystemName(), result.path(JSON.DATA).path(JSON.NAME).asText(), "Message is LayoutBlock1"); // test IOException handling when listening by triggering exception and // observing that block1 is no longer being listened to connection.setThrowIOException(true); lb.redrawLayoutBlockPanels(); assertEquals( 1, lb.getNumPropertyChangeListeners(), "Block is no longer listened to by service"); instance.onMessage(JsonLayoutBlock.LAYOUTBLOCK, message, new JsonRequest(locale, JSON.V5, JSON.POST, 42)); assertEquals( 2, lb.getNumPropertyChangeListeners(), "Block is being listened to by service"); instance.onClose(); assertEquals( 1, lb.getNumPropertyChangeListeners(), "Block is no longer listened to by service"); } /** * Test of onMessage method, of class JsonLayoutBlockSocketService. * * @throws java.io.IOException for unexpected errors * @throws jmri.JmriException for unexpected errors * @throws jmri.server.json.JsonException for unexpected errors */ @Test public void testOnMessage() throws IOException, JmriException, JsonException { JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null); LayoutBlockManager manager = InstanceManager.getDefault(LayoutBlockManager.class); LayoutBlock lb = manager.createNewLayoutBlock(null, "LayoutBlock1"); assertNotNull( lb, "LayoutBlock is created"); assertEquals( 1, lb.getPropertyChangeListeners().length, "LayoutBlock has 1 listener"); // test GETs JsonLayoutBlockSocketService instance = new JsonLayoutBlockSocketService(connection); instance.onMessage(JsonLayoutBlock.LAYOUTBLOCK, instance.getConnection().getObjectMapper().readTree("{\"name\":\"" + lb.getSystemName() + "\"}"), new JsonRequest(locale, JSON.V5, JSON.GET, 42)); // onMessage causes a listener to be added to requested LayoutBlocks if // not already listening assertEquals( 2, lb.getPropertyChangeListeners().length, "LayoutBlock has 2 listeners"); // test POSTs instance.onMessage(JsonLayoutBlock.LAYOUTBLOCK, instance.getConnection().getObjectMapper() .readTree("{\"name\":\"" + lb.getSystemName() + "\", \"userName\":\"LayoutBlock2\"}"), new JsonRequest(locale, JSON.V5, JSON.POST, 42)); // onMessage causes a listener to be added to requested LayoutBlocks if // not already listening assertEquals( 2, lb.getPropertyChangeListeners().length, "LayoutBlock has 2 listeners"); assertEquals( "LayoutBlock2", lb.getUserName(), "LayoutBlock user name is changed"); instance.onMessage(JsonLayoutBlock.LAYOUTBLOCK, instance.getConnection().getObjectMapper() .readTree("{\"name\":\"" + lb.getSystemName() + "\", \"comment\":\"this is a comment\"}"), new JsonRequest(locale, JSON.V5, JSON.POST, 42)); assertEquals( "this is a comment", lb.getComment(), "LayoutBlock has comment"); instance.onMessage(JsonLayoutBlock.LAYOUTBLOCK, instance.getConnection().getObjectMapper() .readTree("{\"name\":\"" + lb.getSystemName() + "\", \"comment\":null}"), new JsonRequest(locale, JSON.V5, JSON.POST, 42)); assertNull( lb.getComment(), "LayoutBlock has no comment"); // test PUTs instance.onMessage(JsonLayoutBlock.LAYOUTBLOCK, instance.getConnection().getObjectMapper().readTree("{\"name\":null, \"userName\":\"LayoutBlock3\"}"), new JsonRequest(locale, JSON.V5, JSON.PUT, 42)); assertNotNull( manager.getLayoutBlock("LayoutBlock3"), "New LayoutBlock created"); // test DELETEs // first add a named reference listener to trigger a deletion conflict lb.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { // do nothing } }, lb.getUserName(), "Test Listener"); ObjectNode message = instance.getConnection().getObjectMapper().createObjectNode().put(JSON.NAME, lb.getSystemName()); JsonException ex = assertThrows( JsonException.class, () -> instance.onMessage(JsonLayoutBlock.LAYOUTBLOCK, message, new JsonRequest(locale, JSON.V5, JSON.DELETE, 42)), "Expected exception not thrown"); assertEquals(409, ex.getCode()); assertEquals(1, ex.getAdditionalData().path(JSON.CONFLICT).size()); assertEquals("Test Listener", ex.getAdditionalData().path(JSON.CONFLICT).path(0).asText()); ObjectNode message2 = message.put(JSON.FORCE_DELETE, ex.getAdditionalData().path(JSON.FORCE_DELETE).asText()); assertNotNull( manager.getBySystemName(lb.getSystemName()), "LayoutBlock not deleted"); // will throw if prior catch failed instance.onMessage(JsonLayoutBlock.LAYOUTBLOCK, message2, new JsonRequest(locale, JSON.V5, JSON.DELETE, 42)); assertNull( manager.getBySystemName(lb.getSystemName()), "LayoutBlock deleted"); ex = assertThrows( JsonException.class, () -> // deleting again should throw an exception instance.onMessage(JsonLayoutBlock.LAYOUTBLOCK, message2, new JsonRequest(locale, JSON.V5, JSON.DELETE, 42)), "Expected exception not thrown."); assertEquals(404, ex.getCode()); instance.onClose(); // clean up } /** * Test of onList method, of class JsonLayoutBlockSocketService. * * @throws java.io.IOException for unexpected errors * @throws jmri.JmriException for unexpected errors * @throws jmri.server.json.JsonException for unexpected errors */ @Test public void testOnList() throws IOException, JmriException, JsonException { LayoutBlockManager manager = InstanceManager.getDefault(LayoutBlockManager.class); JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null); LayoutBlock lb1 = manager.createNewLayoutBlock(null, "LayoutBlock1"); LayoutBlock lb2 = manager.createNewLayoutBlock(null, "LayoutBlock2"); assertNotNull( lb1, "LayoutBlock1 is created"); assertNotNull( lb2, "LayoutBlock2 is created"); assertEquals( 1, lb1.getPropertyChangeListeners().length, "LayoutBlock1 has 1 listener"); JsonLayoutBlockSocketService instance = new JsonLayoutBlockSocketService(connection); instance.onList(JsonLayoutBlock.LAYOUTBLOCK, NullNode.getInstance(), new JsonRequest(locale, JSON.V5, JSON.GET, 0)); // onList should not add a listener to all LayoutBlocks assertEquals( 1, lb1.getPropertyChangeListeners().length, "LayoutBlock1 has 1 listener"); JsonNode message = connection.getMessage(); assertNotNull(message); assertTrue( message.isArray(), "Message is an array"); assertEquals( manager.getNamedBeanSet().size(), message.size(), "All LayoutBlocks are listed"); instance.onClose(); // clean up } /** * Test of onClose method, of class JsonLayoutBlockSocketService. * * @throws java.io.IOException for unexpected errors * @throws jmri.JmriException for unexpected errors * @throws jmri.server.json.JsonException for unexpected errors */ @Test public void testOnClose() throws IOException, JmriException, JsonException { LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).createNewLayoutBlock(null, "LayoutBlock1"); assertNotNull( lb, "LayoutBlock is created"); assertEquals( 1, lb.getPropertyChangeListeners().length, "LayoutBlock has 1 listener"); JsonLayoutBlockSocketService instance = new JsonLayoutBlockSocketService(new JsonMockConnection((DataOutputStream) null)); instance.onMessage(JsonLayoutBlock.LAYOUTBLOCK, instance.getConnection().getObjectMapper().readTree("{\"name\":\"" + lb.getSystemName() + "\"}"), new JsonRequest(locale, JSON.V5, JSON.GET, 42)); // onMessage causes a listener to be added to requested LayoutBlocks assertEquals( 2, lb.getPropertyChangeListeners().length, "LayoutBlock has 2 listeners"); instance.onClose(); // onClose removes listeners assertEquals( 1, lb.getPropertyChangeListeners().length, "LayoutBlock has 1 listener"); } }