1919package de .rwth .idsg .steve .utils ;
2020
2121import com .fasterxml .jackson .core .JsonParser ;
22+ import com .fasterxml .jackson .core .JsonProcessingException ;
2223import com .fasterxml .jackson .core .TreeNode ;
2324import com .fasterxml .jackson .databind .JsonNode ;
2425import com .fasterxml .jackson .databind .ObjectMapper ;
26+ import com .fasterxml .jackson .databind .node .ArrayNode ;
27+ import com .fasterxml .jackson .databind .node .JsonNodeFactory ;
28+ import com .fasterxml .jackson .databind .node .NullNode ;
29+ import com .fasterxml .jackson .databind .node .ObjectNode ;
2530import de .rwth .idsg .ocpp .jaxb .RequestType ;
2631import de .rwth .idsg .ocpp .jaxb .ResponseType ;
2732import de .rwth .idsg .steve .SteveException ;
3641import de .rwth .idsg .steve .ocpp .ws .data .OcppJsonResponse ;
3742import de .rwth .idsg .steve .ocpp .ws .data .OcppJsonResult ;
3843import de .rwth .idsg .steve .ocpp .ws .pipeline .Serializer ;
44+ import lombok .Getter ;
45+ import lombok .Setter ;
46+ import lombok .Value ;
3947import lombok .extern .slf4j .Slf4j ;
4048import org .eclipse .jetty .websocket .api .Session ;
4149import org .eclipse .jetty .websocket .api .StatusCode ;
5058import java .io .IOException ;
5159import java .net .URI ;
5260import java .util .ArrayList ;
61+ import java .util .HashMap ;
5362import java .util .LinkedHashMap ;
5463import java .util .Map ;
64+ import java .util .Objects ;
5565import java .util .UUID ;
5666import java .util .concurrent .CountDownLatch ;
5767import java .util .concurrent .Future ;
@@ -69,6 +79,7 @@ public class OcppJsonChargePoint {
6979 private final String chargeBoxId ;
7080 private final String connectionPath ;
7181 private final Map <String , ResponseContext > responseContextMap ;
82+ private final Map <String , RequestContext > requestContextMap ;
7283 private final MessageDeserializer deserializer ;
7384 private final WebSocketClient client ;
7485 private final CountDownLatch closeHappenedSignal ;
@@ -88,6 +99,7 @@ public OcppJsonChargePoint(String ocppVersion, String chargeBoxId, String pathPr
8899 this .chargeBoxId = chargeBoxId ;
89100 this .connectionPath = pathPrefix + chargeBoxId ;
90101 this .responseContextMap = new LinkedHashMap <>(); // because we want to keep the insertion order of test cases
102+ this .requestContextMap = new HashMap <>();
91103 this .deserializer = new MessageDeserializer ();
92104 this .client = new WebSocketClient ();
93105 this .closeHappenedSignal = new CountDownLatch (1 );
@@ -121,6 +133,8 @@ public void onMessage(Session session, String msg) {
121133 } else if (ocppMsg instanceof OcppJsonError ) {
122134 ResponseContext ctx = responseContextMap .remove (ocppMsg .getMessageId ());
123135 ctx .errorHandler .accept ((OcppJsonError ) ocppMsg );
136+ } else if (ocppMsg instanceof OcppJsonCallForTesting ) {
137+ handleCall ((OcppJsonCallForTesting ) ocppMsg );
124138 }
125139 } catch (Exception e ) {
126140 log .error ("Exception" , e );
@@ -171,9 +185,25 @@ public <T extends ResponseType> void prepare(RequestType payload, String action,
171185 responseContextMap .put (messageId , resCtx );
172186 }
173187
188+ public void expectRequest (RequestType expectedRequest , ResponseType plannedResponse ) {
189+ String requestPayload ;
190+ JsonNode responsePayload ;
191+ try {
192+ ObjectMapper mapper = JsonObjectMapper .INSTANCE .getMapper ();
193+ requestPayload = mapper .writeValueAsString (expectedRequest );
194+ responsePayload = mapper .valueToTree (plannedResponse );
195+ } catch (JsonProcessingException e ) {
196+ throw new RuntimeException (e );
197+ }
198+
199+ String action = getOperationName (expectedRequest );
200+ requestContextMap .put (action , new RequestContext (requestPayload , responsePayload ));
201+ }
202+
174203 public void process () {
204+ int requestCount = requestContextMap .values ().size ();
175205 int responseCount = responseContextMap .values ().size ();
176- receivedMessagesSignal = new CountDownLatch (responseCount );
206+ receivedMessagesSignal = new CountDownLatch (requestCount + responseCount );
177207
178208 // copy the values in a new list to be iterated over, because otherwise we get a ConcurrentModificationException,
179209 // since the onMessage(..) uses the same responseContextMap to remove an item while looping over its items here.
@@ -231,6 +261,27 @@ private static String getOperationName(RequestType requestType) {
231261 return s ;
232262 }
233263
264+ private void handleCall (OcppJsonCallForTesting call ) {
265+ try {
266+ ArrayNode node = JsonObjectMapper .INSTANCE .getMapper ()
267+ .createArrayNode ()
268+ .add (MessageType .CALL_RESULT .getTypeNr ())
269+ .add (call .getMessageId ())
270+ .add (call .getContext ().getResponsePayload ());
271+
272+ String str = JsonObjectMapper .INSTANCE .getMapper ().writeValueAsString (node );
273+ session .getRemote ().sendString (str );
274+ } catch (Exception e ) {
275+ throw new RuntimeException (e );
276+ }
277+ }
278+
279+ @ Value
280+ private static class RequestContext {
281+ String requestPayload ;
282+ JsonNode responsePayload ;
283+ }
284+
234285 private static class ResponseContext {
235286 private final String outgoingMessage ;
236287 private final Class <ResponseType > responseClass ;
@@ -269,6 +320,8 @@ private OcppJsonMessage extract(String msg) throws Exception {
269320 return handleResult (messageId , parser );
270321 case CALL_ERROR :
271322 return handleError (messageId , parser );
323+ case CALL :
324+ return handleCall (messageId , parser );
272325 default :
273326 throw new SteveException ("Unknown enum type" );
274327 }
@@ -311,6 +364,54 @@ private OcppJsonResponse handleError(String messageId, JsonParser parser) throws
311364 error .setErrorDetails (details );
312365 return error ;
313366 }
367+
368+ private OcppJsonCall handleCall (String messageId , JsonParser parser ) {
369+ // parse action
370+ String action ;
371+ try {
372+ parser .nextToken ();
373+ action = parser .getText ();
374+ } catch (IOException e ) {
375+ throw new RuntimeException ();
376+ }
377+
378+ // parse request payload
379+ String req ;
380+ try {
381+ parser .nextToken ();
382+ JsonNode requestPayload = parser .readValueAsTree ();
383+
384+ // https://github.com/steve-community/steve/issues/1109
385+ if (requestPayload instanceof NullNode ) {
386+ requestPayload = new ObjectNode (JsonNodeFactory .instance );
387+ }
388+
389+ req = requestPayload .toString ();
390+ } catch (IOException e ) {
391+ log .error ("Exception occurred" , e );
392+ throw new RuntimeException ();
393+ }
394+
395+ RequestContext context = requestContextMap .get (action );
396+ if (context == null ) {
397+ testerThreadInterruptReason = new RuntimeException ("Unexpected message arrived: " + req );
398+ testerThread .interrupt ();
399+ } else if (Objects .equals (context .requestPayload , req )) {
400+ requestContextMap .remove (action );
401+ }
402+
403+ OcppJsonCallForTesting call = new OcppJsonCallForTesting ();
404+ call .setAction (action );
405+ call .setMessageId (messageId );
406+ call .setContext (context );
407+ return call ;
408+ }
409+ }
410+
411+ @ Setter
412+ @ Getter
413+ private static class OcppJsonCallForTesting extends OcppJsonCall {
414+ private RequestContext context ;
314415 }
315416
316417}
0 commit comments