diff --git a/.cspell.json b/.cspell.json
index abe743cb..757f0c48 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -92,7 +92,13 @@
"behaviours",
"sanitisation",
"recognised",
- "unrecognised"
+ "unrecognised",
+ "nocreds",
+ "nodir",
+ "detok",
+ "qhdmceurtnlz",
+ "ngrok",
+ "obac"
],
"languageSettings": [
{
diff --git a/docs/migrate_to_v2.md b/docs/migrate_to_v2.md
index 747076be..671210fd 100644
--- a/docs/migrate_to_v2.md
+++ b/docs/migrate_to_v2.md
@@ -277,6 +277,7 @@ The following instance methods have been renamed for consistency. The old names
|---|---|
| `skyflowClient.updateLogLevel(logLevel)` | `skyflowClient.setLogLevel(logLevel)` |
| `TokenMode.getBYOT()` | `TokenMode.getByot()` |
+| `DetokenizeRequest.builder().downloadURL(b)` | `DetokenizeRequest.builder().downloadUrl(b)` |
---
diff --git a/pom.xml b/pom.xml
index df4103e4..05124b67 100644
--- a/pom.xml
+++ b/pom.xml
@@ -184,6 +184,14 @@
3.2.5
false
+
+ @{argLine}
+ --add-opens java.base/java.lang=ALL-UNNAMED
+ --add-opens java.base/java.net=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED
+ --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/sun.net.www.protocol.https=ALL-UNNAMED
+
**/*Test.java
**/*Tests.java
diff --git a/samples/src/main/java/com/example/vault/deprecated/DetokenizeExample.java b/samples/src/main/java/com/example/vault/deprecated/DetokenizeExample.java
new file mode 100644
index 00000000..f0971407
--- /dev/null
+++ b/samples/src/main/java/com/example/vault/deprecated/DetokenizeExample.java
@@ -0,0 +1,69 @@
+package com.example.vault.deprecated;
+
+import com.skyflow.Skyflow;
+import com.skyflow.config.Credentials;
+import com.skyflow.config.VaultConfig;
+import com.skyflow.enums.Env;
+import com.skyflow.enums.LogLevel;
+import com.skyflow.enums.RedactionType;
+import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.tokens.DetokenizeData;
+import com.skyflow.vault.tokens.DetokenizeRequest;
+import com.skyflow.vault.tokens.DetokenizeResponse;
+
+import java.util.ArrayList;
+
+/**
+ * @deprecated Pre-v2.1 pattern. The {@code downloadURL()} builder method is deprecated.
+ * Use {@code downloadUrl()} instead (see {@link com.example.vault.DetokenizeExample}).
+ *
+ * This example is retained for reference during the deprecation window.
+ * {@code downloadURL()} still works but emits a runtime warning and will be removed in a future release.
+ */
+@Deprecated
+public class DetokenizeExample {
+ @SuppressWarnings("deprecation")
+ public static void main(String[] args) throws SkyflowException {
+ // Step 1: Set up Skyflow credentials
+ Credentials credentials = new Credentials();
+ credentials.setToken(""); // Replace with the actual bearer token
+
+ // Step 2: Configure the vault
+ VaultConfig vaultConfig = new VaultConfig();
+ vaultConfig.setVaultId("");
+ vaultConfig.setClusterId("");
+ vaultConfig.setEnv(Env.PROD);
+ vaultConfig.setCredentials(credentials);
+
+ // Step 3: Set up credentials for the Skyflow client
+ Credentials skyflowCredentials = new Credentials();
+ skyflowCredentials.setCredentialsString("");
+
+ // Step 4: Create a Skyflow client
+ Skyflow skyflowClient = Skyflow.builder()
+ .setLogLevel(LogLevel.ERROR)
+ .addVaultConfig(vaultConfig)
+ .addSkyflowCredentials(skyflowCredentials)
+ .build();
+
+ // Step 5: Detokenize with deprecated downloadURL()
+ // DEPRECATED: use downloadUrl(true) instead of downloadURL(true)
+ try {
+ ArrayList detokenizeData = new ArrayList<>();
+ detokenizeData.add(new DetokenizeData("", RedactionType.MASKED));
+ detokenizeData.add(new DetokenizeData(""));
+
+ DetokenizeRequest request = DetokenizeRequest.builder()
+ .detokenizeData(detokenizeData)
+ .continueOnError(true)
+ .downloadURL(true) // @deprecated — use downloadUrl(true)
+ .build();
+
+ DetokenizeResponse response = skyflowClient.vault().detokenize(request);
+ System.out.println("Detokenize Response: " + response);
+ } catch (SkyflowException e) {
+ System.out.println("Error during detokenization:");
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/samples/src/main/java/com/example/vault/deprecated/GetExample.java b/samples/src/main/java/com/example/vault/deprecated/GetExample.java
new file mode 100644
index 00000000..50e2e5fa
--- /dev/null
+++ b/samples/src/main/java/com/example/vault/deprecated/GetExample.java
@@ -0,0 +1,76 @@
+package com.example.vault.deprecated;
+
+import com.skyflow.Skyflow;
+import com.skyflow.config.Credentials;
+import com.skyflow.config.VaultConfig;
+import com.skyflow.enums.Env;
+import com.skyflow.enums.LogLevel;
+import com.skyflow.enums.RedactionType;
+import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.data.GetRequest;
+import com.skyflow.vault.data.GetResponse;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * @deprecated Pre-v2.1 pattern. The {@code "skyflow_id"} key in the response record map is deprecated.
+ * Use {@code "skyflowId"} instead (see {@link com.example.vault.GetExample}).
+ *
+ * This example is retained for reference during the deprecation window.
+ * Both {@code "skyflow_id"} and {@code "skyflowId"} are present in the response map until
+ * {@code "skyflow_id"} is removed in a future release.
+ */
+@Deprecated
+public class GetExample {
+ public static void main(String[] args) throws SkyflowException {
+ // Step 1: Set up credentials
+ Credentials credentials = new Credentials();
+ credentials.setCredentialsString("");
+
+ // Step 2: Configure the vault
+ VaultConfig vaultConfig = new VaultConfig();
+ vaultConfig.setVaultId("");
+ vaultConfig.setClusterId("");
+ vaultConfig.setEnv(Env.PROD);
+ vaultConfig.setCredentials(credentials);
+
+ // Step 3: Set up credentials for the Skyflow client
+ Credentials skyflowCredentials = new Credentials();
+ skyflowCredentials.setCredentialsString("");
+
+ // Step 4: Create a Skyflow client
+ Skyflow skyflowClient = Skyflow.builder()
+ .setLogLevel(LogLevel.ERROR)
+ .addVaultConfig(vaultConfig)
+ .addSkyflowCredentials(skyflowCredentials)
+ .build();
+
+ // Example: Fetch records and read the Skyflow ID using the deprecated "skyflow_id" key
+ // DEPRECATED: the response map contains both "skyflow_id" and "skyflowId".
+ // Access "skyflowId" instead — "skyflow_id" will be removed in a future release.
+ try {
+ ArrayList ids = new ArrayList<>();
+ ids.add("");
+
+ GetRequest request = GetRequest.builder()
+ .ids(ids)
+ .table("")
+ .redactionType(RedactionType.PLAIN_TEXT)
+ .build();
+
+ GetResponse response = skyflowClient.vault().get(request);
+
+ // DEPRECATED: reading "skyflow_id" from the response map
+ for (HashMap record : response.getData()) {
+ String deprecatedId = (String) record.get("skyflow_id"); // @deprecated — use "skyflowId"
+ String preferredId = (String) record.get("skyflowId"); // preferred
+ System.out.println("skyflow_id (deprecated): " + deprecatedId);
+ System.out.println("skyflowId (preferred) : " + preferredId);
+ }
+ } catch (SkyflowException e) {
+ System.out.println("Error during fetch:");
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/samples/src/main/java/com/example/vault/deprecated/UpdateExample.java b/samples/src/main/java/com/example/vault/deprecated/UpdateExample.java
index aaead19c..f33a7606 100644
--- a/samples/src/main/java/com/example/vault/deprecated/UpdateExample.java
+++ b/samples/src/main/java/com/example/vault/deprecated/UpdateExample.java
@@ -13,14 +13,18 @@
import java.util.HashMap;
/**
- * @deprecated Pre-v2.1 pattern. The "skyflow_id" key in the data map is deprecated.
- * Use "skyflowId" instead (see {@link com.example.vault.UpdateExample}).
+ * @deprecated Pre-v2.1 pattern. Demonstrates two deprecated APIs:
+ *
+ * - The {@code "skyflow_id"} key in the data map — use {@code "skyflowId"} instead.
+ * - {@code updateLogLevel()} on the Skyflow client — use {@code setLogLevel()} instead.
+ *
+ * See {@link com.example.vault.UpdateExample} for the current pattern.
*
- * This example is retained for reference during the deprecation window.
- * "skyflow_id" still works but emits a runtime warning and will be removed in a future release.
+ * Both still work but emit runtime warnings and will be removed in a future release.
*/
@Deprecated
public class UpdateExample {
+ @SuppressWarnings("deprecation")
public static void main(String[] args) throws SkyflowException {
// Step 1: Set up credentials for the first vault configuration
Credentials credentials = new Credentials();
@@ -38,11 +42,12 @@ public static void main(String[] args) throws SkyflowException {
skyflowCredentials.setCredentialsString(""); // Replace with the actual credentials string
// Step 4: Create a Skyflow client and add vault configurations
+ // DEPRECATED: use setLogLevel() instead of updateLogLevel()
Skyflow skyflowClient = Skyflow.builder()
- .setLogLevel(LogLevel.ERROR) // Enable debugging for detailed logs
- .addVaultConfig(vaultConfig) // Add the vault configuration
- .addSkyflowCredentials(skyflowCredentials) // Add general Skyflow credentials
+ .addVaultConfig(vaultConfig)
+ .addSkyflowCredentials(skyflowCredentials)
.build();
+ skyflowClient.updateLogLevel(LogLevel.ERROR); // @deprecated — use setLogLevel(LogLevel.ERROR)
// Step 5: Update records with TokenMode enabled
// DEPRECATED: use "skyflowId" key instead of "skyflow_id"
diff --git a/src/main/java/com/skyflow/ConnectionClient.java b/src/main/java/com/skyflow/ConnectionClient.java
index 7cb10713..d67122ad 100644
--- a/src/main/java/com/skyflow/ConnectionClient.java
+++ b/src/main/java/com/skyflow/ConnectionClient.java
@@ -86,6 +86,8 @@ private void prioritiseCredentials() throws SkyflowException {
} catch (DotenvException e) {
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(),
ErrorMessage.EmptyCredentials.getMessage());
+ } catch (SkyflowException e) {
+ throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/src/main/java/com/skyflow/VaultClient.java b/src/main/java/com/skyflow/VaultClient.java
index b9cdfd98..1d5e5d74 100644
--- a/src/main/java/com/skyflow/VaultClient.java
+++ b/src/main/java/com/skyflow/VaultClient.java
@@ -876,6 +876,8 @@ private void prioritiseCredentials() throws SkyflowException {
} catch (DotenvException e) {
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(),
ErrorMessage.EmptyCredentials.getMessage());
+ } catch (SkyflowException e) {
+ throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/src/main/java/com/skyflow/logs/InfoLogs.java b/src/main/java/com/skyflow/logs/InfoLogs.java
index af16698a..e747bfa2 100644
--- a/src/main/java/com/skyflow/logs/InfoLogs.java
+++ b/src/main/java/com/skyflow/logs/InfoLogs.java
@@ -101,7 +101,10 @@ public enum InfoLogs {
DEPRECATED_SKYFLOW_ID_REQUEST_KEY("[DEPRECATED] Request data key 'skyflow_id' is deprecated and will be removed in an upcoming release. Use 'skyflowId' instead."),
DEPRECATED_DOWNLOAD_URL("[DEPRECATED] Method 'downloadURL()' is deprecated and will be removed in an upcoming release. Use 'downloadUrl()' instead."),
DEPRECATED_GET_BYOT("[DEPRECATED] Method 'getBYOT()' is deprecated and will be removed in an upcoming release. Use 'getByot()' instead."),
- DEPRECATED_UPDATE_LOG_LEVEL("[DEPRECATED] Method 'updateLogLevel()' is deprecated and will be removed in an upcoming release. Use 'setLogLevel()' instead.");
+ DEPRECATED_UPDATE_LOG_LEVEL("[DEPRECATED] Method 'updateLogLevel()' is deprecated and will be removed in an upcoming release. Use 'setLogLevel()' instead."),
+ DEPRECATED_CREDENTIAL_CLIENT_ID("[DEPRECATED] Credential field 'clientID' is deprecated and will be removed in an upcoming release. Use 'clientId' instead."),
+ DEPRECATED_CREDENTIAL_KEY_ID("[DEPRECATED] Credential field 'keyID' is deprecated and will be removed in an upcoming release. Use 'keyId' instead."),
+ DEPRECATED_CREDENTIAL_TOKEN_URI("[DEPRECATED] Credential field 'tokenURI' is deprecated and will be removed in an upcoming release. Use 'tokenUri' instead.");
diff --git a/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java b/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java
index d2a57fb4..ad7cae30 100644
--- a/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java
+++ b/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java
@@ -108,6 +108,9 @@ private static V1GetAuthTokenResponse getBearerTokenFromCredentials(
JsonElement clientId = credentials.get("clientId");
if (clientId == null) {
clientId = credentials.get("clientID");
+ if (clientId != null) {
+ LogUtil.printWarningLog(InfoLogs.DEPRECATED_CREDENTIAL_CLIENT_ID.getLog());
+ }
}
if (clientId == null) {
LogUtil.printErrorLog(ErrorLogs.CLIENT_ID_IS_REQUIRED.getLog());
@@ -117,6 +120,9 @@ private static V1GetAuthTokenResponse getBearerTokenFromCredentials(
JsonElement keyId = credentials.get("keyId");
if (keyId == null) {
keyId = credentials.get("keyID");
+ if (keyId != null) {
+ LogUtil.printWarningLog(InfoLogs.DEPRECATED_CREDENTIAL_KEY_ID.getLog());
+ }
}
if (keyId == null) {
LogUtil.printErrorLog(ErrorLogs.KEY_ID_IS_REQUIRED.getLog());
@@ -126,6 +132,9 @@ private static V1GetAuthTokenResponse getBearerTokenFromCredentials(
JsonElement tokenUri = credentials.get("tokenUri");
if (tokenUri == null) {
tokenUri = credentials.get("tokenURI");
+ if (tokenUri != null) {
+ LogUtil.printWarningLog(InfoLogs.DEPRECATED_CREDENTIAL_TOKEN_URI.getLog());
+ }
}
if (tokenUri == null) {
LogUtil.printErrorLog(ErrorLogs.TOKEN_URI_IS_REQUIRED.getLog());
diff --git a/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java b/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java
index efb6600e..b909e45b 100644
--- a/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java
+++ b/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java
@@ -109,6 +109,9 @@ private static List generateSignedTokensFromCredentials
JsonElement clientId = credentials.get("clientId");
if (clientId == null) {
clientId = credentials.get("clientID");
+ if (clientId != null) {
+ LogUtil.printWarningLog(InfoLogs.DEPRECATED_CREDENTIAL_CLIENT_ID.getLog());
+ }
}
if (clientId == null) {
LogUtil.printErrorLog(ErrorLogs.CLIENT_ID_IS_REQUIRED.getLog());
@@ -118,6 +121,9 @@ private static List generateSignedTokensFromCredentials
JsonElement keyId = credentials.get("keyId");
if (keyId == null) {
keyId = credentials.get("keyID");
+ if (keyId != null) {
+ LogUtil.printWarningLog(InfoLogs.DEPRECATED_CREDENTIAL_KEY_ID.getLog());
+ }
}
if (keyId == null) {
LogUtil.printErrorLog(ErrorLogs.KEY_ID_IS_REQUIRED.getLog());
diff --git a/src/main/java/com/skyflow/utils/validations/Validations.java b/src/main/java/com/skyflow/utils/validations/Validations.java
index 3bd75626..0c36445c 100644
--- a/src/main/java/com/skyflow/utils/validations/Validations.java
+++ b/src/main/java/com/skyflow/utils/validations/Validations.java
@@ -968,7 +968,7 @@ public static void validateDeidentifyFileRequest(DeidentifyFileRequest request)
if (request.getWaitTime() != null && request.getWaitTime() <= 0) {
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidWaitTime.getMessage());
}
- if(request.getWaitTime() > 64) {
+ if(request.getWaitTime() != null && request.getWaitTime() > 64) {
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.WaitTimeExceedsLimit.getMessage());
}
}
diff --git a/src/main/java/com/skyflow/vault/controller/VaultController.java b/src/main/java/com/skyflow/vault/controller/VaultController.java
index 980760ae..1812b83b 100644
--- a/src/main/java/com/skyflow/vault/controller/VaultController.java
+++ b/src/main/java/com/skyflow/vault/controller/VaultController.java
@@ -238,7 +238,7 @@ public InsertResponse insert(InsertRequest insertRequest) throws SkyflowExceptio
return new InsertResponse(null, errorFields.isEmpty() ? null : errorFields);
}
if (errorFields.isEmpty()) {
- return new InsertResponse(insertedFields.isEmpty() ? null : insertedFields, null);
+ return new InsertResponse(insertedFields, null);
}
return new InsertResponse(insertedFields, errorFields);
}
diff --git a/src/test/java/com/skyflow/ConnectionClientDotenvTests.java b/src/test/java/com/skyflow/ConnectionClientDotenvTests.java
new file mode 100644
index 00000000..4916f628
--- /dev/null
+++ b/src/test/java/com/skyflow/ConnectionClientDotenvTests.java
@@ -0,0 +1,83 @@
+package com.skyflow;
+
+import com.skyflow.config.ConnectionConfig;
+import com.skyflow.errors.ErrorMessage;
+import com.skyflow.errors.SkyflowException;
+import com.skyflow.utils.Constants;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+/**
+ * Tests for ConnectionClient's prioritiseCredentials dotenv path.
+ *
+ * These tests write a temporary .env file to exercise the code path where
+ * no ConnectionConfig credentials and no common credentials are set, so the
+ * code falls through to read from a .env file.
+ */
+public class ConnectionClientDotenvTests {
+
+ private static final String ENV_FILE = ".env";
+ private byte[] originalEnvContent;
+
+ @Before
+ public void saveEnvFileState() throws IOException {
+ File f = new File(ENV_FILE);
+ originalEnvContent = f.exists() ? Files.readAllBytes(Paths.get(ENV_FILE)) : null;
+ }
+
+ @After
+ public void restoreEnvFile() throws IOException {
+ if (originalEnvContent != null) {
+ Files.write(Paths.get(ENV_FILE), originalEnvContent);
+ } else {
+ Files.deleteIfExists(Paths.get(ENV_FILE));
+ }
+ }
+
+ private ConnectionClient buildClientWithNoCreds(String id) {
+ ConnectionConfig config = new ConnectionConfig();
+ config.setConnectionId(id);
+ config.setConnectionUrl("https://test.dotenv.url");
+ // No credentials on config, no commonCredentials
+ return new ConnectionClient(config, null);
+ }
+
+ @Test
+ public void testPrioritiseCredentials_dotenvReturnsCredentials_setsCredentials() throws Exception {
+ // Write a .env file with a valid credentials string value
+ try (FileWriter fw = new FileWriter(ENV_FILE)) {
+ fw.write(Constants.ENV_CREDENTIALS_KEY_NAME + "={\"token\":\"env-token-value\"}\n");
+ }
+
+ ConnectionClient client = buildClientWithNoCreds("dotenv-valid-1");
+ // updateConnectionConfig calls prioritiseCredentials which reads from .env
+ client.updateConnectionConfig(client.getConnectionConfig());
+ }
+
+ @Test
+ public void testPrioritiseCredentials_dotenvReturnsNullKey_throwsSkyflowException() throws Exception {
+ // Write a .env file WITHOUT the SKYFLOW_CREDENTIALS key
+ try (FileWriter fw = new FileWriter(ENV_FILE)) {
+ fw.write("SOME_OTHER_KEY=some_value\n");
+ }
+
+ ConnectionClient client = buildClientWithNoCreds("dotenv-null-1");
+ // Null sysCredentials → SkyflowException thrown directly
+ try {
+ client.updateConnectionConfig(client.getConnectionConfig());
+ Assert.fail("Should have thrown SkyflowException");
+ } catch (SkyflowException e) {
+ Assert.assertTrue(e.getMessage().contains(ErrorMessage.EmptyCredentials.getMessage()));
+ } catch (RuntimeException e) {
+ Assert.fail("Expected direct SkyflowException, not RuntimeException wrapping it");
+ }
+ }
+}
diff --git a/src/test/java/com/skyflow/ConnectionClientTests.java b/src/test/java/com/skyflow/ConnectionClientTests.java
index 4a69120c..c24bb20a 100644
--- a/src/test/java/com/skyflow/ConnectionClientTests.java
+++ b/src/test/java/com/skyflow/ConnectionClientTests.java
@@ -2,6 +2,7 @@
import com.skyflow.config.ConnectionConfig;
import com.skyflow.config.Credentials;
+import com.skyflow.errors.SkyflowException;
import io.github.cdimascio.dotenv.Dotenv;
import org.junit.Assert;
import org.junit.BeforeClass;
@@ -92,4 +93,96 @@ public void testSetBearerTokenWithEnvCredentials() {
Assert.fail(INVALID_EXCEPTION_THROWN);
}
}
+
+ @Test
+ public void testSetBearerToken_withApiKey_setsAndReusesApiKey() {
+ try {
+ Credentials creds = new Credentials();
+ creds.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321");
+ ConnectionConfig config = new ConnectionConfig();
+ config.setConnectionId("isolated-apikey-1");
+ config.setConnectionUrl("https://test.isolated.url");
+ config.setCredentials(creds);
+ ConnectionClient client = new ConnectionClient(config, null);
+
+ // First call: apiKey == null → setApiKey() sets it
+ client.setBearerToken();
+ Assert.assertEquals("sky-ab123-abcd1234cdef1234abcd4321cdef4321", client.apiKey);
+
+ // Second call: apiKey != null → setApiKey() logs REUSE_API_KEY (line 60)
+ client.setBearerToken();
+ Assert.assertEquals("sky-ab123-abcd1234cdef1234abcd4321cdef4321", client.apiKey);
+ } catch (Exception e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN);
+ }
+ }
+
+ @Test
+ public void testSetBearerToken_withValidNonExpiredToken_reusesBearerToken() {
+ try {
+ // far-future JWT: base64({"exp":9999999999}) = eyJleHAiOjk5OTk5OTk5OTl9 — never expires
+ Credentials creds = new Credentials();
+ creds.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y");
+ ConnectionConfig config = new ConnectionConfig();
+ config.setConnectionId("isolated-token-1");
+ config.setConnectionUrl("https://test.isolated.url");
+ config.setCredentials(creds);
+ ConnectionClient client = new ConnectionClient(config, null);
+
+ // First call: this.token == null → Token.isExpired(null)=true → generates token from creds.getToken()
+ client.setBearerToken();
+ Assert.assertEquals("x.eyJleHAiOjk5OTk5OTk5OTl9.y", client.token);
+
+ // Second call: token not null, not empty, not expired → REUSE_BEARER_TOKEN else branch (line 52)
+ client.setBearerToken();
+ Assert.assertEquals("x.eyJleHAiOjk5OTk5OTk5OTl9.y", client.token);
+ } catch (Exception e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN);
+ }
+ }
+
+ @Test
+ public void testPrioritiseCredentials_credentialChange_resetsToken() {
+ try {
+ Credentials credentialsA = new Credentials();
+ credentialsA.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y");
+ ConnectionConfig config = new ConnectionConfig();
+ config.setConnectionId("isolated-change-1");
+ config.setConnectionUrl("https://test.isolated.url");
+ config.setCredentials(credentialsA);
+ ConnectionClient client = new ConnectionClient(config, null);
+
+ client.updateConnectionConfig(config); // sets finalCredentials = credentialsA (original=null → no reset)
+ client.token = "cached-token-value"; // simulate previously obtained bearer token
+
+ // Change to different credentials object
+ Credentials credentialsB = new Credentials();
+ credentialsB.setToken("different-token");
+ config.setCredentials(credentialsB);
+
+ client.updateConnectionConfig(config); // original=A, new=B → !A.equals(B) → reset (lines 83-84)
+ Assert.assertNull(client.token);
+ Assert.assertNull(client.apiKey);
+ } catch (Exception e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN);
+ }
+ }
+
+ @Test
+ public void testSetBearerToken_noCredentials_throwsEmptyCredentials() {
+ ConnectionConfig config = new ConnectionConfig();
+ config.setConnectionId("isolated-nocreds-1");
+ config.setConnectionUrl("https://test.isolated.url");
+ // No credentials on config, no commonCredentials
+ ConnectionClient client = new ConnectionClient(config, null);
+ try {
+ client.setBearerToken();
+ Assert.fail("Should have thrown SkyflowException");
+ } catch (SkyflowException e) {
+ // SkyflowException expected — message varies by environment
+ // (EmptyCredentials when no .env, or credential error when .env provides creds)
+ } catch (Exception e) {
+ Assert.fail("Expected SkyflowException, got: " + e.getClass().getName());
+ }
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/skyflow/SkyflowTests.java b/src/test/java/com/skyflow/SkyflowTests.java
index 12e83e06..983419a7 100644
--- a/src/test/java/com/skyflow/SkyflowTests.java
+++ b/src/test/java/com/skyflow/SkyflowTests.java
@@ -685,4 +685,163 @@ public void testDetectMethodWithInvalidVaultId() {
Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage());
}
}
+
+ @Test
+ public void testUpdateVaultConfig_withNewClusterIdAndCredentials_updatesAllFields() {
+ try {
+ VaultConfig config = new VaultConfig();
+ config.setVaultId(vaultID);
+ config.setClusterId(clusterID);
+ config.setEnv(Env.DEV);
+ Credentials creds = new Credentials();
+ creds.setToken(token);
+ config.setCredentials(creds);
+ Skyflow skyflowClient = Skyflow.builder().addVaultConfig(config).build();
+
+ // Update with a new non-null clusterId and new non-null credentials — covers
+ // the non-null (true) branches for all three ternaries in findAndUpdateVaultConfig
+ Credentials newCreds = new Credentials();
+ newCreds.setToken("updated-token-value");
+ VaultConfig update = new VaultConfig();
+ update.setVaultId(vaultID);
+ update.setClusterId(newClusterID);
+ update.setEnv(Env.PROD);
+ update.setCredentials(newCreds);
+ skyflowClient.updateVaultConfig(update);
+ Assert.assertEquals(newClusterID, skyflowClient.getVaultConfig(vaultID).getClusterId());
+ Assert.assertEquals(Env.PROD, skyflowClient.getVaultConfig(vaultID).getEnv());
+ Assert.assertEquals("updated-token-value", skyflowClient.getVaultConfig(vaultID).getCredentials().getToken());
+ } catch (SkyflowException e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN);
+ }
+ }
+
+ @Test
+ public void testUpdateConnectionConfig_withNewCredentials_updatesCredentials() {
+ try {
+ ConnectionConfig config = new ConnectionConfig();
+ config.setConnectionId(connectionID);
+ config.setConnectionUrl(connectionURL);
+ Credentials oldCreds = new Credentials();
+ oldCreds.setToken(token);
+ config.setCredentials(oldCreds);
+ Skyflow skyflowClient = Skyflow.builder().addConnectionConfig(config).build();
+
+ // Update with new non-null credentials and new non-null connectionUrl — covers
+ // the non-null (true) branches for both ternaries in findAndUpdateConnectionConfig
+ Credentials newCreds = new Credentials();
+ newCreds.setToken("new-token-value");
+ ConnectionConfig update = new ConnectionConfig();
+ update.setConnectionId(connectionID);
+ update.setConnectionUrl(newConnectionURL);
+ update.setCredentials(newCreds);
+ skyflowClient.updateConnectionConfig(update);
+ Assert.assertEquals("new-token-value", skyflowClient.getConnectionConfig(connectionID).getCredentials().getToken());
+ Assert.assertEquals(newConnectionURL, skyflowClient.getConnectionConfig(connectionID).getConnectionUrl());
+ } catch (SkyflowException e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN);
+ }
+ }
+
+ @Test
+ public void testUpdateVaultConfig_withNullEnv_fallsBackToPreviousEnv() {
+ // VaultConfig's constructor defaults env=PROD so getEnv() is never null via normal API.
+ // Use an anonymous subclass to make getEnv() return null, exercising the false branch
+ // of `vaultConfig.getEnv() != null` in findAndUpdateVaultConfig.
+ try {
+ Credentials creds = new Credentials();
+ creds.setToken(token);
+ VaultConfig initial = new VaultConfig();
+ initial.setVaultId(vaultID);
+ initial.setClusterId(clusterID);
+ initial.setEnv(Env.SANDBOX);
+ initial.setCredentials(creds);
+ Skyflow skyflowClient = Skyflow.builder().addVaultConfig(initial).build();
+
+ VaultConfig updateWithNullEnv = new VaultConfig() {
+ @Override public Env getEnv() { return null; }
+ };
+ updateWithNullEnv.setVaultId(vaultID);
+ updateWithNullEnv.setClusterId(clusterID);
+ updateWithNullEnv.setCredentials(creds);
+
+ skyflowClient.updateVaultConfig(updateWithNullEnv);
+ // env falls back to previous (SANDBOX)
+ Assert.assertEquals(Env.SANDBOX, skyflowClient.getVaultConfig(vaultID).getEnv());
+ } catch (SkyflowException e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN);
+ }
+ }
+
+ @Test
+ public void testFindAndUpdateVaultConfig_withNullClusterId_fallsBackToPreviousClusterId() {
+ // Validation enforces non-null clusterId, so the false branch of
+ // `vaultConfig.getClusterId() != null` in findAndUpdateVaultConfig is unreachable
+ // via the normal flow. Call the private method directly via reflection.
+ try {
+ Credentials creds = new Credentials();
+ creds.setToken(token);
+ VaultConfig initial = new VaultConfig();
+ initial.setVaultId(vaultID);
+ initial.setClusterId(clusterID);
+ initial.setEnv(Env.DEV);
+ initial.setCredentials(creds);
+ Skyflow skyflowClient = Skyflow.builder().addVaultConfig(initial).build();
+
+ java.lang.reflect.Field builderField = Skyflow.class.getDeclaredField("builder");
+ builderField.setAccessible(true);
+ Object builder = builderField.get(skyflowClient);
+
+ VaultConfig nullClusterConfig = new VaultConfig();
+ nullClusterConfig.setVaultId(vaultID);
+ // Override clusterId field to null via reflection (setter enforces non-null)
+ java.lang.reflect.Field clusterIdField = VaultConfig.class.getDeclaredField("clusterId");
+ clusterIdField.setAccessible(true);
+ clusterIdField.set(nullClusterConfig, null);
+
+ java.lang.reflect.Method method = builder.getClass().getDeclaredMethod(
+ "findAndUpdateVaultConfig", VaultConfig.class);
+ method.setAccessible(true);
+ VaultConfig result = (VaultConfig) method.invoke(builder, nullClusterConfig);
+
+ Assert.assertEquals(clusterID, result.getClusterId());
+ } catch (SkyflowException e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN);
+ } catch (Exception e) {
+ Assert.fail("Reflection failed: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testFindAndUpdateConnectionConfig_withNullConnectionUrl_fallsBackToPreviousUrl() {
+ // `findAndUpdateConnectionConfig` has a ternary for connectionUrl that falls back
+ // to previousConfig.getConnectionUrl() when the incoming url is null.
+ // Since validation enforces non-null url, we call the private method directly
+ // via reflection to cover the false branch.
+ try {
+ ConnectionConfig initial = new ConnectionConfig();
+ initial.setConnectionId(connectionID);
+ initial.setConnectionUrl(connectionURL);
+ Skyflow skyflowClient = Skyflow.builder().addConnectionConfig(initial).build();
+
+ java.lang.reflect.Field builderField = Skyflow.class.getDeclaredField("builder");
+ builderField.setAccessible(true);
+ Object builder = builderField.get(skyflowClient);
+
+ ConnectionConfig nullUrlConfig = new ConnectionConfig();
+ nullUrlConfig.setConnectionId(connectionID);
+ // connectionUrl not set → remains null
+
+ java.lang.reflect.Method method = builder.getClass().getDeclaredMethod(
+ "findAndUpdateConnectionConfig", ConnectionConfig.class);
+ method.setAccessible(true);
+ ConnectionConfig result = (ConnectionConfig) method.invoke(builder, nullUrlConfig);
+
+ Assert.assertEquals(connectionURL, result.getConnectionUrl());
+ } catch (SkyflowException e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN);
+ } catch (Exception e) {
+ Assert.fail("Reflection failed: " + e.getMessage());
+ }
+ }
}
diff --git a/src/test/java/com/skyflow/VaultClientDotenvTests.java b/src/test/java/com/skyflow/VaultClientDotenvTests.java
new file mode 100644
index 00000000..1a54b13d
--- /dev/null
+++ b/src/test/java/com/skyflow/VaultClientDotenvTests.java
@@ -0,0 +1,103 @@
+package com.skyflow;
+
+import com.skyflow.config.Credentials;
+import com.skyflow.config.VaultConfig;
+import com.skyflow.enums.Env;
+import com.skyflow.errors.ErrorMessage;
+import com.skyflow.errors.SkyflowException;
+import com.skyflow.utils.Constants;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+/**
+ * Tests for VaultClient's prioritiseCredentials dotenv path.
+ *
+ * These tests write a temporary .env file to exercise the code path where
+ * no VaultConfig credentials and no common credentials are set, so the code
+ * falls through to read from a .env file.
+ */
+public class VaultClientDotenvTests {
+
+ private static final String ENV_FILE = ".env";
+ private byte[] originalEnvContent;
+
+ @Before
+ public void saveEnvFileState() throws IOException {
+ File f = new File(ENV_FILE);
+ originalEnvContent = f.exists() ? Files.readAllBytes(Paths.get(ENV_FILE)) : null;
+ }
+
+ @After
+ public void restoreEnvFile() throws IOException {
+ if (originalEnvContent != null) {
+ Files.write(Paths.get(ENV_FILE), originalEnvContent);
+ } else {
+ Files.deleteIfExists(Paths.get(ENV_FILE));
+ }
+ }
+
+ private VaultClient buildClientWithNoCreds(String vaultId, String clusterId) {
+ VaultConfig config = new VaultConfig();
+ config.setVaultId(vaultId);
+ config.setClusterId(clusterId);
+ config.setEnv(Env.DEV);
+ // No credentials set
+ return new VaultClient(config, null);
+ }
+
+ /**
+ * Covers the dotenv success path: Dotenv.load() succeeds and returns a
+ * non-null credentials string, so finalCredentials is set via
+ * credentialsString. Lines ~862-870 of VaultClient.java.
+ */
+ @Test
+ public void testPrioritiseCredentials_dotenvReturnsCredentials_setsCredentials() throws Exception {
+ // Write a .env file with a valid credentials string value
+ try (FileWriter fw = new FileWriter(ENV_FILE)) {
+ fw.write(Constants.ENV_CREDENTIALS_KEY_NAME + "={\"token\":\"env-token-value\"}\n");
+ }
+
+ VaultClient client = buildClientWithNoCreds("dotenv-vault-1", "cluster1");
+ // updateVaultConfig() calls prioritiseCredentials() which reads from .env
+ // Should not throw since sysCredentials is non-null
+ client.updateVaultConfig();
+
+ // finalCredentials should be set with credentials string
+ java.lang.reflect.Field field = VaultClient.class.getDeclaredField("finalCredentials");
+ field.setAccessible(true);
+ Credentials finalCreds = (Credentials) field.get(client);
+ Assert.assertNotNull(finalCreds);
+ Assert.assertEquals("{\"token\":\"env-token-value\"}", finalCreds.getCredentialsString());
+ }
+
+ /**
+ * Covers the path where dotenv loads but the key is absent (returns null),
+ * causing SkyflowException(EmptyCredentials) to be thrown directly.
+ * Lines ~864-876 of VaultClient.java.
+ */
+ @Test
+ public void testPrioritiseCredentials_dotenvKeyMissing_throwsSkyflowException() throws Exception {
+ // Write a .env file WITHOUT the SKYFLOW_CREDENTIALS key
+ try (FileWriter fw = new FileWriter(ENV_FILE)) {
+ fw.write("SOME_OTHER_KEY=some_value\n");
+ }
+
+ VaultClient client = buildClientWithNoCreds("dotenv-vault-2", "cluster2");
+ try {
+ client.updateVaultConfig();
+ Assert.fail("Should have thrown SkyflowException");
+ } catch (SkyflowException e) {
+ Assert.assertTrue(e.getMessage().contains(ErrorMessage.EmptyCredentials.getMessage()));
+ } catch (RuntimeException e) {
+ Assert.fail("Expected direct SkyflowException, not RuntimeException wrapping it");
+ }
+ }
+}
diff --git a/src/test/java/com/skyflow/VaultClientTests.java b/src/test/java/com/skyflow/VaultClientTests.java
index f3e9f738..d98f5964 100644
--- a/src/test/java/com/skyflow/VaultClientTests.java
+++ b/src/test/java/com/skyflow/VaultClientTests.java
@@ -29,13 +29,23 @@
import com.skyflow.vault.tokens.DetokenizeData;
import com.skyflow.vault.tokens.DetokenizeRequest;
import com.skyflow.vault.tokens.TokenizeRequest;
+import com.skyflow.vault.data.FileUploadRequest;
import io.github.cdimascio.dotenv.Dotenv;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.mockito.Mockito;
import java.io.File;
import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
public class VaultClientTests {
private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception";
@@ -168,26 +178,6 @@ public void testGetDetokenizePayload() {
}
}
- @Test
- public void testGetDetokenizePayloadWithNewDownloadUrl() {
- try {
- DetokenizeData detokenizeDataRecord1 = new DetokenizeData(token);
- detokenizeData.clear();
- detokenizeData.add(detokenizeDataRecord1);
- DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder()
- .detokenizeData(detokenizeData)
- .downloadUrl(true) // new form
- .continueOnError(false)
- .build();
- V1DetokenizePayload payload = vaultClient.getDetokenizePayload(detokenizeRequest);
- Assert.assertTrue("new downloadUrl() should be reflected in payload", payload.getDownloadUrl().get());
- Assert.assertTrue("new getDownloadUrl() should return true", detokenizeRequest.getDownloadUrl());
- Assert.assertTrue("deprecated getDownloadURL() should return same value", detokenizeRequest.getDownloadURL());
- } catch (Exception e) {
- Assert.fail(INVALID_EXCEPTION_THROWN);
- }
- }
-
@Test
public void testGetBulkInsertRequestBody() {
try {
@@ -948,4 +938,317 @@ private void setPrivateField(Object obj, String fieldName, Object value) throws
field.setAccessible(true);
field.set(obj, value);
}
+
+ @Test
+ public void testGetFileForFileUpload_withFileObject() {
+ try {
+ java.io.File fileObj = java.io.File.createTempFile("upload-test", ".txt");
+ fileObj.deleteOnExit();
+ FileUploadRequest request = FileUploadRequest.builder()
+ .fileObject(fileObj)
+ .table("test_table")
+ .columnName("test_col")
+ .build();
+ java.io.File result = vaultClient.getFileForFileUpload(request);
+ Assert.assertEquals(fileObj, result);
+ } catch (Exception e) {
+ Assert.fail("Should not have thrown: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetBearerToken_validNonExpiredToken_reusesToken() {
+ try {
+ // far-future JWT: header.payload.sig where payload base64 decodes to {"exp":9999999999}
+ Credentials creds = new Credentials();
+ creds.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y");
+ VaultConfig config = new VaultConfig();
+ config.setVaultId(vaultID);
+ config.setClusterId(clusterID);
+ config.setEnv(com.skyflow.enums.Env.DEV);
+ config.setCredentials(creds);
+ VaultClient freshClient = new VaultClient(config, null);
+
+ // First call: token=null → generates from creds.getToken()
+ freshClient.setBearerToken();
+ Assert.assertEquals("x.eyJleHAiOjk5OTk5OTk5OTl9.y", getPrivateField(freshClient, "token"));
+
+ // Second call: token valid, not expired → REUSE_BEARER_TOKEN else branch
+ freshClient.setBearerToken();
+ Assert.assertEquals("x.eyJleHAiOjk5OTk5OTk5OTl9.y", getPrivateField(freshClient, "token"));
+ } catch (Exception e) {
+ Assert.fail("Should not have thrown: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetDeidentifyImageRequest_withMaskingMethod() {
+ try {
+ java.io.File file = new java.io.File("test.jpg");
+ FileInput fileInput = FileInput.builder().file(file).build();
+ List entities = Arrays.asList(DetectEntities.NAME);
+ TokenFormat tokenFormat = TokenFormat.builder().entityOnly(entities).build();
+
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(fileInput)
+ .entities(entities)
+ .tokenFormat(tokenFormat)
+ .maskingMethod(MaskingMethod.BLACKBOX)
+ .outputProcessedImage(true)
+ .build();
+
+ DeidentifyFileImageRequestDeidentifyImage imageRequest =
+ vaultClient.getDeidentifyImageRequest(request, vaultID, "base64content", "jpg");
+
+ Assert.assertNotNull(imageRequest);
+ Assert.assertTrue(imageRequest.getMaskingMethod().isPresent());
+ } catch (Exception e) {
+ Assert.fail("Should not have thrown: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetDeIdentifyTextResponse_withEntityScores() {
+ Locations location = Locations.builder()
+ .startIndex(0)
+ .endIndex(5)
+ .startIndexProcessed(0)
+ .endIndexProcessed(5)
+ .build();
+
+ Map scores = new HashMap<>();
+ scores.put("EMAIL_ADDRESS", 0.95);
+
+ StringResponseEntities entity = StringResponseEntities.builder()
+ .location(location)
+ .token("tok")
+ .value("val")
+ .entityType("EMAIL_ADDRESS")
+ .entityScores(scores)
+ .build();
+
+ DeidentifyStringResponse deidentifyResponse = DeidentifyStringResponse.builder()
+ .entities(Collections.singletonList(entity))
+ .processedText("processed text")
+ .wordCount(2)
+ .characterCount(13)
+ .build();
+
+ DeidentifyTextResponse result = vaultClient.getDeIdentifyTextResponse(deidentifyResponse);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals(1, result.getEntities().size());
+ // Entity scores map lambda was invoked → getScores() should have the score
+ Assert.assertEquals(0.95, result.getEntities().get(0).getScores().get("EMAIL_ADDRESS"), 0.001);
+ }
+
+ @Test
+ public void testPrioritiseCredentials_credentialChange_resetsTokenAndApiKey() {
+ try {
+ Credentials credentialsA = new Credentials();
+ credentialsA.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y");
+ VaultConfig config = new VaultConfig();
+ config.setVaultId("isolated-vault-change");
+ config.setClusterId(clusterID);
+ config.setEnv(com.skyflow.enums.Env.DEV);
+ config.setCredentials(credentialsA);
+ VaultClient freshClient = new VaultClient(config, null);
+
+ freshClient.updateVaultConfig(); // sets finalCredentials = credentialsA
+ setPrivateField(freshClient, "token", "cached-token"); // simulate prior auth
+
+ Credentials credentialsB = new Credentials();
+ credentialsB.setToken("other-token");
+ config.setCredentials(credentialsB);
+
+ freshClient.updateVaultConfig(); // original=A, new=B → different → reset token/apiKey
+ Assert.assertNull(getPrivateField(freshClient, "token"));
+ Assert.assertNull(getPrivateField(freshClient, "apiKey"));
+ } catch (Exception e) {
+ Assert.fail("Should not have thrown: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetBearerToken_noCredentials_throwsEmptyCredentials() {
+ VaultConfig config = new VaultConfig();
+ config.setVaultId("isolated-vault-nocreds");
+ config.setClusterId(clusterID);
+ config.setEnv(com.skyflow.enums.Env.DEV);
+ // No credentials — will hit dotenv path → DotenvException → SkyflowException(EmptyCredentials)
+ VaultClient freshClient = new VaultClient(config, null);
+ try {
+ freshClient.setBearerToken();
+ Assert.fail("Should have thrown SkyflowException");
+ } catch (SkyflowException e) {
+ // SkyflowException expected — message varies by environment
+ // (EmptyCredentials when no .env, or credential error when .env provides creds)
+ } catch (Exception e) {
+ Assert.fail("Expected SkyflowException, got: " + e.getClass().getName() + ": " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testUpdateExecutorInHTTP_interceptorAddsAuthorizationHeader() {
+ try {
+ Credentials creds = new Credentials();
+ creds.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y");
+ VaultConfig config = new VaultConfig();
+ config.setVaultId("isolated-vault-http");
+ config.setClusterId(clusterID);
+ config.setEnv(com.skyflow.enums.Env.DEV);
+ config.setCredentials(creds);
+ VaultClient freshClient = new VaultClient(config, null);
+
+ freshClient.setBearerToken(); // triggers updateExecutorInHTTP → creates sharedHttpClient with interceptor
+
+ // Access sharedHttpClient via reflection
+ java.lang.reflect.Field field = VaultClient.class.getDeclaredField("sharedHttpClient");
+ field.setAccessible(true);
+ OkHttpClient httpClient = (OkHttpClient) field.get(freshClient);
+ Assert.assertNotNull(httpClient);
+ Assert.assertFalse(httpClient.interceptors().isEmpty());
+
+ // Get the interceptor (our lambda)
+ okhttp3.Interceptor interceptor = httpClient.interceptors().get(0);
+
+ // Mock Chain and invoke the interceptor
+ okhttp3.Interceptor.Chain mockChain = Mockito.mock(okhttp3.Interceptor.Chain.class);
+ Request mockRequest = new Request.Builder().url("https://example.com").build();
+ Mockito.when(mockChain.request()).thenReturn(mockRequest);
+
+ Response mockResponse = new Response.Builder()
+ .request(mockRequest)
+ .protocol(Protocol.HTTP_1_1)
+ .code(200)
+ .message("OK")
+ .body(ResponseBody.create("", MediaType.get("application/json")))
+ .build();
+ Mockito.when(mockChain.proceed(Mockito.any(Request.class))).thenReturn(mockResponse);
+
+ // Invoke the lambda — this covers lambda$updateExecutorInHTTP$21
+ Response response = interceptor.intercept(mockChain);
+ Assert.assertNotNull(response);
+
+ // Verify the interceptor added the Authorization header
+ Mockito.verify(mockChain).proceed(Mockito.argThat(req ->
+ req.header("Authorization") != null &&
+ req.header("Authorization").startsWith("Bearer ")
+ ));
+ } catch (Exception e) {
+ Assert.fail("Should not have thrown: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetFileForFileUpload_withNoFileInput_returnsNull() {
+ try {
+ FileUploadRequest request = FileUploadRequest.builder()
+ .table("test_table")
+ .columnName("test_col")
+ .build();
+ java.io.File result = vaultClient.getFileForFileUpload(request);
+ Assert.assertNull(result);
+ } catch (Exception e) {
+ Assert.fail("Should not have thrown: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetDeidentifyImageRequest_withEntityOnlyNull_andEntityUniqueCounterNonEmpty() {
+ try {
+ java.io.File file = new java.io.File("test.jpg");
+ FileInput fileInput = FileInput.builder().file(file).build();
+ List entities = Arrays.asList(DetectEntities.NAME);
+ TokenFormat tokenFormat = TokenFormat.builder()
+ .entityUniqueCounter(entities)
+ .build();
+
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(fileInput)
+ .entities(entities)
+ .tokenFormat(tokenFormat)
+ .build();
+
+ DeidentifyFileImageRequestDeidentifyImage imageRequest =
+ vaultClient.getDeidentifyImageRequest(request, vaultID, "base64content", "jpg");
+
+ Assert.assertNotNull(imageRequest);
+ } catch (Exception e) {
+ Assert.fail("Should not have thrown: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetDeidentifyImageRequest_withEmptyEntityOnlyList() {
+ try {
+ java.io.File file = new java.io.File("test.jpg");
+ FileInput fileInput = FileInput.builder().file(file).build();
+ List entities = Arrays.asList(DetectEntities.NAME);
+ TokenFormat tokenFormat = TokenFormat.builder()
+ .entityOnly(Collections.emptyList())
+ .entityUniqueCounter(Collections.emptyList())
+ .build();
+
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(fileInput)
+ .entities(entities)
+ .tokenFormat(tokenFormat)
+ .build();
+
+ DeidentifyFileImageRequestDeidentifyImage imageRequest =
+ vaultClient.getDeidentifyImageRequest(request, vaultID, "base64content", "jpg");
+ Assert.assertNotNull(imageRequest);
+ } catch (Exception e) {
+ Assert.fail("Should not have thrown: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetDeidentifyGenericFileRequest_withEmptyEntityLists() {
+ try {
+ java.io.File file = new java.io.File("test.pdf");
+ FileInput fileInput = FileInput.builder().file(file).build();
+ List entities = Arrays.asList(DetectEntities.NAME);
+ TokenFormat tokenFormat = TokenFormat.builder()
+ .entityOnly(Collections.emptyList())
+ .entityUniqueCounter(Collections.emptyList())
+ .build();
+
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(fileInput)
+ .entities(entities)
+ .tokenFormat(tokenFormat)
+ .build();
+
+ com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequest result =
+ vaultClient.getDeidentifyGenericFileRequest(request, vaultID, "base64content", "pdf");
+ Assert.assertNotNull(result);
+ } catch (Exception e) {
+ Assert.fail("Should not have thrown: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetDeidentifyGenericFileRequest_withNullFileExtension() {
+ // Covers the `fileExtension != null ? ... : null` false branch at line 779.
+ // The ternary evaluates null, which is then passed to FileData.builder().dataFormat(null)
+ // which throws — confirming the null branch of the ternary was exercised.
+ java.io.File file = new java.io.File("test.pdf");
+ FileInput fileInput = FileInput.builder().file(file).build();
+ List entities = Arrays.asList(DetectEntities.NAME);
+
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(fileInput)
+ .entities(entities)
+ .build();
+
+ try {
+ vaultClient.getDeidentifyGenericFileRequest(request, vaultID, "base64content", null);
+ Assert.fail("Expected exception from null dataFormat");
+ } catch (Exception e) {
+ // null fileExtension → ternary false branch → null passed to dataFormat() → throws
+ Assert.assertNotNull(e.getMessage());
+ }
+ }
}
diff --git a/src/test/java/com/skyflow/serviceaccount/util/TokenTests.java b/src/test/java/com/skyflow/serviceaccount/util/TokenTests.java
index ed5c72b2..88887681 100644
--- a/src/test/java/com/skyflow/serviceaccount/util/TokenTests.java
+++ b/src/test/java/com/skyflow/serviceaccount/util/TokenTests.java
@@ -52,4 +52,16 @@ public void testExpiredTokenForIsExpiredToken() {
Assert.fail(INVALID_EXCEPTION_THROWN);
}
}
+
+ @Test
+ public void testExpiredJwtTokenForIsExpiredToken() {
+ // 3-part fake JWT: middle is base64({"exp":1}) = eyJleHAiOjF9, exp=1970 → always expired
+ Assert.assertTrue(Token.isExpired("x.eyJleHAiOjF9.y"));
+ }
+
+ @Test
+ public void testValidJwtTokenForIsExpiredToken() {
+ // 3-part fake JWT: middle is base64({"exp":9999999999}) = eyJleHAiOjk5OTk5OTk5OTl9, far-future
+ Assert.assertFalse(Token.isExpired("x.eyJleHAiOjk5OTk5OTk5OTl9.y"));
+ }
}
diff --git a/src/test/java/com/skyflow/utils/HttpUtilityTests.java b/src/test/java/com/skyflow/utils/HttpUtilityTests.java
index 46be8f6a..0dbdf00b 100644
--- a/src/test/java/com/skyflow/utils/HttpUtilityTests.java
+++ b/src/test/java/com/skyflow/utils/HttpUtilityTests.java
@@ -189,4 +189,40 @@ public void testSendRequestFormURLEncodedWithSpecialCharacters() {
fail(INVALID_EXCEPTION_THROWN);
}
}
+
+ @Test
+ public void testAppendRequestId_withNonNullRequestId() {
+ String result = HttpUtility.appendRequestId("base message", "req-123");
+ Assert.assertEquals("base message - requestId: req-123", result);
+ }
+
+ @Test
+ public void testAppendRequestId_withNullRequestId() {
+ String result = HttpUtility.appendRequestId("base message", null);
+ Assert.assertEquals("base message", result);
+ }
+
+ @Test
+ public void testAppendRequestId_withEmptyRequestId() {
+ String result = HttpUtility.appendRequestId("base message", "");
+ Assert.assertEquals("base message", result);
+ }
+
+ @Test
+ @PrepareForTest({URL.class, HttpURLConnection.class})
+ public void testSendRequestWithNestedJsonBody() {
+ try {
+ given(mockConnection.getRequestProperty("content-type")).willReturn("application/json");
+ Map headers = new HashMap<>();
+ headers.put("content-type", "application/json");
+ JsonObject nested = new JsonObject();
+ nested.addProperty("inner", "value");
+ JsonObject params = new JsonObject();
+ params.add("outer", nested);
+ 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/BinAuditTests.java b/src/test/java/com/skyflow/vault/BinAuditTests.java
new file mode 100644
index 00000000..25da7fc8
--- /dev/null
+++ b/src/test/java/com/skyflow/vault/BinAuditTests.java
@@ -0,0 +1,34 @@
+package com.skyflow.vault;
+
+import com.skyflow.vault.audit.ListEventRequest;
+import com.skyflow.vault.audit.ListEventResponse;
+import com.skyflow.vault.bin.GetBinRequest;
+import com.skyflow.vault.bin.GetBinResponse;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class BinAuditTests {
+ @Test
+ public void testGetBinRequestConstructor() {
+ GetBinRequest req = new GetBinRequest();
+ Assert.assertNotNull(req);
+ }
+
+ @Test
+ public void testGetBinResponseConstructor() {
+ GetBinResponse resp = new GetBinResponse();
+ Assert.assertNotNull(resp);
+ }
+
+ @Test
+ public void testListEventRequestConstructor() {
+ ListEventRequest req = new ListEventRequest();
+ Assert.assertNotNull(req);
+ }
+
+ @Test
+ public void testListEventResponseConstructor() {
+ ListEventResponse resp = new ListEventResponse();
+ Assert.assertNotNull(resp);
+ }
+}
diff --git a/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java b/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java
index b121280a..6c858b45 100644
--- a/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java
+++ b/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java
@@ -4,38 +4,64 @@
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 com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
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.io.IOException;
+import java.net.URL;
import java.util.HashMap;
+import java.util.Map;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.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";
- private static String connectionID = null;
- private static String connectionURL = null;
- private static ConnectionConfig connectionConfig = null;
- private static Skyflow skyflowClient = null;
+ private static final String API_KEY = "sky-ab123-abcd1234cdef1234abcd4321cdef4321";
+ private static final String REQUEST_ID = "req-test-123";
- @BeforeClass
- public static void setup() {
- connectionID = "vault123";
- connectionURL = "https://test.connection.url";
+ private static ConnectionConfig connectionConfig;
+ private static Credentials credentials;
+ private ConnectionController controller;
- Credentials credentials = new Credentials();
- credentials.setToken("valid-token");
+ @BeforeClass
+ public static void setupClass() {
+ credentials = new Credentials();
+ credentials.setApiKey(API_KEY);
connectionConfig = new ConnectionConfig();
- connectionConfig.setConnectionId(connectionID);
- connectionConfig.setConnectionUrl(connectionURL);
+ connectionConfig.setConnectionId("conn123");
+ connectionConfig.setConnectionUrl("https://test.connection.url");
connectionConfig.setCredentials(credentials);
}
+ @Before
+ public void setup() {
+ controller = new ConnectionController(connectionConfig, credentials);
+ PowerMockito.mockStatic(HttpUtility.class);
+ }
+
+ // --- existing validation test (kept) ---
+
@Test
public void testInvalidRequestInInvokeConnectionMethod() {
try {
@@ -49,4 +75,336 @@ public void testInvalidRequestInInvokeConnectionMethod() {
Assert.assertEquals(ErrorMessage.EmptyRequestBody.getMessage(), e.getMessage());
}
}
+
+ // --- happy-path tests ---
+
+ @Test
+ public void testInvoke_successWithDefaultRequest() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("{\"data\":\"test-value\"}");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder().build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ Assert.assertNotNull(response.getData());
+ }
+
+ @Test
+ public void testInvoke_successWithGetMethod() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("{\"result\":\"ok\"}");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .method(RequestMethod.GET)
+ .build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ }
+
+ @Test
+ public void testInvoke_successWithDeleteMethod() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("{\"deleted\":true}");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .method(RequestMethod.DELETE)
+ .build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ }
+
+ @Test
+ public void testInvoke_successWithPutMethod() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("{\"updated\":true}");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ Map body = new HashMap<>();
+ body.put("field", "value");
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .method(RequestMethod.PUT)
+ .requestBody(body)
+ .build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ }
+
+ @Test
+ public void testInvoke_successWithStringBodyAndJsonContentType() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("{\"parsed\":true}");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ Map headers = new HashMap<>();
+ headers.put("content-type", "application/json");
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .method(RequestMethod.POST)
+ .requestHeaders(headers)
+ .requestBody("{\"key\":\"value\"}")
+ .build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ }
+
+ @Test
+ public void testInvoke_successWithStringBodyAndNonJsonContentType() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("ok");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ Map headers = new HashMap<>();
+ headers.put("content-type", "text/plain");
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .method(RequestMethod.POST)
+ .requestHeaders(headers)
+ .requestBody("raw body content")
+ .build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ }
+
+ @Test
+ public void testInvoke_successWithObjectBody() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("{\"result\":\"ok\"}");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ Map body = new HashMap<>();
+ body.put("card_number", "4111111111111111");
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .method(RequestMethod.POST)
+ .requestBody(body)
+ .build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ }
+
+ @Test
+ public void testInvoke_withPathParams() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("{\"data\":\"ok\"}");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ Map pathParams = new HashMap<>();
+ pathParams.put("id", "record-123");
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .method(RequestMethod.GET)
+ .pathParams(pathParams)
+ .build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ }
+
+ @Test
+ public void testInvoke_withQueryParams() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("{\"data\":\"ok\"}");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ Map queryParams = new HashMap<>();
+ queryParams.put("limit", "10");
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .method(RequestMethod.GET)
+ .queryParams(queryParams)
+ .build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ }
+
+ @Test
+ public void testInvoke_withRequestHeaders() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("{\"ok\":true}");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ Map headers = new HashMap<>();
+ headers.put("x-custom-header", "custom-value");
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .method(RequestMethod.GET)
+ .requestHeaders(headers)
+ .build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ }
+
+ @Test
+ public void testInvoke_nonJsonResponseWrappedUnderResponseKey() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("plain-text-response");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder().build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ Assert.assertNotNull(response.getData());
+ JsonObject data = JsonParser.parseString(response.getData().toString()).getAsJsonObject();
+ Assert.assertTrue(data.has("response"));
+ Assert.assertEquals("plain-text-response", data.get("response").getAsString());
+ }
+
+ @Test
+ public void testInvoke_responseContainsRequestId() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("{\"data\":\"ok\"}");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder().build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ Assert.assertNotNull(response.getMetadata());
+ Assert.assertEquals(REQUEST_ID, response.getMetadata().get("requestId"));
+ }
+
+ @Test
+ public void testInvoke_errorsNullOnSuccess() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenReturn("{\"data\":\"ok\"}");
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder().build();
+ InvokeConnectionResponse response = controller.invoke(request);
+
+ Assert.assertNotNull(response);
+ Assert.assertNull(response.getErrors());
+ }
+
+ // --- error / validation-failure tests ---
+
+ @Test
+ public void testInvoke_ioExceptionThrowsSkyflowException() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenThrow(new IOException("connection refused"));
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ try {
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder().build();
+ controller.invoke(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertNotNull(e.getMessage());
+ }
+ }
+
+ @Test
+ public void testInvoke_skyflowExceptionFromSendRequestPropagates() throws Exception {
+ when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()))
+ .thenThrow(new SkyflowException("upstream error", new RuntimeException()));
+ when(HttpUtility.getRequestID()).thenReturn(REQUEST_ID);
+
+ try {
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder().build();
+ controller.invoke(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertNotNull(e.getMessage());
+ }
+ }
+
+ @Test
+ public void testInvoke_emptyRequestHeadersThrowsSkyflowException() {
+ try {
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .requestHeaders(new HashMap<>())
+ .build();
+ controller.invoke(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode());
+ Assert.assertEquals(ErrorMessage.EmptyRequestHeaders.getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ public void testInvoke_emptyPathParamsThrowsSkyflowException() {
+ try {
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .pathParams(new HashMap<>())
+ .build();
+ controller.invoke(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode());
+ Assert.assertEquals(ErrorMessage.EmptyPathParams.getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ public void testInvoke_emptyQueryParamsThrowsSkyflowException() {
+ try {
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .queryParams(new HashMap<>())
+ .build();
+ controller.invoke(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode());
+ Assert.assertEquals(ErrorMessage.EmptyQueryParams.getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ public void testInvoke_emptyStringBodyThrowsSkyflowException() {
+ try {
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .requestBody(" ")
+ .build();
+ controller.invoke(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 testInvoke_emptyHashMapBodyThrowsSkyflowException() {
+ try {
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .requestBody(new HashMap<>())
+ .build();
+ controller.invoke(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 testInvoke_nullHeaderValueThrowsSkyflowException() {
+ try {
+ Map headers = new HashMap<>();
+ headers.put("x-header", null);
+ InvokeConnectionRequest request = InvokeConnectionRequest.builder()
+ .requestHeaders(headers)
+ .build();
+ controller.invoke(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode());
+ Assert.assertEquals(ErrorMessage.InvalidRequestHeaders.getMessage(), e.getMessage());
+ }
+ }
}
diff --git a/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java b/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java
index aae713b1..52bb2be4 100644
--- a/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java
+++ b/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java
@@ -1,30 +1,66 @@
package com.skyflow.vault.controller;
import com.skyflow.Skyflow;
+import com.skyflow.VaultClient;
import com.skyflow.config.Credentials;
import com.skyflow.config.VaultConfig;
+import com.skyflow.enums.DetectEntities;
import com.skyflow.enums.Env;
import com.skyflow.enums.LogLevel;
import com.skyflow.errors.ErrorMessage;
import com.skyflow.errors.HttpStatus;
import com.skyflow.errors.SkyflowException;
+import com.skyflow.generated.rest.ApiClient;
+import com.skyflow.generated.rest.core.ApiClientApiException;
+import com.skyflow.generated.rest.resources.files.FilesClient;
+import com.skyflow.generated.rest.resources.files.requests.GetRunRequest;
+import com.skyflow.generated.rest.resources.strings.StringsClient;
+import com.skyflow.generated.rest.types.DeidentifiedFileOutput;
+import com.skyflow.generated.rest.types.DeidentifiedFileOutputProcessedFileExtension;
+import com.skyflow.generated.rest.types.DeidentifyStringResponse;
+import com.skyflow.generated.rest.types.DetectRunsResponse;
+import com.skyflow.generated.rest.types.DetectRunsResponseOutputType;
+import com.skyflow.generated.rest.types.DetectRunsResponseStatus;
+import com.skyflow.generated.rest.types.IdentifyResponse;
+import com.skyflow.generated.rest.types.WordCharacterCount;
import com.skyflow.utils.Constants;
import com.skyflow.utils.Utils;
import com.skyflow.vault.detect.DeidentifyTextRequest;
+import com.skyflow.vault.detect.DeidentifyTextResponse;
+import com.skyflow.vault.detect.DeidentifyFileResponse;
+import com.skyflow.vault.detect.GetDetectRunRequest;
import com.skyflow.vault.detect.ReidentifyTextRequest;
+import com.skyflow.vault.detect.ReidentifyTextResponse;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.util.Base64;
+import java.util.Collections;
+
+import com.skyflow.generated.rest.core.RequestOptions;
+import com.skyflow.vault.detect.DeidentifyFileRequest;
+import com.skyflow.vault.detect.FileInput;
+import com.skyflow.vault.detect.TokenFormat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
public class DetectControllerTests {
private static final String EXCEPTION_NOT_THROWN = "Should have thrown an exception";
+ private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception";
private static String vaultID = null;
private static String clusterID = null;
private static VaultConfig vaultConfig = null;
private static Skyflow skyflowClient = null;
@BeforeClass
- public static void setup() throws SkyflowException, NoSuchMethodException {
+ public static void setup() throws SkyflowException {
vaultID = "vault123";
clusterID = "cluster123";
@@ -37,13 +73,31 @@ public static void setup() throws SkyflowException, NoSuchMethodException {
vaultConfig.setEnv(Env.DEV);
vaultConfig.setCredentials(credentials);
-
skyflowClient = Skyflow.builder()
.setLogLevel(LogLevel.DEBUG)
.addVaultConfig(vaultConfig)
.build();
}
+ // ─── helper: build a DetectController with a mocked ApiClient ─────────────
+
+ private static DetectController createDetectControllerWithMock(ApiClient mockApiClient) throws Exception {
+ Credentials creds = new Credentials();
+ creds.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321");
+
+ VaultConfig config = new VaultConfig();
+ config.setVaultId(vaultID);
+ config.setClusterId(clusterID);
+ config.setEnv(Env.DEV);
+
+ DetectController controller = new DetectController(config, creds);
+ Field f = VaultClient.class.getDeclaredField("apiClient");
+ f.setAccessible(true);
+ f.set(controller, mockApiClient);
+ return controller;
+ }
+
+ // ─── deidentifyText — validation ──────────────────────────────────────────
@Test
public void testNullTextInRequestInDeidentifyStringMethod() {
@@ -81,6 +135,82 @@ public void testEmptyTextInRequestInDeidentifyStringMethod() {
}
}
+ // ─── deidentifyText — happy path ──────────────────────────────────────────
+
+ @Test
+ public void testDeidentifyTextHappyPath() throws Exception {
+ StringsClient mockStringsClient = Mockito.mock(StringsClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.strings()).thenReturn(mockStringsClient);
+
+ DeidentifyStringResponse fakeResponse = DeidentifyStringResponse.builder()
+ .processedText("hello [REDACTED]")
+ .wordCount(2)
+ .characterCount(16)
+ .build();
+
+ when(mockStringsClient.deidentifyString(any(), any())).thenReturn(fakeResponse);
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ DeidentifyTextRequest request = DeidentifyTextRequest.builder().text("hello world").build();
+
+ try {
+ DeidentifyTextResponse response = controller.deidentifyText(request);
+ Assert.assertNotNull(response);
+ Assert.assertEquals("hello [REDACTED]", response.getProcessedText());
+ Assert.assertEquals(2, response.getWordCount());
+ Assert.assertEquals(16, response.getCharCount());
+ } catch (SkyflowException e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN + ": " + e.getMessage());
+ }
+ }
+
+ // ─── deidentifyText — API error path ──────────────────────────────────────
+
+ @Test
+ public void testDeidentifyTextApiError() throws Exception {
+ StringsClient mockStringsClient = Mockito.mock(StringsClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.strings()).thenReturn(mockStringsClient);
+
+ int expectedStatusCode = 403;
+ when(mockStringsClient.deidentifyString(any(), any()))
+ .thenThrow(new ApiClientApiException("Forbidden", expectedStatusCode, "access denied"));
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ DeidentifyTextRequest request = DeidentifyTextRequest.builder().text("hello world").build();
+
+ try {
+ controller.deidentifyText(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(expectedStatusCode, e.getHttpCode());
+ }
+ }
+
+ @Test
+ public void testDeidentifyTextApiError500() throws Exception {
+ StringsClient mockStringsClient = Mockito.mock(StringsClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.strings()).thenReturn(mockStringsClient);
+
+ int expectedStatusCode = 500;
+ when(mockStringsClient.deidentifyString(any(), any()))
+ .thenThrow(new ApiClientApiException("Internal Server Error", expectedStatusCode, "server error body"));
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ DeidentifyTextRequest request = DeidentifyTextRequest.builder().text("some text").build();
+
+ try {
+ controller.deidentifyText(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(expectedStatusCode, e.getHttpCode());
+ }
+ }
+
+ // ─── reidentifyText — validation ──────────────────────────────────────────
+
@Test
public void testNullTextInRequestInReidentifyStringMethod() {
try {
@@ -117,5 +247,701 @@ public void testEmptyTextInRequestInReidentifyStringMethod() {
}
}
-}
+ // ─── reidentifyText — happy path ──────────────────────────────────────────
+
+ @Test
+ public void testReidentifyTextHappyPath() throws Exception {
+ StringsClient mockStringsClient = Mockito.mock(StringsClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.strings()).thenReturn(mockStringsClient);
+
+ IdentifyResponse fakeResponse = IdentifyResponse.builder().text("original text").build();
+ when(mockStringsClient.reidentifyString(any(), any())).thenReturn(fakeResponse);
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ ReidentifyTextRequest request = ReidentifyTextRequest.builder().text("tokenized text").build();
+
+ try {
+ ReidentifyTextResponse response = controller.reidentifyText(request);
+ Assert.assertNotNull(response);
+ Assert.assertEquals("original text", response.getProcessedText());
+ } catch (SkyflowException e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN + ": " + e.getMessage());
+ }
+ }
+
+ // ─── reidentifyText — API error path ──────────────────────────────────────
+
+ @Test
+ public void testReidentifyTextApiError() throws Exception {
+ StringsClient mockStringsClient = Mockito.mock(StringsClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.strings()).thenReturn(mockStringsClient);
+
+ int expectedStatusCode = 401;
+ when(mockStringsClient.reidentifyString(any(), any()))
+ .thenThrow(new ApiClientApiException("Unauthorized", expectedStatusCode, "unauthorized body"));
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ ReidentifyTextRequest request = ReidentifyTextRequest.builder().text("some text").build();
+
+ try {
+ controller.reidentifyText(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(expectedStatusCode, e.getHttpCode());
+ }
+ }
+
+ @Test
+ public void testReidentifyTextApiError500() throws Exception {
+ StringsClient mockStringsClient = Mockito.mock(StringsClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.strings()).thenReturn(mockStringsClient);
+
+ int expectedStatusCode = 500;
+ when(mockStringsClient.reidentifyString(any(), any()))
+ .thenThrow(new ApiClientApiException("Internal Server Error", expectedStatusCode, "server error body"));
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ ReidentifyTextRequest request = ReidentifyTextRequest.builder().text("some text").build();
+
+ try {
+ controller.reidentifyText(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(expectedStatusCode, e.getHttpCode());
+ }
+ }
+
+ // ─── getDetectRun — validation ────────────────────────────────────────────
+
+ @Test
+ public void testNullRunIdInGetDetectRunRequest() {
+ try {
+ GetDetectRunRequest request = GetDetectRunRequest.builder().runId(null).build();
+ skyflowClient = Skyflow.builder().setLogLevel(LogLevel.DEBUG).addVaultConfig(vaultConfig).build();
+ skyflowClient.detect(vaultID).getDetectRun(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(HttpStatus.BAD_REQUEST.getHttpStatus(), e.getHttpStatus());
+ }
+ }
+
+ @Test
+ public void testEmptyRunIdInGetDetectRunRequest() {
+ try {
+ GetDetectRunRequest request = GetDetectRunRequest.builder().runId("").build();
+ skyflowClient = Skyflow.builder().setLogLevel(LogLevel.DEBUG).addVaultConfig(vaultConfig).build();
+ skyflowClient.detect(vaultID).getDetectRun(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(HttpStatus.BAD_REQUEST.getHttpStatus(), e.getHttpStatus());
+ }
+ }
+
+ // ─── getDetectRun — happy path (no output list) ───────────────────────────
+
+ @Test
+ public void testGetDetectRunHappyPathNoOutput() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+
+ DetectRunsResponse fakeRunsResponse = DetectRunsResponse.builder()
+ .status(DetectRunsResponseStatus.SUCCESS)
+ .outputType(DetectRunsResponseOutputType.BASE_64)
+ .size(10.5f)
+ .duration(1.2f)
+ .build();
+
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class)))
+ .thenReturn(fakeRunsResponse);
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ GetDetectRunRequest request = GetDetectRunRequest.builder().runId("run-123").build();
+
+ try {
+ DeidentifyFileResponse response = controller.getDetectRun(request);
+ Assert.assertNotNull(response);
+ Assert.assertEquals("run-123", response.getRunId());
+ } catch (SkyflowException e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN + ": " + e.getMessage());
+ }
+ }
+
+ // ─── getDetectRun — API error path ────────────────────────────────────────
+
+ @Test
+ public void testGetDetectRunApiError() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+
+ int expectedStatusCode = 404;
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class)))
+ .thenThrow(new ApiClientApiException("Not Found", expectedStatusCode, "run not found"));
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ GetDetectRunRequest request = GetDetectRunRequest.builder().runId("run-999").build();
+
+ try {
+ controller.getDetectRun(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(expectedStatusCode, e.getHttpCode());
+ }
+ }
+
+ // ─── helpers ─────────────────────────────────────────────────────────────
+
+ private static DetectRunsResponse buildSuccessDetectRunsResponse() {
+ return DetectRunsResponse.builder()
+ .status(DetectRunsResponseStatus.SUCCESS)
+ .outputType(DetectRunsResponseOutputType.BASE_64)
+ .size(1.0f)
+ .duration(0.5f)
+ .build();
+ }
+
+ private DeidentifyFileResponse runDeidentifyFileForExtension(
+ String extension, FilesClient mockFilesClient) throws Exception {
+ return runDeidentifyFileForExtension(extension, mockFilesClient, null);
+ }
+
+ private DeidentifyFileResponse runDeidentifyFileForExtension(
+ String extension, FilesClient mockFilesClient, TokenFormat tokenFormat) throws Exception {
+ File tmpFile = File.createTempFile("test-detect", "." + extension);
+ tmpFile.deleteOnExit();
+ Files.write(tmpFile.toPath(), ("content for " + extension).getBytes());
+
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class), any(RequestOptions.class)))
+ .thenReturn(buildSuccessDetectRunsResponse());
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ DeidentifyFileRequest.DeidentifyFileRequestBuilder builder = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().file(tmpFile).build());
+ if (tokenFormat != null) {
+ builder.tokenFormat(tokenFormat);
+ }
+ return controller.deidentifyFile(builder.build());
+ }
+
+ @Test
+ public void testGetDetectRunApiError500() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+
+ int expectedStatusCode = 500;
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class)))
+ .thenThrow(new ApiClientApiException("Internal Server Error", expectedStatusCode, "internal error body"));
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ GetDetectRunRequest request = GetDetectRunRequest.builder().runId("run-abc").build();
+
+ try {
+ controller.getDetectRun(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(expectedStatusCode, e.getHttpCode());
+ }
+ }
+
+ // ─── deidentifyFile — validation ──────────────────────────────────────────
+
+ @Test
+ public void testDeidentifyFile_nullRequest() {
+ try {
+ skyflowClient = Skyflow.builder().setLogLevel(LogLevel.DEBUG).addVaultConfig(vaultConfig).build();
+ skyflowClient.detect(vaultID).deidentifyFile(null);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(ErrorMessage.EmptyRequestBody.getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ public void testDeidentifyFile_noFileOrPathProvided() {
+ try {
+ skyflowClient = Skyflow.builder().setLogLevel(LogLevel.DEBUG).addVaultConfig(vaultConfig).build();
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().build())
+ .build();
+ skyflowClient.detect(vaultID).deidentifyFile(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(ErrorMessage.EmptyFileAndFilePathInDeIdentifyFile.getMessage(), e.getMessage());
+ }
+ }
+
+ // ─── deidentifyFile — happy path ──────────────────────────────────────────
+
+ @Test
+ public void testDeidentifyFile_successWithTxtFileObject() throws Exception {
+ File tmpFile = File.createTempFile("test-detect", ".txt");
+ tmpFile.deleteOnExit();
+ Files.write(tmpFile.toPath(), "hello world".getBytes());
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+ when(mockFilesClient.deidentifyText(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-txt-001").build());
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class), any(RequestOptions.class)))
+ .thenReturn(buildSuccessDetectRunsResponse());
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().file(tmpFile).build()).build();
+
+ DeidentifyFileResponse response = controller.deidentifyFile(request);
+ Assert.assertNotNull(response);
+ }
+
+ @Test
+ public void testDeidentifyFile_successWithFilePath() throws Exception {
+ File tmpFile = File.createTempFile("test-detect-path", ".txt");
+ tmpFile.deleteOnExit();
+ Files.write(tmpFile.toPath(), "content".getBytes());
+
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+ when(mockFilesClient.deidentifyText(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-path-001").build());
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class), any(RequestOptions.class)))
+ .thenReturn(buildSuccessDetectRunsResponse());
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().filePath(tmpFile.getAbsolutePath()).build()).build();
+
+ DeidentifyFileResponse response = controller.deidentifyFile(request);
+ Assert.assertNotNull(response);
+ }
+
+ @Test
+ public void testDeidentifyFile_successWithOutputFile() throws Exception {
+ File tmpFile = File.createTempFile("test-detect-out", ".txt");
+ tmpFile.deleteOnExit();
+ Files.write(tmpFile.toPath(), "content".getBytes());
+
+ String b64 = Base64.getEncoder().encodeToString("processed content".getBytes());
+ DeidentifiedFileOutput outputItem = DeidentifiedFileOutput.builder()
+ .processedFile(b64)
+ .processedFileExtension(DeidentifiedFileOutputProcessedFileExtension.TXT)
+ .build();
+ DetectRunsResponse successWithOutput = DetectRunsResponse.builder()
+ .status(DetectRunsResponseStatus.SUCCESS)
+ .outputType(DetectRunsResponseOutputType.BASE_64)
+ .size(1.0f).duration(0.5f)
+ .output(Collections.singletonList(outputItem))
+ .build();
+
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+ when(mockFilesClient.deidentifyText(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-out-001").build());
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class), any(RequestOptions.class)))
+ .thenReturn(successWithOutput);
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().file(tmpFile).build())
+ .outputDirectory(System.getProperty("java.io.tmpdir"))
+ .build();
+
+ DeidentifyFileResponse response = controller.deidentifyFile(request);
+ Assert.assertNotNull(response);
+ }
+
+ // ─── deidentifyFile — IN_PROGRESS timeout ─────────────────────────────────
+
+ @Test
+ public void testDeidentifyFile_inProgressTimeout() throws Exception {
+ File tmpFile = File.createTempFile("test-detect-prog", ".txt");
+ tmpFile.deleteOnExit();
+ Files.write(tmpFile.toPath(), "content".getBytes());
+
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+ when(mockFilesClient.deidentifyText(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-prog-001").build());
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class), any(RequestOptions.class)))
+ .thenReturn(DetectRunsResponse.builder().status(DetectRunsResponseStatus.IN_PROGRESS).build());
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().file(tmpFile).build())
+ .waitTime(1)
+ .build();
+
+ DeidentifyFileResponse response = controller.deidentifyFile(request);
+ Assert.assertNotNull(response);
+ Assert.assertEquals("IN_PROGRESS", response.getStatus());
+ }
+
+ // ─── deidentifyFile — error paths ─────────────────────────────────────────
+
+ @Test
+ public void testDeidentifyFile_nonExistentFilePath() {
+ try {
+ DetectController controller = createDetectControllerWithMock(Mockito.mock(ApiClient.class));
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().filePath("/nonexistent/path/file.txt").build())
+ .build();
+ controller.deidentifyFile(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (Exception e) {
+ Assert.assertNotNull(e.getMessage());
+ }
+ }
+
+ @Test
+ public void testDeidentifyFile_processFileApiError() throws Exception {
+ File tmpFile = File.createTempFile("test-detect-err", ".txt");
+ tmpFile.deleteOnExit();
+ Files.write(tmpFile.toPath(), "content".getBytes());
+
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+ when(mockFilesClient.deidentifyText(any()))
+ .thenThrow(new ApiClientApiException("forbidden", 403, "access denied"));
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().file(tmpFile).build()).build();
+
+ try {
+ controller.deidentifyFile(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(403, e.getHttpCode());
+ }
+ }
+
+ @Test
+ public void testDeidentifyFile_pollForResultsApiError() throws Exception {
+ File tmpFile = File.createTempFile("test-detect-poll", ".txt");
+ tmpFile.deleteOnExit();
+ Files.write(tmpFile.toPath(), "content".getBytes());
+
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+ when(mockFilesClient.deidentifyText(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-poll-err").build());
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class), any(RequestOptions.class)))
+ .thenThrow(new ApiClientApiException("unavailable", 503, "service unavailable"));
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().file(tmpFile).build()).build();
+
+ try {
+ controller.deidentifyFile(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(ErrorMessage.PollingForResultsFailed.getMessage(), e.getMessage());
+ }
+ }
+
+ // ─── processFileByType — all extensions ───────────────────────────────────
+
+ @Test
+ public void testDeidentifyFile_pdfExtension() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyPdf(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-pdf").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("pdf", mockFilesClient));
+ }
+
+ @Test
+ public void testDeidentifyFile_mp3Extension() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyAudio(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-mp3").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("mp3", mockFilesClient));
+ }
+
+ @Test
+ public void testDeidentifyFile_jpgExtension() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyImage(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-jpg").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("jpg", mockFilesClient));
+ }
+
+ @Test
+ public void testDeidentifyFile_pptExtension() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyPresentation(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-ppt").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("ppt", mockFilesClient));
+ }
+
+ @Test
+ public void testDeidentifyFile_csvExtension() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifySpreadsheet(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-csv").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("csv", mockFilesClient));
+ }
+
+ @Test
+ public void testDeidentifyFile_docExtension() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyDocument(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-doc").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("doc", mockFilesClient));
+ }
+
+ @Test
+ public void testDeidentifyFile_jsonExtension() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyStructuredText(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-json").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("json", mockFilesClient));
+ }
+
+ @Test
+ public void testDeidentifyFile_defaultExtension() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyFile(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-dcm").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("dcm", mockFilesClient));
+ }
+
+ // ─── parseDeidentifyFileResponse — wordCharacterCount branch L272-273 ─────
+
+ @Test
+ public void testGetDetectRun_withWordCharacterCount() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+
+ DeidentifiedFileOutput outputItem = DeidentifiedFileOutput.builder().build();
+ WordCharacterCount wordCharCount = WordCharacterCount.builder()
+ .wordCount(10)
+ .characterCount(55)
+ .build();
+
+ DetectRunsResponse fakeRunsResponse = DetectRunsResponse.builder()
+ .status(DetectRunsResponseStatus.SUCCESS)
+ .outputType(DetectRunsResponseOutputType.BASE_64)
+ .size(5.0f)
+ .duration(0.5f)
+ .output(Collections.singletonList(outputItem))
+ .wordCharacterCount(wordCharCount)
+ .build();
+
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class)))
+ .thenReturn(fakeRunsResponse);
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ GetDetectRunRequest request = GetDetectRunRequest.builder().runId("run-wc-001").build();
+
+ try {
+ DeidentifyFileResponse response = controller.getDetectRun(request);
+ Assert.assertNotNull(response);
+ Assert.assertEquals(10, (int) response.getWordCount());
+ Assert.assertEquals(55, (int) response.getCharCount());
+ } catch (SkyflowException e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN + ": " + e.getMessage());
+ }
+ }
+
+ // ─── deidentifyFile — processedFile present, no outputDirectory (lines 146, 168) ───
+
+ @Test
+ public void testDeidentifyFile_successWithProcessedFileNoOutputDir() throws Exception {
+ File tmpFile = File.createTempFile("test-detect-nodir", ".txt");
+ tmpFile.deleteOnExit();
+ Files.write(tmpFile.toPath(), "content".getBytes());
+
+ String b64 = Base64.getEncoder().encodeToString("processed content".getBytes());
+ DeidentifiedFileOutput outputItem = DeidentifiedFileOutput.builder()
+ .processedFile(b64)
+ .processedFileExtension(DeidentifiedFileOutputProcessedFileExtension.TXT)
+ .build();
+ DetectRunsResponse successWithOutput = DetectRunsResponse.builder()
+ .status(DetectRunsResponseStatus.SUCCESS)
+ .outputType(DetectRunsResponseOutputType.BASE_64)
+ .size(1.0f).duration(0.5f)
+ .output(Collections.singletonList(outputItem))
+ .build();
+
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+ when(mockFilesClient.deidentifyText(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-nodir-001").build());
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class), any(RequestOptions.class)))
+ .thenReturn(successWithOutput);
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ // no outputDirectory → file written via new File(outputFileName) (line 146)
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().file(tmpFile).build())
+ .build();
+
+ DeidentifyFileResponse response = controller.deidentifyFile(request);
+ Assert.assertNotNull(response);
+ }
+
+ // ─── pollForResults — IN_PROGRESS retry then SUCCESS (lines 218-229) ─────────
+
+ @Test
+ public void testDeidentifyFile_inProgressThenSuccess() throws Exception {
+ File tmpFile = File.createTempFile("test-detect-retry", ".txt");
+ tmpFile.deleteOnExit();
+ Files.write(tmpFile.toPath(), "content".getBytes());
+
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+ when(mockFilesClient.deidentifyText(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-retry-001").build());
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class), any(RequestOptions.class)))
+ .thenReturn(DetectRunsResponse.builder().status(DetectRunsResponseStatus.IN_PROGRESS).build())
+ .thenReturn(buildSuccessDetectRunsResponse());
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ // waitTime=2: first poll IN_PROGRESS → sleeps 1s → second poll SUCCESS
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().file(tmpFile).build())
+ .waitTime(2)
+ .build();
+
+ DeidentifyFileResponse response = controller.deidentifyFile(request);
+ Assert.assertNotNull(response);
+ Assert.assertFalse("IN_PROGRESS".equals(response.getStatus()));
+ }
+
+ // ─── pollForResults — IN_PROGRESS retry (else branch, lines 225-226) ────────
+
+ @Test
+ public void testDeidentifyFile_inProgressElseBranchThenSuccess() throws Exception {
+ File tmpFile = File.createTempFile("test-detect-else", ".txt");
+ tmpFile.deleteOnExit();
+ Files.write(tmpFile.toPath(), "content".getBytes());
+
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+ when(mockFilesClient.deidentifyText(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-else-001").build());
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class), any(RequestOptions.class)))
+ .thenReturn(DetectRunsResponse.builder().status(DetectRunsResponseStatus.IN_PROGRESS).build())
+ .thenReturn(buildSuccessDetectRunsResponse());
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ // waitTime=3: currentWaitTime=1, nextWaitTime=2 < 3 → else branch (L225-226), sleep 2s
+ DeidentifyFileRequest request = DeidentifyFileRequest.builder()
+ .file(FileInput.builder().file(tmpFile).build())
+ .waitTime(3)
+ .build();
+
+ DeidentifyFileResponse response = controller.deidentifyFile(request);
+ Assert.assertNotNull(response);
+ }
+
+ // ─── parseDeidentifyFileResponse — processedFile present branch L283-291 ──
+
+ @Test
+ public void testGetDetectRun_withProcessedFile() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ ApiClient mockApiClient = Mockito.mock(ApiClient.class);
+ when(mockApiClient.files()).thenReturn(mockFilesClient);
+
+ String base64Content = Base64.getEncoder().encodeToString("test file content".getBytes());
+ DeidentifiedFileOutput outputItem = DeidentifiedFileOutput.builder()
+ .processedFile(base64Content)
+ .processedFileExtension(DeidentifiedFileOutputProcessedFileExtension.TXT)
+ .build();
+
+ DetectRunsResponse fakeRunsResponse = DetectRunsResponse.builder()
+ .status(DetectRunsResponseStatus.SUCCESS)
+ .outputType(DetectRunsResponseOutputType.BASE_64)
+ .size(1.0f)
+ .duration(0.1f)
+ .output(Collections.singletonList(outputItem))
+ .build();
+
+ when(mockFilesClient.getRun(anyString(), any(GetRunRequest.class)))
+ .thenReturn(fakeRunsResponse);
+
+ DetectController controller = createDetectControllerWithMock(mockApiClient);
+ GetDetectRunRequest request = GetDetectRunRequest.builder().runId("run-file-001").build();
+
+ try {
+ DeidentifyFileResponse response = controller.getDetectRun(request);
+ Assert.assertNotNull(response);
+ Assert.assertEquals("run-file-001", response.getRunId());
+ } catch (SkyflowException e) {
+ Assert.fail(INVALID_EXCEPTION_THROWN + ": " + e.getMessage());
+ }
+ }
+
+ // ─── entityUniqueCounter branches in VaultClient request builders ─────────
+
+ private static TokenFormat buildEntityUniqueCounterTokenFormat() {
+ return TokenFormat.builder()
+ .entityUniqueCounter(java.util.Collections.singletonList(DetectEntities.EMAIL_ADDRESS))
+ .build();
+ }
+
+ @Test
+ public void testDeidentifyFile_txt_withEntityUniqueCounter() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyText(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-euc-txt").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("txt", mockFilesClient, buildEntityUniqueCounterTokenFormat()));
+ }
+
+ @Test
+ public void testDeidentifyFile_mp3_withEntityUniqueCounter() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyAudio(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-euc-mp3").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("mp3", mockFilesClient, buildEntityUniqueCounterTokenFormat()));
+ }
+
+ @Test
+ public void testDeidentifyFile_pdf_withEntityUniqueCounter() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyPdf(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-euc-pdf").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("pdf", mockFilesClient, buildEntityUniqueCounterTokenFormat()));
+ }
+
+ @Test
+ public void testDeidentifyFile_jpg_withEntityUniqueCounter() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyImage(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-euc-jpg").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("jpg", mockFilesClient, buildEntityUniqueCounterTokenFormat()));
+ }
+
+ @Test
+ public void testDeidentifyFile_csv_withEntityUniqueCounter() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifySpreadsheet(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-euc-csv").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("csv", mockFilesClient, buildEntityUniqueCounterTokenFormat()));
+ }
+
+ @Test
+ public void testDeidentifyFile_dcm_withEntityUniqueCounter() throws Exception {
+ FilesClient mockFilesClient = Mockito.mock(FilesClient.class);
+ when(mockFilesClient.deidentifyFile(any())).thenReturn(
+ com.skyflow.generated.rest.types.DeidentifyFileResponse.builder().runId("run-euc-dcm").build());
+ Assert.assertNotNull(runDeidentifyFileForExtension("dcm", mockFilesClient, buildEntityUniqueCounterTokenFormat()));
+ }
+}
diff --git a/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java b/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java
index 77a8ed95..4c1ecb2b 100644
--- a/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java
+++ b/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java
@@ -1,29 +1,79 @@
package com.skyflow.vault.controller;
import com.skyflow.Skyflow;
+import com.skyflow.VaultClient;
import com.skyflow.config.Credentials;
import com.skyflow.config.VaultConfig;
import com.skyflow.enums.Env;
import com.skyflow.enums.LogLevel;
+import com.skyflow.enums.RedactionType;
import com.skyflow.errors.ErrorCode;
import com.skyflow.errors.ErrorMessage;
import com.skyflow.errors.HttpStatus;
import com.skyflow.errors.SkyflowException;
import com.skyflow.generated.rest.ApiClient;
+import com.skyflow.generated.rest.core.ApiClientApiException;
+import com.skyflow.generated.rest.core.ApiClientHttpResponse;
+import com.skyflow.generated.rest.resources.query.QueryClient;
+import com.skyflow.generated.rest.resources.records.RawRecordsClient;
+import com.skyflow.generated.rest.resources.records.RecordsClient;
+import com.skyflow.generated.rest.resources.tokens.RawTokensClient;
+import com.skyflow.generated.rest.resources.tokens.TokensClient;
+import com.skyflow.generated.rest.types.UploadFileV2Response;
+import com.skyflow.generated.rest.types.V1BatchOperationResponse;
+import com.skyflow.generated.rest.types.V1BulkDeleteRecordResponse;
+import com.skyflow.generated.rest.types.V1BulkGetRecordResponse;
+import com.skyflow.generated.rest.types.V1DetokenizeRecordResponse;
+import com.skyflow.generated.rest.types.V1DetokenizeResponse;
import com.skyflow.generated.rest.types.V1FieldRecords;
+import com.skyflow.generated.rest.types.V1GetQueryResponse;
+import com.skyflow.generated.rest.types.V1InsertRecordResponse;
+import com.skyflow.generated.rest.types.V1RecordMetaProperties;
+import com.skyflow.generated.rest.types.V1TokenizeRecordResponse;
+import com.skyflow.generated.rest.types.V1TokenizeResponse;
+import com.skyflow.generated.rest.types.V1UpdateRecordResponse;
import com.skyflow.utils.Constants;
import com.skyflow.utils.Utils;
-import com.skyflow.vault.data.*;
+import com.skyflow.vault.data.DeleteRequest;
+import com.skyflow.vault.data.DeleteResponse;
+import com.skyflow.vault.data.FileUploadRequest;
+import com.skyflow.vault.data.FileUploadResponse;
+import com.skyflow.vault.data.GetRequest;
+import com.skyflow.vault.data.GetResponse;
+import com.skyflow.vault.data.InsertRequest;
+import com.skyflow.vault.data.InsertResponse;
+import com.skyflow.vault.data.QueryRequest;
+import com.skyflow.vault.data.QueryResponse;
+import com.skyflow.vault.data.UpdateRequest;
+import com.skyflow.vault.data.UpdateResponse;
+import com.skyflow.vault.tokens.ColumnValue;
+import com.skyflow.vault.tokens.DetokenizeData;
import com.skyflow.vault.tokens.DetokenizeRequest;
+import com.skyflow.vault.tokens.DetokenizeResponse;
import com.skyflow.vault.tokens.TokenizeRequest;
+import com.skyflow.vault.tokens.TokenizeResponse;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.Response;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.mockito.Mockito;
+import java.io.File;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
public class VaultControllerTests {
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";
@@ -31,7 +81,6 @@ public class VaultControllerTests {
private static String clusterID = null;
private static VaultConfig vaultConfig = null;
private static Skyflow skyflowClient = null;
- private ApiClient mockApiClient;
@BeforeClass
public static void setup() throws SkyflowException, NoSuchMethodException {
@@ -47,14 +96,42 @@ public static void setup() throws SkyflowException, NoSuchMethodException {
vaultConfig.setEnv(Env.DEV);
vaultConfig.setCredentials(credentials);
-
skyflowClient = Skyflow.builder()
.setLogLevel(LogLevel.DEBUG)
.addVaultConfig(vaultConfig)
.build();
+ }
+
+ // --- helpers ---
+
+ private static VaultController createControllerWithMock(ApiClient mockApiClient) throws Exception {
+ Credentials creds = new Credentials();
+ creds.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321");
+
+ VaultConfig config = new VaultConfig();
+ config.setVaultId(vaultID);
+ config.setClusterId(clusterID);
+ config.setEnv(Env.DEV);
+ VaultController controller = new VaultController(config, creds);
+ Field f = VaultClient.class.getDeclaredField("apiClient");
+ f.setAccessible(true);
+ f.set(controller, mockApiClient);
+ return controller;
}
+ private static Response buildOkHttpResponse() {
+ return new Response.Builder()
+ .request(new Request.Builder().url("https://dummy.example.com").build())
+ .protocol(Protocol.HTTP_1_1)
+ .code(200)
+ .message("OK")
+ .header(Constants.REQUEST_ID_HEADER_KEY, "req-test-123")
+ .build();
+ }
+
+ // --- validation failure tests (existing) ---
+
@Test
public void testInvalidRequestInInsertMethod() {
try {
@@ -190,6 +267,8 @@ public void testInvalidRequestInFileUploadMethod() {
}
}
+ // --- getFormattedGetRecord / getFormattedQueryRecord tests ---
+
@Test
public void testGetFormattedGetRecordNormalisesSkyflowId() throws Exception {
Map fields = new HashMap<>();
@@ -241,6 +320,8 @@ public void testGetFormattedGetRecordNormalisesSkyflowIdInTokensBranch() throws
Assert.assertEquals("other token fields should be preserved", "tok-card-abc", result.get("card_number"));
}
+ // --- downloadUrl tests ---
+
@Test
public void testGetRequestDownloadUrlNewForm() {
GetRequest request = GetRequest.builder()
@@ -291,7 +372,7 @@ public void testDetokenizeRequestDownloadUrlDefaultIsFalse() {
Assert.assertFalse("downloadUrl should be false by default", request.getDownloadUrl());
}
- // extractUpdateSkyflowId — all cases
+ // --- extractUpdateSkyflowId tests ---
@Test
public void testExtractUpdateSkyflowId_onlyCamelCase() throws Exception {
@@ -353,4 +434,918 @@ public void testExtractUpdateSkyflowId_bothKeys_removesBothFromMap() throws Exce
Assert.assertTrue("other fields should be preserved", data.containsKey("card_number"));
}
+ // --- insert (bulk) ---
+
+ @Test
+ public void testInsert_bulkSuccess() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ RecordsClient mockRecords = Mockito.mock(RecordsClient.class);
+ when(mockApi.records()).thenReturn(mockRecords);
+
+ V1RecordMetaProperties meta = V1RecordMetaProperties.builder().skyflowId("id-123").build();
+ V1InsertRecordResponse insertResp = V1InsertRecordResponse.builder()
+ .records(Collections.singletonList(meta))
+ .build();
+ when(mockRecords.recordServiceInsertRecord(anyString(), anyString(), any())).thenReturn(insertResp);
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ ArrayList> values = new ArrayList<>();
+ HashMap row = new HashMap<>();
+ row.put("card_number", "4111111111111111");
+ values.add(row);
+ InsertRequest request = InsertRequest.builder().table("test_table").values(values).build();
+
+ InsertResponse response = controller.insert(request);
+
+ Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response);
+ Assert.assertNotNull("insertedFields should not be null", response.getInsertedFields());
+ Assert.assertEquals(1, response.getInsertedFields().size());
+ Assert.assertEquals("id-123", response.getInsertedFields().get(0).get("skyflowId"));
+ }
+
+ @Test
+ public void testInsert_bulkApiErrorThrowsSkyflowException() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ RecordsClient mockRecords = Mockito.mock(RecordsClient.class);
+ when(mockApi.records()).thenReturn(mockRecords);
+ when(mockRecords.recordServiceInsertRecord(anyString(), anyString(), any()))
+ .thenThrow(new ApiClientApiException("insert failed", 400, "bad request body"));
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ ArrayList> values = new ArrayList<>();
+ HashMap row = new HashMap<>();
+ row.put("card_number", "4111111111111111");
+ values.add(row);
+ InsertRequest request = InsertRequest.builder().table("test_table").values(values).build();
+
+ try {
+ controller.insert(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(400, e.getHttpCode());
+ }
+ }
+
+ // --- insert (batch / continueOnError) ---
+
+ @Test
+ public void testInsert_batchSuccess() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ RecordsClient mockRecords = Mockito.mock(RecordsClient.class);
+ RawRecordsClient mockRawRecords = Mockito.mock(RawRecordsClient.class);
+ when(mockApi.records()).thenReturn(mockRecords);
+ when(mockRecords.withRawResponse()).thenReturn(mockRawRecords);
+
+ V1BatchOperationResponse batchBody = V1BatchOperationResponse.builder().build();
+ Response rawResp = buildOkHttpResponse();
+ ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(batchBody, rawResp);
+ when(mockRawRecords.recordServiceBatchOperation(anyString(), any(), any())).thenReturn(httpResp);
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ ArrayList> values = new ArrayList<>();
+ HashMap row = new HashMap<>();
+ row.put("card_number", "4111111111111111");
+ values.add(row);
+ InsertRequest request = InsertRequest.builder()
+ .table("test_table")
+ .values(values)
+ .continueOnError(true)
+ .build();
+
+ InsertResponse response = controller.insert(request);
+ Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response);
+ }
+
+ @Test
+ public void testInsert_batchApiErrorThrowsSkyflowException() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ RecordsClient mockRecords = Mockito.mock(RecordsClient.class);
+ RawRecordsClient mockRawRecords = Mockito.mock(RawRecordsClient.class);
+ when(mockApi.records()).thenReturn(mockRecords);
+ when(mockRecords.withRawResponse()).thenReturn(mockRawRecords);
+ when(mockRawRecords.recordServiceBatchOperation(anyString(), any(), any()))
+ .thenThrow(new ApiClientApiException("batch failed", 500, "server error"));
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ ArrayList> values = new ArrayList<>();
+ HashMap row = new HashMap<>();
+ row.put("card_number", "4111111111111111");
+ values.add(row);
+ InsertRequest request = InsertRequest.builder()
+ .table("test_table")
+ .values(values)
+ .continueOnError(true)
+ .build();
+
+ try {
+ controller.insert(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(500, e.getHttpCode());
+ }
+ }
+
+ // --- detokenize ---
+
+ @Test
+ public void testDetokenize_success() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ TokensClient mockTokens = Mockito.mock(TokensClient.class);
+ RawTokensClient mockRawTokens = Mockito.mock(RawTokensClient.class);
+ when(mockApi.tokens()).thenReturn(mockTokens);
+ when(mockTokens.withRawResponse()).thenReturn(mockRawTokens);
+
+ V1DetokenizeRecordResponse detokRecord = V1DetokenizeRecordResponse.builder()
+ .token("tok-123")
+ .build();
+ V1DetokenizeResponse detokBody = V1DetokenizeResponse.builder()
+ .records(Collections.singletonList(detokRecord))
+ .build();
+ Response rawResp = buildOkHttpResponse();
+ ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(detokBody, rawResp);
+ when(mockRawTokens.recordServiceDetokenize(anyString(), any(), any())).thenReturn(httpResp);
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ ArrayList detokenizeDataList = new ArrayList<>();
+ detokenizeDataList.add(new DetokenizeData("tok-123"));
+ DetokenizeRequest request = DetokenizeRequest.builder()
+ .detokenizeData(detokenizeDataList)
+ .build();
+
+ DetokenizeResponse response = controller.detokenize(request);
+ Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response);
+ Assert.assertNotNull("detokenizedFields should not be null", response.getDetokenizedFields());
+ Assert.assertEquals(1, response.getDetokenizedFields().size());
+ }
+
+ @Test
+ public void testDetokenize_apiErrorThrowsSkyflowException() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ TokensClient mockTokens = Mockito.mock(TokensClient.class);
+ RawTokensClient mockRawTokens = Mockito.mock(RawTokensClient.class);
+ when(mockApi.tokens()).thenReturn(mockTokens);
+ when(mockTokens.withRawResponse()).thenReturn(mockRawTokens);
+ when(mockRawTokens.recordServiceDetokenize(anyString(), any(), any()))
+ .thenThrow(new ApiClientApiException("detokenize failed", 401, "unauthorized"));
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ ArrayList detokenizeDataList = new ArrayList<>();
+ detokenizeDataList.add(new DetokenizeData("tok-bad"));
+ DetokenizeRequest request = DetokenizeRequest.builder()
+ .detokenizeData(detokenizeDataList)
+ .build();
+
+ try {
+ controller.detokenize(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(401, e.getHttpCode());
+ }
+ }
+
+ // --- get ---
+
+ @Test
+ public void testGet_success() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ RecordsClient mockRecords = Mockito.mock(RecordsClient.class);
+ when(mockApi.records()).thenReturn(mockRecords);
+
+ Map fields = new HashMap<>();
+ fields.put("skyflow_id", "id-get-001");
+ fields.put("card_number", "4111111111111111");
+ V1FieldRecords fieldRecords = V1FieldRecords.builder().fields(fields).build();
+ V1BulkGetRecordResponse getResp = V1BulkGetRecordResponse.builder()
+ .records(Collections.singletonList(fieldRecords))
+ .build();
+ when(mockRecords.recordServiceBulkGetRecord(anyString(), anyString(), any(), any())).thenReturn(getResp);
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ ArrayList ids = new ArrayList<>();
+ ids.add("id-get-001");
+ GetRequest request = GetRequest.builder().table("test_table").ids(ids).build();
+
+ GetResponse response = controller.get(request);
+ Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response);
+ Assert.assertNotNull("data should not be null", response.getData());
+ Assert.assertEquals(1, response.getData().size());
+ Assert.assertEquals("id-get-001", response.getData().get(0).get("skyflowId"));
+ }
+
+ @Test
+ public void testGet_apiErrorThrowsSkyflowException() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ RecordsClient mockRecords = Mockito.mock(RecordsClient.class);
+ when(mockApi.records()).thenReturn(mockRecords);
+ when(mockRecords.recordServiceBulkGetRecord(anyString(), anyString(), any(), any()))
+ .thenThrow(new ApiClientApiException("get failed", 404, "not found"));
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ ArrayList ids = new ArrayList<>();
+ ids.add("id-missing");
+ GetRequest request = GetRequest.builder().table("test_table").ids(ids).build();
+
+ try {
+ controller.get(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(404, e.getHttpCode());
+ }
+ }
+
+ // --- update ---
+
+ @Test
+ public void testUpdate_success() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ RecordsClient mockRecords = Mockito.mock(RecordsClient.class);
+ when(mockApi.records()).thenReturn(mockRecords);
+
+ V1UpdateRecordResponse updateResp = V1UpdateRecordResponse.builder().skyflowId("id-upd-001").build();
+ when(mockRecords.recordServiceUpdateRecord(anyString(), anyString(), anyString(), any(), any()))
+ .thenReturn(updateResp);
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ HashMap data = new HashMap<>();
+ data.put("skyflowId", "id-upd-001");
+ data.put("card_number", "9999999999999999");
+ UpdateRequest request = UpdateRequest.builder().table("test_table").data(data).build();
+
+ UpdateResponse response = controller.update(request);
+ Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response);
+ }
+
+ @Test
+ public void testUpdate_apiErrorThrowsSkyflowException() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ RecordsClient mockRecords = Mockito.mock(RecordsClient.class);
+ when(mockApi.records()).thenReturn(mockRecords);
+ when(mockRecords.recordServiceUpdateRecord(anyString(), anyString(), anyString(), any(), any()))
+ .thenThrow(new ApiClientApiException("update failed", 403, "forbidden"));
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ HashMap data = new HashMap<>();
+ data.put("skyflowId", "id-upd-bad");
+ data.put("card_number", "0000000000000000");
+ UpdateRequest request = UpdateRequest.builder().table("test_table").data(data).build();
+
+ try {
+ controller.update(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(403, e.getHttpCode());
+ }
+ }
+
+ // --- delete ---
+
+ @Test
+ public void testDelete_success() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ RecordsClient mockRecords = Mockito.mock(RecordsClient.class);
+ when(mockApi.records()).thenReturn(mockRecords);
+
+ V1BulkDeleteRecordResponse deleteResp = V1BulkDeleteRecordResponse.builder()
+ .recordIdResponse(Collections.singletonList("id-del-001"))
+ .build();
+ when(mockRecords.recordServiceBulkDeleteRecord(anyString(), anyString(), any(), any()))
+ .thenReturn(deleteResp);
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ ArrayList ids = new ArrayList<>();
+ ids.add("id-del-001");
+ DeleteRequest request = DeleteRequest.builder().table("test_table").ids(ids).build();
+
+ DeleteResponse response = controller.delete(request);
+ Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response);
+ Assert.assertNotNull("deletedIds should not be null", response.getDeletedIds());
+ Assert.assertEquals(1, response.getDeletedIds().size());
+ Assert.assertEquals("id-del-001", response.getDeletedIds().get(0));
+ }
+
+ @Test
+ public void testDelete_apiErrorThrowsSkyflowException() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ RecordsClient mockRecords = Mockito.mock(RecordsClient.class);
+ when(mockApi.records()).thenReturn(mockRecords);
+ when(mockRecords.recordServiceBulkDeleteRecord(anyString(), anyString(), any(), any()))
+ .thenThrow(new ApiClientApiException("delete failed", 400, "bad id"));
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ ArrayList ids = new ArrayList<>();
+ ids.add("id-bad");
+ DeleteRequest request = DeleteRequest.builder().table("test_table").ids(ids).build();
+
+ try {
+ controller.delete(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(400, e.getHttpCode());
+ }
+ }
+
+ // --- query ---
+
+ @Test
+ public void testQuery_success() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ QueryClient mockQuery = Mockito.mock(QueryClient.class);
+ when(mockApi.query()).thenReturn(mockQuery);
+
+ Map fields = new HashMap<>();
+ fields.put("skyflow_id", "id-qry-001");
+ V1FieldRecords fieldRecords = V1FieldRecords.builder().fields(fields).build();
+ V1GetQueryResponse queryResp = V1GetQueryResponse.builder()
+ .records(Collections.singletonList(fieldRecords))
+ .build();
+ when(mockQuery.queryServiceExecuteQuery(anyString(), any(), any())).thenReturn(queryResp);
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ QueryRequest request = QueryRequest.builder().query("SELECT * FROM test_table LIMIT 1").build();
+
+ QueryResponse response = controller.query(request);
+ Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response);
+ Assert.assertNotNull("fields should not be null", response.getFields());
+ Assert.assertEquals(1, response.getFields().size());
+ Assert.assertEquals("id-qry-001", response.getFields().get(0).get("skyflowId"));
+ }
+
+ @Test
+ public void testQuery_apiErrorThrowsSkyflowException() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ QueryClient mockQuery = Mockito.mock(QueryClient.class);
+ when(mockApi.query()).thenReturn(mockQuery);
+ when(mockQuery.queryServiceExecuteQuery(anyString(), any(), any()))
+ .thenThrow(new ApiClientApiException("query failed", 400, "invalid sql"));
+
+ VaultController controller = createControllerWithMock(mockApi);
+
+ QueryRequest request = QueryRequest.builder().query("SELECT * FROM test_table LIMIT 1").build();
+
+ try {
+ controller.query(request);
+ Assert.fail(EXCEPTION_NOT_THROWN);
+ } catch (SkyflowException e) {
+ Assert.assertEquals(400, e.getHttpCode());
+ }
+ }
+
+ // --- insert (batch) with actual records — covers getFormattedBatchInsertRecord ---
+
+ @Test
+ public void testInsert_batchSuccessWithRecords() throws Exception {
+ ApiClient mockApi = Mockito.mock(ApiClient.class);
+ RecordsClient mockRecords = Mockito.mock(RecordsClient.class);
+ RawRecordsClient mockRawRecords = Mockito.mock(RawRecordsClient.class);
+ when(mockApi.records()).thenReturn(mockRecords);
+ when(mockRecords.withRawResponse()).thenReturn(mockRawRecords);
+
+ // Build a response item whose Body contains a records array with skyflowId and tokens
+ Map tokens = new HashMap<>();
+ tokens.put("card_number", "tok-card-111");
+
+ Map recordEntry = new HashMap<>();
+ recordEntry.put("skyflowId", "id-batch-001");
+ recordEntry.put("tokens", tokens);
+
+ List