348 lines
16 KiB
Java
348 lines
16 KiB
Java
package jmri.server.json;
|
|
|
|
import static jmri.server.json.JsonTestServiceFactory.TEST;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
|
|
import java.io.*;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Locale;
|
|
|
|
import jmri.profile.NullProfile;
|
|
import jmri.util.JUnitAppender;
|
|
import jmri.util.JUnitUtil;
|
|
|
|
import org.junit.jupiter.api.*;
|
|
import org.junit.jupiter.api.io.TempDir;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
import static org.junit.jupiter.api.Assertions.fail;
|
|
|
|
/**
|
|
*
|
|
* @author Paul Bender Copyright (C) 2017
|
|
* @author Randall Wood Copyright 2018
|
|
*/
|
|
public class JsonClientHandlerTest {
|
|
|
|
private Locale locale = Locale.ENGLISH;
|
|
|
|
@BeforeEach
|
|
public void setUp(@TempDir File folder) throws IOException {
|
|
JUnitUtil.setUp();
|
|
JUnitUtil.resetProfileManager(new NullProfile(folder));
|
|
JUnitUtil.initRosterConfigManager();
|
|
}
|
|
|
|
@AfterEach
|
|
public void tearDown() {
|
|
JUnitUtil.deregisterBlockManagerShutdownTask();
|
|
JUnitUtil.tearDown();
|
|
}
|
|
|
|
/**
|
|
* Test of onClose method, of class JsonClientHandler.
|
|
*/
|
|
@Test
|
|
public void testOnClose() {
|
|
JsonClientHandler instance = new TestJsonClientHandler();
|
|
assertFalse(instance.getServices().isEmpty());
|
|
instance.onClose();
|
|
assertTrue(instance.getServices().isEmpty());
|
|
}
|
|
|
|
/**
|
|
* Test of onMessage method, of class JsonClientHandler.
|
|
*
|
|
* @throws IOException if unable to pass message
|
|
*/
|
|
@Test
|
|
public void testOnMessage_String() throws IOException {
|
|
// valid list request
|
|
String string = "{\"type\":\"test\",\"method\":\"list\"}";
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
JsonClientHandler instance = new TestJsonClientHandler(connection);
|
|
instance.onMessage(string);
|
|
JsonNode message = connection.getMessage();
|
|
assertNotNull( message, "Response provided");
|
|
assertTrue( message.isArray(), "Response is an array");
|
|
assertEquals( 2, message.size(), "Response array contains two elements");
|
|
assertTrue( message.get(0).isObject(), "Response array element 0 is an object");
|
|
assertEquals( 2, message.get(0).size(), "Response array element 0 is a JSON message");
|
|
assertTrue( message.get(1).isObject(), "Response array element 1 is an object");
|
|
assertEquals( 2, message.get(1).size(), "Response array element 1 is a JSON message");
|
|
// non-JSON request
|
|
instance.onMessage("not a JSON object");
|
|
message = connection.getMessage();
|
|
assertNotNull( message, "Response provided");
|
|
JUnitAppender.assertWarnMessageStartsWith("Exception processing \"not a JSON object\"");
|
|
assertTrue( message.isObject(), "Error response is an object");
|
|
assertEquals( JsonException.ERROR, message.path(JSON.TYPE).asText(), "Error response is an ERROR");
|
|
assertEquals( 500,
|
|
message.path(JSON.DATA).path(JsonException.CODE).asInt(),
|
|
"Error response is type 500");
|
|
// ping request (triggers special paths in JsonClientHandler)
|
|
instance.onMessage("{\"type\":\"ping\"}");
|
|
message = connection.getMessage();
|
|
assertNotNull( message, "Response provided");
|
|
assertTrue( message.isObject(), "Response is an object");
|
|
assertEquals( 1, message.size(), "Response array contains one elements");
|
|
assertEquals( JSON.PONG, message.path(JSON.TYPE).asText(), "Response type is pong");
|
|
}
|
|
|
|
/**
|
|
* Test of onMessage method, of class JsonClientHandler.
|
|
*
|
|
* @throws IOException if unable to pass message
|
|
*/
|
|
@Test
|
|
public void testOnMessage_JsonNode_Method_list() throws IOException {
|
|
testOnMessage_JsonNode_Method_list("{\"type\":\"test\",\"method\":\"list\"}");
|
|
testOnMessage_JsonNode_Method_list("{\"type\":\"list\",\"list\":\"test\"}");
|
|
testOnMessage_JsonNode_Method_list("{\"list\":\"test\"}");
|
|
}
|
|
|
|
private void testOnMessage_JsonNode_Method_list(String message) throws IOException {
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
JsonClientHandler instance = new TestJsonClientHandler(connection);
|
|
JsonNode node = connection.getObjectMapper().readTree(message);
|
|
instance.onMessage(node);
|
|
JsonNode response = connection.getMessage();
|
|
assertNotNull( response, "Response provided");
|
|
assertTrue( response.isArray(), "Response is an array");
|
|
assertEquals( 2, response.size(), "Response array contains two elements");
|
|
assertTrue( response.get(0).isObject(), "Response array element 0 is an object");
|
|
assertEquals( 2, response.get(0).size(), "Response array element 0 is a JSON message");
|
|
assertTrue( response.get(1).isObject(), "Response array element 1 is an object");
|
|
assertEquals( 2, response.get(1).size(), "Response array element 1 is a JSON message");
|
|
}
|
|
|
|
/**
|
|
* Test of onMessage method attempting to list an invalid type.
|
|
*
|
|
* @throws java.io.IOException if unable to pass message
|
|
*/
|
|
@Test
|
|
public void testOnMessage_JsonNode_Method_list_invalidType() throws IOException {
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
connection.setLocale(locale);
|
|
JsonClientHandler instance = new TestJsonClientHandler(connection);
|
|
JsonNode node = connection.getObjectMapper().readTree("{\"type\":\"non-existant-type\",\"method\":\"list\"}");
|
|
instance.onMessage(node);
|
|
JUnitAppender.assertWarnMessage("Requested list type 'non-existant-type' unknown.");
|
|
JsonNode message = connection.getMessage();
|
|
assertNotNull( message, "Response provided");
|
|
assertTrue( message.isObject(), "Response is an object");
|
|
assertEquals( 404, message.path(JSON.DATA).path(JsonException.CODE).asInt(),
|
|
"Response contains error code 404");
|
|
}
|
|
|
|
/**
|
|
* Test of onMessage method with an invalid type using the get method.
|
|
*
|
|
* @throws java.io.IOException if unable to pass message
|
|
*/
|
|
@Test
|
|
public void testOnMessage_JsonNode_Method_get_invalidType() throws IOException {
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
connection.setLocale(locale);
|
|
JsonClientHandler instance = new TestJsonClientHandler(connection);
|
|
JsonNode node = connection.getObjectMapper().readTree("{\"type\":\"non-existant-type\",\"method\":\"get\"}");
|
|
instance.onMessage(node);
|
|
JUnitAppender.assertWarnMessage("Requested type 'non-existant-type' unknown.");
|
|
JsonNode message = connection.getMessage();
|
|
assertNotNull( message, "Response provided");
|
|
assertTrue( message.isObject(), "Response is an object");
|
|
assertEquals( 404, message.path(JSON.DATA).path(JsonException.CODE).asInt(),
|
|
"Response contains error code 404");
|
|
}
|
|
|
|
/**
|
|
* Test of onMessage method with valid type but missing data.
|
|
*
|
|
* @throws java.io.IOException if unable to pass message
|
|
*/
|
|
@Test
|
|
public void testOnMessage_JsonNode_Method_post_missingData() throws IOException {
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
connection.setLocale(locale);
|
|
JsonClientHandler instance = new TestJsonClientHandler(connection);
|
|
JsonNode node = connection.getObjectMapper().readTree("{\"type\":\"test\", \"method\":\"post\"}");
|
|
instance.onMessage(node);
|
|
JsonNode message = connection.getMessage();
|
|
assertNotNull( message, "Response provided");
|
|
JsonNode data = message.path(JSON.DATA);
|
|
assertTrue( message.isObject(), "Response is an object");
|
|
assertEquals( JsonException.ERROR, message.path(JSON.TYPE).asText(),
|
|
"Response is an error");
|
|
assertEquals( 400, data.path(JsonException.CODE).asInt(), "Response contains error code 400");
|
|
assertEquals( "Data property of JSON message missing.",
|
|
data.path(JsonException.MESSAGE).asText(),
|
|
"Response contains error message");
|
|
}
|
|
|
|
/**
|
|
* Test of onMessage method when service throws JmriException. A test
|
|
* message with the data property {@literal {"throws":"JmriException"} }
|
|
* will throw this exception for this test.
|
|
*
|
|
* @throws java.io.IOException if unable to pass message
|
|
*/
|
|
@Test
|
|
public void testOnMessage_JsonNode_Method_service_throws_JmriException() throws IOException {
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
connection.setLocale(locale);
|
|
JsonClientHandler instance = new TestJsonClientHandler(connection);
|
|
JsonNode node =
|
|
connection.getObjectMapper().readTree("{\"type\":\"test\", \"data\":{\"throws\":\"JmriException\"}}");
|
|
instance.onMessage(node);
|
|
JsonNode message = connection.getMessage();
|
|
assertNotNull( message, "Response provided");
|
|
JsonNode data = message.path(JSON.DATA);
|
|
assertTrue( message.isObject(), "Response is an object");
|
|
assertEquals( JsonException.ERROR, message.path(JSON.TYPE).asText(),
|
|
"Response is an error");
|
|
assertEquals( 500, data.path(JsonException.CODE).asInt(), "Response contains error code 500");
|
|
assertEquals( "Unsupported operation attempted: null.",
|
|
data.path(JsonException.MESSAGE).asText(),
|
|
"Response contains error message");
|
|
JUnitAppender.assertWarnMessage("Unsupported operation attempted {\"type\":\"test\",\"data\":{\"throws\":\"JmriException\"}}");
|
|
}
|
|
|
|
/**
|
|
* Test of onMessage method with valid messages containing the get method.
|
|
*
|
|
* @throws java.io.IOException if unable to pass messages
|
|
*/
|
|
@Test
|
|
public void testOnMessage_JsonNode_Method_get() throws IOException {
|
|
testOnMessage_JsonNode_Method_get("{\"type\":\"test\",\"data\":{\"name\":\"test\"},\"method\":\"get\"}");
|
|
testOnMessage_JsonNode_Method_get("{\"type\":\"test\",\"data\":{\"name\":\"test\",\"method\":\"get\"}}");
|
|
}
|
|
|
|
private void testOnMessage_JsonNode_Method_get(String message) throws IOException {
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
JsonClientHandler instance = new TestJsonClientHandler(connection);
|
|
JsonNode node = connection.getObjectMapper().readTree(message);
|
|
instance.onMessage(node);
|
|
JsonNode root = connection.getMessage();
|
|
assertNotNull( root, "Response provided");
|
|
JsonNode data = root.path(JSON.DATA);
|
|
assertTrue( root.isObject(), "Response is an object");
|
|
assertEquals( 2, root.size(), "Response object contains two elements");
|
|
assertEquals( TEST, root.path(JSON.TYPE).asText(), "Response object type is test");
|
|
assertEquals( TEST, data.path(JSON.NAME).asText(), "Response object data name is test");
|
|
assertEquals( 1, data.size(), "Response object data size is 1");
|
|
}
|
|
|
|
@Test
|
|
public void testOnMessage_JsonNode_Method_get_exception() throws IOException {
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
JsonClientHandler instance = new TestJsonClientHandler(connection);
|
|
JsonNode node = connection.getObjectMapper()
|
|
.readTree("{\"type\":\"test\",\"data\":{\"name\":\"JsonException\"},\"method\":\"get\"}");
|
|
instance.onMessage(node);
|
|
JsonNode root = connection.getMessage();
|
|
assertNotNull( root, "Response provided");
|
|
JsonNode data = root.path(JSON.DATA);
|
|
assertTrue( root.isObject(), "Error response is an object");
|
|
assertEquals( JsonException.ERROR, root.path(JSON.TYPE).asText(), "Error response is an ERROR");
|
|
assertEquals( 499, data.path(JsonException.CODE).asInt(), "Error response is type 499");
|
|
}
|
|
|
|
@Test
|
|
public void testOnMessage_JsonNode_Method_Goodbye() throws IOException {
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
JsonClientHandler instance = new TestJsonClientHandler(connection);
|
|
JsonNode node = connection.getObjectMapper().readTree("{\"type\":\"goodbye\"}");
|
|
assertTrue(connection.isOpen());
|
|
instance.onMessage(node);
|
|
JsonNode root = connection.getMessage();
|
|
assertNotNull( root, "Response provided");
|
|
JsonNode data = root.path(JSON.DATA);
|
|
assertTrue( root.isObject(), "Response is an object");
|
|
assertEquals( JSON.GOODBYE, root.path(JSON.TYPE).asText(), "Response is a Goodbye message");
|
|
assertTrue(data.isMissingNode());
|
|
assertFalse(connection.isOpen());
|
|
}
|
|
|
|
/**
|
|
* Test all methods except {@code get} and {@code list} for missing data
|
|
* node. The {@code get} and {@code list} methods are not required to have a
|
|
* data node by the JsonClientHandler.
|
|
*
|
|
* @throws IOException if an unexpected IOException occurs
|
|
*/
|
|
@Test
|
|
public void testOnMessage_JsonNode_missing_data() throws IOException {
|
|
testOnMessage_JsonNode_missing_data("{\"type\":\"test\",\"method\":\"post\"}");
|
|
testOnMessage_JsonNode_missing_data("{\"type\":\"test\",\"method\":\"put\"}");
|
|
testOnMessage_JsonNode_missing_data("{\"type\":\"test\",\"method\":\"delete\"}");
|
|
}
|
|
|
|
private void testOnMessage_JsonNode_missing_data(String message) throws IOException {
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
JsonClientHandler instance = new TestJsonClientHandler(connection);
|
|
JsonNode node = connection.getObjectMapper().readTree(message);
|
|
instance.onMessage(node);
|
|
JsonNode root = connection.getMessage();
|
|
assertNotNull( root, "Response provided");
|
|
JsonNode data = root.path(JSON.DATA);
|
|
assertTrue( root.isObject(), "Error response is an object");
|
|
assertEquals( JsonException.ERROR, root.path(JSON.TYPE).asText(), "Error response is an ERROR");
|
|
assertEquals( 400, data.path(JsonException.CODE).asInt(), "Error response is type 400");
|
|
}
|
|
|
|
/**
|
|
* Test that locale on connection is set by handler.
|
|
*
|
|
* @throws IOException if unable to pass message
|
|
*/
|
|
@Test
|
|
public void testSetLocale() throws IOException {
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
JsonClientHandler instance = new TestJsonClientHandler(connection);
|
|
connection.setLocale(Locale.ITALY);
|
|
assertEquals( Locale.ITALY, connection.getLocale(), "Connection is IT Italian");
|
|
instance.onMessage("{\"type\":\"locale\", \"data\":{\"locale\":\"en-US\"}}");
|
|
assertEquals( Locale.US, connection.getLocale(), "Connection is US English");
|
|
}
|
|
|
|
/**
|
|
* Test that invalid versions are handled correctly.
|
|
*/
|
|
@Test
|
|
public void testSetInvalidVersion() {
|
|
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
|
|
connection.setVersion("v4"); // not valid
|
|
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
|
() -> {
|
|
var result = new TestJsonClientHandler(connection).getServices();
|
|
fail("should not get to here, created " + result);
|
|
});
|
|
assertNotNull(ex);
|
|
JUnitAppender.assertErrorMessage("Unable to create handler for version v4");
|
|
}
|
|
|
|
private static class TestJsonClientHandler extends JsonClientHandler {
|
|
|
|
TestJsonClientHandler(JsonConnection connection) {
|
|
super(connection);
|
|
}
|
|
|
|
TestJsonClientHandler() {
|
|
this(new JsonMockConnection((DataOutputStream) null));
|
|
}
|
|
|
|
@Override
|
|
public HashMap<String, HashSet<JsonSocketService<?>>> getServices() {
|
|
return super.getServices();
|
|
}
|
|
}
|
|
}
|