Files
2026-06-17 14:00:51 +02:00

476 lines
22 KiB
Java

package jmri.web.servlet.json;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import javax.servlet.ServletException;
import jmri.InstanceManager;
import jmri.JmriException;
import jmri.PowerManager;
import jmri.Sensor;
import jmri.SensorManager;
import jmri.jmrix.internal.InternalSystemConnectionMemo;
import jmri.managers.DefaultPowerManager;
import jmri.server.json.JSON;
import jmri.server.json.JsonException;
import jmri.server.json.JsonServerPreferences;
import jmri.server.json.power.JsonPowerServiceFactory;
import jmri.util.JUnitAppender;
import jmri.util.JUnitUtil;
import jmri.util.web.MockServletExchange;
import org.junit.jupiter.api.*;
import javax.servlet.http.HttpServletResponse;
import jmri.web.servlet.ServletUtil;
import static jmri.util.web.MockServletExchange.DELETE;
import static jmri.util.web.MockServletExchange.GET;
import static jmri.util.web.MockServletExchange.POST;
import static jmri.util.web.MockServletExchange.PUT;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Tests for the jmri.web.servlet.json.JsonServlet class
*
* @author Paul Bender Copyright (C) 2012, 2016
* @author Randall Wood 2018
*/
public class JsonServletTest {
@Test
public void testCtor() {
JsonServlet a = new JsonServlet();
assertNotNull(a);
}
/**
* Test doGet() with a passed in expected result.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
*/
@Test
public void testDoGetResult() throws IOException, ServletException {
JsonServlet instance = new MockJsonServlet();
ObjectNode result = new ObjectMapper().createObjectNode();
// test a schema valid message with validation on
result.put("type", "pong");
MockServletExchange validCtx = getJsonMockServletExchange(GET, "/json/test")
.withAttribute("result", result);
instance.init(validCtx.getConfig());
instance.doGet(validCtx.getRequest(), validCtx.getResponse());
assertEquals( HttpServletResponse.SC_OK, validCtx.getResponseStatus(), "HTTP OK");
assertEquals( result.toString(), validCtx.getResponseContentAsString(), "Contains result");
// test a schema invalid message with validation on
result.put("type", "invalid-type");
result.put("type", "invalid-type");
MockServletExchange invalidCtx = getJsonMockServletExchange("GET", "/json/test")
.withAttribute("result", result);
instance.doGet(invalidCtx.getRequest(), invalidCtx.getResponse());
assertEquals( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, invalidCtx.getResponseStatus(), "HTTP Internal Error");
assertNotEquals( result.toString(), invalidCtx.getResponseContentAsString(), "Does not contain result");
JUnitAppender.assertWarnMessage("Errors validating {\"type\":\"invalid-type\"}");
JUnitAppender.assertWarnMessageStartingWith("JSON Validation Error: 1028");
JUnitAppender.assertWarnMessageStartingWith("JSON Validation Error: 1008");
// As of 1.0.* this was code 1003 but by 1.3.3 it is 1029 -
JUnitAppender.assertWarnMessageStartingWith("JSON Validation Error: 1029");
// test a schema invalid message with validation off
InstanceManager.getDefault(JsonServerPreferences.class).setValidateServerMessages(false);
result.put("type", "invalid-type");
MockServletExchange valOffCtx = getJsonMockServletExchange(GET, "/json/test")
.withAttribute("result", result);
instance.doGet(valOffCtx.getRequest(), valOffCtx.getResponse());
assertEquals( HttpServletResponse.SC_OK, valOffCtx.getResponseStatus(), "HTTP OK");
assertEquals( result.toString(), valOffCtx.getResponseContentAsString(), "Contains result");
}
/**
* Test doGet() with a passed in expected result and a good ID parameter.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
*/
@Test
public void testDoGetPowerWithGoodId() throws IOException, ServletException {
InstanceManager.setDefault(PowerManager.class, new DefaultPowerManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class)));
MockServletExchange ctx = getJsonMockServletExchange(GET, "/json/power")
.withParameter("id", "42");
JsonServlet instance = new MockJsonServlet();
instance.init(ctx.getConfig());
// test a schema valid message with validation on
instance.doGet(ctx.getRequest(), ctx.getResponse());
assertEquals( HttpServletResponse.SC_OK, ctx.getResponseStatus(), "HTTP OK");
JsonNode node = new ObjectMapper().readTree(ctx.getResponseContentAsString());
assertTrue( node.isObject(), "Node is object");
assertEquals( JSON.LIST, node.path(JSON.TYPE).asText(), "Node type is list");
assertTrue( node.path(JSON.DATA).isArray(), "Node data is array");
assertEquals( 42, node.path(JSON.ID).asInt(), "Node ID is 42");
assertEquals( 1, node.path(JSON.DATA).size(), "Node data has 1 entry");
}
/**
* Test doGet() with a passed in expected result and a bad ID parameter.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
*/
@Test
public void testDoGetPowerWithBadId() throws IOException, ServletException {
InstanceManager.setDefault(PowerManager.class, new DefaultPowerManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class)));
// test a schema valid message with validation on
MockServletExchange ctx = getJsonMockServletExchange(GET, "/json/power")
.withParameter("id", "bad-parameter");
JsonServlet instance = new MockJsonServlet();
instance.init(ctx.getConfig());
instance.doGet(ctx.getRequest(), ctx.getResponse());
assertEquals( HttpServletResponse.SC_OK, ctx.getResponseStatus(), "HTTP OK");
JsonNode node = new ObjectMapper().readTree(ctx.getResponseContentAsString());
assertTrue( node.isArray(), "Node is array");
assertEquals( 1, node.size(), "Array has 1 entry");
JUnitAppender.assertErrorMessage("Unable to parse JSON {\"id\":bad-parameter}");
}
/**
* Test doGet() with a request for power status with no version specified.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
*/
@Test
public void testDoGetPowerNoVersion() throws IOException, ServletException {
InstanceManager.setDefault(PowerManager.class, new DefaultPowerManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class)));
MockServletExchange ctx = getJsonMockServletExchange(GET, "/json/power");
JsonServlet instance = new MockJsonServlet();
instance.init(ctx.getConfig());
// test a schema valid message with validation on
instance.doGet(ctx.getRequest(), ctx.getResponse());
assertEquals( HttpServletResponse.SC_OK, ctx.getResponseStatus(), "HTTP OK");
JsonNode node = new ObjectMapper().readTree(ctx.getResponseContentAsString());
assertTrue( node.isArray(), "Node is array");
assertEquals( 1, node.size(), "Array has 1 entry");
}
/**
* Test doGet() with a request for power status with version 5 specified.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
*/
@Test
public void testDoGetPowerV5() throws IOException, ServletException {
InstanceManager.setDefault(PowerManager.class, new DefaultPowerManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class)));
MockServletExchange ctx = getJsonMockServletExchange(GET, "/json/v5/power");
JsonServlet instance = new MockJsonServlet();
instance.init(ctx.getConfig());
// test a schema valid message with validation on
instance.doGet(ctx.getRequest(), ctx.getResponse());
assertEquals( HttpServletResponse.SC_OK, ctx.getResponseStatus(), "HTTP OK");
JsonNode node = new ObjectMapper().readTree(ctx.getResponseContentAsString());
assertTrue( node.isArray(), "Node is array");
assertEquals( 1, node.size(), "Array has 1 entry");
}
/**
* Test doGet() with a request for power status with version 4 (not allowed) specified.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
*/
@Test
public void testDoGetPowerV4() throws IOException, ServletException {
InstanceManager.setDefault(PowerManager.class, new DefaultPowerManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class)));
MockServletExchange ctx = getJsonMockServletExchange(GET, "/json/v4/power");
JsonServlet instance = new MockJsonServlet();
instance.init(ctx.getConfig());
// test a schema valid message with validation on
instance.doGet(ctx.getRequest(), ctx.getResponse());
assertEquals(HttpServletResponse.SC_NOT_FOUND, ctx.getResponseStatus(), "HTTP Not Found");
JsonNode node = new ObjectMapper().readTree(ctx.getResponseContentAsString());
assertTrue( node.isObject(), "Node is object");
assertEquals( JsonException.ERROR, node.path(JSON.TYPE).asText(), "Node is error");
JUnitAppender.assertWarnMessage("Requested type 'v4' unknown.");
}
/**
* Test doPost() with a request for power status with version 5 specified.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
* @throws JmriException unexpected failure in test context
*/
@Test
public void testDoPostPowerV5Content() throws IOException, ServletException, JmriException {
InstanceManager.setDefault(PowerManager.class, new DefaultPowerManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class)));
assertEquals(PowerManager.UNKNOWN, InstanceManager.getDefault(PowerManager.class).getPower());
MockServletExchange ctx = getJsonMockServletExchange(POST, "/json/v5/power")
.withBody("{\"state\":4}");
JsonServlet instance = new MockJsonServlet();
instance.init(ctx.getConfig());
// test a schema valid message with validation on
instance.doPost(ctx.getRequest(), ctx.getResponse());
assertEquals(HttpServletResponse.SC_OK, ctx.getResponseStatus(), "HTTP OK");
JsonNode node = new ObjectMapper().readTree(ctx.getResponseContentAsString());
assertTrue(node.isObject(), "Node is object");
assertEquals(JsonPowerServiceFactory.POWER, node.path(JSON.TYPE).asText(), "Object is power");
assertEquals(PowerManager.OFF, InstanceManager.getDefault(PowerManager.class).getPower());
}
/**
* Test doPost() with a request for power status with version 5 specified.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
* @throws JmriException unexpected failure in test context
*/
@Test
public void testDoPostPowerV5Parameters() throws IOException, ServletException, JmriException {
InstanceManager.setDefault(PowerManager.class, new DefaultPowerManager(InstanceManager.getDefault(InternalSystemConnectionMemo.class)));
assertEquals(PowerManager.UNKNOWN, InstanceManager.getDefault(PowerManager.class).getPower());
MockServletExchange ctx = getJsonMockServletExchange(POST, "/json/v5/power")
// content type must not be JSON to use parameters
.withRequestContentType("")
.withParameter(JSON.STATE, "4");
JsonServlet instance = new MockJsonServlet();
instance.init(ctx.getConfig());
// test a schema valid message with validation on
instance.doPost(ctx.getRequest(), ctx.getResponse());
assertEquals(HttpServletResponse.SC_OK, ctx.getResponseStatus(), "HTTP OK");
JsonNode node = new ObjectMapper().readTree(ctx.getResponseContentAsString());
assertTrue( node.isObject(), "Node is object");
assertEquals( JsonPowerServiceFactory.POWER, node.path(JSON.TYPE).asText(), "Object is power");
assertEquals(PowerManager.OFF, InstanceManager.getDefault(PowerManager.class).getPower());
}
/**
* Test doGet() with a request for nothing with version 5 specified.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
*/
@Test
public void testGetV5noParameters() throws ServletException, IOException {
MockServletExchange ctx = getJsonMockServletExchange(GET, "/json/v5");
JsonServlet instance = new MockJsonServlet();
instance.init(ctx.getConfig());
// test a schema valid message with validation on
instance.doGet(ctx.getRequest(), ctx.getResponse());
assertEquals(HttpServletResponse.SC_OK, ctx.getResponseStatus(), "HTTP OK");
assertEquals("text/html; charset=utf-8", ctx.getResponseContentType(), "Content type is HTML");
}
/**
* Test doGet() with a request for two sensors with version 5 specified.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
*/
@Test
public void testGetTwoSensorsV5() throws ServletException, IOException {
SensorManager manager = InstanceManager.getDefault(SensorManager.class);
manager.provide("IS1");
manager.provide("IS2");
JsonServlet instance = new MockJsonServlet();
ObjectMapper mapper = new ObjectMapper();
// request list of sensors
MockServletExchange listCtx = getJsonMockServletExchange(GET, "/json/v5/sensor");
instance.init(listCtx.getConfig());
instance.doGet(listCtx.getRequest(), listCtx.getResponse());
assertEquals(HttpServletResponse.SC_OK, listCtx.getResponseStatus(), "HTTP OK");
JsonNode node = mapper.readTree(listCtx.getResponseContentAsString());
assertTrue( node.isArray(), "Node is array");
assertEquals( 2, node.size(), "Array has 2 entries");
assertEquals( "IS1", node.path(0).path(JSON.DATA).path(JSON.NAME).asText(), "Sensor 1");
assertEquals( "IS2", node.path(1).path(JSON.DATA).path(JSON.NAME).asText(), "Sensor 2");
// request sensor IS1
MockServletExchange getOneCtx = getJsonMockServletExchange("GET", "/json/v5/sensor/IS1");
instance.doGet(getOneCtx.getRequest(), getOneCtx.getResponse());
assertEquals(HttpServletResponse.SC_OK, getOneCtx.getResponseStatus(), "HTTP OK");
node = mapper.readTree(getOneCtx.getResponseContentAsString());
assertTrue( node.isObject(), "Node is object");
assertEquals( 2, node.size(), "Node has 2 entries");
assertEquals( "IS1", node.path(JSON.DATA).path(JSON.NAME).asText(), "Sensor 1");
// request sensor IS3 (does not exist)
MockServletExchange getMissingCtx = getJsonMockServletExchange("GET", "/json/v5/sensor/IS3");
instance.doGet(getMissingCtx.getRequest(), getMissingCtx.getResponse());
assertEquals(HttpServletResponse.SC_NOT_FOUND, getMissingCtx.getResponseStatus(), "HTTP Not Found");
node = mapper.readTree(getMissingCtx.getResponseContentAsString());
assertTrue( node.isObject(), "Node is object");
assertEquals( JsonException.ERROR, node.path(JSON.TYPE).asText(), "Node is error");
}
/**
* Test creating, changing, and deleting a sensor with version 5 specified.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
*/
@Test
public void testGetCreateAndDeleteSensorV5() throws ServletException, IOException {
SensorManager manager = InstanceManager.getDefault(SensorManager.class);
JsonServlet instance = new MockJsonServlet();
ObjectMapper mapper = new ObjectMapper();
// create sensor IS3
assertNull(manager.getBySystemName("IS3"), "Sensor does not exist");
MockServletExchange putCtx = getJsonMockServletExchange(PUT, "/json/v5/sensor")
.withBody("{\"name\":\"IS3\"}");
instance.init(putCtx.getConfig());
instance.doPut(putCtx.getRequest(), putCtx.getResponse());
assertEquals(HttpServletResponse.SC_OK, putCtx.getResponseStatus(), "HTTP OK");
JsonNode node = mapper.readTree(putCtx.getResponseContentAsString());
assertTrue( node.isObject(), "Node is object");
assertEquals( 2, node.size(), "Object has 2 parameters");
assertEquals( "IS3", node.path(JSON.DATA).path(JSON.NAME).asText(), "Sensor 3");
assertEquals( 0, node.path(JSON.DATA).path(JSON.STATE).asInt(), "Unknown state");
Sensor is3 = manager.getBySystemName("IS3");
assertNotNull(is3, "Sensor exists");
assertEquals(Sensor.UNKNOWN, is3.getCommandedState(), "Sensor state unknown");
// modify sensor IS3
MockServletExchange postCtx = getJsonMockServletExchange(POST, "/json/v5/sensor/IS3")
.withBody("{\"state\":4}");
instance.doPost(postCtx.getRequest(), postCtx.getResponse());
assertEquals(HttpServletResponse.SC_OK, postCtx.getResponseStatus(), "HTTP OK");
node = mapper.readTree(postCtx.getResponseContentAsString());
assertTrue( node.isObject(), "Node is object");
assertEquals( 2, node.size(), "Object has 2 parameters");
assertEquals( "IS3", node.path(JSON.DATA).path(JSON.NAME).asText(), "Sensor 3");
assertEquals( 4, node.path(JSON.DATA).path(JSON.STATE).asInt(), "Thrown state");
assertEquals(Sensor.INACTIVE, is3.getCommandedState(), "Sensor state changed");
// delete sensor IS3
MockServletExchange deleteCtx = getJsonMockServletExchange(DELETE, "/json/v5/sensor/IS3");
instance.doDelete(deleteCtx.getRequest(), deleteCtx.getResponse());
assertEquals(HttpServletResponse.SC_OK, deleteCtx.getResponseStatus(), "HTTP OK");
assertEquals(0, deleteCtx.getResponseContentAsString().length(), "Response body should be empty");
assertNull( manager.getBySystemName("IS3"), "No Sensor");
}
/**
* Test getting, changing, and deleting an unknown type with version 5 specified.
*
* @throws java.io.IOException unexpected failure in test context
* @throws javax.servlet.ServletException unexpected failure in test context
*/
@Test
public void testV5ManipulateUnknownType() throws ServletException, IOException {
ObjectMapper mapper = new ObjectMapper();
String uri = "/json/v5/invalid-type/invalid-name";
// GET unknown type
MockServletExchange getCtx = getJsonMockServletExchange(GET, uri)
.withRequestContentType("");
JsonServlet instance = new MockJsonServlet();
instance.init(getCtx.getConfig());
instance.doGet(getCtx.getRequest(), getCtx.getResponse());
assertEquals(HttpServletResponse.SC_NOT_FOUND, getCtx.getResponseStatus(), "HTTP Not Found");
JsonNode node = mapper.readTree(getCtx.getResponseContentAsString());
assertTrue( node.isObject(), "Node is object");
assertEquals( JsonException.ERROR, node.path(JSON.TYPE).asText(), "Node is error");
JUnitAppender.assertWarnMessage("Requested type 'invalid-type' unknown.");
// POST unknown type
MockServletExchange postCtx = getJsonMockServletExchange(POST, uri)
.withRequestContentType("");
instance.doPost(postCtx.getRequest(), postCtx.getResponse());
assertEquals(HttpServletResponse.SC_NOT_FOUND, postCtx.getResponseStatus(), "HTTP Not Found");
node = mapper.readTree(postCtx.getResponseContentAsString());
assertTrue( node.isObject(), "Node is object");
assertEquals( JsonException.ERROR, node.path(JSON.TYPE).asText(), "Node is error");
JUnitAppender.assertWarnMessage("Requested type 'invalid-type' unknown.");
// DELETE unknown type
MockServletExchange deleteCtx = getJsonMockServletExchange(DELETE, uri)
.withRequestContentType("");
instance.doDelete(deleteCtx.getRequest(), deleteCtx.getResponse());
assertEquals(HttpServletResponse.SC_NOT_FOUND, deleteCtx.getResponseStatus(), "HTTP Not Found");
node = mapper.readTree(deleteCtx.getResponseContentAsString());
assertTrue( node.isObject(), "Node is object");
assertEquals( JsonException.ERROR, node.path(JSON.TYPE).asText(), "Node is error");
JUnitAppender.assertWarnMessage("Requested type 'invalid-type' unknown.");
}
@BeforeEach
public void setUp() {
JUnitUtil.setUp();
JUnitUtil.resetProfileManager();
JUnitUtil.initInternalSensorManager();
InstanceManager.getDefault(JsonServerPreferences.class).setValidateServerMessages(true);
}
@AfterEach
public void tearDown() {
JUnitUtil.tearDown();
}
private static class MockJsonServlet extends JsonServlet {
@Override
void superInit() {
// do nothing
}
}
private MockServletExchange getJsonMockServletExchange(String method, String uri) {
return new MockServletExchange(method, uri)
.withRequestContentType(ServletUtil.APPLICATION_JSON)
.withContextPath("/json");
}
}