From 39b6817ae9257c514a73e10704d759dc3cd95cb6 Mon Sep 17 00:00:00 2001
From: Igor Polevoy <500941+ipolevoy@users.noreply.github.com>
Date: Tue, 13 Dec 2022 19:20:20 -0600
Subject: [PATCH] #1258 - Add convenience methods to controller specs to work
with JSON
---
.../javalite/activeweb/RequestBuilder.java | 15 +++++
.../javalite/activeweb/RequestSpecHelper.java | 23 ++++++-
.../java/app/controllers/JSONController.java | 21 ++++++
.../app/controllers/JSONControllerSpec.java | 64 +++++++++++++++++++
.../org/javalite/activeweb/HttpSupport.java | 42 +++++++++++-
.../main/java/org/javalite/json/JSONList.java | 13 ++++
.../main/java/org/javalite/json/JSONMap.java | 10 ++-
7 files changed, 183 insertions(+), 5 deletions(-)
create mode 100644 activeweb-testing/src/test/java/app/controllers/JSONController.java
create mode 100644 activeweb-testing/src/test/java/app/controllers/JSONControllerSpec.java
diff --git a/activeweb-testing/src/main/java/org/javalite/activeweb/RequestBuilder.java b/activeweb-testing/src/main/java/org/javalite/activeweb/RequestBuilder.java
index ec1212870..fa71ea05e 100644
--- a/activeweb-testing/src/main/java/org/javalite/activeweb/RequestBuilder.java
+++ b/activeweb-testing/src/main/java/org/javalite/activeweb/RequestBuilder.java
@@ -270,6 +270,21 @@ public RequestBuilder content(String content) {
return content(content.getBytes());
}
+
+ /**
+ * Convenience method. Expects the content to be some JSON document (object or array).
+ * This method does NOT check the validity of the document. It is a responsibility of the controller.
+ *
+ * This method will automatically set the header "Content-Type" to be "application/json", and that is the convenience.
+ *
+ * @param content JSON document is expected
+ * @return self.
+ */
+ public RequestBuilder json(String content) {
+ contentType("application/json");
+ return content(content.getBytes());
+ }
+
/**
* Use to post content to a tested controller.
*
diff --git a/activeweb-testing/src/main/java/org/javalite/activeweb/RequestSpecHelper.java b/activeweb-testing/src/main/java/org/javalite/activeweb/RequestSpecHelper.java
index c9a319799..6b88a6dd7 100644
--- a/activeweb-testing/src/main/java/org/javalite/activeweb/RequestSpecHelper.java
+++ b/activeweb-testing/src/main/java/org/javalite/activeweb/RequestSpecHelper.java
@@ -17,6 +17,8 @@
import org.javalite.common.Convert;
+import org.javalite.json.JSONList;
+import org.javalite.json.JSONMap;
import org.javalite.test.jspec.TestException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
@@ -27,8 +29,6 @@
import java.util.*;
-import static org.javalite.common.Util.blank;
-
/**
* This class is not used directly in applications.
*
@@ -80,6 +80,25 @@ protected String responseContent(){
}
}
+
+ /**
+ * Provides content generated by controller in the {@link JSONMap} format, after controller execution - if views were integrated.
+ *
+ * @return content generated by controller/view already converted to JSONMap
object.
+ */
+ protected JSONMap responseJSONMap(){
+ return new JSONMap(responseContent());
+ }
+
+ /**
+ * Provides content generated by controller in the {@link JSONList} format, after controller execution - if views were integrated.
+ *
+ * @return content generated by controller/view already converted to JSONList
object.
+ */
+ protected JSONList responseJSONList(){
+ return new JSONList(responseContent());
+ }
+
/**
* Provides content generated by controller as bytes.
*
diff --git a/activeweb-testing/src/test/java/app/controllers/JSONController.java b/activeweb-testing/src/test/java/app/controllers/JSONController.java
new file mode 100644
index 000000000..f087b8297
--- /dev/null
+++ b/activeweb-testing/src/test/java/app/controllers/JSONController.java
@@ -0,0 +1,21 @@
+package app.controllers;
+
+import org.javalite.activeweb.AppController;
+import org.javalite.activeweb.annotations.POST;
+
+import org.javalite.json.JSONMap;
+
+public class JSONController extends AppController {
+
+ @POST
+ public void index1(){
+ JSONMap request = getRequestJSONMap();
+ request.put("last_name", "Doe");
+ respondJSON(request);
+ }
+
+ @POST
+ public void index2(){
+ respondJSON(getRequestJSONList());
+ }
+}
diff --git a/activeweb-testing/src/test/java/app/controllers/JSONControllerSpec.java b/activeweb-testing/src/test/java/app/controllers/JSONControllerSpec.java
new file mode 100644
index 000000000..2251e7fe6
--- /dev/null
+++ b/activeweb-testing/src/test/java/app/controllers/JSONControllerSpec.java
@@ -0,0 +1,64 @@
+package app.controllers;
+
+import org.javalite.activeweb.ControllerSpec;
+import org.javalite.json.JSONList;
+import org.javalite.json.JSONMap;
+import org.junit.Test;
+
+public class JSONControllerSpec extends ControllerSpec {
+
+ @Test
+ public void shouldPostJSONMap(){
+
+ String doc = """
+ {
+ "first_name" : "John"
+ }
+ """;
+ request().json(doc).post("index1");
+
+ JSONMap response = responseJSONMap();
+
+ the(response).shouldContain("first_name");
+ the(response).shouldContain("last_name");
+
+ the(response.get("first_name")).shouldContain("John");
+ the(response.get("last_name")).shouldContain("Doe");
+
+ the(contentType()).shouldBeEqual("application/json");
+ }
+
+ @Test
+ public void shouldPostJSONList(){
+
+ String doc = """
+ [
+ "John" , "Doe"
+ ]
+ """;
+ request().json(doc).post("index2");
+
+ JSONList response = responseJSONList();
+
+ the(response).shouldContain("John");
+ the(response).shouldContain("Doe");
+ the(contentType()).shouldBeEqual("application/json");
+ }
+
+ @Test
+ public void shouldPostJSONString(){
+
+ String doc = """
+ [
+ "John" , "Doe"
+ ]
+ """;
+ request().json(doc).post("index2");
+
+ JSONList response = responseJSONList();
+
+ the(response).shouldContain("John");
+ the(response).shouldContain("Doe");
+ the(contentType()).shouldBeEqual("application/json");
+ }
+}
diff --git a/activeweb/src/main/java/org/javalite/activeweb/HttpSupport.java b/activeweb/src/main/java/org/javalite/activeweb/HttpSupport.java
index b61b69f3e..27d9fef12 100644
--- a/activeweb/src/main/java/org/javalite/activeweb/HttpSupport.java
+++ b/activeweb/src/main/java/org/javalite/activeweb/HttpSupport.java
@@ -22,6 +22,8 @@
import org.javalite.common.Convert;
import org.javalite.json.JSONHelper;
import org.javalite.common.Util;
+import org.javalite.json.JSONList;
+import org.javalite.json.JSONMap;
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import org.slf4j.Logger;
@@ -491,8 +493,7 @@ protected HttpBuilder redirect(Class controllerClas
}
/**
- * This method will send the text to a client verbatim. It will not use any layouts. Use it to build app.services
- * and to support AJAX.
+ * This method will send the text to a client verbatim. It will not use any layouts. Use it to support AJAX or API functions.
*
* @param text text of response.
* @return {@link HttpSupport.HttpBuilder}, to accept additional information.
@@ -502,10 +503,28 @@ protected HttpBuilder respond(String text){
text = "null";
}
DirectResponse resp = new DirectResponse(text);
+ String contentType = RequestContext.getHttpRequest().getHeader("Content-Type");
+ //TODO: is this some spaghetti code? Seems we have Content-type in RequestContext as well as in ControllerResponse?
+ if(contentType != null){
+ resp.setContentType(contentType);
+ }
+
RequestContext.setControllerResponse(resp);
return new HttpBuilder(resp);
}
+ /**
+ * Serializes the argument as a JSON string and sets the "Content-Type" header to "application/json".
+ * See {@link HttpSupport#respond(String)}.
+ *
+ * @param object object to serialize to JSON
+ * @return self
+ */
+ protected HttpBuilder respondJSON(Object object){
+ header("Content-Type", "application/json");
+ return respond(JSONHelper.toJsonString(object));
+ }
+
/**
* Convenience method for downloading files. This method will force the browser to find a handler(external program)
* for this file (content type) and will provide a name of file to the browser. This method sets an HTTP header
@@ -1340,6 +1359,25 @@ protected InputStream getRequestStream() {
}
}
+
+ /**
+ * Reads entire request data and converts it to {@link JSONMap}.
+ *
+ * @return data sent by client as {@link JSONMap}.
+ */
+ protected JSONMap getRequestJSONMap() {
+ return new JSONMap(getRequestString());
+ }
+
+ /**
+ * Reads entire request data and converts it to {@link JSONList}.
+ *
+ * @return data sent by client as {@link JSONList}.
+ */
+ protected JSONList getRequestJSONList() {
+ return new JSONList(getRequestString());
+ }
+
/**
* Reads entire request data as String. Do not use for large data sets to avoid
* memory issues, instead use {@link #getRequestInputStream()}.
diff --git a/javalite-common/src/main/java/org/javalite/json/JSONList.java b/javalite-common/src/main/java/org/javalite/json/JSONList.java
index 8881d94e8..388d00152 100644
--- a/javalite-common/src/main/java/org/javalite/json/JSONList.java
+++ b/javalite-common/src/main/java/org/javalite/json/JSONList.java
@@ -37,9 +37,15 @@
* @see JSONList#getMap(int)
*/
public class JSONList extends ArrayList {
+
+
public JSONList(List jsonList){
super(jsonList);
}
+
+ /**
+ * @param jsonList JSON array as string.
+ */
public JSONList(String jsonList){
super(JSONHelper.toList(jsonList));
}
@@ -145,4 +151,11 @@ public String toString() {
public String toJSON(boolean pretty){
return JSONHelper.toJsonString(this, pretty);
}
+
+ /**
+ * @return a JSON representation of this object
+ */
+ public String toJSON(){
+ return JSONHelper.toJsonString(this, false);
+ }
}
diff --git a/javalite-common/src/main/java/org/javalite/json/JSONMap.java b/javalite-common/src/main/java/org/javalite/json/JSONMap.java
index c0d07b56e..2c53309b1 100644
--- a/javalite-common/src/main/java/org/javalite/json/JSONMap.java
+++ b/javalite-common/src/main/java/org/javalite/json/JSONMap.java
@@ -39,6 +39,15 @@ public JSONMap(String ... keysAndValues){
super(map(keysAndValues));
}
+ /**
+ * Converts the String into a JSONMap.
+ *
+ * @param jsonString JSON Object document as string.
+ */
+ public JSONMap(String jsonString){
+ super(JSONHelper.toJSONMap(jsonString));
+ }
+
public JSONMap(){}
/**
@@ -285,7 +294,6 @@ public String toJSON(boolean pretty){
}
/**
- *
* @return a JSON representation of this object
*/
public String toJSON(){