From c74d242c84f478840e414abf384411aa778e5eff Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Wed, 9 Sep 2026 17:52:34 +0530 Subject: [PATCH 1/4] SK-3131 update samples --- .../java/com/example/vault/DeleteExample.java | 16 ++++--- .../com/example/vault/DetokenizeExample.java | 2 +- .../java/com/example/vault/GetExample.java | 25 ++++++----- .../java/com/example/vault/InsertExample.java | 42 +++++++++---------- .../java/com/example/vault/QueryExample.java | 2 +- .../java/com/example/vault/UpdateExample.java | 6 ++- 6 files changed, 51 insertions(+), 42 deletions(-) diff --git a/flowvault/samples/src/main/java/com/example/vault/DeleteExample.java b/flowvault/samples/src/main/java/com/example/vault/DeleteExample.java index 224e0ac9..347b9557 100644 --- a/flowvault/samples/src/main/java/com/example/vault/DeleteExample.java +++ b/flowvault/samples/src/main/java/com/example/vault/DeleteExample.java @@ -3,12 +3,11 @@ import com.skyflow.Skyflow; import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; +import com.skyflow.enums.CustomHeaderKey; import com.skyflow.enums.Env; import com.skyflow.enums.LogLevel; import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.DeleteRequest; -import com.skyflow.vault.data.DeleteResponse; -import com.skyflow.vault.data.DeleteResponseRecord; +import com.skyflow.vault.data.*; import java.util.ArrayList; import java.util.List; @@ -45,6 +44,8 @@ public static void main(String[] args) { // Step 4: Prepare the skyflow IDs to delete. // Either ids or uniqueValues is required; specifying both fails validation. + // Running this actually removes the record — rerunning GetExample/UpdateExample + // against the same skyflowId afterward will then fail, since it's gone. List ids = new ArrayList<>(); ids.add(""); @@ -54,7 +55,12 @@ public static void main(String[] args) { .ids(ids) .build(); - DeleteResponse response = skyflowClient.vault().delete(request); + DeleteOptions options = DeleteOptions.builder() + .interceptor(ctx -> { + ctx.addHeader(CustomHeaderKey.REQUEST_ID_HEADER, "DeleteOptions"); // pass the request id here + }) + .build(); + DeleteResponse response = skyflowClient.vault().delete(request, options); // Step 6: Read the outcome. A record succeeded when its error is null. for (DeleteResponseRecord record : response.getRecords()) { @@ -66,7 +72,7 @@ public static void main(String[] args) { } } catch (SkyflowException e) { // Step 7: Handle any errors that occur during the process - System.err.println("Error in delete operation:\t" + e.getMessage()); + System.err.println("Error in delete operation:\t" + e); } } } diff --git a/flowvault/samples/src/main/java/com/example/vault/DetokenizeExample.java b/flowvault/samples/src/main/java/com/example/vault/DetokenizeExample.java index 62068da7..4f077223 100644 --- a/flowvault/samples/src/main/java/com/example/vault/DetokenizeExample.java +++ b/flowvault/samples/src/main/java/com/example/vault/DetokenizeExample.java @@ -48,7 +48,7 @@ public static void main(String[] args) { List tokenGroupRedactions = new ArrayList<>(); tokenGroupRedactions.add(TokenGroupRedactions.builder() .tokenGroupName("") - .redaction("PLAIN_TEXT") + .redaction("plain_text") .build()); // Step 5: Build and execute the detokenize request diff --git a/flowvault/samples/src/main/java/com/example/vault/GetExample.java b/flowvault/samples/src/main/java/com/example/vault/GetExample.java index ad7d86e2..b9c539b2 100644 --- a/flowvault/samples/src/main/java/com/example/vault/GetExample.java +++ b/flowvault/samples/src/main/java/com/example/vault/GetExample.java @@ -36,7 +36,8 @@ public static void main(String[] args) { // Step 2: Configure the vault with required parameters VaultConfig vaultConfig = new VaultConfig(); vaultConfig.setVaultId(""); - vaultConfig.setClusterId(""); + vaultConfig.setVaultUrl(""); +// vaultConfig.setClusterId(""); vaultConfig.setEnv(Env.PROD); vaultConfig.setCredentials(credentials); @@ -48,12 +49,12 @@ public static void main(String[] args) { // Step 4: Prepare the skyflow IDs to fetch and any column redactions ArrayList ids = new ArrayList<>(); - ids.add(""); - + ids.add(""); + ids.add(""); List columnRedactions = new ArrayList<>(); columnRedactions.add(ColumnRedactions.builder() .columnName("") - .redaction("PLAIN_TEXT") + .redaction("plain_text") .build()); // Step 5: Build and execute the get request @@ -68,16 +69,18 @@ public static void main(String[] args) { // Step 6: Read the fetched records for (GetResponseRecord record : response.getRecords()) { System.out.printf("get: %s -> skyflowId=%s%n", record.getTableName(), record.getSkyflowId()); - for (Map.Entry> column : record.getTokens().entrySet()) { - for (Token token : column.getValue()) { - System.out.printf(" %s[%s] -> %s%n", - column.getKey(), token.getTokenGroupName(), token.getToken()); - } - } + System.out.println("dta"+ record.getData()); + +// for (Map.Entry> column : record.getTokens().entrySet()) { +// for (Token token : column.getValue()) { +// System.out.printf(" %s[%s] -> %s%n", +// column.getKey(), token.getTokenGroupName(), token.getToken()); +// } +// } } } catch (SkyflowException e) { // Step 7: Handle any errors that occur during the process - System.err.println("Error in get operation:\t" + e.getMessage()); + System.err.println("Error in get operation:\t" + e); } } } diff --git a/flowvault/samples/src/main/java/com/example/vault/InsertExample.java b/flowvault/samples/src/main/java/com/example/vault/InsertExample.java index 3afcf813..0725b409 100644 --- a/flowvault/samples/src/main/java/com/example/vault/InsertExample.java +++ b/flowvault/samples/src/main/java/com/example/vault/InsertExample.java @@ -3,14 +3,11 @@ import com.skyflow.Skyflow; import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; +import com.skyflow.enums.CustomHeaderKey; import com.skyflow.enums.Env; import com.skyflow.enums.LogLevel; import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.InsertRequest; -import com.skyflow.vault.data.InsertRequestRecord; -import com.skyflow.vault.data.InsertResponse; -import com.skyflow.vault.data.InsertResponseRecord; -import com.skyflow.vault.data.Token; +import com.skyflow.vault.data.*; import java.util.ArrayList; import java.util.HashMap; @@ -50,6 +47,7 @@ public static void main(String[] args) { InsertRequestRecord record = InsertRequestRecord.builder() .data(data) + .tableName("") .build(); List records = new ArrayList<>(); @@ -57,30 +55,30 @@ public static void main(String[] args) { // Step 5: Build and execute the insert request InsertRequest request = InsertRequest.builder() - .tableName("") +// .tableName("") .records(records) .build(); + InsertOptions options = InsertOptions.builder() + .interceptor(ctx -> { + ctx.addHeader(CustomHeaderKey.REQUEST_ID_HEADER, "demo"); // pass the request id here + }) + .build(); + InsertResponse response = skyflowClient.vault().insert(request, options); - InsertResponse response = skyflowClient.vault().insert(request); - - // Step 6: Read the outcome. A record succeeded when its error is null. + // Step 6: Print every field on each response record. for (InsertResponseRecord insertedRecord : response.getRecords()) { - if (insertedRecord.getError() == null) { - System.out.printf("insert: %s -> skyflowId=%s%n", - insertedRecord.getTableName(), insertedRecord.getSkyflowId()); - for (Map.Entry> column : insertedRecord.getTokens().entrySet()) { - for (Token token : column.getValue()) { - System.out.printf(" %s[%s] -> %s%n", - column.getKey(), token.getTokenGroupName(), token.getToken()); - } - } - } else { - System.out.printf("insert failed (%d): %s%n", insertedRecord.getHttpCode(), insertedRecord.getError()); - } + System.out.println("tableName:\t" + insertedRecord.getTableName()); + System.out.println("skyflowId:\t" + insertedRecord.getSkyflowId()); + System.out.println("tokens:\t\t" + insertedRecord.getTokens()); + System.out.println("data:\t\t" + insertedRecord.getData()); + System.out.println("hashedData:\t" + insertedRecord.getHashedData()); + System.out.println("httpCode:\t" + insertedRecord.getHttpCode()); + System.out.println("error:\t\t" + insertedRecord.getError()); +// System.out.println("request id " +insertedRecord.ge); } } catch (SkyflowException e) { // Step 7: Handle any errors that occur during the process - System.err.println("Error in insert operation:\t" + e.getMessage()); + System.err.println("Error in insert operation:\t" + e); } } } diff --git a/flowvault/samples/src/main/java/com/example/vault/QueryExample.java b/flowvault/samples/src/main/java/com/example/vault/QueryExample.java index 8879a250..4dd2e61e 100644 --- a/flowvault/samples/src/main/java/com/example/vault/QueryExample.java +++ b/flowvault/samples/src/main/java/com/example/vault/QueryExample.java @@ -47,7 +47,7 @@ public static void main(String[] args) { for (QueryResponseRecord record : response.getRecords()) { System.out.println("query row: " + record.getData()); } - System.out.println("columns: " + response.getColumns()); + System.out.println("columns: " + (response.getMetadata() != null ? response.getMetadata().getColumns() : null)); } catch (SkyflowException e) { // Step 6: Handle any errors that occur during the process System.err.println("Error in query operation:\t" + e.getMessage()); diff --git a/flowvault/samples/src/main/java/com/example/vault/UpdateExample.java b/flowvault/samples/src/main/java/com/example/vault/UpdateExample.java index a74907da..9b91b348 100644 --- a/flowvault/samples/src/main/java/com/example/vault/UpdateExample.java +++ b/flowvault/samples/src/main/java/com/example/vault/UpdateExample.java @@ -5,6 +5,7 @@ import com.skyflow.config.VaultConfig; import com.skyflow.enums.Env; import com.skyflow.enums.LogLevel; +import com.skyflow.enums.UpdateType; import com.skyflow.errors.SkyflowException; import com.skyflow.vault.data.UpdateRequest; import com.skyflow.vault.data.UpdateRequestRecord; @@ -55,11 +56,11 @@ public static void main(String[] args) { records.add(updateRecord); // Step 5: Build and execute the update request. - // updateType accepts "UPDATE" (default) or "REPLACE". + // updateType accepts UpdateType.UPDATE (default) or UpdateType.REPLACE. UpdateRequest request = UpdateRequest.builder() .tableName("") .records(records) - .updateType("REPLACE") + .updateType(UpdateType.REPLACE) .build(); UpdateResponse response = skyflowClient.vault().update(request); @@ -67,6 +68,7 @@ public static void main(String[] args) { // Step 6: Read the outcome. A record succeeded when its error is null. for (UpdateResponseRecord record : response.getRecords()) { if (record.getError() == null) { + System.out.println("data" + record.getTokens()); System.out.printf("update: %s -> skyflowId=%s%n", record.getTableName(), record.getSkyflowId()); } else { System.out.printf("update failed (%d): %s%n", record.getHttpCode(), record.getError()); From c4caac3fe3efadcba35b83adeab85360e45c520d Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Wed, 9 Sep 2026 18:56:23 +0530 Subject: [PATCH 2/4] SK-3131 update the response strcuture --- .../main/java/com/skyflow/utils/Utils.java | 125 ++++++++++++++++++ .../vault/controller/VaultController.java | 35 +++++ .../controller/VaultControllerTests.java | 78 +++++++++++ 3 files changed, 238 insertions(+) diff --git a/flowvault/src/main/java/com/skyflow/utils/Utils.java b/flowvault/src/main/java/com/skyflow/utils/Utils.java index c8d72b77..67bf90fc 100644 --- a/flowvault/src/main/java/com/skyflow/utils/Utils.java +++ b/flowvault/src/main/java/com/skyflow/utils/Utils.java @@ -539,6 +539,131 @@ public static ErrorRecord createErrorRecord(Map recordMap, int i return err; } + // ── Unary "records"-shaped exception fallback ───────────────────────────── + // + // A unary call's own (only) record can fail outright — e.g. an invalid column on the sole + // record in an update/insert/get/delete request — and the vault reflects that as the overall + // HTTP status, so the generated client throws ApiClientApiException instead of returning a + // normal response body. When the exception body still has the familiar per-record shape + // ({"records": [...]} for insert/update/get/delete, {"response": [...]} for detokenize), the + // failure belongs on the response the same way a 200 partial-success does — not as a thrown + // exception. Each handler below returns null when the body doesn't match that shape, so the + // caller falls back to throwing a SkyflowException as before. + + /** Record maps under {@code key} in an exception body, or null if the shape doesn't match. */ + private static List> extractExceptionRecords(ApiClientApiException apiException, String key) { + Object rawBody = apiException.body(); + if (!(rawBody instanceof Map)) { + return null; + } + Object recordsField = ((Map) rawBody).get(key); + if (!(recordsField instanceof List)) { + return null; + } + List> records = new ArrayList<>(); + for (Object recordObj : (List) recordsField) { + if (recordObj instanceof Map) { + //noinspection unchecked + records.add((Map) recordObj); + } + } + return records.isEmpty() ? null : records; + } + + public static InsertResponse handleInsertRequestException(ApiClientApiException apiException) { + List> recordMaps = extractExceptionRecords(apiException, "records"); + if (recordMaps == null) { + return null; + } + String requestId = extractRequestId(apiException.headers()); + List records = new ArrayList<>(); + for (Map recordMap : recordMaps) { + records.add(new InsertResponseRecord( + readString(recordMap, "tableName"), + readString(recordMap, "skyflowID"), + null, null, null, + readHttpCode(recordMap, apiException.statusCode()), + readErrorMessage(recordMap), + requestId)); + } + return new InsertResponse(records); + } + + public static UpdateResponse handleUpdateRequestException(ApiClientApiException apiException) { + List> recordMaps = extractExceptionRecords(apiException, "records"); + if (recordMaps == null) { + return null; + } + String requestId = extractRequestId(apiException.headers()); + List records = new ArrayList<>(); + for (Map recordMap : recordMaps) { + records.add(new UpdateResponseRecord( + readString(recordMap, "tableName"), + readString(recordMap, "skyflowID"), + null, null, null, + readHttpCode(recordMap, apiException.statusCode()), + readErrorMessage(recordMap), + requestId)); + } + return new UpdateResponse(records); + } + + public static GetResponse handleGetRequestException(ApiClientApiException apiException) { + List> recordMaps = extractExceptionRecords(apiException, "records"); + if (recordMaps == null) { + return null; + } + String requestId = extractRequestId(apiException.headers()); + List records = new ArrayList<>(); + for (Map recordMap : recordMaps) { + records.add(new GetResponseRecord( + readString(recordMap, "tableName"), + readString(recordMap, "skyflowID"), + null, null, null, + readHttpCode(recordMap, apiException.statusCode()), + readErrorMessage(recordMap), + requestId)); + } + return new GetResponse(records); + } + + public static DeleteResponse handleDeleteRequestException(ApiClientApiException apiException) { + List> recordMaps = extractExceptionRecords(apiException, "records"); + if (recordMaps == null) { + return null; + } + String requestId = extractRequestId(apiException.headers()); + List records = new ArrayList<>(); + for (Map recordMap : recordMaps) { + records.add(new DeleteResponseRecord( + readString(recordMap, "skyflowID"), + readHttpCode(recordMap, apiException.statusCode()), + readErrorMessage(recordMap), + requestId)); + } + return new DeleteResponse(records); + } + + public static DetokenizeResponse handleDetokenizeRequestException(ApiClientApiException apiException) { + List> recordMaps = extractExceptionRecords(apiException, "response"); + if (recordMaps == null) { + return null; + } + String requestId = extractRequestId(apiException.headers()); + List records = new ArrayList<>(); + for (Map recordMap : recordMaps) { + records.add(new DetokenizeResponseRecord( + readString(recordMap, "token"), + null, + readString(recordMap, "tokenGroupName"), + null, + readHttpCode(recordMap, apiException.statusCode()), + readErrorMessage(recordMap), + requestId)); + } + return new DetokenizeResponse(records); + } + // Errors are parsed into ErrorRecord (shared with the other bulk ops), then projected onto // the unified BulkInsertResponseRecord shape that bulk insert now returns. public static List handleBulkInsertBatchException( diff --git a/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java b/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java index 897c7269..c0ab02cc 100644 --- a/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java +++ b/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java @@ -142,6 +142,13 @@ public InsertResponse insert(InsertRequest insertRequest, InsertOptions options) LogUtil.printInfoLog(InfoLogs.INSERT_REQUEST_RESOLVED.getLog()); return formattedResponse; } catch (ApiClientApiException e) { + // The lone record in a unary request can fail outright, which the vault reflects as + // the overall HTTP status. If the body still carries the usual per-record shape, + // surface it on the response like a 200 partial success would, not as an exception. + InsertResponse fallback = Utils.handleInsertRequestException(e); + if (fallback != null) { + return fallback; + } String bodyString = gson.toJson(e.body()); LogUtil.printErrorLog(ErrorLogs.INSERT_RECORDS_REJECTED.getLog()); throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); @@ -256,6 +263,13 @@ public DetokenizeResponse detokenize(DetokenizeRequest detokenizeRequest, Detoke LogUtil.printInfoLog(InfoLogs.DETOKENIZE_REQUEST_RESOLVED.getLog()); return formattedResponse; } catch (ApiClientApiException e) { + // The lone record in a unary request can fail outright, which the vault reflects as + // the overall HTTP status. If the body still carries the usual per-record shape, + // surface it on the response like a 200 partial success would, not as an exception. + DetokenizeResponse fallback = Utils.handleDetokenizeRequestException(e); + if (fallback != null) { + return fallback; + } String bodyString = gson.toJson(e.body()); LogUtil.printErrorLog(ErrorLogs.DETOKENIZE_REQUEST_REJECTED.getLog()); throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); @@ -375,6 +389,13 @@ public DeleteResponse delete(DeleteRequest deleteRequest, DeleteOptions options) LogUtil.printInfoLog(InfoLogs.DELETE_REQUEST_RESOLVED.getLog()); return formattedResponse; } catch (ApiClientApiException e) { + // The lone record in a unary request can fail outright, which the vault reflects as + // the overall HTTP status. If the body still carries the usual per-record shape, + // surface it on the response like a 200 partial success would, not as an exception. + DeleteResponse fallback = Utils.handleDeleteRequestException(e); + if (fallback != null) { + return fallback; + } String bodyString = gson.toJson(e.body()); LogUtil.printErrorLog(ErrorLogs.DELETE_REQUEST_REJECTED.getLog()); throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); @@ -610,6 +631,13 @@ public UpdateResponse update(UpdateRequest updateRequest, UpdateOptions options) LogUtil.printInfoLog(InfoLogs.UPDATE_REQUEST_RESOLVED.getLog()); return formattedResponse; } catch (ApiClientApiException e) { + // The lone record in a unary request can fail outright, which the vault reflects as + // the overall HTTP status. If the body still carries the usual per-record shape, + // surface it on the response like a 200 partial success would, not as an exception. + UpdateResponse fallback = Utils.handleUpdateRequestException(e); + if (fallback != null) { + return fallback; + } String bodyString = gson.toJson(e.body()); LogUtil.printErrorLog(ErrorLogs.UPDATE_REQUEST_REJECTED.getLog()); throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); @@ -645,6 +673,13 @@ public GetResponse get(GetRequest getRequest, GetOptions options) throws Skyflow LogUtil.printInfoLog(InfoLogs.GET_REQUEST_RESOLVED.getLog()); return formattedResponse; } catch (ApiClientApiException e) { + // The lone record in a unary request can fail outright, which the vault reflects as + // the overall HTTP status. If the body still carries the usual per-record shape, + // surface it on the response like a 200 partial success would, not as an exception. + GetResponse fallback = Utils.handleGetRequestException(e); + if (fallback != null) { + return fallback; + } String bodyString = gson.toJson(e.body()); LogUtil.printErrorLog(ErrorLogs.GET_REQUEST_REJECTED.getLog()); throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); diff --git a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java index bc67e377..1cf59789 100644 --- a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java @@ -312,6 +312,84 @@ public void testUpdate_success() throws Exception { Assert.assertEquals("sky-1", response.getRecords().get(0).getSkyflowId()); } + // Regression: a single failing record in a unary call can make the vault reflect the failure + // as the overall HTTP status (here 400), so the generated client throws ApiClientApiException + // instead of returning a normal body. When that exception body still has the familiar + // per-record "records" shape, it must land on the UpdateResponse like a 200 partial success + // would - not surface as a thrown SkyflowException. + @Test + public void testUpdate_recordLevelFailureReflectedAsHttpErrorStillReturnsResponse() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + Map failedRecord = new HashMap<>(); + failedRecord.put("skyflowID", null); + failedRecord.put("tokens", null); + failedRecord.put("data", null); + failedRecord.put("hashedData", null); + failedRecord.put("error", "UPDATE failed. Column card_numbe is invalid. Specify a valid column."); + failedRecord.put("httpCode", 400); + failedRecord.put("tableName", ""); + Map responseBody = new HashMap<>(); + responseBody.put("records", Collections.singletonList(failedRecord)); + + when(mockRaw.update(any(), any())) + .thenThrow(new ApiClientApiException("Error with status code 400", 400, responseBody)); + + VaultController controller = createControllerWithMock(mockApi); + + UpdateRequestRecord updateRecord = UpdateRequestRecord.builder().skyflowId("sky-1").build(); + UpdateRequest request = UpdateRequest.builder() + .tableName("table1") + .records(Collections.singletonList(updateRecord)) + .build(); + + UpdateResponse response = controller.update(request); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals("UPDATE failed. Column card_numbe is invalid. Specify a valid column.", + response.getRecords().get(0).getError()); + Assert.assertEquals(400, response.getRecords().get(0).getHttpCode()); + Assert.assertNull(response.getRecords().get(0).getSkyflowId()); + } + + // Regression: a genuine whole-request API error (e.g. vault not found) has no "records" key + // at all, so the fallback added above must not swallow it - it still has to throw. + @Test + public void testUpdate_wholeRequestApiErrorStillThrows() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + Map errorBody = new HashMap<>(); + errorBody.put("grpc_code", 5); + errorBody.put("http_code", 404); + errorBody.put("message", "Invalid request. Vault not found for vaultID: vault123. Specify a valid vaultID."); + errorBody.put("http_status", "Not Found"); + errorBody.put("details", new ArrayList<>()); + Map responseBody = new HashMap<>(); + responseBody.put("error", errorBody); + + when(mockRaw.update(any(), any())) + .thenThrow(new ApiClientApiException("Error with status code 404", 404, responseBody)); + + VaultController controller = createControllerWithMock(mockApi); + + UpdateRequestRecord updateRecord = UpdateRequestRecord.builder().skyflowId("sky-1").build(); + UpdateRequest request = UpdateRequest.builder() + .tableName("table1") + .records(Collections.singletonList(updateRecord)) + .build(); + + try { + controller.update(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals( + "Invalid request. Vault not found for vaultID: vault123. Specify a valid vaultID.", + e.getMessage()); + Assert.assertEquals(404, e.getHttpCode()); + } + } + @Test public void testUpdate_invalidRequestThrowsSkyflowException() throws Exception { ApiClient mockApi = Mockito.mock(ApiClient.class); From edec3005bfbe2915155032b1322a153a2b38cfb2 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Wed, 9 Sep 2026 19:06:44 +0530 Subject: [PATCH 3/4] SK-3131 update tests --- .../skyflow/errors/SkyflowExceptionTest.java | 16 ++ .../java/com/skyflow/utils/UtilsTests.java | 227 ++++++++++++++++++ .../controller/VaultControllerTests.java | 4 +- 3 files changed, 245 insertions(+), 2 deletions(-) diff --git a/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java b/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java index 1fb01e6e..eb7e0706 100644 --- a/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java +++ b/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java @@ -192,6 +192,22 @@ public void testNonJsonBodyFallsBackToRawBodyAsMessage() { Assert.assertEquals("plain text error response", ex.getMessage()); } + @Test + public void testJsonBodyWithGenericApiErrorEnvelopeStillWorks() { + Map> headers = new HashMap<>(); + String json = "{\"error\":{\"grpc_code\":5,\"http_code\":404," + + "\"message\":\"Invalid request. Vault not found for vaultID: vault123. " + + "Specify a valid vaultID.\",\"http_status\":\"Not Found\",\"details\":[]}}"; + SkyflowException ex = new SkyflowException(404, new RuntimeException("fail"), headers, json); + Assert.assertEquals("Invalid request. Vault not found for vaultID: vault123. " + + "Specify a valid vaultID.", ex.getMessage()); + Assert.assertEquals(Integer.valueOf(5), ex.getGrpcCode()); + Assert.assertEquals("Not Found", ex.getHttpStatus()); + Assert.assertEquals(404, ex.getHttpCode()); + Assert.assertNotNull(ex.getDetails()); + Assert.assertEquals(0, ex.getDetails().size()); + } + @Test public void testNullBodyNullCauseMessageFallsBackToErrorOccurred() { Map> headers = new HashMap<>(); diff --git a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java index 80ad7429..f74348c9 100644 --- a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java +++ b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java @@ -48,15 +48,19 @@ import com.skyflow.vault.data.ColumnRedactions; import com.skyflow.vault.data.DeleteRequest; import com.skyflow.vault.data.DeleteResponse; +import com.skyflow.vault.data.DeleteResponseRecord; import com.skyflow.vault.data.DetokenizeRequest; import com.skyflow.vault.data.DetokenizeResponse; +import com.skyflow.vault.data.DetokenizeResponseRecord; import com.skyflow.vault.data.ErrorRecord; import com.skyflow.vault.data.GetRequest; import com.skyflow.vault.data.GetRequestRecord; import com.skyflow.vault.data.GetResponse; +import com.skyflow.vault.data.GetResponseRecord; import com.skyflow.vault.data.InsertRequestRecord; import com.skyflow.vault.data.InsertRequest; import com.skyflow.vault.data.InsertResponse; +import com.skyflow.vault.data.InsertResponseRecord; import com.skyflow.vault.data.QueryRequest; import com.skyflow.vault.data.QueryResponse; import com.skyflow.vault.data.TokenGroupRedactions; @@ -2498,4 +2502,227 @@ public void testDeleteTokensErrorRecord_keepsTheErrorAndEchoedToken() { Assert.assertEquals("Token not found", record.getError()); Assert.assertEquals("tok-1", record.getToken()); } + + // ── handleInsertRequestException / handleUpdateRequestException / handleGetRequestException / + // handleDeleteRequestException / handleDetokenizeRequestException ────────────────────────── + // + // These convert a unary call's ApiClientApiException back into a normal response when the + // body still has the familiar per-record shape. extractExceptionRecords is shared by all five, + // so its null/malformed-input robustness is exercised thoroughly once here (via insert) and + // the remaining handlers each get a focused shape-guard + happy-path check. + + @Test + public void testHandleInsertRequestException_nullBodyReturnsNull() { + ApiClientApiException ex = new ApiClientApiException("boom", 400, null); + Assert.assertNull(Utils.handleInsertRequestException(ex)); + } + + @Test + public void testHandleInsertRequestException_nonMapBodyReturnsNull() { + ApiClientApiException ex = new ApiClientApiException("boom", 400, "plain text body"); + Assert.assertNull(Utils.handleInsertRequestException(ex)); + } + + @Test + public void testHandleInsertRequestException_missingRecordsKeyReturnsNull() { + Map body = new HashMap<>(); + body.put("message", "no records key here"); + ApiClientApiException ex = new ApiClientApiException("boom", 400, body); + Assert.assertNull(Utils.handleInsertRequestException(ex)); + } + + @Test + public void testHandleInsertRequestException_recordsValueNotAListReturnsNull() { + Map body = new HashMap<>(); + body.put("records", "not a list"); + ApiClientApiException ex = new ApiClientApiException("boom", 400, body); + Assert.assertNull(Utils.handleInsertRequestException(ex)); + } + + @Test + public void testHandleInsertRequestException_emptyRecordsListReturnsNull() { + Map body = new HashMap<>(); + body.put("records", new ArrayList<>()); + ApiClientApiException ex = new ApiClientApiException("boom", 400, body); + Assert.assertNull(Utils.handleInsertRequestException(ex)); + } + + @Test + public void testHandleInsertRequestException_recordsWithOnlyNonMapElementsReturnsNull() { + Map body = new HashMap<>(); + body.put("records", Arrays.asList("not-a-map", 123, null)); + ApiClientApiException ex = new ApiClientApiException("boom", 400, body); + Assert.assertNull(Utils.handleInsertRequestException(ex)); + } + + @Test + public void testHandleInsertRequestException_mixedValidAndInvalidElementsKeepsOnlyValidOnes() { + Map validRecord = new HashMap<>(); + validRecord.put("error", "bad column"); + validRecord.put("httpCode", 400); + Map body = new HashMap<>(); + body.put("records", Arrays.asList("not-a-map", validRecord)); + ApiClientApiException ex = new ApiClientApiException("boom", 400, body); + + InsertResponse response = Utils.handleInsertRequestException(ex); + Assert.assertNotNull(response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals("bad column", response.getRecords().get(0).getError()); + } + + @Test + public void testHandleInsertRequestException_missingHttpCodeFallsBackToExceptionStatusCode() { + Map record = new HashMap<>(); + record.put("error", "bad column"); + Map body = new HashMap<>(); + body.put("records", Collections.singletonList(record)); + ApiClientApiException ex = new ApiClientApiException("boom", 422, body); + + InsertResponse response = Utils.handleInsertRequestException(ex); + Assert.assertNotNull(response); + Assert.assertEquals(422, response.getRecords().get(0).getHttpCode()); + } + + @Test + public void testHandleInsertRequestException_missingErrorFallsBackToUnknownError() { + Map record = new HashMap<>(); + record.put("httpCode", 400); + Map body = new HashMap<>(); + body.put("records", Collections.singletonList(record)); + ApiClientApiException ex = new ApiClientApiException("boom", 400, body); + + InsertResponse response = Utils.handleInsertRequestException(ex); + Assert.assertNotNull(response); + Assert.assertEquals("Unknown error", response.getRecords().get(0).getError()); + } + + @Test + public void testHandleInsertRequestException_validRecordsShapePopulatesResponseAndRequestId() { + Map record = new HashMap<>(); + record.put("skyflowID", null); + record.put("tableName", "table5"); + record.put("error", "INSERT failed. Column card_numbe is invalid. Specify a valid column."); + record.put("httpCode", 400); + Map body = new HashMap<>(); + body.put("records", Collections.singletonList(record)); + ApiClientApiException ex = new ApiClientApiException("Error with status code 400", 400, body); + + InsertResponse response = Utils.handleInsertRequestException(ex); + Assert.assertNotNull(response); + Assert.assertEquals(1, response.getRecords().size()); + InsertResponseRecord result = response.getRecords().get(0); + Assert.assertEquals("table5", result.getTableName()); + Assert.assertNull(result.getSkyflowId()); + Assert.assertEquals("INSERT failed. Column card_numbe is invalid. Specify a valid column.", result.getError()); + Assert.assertEquals(400, result.getHttpCode()); + } + + @Test + public void testHandleUpdateRequestException_nonRecordsShapeReturnsNull() { + Map errorBody = new HashMap<>(); + errorBody.put("message", "whole request failed"); + Map body = new HashMap<>(); + body.put("error", errorBody); + ApiClientApiException ex = new ApiClientApiException("boom", 404, body); + Assert.assertNull(Utils.handleUpdateRequestException(ex)); + } + + @Test + public void testHandleUpdateRequestException_validRecordsShapePopulatesResponse() { + Map record = new HashMap<>(); + record.put("error", "UPDATE failed. Column card_numbe is invalid. Specify a valid column."); + record.put("httpCode", 400); + record.put("tableName", ""); + Map body = new HashMap<>(); + body.put("records", Collections.singletonList(record)); + ApiClientApiException ex = new ApiClientApiException("boom", 400, body); + + UpdateResponse response = Utils.handleUpdateRequestException(ex); + Assert.assertNotNull(response); + Assert.assertEquals("UPDATE failed. Column card_numbe is invalid. Specify a valid column.", + response.getRecords().get(0).getError()); + Assert.assertEquals(400, response.getRecords().get(0).getHttpCode()); + } + + @Test + public void testHandleGetRequestException_nonRecordsShapeReturnsNull() { + ApiClientApiException ex = new ApiClientApiException("boom", 500, "server error"); + Assert.assertNull(Utils.handleGetRequestException(ex)); + } + + @Test + public void testHandleGetRequestException_validRecordsShapePopulatesResponse() { + Map record = new HashMap<>(); + record.put("skyflowID", "sky-1"); + record.put("tableName", "table1"); + record.put("error", "GET failed. Record not found."); + record.put("httpCode", 404); + Map body = new HashMap<>(); + body.put("records", Collections.singletonList(record)); + ApiClientApiException ex = new ApiClientApiException("boom", 404, body); + + GetResponse response = Utils.handleGetRequestException(ex); + Assert.assertNotNull(response); + GetResponseRecord result = response.getRecords().get(0); + Assert.assertEquals("sky-1", result.getSkyflowId()); + Assert.assertEquals("table1", result.getTableName()); + Assert.assertEquals(404, result.getHttpCode()); + } + + @Test + public void testHandleDeleteRequestException_nonRecordsShapeReturnsNull() { + Map body = new HashMap<>(); + body.put("records", "not a list"); + ApiClientApiException ex = new ApiClientApiException("boom", 400, body); + Assert.assertNull(Utils.handleDeleteRequestException(ex)); + } + + @Test + public void testHandleDeleteRequestException_validRecordsShapePopulatesResponse() { + Map record = new HashMap<>(); + record.put("skyflowID", "sky-1"); + record.put("error", "DELETE failed. Record not found."); + record.put("httpCode", 404); + Map body = new HashMap<>(); + body.put("records", Collections.singletonList(record)); + ApiClientApiException ex = new ApiClientApiException("boom", 404, body); + + DeleteResponse response = Utils.handleDeleteRequestException(ex); + Assert.assertNotNull(response); + DeleteResponseRecord result = response.getRecords().get(0); + Assert.assertEquals("sky-1", result.getSkyflowId()); + Assert.assertEquals(Integer.valueOf(404), result.getHttpCode()); + } + + @Test + public void testHandleDetokenizeRequestException_recordsKeyIsWrongShapeForDetokenizeReturnsNull() { + // Detokenize's own wire key is "response", not "records" - a body shaped for the other + // unary ops must not be mistaken for a detokenize failure. + Map record = new HashMap<>(); + record.put("error", "some error"); + Map body = new HashMap<>(); + body.put("records", Collections.singletonList(record)); + ApiClientApiException ex = new ApiClientApiException("boom", 400, body); + Assert.assertNull(Utils.handleDetokenizeRequestException(ex)); + } + + @Test + public void testHandleDetokenizeRequestException_validResponseShapePopulatesResponse() { + Map record = new HashMap<>(); + record.put("token", "tok-1"); + record.put("tokenGroupName", "group1"); + record.put("error", "DETOKENIZE failed. Token not found."); + record.put("httpCode", 404); + Map body = new HashMap<>(); + body.put("response", Collections.singletonList(record)); + ApiClientApiException ex = new ApiClientApiException("boom", 404, body); + + DetokenizeResponse response = Utils.handleDetokenizeRequestException(ex); + Assert.assertNotNull(response); + DetokenizeResponseRecord result = response.getRecords().get(0); + Assert.assertEquals("tok-1", result.getToken()); + Assert.assertEquals("group1", result.getTokenGroupName()); + Assert.assertEquals("DETOKENIZE failed. Token not found.", result.getError()); + Assert.assertEquals(404, result.getHttpCode()); + } } diff --git a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java index 1cf59789..870cb5ce 100644 --- a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java @@ -327,7 +327,7 @@ public void testUpdate_recordLevelFailureReflectedAsHttpErrorStillReturnsRespons failedRecord.put("tokens", null); failedRecord.put("data", null); failedRecord.put("hashedData", null); - failedRecord.put("error", "UPDATE failed. Column card_numbe is invalid. Specify a valid column."); + failedRecord.put("error", "UPDATE failed. Column card_number is invalid. Specify a valid column."); failedRecord.put("httpCode", 400); failedRecord.put("tableName", ""); Map responseBody = new HashMap<>(); @@ -346,7 +346,7 @@ public void testUpdate_recordLevelFailureReflectedAsHttpErrorStillReturnsRespons UpdateResponse response = controller.update(request); Assert.assertEquals(1, response.getRecords().size()); - Assert.assertEquals("UPDATE failed. Column card_numbe is invalid. Specify a valid column.", + Assert.assertEquals("UPDATE failed. Column card_number is invalid. Specify a valid column.", response.getRecords().get(0).getError()); Assert.assertEquals(400, response.getRecords().get(0).getHttpCode()); Assert.assertNull(response.getRecords().get(0).getSkyflowId()); From 29217de84dd0a20066f33b4ba329301209a67291 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Wed, 9 Sep 2026 19:29:50 +0530 Subject: [PATCH 4/4] SK-3131 fix spell check --- flowvault/src/test/java/com/skyflow/utils/UtilsTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java index f74348c9..be3115d8 100644 --- a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java +++ b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java @@ -2601,7 +2601,7 @@ public void testHandleInsertRequestException_validRecordsShapePopulatesResponseA Map record = new HashMap<>(); record.put("skyflowID", null); record.put("tableName", "table5"); - record.put("error", "INSERT failed. Column card_numbe is invalid. Specify a valid column."); + record.put("error", "INSERT failed. Column card_number is invalid. Specify a valid column."); record.put("httpCode", 400); Map body = new HashMap<>(); body.put("records", Collections.singletonList(record)); @@ -2613,7 +2613,7 @@ public void testHandleInsertRequestException_validRecordsShapePopulatesResponseA InsertResponseRecord result = response.getRecords().get(0); Assert.assertEquals("table5", result.getTableName()); Assert.assertNull(result.getSkyflowId()); - Assert.assertEquals("INSERT failed. Column card_numbe is invalid. Specify a valid column.", result.getError()); + Assert.assertEquals("INSERT failed. Column card_number is invalid. Specify a valid column.", result.getError()); Assert.assertEquals(400, result.getHttpCode()); } @@ -2630,7 +2630,7 @@ public void testHandleUpdateRequestException_nonRecordsShapeReturnsNull() { @Test public void testHandleUpdateRequestException_validRecordsShapePopulatesResponse() { Map record = new HashMap<>(); - record.put("error", "UPDATE failed. Column card_numbe is invalid. Specify a valid column."); + record.put("error", "UPDATE failed. Column card_number is invalid. Specify a valid column."); record.put("httpCode", 400); record.put("tableName", ""); Map body = new HashMap<>(); @@ -2639,7 +2639,7 @@ public void testHandleUpdateRequestException_validRecordsShapePopulatesResponse( UpdateResponse response = Utils.handleUpdateRequestException(ex); Assert.assertNotNull(response); - Assert.assertEquals("UPDATE failed. Column card_numbe is invalid. Specify a valid column.", + Assert.assertEquals("UPDATE failed. Column card_number is invalid. Specify a valid column.", response.getRecords().get(0).getError()); Assert.assertEquals(400, response.getRecords().get(0).getHttpCode()); }