diff --git a/app/server/appsmith-interfaces/src/test/java/com/appsmith/external/connections/OAuth2ClientCredentialsTest.java b/app/server/appsmith-interfaces/src/test/java/com/appsmith/external/connections/OAuth2ClientCredentialsTest.java index abbdf629c6..45df948f8a 100644 --- a/app/server/appsmith-interfaces/src/test/java/com/appsmith/external/connections/OAuth2ClientCredentialsTest.java +++ b/app/server/appsmith-interfaces/src/test/java/com/appsmith/external/connections/OAuth2ClientCredentialsTest.java @@ -89,7 +89,7 @@ public class OAuth2ClientCredentialsTest { @Test public void testCreate_withIsAuthorizationHeaderTrue_sendsCredentialsInHeader() throws InterruptedException { - String baseUrl = String.format("http://localhost:%s", mockEndpoint.getPort()); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); OAuth2 oAuth2 = new OAuth2(); @@ -115,7 +115,7 @@ public class OAuth2ClientCredentialsTest { @Test public void testCreate_withIsAuthorizationHeaderFalse_sendsCredentialsInBody() throws InterruptedException, EOFException { - String baseUrl = String.format("http://localhost:%s", mockEndpoint.getPort()); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); OAuth2 oAuth2 = new OAuth2(); diff --git a/app/server/appsmith-plugins/graphqlPlugin/src/test/java/com/external/plugins/GraphQLPluginTest.java b/app/server/appsmith-plugins/graphqlPlugin/src/test/java/com/external/plugins/GraphQLPluginTest.java index e81b988aac..378d4b4ae3 100644 --- a/app/server/appsmith-plugins/graphqlPlugin/src/test/java/com/external/plugins/GraphQLPluginTest.java +++ b/app/server/appsmith-plugins/graphqlPlugin/src/test/java/com/external/plugins/GraphQLPluginTest.java @@ -18,15 +18,18 @@ import com.appsmith.external.models.Property; import com.appsmith.external.services.SharedConfig; import com.external.plugins.exceptions.GraphQLPluginError; import com.external.utils.GraphQLHintMessageUtils; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; +import mockwebserver3.MockResponse; +import mockwebserver3.MockWebServer; +import mockwebserver3.RecordedRequest; import net.minidev.json.JSONObject; import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; +import okio.Buffer; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; @@ -40,17 +43,19 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuple2; import javax.crypto.SecretKey; +import java.io.EOFException; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Arrays; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; +import java.util.concurrent.TimeUnit; import static com.appsmith.external.constants.Authentication.API_KEY; import static com.appsmith.external.constants.Authentication.OAUTH2; @@ -71,7 +76,9 @@ public class GraphQLPluginTest { private static GraphQLHintMessageUtils hintMessageUtils; - public class MockSharedConfig implements SharedConfig { + private static MockWebServer mockEndpoint; + + public static class MockSharedConfig implements SharedConfig { @Override public int getCodecSize() { @@ -99,12 +106,19 @@ public class GraphQLPluginTest { .waitingFor(Wait.forHttp("/").forStatusCode(404)); @BeforeEach - public void setUp() { + public void setUp() throws IOException { hintMessageUtils = new GraphQLHintMessageUtils(); + mockEndpoint = new MockWebServer(); + mockEndpoint.start(); + } + + @AfterEach + public void tearDown() throws IOException { + mockEndpoint.shutdown(); } private DatasourceConfiguration getDefaultDatasourceConfig() { - String address = graphqlContainer.getContainerIpAddress(); + String address = graphqlContainer.getHost(); Integer port = graphqlContainer.getFirstMappedPort(); DatasourceConfiguration dsConfig = new DatasourceConfiguration(); dsConfig.setUrl("http://" + address + ":" + port + "/graphql"); @@ -115,15 +129,16 @@ public class GraphQLPluginTest { ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setHeaders(List.of(new Property("content-type", "application/json"))); actionConfig.setHttpMethod(HttpMethod.POST); - String requestBody = "query {\n" + - "\tallPosts(first: 1) {\n" + - "\t\tnodes {\n" + - "\t\t\tid\n" + - "\t\t}\n" + - "\t}\n" + - "}"; + String requestBody = """ + query { + allPosts(first: 1) { + nodes { + id + } + } + }"""; actionConfig.setBody(requestBody); - List properties = new ArrayList(); + List properties = new ArrayList<>(); properties.add(new Property("smartSubstitution", "true")); properties.add(new Property("queryVariables", "")); actionConfig.setPluginSpecifiedTemplates(properties); @@ -134,19 +149,21 @@ public class GraphQLPluginTest { public void testValidGraphQLApiExecutionWithQueryVariablesWithHttpPost() { DatasourceConfiguration dsConfig = getDefaultDatasourceConfig(); ActionConfiguration actionConfig = getDefaultActionConfiguration(); - String queryBody = "query($limit: Int) {\n" + - "\tallPosts(first: $limit) {\n" + - "\t\tnodes {\n" + - "\t\t\tid\n" + - "\t\t}\n" + - "\t}\n" + - "}"; + String queryBody = """ + query($limit: Int) { + allPosts(first: $limit) { + nodes { + id + } + } + }"""; actionConfig.setBody(queryBody); - List properties = new ArrayList(); + List properties = new ArrayList<>(); properties.add(new Property("", "true")); - properties.add(new Property("", "{\n" + - " \"limit\": 2\n" + - "}")); + properties.add(new Property("", """ + { + "limit": 2 + }""")); actionConfig.setPluginSpecifiedTemplates(properties); Mono resultMono = pluginExecutor.executeParameterized(null, new ExecuteActionDTO(), dsConfig, actionConfig); @@ -168,22 +185,24 @@ public class GraphQLPluginTest { //changing the url to add whitespaces at the start and end of the url String url = dsConfig.getUrl(); - url = String.format("%-" + (url.length() + 4) + "s" ,url); - url = String.format("%" + (url.length() + 4) + "s" ,url); + url = String.format("%-" + (url.length() + 4) + "s", url); + url = String.format("%" + (url.length() + 4) + "s", url); dsConfig.setUrl(url); - String queryBody = "query($limit: Int) {\n" + - "\tallPosts(first: $limit) {\n" + - "\t\tnodes {\n" + - "\t\t\tid\n" + - "\t\t}\n" + - "\t}\n" + - "}"; + String queryBody = """ + query($limit: Int) { + allPosts(first: $limit) { + nodes { + id + } + } + }"""; actionConfig.setBody(queryBody); - List properties = new ArrayList(); + List properties = new ArrayList<>(); properties.add(new Property("", "true")); - properties.add(new Property("", "{\n" + - " \"limit\": 2\n" + - "}")); + properties.add(new Property("", """ + { + "limit": 2 + }""")); actionConfig.setPluginSpecifiedTemplates(properties); Mono resultMono = pluginExecutor.executeParameterized(null, new ExecuteActionDTO(), dsConfig, actionConfig); @@ -201,12 +220,14 @@ public class GraphQLPluginTest { dsConfig.setUrl("https://rickandmortyapi.com/graphql"); ActionConfiguration actionConfig = getDefaultActionConfiguration(); actionConfig.setHttpMethod(HttpMethod.GET); - actionConfig.setBody("query Query {\n" + - " character(id: 1) {\n" + - " created\n" + - " }\n" + - "}\n"); - List properties = new ArrayList(); + actionConfig.setBody(""" + query Query { + character(id: 1) { + created + } + } + """); + List properties = new ArrayList<>(); properties.add(new Property("smartSubstitution", "true")); properties.add(new Property("queryVariables", "")); actionConfig.setPluginSpecifiedTemplates(properties); @@ -264,14 +285,15 @@ public class GraphQLPluginTest { public void testInvalidQueryBodyError() { DatasourceConfiguration dsConfig = getDefaultDatasourceConfig(); ActionConfiguration actionConfig = getDefaultActionConfiguration(); - actionConfig.setBody("query Capsules {\n" + - " capsules(limit: 1, offset: 0) {\n" + - " dragon \n" + - " id\n" + - " name\n" + - " }\n" + - " }\n" + - "}"); + actionConfig.setBody(""" + query Capsules { + capsules(limit: 1, offset: 0) { + dragon\s + id + name + } + } + }"""); Mono resultMono = pluginExecutor.executeParameterized(null, new ExecuteActionDTO(), dsConfig, actionConfig); StepVerifier.create(resultMono) @@ -287,32 +309,40 @@ public class GraphQLPluginTest { public void testInvalidQueryVariablesError() { DatasourceConfiguration dsConfig = getDefaultDatasourceConfig(); ActionConfiguration actionConfig = getDefaultActionConfiguration(); - actionConfig.setBody("query Capsules($limit: Int, $offset: Int) {\n" + - " capsules(limit: $limit, offset: $offset) {\n" + - " dragon {\n" + - " id\n" + - " name\n" + - " }\n" + - " }\n" + - "}"); - actionConfig.getPluginSpecifiedTemplates().get(QUERY_VARIABLES_INDEX).setValue("{\n" + - " \"limit\": 1\n" + - " \"offset\": 0\n" + - "}"); + actionConfig.setBody(""" + query Capsules($limit: Int, $offset: Int) { + capsules(limit: $limit, offset: $offset) { + dragon { + id + name + } + } + }"""); + actionConfig.getPluginSpecifiedTemplates().get(QUERY_VARIABLES_INDEX).setValue(""" + { + "limit": 1 + "offset": 0 + }"""); Mono resultMono = pluginExecutor.executeParameterized(null, new ExecuteActionDTO(), dsConfig, actionConfig); StepVerifier.create(resultMono) .verifyErrorSatisfies(error -> { assertTrue(error instanceof AppsmithPluginException); String expectedMessage = "GraphQL query variables are not in proper JSON format: Expected a ',' " + "or '}' at 18 [character 3 line 3]"; - assertTrue(expectedMessage.equals(error.getMessage())); + assertEquals(expectedMessage, error.getMessage()); }); } @Test public void testValidSignature() { DatasourceConfiguration dsConfig = getDefaultDatasourceConfig(); - dsConfig.setUrl("http://httpbin.org/headers"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); final String secretKey = "a-random-key-that-should-be-32-chars-long-at-least"; dsConfig.setProperties(List.of( @@ -329,22 +359,29 @@ public class GraphQLPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - String token = ((ObjectNode) result.getBody()).get("headers").get("X-Appsmith-Signature").asText(); - final SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); - final String issuer = Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody() - .getIssuer(); - assertEquals("Appsmith", issuer); - final Iterator> fields = ((ObjectNode) result.getRequest().getHeaders()).fields(); - fields.forEachRemaining(field -> { - if ("X-Appsmith-Signature".equalsIgnoreCase(field.getKey())) { - assertEquals(token, field.getValue().get(0).asText()); - } - }); + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + String token = recordedRequest.getHeaders().get("X-Appsmith-Signature"); + + final SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + final String issuer = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody() + .getIssuer(); + assertEquals("Appsmith", issuer); + final Iterator> fields = ((ObjectNode) result.getRequest().getHeaders()).fields(); + fields.forEachRemaining(field -> { + if ("X-Appsmith-Signature".equalsIgnoreCase(field.getKey())) { + assertEquals(token, field.getValue().get(0).asText()); + } + }); + } catch (InterruptedException e) { + assert false : e.getMessage(); + } }) .verifyComplete(); @@ -362,7 +399,7 @@ public class GraphQLPluginTest { StepVerifier .create(invalidsMono) - .assertNext(invalids -> invalids.containsAll(Set.of("Missing Client ID", "Missing Client Secret", "Missing Access Token URL"))); + .assertNext(invalids -> assertTrue(invalids.containsAll(Set.of("Missing Client ID", "Missing Client Secret", "Missing Access Token URL")))); } /** @@ -375,19 +412,28 @@ public class GraphQLPluginTest { @Test public void testSmartSubstitutionInQueryBodyForNumberStringBooleanAndSchemaTypes() { DatasourceConfiguration dsConfig = getDefaultDatasourceConfig(); - dsConfig.setUrl("https://postman-echo.com/post"); + + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = getDefaultActionConfiguration(); - actionConfig.setBody("query Capsules {\n" + - " capsules(myNum: {{Input1.text}}, myStr: {{Input2.text}}, myBool: {{Input3.text}}) {\n" + - " dragon {\n" + - " {{Input4.text}}\n" + - " name\n" + - " }\n" + - " }\n" + - "}\n" + - "\n" + - "\n"); + actionConfig.setBody(""" + query Capsules { + capsules(myNum: {{Input1.text}}, myStr: {{Input2.text}}, myBool: {{Input3.text}}) { + dragon { + {{Input4.text}} + name + } + } + } + + + """); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); List params = new ArrayList<>(); @@ -414,21 +460,35 @@ public class GraphQLPluginTest { executeActionDTO.setParams(params); Mono resultMono = pluginExecutor.executeParameterized(null, executeActionDTO, dsConfig, actionConfig); + StepVerifier.create(resultMono) .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - String expectedQueryBody = "query Capsules {\n capsules(myNum: 3, myStr: \"this is a string! Yay" + - " :D\", myBool: true) {\n dragon {\n id\n name\n }\n }\n}\n\n\n"; + String expectedQueryBody = """ + query Capsules { + capsules(myNum: 3, myStr: "this is a string! Yay :D", myBool: true) { + dragon { + id + name + } + } + } + + + """; JSONParser jsonParser = new JSONParser(JSONParser.MODE_PERMISSIVE); - ObjectMapper objectMapper = new ObjectMapper(); try { - JSONObject resultJson = (JSONObject) jsonParser.parse(String.valueOf(result.getBody())); - // Object resultData = ((JSONObject) resultJson.get("json")).get("query"); - String resultData = ((JSONObject) resultJson.get("json")).getAsString("query"); - String parsedJsonAsString = objectMapper.writeValueAsString(resultData); + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + JSONObject resultJson = (JSONObject) jsonParser.parse(new String(bodyBytes)); + String resultData = resultJson.getAsString("query"); assertEquals(expectedQueryBody, resultData); - } catch (ParseException | JsonProcessingException e) { + } catch (ParseException | EOFException | InterruptedException e) { assert false : e.getMessage(); } @@ -436,23 +496,23 @@ public class GraphQLPluginTest { ActionExecutionRequest request = result.getRequest(); List> parameters = (List>) request.getProperties().get("smart-substitution-parameters"); - assertEquals(parameters.size(), 4); + assertEquals(4, parameters.size()); Map.Entry parameterEntry = parameters.get(0); - assertEquals(parameterEntry.getKey(), "3"); - assertEquals(parameterEntry.getValue(), "GRAPHQL_BODY_INTEGER"); + assertEquals("3", parameterEntry.getKey()); + assertEquals("GRAPHQL_BODY_INTEGER", parameterEntry.getValue()); parameterEntry = parameters.get(1); - assertEquals(parameterEntry.getKey(), "this is a string! Yay :D"); - assertEquals(parameterEntry.getValue(), "GRAPHQL_BODY_STRING"); + assertEquals("this is a string! Yay :D", parameterEntry.getKey()); + assertEquals("GRAPHQL_BODY_STRING", parameterEntry.getValue()); parameterEntry = parameters.get(2); - assertEquals(parameterEntry.getKey(), "true"); - assertEquals(parameterEntry.getValue(), "GRAPHQL_BODY_BOOLEAN"); + assertEquals("true", parameterEntry.getKey()); + assertEquals("GRAPHQL_BODY_BOOLEAN", parameterEntry.getValue()); parameterEntry = parameters.get(3); - assertEquals(parameterEntry.getKey(), "id"); - assertEquals(parameterEntry.getValue(), "GRAPHQL_BODY_PARTIAL"); + assertEquals("id", parameterEntry.getKey()); + assertEquals("GRAPHQL_BODY_PARTIAL", parameterEntry.getValue()); }) .verifyComplete(); } @@ -463,7 +523,14 @@ public class GraphQLPluginTest { @Test public void testSmartSubstitutionInQueryBodyForFullBodySubstitution() { DatasourceConfiguration dsConfig = getDefaultDatasourceConfig(); - dsConfig.setUrl("https://postman-echo.com/post"); + + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = getDefaultActionConfiguration(); actionConfig.setBody("{{Input1.text}}"); @@ -472,14 +539,15 @@ public class GraphQLPluginTest { List params = new ArrayList<>(); Param param1 = new Param(); param1.setKey("Input1.text"); - param1.setValue("query Capsules {\n" + - " capsules(limit: 1, offset: 0) {\n" + - " dragon {\n" + - " id\n" + - " name\n" + - " }\n" + - " }\n" + - "}"); + param1.setValue(""" + query Capsules { + capsules(limit: 1, offset: 0) { + dragon { + id + name + } + } + }"""); param1.setClientDataType(ClientDataType.STRING); params.add(param1); executeActionDTO.setParams(params); @@ -489,23 +557,29 @@ public class GraphQLPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - String expectedQueryBody = "query Capsules {\n" + - " capsules(limit: 1, offset: 0) {\n" + - " dragon {\n" + - " id\n" + - " name\n" + - " }\n" + - " }\n" + - "}"; + String expectedQueryBody = """ + query Capsules { + capsules(limit: 1, offset: 0) { + dragon { + id + name + } + } + }"""; JSONParser jsonParser = new JSONParser(JSONParser.MODE_PERMISSIVE); - ObjectMapper objectMapper = new ObjectMapper(); try { - JSONObject resultJson = (JSONObject) jsonParser.parse(String.valueOf(result.getBody())); - // Object resultData = ((JSONObject) resultJson.get("json")).get("query"); - String resultData = ((JSONObject) resultJson.get("json")).getAsString("query"); - String parsedJsonAsString = objectMapper.writeValueAsString(resultData); + + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + JSONObject resultJson = (JSONObject) jsonParser.parse(new String(bodyBytes)); + String resultData = resultJson.getAsString("query"); assertEquals(expectedQueryBody, resultData); - } catch (ParseException | JsonProcessingException e) { + } catch (ParseException | EOFException | InterruptedException e) { assert false : e.getMessage(); } @@ -513,18 +587,19 @@ public class GraphQLPluginTest { ActionExecutionRequest request = result.getRequest(); List> parameters = (List>) request.getProperties().get("smart-substitution-parameters"); - assertEquals(parameters.size(), 1); + assertEquals(1, parameters.size()); Map.Entry parameterEntry = parameters.get(0); - assertEquals(parameterEntry.getKey(), "query Capsules {\n" + - " capsules(limit: 1, offset: 0) {\n" + - " dragon {\n" + - " id\n" + - " name\n" + - " }\n" + - " }\n" + - "}"); - assertEquals(parameterEntry.getValue(), "GRAPHQL_BODY_FULL"); + assertEquals(parameterEntry.getKey(), """ + query Capsules { + capsules(limit: 1, offset: 0) { + dragon { + id + name + } + } + }"""); + assertEquals("GRAPHQL_BODY_FULL", parameterEntry.getValue()); }) .verifyComplete(); } @@ -532,18 +607,26 @@ public class GraphQLPluginTest { @Test public void testSmartSubstitutionQueryVariables() { DatasourceConfiguration dsConfig = getDefaultDatasourceConfig(); - dsConfig.setUrl("https://postman-echo.com/post"); + + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = getDefaultActionConfiguration(); - String queryVariables = "{\n" + - "\t\"name\" : {{Input1.text}},\n" + - "\t\"email\" : {{Input2.text}},\n" + - "\t\"username\" : {{Input3.text}},\n" + - "\t\"password\" : \"{{Input4.text}}\",\n" + - "\t\"newField\" : \"{{Input5.text}}\",\n" + - "\t\"tableRow\" : {{Table1.selectedRow}},\n" + - "\t\"table\" : \"{{Table1.tableData}}\"\n" + - "}"; + String queryVariables = """ + { + "name" : {{Input1.text}}, + "email" : {{Input2.text}}, + "username" : {{Input3.text}}, + "password" : "{{Input4.text}}", + "newField" : "{{Input5.text}}", + "tableRow" : {{Table1.selectedRow}}, + "table" : "{{Table1.tableData}}" + }"""; actionConfig.getPluginSpecifiedTemplates().get(QUERY_VARIABLES_INDEX).setValue(queryVariables); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); @@ -590,43 +673,48 @@ public class GraphQLPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - String resultBody = "{\"password\":\"12/01/2018\",\"name\":\"this is a string! Yay :D\",\"newField\":null,\"tableRow\":{\"orderAmount\":4.99,\"id\":2381224,\"userName\":\"Michael Lawson\",\"email\":\"michael.lawson@reqres.in\",\"productName\":\"Chicken Sandwich\"},\"email\":true,\"table\":[{\"orderAmount\":4.99,\"id\":2381224,\"userName\":\"Michael Lawson\",\"email\":\"michael.lawson@reqres.in\",\"productName\":\"Chicken Sandwich\"},{\"orderAmount\":9.99,\"id\":2736212,\"userName\":\"Lindsay Ferguson\",\"email\":\"lindsay.ferguson@reqres.in\",\"productName\":\"Tuna Salad\"},{\"orderAmount\":19.99,\"id\":6788734,\"userName\":\"Tobias Funke\",\"email\":\"tobias.funke@reqres.in\",\"productName\":\"Beef steak\"}],\"username\":0}"; + String expectedResultData = "{\"password\":\"12\\/01\\/2018\",\"name\":\"this is a string! Yay :D\",\"newField\":null,\"tableRow\":{\"orderAmount\":4.99,\"id\":2381224,\"userName\":\"Michael Lawson\",\"email\":\"michael.lawson@reqres.in\",\"productName\":\"Chicken Sandwich\"},\"email\":true,\"table\":[{\"orderAmount\":4.99,\"id\":2381224,\"userName\":\"Michael Lawson\",\"email\":\"michael.lawson@reqres.in\",\"productName\":\"Chicken Sandwich\"},{\"orderAmount\":9.99,\"id\":2736212,\"userName\":\"Lindsay Ferguson\",\"email\":\"lindsay.ferguson@reqres.in\",\"productName\":\"Tuna Salad\"},{\"orderAmount\":19.99,\"id\":6788734,\"userName\":\"Tobias Funke\",\"email\":\"tobias.funke@reqres.in\",\"productName\":\"Beef steak\"}],\"username\":0}"; JSONParser jsonParser = new JSONParser(JSONParser.MODE_PERMISSIVE); - ObjectMapper objectMapper = new ObjectMapper(); try { - JSONObject resultJson = (JSONObject) jsonParser.parse(String.valueOf(result.getBody())); - Object resultData = ((JSONObject) resultJson.get("json")).get("variables"); - String parsedJsonAsString = objectMapper.writeValueAsString(resultData); - assertEquals(resultBody, parsedJsonAsString); - } catch (ParseException | JsonProcessingException e) { - e.printStackTrace(); + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + JSONObject resultJson = (JSONObject) jsonParser.parse(new String(bodyBytes)); + Object resultData = resultJson.getAsString("variables"); + assertEquals(expectedResultData, resultData); + } catch (ParseException | EOFException | InterruptedException e) { + assert false : e.getMessage(); } // Assert the debug request parameters are getting set. ActionExecutionRequest request = result.getRequest(); List> parameters = (List>) request.getProperties().get("smart-substitution-parameters"); - assertEquals(parameters.size(), 7); + assertEquals(7, parameters.size()); Map.Entry parameterEntry = parameters.get(0); - assertEquals(parameterEntry.getKey(), "this is a string! Yay :D"); - assertEquals(parameterEntry.getValue(), "STRING"); + assertEquals("this is a string! Yay :D", parameterEntry.getKey()); + assertEquals("STRING", parameterEntry.getValue()); parameterEntry = parameters.get(1); - assertEquals(parameterEntry.getKey(), "true"); - assertEquals(parameterEntry.getValue(), "BOOLEAN"); + assertEquals("true", parameterEntry.getKey()); + assertEquals("BOOLEAN", parameterEntry.getValue()); parameterEntry = parameters.get(2); - assertEquals(parameterEntry.getKey(), "0"); - assertEquals(parameterEntry.getValue(), "INTEGER"); + assertEquals("0", parameterEntry.getKey()); + assertEquals("INTEGER", parameterEntry.getValue()); parameterEntry = parameters.get(3); - assertEquals(parameterEntry.getKey(), "12/01/2018"); - assertEquals(parameterEntry.getValue(), "STRING"); + assertEquals("12/01/2018", parameterEntry.getKey()); + assertEquals("STRING", parameterEntry.getValue()); parameterEntry = parameters.get(4); - assertEquals(parameterEntry.getKey(), "null"); - assertEquals(parameterEntry.getValue(), "NULL"); + assertEquals("null", parameterEntry.getKey()); + assertEquals("NULL", parameterEntry.getValue()); }) .verifyComplete(); } @@ -634,7 +722,15 @@ public class GraphQLPluginTest { @Test public void testRequestWithApiKeyHeader() { DatasourceConfiguration dsConfig = getDefaultDatasourceConfig(); - dsConfig.setUrl("https://postman-echo.com/post"); + + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); + AuthenticationDTO authenticationDTO = new ApiKeyAuth(ApiKeyAuth.Type.HEADER, "api_key", "Token", "test"); dsConfig.setAuthentication(authenticationDTO); @@ -711,7 +807,7 @@ public class GraphQLPluginTest { Set expectedDuplicateHeaders = new HashSet<>(); expectedDuplicateHeaders.add("myHeader1"); expectedDuplicateHeaders.add("myHeader2"); - assertTrue(expectedDuplicateHeaders.equals(duplicateHeadersWithDsConfigOnly.get(DATASOURCE_CONFIG_ONLY))); + assertEquals(expectedDuplicateHeaders, duplicateHeadersWithDsConfigOnly.get(DATASOURCE_CONFIG_ONLY)); /* Test duplicate query params in datasource configuration only */ Map> duplicateParamsWithDsConfigOnly = @@ -722,7 +818,7 @@ public class GraphQLPluginTest { Set expectedDuplicateParams = new HashSet<>(); expectedDuplicateParams.add("myParam1"); expectedDuplicateParams.add("myParam2"); - assertTrue(expectedDuplicateParams.equals(duplicateParamsWithDsConfigOnly.get(DATASOURCE_CONFIG_ONLY))); + assertEquals(expectedDuplicateParams, duplicateParamsWithDsConfigOnly.get(DATASOURCE_CONFIG_ONLY)); /* Test duplicate headers in datasource + action configuration */ Map> allDuplicateHeaders = @@ -732,19 +828,19 @@ public class GraphQLPluginTest { expectedDuplicateHeaders = new HashSet<>(); expectedDuplicateHeaders.add("myHeader1"); expectedDuplicateHeaders.add("myHeader2"); - assertTrue(expectedDuplicateHeaders.equals(allDuplicateHeaders.get(DATASOURCE_CONFIG_ONLY))); + assertEquals(expectedDuplicateHeaders, allDuplicateHeaders.get(DATASOURCE_CONFIG_ONLY)); // Header duplicates in action config only expectedDuplicateHeaders = new HashSet<>(); expectedDuplicateHeaders.add("myHeader4"); expectedDuplicateHeaders.add("myHeader4"); - assertTrue(expectedDuplicateHeaders.equals(allDuplicateHeaders.get(ACTION_CONFIG_ONLY))); + assertEquals(expectedDuplicateHeaders, allDuplicateHeaders.get(ACTION_CONFIG_ONLY)); // Header duplicates with one instance in action and another in datasource config expectedDuplicateHeaders = new HashSet<>(); expectedDuplicateHeaders.add("myHeader3"); expectedDuplicateHeaders.add("apiKey"); - assertTrue(expectedDuplicateHeaders.equals(allDuplicateHeaders.get(DATASOURCE_AND_ACTION_CONFIG))); + assertEquals(expectedDuplicateHeaders, allDuplicateHeaders.get(DATASOURCE_AND_ACTION_CONFIG)); /* Test duplicate query params in action + datasource config */ Map> allDuplicateParams = @@ -755,17 +851,17 @@ public class GraphQLPluginTest { expectedDuplicateParams = new HashSet<>(); expectedDuplicateParams.add("myParam1"); expectedDuplicateParams.add("myParam2"); - assertTrue(expectedDuplicateParams.equals(allDuplicateParams.get(DATASOURCE_CONFIG_ONLY))); + assertEquals(expectedDuplicateParams, allDuplicateParams.get(DATASOURCE_CONFIG_ONLY)); // Query param duplicates in action config only expectedDuplicateParams = new HashSet<>(); expectedDuplicateParams.add("myParam4"); - assertTrue(expectedDuplicateParams.equals(allDuplicateParams.get(ACTION_CONFIG_ONLY))); + assertEquals(expectedDuplicateParams, allDuplicateParams.get(ACTION_CONFIG_ONLY)); // Query param duplicates in action + datasource config expectedDuplicateParams = new HashSet<>(); expectedDuplicateParams.add("myParam3"); - assertTrue(expectedDuplicateParams.equals(allDuplicateParams.get(DATASOURCE_AND_ACTION_CONFIG))); + assertEquals(expectedDuplicateParams, allDuplicateParams.get(DATASOURCE_AND_ACTION_CONFIG)); } @Test @@ -798,7 +894,7 @@ public class GraphQLPluginTest { // Header duplicates with one instance in action and another in datasource config HashSet expectedDuplicateHeaders = new HashSet<>(); expectedDuplicateHeaders.add("Authorization"); - assertTrue(expectedDuplicateHeaders.equals(allDuplicateHeaders.get(DATASOURCE_AND_ACTION_CONFIG))); + assertEquals(expectedDuplicateHeaders, allDuplicateHeaders.get(DATASOURCE_AND_ACTION_CONFIG)); } @Test @@ -833,7 +929,7 @@ public class GraphQLPluginTest { // Param duplicates with one instance in action and another in datasource config HashSet expectedDuplicateParams = new HashSet<>(); expectedDuplicateParams.add("access_token"); - assertTrue(expectedDuplicateParams.equals(allDuplicateParams.get(DATASOURCE_AND_ACTION_CONFIG))); + assertEquals(expectedDuplicateParams, allDuplicateParams.get(DATASOURCE_AND_ACTION_CONFIG)); } /** @@ -872,7 +968,7 @@ public class GraphQLPluginTest { " because this datasource has duplicate definition(s) for header(s): [myHeader1]. Please " + "remove the duplicate definition(s) to resolve this warning. Please note that some of the" + " authentication mechanisms also implicitly define a header."); - assertTrue(expectedDatasourceHintMessages.equals(datasourceHintMessages)); + assertEquals(expectedDatasourceHintMessages, datasourceHintMessages); Set actionHintMessages = tuple.getT2(); Set expectedActionHintMessages = new HashSet<>(); @@ -883,7 +979,7 @@ public class GraphQLPluginTest { expectedActionHintMessages.add("Your API query may not run as expected because its datasource has" + " duplicate definition(s) for header(s): [myHeader1]. Please remove the duplicate " + "definition(s) from the datasource to resolve this warning."); - assertTrue(expectedActionHintMessages.equals(actionHintMessages)); + assertEquals(expectedActionHintMessages, actionHintMessages); }) .verifyComplete(); } @@ -927,7 +1023,7 @@ public class GraphQLPluginTest { expectedActionHintMessages.add("Your API query may not run as expected because it has duplicate " + "definition(s) for param(s): [myParam1]. Please remove the duplicate definition(s) from " + "the 'Params' tab to resolve this warning."); - assertTrue(expectedActionHintMessages.equals(actionHintMessages)); + assertEquals(expectedActionHintMessages, actionHintMessages); }) .verifyComplete(); } @@ -965,7 +1061,7 @@ public class GraphQLPluginTest { " the 'Headers' section of either the API query or the datasource. Please note that some " + "of the authentication mechanisms also implicitly define a header."); - assertTrue(expectedActionHintMessages.equals(actionHintMessages)); + assertEquals(expectedActionHintMessages, actionHintMessages); }) .verifyComplete(); } @@ -1004,7 +1100,7 @@ public class GraphQLPluginTest { " the 'Params' section of either the API query or the datasource. Please note that some " + "of the authentication mechanisms also implicitly define a param."); - assertTrue(expectedActionHintMessages.equals(actionHintMessages)); + assertEquals(expectedActionHintMessages, actionHintMessages); }) .verifyComplete(); } @@ -1012,7 +1108,14 @@ public class GraphQLPluginTest { @Test public void testQueryParamsInDatasource() { DatasourceConfiguration dsConfig = getDefaultDatasourceConfig(); - dsConfig.setUrl("https://postman-echo.com/post"); + + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = getDefaultActionConfiguration(); actionConfig.setEncodeParamsToggle(true); @@ -1028,9 +1131,18 @@ public class GraphQLPluginTest { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - String expected_url = "\"https://postman-echo.com/post?query_key=query+val\""; - JsonNode url = ((ObjectNode) result.getBody()).get("url"); - assertEquals(expected_url, url.toString()); + try { + RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + + assert recordedRequest != null; + String recordedRequestPath = recordedRequest.getPath(); + + String expectedUrl = "/?query_key=query+val"; + assertEquals(expectedUrl, recordedRequestPath); + } catch (InterruptedException e) { + assert false : e.getMessage(); + } + }) .verifyComplete(); } @@ -1088,7 +1200,7 @@ public class GraphQLPluginTest { actionConfig.setPaginationType(PaginationType.CURSOR); actionConfig.getPluginSpecifiedTemplates().get(QUERY_VARIABLES_INDEX).setValue("{}"); - Map paginationDataMap = new HashMap(); + Map paginationDataMap = new HashMap<>(); setValueSafelyInFormData(paginationDataMap, "cursorBased.next.limit.name", "first"); setValueSafelyInFormData(paginationDataMap, "cursorBased.next.limit.value", "3"); setValueSafelyInFormData(paginationDataMap, "cursorBased.next.cursor.name", "endCursor"); @@ -1118,7 +1230,7 @@ public class GraphQLPluginTest { actionConfig.setPaginationType(PaginationType.CURSOR); actionConfig.getPluginSpecifiedTemplates().get(QUERY_VARIABLES_INDEX).setValue("{}"); - Map paginationDataMap = new HashMap(); + Map paginationDataMap = new HashMap<>(); setValueSafelyInFormData(paginationDataMap, "cursorBased.previous.limit.name", "last"); setValueSafelyInFormData(paginationDataMap, "cursorBased.previous.limit.value", "3"); setValueSafelyInFormData(paginationDataMap, "cursorBased.previous.cursor.name", "startCursor"); @@ -1150,7 +1262,7 @@ public class GraphQLPluginTest { actionConfig.setPaginationType(PaginationType.CURSOR); actionConfig.getPluginSpecifiedTemplates().get(QUERY_VARIABLES_INDEX).setValue("{}"); - Map paginationDataMap = new HashMap(); + Map paginationDataMap = new HashMap<>(); setValueSafelyInFormData(paginationDataMap, "cursorBased.next.limit.name", "first"); setValueSafelyInFormData(paginationDataMap, "cursorBased.next.limit.value", "3"); setValueSafelyInFormData(paginationDataMap, "cursorBased.next.cursor.name", "endCursor"); @@ -1171,11 +1283,14 @@ public class GraphQLPluginTest { @Test public void verifyUniquenessOfGraphQLPluginErrorCode() { - assert (Arrays.stream(GraphQLPluginError.values()).map(GraphQLPluginError::getAppErrorCode).distinct().count() == GraphQLPluginError.values().length); + assertEquals(GraphQLPluginError.values().length, Arrays.stream(GraphQLPluginError.values()).map(GraphQLPluginError::getAppErrorCode).distinct().count()); - assert (Arrays.stream(GraphQLPluginError.values()).map(GraphQLPluginError::getAppErrorCode) - .filter(appErrorCode-> appErrorCode.length() != 11 || !appErrorCode.startsWith("PE-GQL")) - .collect(Collectors.toList()).size() == 0); + assertEquals(0, + Arrays.stream(GraphQLPluginError.values()) + .map(GraphQLPluginError::getAppErrorCode) + .filter(appErrorCode -> appErrorCode.length() != 11 || !appErrorCode.startsWith("PE-GQL")) + .toList() + .size()); } diff --git a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java index b9a3bb4bf5..6879928d7a 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java @@ -8,21 +8,17 @@ import com.appsmith.external.helpers.restApiUtils.helpers.HintMessageUtils; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionRequest; import com.appsmith.external.models.ActionExecutionResult; -import com.appsmith.external.models.ApiContentType; import com.appsmith.external.models.ApiKeyAuth; import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.OAuth2; -import com.appsmith.external.models.PaginationType; import com.appsmith.external.models.PaginationField; +import com.appsmith.external.models.PaginationType; import com.appsmith.external.models.Param; import com.appsmith.external.models.Property; import com.appsmith.external.services.SharedConfig; import com.external.plugins.exceptions.RestApiPluginError; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; @@ -31,10 +27,9 @@ import io.jsonwebtoken.security.SignatureException; import mockwebserver3.MockResponse; import mockwebserver3.MockWebServer; import mockwebserver3.RecordedRequest; -import net.minidev.json.JSONObject; -import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; import okhttp3.HttpUrl; +import okio.Buffer; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; @@ -45,10 +40,13 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuple2; import javax.crypto.SecretKey; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -56,8 +54,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Arrays; -import java.util.stream.Collectors; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.zip.GZIPOutputStream; import static com.appsmith.external.constants.Authentication.API_KEY; import static com.appsmith.external.constants.Authentication.OAUTH2; @@ -77,7 +76,9 @@ public class RestApiPluginTest { private static HintMessageUtils hintMessageUtils; - public class MockSharedConfig implements SharedConfig { + private static MockWebServer mockEndpoint; + + public static class MockSharedConfig implements SharedConfig { @Override public int getCodecSize() { @@ -98,14 +99,29 @@ public class RestApiPluginTest { RestApiPlugin.RestApiPluginExecutor pluginExecutor = new RestApiPlugin.RestApiPluginExecutor(new MockSharedConfig()); @BeforeEach - public void setUp() { + public void setUp() throws IOException { hintMessageUtils = new HintMessageUtils(); + mockEndpoint = new MockWebServer(); + mockEndpoint.start(); } + @AfterEach + public void tearDown() throws IOException { + mockEndpoint.shutdown(); + } + @Test public void testValidJsonApiExecution() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); + ActionConfiguration actionConfig = new ActionConfiguration(); final List headers = List.of(new Property("content-type", "application/json")); @@ -119,10 +135,24 @@ public class RestApiPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - JsonNode data = ((ObjectNode) result.getBody()).get("data"); - assertEquals(requestBody, data.toString()); + + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + assertEquals(requestBody, new String(bodyBytes)); + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); + } + + final ActionExecutionRequest request = result.getRequest(); - assertEquals("https://postman-echo.com/post", request.getUrl()); + assertEquals(baseUrl, request.getUrl()); assertEquals(HttpMethod.POST, request.getHttpMethod()); assertEquals(requestBody, request.getBody().toString()); final Iterator> fields = ((ObjectNode) result.getRequest().getHeaders()).fields(); @@ -138,8 +168,15 @@ public class RestApiPluginTest { @Test public void testValidApiExecutionWithWhitespacesInUrl() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); + + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); //added whitespaces in url to validate successful execution of the same - dsConfig.setUrl(" https://postman-echo.com/post "); + dsConfig.setUrl(" " + baseUrl + " "); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = new ActionConfiguration(); final List headers = List.of(new Property("content-type", "application/json")); @@ -159,13 +196,11 @@ public class RestApiPluginTest { @Test public void testExecuteApiWithPaginationForPreviousUrl() throws IOException { - MockWebServer mockWebServer = new MockWebServer(); - MockResponse mockRedirectResponse = new MockResponse() - .setResponseCode(200); - mockWebServer.enqueue(mockRedirectResponse); - mockWebServer.start(); + MockResponse mockRedirectResponse = new MockResponse().setResponseCode(200); + mockEndpoint.enqueue(mockRedirectResponse); + mockEndpoint.start(); - HttpUrl mockHttpUrl = mockWebServer.url("/mock"); + HttpUrl mockHttpUrl = mockEndpoint.url("/mock"); String previousUrl = mockHttpUrl + "?pageSize=1&page=2&mock_filter=abc 11"; String nextUrl = mockHttpUrl + "?pageSize=1&page=4&mock_filter=abc 11"; @@ -180,9 +215,9 @@ public class RestApiPluginTest { final List headers = List.of(); final List queryParameters = List.of( - new Property("mock_filter","abc 11"), - new Property("pageSize","1"), - new Property("page","3") + new Property("mock_filter", "abc 11"), + new Property("pageSize", "1"), + new Property("page", "3") ); ActionConfiguration actionConfig = new ActionConfiguration(); @@ -200,18 +235,19 @@ public class RestApiPluginTest { actionConfig.setEncodeParamsToggle(true); - actionConfig.setPluginSpecifiedTemplates(List.of(new Property(null,true))); + actionConfig.setPluginSpecifiedTemplates(List.of(new Property(null, true))); - actionConfig.setFormData(Collections.singletonMap("apiContentType","none")); + actionConfig.setFormData(Collections.singletonMap("apiContentType", "none")); Mono resultMono = pluginExecutor.executeParameterized(null, executeActionDTO, dsConfig, actionConfig); StepVerifier.create(resultMono) .assertNext(result -> { try { - RecordedRequest recordedRequest = mockWebServer.takeRequest(); + RecordedRequest recordedRequest = mockEndpoint.takeRequest(); HttpUrl requestUrl = recordedRequest.getRequestUrl(); String encodedPreviousUrl = mockHttpUrl + "?pageSize=1&page=2&mock_filter=abc+11"; + assert requestUrl != null; assertEquals(encodedPreviousUrl, requestUrl.toString()); } catch (InterruptedException e) { fail("Mock web server failed to capture request."); @@ -222,13 +258,11 @@ public class RestApiPluginTest { @Test public void testExecuteApiWithPaginationForPreviousEncodedUrl() throws IOException { - MockWebServer mockWebServer = new MockWebServer(); - MockResponse mockRedirectResponse = new MockResponse() - .setResponseCode(200); - mockWebServer.enqueue(mockRedirectResponse); - mockWebServer.start(); + MockResponse mockRedirectResponse = new MockResponse().setResponseCode(200); + mockEndpoint.enqueue(mockRedirectResponse); + mockEndpoint.start(); - HttpUrl mockHttpUrl = mockWebServer.url("/mock"); + HttpUrl mockHttpUrl = mockEndpoint.url("/mock"); String previousUrl = URLEncoder.encode(mockHttpUrl + "?pageSize=1&page=2&mock_filter=abc 11", StandardCharsets.UTF_8); @@ -244,9 +278,9 @@ public class RestApiPluginTest { final List headers = List.of(); final List queryParameters = List.of( - new Property("mock_filter","abc 11"), - new Property("pageSize","1"), - new Property("page","3") + new Property("mock_filter", "abc 11"), + new Property("pageSize", "1"), + new Property("page", "3") ); ActionConfiguration actionConfig = new ActionConfiguration(); @@ -264,18 +298,19 @@ public class RestApiPluginTest { actionConfig.setEncodeParamsToggle(true); - actionConfig.setPluginSpecifiedTemplates(List.of(new Property(null,true))); + actionConfig.setPluginSpecifiedTemplates(List.of(new Property(null, true))); - actionConfig.setFormData(Collections.singletonMap("apiContentType","none")); + actionConfig.setFormData(Collections.singletonMap("apiContentType", "none")); Mono resultMono = pluginExecutor.executeParameterized(null, executeActionDTO, dsConfig, actionConfig); StepVerifier.create(resultMono) .assertNext(result -> { try { - RecordedRequest recordedRequest = mockWebServer.takeRequest(); + RecordedRequest recordedRequest = mockEndpoint.takeRequest(); HttpUrl requestUrl = recordedRequest.getRequestUrl(); String encodedPreviousUrl = mockHttpUrl + "?pageSize=1&page=2&mock_filter=abc+11"; + assert requestUrl != null; assertEquals(encodedPreviousUrl, requestUrl.toString()); } catch (InterruptedException e) { fail("Mock web server failed to capture request."); @@ -286,13 +321,11 @@ public class RestApiPluginTest { @Test public void testExecuteApiWithPaginationForNextUrl() throws IOException { - MockWebServer mockWebServer = new MockWebServer(); - MockResponse mockRedirectResponse = new MockResponse() - .setResponseCode(200); - mockWebServer.enqueue(mockRedirectResponse); - mockWebServer.start(); + MockResponse mockRedirectResponse = new MockResponse().setResponseCode(200); + mockEndpoint.enqueue(mockRedirectResponse); + mockEndpoint.start(); - HttpUrl mockHttpUrl = mockWebServer.url("/mock"); + HttpUrl mockHttpUrl = mockEndpoint.url("/mock"); String previousUrl = mockHttpUrl + "?pageSize=1&page=2&mock_filter=abc 11"; String nextUrl = mockHttpUrl + "?pageSize=1&page=4&mock_filter=abc 11"; @@ -307,9 +340,9 @@ public class RestApiPluginTest { final List headers = List.of(); final List queryParameters = List.of( - new Property("mock_filter","abc 11"), - new Property("pageSize","1"), - new Property("page","3") + new Property("mock_filter", "abc 11"), + new Property("pageSize", "1"), + new Property("page", "3") ); ActionConfiguration actionConfig = new ActionConfiguration(); @@ -327,18 +360,19 @@ public class RestApiPluginTest { actionConfig.setEncodeParamsToggle(true); - actionConfig.setPluginSpecifiedTemplates(List.of(new Property(null,true))); + actionConfig.setPluginSpecifiedTemplates(List.of(new Property(null, true))); - actionConfig.setFormData(Collections.singletonMap("apiContentType","none")); + actionConfig.setFormData(Collections.singletonMap("apiContentType", "none")); Mono resultMono = pluginExecutor.executeParameterized(null, executeActionDTO, dsConfig, actionConfig); StepVerifier.create(resultMono) .assertNext(result -> { try { - RecordedRequest recordedRequest = mockWebServer.takeRequest(); + RecordedRequest recordedRequest = mockEndpoint.takeRequest(); HttpUrl requestUrl = recordedRequest.getRequestUrl(); String encodedNextUrl = mockHttpUrl + "?pageSize=1&page=4&mock_filter=abc+11"; + assert requestUrl != null; assertEquals(encodedNextUrl, requestUrl.toString()); } catch (InterruptedException e) { fail("Mock web server failed to capture request."); @@ -349,13 +383,11 @@ public class RestApiPluginTest { @Test public void testExecuteApiWithPaginationForNextEncodedUrl() throws IOException { - MockWebServer mockWebServer = new MockWebServer(); - MockResponse mockRedirectResponse = new MockResponse() - .setResponseCode(200); - mockWebServer.enqueue(mockRedirectResponse); - mockWebServer.start(); + MockResponse mockRedirectResponse = new MockResponse().setResponseCode(200); + mockEndpoint.enqueue(mockRedirectResponse); + mockEndpoint.start(); - HttpUrl mockHttpUrl = mockWebServer.url("/mock"); + HttpUrl mockHttpUrl = mockEndpoint.url("/mock"); String previousUrl = mockHttpUrl + "?pageSize=1&page=2&mock_filter=abc 11"; String nextUrl = URLEncoder.encode(mockHttpUrl + "?pageSize=1&page=4&mock_filter=abc 11", StandardCharsets.UTF_8); @@ -370,9 +402,9 @@ public class RestApiPluginTest { final List headers = List.of(); final List queryParameters = List.of( - new Property("mock_filter","abc 11"), - new Property("pageSize","1"), - new Property("page","3") + new Property("mock_filter", "abc 11"), + new Property("pageSize", "1"), + new Property("page", "3") ); ActionConfiguration actionConfig = new ActionConfiguration(); @@ -390,18 +422,19 @@ public class RestApiPluginTest { actionConfig.setEncodeParamsToggle(true); - actionConfig.setPluginSpecifiedTemplates(List.of(new Property(null,true))); + actionConfig.setPluginSpecifiedTemplates(List.of(new Property(null, true))); - actionConfig.setFormData(Collections.singletonMap("apiContentType","none")); + actionConfig.setFormData(Collections.singletonMap("apiContentType", "none")); Mono resultMono = pluginExecutor.executeParameterized(null, executeActionDTO, dsConfig, actionConfig); StepVerifier.create(resultMono) .assertNext(result -> { try { - RecordedRequest recordedRequest = mockWebServer.takeRequest(); + RecordedRequest recordedRequest = mockEndpoint.takeRequest(); HttpUrl requestUrl = recordedRequest.getRequestUrl(); String encodedNextUrl = mockHttpUrl + "?pageSize=1&page=4&mock_filter=abc+11"; + assert requestUrl != null; assertEquals(encodedNextUrl, requestUrl.toString()); } catch (InterruptedException e) { fail("Mock web server failed to capture request."); @@ -413,7 +446,14 @@ public class RestApiPluginTest { @Test public void testValidFormApiExecution() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); + ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setHeaders(List.of(new Property("content-type", "application/x-www-form-urlencoded"))); @@ -429,8 +469,21 @@ public class RestApiPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - JsonNode data = ((ObjectNode) result.getBody()).get("form"); - assertEquals("{\"key\":\"value\",\"key1\":\"value1\"}", data.toString()); + + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + assertEquals("key=value&key1=value1", new String(bodyBytes)); + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); + } + assertEquals("key=value&key1=value1", result.getRequest().getBody()); }) .verifyComplete(); @@ -439,7 +492,15 @@ public class RestApiPluginTest { @Test public void testValidRawApiExecution() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); + ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setHeaders(List.of(new Property("content-type", "text/plain;charset=UTF-8"))); @@ -453,8 +514,20 @@ public class RestApiPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - JsonNode data = ((ObjectNode) result.getBody()).get("data"); - assertEquals("\"{\\\"key\\\":\\\"value\\\"}\"", data.toString()); + + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + assertEquals(requestBody, new String(bodyBytes)); + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); + } }) .verifyComplete(); } @@ -462,7 +535,15 @@ public class RestApiPluginTest { @Test public void testValidSignature() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("http://httpbin.org/headers"); + + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); + final String secretKey = "a-random-key-that-should-be-32-chars-long-at-least"; dsConfig.setProperties(List.of( @@ -478,22 +559,30 @@ public class RestApiPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - String token = ((ObjectNode) result.getBody()).get("headers").get("X-Appsmith-Signature").asText(); - final SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); - final String issuer = Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody() - .getIssuer(); - assertEquals("Appsmith", issuer); - final Iterator> fields = ((ObjectNode) result.getRequest().getHeaders()).fields(); - fields.forEachRemaining(field -> { - if ("X-Appsmith-Signature".equalsIgnoreCase(field.getKey())) { - assertEquals(token, field.getValue().get(0).asText()); - } - }); + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + + String token = recordedRequest.getHeaders().get("X-Appsmith-Signature"); + final SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + final String issuer = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody() + .getIssuer(); + assertEquals("Appsmith", issuer); + final Iterator> fields = ((ObjectNode) result.getRequest().getHeaders()).fields(); + fields.forEachRemaining(field -> { + if ("X-Appsmith-Signature".equalsIgnoreCase(field.getKey())) { + assertEquals(token, field.getValue().get(0).asText()); + } + }); + + } catch (InterruptedException e) { + assert false : e.getMessage(); + } }) .verifyComplete(); @@ -511,15 +600,15 @@ public class RestApiPluginTest { param.setPseudoBindingName("k0"); executeActionDTO.setParams(Collections.singletonList(param)); - executeActionDTO.setParamProperties(Collections.singletonMap("k0","string")); - executeActionDTO.setParameterMap(Collections.singletonMap("Input1.text","k0")); - executeActionDTO.setInvertParameterMap(Collections.singletonMap("k0","Input1.text")); + executeActionDTO.setParamProperties(Collections.singletonMap("k0", "string")); + executeActionDTO.setParameterMap(Collections.singletonMap("Input1.text", "k0")); + executeActionDTO.setInvertParameterMap(Collections.singletonMap("k0", "Input1.text")); DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); datasourceConfiguration.setUrl("https://postman-echo.com/get"); final List headers = List.of( - new Property("content-type",MediaType.TEXT_PLAIN_VALUE)); + new Property("content-type", MediaType.TEXT_PLAIN_VALUE)); final List queryParameters = List.of(); @@ -534,13 +623,13 @@ public class RestApiPluginTest { actionConfiguration.setEncodeParamsToggle(true); - actionConfiguration.setPluginSpecifiedTemplates(List.of(new Property(null,true))); + actionConfiguration.setPluginSpecifiedTemplates(List.of(new Property(null, true))); actionConfiguration.setFormData(Collections.singletonMap("apiContentType", MediaType.TEXT_PLAIN_VALUE)); - String[] requestBodyList = {"abc is equals to {{Input1.text}}","{ \"abc\": {{Input1.text}} }",""}; + String[] requestBodyList = {"abc is equals to {{Input1.text}}", "{ \"abc\": {{Input1.text}} }", ""}; - String[] finalRequestBodyList = {"abc is equals to \"123\"","{ \"abc\": \"123\" }",""}; + String[] finalRequestBodyList = {"abc is equals to \"123\"", "{ \"abc\": \"123\" }", ""}; for (int requestBodyIndex = 0; requestBodyIndex < requestBodyList.length; requestBodyIndex++) { @@ -556,15 +645,12 @@ public class RestApiPluginTest { JsonNode args = body.get("args"); int index = 0; StringBuilder actualRequestBody = new StringBuilder(); - while (true) { - if (!args.has(String.valueOf(index))) { - break; - } + while (args.has(String.valueOf(index))) { JsonNode ans = args.get(String.valueOf(index)); index++; actualRequestBody.append(ans.asText()); } - assertEquals(finalRequestBodyList[currentIndex],actualRequestBody.toString()); + assertEquals(finalRequestBodyList[currentIndex], actualRequestBody.toString()); final ActionExecutionRequest request = result.getRequest(); assertEquals(HttpMethod.GET, request.getHttpMethod()); }) @@ -575,7 +661,13 @@ public class RestApiPluginTest { @Test public void testInvalidSignature() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("http://httpbin.org/headers"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); final String secretKey = "a-random-key-that-should-be-32-chars-long-at-least"; dsConfig.setProperties(List.of( @@ -591,12 +683,20 @@ public class RestApiPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - String token = ((ObjectNode) result.getBody()).get("headers").get("X-Appsmith-Signature").asText(); - final SecretKey key = Keys.hmacShaKeyFor((secretKey + "-abc").getBytes(StandardCharsets.UTF_8)); - final JwtParser parser = Jwts.parserBuilder().setSigningKey(key).build(); + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + String token = recordedRequest.getHeaders().get("X-Appsmith-Signature"); + + final SecretKey key = Keys.hmacShaKeyFor((secretKey + "-abc").getBytes(StandardCharsets.UTF_8)); + final JwtParser parser = Jwts.parserBuilder().setSigningKey(key).build(); + + assertThrows(SignatureException.class, () -> parser.parseClaimsJws(token)); + } catch (InterruptedException e) { + assert false : e.getMessage(); + } - assertThrows(SignatureException.class, () -> parser.parseClaimsJws(token)); }) .verifyComplete(); } @@ -604,7 +704,14 @@ public class RestApiPluginTest { @Test public void testEncodeParamsToggleOn() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setHeaders(List.of(new Property("content-type", "application/json"))); @@ -624,9 +731,23 @@ public class RestApiPluginTest { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - String expected_url = "\"https://postman-echo.com/post?query_key=query+val\""; - JsonNode url = ((ObjectNode) result.getBody()).get("url"); - assertEquals(expected_url, url.toString()); + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + assertEquals(requestBody, new String(bodyBytes)); + + String requestPath = recordedRequest.getPath(); + assertEquals("/?query_key=query+val", requestPath); + + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); + } }) .verifyComplete(); } @@ -634,7 +755,13 @@ public class RestApiPluginTest { @Test public void testEncodeParamsToggleNull() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setHeaders(List.of(new Property("content-type", "application/json"))); @@ -654,9 +781,23 @@ public class RestApiPluginTest { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - String expected_url = "\"https://postman-echo.com/post?query_key=query+val\""; - JsonNode url = ((ObjectNode) result.getBody()).get("url"); - assertEquals(expected_url, url.toString()); + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + assertEquals(requestBody, new String(bodyBytes)); + + String requestPath = recordedRequest.getPath(); + assertEquals("/?query_key=query+val", requestPath); + + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); + } }) .verifyComplete(); } @@ -664,7 +805,13 @@ public class RestApiPluginTest { @Test public void testEncodeParamsToggleOff() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setHeaders(List.of(new Property("content-type", "application/json"))); @@ -683,9 +830,8 @@ public class RestApiPluginTest { dsConfig, actionConfig)); StepVerifier.create(resultMono) - .assertNext(actionExecutionResult -> { - assertTrue(actionExecutionResult.getPluginErrorDetails().getDownstreamErrorMessage().contains("Invalid character ' ' for QUERY_PARAM in \"query val\"")); - }); + .assertNext(actionExecutionResult -> assertTrue(actionExecutionResult.getPluginErrorDetails().getDownstreamErrorMessage().contains("Invalid character ' ' for QUERY_PARAM in \"query val\""))) + .verifyComplete(); } @Test @@ -706,20 +852,27 @@ public class RestApiPluginTest { @Test public void testSmartSubstitutionJSONBody() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setHeaders(List.of(new Property("content-type", "application/json"))); actionConfig.setHttpMethod(HttpMethod.POST); - String requestBody = "{\n" + - "\t\"name\" : {{Input1.text}},\n" + - "\t\"email\" : {{Input2.text}},\n" + - "\t\"username\" : {{Input3.text}},\n" + - "\t\"password\" : \"{{Input4.text}}\",\n" + - "\t\"newField\" : \"{{Input5.text}}\",\n" + - "\t\"tableRow\" : {{Table1.selectedRow}},\n" + - "\t\"table\" : \"{{Table1.tableData}}\"\n" + - "}"; + String requestBody = """ + { + \t"name" : {{Input1.text}}, + \t"email" : {{Input2.text}}, + \t"username" : {{Input3.text}}, + \t"password" : "{{Input4.text}}", + \t"newField" : "{{Input5.text}}", + \t"tableRow" : {{Table1.selectedRow}}, + \t"table" : "{{Table1.tableData}}" + }"""; actionConfig.setBody(requestBody); List pluginSpecifiedTemplates = new ArrayList<>(); pluginSpecifiedTemplates.add(new Property("jsonSmartSubstitution", "true")); @@ -770,42 +923,46 @@ public class RestApiPluginTest { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); String resultBody = "{\"password\":\"12/01/2018\",\"name\":\"this is a string! Yay :D\",\"newField\":null,\"tableRow\":{\"orderAmount\":4.99,\"id\":2381224,\"userName\":\"Michael Lawson\",\"email\":\"michael.lawson@reqres.in\",\"productName\":\"Chicken Sandwich\"},\"email\":true,\"table\":[{\"orderAmount\":4.99,\"id\":2381224,\"userName\":\"Michael Lawson\",\"email\":\"michael.lawson@reqres.in\",\"productName\":\"Chicken Sandwich\"},{\"orderAmount\":9.99,\"id\":2736212,\"userName\":\"Lindsay Ferguson\",\"email\":\"lindsay.ferguson@reqres.in\",\"productName\":\"Tuna Salad\"},{\"orderAmount\":19.99,\"id\":6788734,\"userName\":\"Tobias Funke\",\"email\":\"tobias.funke@reqres.in\",\"productName\":\"Beef steak\"}],\"username\":0}"; - JSONParser jsonParser = new JSONParser(JSONParser.MODE_PERMISSIVE); - ObjectMapper objectMapper = new ObjectMapper(); + try { - JSONObject resultJson = (JSONObject) jsonParser.parse(String.valueOf(result.getBody())); - Object resultData = resultJson.get("json"); - String parsedJsonAsString = objectMapper.writeValueAsString(resultData); - assertEquals(resultBody, parsedJsonAsString); - } catch (ParseException | JsonProcessingException e) { - e.printStackTrace(); + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + assertEquals(resultBody, new String(bodyBytes)); + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); } // Assert the debug request parameters are getting set. ActionExecutionRequest request = result.getRequest(); List> parameters = (List>) request.getProperties().get("smart-substitution-parameters"); - assertEquals(parameters.size(), 7); + assertEquals(7, parameters.size()); Map.Entry parameterEntry = parameters.get(0); - assertEquals(parameterEntry.getKey(), "this is a string! Yay :D"); - assertEquals(parameterEntry.getValue(), "STRING"); + assertEquals("this is a string! Yay :D", parameterEntry.getKey()); + assertEquals("STRING", parameterEntry.getValue()); parameterEntry = parameters.get(1); - assertEquals(parameterEntry.getKey(), "true"); - assertEquals(parameterEntry.getValue(), "BOOLEAN"); + assertEquals("true", parameterEntry.getKey()); + assertEquals("BOOLEAN", parameterEntry.getValue()); parameterEntry = parameters.get(2); - assertEquals(parameterEntry.getKey(), "0"); - assertEquals(parameterEntry.getValue(), "INTEGER"); + assertEquals("0", parameterEntry.getKey()); + assertEquals("INTEGER", parameterEntry.getValue()); parameterEntry = parameters.get(3); - assertEquals(parameterEntry.getKey(), "12/01/2018"); - assertEquals(parameterEntry.getValue(), "STRING"); + assertEquals("12/01/2018", parameterEntry.getKey()); + assertEquals("STRING", parameterEntry.getValue()); parameterEntry = parameters.get(4); - assertEquals(parameterEntry.getKey(), "null"); - assertEquals(parameterEntry.getValue(), "NULL"); + assertEquals("null", parameterEntry.getKey()); + assertEquals("NULL", parameterEntry.getValue()); }) .verifyComplete(); } @@ -813,13 +970,18 @@ public class RestApiPluginTest { @Test public void testMultipartFormData() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("http://httpbin.org/post"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setAutoGeneratedHeaders(List.of(new Property("content-type", "multipart/form-data"))); actionConfig.setHttpMethod(HttpMethod.POST); - String requestBody = "{\"key1\":\"onlyValue\"}"; final Property key1 = new Property("key1", "onlyValue"); final Property key2 = new Property("key2", "{\"name\":\"fileName\", \"type\":\"application/json\", \"data\":{\"key\":\"value\"}}"); final Property key3 = new Property("key3", "[{\"name\":\"fileName2\", \"type\":\"application/json\", \"data\":{\"key2\":\"value2\"}}]"); @@ -839,10 +1001,39 @@ public class RestApiPluginTest { "key2", "", "key3", ""), result.getRequest().getBody()); - JsonNode formDataResponse = ((ObjectNode) result.getBody()).get("form"); - assertEquals(requestBody, formDataResponse.toString()); - JsonNode fileDataResponse = ((ObjectNode) result.getBody()).get("files"); - assertEquals("{\"key2\":\"{key=value}\",\"key3\":\"{key2=value2}\"}", fileDataResponse.toString()); + + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + String bodyString = new String(bodyBytes); + + assertTrue(bodyString.contains(""" + Content-Disposition: form-data; name="key1"\r + Content-Type: text/plain;charset=UTF-8\r + Content-Length: 9\r + \r + onlyValue""")); + + assertTrue(bodyString.contains(""" + Content-Disposition: form-data; name="key2"; filename="fileName"\r + Content-Type: application/json\r + \r + {key=value}""")); + + assertTrue(bodyString.contains(""" + Content-Disposition: form-data; name="key3"; filename="fileName2"\r + Content-Type: application/json\r + \r + {key2=value2}""")); + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); + } }) .verifyComplete(); } @@ -850,19 +1041,26 @@ public class RestApiPluginTest { @Test public void testParsingBodyWithInvalidJSONHeader() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://mock-api.appsmith.com/echo/raw"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("invalid json text") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setHeaders(List.of(new Property("content-type", "application/json"))); actionConfig.setHttpMethod(HttpMethod.POST); - String requestBody = "{\n" + - " \"headers\": {\n" + - " \"Content-Type\": \"application/json\",\n" + - " \"X-RANDOM-HEADER\": \"random-value\"\n" + - " },\n" + - " \"body\": \"invalid json text\"\n" + - "}"; + String requestBody = """ + { + "headers": { + "Content-Type": "application/json", + "X-RANDOM-HEADER": "random-value" + }, + "body": "invalid json text" + }"""; actionConfig.setBody(requestBody); Mono resultMono = pluginExecutor.executeParameterized(null, new ExecuteActionDTO(), dsConfig, actionConfig); @@ -870,9 +1068,25 @@ public class RestApiPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); + + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + assertEquals("{\"headers\":{\"X-RANDOM-HEADER\":\"random-value\",\"Content-Type\":\"application/json\"},\"body\":\"invalid json text\"}", + new String(bodyBytes)); + + String contentType = recordedRequest.getHeaders().get("Content-Type"); + assertEquals("application/json", contentType); + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); + } assertEquals("invalid json text", result.getBody()); - ArrayNode data = (ArrayNode) result.getHeaders().get("Content-Type"); - assertEquals("application/json; charset=utf-8", data.get(0).asText()); assertEquals(1, result.getMessages().size()); String expectedMessage = "The response returned by this API is not a valid JSON. Please " + @@ -888,7 +1102,14 @@ public class RestApiPluginTest { @Test public void testRequestWithApiKeyHeader() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); + AuthenticationDTO authenticationDTO = new ApiKeyAuth(ApiKeyAuth.Type.HEADER, "api_key", "Token", "test"); dsConfig.setAuthentication(authenticationDTO); @@ -897,7 +1118,7 @@ public class RestApiPluginTest { new Property("content-type", "application/json"), new Property(HttpHeaders.AUTHORIZATION, "auth-value") )); - actionConfig.setAutoGeneratedHeaders(List.of(new Property("content-type","application/json"))); + actionConfig.setAutoGeneratedHeaders(List.of(new Property("content-type", "application/json"))); actionConfig.setHttpMethod(HttpMethod.POST); String requestBody = "{\"key\":\"value\"}"; @@ -923,15 +1144,22 @@ public class RestApiPluginTest { @Test public void testSmartSubstitutionEvaluatedValueContainingQuestionMark() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setHeaders(List.of(new Property("content-type", "application/json"))); actionConfig.setHttpMethod(HttpMethod.POST); - String requestBody = "{\n" + - "\t\"name\" : {{Input1.text}},\n" + - "\t\"email\" : {{Input2.text}},\n" + - "}"; + String requestBody = """ + { + \t"name" : {{Input1.text}}, + \t"email" : {{Input2.text}}, + }"""; actionConfig.setBody(requestBody); List pluginSpecifiedTemplates = new ArrayList<>(); pluginSpecifiedTemplates.add(new Property("jsonSmartSubstitution", "true")); @@ -956,16 +1184,19 @@ public class RestApiPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - String resultBody = "{\"name\":\"this is a string with a ? \",\"email\":\"email@email.com\"}"; - JSONParser jsonParser = new JSONParser(JSONParser.MODE_PERMISSIVE); - ObjectMapper objectMapper = new ObjectMapper(); + try { - JSONObject resultJson = (JSONObject) jsonParser.parse(String.valueOf(result.getBody())); - Object resultData = resultJson.get("json"); - String parsedJsonAsString = objectMapper.writeValueAsString(resultData); - assertEquals(resultBody, parsedJsonAsString); - } catch (ParseException | JsonProcessingException e) { - e.printStackTrace(); + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + String resultBody = "{\"name\":\"this is a string with a ? \",\"email\":\"email@email.com\"}"; + assertEquals(resultBody, new String(bodyBytes)); + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); } }) .verifyComplete(); @@ -1021,7 +1252,7 @@ public class RestApiPluginTest { Set expectedDuplicateHeaders = new HashSet<>(); expectedDuplicateHeaders.add("myHeader1"); expectedDuplicateHeaders.add("myHeader2"); - assertTrue(expectedDuplicateHeaders.equals(duplicateHeadersWithDsConfigOnly.get(DATASOURCE_CONFIG_ONLY))); + assertEquals(expectedDuplicateHeaders, duplicateHeadersWithDsConfigOnly.get(DATASOURCE_CONFIG_ONLY)); /* Test duplicate query params in datasource configuration only */ Map> duplicateParamsWithDsConfigOnly = @@ -1032,7 +1263,7 @@ public class RestApiPluginTest { Set expectedDuplicateParams = new HashSet<>(); expectedDuplicateParams.add("myParam1"); expectedDuplicateParams.add("myParam2"); - assertTrue(expectedDuplicateParams.equals(duplicateParamsWithDsConfigOnly.get(DATASOURCE_CONFIG_ONLY))); + assertEquals(expectedDuplicateParams, duplicateParamsWithDsConfigOnly.get(DATASOURCE_CONFIG_ONLY)); /* Test duplicate headers in datasource + action configuration */ Map> allDuplicateHeaders = @@ -1042,19 +1273,19 @@ public class RestApiPluginTest { expectedDuplicateHeaders = new HashSet<>(); expectedDuplicateHeaders.add("myHeader1"); expectedDuplicateHeaders.add("myHeader2"); - assertTrue(expectedDuplicateHeaders.equals(allDuplicateHeaders.get(DATASOURCE_CONFIG_ONLY))); + assertEquals(expectedDuplicateHeaders, allDuplicateHeaders.get(DATASOURCE_CONFIG_ONLY)); // Header duplicates in action config only expectedDuplicateHeaders = new HashSet<>(); expectedDuplicateHeaders.add("myHeader4"); expectedDuplicateHeaders.add("myHeader4"); - assertTrue(expectedDuplicateHeaders.equals(allDuplicateHeaders.get(ACTION_CONFIG_ONLY))); + assertEquals(expectedDuplicateHeaders, allDuplicateHeaders.get(ACTION_CONFIG_ONLY)); // Header duplicates with one instance in action and another in datasource config expectedDuplicateHeaders = new HashSet<>(); expectedDuplicateHeaders.add("myHeader3"); expectedDuplicateHeaders.add("apiKey"); - assertTrue(expectedDuplicateHeaders.equals(allDuplicateHeaders.get(DATASOURCE_AND_ACTION_CONFIG))); + assertEquals(expectedDuplicateHeaders, allDuplicateHeaders.get(DATASOURCE_AND_ACTION_CONFIG)); /* Test duplicate query params in action + datasource config */ Map> allDuplicateParams = @@ -1065,17 +1296,17 @@ public class RestApiPluginTest { expectedDuplicateParams = new HashSet<>(); expectedDuplicateParams.add("myParam1"); expectedDuplicateParams.add("myParam2"); - assertTrue(expectedDuplicateParams.equals(allDuplicateParams.get(DATASOURCE_CONFIG_ONLY))); + assertEquals(expectedDuplicateParams, allDuplicateParams.get(DATASOURCE_CONFIG_ONLY)); // Query param duplicates in action config only expectedDuplicateParams = new HashSet<>(); expectedDuplicateParams.add("myParam4"); - assertTrue(expectedDuplicateParams.equals(allDuplicateParams.get(ACTION_CONFIG_ONLY))); + assertEquals(expectedDuplicateParams, allDuplicateParams.get(ACTION_CONFIG_ONLY)); // Query param duplicates in action + datasource config expectedDuplicateParams = new HashSet<>(); expectedDuplicateParams.add("myParam3"); - assertTrue(expectedDuplicateParams.equals(allDuplicateParams.get(DATASOURCE_AND_ACTION_CONFIG))); + assertEquals(expectedDuplicateParams, allDuplicateParams.get(DATASOURCE_AND_ACTION_CONFIG)); } @Test @@ -1108,7 +1339,7 @@ public class RestApiPluginTest { // Header duplicates with one instance in action and another in datasource config HashSet expectedDuplicateHeaders = new HashSet<>(); expectedDuplicateHeaders.add("Authorization"); - assertTrue(expectedDuplicateHeaders.equals(allDuplicateHeaders.get(DATASOURCE_AND_ACTION_CONFIG))); + assertEquals(expectedDuplicateHeaders, allDuplicateHeaders.get(DATASOURCE_AND_ACTION_CONFIG)); } @Test @@ -1143,7 +1374,7 @@ public class RestApiPluginTest { // Param duplicates with one instance in action and another in datasource config HashSet expectedDuplicateParams = new HashSet<>(); expectedDuplicateParams.add("access_token"); - assertTrue(expectedDuplicateParams.equals(allDuplicateParams.get(DATASOURCE_AND_ACTION_CONFIG))); + assertEquals(expectedDuplicateParams, allDuplicateParams.get(DATASOURCE_AND_ACTION_CONFIG)); } /** @@ -1181,7 +1412,7 @@ public class RestApiPluginTest { " because this datasource has duplicate definition(s) for header(s): [myHeader1]. Please " + "remove the duplicate definition(s) to resolve this warning. Please note that some of the" + " authentication mechanisms also implicitly define a header."); - assertTrue(expectedDatasourceHintMessages.equals(datasourceHintMessages)); + assertEquals(expectedDatasourceHintMessages, datasourceHintMessages); Set actionHintMessages = tuple.getT2(); Set expectedActionHintMessages = new HashSet<>(); @@ -1192,7 +1423,7 @@ public class RestApiPluginTest { expectedActionHintMessages.add("Your API query may not run as expected because its datasource has" + " duplicate definition(s) for header(s): [myHeader1]. Please remove the duplicate " + "definition(s) from the datasource to resolve this warning."); - assertTrue(expectedActionHintMessages.equals(actionHintMessages)); + assertEquals(expectedActionHintMessages, actionHintMessages); }) .verifyComplete(); } @@ -1234,7 +1465,7 @@ public class RestApiPluginTest { expectedActionHintMessages.add("Your API query may not run as expected because it has duplicate " + "definition(s) for param(s): [myParam1]. Please remove the duplicate definition(s) from " + "the 'Params' tab to resolve this warning."); - assertTrue(expectedActionHintMessages.equals(actionHintMessages)); + assertEquals(expectedActionHintMessages, actionHintMessages); }) .verifyComplete(); } @@ -1272,7 +1503,7 @@ public class RestApiPluginTest { " the 'Headers' section of either the API query or the datasource. Please note that some " + "of the authentication mechanisms also implicitly define a header."); - assertTrue(expectedActionHintMessages.equals(actionHintMessages)); + assertEquals(expectedActionHintMessages, actionHintMessages); }) .verifyComplete(); } @@ -1310,7 +1541,7 @@ public class RestApiPluginTest { " the 'Params' section of either the API query or the datasource. Please note that some " + "of the authentication mechanisms also implicitly define a param."); - assertTrue(expectedActionHintMessages.equals(actionHintMessages)); + assertEquals(expectedActionHintMessages, actionHintMessages); }) .verifyComplete(); } @@ -1318,7 +1549,13 @@ public class RestApiPluginTest { @Test public void testQueryParamsInDatasource() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setHeaders(List.of(new Property("content-type", "application/json"))); @@ -1338,9 +1575,23 @@ public class RestApiPluginTest { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - String expected_url = "\"https://postman-echo.com/post?query_key=query+val\""; - JsonNode url = ((ObjectNode) result.getBody()).get("url"); - assertEquals(expected_url, url.toString()); + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + assertEquals(requestBody, new String(bodyBytes)); + + String requestPath = recordedRequest.getPath(); + assertEquals("/?query_key=query+val", requestPath); + + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); + } }) .verifyComplete(); } @@ -1399,14 +1650,14 @@ public class RestApiPluginTest { @Test public void testDenyInstanceMetadataAwsWithRedirect() throws IOException { // Generate a mock response which redirects to the invalid host - MockWebServer mockWebServer = new MockWebServer(); + mockEndpoint = new MockWebServer(); MockResponse mockRedirectResponse = new MockResponse() .setResponseCode(301) .addHeader("Location", "http://169.254.169.254.nip.io/latest/meta-data"); - mockWebServer.enqueue(mockRedirectResponse); - mockWebServer.start(); + mockEndpoint.enqueue(mockRedirectResponse); + mockEndpoint.start(); - HttpUrl mockHttpUrl = mockWebServer.url("/mock/redirect"); + HttpUrl mockHttpUrl = mockEndpoint.url("/mock/redirect"); DatasourceConfiguration dsConfig = new DatasourceConfiguration(); dsConfig.setUrl(mockHttpUrl.toString()); @@ -1452,8 +1703,32 @@ public class RestApiPluginTest { @Test public void testAPIResponseEncodedInGzipFormat() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("http://postman-echo.com/gzip"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + Function compressor = (data) -> { + ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length()); + GZIPOutputStream gzip; + try { + gzip = new GZIPOutputStream(bos); + + gzip.write(data.getBytes()); + gzip.close(); + byte[] compressed = bos.toByteArray(); + bos.close(); + return compressed; + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + String body = "GzippedBody"; + + try (Buffer buffer = new Buffer()) { + mockEndpoint + .enqueue(new MockResponse() + .setBody(buffer.write(compressor.apply(body))) + .addHeader("Content-Encoding", "gzip")); + } ActionConfiguration actionConfig = new ActionConfiguration(); actionConfig.setHeaders(List.of( new Property("content-type", "application/json") @@ -1464,13 +1739,9 @@ public class RestApiPluginTest { StepVerifier.create(resultMono) .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); - assertNotNull(result.getRequest().getBody()); - final Iterator> fields = ((ObjectNode) result.getRequest().getHeaders()).fields(); - fields.forEachRemaining(field -> { - if ("gzipped".equalsIgnoreCase(field.getKey())) { - assertEquals("true", field.getValue().get(0).asText()); - } - }); + assertNotNull(result.getBody()); + + assertEquals(body, result.getBody()); }) .verifyComplete(); } @@ -1478,7 +1749,13 @@ public class RestApiPluginTest { @Test public void testNumericStringHavingLeadingZero() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = new ActionConfiguration(); final List headers = List.of(new Property("content-type", "application/json")); @@ -1500,10 +1777,23 @@ public class RestApiPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - JsonNode data = ((ObjectNode) result.getBody()).get("data"); - assertEquals(requestBody.replace("{{phoneNumber.text}}", param.getValue()), data.toString()); + + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + assertEquals(requestBody.replace("{{phoneNumber.text}}", param.getValue()), new String(bodyBytes)); + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); + } + final ActionExecutionRequest request = result.getRequest(); - assertEquals("https://postman-echo.com/post", request.getUrl()); + assertEquals(baseUrl, request.getUrl()); assertEquals(HttpMethod.POST, request.getHttpMethod()); final Iterator> fields = ((ObjectNode) result.getRequest().getHeaders()).fields(); fields.forEachRemaining(field -> { @@ -1518,7 +1808,13 @@ public class RestApiPluginTest { @Test public void whenBindingFoundWithoutValue_doNotReplaceWithNull() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); - dsConfig.setUrl("https://postman-echo.com/post"); + String baseUrl = String.format("http://%s:%s", mockEndpoint.getHostName(), mockEndpoint.getPort()); + dsConfig.setUrl(baseUrl); + + mockEndpoint + .enqueue(new MockResponse() + .setBody("{}") + .addHeader("Content-Type", "application/json")); ActionConfiguration actionConfig = new ActionConfiguration(); final List headers = List.of(new Property("content-type", "application/json")); @@ -1540,10 +1836,23 @@ public class RestApiPluginTest { .assertNext(result -> { assertTrue(result.getIsExecutionSuccess()); assertNotNull(result.getBody()); - JsonNode data = ((ObjectNode) result.getBody()).get("data"); - assertEquals(requestBody.replace("{{Input1.text}}", param.getValue()), data.toString()); + + try { + final RecordedRequest recordedRequest = mockEndpoint.takeRequest(30, TimeUnit.SECONDS); + assert recordedRequest != null; + final Buffer recordedRequestBody = recordedRequest.getBody(); + byte[] bodyBytes = new byte[(int) recordedRequestBody.size()]; + + recordedRequestBody.readFully(bodyBytes); + recordedRequestBody.close(); + + assertEquals(requestBody.replace("{{Input1.text}}", param.getValue()), new String(bodyBytes)); + } catch (EOFException | InterruptedException e) { + assert false : e.getMessage(); + } + final ActionExecutionRequest request = result.getRequest(); - assertEquals("https://postman-echo.com/post", request.getUrl()); + assertEquals(baseUrl, request.getUrl()); assertEquals(HttpMethod.POST, request.getHttpMethod()); final Iterator> fields = ((ObjectNode) result.getRequest().getHeaders()).fields(); fields.forEachRemaining(field -> { @@ -1557,11 +1866,10 @@ public class RestApiPluginTest { @Test public void verifyUniquenessOfRestApiPluginErrorCode() { - assert (Arrays.stream(RestApiPluginError.values()).map(RestApiPluginError::getAppErrorCode).distinct().count() == RestApiPluginError.values().length); + assertEquals(RestApiPluginError.values().length, Arrays.stream(RestApiPluginError.values()).map(RestApiPluginError::getAppErrorCode).distinct().count()); - assert (Arrays.stream(RestApiPluginError.values()).map(RestApiPluginError::getAppErrorCode) - .filter(appErrorCode-> appErrorCode.length() != 11 || !appErrorCode.startsWith("PE-RST")) - .collect(Collectors.toList()).size() == 0); + assertEquals(0, Arrays.stream(RestApiPluginError.values()).map(RestApiPluginError::getAppErrorCode) + .filter(appErrorCode -> appErrorCode.length() != 11 || !appErrorCode.startsWith("PE-RST")).count()); } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java index 2dc2605f9c..7ce1819050 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java @@ -89,51 +89,51 @@ public class CurlImporterServiceTest { @Test public void lexerTests() { - assertThat(curlImporterService.lex("curl http://httpbin.org/get")) - .isEqualTo(List.of("curl", "http://httpbin.org/get")); - assertThat(curlImporterService.lex("curl -H 'X-Something: something else' http://httpbin.org/get")) - .isEqualTo(List.of("curl", "-H", "X-Something: something else", "http://httpbin.org/get")); - assertThat(curlImporterService.lex("curl -H \"X-Something: something else\" http://httpbin.org/get")) - .isEqualTo(List.of("curl", "-H", "X-Something: something else", "http://httpbin.org/get")); - assertThat(curlImporterService.lex("curl -H X-Something:\\ something\\ else http://httpbin.org/get")) - .isEqualTo(List.of("curl", "-H", "X-Something: something else", "http://httpbin.org/get")); + assertThat(curlImporterService.lex("curl http://example.org/get")) + .isEqualTo(List.of("curl", "http://example.org/get")); + assertThat(curlImporterService.lex("curl -H 'X-Something: something else' http://example.org/get")) + .isEqualTo(List.of("curl", "-H", "X-Something: something else", "http://example.org/get")); + assertThat(curlImporterService.lex("curl -H \"X-Something: something else\" http://example.org/get")) + .isEqualTo(List.of("curl", "-H", "X-Something: something else", "http://example.org/get")); + assertThat(curlImporterService.lex("curl -H X-Something:\\ something\\ else http://example.org/get")) + .isEqualTo(List.of("curl", "-H", "X-Something: something else", "http://example.org/get")); - assertThat(curlImporterService.lex("curl -H \"X-Something: something \\\"quoted\\\" else\" http://httpbin.org/get")) - .isEqualTo(List.of("curl", "-H", "X-Something: something \"quoted\" else", "http://httpbin.org/get")); - assertThat(curlImporterService.lex("curl -H \"X-Something: something \\\\\\\"quoted\\\" else\" http://httpbin.org/get")) - .isEqualTo(List.of("curl", "-H", "X-Something: something \\\"quoted\" else", "http://httpbin.org/get")); + assertThat(curlImporterService.lex("curl -H \"X-Something: something \\\"quoted\\\" else\" http://example.org/get")) + .isEqualTo(List.of("curl", "-H", "X-Something: something \"quoted\" else", "http://example.org/get")); + assertThat(curlImporterService.lex("curl -H \"X-Something: something \\\\\\\"quoted\\\" else\" http://example.org/get")) + .isEqualTo(List.of("curl", "-H", "X-Something: something \\\"quoted\" else", "http://example.org/get")); // The following tests are meant for cases when any of the components have nested quotes within them // In this example, the header argument is surrounded by single quotes, the value for it is surrounded by double quotes, // and the contents of the value has two single quotes - assertThat(curlImporterService.lex("curl -H 'X-Something: \"something '\\''quoted with nesting'\\'' else\"' http://httpbin.org/get")) - .isEqualTo(List.of("curl", "-H", "X-Something: \"something 'quoted with nesting' else\"", "http://httpbin.org/get")); + assertThat(curlImporterService.lex("curl -H 'X-Something: \"something '\\''quoted with nesting'\\'' else\"' http://example.org/get")) + .isEqualTo(List.of("curl", "-H", "X-Something: \"something 'quoted with nesting' else\"", "http://example.org/get")); // In this example, the header argument is surrounded by single quotes, the value for it is surrounded by double quotes, // and the contents of the value has one single quote - assertThat(curlImporterService.lex("curl -H 'X-Something: \"something '\\''ed with nesting else\"' http://httpbin.org/get")) - .isEqualTo(List.of("curl", "-H", "X-Something: \"something 'ed with nesting else\"", "http://httpbin.org/get")); + assertThat(curlImporterService.lex("curl -H 'X-Something: \"something '\\''ed with nesting else\"' http://example.org/get")) + .isEqualTo(List.of("curl", "-H", "X-Something: \"something 'ed with nesting else\"", "http://example.org/get")); // In the following test, we're simulating a subshell. This subshell call is outside of quotes try { - curlImporterService.lex("curl -H 'X-Something: \"something '$(echo test)' quoted with nesting else\"' http://httpbin.org/get"); + curlImporterService.lex("curl -H 'X-Something: \"something '$(echo test)' quoted with nesting else\"' http://example.org/get"); } catch (Exception e) { assertThat(e).isInstanceOf(AppsmithException.class); assertThat(e.getMessage()).isEqualTo(AppsmithError.GENERIC_BAD_REQUEST.getMessage("Please do not try to invoke a subshell in the cURL")); } try { - curlImporterService.lex("curl -H 'X-Something: \"something '`echo test`' quoted with nesting else\"' http://httpbin.org/get"); + curlImporterService.lex("curl -H 'X-Something: \"something '`echo test`' quoted with nesting else\"' http://example.org/get"); } catch (Exception e) { assertThat(e).isInstanceOf(AppsmithException.class); assertThat(e.getMessage()).isEqualTo(AppsmithError.GENERIC_BAD_REQUEST.getMessage("Please do not try to invoke a subshell in the cURL")); } // In the following test, we're simulating a subshell. Subshells can be inside double-quoted strings as well try { - curlImporterService.lex("curl -H \"X-Something: 'something $(echo test) quoted with nesting else'\" http://httpbin.org/get"); + curlImporterService.lex("curl -H \"X-Something: 'something $(echo test) quoted with nesting else'\" http://example.org/get"); } catch (Exception e) { assertThat(e).isInstanceOf(AppsmithException.class); assertThat(e.getMessage()).isEqualTo(AppsmithError.GENERIC_BAD_REQUEST.getMessage("Please do not try to invoke a subshell in the cURL")); } try { - curlImporterService.lex("curl -H \"X-Something: 'something `echo test` quoted with nesting else'\" http://httpbin.org/get"); + curlImporterService.lex("curl -H \"X-Something: 'something `echo test` quoted with nesting else'\" http://example.org/get"); } catch (Exception e) { assertThat(e).isInstanceOf(AppsmithException.class); assertThat(e.getMessage()).isEqualTo(AppsmithError.GENERIC_BAD_REQUEST.getMessage("Please do not try to invoke a subshell in the cURL")); @@ -760,9 +760,9 @@ public class CurlImporterServiceTest { @Test public void parseWithSpacedHeader() throws AppsmithException { - ActionDTO action = curlImporterService.curlToAction("curl -H \"Accept:application/json\" http://httpbin.org/get"); + ActionDTO action = curlImporterService.curlToAction("curl -H \"Accept:application/json\" http://example.org/get"); assertMethod(action, HttpMethod.GET); - assertUrl(action, "http://httpbin.org"); + assertUrl(action, "http://example.org"); assertPath(action, "/get"); assertHeaders(action, new Property("Accept", "application/json")); assertEmptyBody(action); @@ -797,9 +797,9 @@ public class CurlImporterServiceTest { public void parseMultiFormData() throws AppsmithException { // In the curl command, we test for a combination of --form and -F // Also some values are double-quoted while some aren't. This tests a permutation of all such fields - ActionDTO action = curlImporterService.curlToAction("curl --request POST 'http://httpbin.org/post' -F 'somekey=value' --form 'anotherKey=\"anotherValue\"'"); + ActionDTO action = curlImporterService.curlToAction("curl --request POST 'http://example.org/post' -F 'somekey=value' --form 'anotherKey=\"anotherValue\"'"); assertMethod(action, HttpMethod.POST); - assertUrl(action, "http://httpbin.org"); + assertUrl(action, "http://example.org"); assertPath(action, "/post"); assertHeaders(action, new Property("Content-Type", "multipart/form-data")); assertEmptyBody(action); @@ -812,9 +812,9 @@ public class CurlImporterServiceTest { @Test public void dontEatBackslashesInSingleQuotes() throws AppsmithException { - ActionDTO action = curlImporterService.curlToAction("curl http://httpbin.org/post -d 'a\\n'"); + ActionDTO action = curlImporterService.curlToAction("curl http://example.org/post -d 'a\\n'"); assertMethod(action, HttpMethod.POST); - assertUrl(action, "http://httpbin.org"); + assertUrl(action, "http://example.org"); assertPath(action, "/post"); assertBody(action, "a\\n"); assertEmptyBodyFormData(action); @@ -822,14 +822,14 @@ public class CurlImporterServiceTest { @Test public void importInvalidMethod() { - assertThatThrownBy(() -> curlImporterService.curlToAction("curl -X incorrect-charactèrs http://httpbin.org/get")) + assertThatThrownBy(() -> curlImporterService.curlToAction("curl -X incorrect-charactèrs http://example.org/get")) .isInstanceOf(AppsmithException.class) .matches(err -> ((AppsmithException) err).getError() == AppsmithError.INVALID_CURL_METHOD); } @Test public void importInvalidHeader() { - assertThatThrownBy(() -> curlImporterService.curlToAction("curl -H x-custom http://httpbin.org/headers")) + assertThatThrownBy(() -> curlImporterService.curlToAction("curl -H x-custom http://example.org/headers")) .isInstanceOf(AppsmithException.class) .matches(err -> ((AppsmithException) err).getError() == AppsmithError.INVALID_CURL_HEADER); } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesWorkspaceClonerTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesWorkspaceClonerTests.java index 8d4f07413b..2e727eb438 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesWorkspaceClonerTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesWorkspaceClonerTests.java @@ -450,7 +450,7 @@ public class ExamplesWorkspaceClonerTests { ds1.setPluginId(pluginId); final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); ds1.setDatasourceConfiguration(datasourceConfiguration); - datasourceConfiguration.setUrl("http://httpbin.org/get"); + datasourceConfiguration.setUrl("http://example.org/get"); datasourceConfiguration.setHeaders(List.of( new Property("X-Answer", "42") )); @@ -505,7 +505,7 @@ public class ExamplesWorkspaceClonerTests { ds1.setPluginId(installedPlugin.getId()); final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); ds1.setDatasourceConfiguration(datasourceConfiguration); - datasourceConfiguration.setUrl("http://httpbin.org/get"); + datasourceConfiguration.setUrl("http://example.org/get"); datasourceConfiguration.setHeaders(List.of( new Property("X-Answer", "42") )); diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java index 2a7dedfbb6..d29c1a6b5f 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java @@ -215,7 +215,7 @@ public class ImportExportApplicationServiceTests { ds1.setWorkspaceId(workspaceId); ds1.setPluginId(installedPlugin.getId()); final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); - datasourceConfiguration.setUrl("http://httpbin.org/get"); + datasourceConfiguration.setUrl("http://example.org/get"); datasourceConfiguration.setHeaders(List.of( new Property("X-Answer", "42") )); diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java index a2a4037532..305be77584 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java @@ -223,7 +223,7 @@ public class ImportExportApplicationServiceV2Tests { ds1.setWorkspaceId(workspaceId); ds1.setPluginId(installedPlugin.getId()); final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); - datasourceConfiguration.setUrl("http://httpbin.org/get"); + datasourceConfiguration.setUrl("http://example.org/get"); datasourceConfiguration.setHeaders(List.of( new Property("X-Answer", "42") )); diff --git a/app/server/appsmith-server/src/test/resources/application-test.properties b/app/server/appsmith-server/src/test/resources/application-test.properties index 0777b1f8d6..e78a9816ac 100644 --- a/app/server/appsmith-server/src/test/resources/application-test.properties +++ b/app/server/appsmith-server/src/test/resources/application-test.properties @@ -1,2 +1,3 @@ # embedded mongo DB version which is used during junit tests -de.flapdoodle.mongodb.embedded.version=5.0.5 \ No newline at end of file +de.flapdoodle.mongodb.embedded.version=5.0.5 +logging.level.root=error \ No newline at end of file