diff --git a/src/main/java/com/skyflow/logs/InfoLogs.java b/src/main/java/com/skyflow/logs/InfoLogs.java index f71fc416..18363670 100644 --- a/src/main/java/com/skyflow/logs/InfoLogs.java +++ b/src/main/java/com/skyflow/logs/InfoLogs.java @@ -14,7 +14,7 @@ public enum InfoLogs { // Bearer token generation EMPTY_BEARER_TOKEN("Bearer token is empty."), - BEARER_TOKEN_EXPIRED("Bearer token is expired."), + BEARER_TOKEN_EXPIRED("Bearer token is invalid or expired."), GET_BEARER_TOKEN_TRIGGERED("getBearerToken method triggered."), GET_BEARER_TOKEN_SUCCESS("Bearer token generated."), GET_SIGNED_DATA_TOKENS_TRIGGERED("getSignedDataTokens method triggered."), diff --git a/src/main/java/com/skyflow/utils/Constants.java b/src/main/java/com/skyflow/utils/Constants.java index 1f26cd9d..92a608ac 100644 --- a/src/main/java/com/skyflow/utils/Constants.java +++ b/src/main/java/com/skyflow/utils/Constants.java @@ -34,6 +34,8 @@ public final class Constants { public static final String ERROR_FROM_CLIENT_HEADER_KEY = "error-from-client"; public static final String PROCESSED_FILE_NAME_PREFIX = "processed-"; public static final String DEIDENTIFIED_FILE_PREFIX = "deidentified"; + public static final String CURLY_PLACEHOLDER = "{%s}"; + public static final class HttpHeader { public static final String CONTENT_TYPE = "content-type"; public static final String CONTENT_TYPE_JSON = "application/json"; @@ -60,6 +62,13 @@ private EncodingType() { // Utility class constructor } } + public static final String EMPTY_STRING = ""; + public static final String QUOTE = "\""; + public static final class HttpUtilityExtra { + public static final String RAW_BODY_KEY = "__raw_body__"; + public static final String SDK_GENERATED_PREFIX = "SDK-Generated-"; + private HttpUtilityExtra() {} + } public static final class FileFormatType { public static final String TXT = "txt"; diff --git a/src/main/java/com/skyflow/utils/HttpUtility.java b/src/main/java/com/skyflow/utils/HttpUtility.java index 9c8aaf6d..fd30ed26 100644 --- a/src/main/java/com/skyflow/utils/HttpUtility.java +++ b/src/main/java/com/skyflow/utils/HttpUtility.java @@ -7,11 +7,13 @@ import java.io.*; import java.net.HttpURLConnection; import java.net.URL; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.UUID; public final class HttpUtility { @@ -32,8 +34,11 @@ public static String sendRequest(String method, URL url, JsonObject params, Map< try { connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod(method); - connection.setRequestProperty(Constants.HttpHeader.CONTENT_TYPE, Constants.HttpHeader.CONTENT_TYPE_JSON); connection.setRequestProperty(Constants.HttpHeader.ACCEPT, Constants.HttpHeader.ACCEPT_ALL); + boolean hasContentType = headers != null && headers.containsKey(Constants.HttpHeader.CONTENT_TYPE); + if (!hasContentType && params != null && !params.isEmpty()) { + connection.setRequestProperty(Constants.HttpHeader.CONTENT_TYPE, Constants.HttpHeader.CONTENT_TYPE_JSON); + } if (headers != null && !headers.isEmpty()) { for (Map.Entry entry : headers.entrySet()) @@ -52,9 +57,12 @@ public static String sendRequest(String method, URL url, JsonObject params, Map< byte[] input = null; String requestContentType = connection.getRequestProperty(Constants.HttpHeader.CONTENT_TYPE); - if (requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_FORM_URLENCODED)) { + // Check if this is a raw body (XML, plain text, etc.) + if (params.has(Constants.HttpUtilityExtra.RAW_BODY_KEY) && params.size() == 1) { + input = params.get(Constants.HttpUtilityExtra.RAW_BODY_KEY).getAsString().getBytes(StandardCharsets.UTF_8); + } else if (requestContentType != null && requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_FORM_URLENCODED)) { input = formatJsonToFormEncodedString(params).getBytes(StandardCharsets.UTF_8); - } else if (requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_MULTIPART)) { + } else if (requestContentType != null && requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_MULTIPART)) { input = formatJsonToMultiPartFormDataString(params, boundary).getBytes(StandardCharsets.UTF_8); } else { input = params.toString().getBytes(StandardCharsets.UTF_8); @@ -67,7 +75,11 @@ public static String sendRequest(String method, URL url, JsonObject params, Map< int httpCode = connection.getResponseCode(); String requestID = connection.getHeaderField(Constants.REQUEST_ID_HEADER_KEY); - HttpUtility.requestID = requestID.split(Constants.HttpUtility.REQUEST_ID_DELIMITER)[0]; + if (requestID != null) { + HttpUtility.requestID = requestID.split(Constants.HttpUtility.REQUEST_ID_DELIMITER)[0]; + } else { + HttpUtility.requestID = Constants.HttpUtilityExtra.SDK_GENERATED_PREFIX + UUID.randomUUID(); + } Map> responseHeaders = connection.getHeaderFields(); Reader streamReader; if (httpCode > 299) { @@ -159,7 +171,13 @@ public static String appendRequestId(String message, String requestId) { } private static String makeFormEncodeKeyValuePair(String key, String value) { - return key + Constants.HttpUtility.FORM_ENCODE_SEPARATOR + value + Constants.HttpUtility.FORM_ENCODE_DELIMITER; + try { + String encodedKey = URLEncoder.encode(key, StandardCharsets.UTF_8.toString()); + String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.toString()); + return encodedKey + Constants.HttpUtility.FORM_ENCODE_SEPARATOR + encodedValue + Constants.HttpUtility.FORM_ENCODE_DELIMITER; + } catch (Exception e) { + return key + Constants.HttpUtility.FORM_ENCODE_SEPARATOR + value + Constants.HttpUtility.FORM_ENCODE_DELIMITER; + } } } diff --git a/src/main/java/com/skyflow/utils/Utils.java b/src/main/java/com/skyflow/utils/Utils.java index 763380ab..c53685cc 100644 --- a/src/main/java/com/skyflow/utils/Utils.java +++ b/src/main/java/com/skyflow/utils/Utils.java @@ -17,6 +17,8 @@ import java.io.File; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -77,10 +79,10 @@ public static PrivateKey getPrivateKeyFromPem(String pemKey) throws SkyflowExcep PrivateKey privateKey = null; if (pemKey.contains(PKCS8PrivateHeader)) { - privateKeyContent = privateKeyContent.replace(PKCS8PrivateHeader, ""); - privateKeyContent = privateKeyContent.replace(PKCS8PrivateFooter, ""); - privateKeyContent = privateKeyContent.replace("\n", ""); - privateKeyContent = privateKeyContent.replace("\r\n", ""); + privateKeyContent = privateKeyContent.replace(PKCS8PrivateHeader, Constants.EMPTY_STRING); + privateKeyContent = privateKeyContent.replace(PKCS8PrivateFooter, Constants.EMPTY_STRING); + privateKeyContent = privateKeyContent.replace("\n", Constants.EMPTY_STRING); + privateKeyContent = privateKeyContent.replace("\r\n", Constants.EMPTY_STRING); privateKey = parsePkcs8PrivateKey(Base64.decodeBase64(privateKeyContent)); } else { LogUtil.printErrorLog(ErrorLogs.JWT_INVALID_FORMAT.getLog()); @@ -110,7 +112,12 @@ public static String constructConnectionURL(ConnectionConfig config, InvokeConne for (Map.Entry entry : invokeConnectionRequest.getPathParams().entrySet()) { String key = entry.getKey(); String value = entry.getValue(); - filledURL = new StringBuilder(filledURL.toString().replace(String.format("{%s}", key), value)); + try { + String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + filledURL = new StringBuilder(filledURL.toString().replace(String.format(Constants.CURLY_PLACEHOLDER, key), encodedValue)); + } catch (Exception e) { + filledURL = new StringBuilder(filledURL.toString().replace(String.format(Constants.CURLY_PLACEHOLDER, key), value)); + } } } @@ -119,7 +126,13 @@ public static String constructConnectionURL(ConnectionConfig config, InvokeConne for (Map.Entry entry : invokeConnectionRequest.getQueryParams().entrySet()) { String key = entry.getKey(); String value = entry.getValue(); - filledURL.append(key).append("=").append(value).append("&"); + try { + String encodedKey = URLEncoder.encode(key, StandardCharsets.UTF_8.name()); + String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + filledURL.append(encodedKey).append(Constants.HttpUtility.FORM_ENCODE_SEPARATOR).append(encodedValue).append(Constants.HttpUtility.FORM_ENCODE_DELIMITER); + } catch (Exception e) { + filledURL.append(key).append(Constants.HttpUtility.FORM_ENCODE_SEPARATOR).append(value).append(Constants.HttpUtility.FORM_ENCODE_DELIMITER); + } } filledURL = new StringBuilder(filledURL.substring(0, filledURL.length() - 1)); } @@ -152,7 +165,7 @@ public static JsonObject getMetrics() { InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(), Constants.SDK_METRIC_CLIENT_DEVICE_MODEL )); - deviceModel = ""; + deviceModel = Constants.EMPTY_STRING; } // Retrieve OS details @@ -164,7 +177,7 @@ public static JsonObject getMetrics() { InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(), Constants.SDK_METRIC_CLIENT_OS_DETAILS )); - osDetails = ""; + osDetails = Constants.EMPTY_STRING; } // Retrieve Java version details @@ -176,7 +189,7 @@ public static JsonObject getMetrics() { InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(), Constants.SDK_METRIC_RUNTIME_DETAILS )); - javaVersion = ""; + javaVersion = Constants.EMPTY_STRING; } details.addProperty(Constants.SDK_METRIC_NAME_VERSION, Constants.SDK_METRIC_NAME_VERSION_PREFIX + sdkVersion); details.addProperty(Constants.SDK_METRIC_CLIENT_DEVICE_MODEL, deviceModel); diff --git a/src/main/java/com/skyflow/utils/validations/Validations.java b/src/main/java/com/skyflow/utils/validations/Validations.java index 31ee00e0..78077867 100644 --- a/src/main/java/com/skyflow/utils/validations/Validations.java +++ b/src/main/java/com/skyflow/utils/validations/Validations.java @@ -1,6 +1,7 @@ package com.skyflow.utils.validations; import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.skyflow.config.ConnectionConfig; import com.skyflow.config.Credentials; @@ -137,12 +138,27 @@ public static void validateInvokeConnectionRequest(InvokeConnectionRequest invok } if (requestBody != null) { - Gson gson = new Gson(); - JsonObject bodyObject = gson.toJsonTree(requestBody).getAsJsonObject(); - if (bodyObject.isEmpty()) { - LogUtil.printErrorLog(Utils.parameterizedString( - ErrorLogs.EMPTY_REQUEST_BODY.getLog(), InterfaceName.INVOKE_CONNECTION.getName())); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRequestBody.getMessage()); + if (requestBody.getClass().equals(Object.class)) { + return; + } + if (requestBody instanceof String) { + String bodyStr = (String) requestBody; + if (bodyStr.trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_REQUEST_BODY.getLog(), InterfaceName.INVOKE_CONNECTION.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRequestBody.getMessage()); + } + } else { + Gson gson = new Gson(); + JsonElement bodyElement = gson.toJsonTree(requestBody); + if (bodyElement.isJsonObject()) { + JsonObject bodyObject = bodyElement.getAsJsonObject(); + if (bodyObject.isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_REQUEST_BODY.getLog(), InterfaceName.INVOKE_CONNECTION.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRequestBody.getMessage()); + } + } } } } diff --git a/src/main/java/com/skyflow/vault/controller/ConnectionController.java b/src/main/java/com/skyflow/vault/controller/ConnectionController.java index e1aff01b..06d806c9 100644 --- a/src/main/java/com/skyflow/vault/controller/ConnectionController.java +++ b/src/main/java/com/skyflow/vault/controller/ConnectionController.java @@ -51,20 +51,50 @@ public InvokeConnectionResponse invoke(InvokeConnectionRequest invokeConnectionR headers.put(Constants.SDK_METRICS_HEADER_KEY, Utils.getMetrics().toString()); RequestMethod requestMethod = invokeConnectionRequest.getMethod(); - JsonObject requestBody = null; Object requestBodyObject = invokeConnectionRequest.getRequestBody(); + String requestContentType = headers + .getOrDefault(Constants.HttpHeader.CONTENT_TYPE, Constants.HttpHeader.CONTENT_TYPE_JSON) + .toLowerCase(); + boolean isJsonRequest = requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_JSON); - if (requestBodyObject != null) { - try { - requestBody = convertObjectToJson(requestBodyObject); - } catch (Exception e) { - LogUtil.printErrorLog(ErrorLogs.INVALID_REQUEST_HEADERS.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidRequestBody.getMessage()); + Object processedRequestBody; + + try { + if (requestBodyObject instanceof String) { + if (isJsonRequest) { + JsonObject jsonWrapper = new JsonObject(); + jsonWrapper.addProperty( + Constants.HttpUtilityExtra.RAW_BODY_KEY, + (String) requestBodyObject + ); + processedRequestBody = jsonWrapper; + } else { + processedRequestBody = requestBodyObject; + } + } else if (requestBodyObject instanceof JsonObject || requestBodyObject == null) { + processedRequestBody = requestBodyObject; + } else { + processedRequestBody = convertObjectToJson(requestBodyObject); } + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.INVALID_REQUEST_HEADERS.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidRequestBody.getMessage()); + } + JsonObject payloadToSend; + if (processedRequestBody instanceof JsonObject || processedRequestBody == null) { + payloadToSend = (JsonObject) processedRequestBody; + } else { + payloadToSend = new JsonObject(); + payloadToSend.addProperty(Constants.HttpUtilityExtra.RAW_BODY_KEY, processedRequestBody.toString()); } - String response = HttpUtility.sendRequest(requestMethod.name(), new URL(filledURL), requestBody, headers); - JsonObject data = JsonParser.parseString(response).getAsJsonObject(); + String response = HttpUtility.sendRequest(requestMethod.name(), new URL(filledURL), payloadToSend, headers); + Object data = response; + try { + data = JsonParser.parseString(response).getAsJsonObject(); + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.INVOKE_CONNECTION_REQUEST_REJECTED.getLog()); + } HashMap metadata = new HashMap<>(); metadata.put(Constants.JsonFieldNames.REQUEST_ID, HttpUtility.getRequestID()); connectionResponse = new InvokeConnectionResponse(data, metadata, null); diff --git a/src/test/java/com/skyflow/utils/HttpUtilityTests.java b/src/test/java/com/skyflow/utils/HttpUtilityTests.java index f7214690..5f7271e1 100644 --- a/src/test/java/com/skyflow/utils/HttpUtilityTests.java +++ b/src/test/java/com/skyflow/utils/HttpUtilityTests.java @@ -124,4 +124,71 @@ public void testSendRequestError() { fail(INVALID_EXCEPTION_THROWN); } } + + // New test cases for content-type handling changes + + @Test + @PrepareForTest({URL.class, HttpURLConnection.class}) + public void testSendRequestWithRawBody() { + try { + given(mockConnection.getRequestProperty("content-type")).willReturn("application/xml"); + Map headers = new HashMap<>(); + headers.put("content-type", "application/xml"); + JsonObject params = new JsonObject(); + params.addProperty("__raw_body__", "test"); + String response = httpUtility.sendRequest("POST", url, params, headers); + Assert.assertEquals(expected, response); + } catch (Exception e) { + fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + @PrepareForTest({URL.class, HttpURLConnection.class}) + public void testSendRequestWithoutContentTypeHeader() { + try { + given(mockConnection.getRequestProperty("content-type")).willReturn("application/json"); + JsonObject params = new JsonObject(); + params.addProperty("key", "value"); + String response = httpUtility.sendRequest("POST", url, params, null); + Assert.assertEquals(expected, response); + } catch (Exception e) { + fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + @PrepareForTest({URL.class, HttpURLConnection.class}) + public void testSendRequestWithNullRequestId() { + try { + given(mockConnection.getHeaderField(anyString())).willReturn(null); + given(mockConnection.getRequestProperty("content-type")).willReturn("application/json"); + Map headers = new HashMap<>(); + headers.put("content-type", "application/json"); + JsonObject params = new JsonObject(); + params.addProperty("key", "value"); + String response = httpUtility.sendRequest("GET", url, params, headers); + Assert.assertEquals(expected, response); + Assert.assertNotNull(HttpUtility.getRequestID()); + } catch (Exception e) { + fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + @PrepareForTest({URL.class, HttpURLConnection.class}) + public void testSendRequestFormURLEncodedWithSpecialCharacters() { + try { + given(mockConnection.getRequestProperty("content-type")).willReturn("application/x-www-form-urlencoded"); + Map headers = new HashMap<>(); + headers.put("content-type", "application/x-www-form-urlencoded"); + JsonObject params = new JsonObject(); + params.addProperty("key", "value with spaces"); + params.addProperty("special", "test@email.com"); + String response = httpUtility.sendRequest("POST", url, params, headers); + Assert.assertEquals(expected, response); + } catch (Exception e) { + fail(INVALID_EXCEPTION_THROWN); + } + } } diff --git a/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java b/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java index 63717b5c..e87d7877 100644 --- a/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java +++ b/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java @@ -437,4 +437,145 @@ public void testInvokeConnectionResponse() { Assert.fail(INVALID_EXCEPTION_THROWN); } } + + // New test cases for content-type handling changes + + @Test + public void testValidStringRequestBodyInInvokeConnectionRequestValidations() { + try { + String xmlBody = "test"; + requestHeaders.put("Content-Type", "application/xml"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(xmlBody) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.assertTrue(request.getRequestBody() instanceof String); + Assert.assertEquals(xmlBody, request.getRequestBody()); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testEmptyStringRequestBodyInInvokeConnectionRequestValidations() { + try { + String emptyBody = ""; + requestHeaders.put("Content-Type", "application/xml"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(emptyBody) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.EmptyRequestBody.getMessage(), e.getMessage()); + } + } + + @Test + public void testWhitespaceOnlyStringRequestBodyInInvokeConnectionRequestValidations() { + try { + String whitespaceBody = " \n\t "; + requestHeaders.put("Content-Type", "application/xml"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(whitespaceBody) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.EmptyRequestBody.getMessage(), e.getMessage()); + } + } + + @Test + public void testXMLStringRequestBodyInInvokeConnectionRequestValidations() { + try { + String xmlBody = "4111111111111111"; + requestHeaders.put("Content-Type", "application/xml"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(xmlBody) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.assertTrue(request.getRequestBody() instanceof String); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testPlainTextRequestBodyInInvokeConnectionRequestValidations() { + try { + String plainText = "This is a plain text request body with some data"; + requestHeaders.put("Content-Type", "text/plain"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(plainText) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.assertTrue(request.getRequestBody() instanceof String); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testHTMLRequestBodyInInvokeConnectionRequestValidations() { + try { + String htmlBody = "

Processing Payment

"; + requestHeaders.put("Content-Type", "text/html"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(htmlBody) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.assertTrue(request.getRequestBody() instanceof String); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testInvokeConnectionResponseWithStringData() { + try { + String xmlData = "success"; + HashMap metadata = new HashMap<>(); + metadata.put("requestId", "xml-12345"); + InvokeConnectionResponse connectionResponse = new InvokeConnectionResponse(xmlData, metadata, null); + + Assert.assertNotNull(connectionResponse.getData()); + Assert.assertTrue(connectionResponse.getData() instanceof String); + Assert.assertEquals(xmlData, connectionResponse.getData()); + Assert.assertEquals("xml-12345", connectionResponse.getMetadata().get("requestId")); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testRequestBodyWithSpecialCharacters() { + try { + String bodyWithSpecialChars = "card_number=4111111111111111&exp_month=12&exp_year=2025&cvv=123"; + requestHeaders.put("Content-Type", "application/x-www-form-urlencoded"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(bodyWithSpecialChars) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.assertTrue(request.getRequestBody() instanceof String); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } } diff --git a/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java b/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java index b121280a..5ded96b2 100644 --- a/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java +++ b/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java @@ -1,19 +1,36 @@ package com.skyflow.vault.controller; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.skyflow.Skyflow; import com.skyflow.config.ConnectionConfig; import com.skyflow.config.Credentials; import com.skyflow.enums.LogLevel; +import com.skyflow.enums.RequestMethod; import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; +import com.skyflow.utils.HttpUtility; import com.skyflow.vault.connection.InvokeConnectionRequest; +import com.skyflow.vault.connection.InvokeConnectionResponse; import org.junit.Assert; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import java.net.URL; import java.util.HashMap; +import java.util.Map; +import static org.mockito.ArgumentMatchers.*; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({HttpUtility.class}) public class ConnectionControllerTests { private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception"; private static final String EXCEPTION_NOT_THROWN = "Should have thrown an exception"; @@ -36,6 +53,12 @@ public static void setup() { connectionConfig.setCredentials(credentials); } + @Before + public void setupMocks() throws Exception { + PowerMockito.mockStatic(HttpUtility.class); + when(HttpUtility.getRequestID()).thenReturn("test-request-id"); + } + @Test public void testInvalidRequestInInvokeConnectionMethod() { try { @@ -49,4 +72,369 @@ public void testInvalidRequestInInvokeConnectionMethod() { Assert.assertEquals(ErrorMessage.EmptyRequestBody.getMessage(), e.getMessage()); } } + + // New test cases for content-type handling changes + + @Test + public void testInvokeConnectionWithStringRequestBody() { + try { + String xmlBody = "test"; + HashMap headers = new HashMap<>(); + headers.put("Content-Type", "application/xml"); + InvokeConnectionRequest connectionRequest = InvokeConnectionRequest.builder() + .requestBody(xmlBody) + .requestHeaders(headers) + .build(); + Assert.assertNotNull(connectionRequest.getRequestBody()); + Assert.assertTrue(connectionRequest.getRequestBody() instanceof String); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testInvokeConnectionWithEmptyStringRequestBody() { + try { + String emptyBody = ""; + HashMap headers = new HashMap<>(); + headers.put("Content-Type", "application/xml"); + InvokeConnectionRequest connectionRequest = InvokeConnectionRequest.builder() + .requestBody(emptyBody) + .requestHeaders(headers) + .build(); + Skyflow skyflowClient = Skyflow.builder().setLogLevel(LogLevel.DEBUG).addConnectionConfig(connectionConfig).build(); + skyflowClient.connection().invoke(connectionRequest); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.EmptyRequestBody.getMessage(), e.getMessage()); + } + } + + @Test + public void testInvokeConnectionWithXMLContentType() { + try { + String xmlBody = "4111111111111111"; + HashMap headers = new HashMap<>(); + headers.put("Content-Type", "application/xml"); + InvokeConnectionRequest connectionRequest = InvokeConnectionRequest.builder() + .requestBody(xmlBody) + .requestHeaders(headers) + .build(); + Assert.assertEquals("application/xml", connectionRequest.getRequestHeaders().get("Content-Type")); + Assert.assertTrue(connectionRequest.getRequestBody() instanceof String); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testInvokeConnectionWithPlainTextContentType() { + try { + String plainTextBody = "This is a plain text request body"; + HashMap headers = new HashMap<>(); + headers.put("Content-Type", "text/plain"); + InvokeConnectionRequest connectionRequest = InvokeConnectionRequest.builder() + .requestBody(plainTextBody) + .requestHeaders(headers) + .build(); + Assert.assertEquals("text/plain", connectionRequest.getRequestHeaders().get("Content-Type")); + Assert.assertTrue(connectionRequest.getRequestBody() instanceof String); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testInvokeConnectionWithHTMLContentType() { + try { + String htmlBody = "

Test

"; + HashMap headers = new HashMap<>(); + headers.put("Content-Type", "text/html"); + InvokeConnectionRequest connectionRequest = InvokeConnectionRequest.builder() + .requestBody(htmlBody) + .requestHeaders(headers) + .build(); + Assert.assertEquals("text/html", connectionRequest.getRequestHeaders().get("Content-Type")); + Assert.assertTrue(connectionRequest.getRequestBody() instanceof String); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + // Tests for new content-type handling logic with actual controller invocation + + @Test + public void testInvokeConnectionWithStringBodyAndJsonContentType() throws Exception { + String jsonResponse = "{\"success\":true}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String stringBody = "{\"key\":\"value\"}"; + Map headers = new HashMap<>(); + headers.put("content-type", "application/json"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(stringBody) + .requestHeaders(headers) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithStringBodyAndXmlContentType() throws Exception { + String xmlResponse = "success"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(xmlResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String xmlBody = "test"; + Map headers = new HashMap<>(); + headers.put("content-type", "application/xml"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(xmlBody) + .requestHeaders(headers) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + // When response is not valid JSON, it should be returned as String + Assert.assertTrue(response.getData() instanceof String); + Assert.assertEquals(xmlResponse, response.getData()); + } + + @Test + public void testInvokeConnectionWithJsonObjectBody() throws Exception { + String jsonResponse = "{\"result\":\"ok\"}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + JsonObject jsonBody = new JsonObject(); + jsonBody.addProperty("test", "value"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(jsonBody) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithNullRequestBody() throws Exception { + String jsonResponse = "{\"status\":\"success\"}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), isNull(), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.GET) + .requestBody(null) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithNonJsonResponse() throws Exception { + String plainTextResponse = "This is a plain text response"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(plainTextResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + JsonObject jsonBody = new JsonObject(); + jsonBody.addProperty("key", "value"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(jsonBody) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + // Non-JSON response should be returned as String + Assert.assertTrue(response.getData() instanceof String); + Assert.assertEquals(plainTextResponse, response.getData()); + } + + @Test + public void testInvokeConnectionWithContentTypeCaseInsensitive() throws Exception { + String jsonResponse = "{\"data\":\"test\"}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String stringBody = "test data"; + Map headers = new HashMap<>(); + // Test with uppercase Content-Type + headers.put("Content-Type", "APPLICATION/JSON"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(stringBody) + .requestHeaders(headers) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithMixedCaseContentType() throws Exception { + String xmlResponse = "data"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(xmlResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String stringBody = "test"; + Map headers = new HashMap<>(); + headers.put("content-type", "Application/Xml"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(stringBody) + .requestHeaders(headers) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof String); + } + + @Test + public void testInvokeConnectionWithMapRequestBody() throws Exception { + String jsonResponse = "{\"created\":true}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + Map mapBody = new HashMap<>(); + mapBody.put("name", "John"); + mapBody.put("age", "30"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(mapBody) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithDefaultContentType() throws Exception { + String jsonResponse = "{\"message\":\"success\"}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String stringBody = "string data"; + // No content-type header - should default to application/json + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(stringBody) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithFormUrlEncodedContentType() throws Exception { + String jsonResponse = "{\"submitted\":true}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String formBody = "param1=value1¶m2=value2"; + Map headers = new HashMap<>(); + headers.put("content-type", "application/x-www-form-urlencoded"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(formBody) + .requestHeaders(headers) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionMetadataContainsRequestId() throws Exception { + String jsonResponse = "{\"data\":\"value\"}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.GET) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertNotNull(response.getMetadata()); + Assert.assertEquals("test-request-id", response.getMetadata().get("requestId")); + } }