diff --git a/README.md b/README.md index 36b03044..e041ca2e 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,9 @@ The Skyflow Java SDK is designed to help with integrating Skyflow into a Java ba - [Generate scoped bearer tokens](#generate-scoped-bearer-tokens) - [Generate signed data tokens](#generate-signed-data-tokens) - [Bearer token expiry edge case](#bearer-token-expiry-edge-case) +- [Error Handling](#error-handling) + - [Catching SkyflowException](#catching-skyflowexception) + - [SkyflowException properties](#skyflowexception-properties) - [Logging](#logging) - [Reporting a Vulnerability](#reporting-a-vulnerability) @@ -2786,6 +2789,48 @@ public class DetokenizeExample { } ``` +# Error Handling + +The SDK uses `SkyflowException` for all errors — both client-side validation errors and server-side API errors. + +## Catching SkyflowException + +Wrap SDK calls in a `try/catch` block and catch `SkyflowException` to handle Skyflow-specific errors separately from unexpected exceptions: + +```java +import com.skyflow.errors.SkyflowException; + +try { + InsertResponse response = skyflowClient.vault().insert(insertRequest); +} catch (SkyflowException e) { + System.err.println("Skyflow error:"); + System.err.println(" HTTP code : " + e.getHttpCode()); + System.err.println(" Message : " + e.getMessage()); + System.err.println(" Request ID: " + e.getRequestId()); + System.err.println(" Details : " + e.getDetails()); +} catch (Exception e) { + System.err.println("Unexpected error: " + e.getMessage()); +} +``` + +## SkyflowException properties + +| Property | Method | Description | +|---|---|---| +| HTTP status code | `getHttpCode()` | Integer status code (e.g. `400`, `404`, `500`). | +| Message | `getMessage()` | Human-readable description of the error. | +| HTTP status string | `getHttpStatus()` | Status string from the server (e.g. `"BAD_REQUEST"`). | +| gRPC code | `getGrpcCode()` | gRPC status code from the server. | +| Request ID | `getRequestId()` | The `x-request-id` header — useful for support escalations. | +| Details | `getDetails()` | `JsonArray` of additional error context from the server. Empty array for validation errors, `null` if the server response omitted the field. | + +**Validation errors** (missing table name, empty token list, etc.) are thrown before any network call: +- `httpCode` is always `400` +- `requestId` and `grpcCode` are `null` +- `details` is an empty array + +**API errors** are returned by the Skyflow server and have all fields populated from the response body and headers. + # Logging The SDK provides logging with Java's built-in logging library. By default, the SDK's logging level is set to `LogLevel.ERROR`. This can be changed using the `setLogLevel(logLevel)` method, as shown below: diff --git a/docs/migrate_to_v2.md b/docs/migrate_to_v2.md index 55facd45..747076be 100644 --- a/docs/migrate_to_v2.md +++ b/docs/migrate_to_v2.md @@ -249,4 +249,35 @@ Response maps now return `skyflowId` (camelCase). The legacy `skyflow_id` key is --- +## Update request data key (v2.1+) + +When calling `update()`, use `skyflowId` (camelCase) as the key in the data map to identify the record. Using `skyflow_id` still works but emits a deprecation warning. If both keys are present, `skyflowId` takes precedence. + +```java +HashMap data = new HashMap<>(); +data.put("skyflowId", ""); // preferred +data.put("card_number", ""); + +UpdateRequest request = UpdateRequest.builder() + .table("") + .data(data) + .returnTokens(true) + .build(); + +skyflowClient.vault().update(request); +``` + +--- + +## Method renames (v2.1+) + +The following instance methods have been renamed for consistency. The old names still work but emit deprecation warnings. + +| Deprecated | Preferred | +|---|---| +| `skyflowClient.updateLogLevel(logLevel)` | `skyflowClient.setLogLevel(logLevel)` | +| `TokenMode.getBYOT()` | `TokenMode.getByot()` | + +--- + For the full list of changes see [CHANGELOG.md](../CHANGELOG.md). diff --git a/samples/src/main/java/com/example/vault/UpdateExample.java b/samples/src/main/java/com/example/vault/UpdateExample.java index a5947780..9757ba44 100644 --- a/samples/src/main/java/com/example/vault/UpdateExample.java +++ b/samples/src/main/java/com/example/vault/UpdateExample.java @@ -46,7 +46,7 @@ public static void main(String[] args) throws SkyflowException { // Step 5: Update records with TokenMode enabled try { HashMap data1 = new HashMap<>(); - data1.put("skyflow_id", ""); // Replace with the Skyflow ID of the record + data1.put("skyflowId", ""); // Replace with the Skyflow ID of the record data1.put("", ""); // Replace with column name and value to update data1.put("", ""); // Replace with another column name and value @@ -71,7 +71,7 @@ public static void main(String[] args) throws SkyflowException { // Step 6: Update records with TokenMode disabled try { HashMap data2 = new HashMap<>(); - data2.put("skyflow_id", ""); // Replace with the Skyflow ID of the record + data2.put("skyflowId", ""); // Replace with the Skyflow ID of the record data2.put("", ""); // Replace with column name and value to update data2.put("", ""); // Replace with another column name and value diff --git a/samples/src/main/java/com/example/vault/deprecated/UpdateExample.java b/samples/src/main/java/com/example/vault/deprecated/UpdateExample.java new file mode 100644 index 00000000..aaead19c --- /dev/null +++ b/samples/src/main/java/com/example/vault/deprecated/UpdateExample.java @@ -0,0 +1,94 @@ +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.TokenMode; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.UpdateRequest; +import com.skyflow.vault.data.UpdateResponse; + +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}). + * + * 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. + */ +@Deprecated +public class UpdateExample { + public static void main(String[] args) throws SkyflowException { + // Step 1: Set up credentials for the first vault configuration + Credentials credentials = new Credentials(); + credentials.setApiKey(""); // Replace with the actual API key + + // Step 2: Configure the vault + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); // Replace with the ID of the vault + vaultConfig.setClusterId(""); // Replace with the cluster ID of the vault + vaultConfig.setEnv(Env.PROD); // Set the environment (e.g., DEV, STAGE, PROD) + vaultConfig.setCredentials(credentials); // Associate the credentials with the vault + + // Step 3: Set up credentials for the Skyflow client + Credentials skyflowCredentials = new Credentials(); + skyflowCredentials.setCredentialsString(""); // Replace with the actual credentials string + + // Step 4: Create a Skyflow client and add vault configurations + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) // Enable debugging for detailed logs + .addVaultConfig(vaultConfig) // Add the vault configuration + .addSkyflowCredentials(skyflowCredentials) // Add general Skyflow credentials + .build(); + + // Step 5: Update records with TokenMode enabled + // DEPRECATED: use "skyflowId" key instead of "skyflow_id" + try { + HashMap data1 = new HashMap<>(); + data1.put("skyflow_id", ""); // @deprecated — use "skyflowId" + data1.put("", ""); // Replace with column name and value to update + data1.put("", ""); // Replace with another column name and value + + HashMap tokens = new HashMap<>(); + tokens.put("", ""); // Replace with the token for COLUMN_NAME_2 + + UpdateRequest updateRequest1 = UpdateRequest.builder() + .table("") // Replace with the table name + .tokenMode(TokenMode.ENABLE) // Enable TokenMode for token validation + .data(data1) // Data to update + .tokens(tokens) // Provide tokens for TokenMode columns + .returnTokens(true) // Return tokens along with the update response + .build(); + + UpdateResponse updateResponse1 = skyflowClient.vault().update(updateRequest1); // Perform the update + System.out.println("Update Response (TokenMode Enabled): " + updateResponse1); + } catch (SkyflowException e) { + System.out.println("Error during update with TokenMode enabled:"); + e.printStackTrace(); + } + + // Step 6: Update records with TokenMode disabled + // DEPRECATED: use "skyflowId" key instead of "skyflow_id" + try { + HashMap data2 = new HashMap<>(); + data2.put("skyflow_id", ""); // @deprecated — use "skyflowId" + data2.put("", ""); // Replace with column name and value to update + data2.put("", ""); // Replace with another column name and value + + UpdateRequest updateRequest2 = UpdateRequest.builder() + .table("") // Replace with the table name + .tokenMode(TokenMode.DISABLE) // Disable TokenMode + .data(data2) // Data to update + .returnTokens(false) // Do not return tokens + .build(); + + UpdateResponse updateResponse2 = skyflowClient.vault().update(updateRequest2); // Perform the update + System.out.println("Update Response (TokenMode Disabled): " + updateResponse2); + } catch (SkyflowException e) { + System.out.println("Error during update with TokenMode disabled:" + e); + } + } +} diff --git a/src/main/java/com/skyflow/Skyflow.java b/src/main/java/com/skyflow/Skyflow.java index 8e51da57..eba8d6fd 100644 --- a/src/main/java/com/skyflow/Skyflow.java +++ b/src/main/java/com/skyflow/Skyflow.java @@ -74,11 +74,18 @@ public Skyflow updateSkyflowCredentials(Credentials credentials) throws SkyflowE return this; } - public Skyflow updateLogLevel(LogLevel logLevel) { + public Skyflow setLogLevel(LogLevel logLevel) { this.builder.setLogLevel(logLevel); return this; } + /** @deprecated Use {@link #setLogLevel(LogLevel)} instead. */ + @Deprecated(since = "2.1", forRemoval = true) + public Skyflow updateLogLevel(LogLevel logLevel) { + LogUtil.printWarningLog(InfoLogs.DEPRECATED_UPDATE_LOG_LEVEL.getLog()); + return setLogLevel(logLevel); + } + public LogLevel getLogLevel() { return this.builder.logLevel; } diff --git a/src/main/java/com/skyflow/errors/SkyflowException.java b/src/main/java/com/skyflow/errors/SkyflowException.java index 32c71f39..6fedf9c3 100644 --- a/src/main/java/com/skyflow/errors/SkyflowException.java +++ b/src/main/java/com/skyflow/errors/SkyflowException.java @@ -9,6 +9,33 @@ import java.util.List; import java.util.Map; +/** + * Exception thrown by all Skyflow SDK operations. + * + *

There are two broad categories of errors: + * + *

    + *
  • Validation errors — caught before any network call is made (e.g. missing table, + * empty token list). These always have {@code httpCode = 400} and an empty + * {@link #getDetails()} array. {@link #getRequestId()} and {@link #getGrpcCode()} are + * {@code null}. + *
  • API errors — returned by the Skyflow server. The HTTP status code, gRPC code, + * human-readable status string, error message, and request ID are all parsed from the + * response and available via the corresponding getters. + *
+ * + *

Typical error-handling pattern: + *

{@code
+ * try {
+ *     InsertResponse response = vault.insert(request);
+ * } catch (SkyflowException e) {
+ *     System.err.println("HTTP " + e.getHttpCode() + " — " + e.getMessage());
+ *     if (e.getRequestId() != null) {
+ *         System.err.println("Request ID: " + e.getRequestId());
+ *     }
+ * }
+ * }
+ */ public class SkyflowException extends Exception { private String requestId; private Integer grpcCode; @@ -33,6 +60,11 @@ public SkyflowException(String message, Throwable cause) { this.message = message; } + /** + * Constructs a validation error with a fixed HTTP 400 status. + * {@link #getDetails()} returns an empty array; {@link #getRequestId()} and + * {@link #getGrpcCode()} return {@code null}. + */ public SkyflowException(int code, String message) { super(message); this.httpCode = code; @@ -41,6 +73,13 @@ public SkyflowException(int code, String message) { this.details = new JsonArray(); } + /** + * Constructs an API error from an HTTP response. + * Parses the JSON error body to populate {@link #getMessage()}, {@link #getGrpcCode()}, + * {@link #getHttpStatus()}, and {@link #getDetails()}. The request ID is read from the + * {@code x-request-id} response header. If the body cannot be parsed, falls back to the + * raw body string as the message. + */ public SkyflowException(int httpCode, Throwable cause, Map> responseHeaders, String responseBody) { super(cause); this.httpCode = httpCode > 0 ? httpCode : 400; @@ -65,6 +104,10 @@ private void setResponseBody(String responseBody, Map> resp } } + /** + * Returns the {@code x-request-id} from the server response, useful for support escalations. + * {@code null} for validation errors that never reached the server. + */ public String getRequestId() { return requestId; } @@ -89,10 +132,19 @@ private void setHttpStatus() { this.httpStatus = statusElement == null ? null : statusElement.getAsString(); } + /** + * Returns the HTTP status code (e.g. 400, 404, 500). + * Defaults to 400 when the server returned a non-positive code. + */ public int getHttpCode() { return httpCode; } + /** + * Returns additional error details from the server response, or an empty array for + * validation errors. Never {@code null} for validation errors; may be {@code null} for + * API errors whose response body contained no {@code details} field. + */ public JsonArray getDetails() { return details; } @@ -112,10 +164,18 @@ private void setDetails(Map> responseHeaders) { } } + /** + * Returns the gRPC status code from the server response. + * {@code null} for validation errors and API responses that omit this field. + */ public Integer getGrpcCode() { return grpcCode; } + /** + * Returns the human-readable HTTP status string from the server response (e.g. + * {@code "Bad Request"}, {@code "Not Found"}). + */ public String getHttpStatus() { return httpStatus; } diff --git a/src/main/java/com/skyflow/logs/InfoLogs.java b/src/main/java/com/skyflow/logs/InfoLogs.java index de93a924..af16698a 100644 --- a/src/main/java/com/skyflow/logs/InfoLogs.java +++ b/src/main/java/com/skyflow/logs/InfoLogs.java @@ -100,7 +100,8 @@ public enum InfoLogs { DEPRECATED_SKYFLOW_ID_KEY("[DEPRECATED] Response key 'skyflow_id' is deprecated and will be removed in an upcoming release. Use 'skyflowId' instead."), 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_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."); diff --git a/src/main/java/com/skyflow/vault/controller/VaultController.java b/src/main/java/com/skyflow/vault/controller/VaultController.java index 434afeae..980760ae 100644 --- a/src/main/java/com/skyflow/vault/controller/VaultController.java +++ b/src/main/java/com/skyflow/vault/controller/VaultController.java @@ -103,7 +103,12 @@ private static synchronized HashMap getFormattedBatchInsertRecor if (records != null) { for (JsonElement recordElement : records) { JsonObject recordObject = recordElement.getAsJsonObject(); - insertRecord.put("skyflowId", recordObject.get("skyflow_id").getAsString()); + if (recordObject.has("skyflowId")) { + insertRecord.put("skyflowId", recordObject.get("skyflowId").getAsString()); + } else if (recordObject.has("skyflow_id")) { + insertRecord.put("skyflowId", recordObject.get("skyflow_id").getAsString()); + LogUtil.printWarningLog(InfoLogs.DEPRECATED_SKYFLOW_ID_KEY.getLog()); + } JsonElement tokensElement = recordObject.get("tokens"); if (tokensElement != null) { insertRecord.putAll(tokensElement.getAsJsonObject().asMap()); diff --git a/src/test/java/com/skyflow/SkyflowTests.java b/src/test/java/com/skyflow/SkyflowTests.java index 0b44c0b4..12e83e06 100644 --- a/src/test/java/com/skyflow/SkyflowTests.java +++ b/src/test/java/com/skyflow/SkyflowTests.java @@ -8,13 +8,36 @@ import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.logger.LogUtil; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + public class SkyflowTests { 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 class CapturingHandler extends Handler { + final List records = new ArrayList<>(); + @Override public void publish(LogRecord r) { records.add(r); } + @Override public void flush() {} + @Override public void close() {} + } + + private CapturingHandler attachCapture() { + CapturingHandler handler = new CapturingHandler(); + handler.setLevel(Level.ALL); + Logger.getLogger(LogUtil.class.getName()).addHandler(handler); + return handler; + } private static String vaultID = null; private static String clusterID = null; private static String newClusterID = null; @@ -150,6 +173,31 @@ public void testUpdatingValidVaultConfigInSkyflowClient() { } } + @Test + public void testUpdateVaultConfigNullCredentialsFallsBackToPrevious() { + try { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultID); + config.setClusterId(clusterID); + config.setEnv(Env.SANDBOX); + + Credentials creds = new Credentials(); + creds.setToken(token); + config.setCredentials(creds); + + Skyflow skyflowClient = Skyflow.builder().addVaultConfig(config).build(); + + // Update with null credentials — should retain previous credentials value + VaultConfig partialUpdate = new VaultConfig(); + partialUpdate.setVaultId(vaultID); + partialUpdate.setClusterId(clusterID); + skyflowClient.updateVaultConfig(partialUpdate); + Assert.assertNotNull(skyflowClient.getVaultConfig(vaultID).getCredentials()); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + @Test public void testRemovingNonExistentVaultConfigInSkyflowBuilder() { try { @@ -339,6 +387,32 @@ public void testUpdatingValidConnectionConfigInSkyflowClient() { } } + @Test + public void testUpdateConnectionConfigWithNullFieldsFallsBackToPrevious() { + try { + ConnectionConfig config = new ConnectionConfig(); + config.setConnectionId(connectionID); + config.setConnectionUrl(connectionURL); + + Credentials creds = new Credentials(); + creds.setToken(token); + config.setCredentials(creds); + + Skyflow skyflowClient = Skyflow.builder().addConnectionConfig(config).build(); + + // Update with null credentials — validation requires connectionUrl, so provide it; + // credentials should fall back to previous value + ConnectionConfig partialUpdate = new ConnectionConfig(); + partialUpdate.setConnectionId(connectionID); + partialUpdate.setConnectionUrl(connectionURL); + // credentials is null → should retain previous value + skyflowClient.updateConnectionConfig(partialUpdate); + Assert.assertNotNull(skyflowClient.getConnectionConfig(connectionID).getCredentials()); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + @Test public void testRemovingNonExistentConnectionConfigInSkyflowBuilder() { try { @@ -429,6 +503,19 @@ public void testDefaultLogLevel() { } @Test + public void testSetLogLevel() { + try { + Skyflow skyflowClient = Skyflow.builder().setLogLevel(LogLevel.INFO).build(); + Assert.assertEquals(LogLevel.INFO, skyflowClient.getLogLevel()); + skyflowClient.setLogLevel(LogLevel.WARN); + Assert.assertEquals(LogLevel.WARN, skyflowClient.getLogLevel()); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + @SuppressWarnings("deprecation") public void testUpdateLogLevel() { try { Skyflow skyflowClient = Skyflow.builder().setLogLevel(LogLevel.INFO).build(); @@ -440,6 +527,38 @@ public void testUpdateLogLevel() { } } + @Test + @SuppressWarnings("deprecation") + public void testUpdateLogLevelEmitsDeprecationWarning() { + try { + // build() calls setupLogger internally — attach capture after so it isn't wiped + Skyflow skyflowClient = Skyflow.builder().setLogLevel(LogLevel.INFO).build(); + CapturingHandler handler = attachCapture(); + skyflowClient.updateLogLevel(LogLevel.WARN); + boolean warnFired = handler.records.stream() + .anyMatch(r -> r.getLevel().equals(Level.WARNING) + && r.getMessage().contains(InfoLogs.DEPRECATED_UPDATE_LOG_LEVEL.getLog())); + Assert.assertTrue("updateLogLevel() should emit a deprecation warning log", warnFired); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + @SuppressWarnings("deprecation") + public void testUpdateLogLevelWarningIsSuppressedAtErrorLevel() { + try { + Skyflow skyflowClient = Skyflow.builder().setLogLevel(LogLevel.ERROR).build(); + CapturingHandler handler = attachCapture(); + skyflowClient.updateLogLevel(LogLevel.WARN); + boolean warnFired = handler.records.stream() + .anyMatch(r -> r.getLevel().equals(Level.WARNING)); + Assert.assertFalse("updateLogLevel() warning should be suppressed at ERROR log level", warnFired); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + @Test public void testVaultMethodWithNoConfig() { try { diff --git a/src/test/java/com/skyflow/config/ManagementConfigTest.java b/src/test/java/com/skyflow/config/ManagementConfigTest.java new file mode 100644 index 00000000..6c11f2fc --- /dev/null +++ b/src/test/java/com/skyflow/config/ManagementConfigTest.java @@ -0,0 +1,14 @@ +package com.skyflow.config; + +import org.junit.Assert; +import org.junit.Test; + +public class ManagementConfigTest { + + @Test + public void testInstantiation() { + // Package-private constructor — accessible from same package + ManagementConfig config = new ManagementConfig(); + Assert.assertNotNull(config); + } +} diff --git a/src/test/java/com/skyflow/enums/DeidentifyFileStatusTest.java b/src/test/java/com/skyflow/enums/DeidentifyFileStatusTest.java new file mode 100644 index 00000000..6d70a17c --- /dev/null +++ b/src/test/java/com/skyflow/enums/DeidentifyFileStatusTest.java @@ -0,0 +1,27 @@ +package com.skyflow.enums; + +import org.junit.Assert; +import org.junit.Test; + +public class DeidentifyFileStatusTest { + + @Test + public void testInProgress() { + Assert.assertEquals("IN_PROGRESS", DeidentifyFileStatus.IN_PROGRESS.value()); + } + + @Test + public void testFailed() { + Assert.assertEquals("FAILED", DeidentifyFileStatus.FAILED.value()); + } + + @Test + public void testSuccess() { + Assert.assertEquals("SUCCESS", DeidentifyFileStatus.SUCCESS.value()); + } + + @Test + public void testUnknown() { + Assert.assertEquals("UNKNOWN", DeidentifyFileStatus.UNKNOWN.value()); + } +} diff --git a/src/test/java/com/skyflow/enums/DetectEntitiesTest.java b/src/test/java/com/skyflow/enums/DetectEntitiesTest.java new file mode 100644 index 00000000..2bb29f0c --- /dev/null +++ b/src/test/java/com/skyflow/enums/DetectEntitiesTest.java @@ -0,0 +1,24 @@ +package com.skyflow.enums; + +import org.junit.Assert; +import org.junit.Test; + +public class DetectEntitiesTest { + + @Test + public void testGetDetectEntities() { + Assert.assertEquals("account_number", DetectEntities.ACCOUNT_NUMBER.getDetectEntities()); + Assert.assertEquals("account_number", DetectEntities.ACCOUNT_NUMBER.toString()); + } + + @Test + public void testAll() { + Assert.assertEquals("all", DetectEntities.ALL.getDetectEntities()); + } + + @Test + public void testName() { + Assert.assertEquals("name", DetectEntities.NAME.getDetectEntities()); + Assert.assertEquals("name", DetectEntities.NAME.toString()); + } +} diff --git a/src/test/java/com/skyflow/enums/DetectOutputTranscriptionsTest.java b/src/test/java/com/skyflow/enums/DetectOutputTranscriptionsTest.java new file mode 100644 index 00000000..6df404ed --- /dev/null +++ b/src/test/java/com/skyflow/enums/DetectOutputTranscriptionsTest.java @@ -0,0 +1,31 @@ +package com.skyflow.enums; + +import org.junit.Assert; +import org.junit.Test; + +public class DetectOutputTranscriptionsTest { + + @Test + public void testDiarizedTranscription() { + Assert.assertEquals("diarized_transcription", DetectOutputTranscriptions.DIARIZED_TRANSCRIPTION.getDetectOutputTranscriptions()); + Assert.assertEquals("diarized_transcription", DetectOutputTranscriptions.DIARIZED_TRANSCRIPTION.toString()); + } + + @Test + public void testMedicalDiarizedTranscription() { + Assert.assertEquals("medical_diarized_transcription", DetectOutputTranscriptions.MEDICAL_DIARIZED_TRANSCRIPTION.getDetectOutputTranscriptions()); + Assert.assertEquals("medical_diarized_transcription", DetectOutputTranscriptions.MEDICAL_DIARIZED_TRANSCRIPTION.toString()); + } + + @Test + public void testMedicalTranscription() { + Assert.assertEquals("medical_transcription", DetectOutputTranscriptions.MEDICAL_TRANSCRIPTION.getDetectOutputTranscriptions()); + Assert.assertEquals("medical_transcription", DetectOutputTranscriptions.MEDICAL_TRANSCRIPTION.toString()); + } + + @Test + public void testTranscription() { + Assert.assertEquals("transcription", DetectOutputTranscriptions.TRANSCRIPTION.getDetectOutputTranscriptions()); + Assert.assertEquals("transcription", DetectOutputTranscriptions.TRANSCRIPTION.toString()); + } +} diff --git a/src/test/java/com/skyflow/enums/MaskingMethodTest.java b/src/test/java/com/skyflow/enums/MaskingMethodTest.java new file mode 100644 index 00000000..f675189e --- /dev/null +++ b/src/test/java/com/skyflow/enums/MaskingMethodTest.java @@ -0,0 +1,19 @@ +package com.skyflow.enums; + +import org.junit.Assert; +import org.junit.Test; + +public class MaskingMethodTest { + + @Test + public void testBlackbox() { + Assert.assertEquals("blackbox", MaskingMethod.BLACKBOX.getMaskingMethod()); + Assert.assertEquals("blackbox", MaskingMethod.BLACKBOX.toString()); + } + + @Test + public void testBlur() { + Assert.assertEquals("blur", MaskingMethod.BLUR.getMaskingMethod()); + Assert.assertEquals("blur", MaskingMethod.BLUR.toString()); + } +} diff --git a/src/test/java/com/skyflow/enums/TokenTypeTest.java b/src/test/java/com/skyflow/enums/TokenTypeTest.java new file mode 100644 index 00000000..4f5c0901 --- /dev/null +++ b/src/test/java/com/skyflow/enums/TokenTypeTest.java @@ -0,0 +1,30 @@ +package com.skyflow.enums; + +import org.junit.Assert; +import org.junit.Test; + +public class TokenTypeTest { + + @Test + public void testVaultToken() { + Assert.assertEquals("vault_token", TokenType.VAULT_TOKEN.getTokenType()); + Assert.assertEquals("vault_token", TokenType.VAULT_TOKEN.toString()); + } + + @Test + public void testEntityUniqueCounter() { + Assert.assertEquals("entity_unq_counter", TokenType.ENTITY_UNIQUE_COUNTER.getTokenType()); + Assert.assertEquals("entity_unq_counter", TokenType.ENTITY_UNIQUE_COUNTER.toString()); + } + + @Test + public void testEntityOnly() { + Assert.assertEquals("entity_only", TokenType.ENTITY_ONLY.getTokenType()); + Assert.assertEquals("entity_only", TokenType.ENTITY_ONLY.toString()); + } + + @Test + public void testGetDefault() { + Assert.assertEquals(TokenType.ENTITY_UNIQUE_COUNTER.getTokenType(), TokenType.VAULT_TOKEN.getDefault()); + } +} diff --git a/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java b/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java index e2a1d921..83df09ee 100644 --- a/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java +++ b/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java @@ -137,4 +137,57 @@ public void testToStringWithNullFields() { Assert.assertTrue(str.contains("httpStatus: null")); Assert.assertTrue(str.contains("details: null")); } + + @Test + public void testZeroHttpCodeDefaultsTo400() { + Map> headers = new HashMap<>(); + String json = "{\"error\":{\"message\":\"zero code\",\"grpc_code\":1,\"http_status\":\"BAD_REQUEST\"}}"; + SkyflowException ex = new SkyflowException(0, new RuntimeException("fail"), headers, json); + Assert.assertEquals(400, ex.getHttpCode()); + } + + @Test + public void testJsonBodyWithoutErrorKey() { + Map> headers = new HashMap<>(); + headers.put("x-request-id", Collections.singletonList("req-no-error")); + String json = "{\"message\":\"no error key here\"}"; + SkyflowException ex = new SkyflowException(400, new RuntimeException("fail"), headers, json); + Assert.assertEquals("req-no-error", ex.getRequestId()); + Assert.assertNull(ex.getGrpcCode()); + Assert.assertNull(ex.getMessage()); + } + + @Test + public void testJsonErrorBodyWithNoMessageField() { + Map> headers = new HashMap<>(); + String json = "{\"error\":{\"grpc_code\":3,\"http_status\":\"INVALID_ARGUMENT\"}}"; + SkyflowException ex = new SkyflowException(400, new RuntimeException("fail"), headers, json); + Assert.assertNull(ex.getMessage()); + Assert.assertEquals(Integer.valueOf(3), ex.getGrpcCode()); + } + + @Test + public void testJsonErrorBodyWithNoGrpcCodeField() { + Map> headers = new HashMap<>(); + String json = "{\"error\":{\"message\":\"some error\",\"http_status\":\"BAD_REQUEST\"}}"; + SkyflowException ex = new SkyflowException(400, new RuntimeException("fail"), headers, json); + Assert.assertEquals("some error", ex.getMessage()); + Assert.assertNull(ex.getGrpcCode()); + } + + @Test + public void testNonJsonBodyFallsBackToRawBodyAsMessage() { + Map> headers = new HashMap<>(); + String body = "plain text error response"; + SkyflowException ex = new SkyflowException(500, new RuntimeException("fail"), headers, body); + Assert.assertEquals("plain text error response", ex.getMessage()); + } + + @Test + public void testNullBodyNullCauseMessageFallsBackToErrorOccurred() { + Map> headers = new HashMap<>(); + SkyflowException ex = new SkyflowException(500, new RuntimeException((String) null), headers, null); + Assert.assertNotNull(ex.getMessage()); + Assert.assertTrue(ex.getMessage().contains("API error")); + } } \ No newline at end of file diff --git a/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java b/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java index 63717b5c..6fee259d 100644 --- a/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java +++ b/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java @@ -11,6 +11,7 @@ import org.junit.BeforeClass; import org.junit.Test; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -433,6 +434,22 @@ public void testInvokeConnectionResponse() { Assert.assertNotNull(connectionResponse.getData()); Assert.assertEquals(responseString, connectionResponse.toString()); Assert.assertEquals(1, connectionResponse.getMetadata().size()); + Assert.assertNull(connectionResponse.getErrors()); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testInvokeConnectionResponseWithErrors() { + try { + ArrayList> errors = new ArrayList<>(); + HashMap err = new HashMap<>(); + err.put("error", "connection failed"); + errors.add(err); + InvokeConnectionResponse response = new InvokeConnectionResponse(null, null, errors); + Assert.assertEquals(1, response.getErrors().size()); + Assert.assertEquals("connection failed", response.getErrors().get(0).get("error")); } catch (Exception e) { Assert.fail(INVALID_EXCEPTION_THROWN); } diff --git a/src/test/java/com/skyflow/vault/data/FileUploadTests.java b/src/test/java/com/skyflow/vault/data/FileUploadTests.java index 1eac84ae..f0ad871f 100644 --- a/src/test/java/com/skyflow/vault/data/FileUploadTests.java +++ b/src/test/java/com/skyflow/vault/data/FileUploadTests.java @@ -1,6 +1,8 @@ package com.skyflow.vault.data; import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; import org.junit.Assert; import org.junit.BeforeClass; @@ -186,6 +188,27 @@ public void testMissingFileData() { } } + @Test + public void testFileUploadResponseGetters() { + ArrayList> errors = new ArrayList<>(); + HashMap err = new HashMap<>(); + err.put("error", "upload failed"); + errors.add(err); + + FileUploadResponse response = new FileUploadResponse("sky-123", errors); + Assert.assertEquals("sky-123", response.getSkyflowId()); + Assert.assertEquals(1, response.getErrors().size()); + Assert.assertEquals("upload failed", response.getErrors().get(0).get("error")); + } + + @Test + public void testFileUploadResponseToString() { + FileUploadResponse response = new FileUploadResponse("sky-456", null); + String json = response.toString(); + Assert.assertTrue(json.contains("sky-456")); + Assert.assertNull(response.getErrors()); + } + @Test public void testMissingFileNameWithBase64Invalid() { try { diff --git a/src/test/java/com/skyflow/vault/data/InsertTests.java b/src/test/java/com/skyflow/vault/data/InsertTests.java index a9178f4a..030bc5ea 100644 --- a/src/test/java/com/skyflow/vault/data/InsertTests.java +++ b/src/test/java/com/skyflow/vault/data/InsertTests.java @@ -419,4 +419,15 @@ public void testInsertResponse() { } } + @Test + public void testInsertRequestBuilderContinueOnErrorFalse() { + values.add(valueMap); + InsertRequest request = InsertRequest.builder() + .table(table) + .values(values) + .continueOnError(false) + .build(); + Assert.assertFalse(request.getContinueOnError()); + } + } diff --git a/src/test/java/com/skyflow/vault/detect/DeidentifyFileRequestTest.java b/src/test/java/com/skyflow/vault/detect/DeidentifyFileRequestTest.java index 1b19d9d9..dd0d7cc5 100644 --- a/src/test/java/com/skyflow/vault/detect/DeidentifyFileRequestTest.java +++ b/src/test/java/com/skyflow/vault/detect/DeidentifyFileRequestTest.java @@ -96,4 +96,37 @@ public void testBuilderWithNullFileAndFilePath() { Assert.assertNull(request.getFileInput().getFile()); Assert.assertNull(request.getFileInput().getFilePath()); } + + @Test + public void testBuilderNullBooleansFallToFalse() { + DeidentifyFileRequest request = DeidentifyFileRequest.builder() + .outputProcessedImage(null) + .outputOcrText(null) + .outputProcessedAudio(null) + .build(); + Assert.assertFalse(request.getOutputProcessedImage()); + Assert.assertFalse(request.getOutputOcrText()); + Assert.assertFalse(request.getOutputProcessedAudio()); + } + + @Test + public void testBuilderWithTokenFormatTransformationsPixelDensityMaxResolutionBleep() { + TokenFormat tf = TokenFormat.builder().build(); + Transformations tr = new Transformations(null); + AudioBleep bleep = AudioBleep.builder().frequency(800.0).build(); + + DeidentifyFileRequest request = DeidentifyFileRequest.builder() + .tokenFormat(tf) + .transformations(tr) + .pixelDensity(150) + .maxResolution(1920) + .bleep(bleep) + .build(); + + Assert.assertEquals(tf, request.getTokenFormat()); + Assert.assertEquals(tr, request.getTransformations()); + Assert.assertEquals(150, request.getPixelDensity().intValue()); + Assert.assertEquals(1920, request.getMaxResolution().intValue()); + Assert.assertEquals(bleep, request.getBleep()); + } } \ No newline at end of file diff --git a/src/test/java/com/skyflow/vault/detect/DeidentifyFileResponseTest.java b/src/test/java/com/skyflow/vault/detect/DeidentifyFileResponseTest.java index 62c7ac7e..d3946719 100644 --- a/src/test/java/com/skyflow/vault/detect/DeidentifyFileResponseTest.java +++ b/src/test/java/com/skyflow/vault/detect/DeidentifyFileResponseTest.java @@ -53,5 +53,17 @@ public void testAllGettersAndToString() { Assert.assertTrue(json.contains(runId)); Assert.assertTrue(json.contains(status)); Assert.assertTrue(json.contains("PERSON")); + Assert.assertEquals(fileInfo, response.getFile()); + Assert.assertEquals(file, response.getFileBase64()); + } + + @Test + public void testShortConstructorAndGetters() { + DeidentifyFileResponse response = new DeidentifyFileResponse("run-456", "PENDING"); + Assert.assertEquals("run-456", response.getRunId()); + Assert.assertEquals("PENDING", response.getStatus()); + Assert.assertNull(response.getFile()); + Assert.assertNull(response.getFileBase64()); + Assert.assertNull(response.getWordCount()); } } \ No newline at end of file diff --git a/src/test/java/com/skyflow/vault/detect/DeidentifyTextTests.java b/src/test/java/com/skyflow/vault/detect/DeidentifyTextTests.java index 08cde4c3..30563a83 100644 --- a/src/test/java/com/skyflow/vault/detect/DeidentifyTextTests.java +++ b/src/test/java/com/skyflow/vault/detect/DeidentifyTextTests.java @@ -214,4 +214,74 @@ public void testDeidentifyResponse() { Assert.fail(INVALID_EXCEPTION_THROWN); } } + + @Test + public void testDeidentifyResponseGetEntitiesAndToString() { + TextIndex ti = new TextIndex(0, 4); + TextIndex pi = new TextIndex(5, 9); + java.util.Map scores = new java.util.HashMap<>(); + scores.put("confidence", 0.95); + EntityInfo ei = new EntityInfo("tok1", "John", ti, pi, "NAME", scores); + + List entities = new ArrayList<>(); + entities.add(ei); + + DeidentifyTextResponse response = new DeidentifyTextResponse(text, entities, 1, 10); + Assert.assertEquals(entities, response.getEntities()); + String json = response.toString(); + Assert.assertNotNull(json); + Assert.assertTrue(json.contains(text)); + } + + @Test + public void testTextIndex() { + TextIndex ti = new TextIndex(3, 7); + Assert.assertEquals(3, ti.getStart()); + Assert.assertEquals(7, ti.getEnd()); + String json = ti.toString(); + Assert.assertNotNull(json); + Assert.assertTrue(json.contains("3")); + } + + @Test + public void testEntityInfoGetters() { + TextIndex ti = new TextIndex(0, 4); + TextIndex pi = new TextIndex(5, 9); + java.util.Map scores = new java.util.HashMap<>(); + scores.put("confidence", 0.9); + EntityInfo ei = new EntityInfo("tok1", "Alice", ti, pi, "NAME", scores); + Assert.assertEquals("tok1", ei.getToken()); + Assert.assertEquals("Alice", ei.getValue()); + Assert.assertEquals(ti, ei.getTextIndex()); + Assert.assertEquals(pi, ei.getProcessedIndex()); + Assert.assertEquals("NAME", ei.getEntity()); + Assert.assertEquals(scores, ei.getScores()); + } + + @Test + public void testTokenFormatGetters() { + TokenFormat tf = TokenFormat.builder() + .vaultToken(detectEntities) + .entityUniqueCounter(detectEntities) + .entityOnly(detectEntities) + .build(); + Assert.assertEquals(detectEntities, tf.getVaultToken()); + Assert.assertEquals(detectEntities, tf.getEntityUniqueCounter()); + Assert.assertEquals(detectEntities, tf.getEntityOnly()); + Assert.assertNotNull(tf.getDefault()); + } + + @Test + public void testTokenFormatDefaultTypeNull() { + TokenFormat tf = TokenFormat.builder().defaultType(null).build(); + Assert.assertEquals(com.skyflow.enums.TokenType.ENTITY_UNIQUE_COUNTER, tf.getDefault()); + } + + @Test + public void testTokenFormatDefaultTypeNonNull() { + TokenFormat tf = TokenFormat.builder() + .defaultType(com.skyflow.enums.TokenType.VAULT_TOKEN) + .build(); + Assert.assertEquals(com.skyflow.enums.TokenType.VAULT_TOKEN, tf.getDefault()); + } } diff --git a/src/test/java/com/skyflow/vault/detect/ReidentifyTextTests.java b/src/test/java/com/skyflow/vault/detect/ReidentifyTextTests.java index e96a6422..63698c22 100644 --- a/src/test/java/com/skyflow/vault/detect/ReidentifyTextTests.java +++ b/src/test/java/com/skyflow/vault/detect/ReidentifyTextTests.java @@ -146,4 +146,12 @@ public void testReidentifyResponse() { Assert.fail(INVALID_EXCEPTION_THROWN); } } + + @Test + public void testReidentifyResponseToString() { + ReidentifyTextResponse response = new ReidentifyTextResponse(text); + String json = response.toString(); + Assert.assertNotNull(json); + Assert.assertTrue(json.contains(text)); + } } \ No newline at end of file diff --git a/src/test/java/com/skyflow/vault/tokens/DetokenizeTests.java b/src/test/java/com/skyflow/vault/tokens/DetokenizeTests.java index 43aad475..0d1cf5a2 100644 --- a/src/test/java/com/skyflow/vault/tokens/DetokenizeTests.java +++ b/src/test/java/com/skyflow/vault/tokens/DetokenizeTests.java @@ -160,6 +160,66 @@ public void testRedactionAndContinueOnErrorInDetokenizeRequestValidations() { } } + @Test + public void testDetokenizeDataNullRedactionTypeDefaultsToDefault() { + DetokenizeData data = new DetokenizeData("tok-null-redaction", null); + Assert.assertEquals(RedactionType.DEFAULT, data.getRedactionType()); + Assert.assertEquals("tok-null-redaction", data.getToken()); + } + + @Test + public void testDetokenizeRecordResponseNullValue() { + V1DetokenizeRecordResponse record = V1DetokenizeRecordResponse.builder() + .token("tok-null-val") + .build(); + DetokenizeRecordResponse response = new DetokenizeRecordResponse(record); + Assert.assertNull(response.getValue()); + Assert.assertNull(response.getType()); + Assert.assertNull(response.getRequestId()); + } + + @Test + public void testDetokenizeRecordResponseNoneValueTypeBecomesNull() { + V1DetokenizeRecordResponse record = V1DetokenizeRecordResponse.builder() + .token("tok-none-type") + .value("some-value") + .valueType(DetokenizeRecordResponseValueType.NONE) + .build(); + DetokenizeRecordResponse response = new DetokenizeRecordResponse(record); + Assert.assertNull("NONE valueType should be normalised to null", response.getType()); + Assert.assertEquals("some-value", response.getValue()); + } + + @Test + public void testDetokenizeRequestBuilderContinueOnErrorTrue() { + try { + ArrayList data = new ArrayList<>(); + data.add(new DetokenizeData("tok-true", RedactionType.PLAIN_TEXT)); + DetokenizeRequest request = DetokenizeRequest.builder() + .detokenizeData(data) + .continueOnError(true) + .build(); + Assert.assertTrue(request.getContinueOnError()); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testDetokenizeRequestBuilderDownloadUrlNull() { + try { + ArrayList data = new ArrayList<>(); + data.add(new DetokenizeData("tok-dl-null", RedactionType.DEFAULT)); + DetokenizeRequest request = DetokenizeRequest.builder() + .detokenizeData(data) + .downloadUrl(null) + .build(); + Assert.assertNull(request.getDownloadUrl()); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + @Test public void testDetokenizeResponse() { try {