@@ -245,6 +245,160 @@ public void normalRequest_commitsRpcServerResponse() throws Exception {
245245 assertArrayEquals (rpcResp , resp .getContentAsByteArray ());
246246 }
247247
248+ // --- Content-Type header: must be application/json-rpc (no charset suffix) ---
249+
250+ @ Test
251+ public void errorResponse_contentTypeIsApplicationJsonRpc () throws Exception {
252+ MockHttpServletResponse resp = doPost ("not valid json" );
253+ assertEquals ("application/json-rpc" , resp .getContentType ());
254+ }
255+
256+ @ Test
257+ public void batchResponse_contentTypeIsApplicationJsonRpc () throws Exception {
258+ byte [] singleResp = "{\" jsonrpc\" :\" 2.0\" ,\" result\" :\" ok\" ,\" id\" :1}"
259+ .getBytes (StandardCharsets .UTF_8 );
260+ doAnswer (inv -> {
261+ OutputStream out = inv .getArgument (1 );
262+ out .write (singleResp );
263+ return 0 ;
264+ }).when (mockRpcServer ).handleRequest (any (InputStream .class ), any (OutputStream .class ));
265+
266+ MockHttpServletResponse resp = doPost ("[{\" id\" :1}]" );
267+ assertEquals ("application/json-rpc" , resp .getContentType ());
268+ }
269+
270+ @ Test
271+ public void allNotificationBatch_contentTypeIsApplicationJsonRpc () throws Exception {
272+ // notification: rpcServer returns 0 bytes → empty batchResult → early return path
273+ doAnswer (inv -> 0 ).when (mockRpcServer )
274+ .handleRequest (any (InputStream .class ), any (OutputStream .class ));
275+
276+ MockHttpServletResponse resp = doPost ("[{\" method\" :\" eth_blockNumber\" }]" );
277+ assertEquals (200 , resp .getStatus ());
278+ assertEquals (0 , resp .getContentLength ());
279+ assertEquals ("application/json-rpc" , resp .getContentType ());
280+ }
281+
282+ // --- Primitive root node → Invalid Request (-32600), id must be JSON null ---
283+
284+ @ Test
285+ public void primitiveRootNull_returnsInvalidRequestWithJsonNullId () throws Exception {
286+ MockHttpServletResponse resp = doPost ("null" );
287+ assertEquals (200 , resp .getStatus ());
288+ JsonNode body = MAPPER .readTree (resp .getContentAsString ());
289+ assertFalse (body .isArray ());
290+ assertEquals ("2.0" , body .get ("jsonrpc" ).asText ());
291+ assertEquals (-32600 , body .get ("error" ).get ("code" ).asInt ());
292+ assertTrue ("id must be JSON null, not the string \" null\" " , body .get ("id" ).isNull ());
293+ assertFalse ("id must not be a string" , body .get ("id" ).isTextual ());
294+ }
295+
296+ @ Test
297+ public void primitiveRootBoolean_returnsInvalidRequest () throws Exception {
298+ MockHttpServletResponse resp = doPost ("true" );
299+ assertEquals (200 , resp .getStatus ());
300+ assertEquals (-32600 ,
301+ MAPPER .readTree (resp .getContentAsString ()).get ("error" ).get ("code" ).asInt ());
302+ }
303+
304+ @ Test
305+ public void primitiveRootNumber_returnsInvalidRequest () throws Exception {
306+ MockHttpServletResponse resp = doPost ("123" );
307+ assertEquals (200 , resp .getStatus ());
308+ assertEquals (-32600 ,
309+ MAPPER .readTree (resp .getContentAsString ()).get ("error" ).get ("code" ).asInt ());
310+ }
311+
312+ @ Test
313+ public void primitiveRootString_returnsInvalidRequest () throws Exception {
314+ MockHttpServletResponse resp = doPost ("\" hello\" " );
315+ assertEquals (200 , resp .getStatus ());
316+ assertEquals (-32600 ,
317+ MAPPER .readTree (resp .getContentAsString ()).get ("error" ).get ("code" ).asInt ());
318+ }
319+
320+ // --- Non-object element inside a batch → Invalid Request per element ---
321+
322+ @ Test
323+ public void batchWithNestedArray_returnsInvalidRequestArray () throws Exception {
324+ MockHttpServletResponse resp = doPost ("[[]]" );
325+ assertEquals (200 , resp .getStatus ());
326+ JsonNode body = MAPPER .readTree (resp .getContentAsString ());
327+ assertTrue ("response must be a JSON array" , body .isArray ());
328+ assertEquals (1 , body .size ());
329+ assertEquals (-32600 , body .get (0 ).get ("error" ).get ("code" ).asInt ());
330+ assertTrue ("id in batch error must be JSON null" , body .get (0 ).get ("id" ).isNull ());
331+ }
332+
333+ @ Test
334+ public void batchWithMixedObjectAndArray_objectProcessedArrayRejected () throws Exception {
335+ byte [] singleResp = "{\" jsonrpc\" :\" 2.0\" ,\" result\" :\" ok\" ,\" id\" :1}"
336+ .getBytes (StandardCharsets .UTF_8 );
337+ doAnswer (inv -> {
338+ OutputStream out = inv .getArgument (1 );
339+ out .write (singleResp );
340+ return 0 ;
341+ }).when (mockRpcServer ).handleRequest (any (InputStream .class ), any (OutputStream .class ));
342+
343+ MockHttpServletResponse resp = doPost ("[{\" id\" :1}, []]" );
344+ assertEquals (200 , resp .getStatus ());
345+ JsonNode body = MAPPER .readTree (resp .getContentAsString ());
346+ assertTrue ("response must be a JSON array" , body .isArray ());
347+ assertEquals (2 , body .size ());
348+ assertEquals ("ok" , body .get (0 ).get ("result" ).asText ());
349+ assertEquals (-32600 , body .get (1 ).get ("error" ).get ("code" ).asInt ());
350+ }
351+
352+ @ Test
353+ public void batchWithNumericAndStringElements_allGetInvalidRequest () throws Exception {
354+ MockHttpServletResponse resp = doPost ("[42, \" foo\" , true]" );
355+ assertEquals (200 , resp .getStatus ());
356+ JsonNode body = MAPPER .readTree (resp .getContentAsString ());
357+ assertTrue ("response must be a JSON array" , body .isArray ());
358+ assertEquals (3 , body .size ());
359+ for (int i = 0 ; i < 3 ; i ++) {
360+ assertEquals (-32600 , body .get (i ).get ("error" ).get ("code" ).asInt ());
361+ }
362+ }
363+
364+ // --- StreamReadConstraints: maxNestingDepth and maxTokenCount must be enforced ---
365+
366+ @ Test
367+ public void excessivelyNestedRequest_returnsParseError () throws Exception {
368+ int limit = CommonParameter .getInstance ().getMaxNestingDepth ();
369+ StringBuilder sb = new StringBuilder ();
370+ for (int i = 0 ; i <= limit ; i ++) {
371+ sb .append ('[' );
372+ }
373+ sb .append ('0' );
374+ for (int i = 0 ; i <= limit ; i ++) {
375+ sb .append (']' );
376+ }
377+
378+ MockHttpServletResponse resp = doPost (sb .toString ());
379+ assertEquals (200 , resp .getStatus ());
380+ assertEquals (-32700 ,
381+ MAPPER .readTree (resp .getContentAsString ()).get ("error" ).get ("code" ).asInt ());
382+ }
383+
384+ @ Test
385+ public void tooManyTokens_returnsParseError () throws Exception {
386+ int limit = CommonParameter .getInstance ().getMaxTokenCount ();
387+ StringBuilder sb = new StringBuilder ("[" );
388+ for (int i = 0 ; i < limit ; i ++) {
389+ if (i > 0 ) {
390+ sb .append (',' );
391+ }
392+ sb .append ('0' );
393+ }
394+ sb .append (']' );
395+
396+ MockHttpServletResponse resp = doPost (sb .toString ());
397+ assertEquals (200 , resp .getStatus ());
398+ assertEquals (-32700 ,
399+ MAPPER .readTree (resp .getContentAsString ()).get ("error" ).get ("code" ).asInt ());
400+ }
401+
248402 // --- helpers ---
249403
250404 private MockHttpServletResponse doPost (String body ) throws Exception {
0 commit comments