Files
JIMRI/java/test/jmri/server/json/time/JsonTimeSocketServiceTest.java
T
2026-06-17 14:00:51 +02:00

193 lines
9.4 KiB
Java

package jmri.server.json.time;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Locale;
import jmri.InstanceManager;
import jmri.JmriException;
import jmri.Timebase;
import jmri.server.json.JSON;
import jmri.server.json.JsonException;
import jmri.server.json.JsonMockConnection;
import jmri.server.json.JsonRequest;
import jmri.util.JUnitAppender;
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.assertThrows;
public class JsonTimeSocketServiceTest {
private final Locale locale = Locale.ENGLISH;
@Test
public void testOnMessage() throws IOException, JmriException, JsonException {
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
JsonTimeSocketService service = new JsonTimeSocketService(connection);
Timebase manager = InstanceManager.getDefault(Timebase.class);
TimebaseTimeListener listener = new TimebaseTimeListener();
Assertions.assertEquals(0, listener.getTime().getTime());
StdDateFormat formatter = new StdDateFormat();
int rate = 60; // rate is one minute every six seconds
assertEquals( 0, manager.getPropertyChangeListeners().length, "No change listeners");
manager.setRun(false); // stop for testing
// GET method
service.onMessage(JSON.TIME, connection.getObjectMapper().createObjectNode(),
new JsonRequest(locale, JSON.V5, JSON.GET, 42));
JsonNode message = connection.getMessage();
Date current = manager.getTime();
assertNotNull( message, "Message is not null");
assertEquals( 1.0, message.path(JSON.DATA).path(JSON.RATE).asDouble(), 0.0, "Rate is realtime");
assertEquals( JSON.OFF, message.path(JSON.DATA).path(JSON.STATE).asInt(), "Timebase is on");
assertEquals( formatter.format(current),
message.findPath(JSON.DATA).path(JSON.TIME).asText(), "Time is correct");
assertEquals( 1, manager.getPropertyChangeListeners().length, "Service is listening to changes");
// Add second listener
manager.addPropertyChangeListener("time", listener);
// POST method
ObjectNode data = connection.getObjectMapper().createObjectNode();
data.put(JSON.RATE, rate); // integer
data.put(JSON.STATE, JSON.ON); // start the fast clock -- to test that listeners set in onMessage work
service.onMessage(JSON.TIME, data, new JsonRequest(locale, JSON.V5, JSON.POST, 42));
message = connection.getMessage();
current = manager.getTime(); // time before fast clock starts
assertNotNull( message, "Message is not null");
assertEquals( rate, message.path(JSON.DATA).path(JSON.RATE).asDouble(), 0.0, "Rate is fast");
assertEquals( JSON.ON, message.path(JSON.DATA).path(JSON.STATE).asInt(), "Timebase is on");
// a timing issue can cause the message turning the fast clock on to
// not get the time at start of running, so don't test that
assertEquals( 2, manager.getPropertyChangeListeners().length, "Service and listener are listening to changes");
Date waitFor = current;
JUnitUtil.waitFor(() -> {
return !manager.getTime().equals(waitFor);
},"current time not different to manager getTime");
// current = listener.getTime(); // get time from listener
message = connection.getMessage();
assertNotNull( message, "Message is not null");
assertEquals( rate, message.path(JSON.DATA).path(JSON.RATE).asDouble(), 0.0, "Rate is fast");
assertEquals( JSON.ON, message.path(JSON.DATA).path(JSON.STATE).asInt(), "Timebase is on");
data.put(JSON.STATE, JSON.OFF); // stop the fast clock
service.onMessage(JSON.TIME, data, new JsonRequest(Locale.ENGLISH, JSON.V5, JSON.POST, 42));
current = manager.getTime();
message = connection.getMessage();
assertNotNull( message, "Message is not null");
assertEquals( rate, message.path(JSON.DATA).path(JSON.RATE).asDouble(), 0.0, "Rate is fast");
assertEquals( JSON.OFF, message.path(JSON.DATA).path(JSON.STATE).asInt(), "Timebase is off");
// a failure on next line indicates JsonTimeHttpService has been changed to send message to client
// before posting changes instead of after posting changes and that change should be undone
assertEquals( formatter.format(current),
message.path(JSON.DATA).path(JSON.TIME).asText(), "Time is current");
// POST unreasonable rate
data.put(JSON.RATE, 123456.789); // double so that both integers and doubles are tested
JsonException ex = assertThrows( JsonException.class, () ->
service.onMessage(JSON.TIME, data,
new JsonRequest(locale, JSON.V5, JSON.POST, 42)),
"Expected exception not thrown");
assertEquals( 400, ex.getCode(), "HTTP Invalid Request");
assertEquals( "Error setting rate.", ex.getMessage(), "Error message");
JUnitAppender.assertErrorMessage("rate of 123456.789 is out of reasonable range 0.1 - 100.0");
// POST bad time
data.put(JSON.RATE, 100); // set rate to max valid rate
data.put(JSON.TIME, "this is not a time");
ex = assertThrows( JsonException.class, () ->
service.onMessage(JSON.TIME, data, new JsonRequest(locale, JSON.V5, JSON.POST, 42)),
"Expected exception not thrown");
assertEquals( 400, ex.getCode(), "HTTP Invalid Request");
assertEquals( "Time not in ISO 8601 format.", ex.getMessage(), "Error message");
// POST good time
data.put(JSON.TIME, formatter.format(waitFor));
service.onMessage(JSON.TIME, data, new JsonRequest(locale, JSON.V5, JSON.POST, 42));
message = connection.getMessage();
current = manager.getTime();
assertNotNull( message, "Message is not null");
assertEquals( 100, message.path(JSON.DATA).path(JSON.RATE).asDouble(), 0.0, "Rate is fast");
assertEquals( JSON.OFF, message.path(JSON.DATA).path(JSON.STATE).asInt(), "Timebase is off");
assertEquals( formatter.format(current),
message.path(JSON.DATA).path(JSON.TIME).asText(), "Time is current");
service.onClose(); // clean up listeners
manager.removePropertyChangeListener("time", listener);
assertEquals( 0, manager.getPropertyChangeListeners().length, "Service is not listening to changes");
}
@Test
public void testOnList() {
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
JsonTimeSocketService service = new JsonTimeSocketService(connection);
JsonException ex = assertThrows( JsonException.class, () ->
service.onList(JSON.TIME, connection.getObjectMapper().createObjectNode(),
new JsonRequest(locale, JSON.V5, JSON.GET, 42)),
"Expected exception not thrown");
assertEquals( 400, ex.getCode(), "Code is HTTP BAD REQUEST");
assertEquals( "time cannot be listed.", ex.getMessage(), "Message is unlistable");
}
/**
* Test that listener handles error states correctly.
*
* @throws JsonException if unexpected exception occurs
* @throws JmriException if unexpected exception occurs
* @throws IOException if unexpected exception occurs
*/
@Test
public void testListenerErrorHandling() throws IOException, JmriException, JsonException {
JsonMockConnection connection = new JsonMockConnection((DataOutputStream) null);
JsonTimeHttpService http = new JsonTimeHttpService(connection.getObjectMapper());
JsonTimeSocketService service = new JsonTimeSocketService(connection, http);
Timebase manager = InstanceManager.getDefault(Timebase.class);
manager.setRun(false); // stop for testing
// GET method
service.onMessage(JSON.TIME, connection.getObjectMapper().createObjectNode(),
new JsonRequest(locale, JSON.V5, JSON.GET, 42));
// We should be listening so make a change
manager.setRate(60); // one minute per second
// Thrown IOException on next message
connection.setThrowIOException(true);
int size = connection.getMessages().size();
manager.setRate(10);
// The deliberately thrown IOException should have been caught and discarded
assertEquals( size, connection.getMessages().size(), "message not sent sfter throwing exception");
}
@BeforeEach
public void setUp() {
JUnitUtil.setUp();
}
@AfterEach
public void tearDown() {
JUnitUtil.tearDown();
}
private static class TimebaseTimeListener implements PropertyChangeListener {
private Date time = new Date();
private TimebaseTimeListener() {
time.setTime(0);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("time")) {
this.time = (Date) evt.getNewValue();
}
}
public Date getTime() {
return new Date(this.time.getTime());
}
}
}