diff --git a/.cspell.json b/.cspell.json index cd9d7cf3..4c434d07 100644 --- a/.cspell.json +++ b/.cspell.json @@ -97,6 +97,7 @@ "unrecognised", "nocreds", "nodir", + "noextension", "detok", "ngrok", "obac", diff --git a/flowvault/README.md b/flowvault/README.md index f04ece9e..13778be3 100644 --- a/flowvault/README.md +++ b/flowvault/README.md @@ -562,34 +562,20 @@ Sample response: { "summary": { "totalTokens": 2, "totalTokenized": 1, "totalPartial": 0, "totalFailed": 1 }, "records": [ - { - "index": 0, - "value": "4111111111111111", - "tokens": [ - { "tokenGroupName": "card_number_cg", "token": "5479-4229-4622-1393", "httpCode": 200, "error": null, "requestId": null } - ] - }, - { - "index": 1, - "value": "john.doe@example.com", - "tokens": [ - { "tokenGroupName": "email_cg", "token": null, "httpCode": 400, "error": "Token group email_cg not found.", "requestId": "a1b2c3d4-..." } - ] - } + { "index": 0, "value": "4111111111111111", "tokenGroupName": "card_number_cg", "token": "5479-4229-4622-1393", "httpCode": 200, "error": null, "requestId": null }, + { "index": 1, "value": "john.doe@example.com", "tokenGroupName": "email_cg", "token": null, "httpCode": 400, "error": "Token group email_cg not found.", "requestId": "a1b2c3d4-..." } ] } ``` -Tokenize reports at **two** levels: one entry per input value in `records`, and inside each of those, one entry per requested token group in `tokens`. Because a single value can map to several token groups, the summary distinguishes fully tokenized values (`totalTokenized`), partially tokenized values where some groups succeeded and others failed (`totalPartial`), and fully failed values (`totalFailed`). The three always add up to `totalTokens`, which counts input values, not tokens produced. +`records` is flat: one entry per (value, token group) outcome, matching the API's own response shape. Because a single value can map to several token groups, several entries can share the same `index` — that's how the SDK tells you which input value an entry belongs to. The summary classifies by index rather than by entry: fully tokenized values (`totalTokenized`), partially tokenized values where some groups succeeded and others failed (`totalPartial`), and fully failed values (`totalFailed`). The three always add up to `totalTokens`, which counts input values, not entries. ```java for (BulkTokenizeResponseRecord record : tokenizeResponse.getRecords()) { - for (TokenizeResponseToken token : record.getTokens()) { - if (token.getError() == null) { - System.out.println(record.getValue() + " -> " + token.getTokenGroupName() + " = " + token.getToken()); - } else { - System.out.println(record.getValue() + " -> " + token.getTokenGroupName() + " failed: " + token.getError()); - } + if (record.getError() == null) { + System.out.println(record.getValue() + " -> " + record.getTokenGroupName() + " = " + record.getToken()); + } else { + System.out.println(record.getValue() + " -> " + record.getTokenGroupName() + " failed: " + record.getError()); } } ``` @@ -805,7 +791,7 @@ Every bulk response exposes `getSummary()` and `getRecords()`. The records list | `getError()` | failures only | Error message for this item. `null` means this item succeeded. | | `getRequestId()` | failures only | The `x-request-id` of the batch this item was in — quote it in support escalations. Items from the same batch share one id. | -The success payload sits alongside those fields on the same object: `getSkyflowId()`/`getTokens()`/`getData()` for insert (`getFields()` is deprecated — it returns the same data in its original, pre-typed `Map` shape, not `getTokens()`'s `Token` objects), `getValue()`/`getTokenGroupName()`/`getMetadata()` for detokenize, `getTokens()` for tokenize, `getToken()` for delete. +The success payload sits alongside those fields on the same object: `getSkyflowId()`/`getTokens()`/`getData()` for insert (`getFields()` is deprecated — it returns the same data in its original, pre-typed `Map` shape, not `getTokens()`'s `Token` objects), `getValue()`/`getTokenGroupName()`/`getMetadata()` for detokenize, `getValue()`/`getTokenGroupName()`/`getToken()` for tokenize, `getToken()` for delete. Summaries per operation: @@ -835,18 +821,16 @@ for (BulkInsertResponseRecord record : response.getRecords()) { } ``` -For tokenize, the check is one level deeper, because a single value can partially succeed: +Tokenize reports one entry per (value, token group) outcome, so a single value can partially succeed — several entries share its `index`: ```java for (BulkTokenizeResponseRecord record : tokenizeResponse.getRecords()) { - for (TokenizeResponseToken token : record.getTokens()) { - if (token.getError() == null) { - System.out.println(record.getIndex() + "/" + token.getTokenGroupName() - + " -> " + token.getToken()); - } else { - System.err.println(record.getIndex() + "/" + token.getTokenGroupName() - + " failed [" + token.getHttpCode() + "] " + token.getError()); - } + if (record.getError() == null) { + System.out.println(record.getIndex() + "/" + record.getTokenGroupName() + + " -> " + record.getToken()); + } else { + System.err.println(record.getIndex() + "/" + record.getTokenGroupName() + + " failed [" + record.getHttpCode() + "] " + record.getError()); } } ``` diff --git a/flowvault/api-report/skyflow-flowvault-java.baseline.jar b/flowvault/api-report/skyflow-flowvault-java.baseline.jar index ecdba540..1294715f 100644 Binary files a/flowvault/api-report/skyflow-flowvault-java.baseline.jar and b/flowvault/api-report/skyflow-flowvault-java.baseline.jar differ diff --git a/flowvault/pom.xml b/flowvault/pom.xml index 5662beb9..8f8e089f 100644 --- a/flowvault/pom.xml +++ b/flowvault/pom.xml @@ -1,4 +1,5 @@ + diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeAsync.java b/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeAsync.java index 762c523b..c8181162 100644 --- a/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeAsync.java +++ b/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeAsync.java @@ -10,7 +10,6 @@ import com.skyflow.vault.data.BulkTokenizeRequestRecord; import com.skyflow.vault.data.BulkTokenizeResponse; import com.skyflow.vault.data.BulkTokenizeResponseRecord; -import com.skyflow.vault.data.TokenizeResponseToken; import java.util.ArrayList; import java.util.List; @@ -83,15 +82,13 @@ public static void main(String[] args) { // Each value reports one entry per token group, so a value can partially succeed. // requestId identifies the API call an error came from and is set on failures only. for (BulkTokenizeResponseRecord record : response.getRecords()) { - for (TokenizeResponseToken token : record.getTokens()) { - if (token.getError() == null) { - System.out.printf("[%d] group '%s' -> %s%n", - record.getIndex(), token.getTokenGroupName(), token.getToken()); - } else { - System.out.printf("[%d] group '%s' failed (%d): %s [requestId=%s]%n", - record.getIndex(), token.getTokenGroupName(), - token.getHttpCode(), token.getError(), token.getRequestId()); - } + if (record.getError() == null) { + System.out.printf("[%d] group '%s' -> %s%n", + record.getIndex(), record.getTokenGroupName(), record.getToken()); + } else { + System.out.printf("[%d] group '%s' failed (%d): %s [requestId=%s]%n", + record.getIndex(), record.getTokenGroupName(), + record.getHttpCode(), record.getError(), record.getRequestId()); } } diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeSync.java b/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeSync.java index 9690f423..9d9c1248 100644 --- a/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeSync.java +++ b/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeSync.java @@ -10,7 +10,6 @@ import com.skyflow.vault.data.BulkTokenizeRequestRecord; import com.skyflow.vault.data.BulkTokenizeResponse; import com.skyflow.vault.data.BulkTokenizeResponseRecord; -import com.skyflow.vault.data.TokenizeResponseToken; import java.util.ArrayList; import java.util.List; @@ -87,15 +86,13 @@ public static void main(String[] args) { // partially succeed: some groups return a token while others return an error. requestId // identifies the API call an error came from and is set on failures only. for (BulkTokenizeResponseRecord record : tokenizeResponse.getRecords()) { - for (TokenizeResponseToken token : record.getTokens()) { - if (token.getError() == null) { - System.out.printf("[%d] group '%s' -> %s%n", - record.getIndex(), token.getTokenGroupName(), token.getToken()); - } else { - System.out.printf("[%d] group '%s' failed (%d): %s [requestId=%s]%n", - record.getIndex(), token.getTokenGroupName(), - token.getHttpCode(), token.getError(), token.getRequestId()); - } + if (record.getError() == null) { + System.out.printf("[%d] group '%s' -> %s%n", + record.getIndex(), record.getTokenGroupName(), record.getToken()); + } else { + System.out.printf("[%d] group '%s' failed (%d): %s [requestId=%s]%n", + record.getIndex(), record.getTokenGroupName(), + record.getHttpCode(), record.getError(), record.getRequestId()); } } diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/FlowTokenizeResponseObjectToken.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/FlowTokenizeResponseObjectToken.java deleted file mode 100644 index 0edfd9a8..00000000 --- a/flowvault/src/main/java/com/skyflow/generated/rest/types/FlowTokenizeResponseObjectToken.java +++ /dev/null @@ -1,188 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ -package com.skyflow.generated.rest.types; - -import com.fasterxml.jackson.annotation.*; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.skyflow.generated.rest.core.ObjectMappers; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -@JsonInclude(JsonInclude.Include.NON_ABSENT) -@JsonDeserialize(builder = FlowTokenizeResponseObjectToken.Builder.class) -public final class FlowTokenizeResponseObjectToken { - private final Optional tokenGroupName; - - private final Optional token; - - private final Optional error; - - private final Optional httpCode; - - private final Map additionalProperties; - - private FlowTokenizeResponseObjectToken( - Optional tokenGroupName, - Optional token, - Optional error, - Optional httpCode, - Map additionalProperties) { - this.tokenGroupName = tokenGroupName; - this.token = token; - this.error = error; - this.httpCode = httpCode; - this.additionalProperties = additionalProperties; - } - - /** - * @return Token group Name - */ - @JsonProperty("tokenGroupName") - public Optional getTokenGroupName() { - return tokenGroupName; - } - - /** - * @return Token value - */ - @JsonProperty("token") - public Optional getToken() { - return token; - } - - /** - * @return Error if tokenization failed - */ - @JsonProperty("error") - public Optional getError() { - return error; - } - - /** - * @return HTTP status code of the response - */ - @JsonProperty("httpCode") - public Optional getHttpCode() { - return httpCode; - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - return other instanceof FlowTokenizeResponseObjectToken && equalTo((FlowTokenizeResponseObjectToken) other); - } - - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - private boolean equalTo(FlowTokenizeResponseObjectToken other) { - return tokenGroupName.equals(other.tokenGroupName) - && token.equals(other.token) - && error.equals(other.error) - && httpCode.equals(other.httpCode); - } - - @Override - public int hashCode() { - return Objects.hash(this.tokenGroupName, this.token, this.error, this.httpCode); - } - - @Override - public String toString() { - return ObjectMappers.stringify(this); - } - - public static Builder builder() { - return new Builder(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static final class Builder { - private Optional tokenGroupName = Optional.empty(); - - private Optional token = Optional.empty(); - - private Optional error = Optional.empty(); - - private Optional httpCode = Optional.empty(); - - @JsonAnySetter - private Map additionalProperties = new HashMap<>(); - - private Builder() {} - - public Builder from(FlowTokenizeResponseObjectToken other) { - tokenGroupName(other.getTokenGroupName()); - token(other.getToken()); - error(other.getError()); - httpCode(other.getHttpCode()); - return this; - } - - /** - *

Token group Name

- */ - @JsonSetter(value = "tokenGroupName", nulls = Nulls.SKIP) - public Builder tokenGroupName(Optional tokenGroupName) { - this.tokenGroupName = tokenGroupName; - return this; - } - - public Builder tokenGroupName(String tokenGroupName) { - this.tokenGroupName = Optional.ofNullable(tokenGroupName); - return this; - } - - /** - *

Token value

- */ - @JsonSetter(value = "token", nulls = Nulls.SKIP) - public Builder token(Optional token) { - this.token = token; - return this; - } - - public Builder token(String token) { - this.token = Optional.ofNullable(token); - return this; - } - - /** - *

Error if tokenization failed

- */ - @JsonSetter(value = "error", nulls = Nulls.SKIP) - public Builder error(Optional error) { - this.error = error; - return this; - } - - public Builder error(String error) { - this.error = Optional.ofNullable(error); - return this; - } - - /** - *

HTTP status code of the response

- */ - @JsonSetter(value = "httpCode", nulls = Nulls.SKIP) - public Builder httpCode(Optional httpCode) { - this.httpCode = httpCode; - return this; - } - - public Builder httpCode(Integer httpCode) { - this.httpCode = Optional.ofNullable(httpCode); - return this; - } - - public FlowTokenizeResponseObjectToken build() { - return new FlowTokenizeResponseObjectToken(tokenGroupName, token, error, httpCode, additionalProperties); - } - } -} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeResponseObject.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeResponseObject.java index eb4a15ac..badc525e 100644 --- a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeResponseObject.java +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeResponseObject.java @@ -7,28 +7,51 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1FlowTokenizeResponseObject.Builder.class) public final class V1FlowTokenizeResponseObject { + private final Optional token; + private final Optional value; - private final Optional> tokens; + private final Optional tokenGroupName; + + private final Optional error; + + private final Optional httpCode; private final Map additionalProperties; private V1FlowTokenizeResponseObject( + Optional token, Optional value, - Optional> tokens, + Optional tokenGroupName, + Optional error, + Optional httpCode, Map additionalProperties) { + this.token = token; this.value = value; - this.tokens = tokens; + this.tokenGroupName = tokenGroupName; + this.error = error; + this.httpCode = httpCode; this.additionalProperties = additionalProperties; } /** - * @return Value of token + * @return Token that was generated + */ + @JsonProperty("token") + public Optional getToken() { + return token; + } + + /** + * @return Value that was tokenized */ @JsonProperty("value") public Optional getValue() { @@ -36,11 +59,27 @@ public Optional getValue() { } /** - * @return Token value + * @return Name of the token group + */ + @JsonProperty("tokenGroupName") + public Optional getTokenGroupName() { + return tokenGroupName; + } + + /** + * @return Error if tokenization failed */ - @JsonProperty("tokens") - public Optional> getTokens() { - return tokens; + @JsonProperty("error") + public Optional getError() { + return error; + } + + /** + * @return HTTP status code of the response + */ + @JsonProperty("httpCode") + public Optional getHttpCode() { + return httpCode; } @Override @@ -55,12 +94,16 @@ public Map getAdditionalProperties() { } private boolean equalTo(V1FlowTokenizeResponseObject other) { - return value.equals(other.value) && tokens.equals(other.tokens); + return token.equals(other.token) + && value.equals(other.value) + && tokenGroupName.equals(other.tokenGroupName) + && error.equals(other.error) + && httpCode.equals(other.httpCode); } @Override public int hashCode() { - return Objects.hash(this.value, this.tokens); + return Objects.hash(this.token, this.value, this.tokenGroupName, this.error, this.httpCode); } @Override @@ -74,9 +117,15 @@ public static Builder builder() { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder { + private Optional token = Optional.empty(); + private Optional value = Optional.empty(); - private Optional> tokens = Optional.empty(); + private Optional tokenGroupName = Optional.empty(); + + private Optional error = Optional.empty(); + + private Optional httpCode = Optional.empty(); @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -84,13 +133,30 @@ public static final class Builder { private Builder() {} public Builder from(V1FlowTokenizeResponseObject other) { + token(other.getToken()); value(other.getValue()); - tokens(other.getTokens()); + tokenGroupName(other.getTokenGroupName()); + error(other.getError()); + httpCode(other.getHttpCode()); + return this; + } + + /** + *

Token that was generated

+ */ + @JsonSetter(value = "token", nulls = Nulls.SKIP) + public Builder token(Optional token) { + this.token = token; + return this; + } + + public Builder token(String token) { + this.token = Optional.ofNullable(token); return this; } /** - *

Value of token

+ *

Value that was tokenized

*/ @JsonSetter(value = "value", nulls = Nulls.SKIP) public Builder value(Optional value) { @@ -104,21 +170,49 @@ public Builder value(Object value) { } /** - *

Token value

+ *

Name of the token group

+ */ + @JsonSetter(value = "tokenGroupName", nulls = Nulls.SKIP) + public Builder tokenGroupName(Optional tokenGroupName) { + this.tokenGroupName = tokenGroupName; + return this; + } + + public Builder tokenGroupName(String tokenGroupName) { + this.tokenGroupName = Optional.ofNullable(tokenGroupName); + return this; + } + + /** + *

Error if tokenization failed

+ */ + @JsonSetter(value = "error", nulls = Nulls.SKIP) + public Builder error(Optional error) { + this.error = error; + return this; + } + + public Builder error(String error) { + this.error = Optional.ofNullable(error); + return this; + } + + /** + *

HTTP status code of the response

*/ - @JsonSetter(value = "tokens", nulls = Nulls.SKIP) - public Builder tokens(Optional> tokens) { - this.tokens = tokens; + @JsonSetter(value = "httpCode", nulls = Nulls.SKIP) + public Builder httpCode(Optional httpCode) { + this.httpCode = httpCode; return this; } - public Builder tokens(List tokens) { - this.tokens = Optional.ofNullable(tokens); + public Builder httpCode(Integer httpCode) { + this.httpCode = Optional.ofNullable(httpCode); return this; } public V1FlowTokenizeResponseObject build() { - return new V1FlowTokenizeResponseObject(value, tokens, additionalProperties); + return new V1FlowTokenizeResponseObject(token, value, tokenGroupName, error, httpCode, additionalProperties); } } -} +} \ No newline at end of file diff --git a/flowvault/src/main/java/com/skyflow/utils/Utils.java b/flowvault/src/main/java/com/skyflow/utils/Utils.java index 9d3f8e0f..cfd8195f 100644 --- a/flowvault/src/main/java/com/skyflow/utils/Utils.java +++ b/flowvault/src/main/java/com/skyflow/utils/Utils.java @@ -15,7 +15,6 @@ import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest; import com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest; import com.skyflow.generated.rest.types.FlowEnumUpdateType; -import com.skyflow.generated.rest.types.FlowTokenizeResponseObjectToken; import com.skyflow.generated.rest.types.V1DeleteTokenResponseObject; import com.skyflow.generated.rest.types.V1FlowDeleteTokenResponse; import com.skyflow.generated.rest.types.V1FlowDetokenizeResponse; @@ -36,7 +35,6 @@ import com.skyflow.vault.data.BulkTokenizeResponseRecord; import com.skyflow.vault.data.DeleteTokensRecord; import com.skyflow.vault.data.TokenizeRequestRecord; -import com.skyflow.vault.data.TokenizeResponseToken; import com.skyflow.vault.data.BulkDeleteTokensResponse; import com.skyflow.vault.data.BulkDetokenizeRequest; import com.skyflow.vault.data.BulkDetokenizeResponse; @@ -678,17 +676,17 @@ public static List handleBulkTokenizeBatchException( if (batchRecords == null) return errorRecords; for (int position = 0; position < batchRecords.size(); position++) { BulkTokenizeRequestRecord requested = batchRecords.get(position); - List tokens = new ArrayList<>(); + int index = startIndex + position; List groupNames = requested.getTokenGroupNames(); if (groupNames == null || groupNames.isEmpty()) { - tokens.add(new TokenizeResponseToken(null, null, httpCode, message, requestId)); + errorRecords.add(new BulkTokenizeResponseRecord( + index, requested.getValue(), null, null, httpCode, message, requestId)); } else { for (String groupName : groupNames) { - tokens.add(new TokenizeResponseToken(groupName, null, httpCode, message, requestId)); + errorRecords.add(new BulkTokenizeResponseRecord( + index, requested.getValue(), groupName, null, httpCode, message, requestId)); } } - errorRecords.add(new BulkTokenizeResponseRecord( - startIndex + position, requested.getValue(), tokens)); } return errorRecords; } @@ -850,57 +848,20 @@ private static V1FlowTokenizeRequestObject buildTokenizeRequestObject(TokenizeRe return builder.build(); } - private static List buildTokenizeResponseTokens( - V1FlowTokenizeResponseObject record, String requestId) { - List tokens = new ArrayList<>(); - if (record.getTokens().isPresent()) { - for (FlowTokenizeResponseObjectToken tokenObj : record.getTokens().get()) { - boolean failed = tokenObj.getError().isPresent() - && tokenObj.getError().get() != null - && !tokenObj.getError().get().isEmpty(); - tokens.add(new TokenizeResponseToken( - tokenObj.getTokenGroupName().orElse(null), - tokenObj.getToken().orElse(null), - tokenObj.getHttpCode().orElse(failed ? 500 : 200), - failed ? tokenObj.getError().get() : null, - requestId - )); - } - } else { - // the API reports one flat row per (value, token group) instead of a nested tokens - // array; the generated type has no fields for those, so they land in additionalProperties - TokenizeResponseToken flat = flatToken(record, requestId); - if (flat != null) { - tokens.add(flat); - } - } - return tokens; - } - - /** - * Reads a flat {@code tokenGroupName}/{@code token}/{@code error}/{@code httpCode} row out of - * the wire object's unmodelled properties. Returns null when the row carries none of them, so a - * genuinely token-less record still reports an empty list rather than a phantom entry. - */ - private static TokenizeResponseToken flatToken(V1FlowTokenizeResponseObject record, String requestId) { - Map extras = record.getAdditionalProperties(); - if (extras == null || extras.isEmpty()) { - return null; - } - boolean carriesTokenFields = extras.containsKey("token") - || extras.containsKey("tokenGroupName") - || extras.containsKey("error") - || extras.containsKey("httpCode"); - if (!carriesTokenFields) { - return null; - } - String error = asNonEmptyString(extras.get("error")); - String token = asNonEmptyString(extras.get("token")); - Integer httpCode = extras.get("httpCode") instanceof Number - ? ((Number) extras.get("httpCode")).intValue() - : (error != null ? 500 : 200); - return new TokenizeResponseToken( - asNonEmptyString(extras.get("tokenGroupName")), token, httpCode, error, requestId); + private static BulkTokenizeResponseRecord buildTokenizeResponseRecord( + int index, V1FlowTokenizeResponseObject record, String requestId) { + boolean failed = record.getError().isPresent() + && record.getError().get() != null + && !record.getError().get().isEmpty(); + return new BulkTokenizeResponseRecord( + index, + record.getValue().orElse(null), + asNonEmptyString(record.getTokenGroupName().orElse(null)), + asNonEmptyString(record.getToken().orElse(null)), + record.getHttpCode().orElse(failed ? 500 : 200), + failed ? record.getError().get() : null, + requestId + ); } /** The API sends "" for a token or error that does not apply; normalise both to null. */ @@ -938,18 +899,15 @@ public static BulkTokenizeResponse formatBulkTokenizeResponse( } /** - * Folds the response rows back onto the records that produced them. + * Assigns each response row the index of the record that produced it. * *

The API emits one row per (value, token group) rather than one per record, and a record * rejected outright yields a single row instead of one per group — so row count is not a * function of the request. Rows do arrive in request order, though, and each carries its value, - * which is enough: a row belongs to the record being filled while it matches that record's value + * which is enough: a row belongs to the record in flight while it matches that record's value * and the record has not yet taken as many rows as it asked for token groups. Anything else * starts the next record. Batching keeps values distinct within a request (see * {@link #createBulkTokenizeBatches}), so the value comparison never straddles two records. - * - *

A response already grouped one-row-per-record folds through this unchanged, since each row - * then matches exactly one record before the value moves on. */ private static List groupTokenizeRows( List rows, @@ -960,43 +918,29 @@ private static List groupTokenizeRows( if (batchRecords == null || batchRecords.isEmpty()) { // nothing to correlate against; fall back to one record per row for (int position = 0; position < rows.size(); position++) { - responseRecords.add(new BulkTokenizeResponseRecord(startIndex + position, - rows.get(position).getValue().orElse(null), - buildTokenizeResponseTokens(rows.get(position), requestId))); + responseRecords.add(buildTokenizeResponseRecord(startIndex + position, rows.get(position), requestId)); } return responseRecords; } int recordPosition = 0; int rowsTakenByRecord = 0; - List tokens = new ArrayList<>(); for (V1FlowTokenizeResponseObject row : rows) { Object rowValue = row.getValue().orElse(null); while (recordPosition < batchRecords.size() && !acceptsRow(batchRecords.get(recordPosition), rowValue, rowsTakenByRecord)) { - responseRecords.add(new BulkTokenizeResponseRecord(startIndex + recordPosition, - batchRecords.get(recordPosition).getValue(), tokens)); - tokens = new ArrayList<>(); rowsTakenByRecord = 0; recordPosition++; } if (recordPosition >= batchRecords.size()) { // more rows than the request can account for; keep them rather than drop them - responseRecords.add(new BulkTokenizeResponseRecord(startIndex + recordPosition, - rowValue, buildTokenizeResponseTokens(row, requestId))); + responseRecords.add(buildTokenizeResponseRecord(startIndex + recordPosition, row, requestId)); recordPosition++; continue; } - tokens.addAll(buildTokenizeResponseTokens(row, requestId)); + responseRecords.add(buildTokenizeResponseRecord(startIndex + recordPosition, row, requestId)); rowsTakenByRecord++; } - // close the record in flight, then any records the response never mentioned - while (recordPosition < batchRecords.size()) { - responseRecords.add(new BulkTokenizeResponseRecord(startIndex + recordPosition, - batchRecords.get(recordPosition).getValue(), tokens)); - tokens = new ArrayList<>(); - recordPosition++; - } return responseRecords; } 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 0d6d4618..37c2182e 100644 --- a/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java +++ b/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java @@ -39,7 +39,6 @@ import com.skyflow.vault.data.BulkDeleteTokensResponseRecord; import com.skyflow.vault.data.BulkTokenizeRequestRecord; import com.skyflow.vault.data.BulkTokenizeResponseRecord; -import com.skyflow.vault.data.TokenizeResponseToken; import com.skyflow.vault.data.BulkDeleteTokensResponse; import com.skyflow.vault.data.BulkDetokenizeRequest; import com.skyflow.vault.data.BulkDetokenizeResponse; diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponse.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponse.java index 115fb9b3..26cf0281 100644 --- a/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponse.java +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponse.java @@ -5,7 +5,11 @@ import com.google.gson.annotations.Expose; import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Set; public class BulkTokenizeResponse { @Expose(serialize = true) @@ -34,35 +38,45 @@ public BulkTokenizeResponse(List records, */ private static TokenizeSummary buildSummary(List records, List originalPayload) { + Map outcomesByIndex = new LinkedHashMap<>(); // index -> [succeeded, failed] + if (records != null) { + for (BulkTokenizeResponseRecord record : records) { + int[] outcomes = outcomesByIndex.computeIfAbsent(record.getIndex(), key -> new int[2]); + if (record.getError() == null) { + outcomes[0]++; + } else { + outcomes[1]++; + } + } + } int totalTokenized = 0; int totalPartial = 0; int totalFailed = 0; - if (records != null) { - for (BulkTokenizeResponseRecord record : records) { - int succeeded = 0; - int failed = 0; - if (record.getTokens() != null) { - for (TokenizeResponseToken token : record.getTokens()) { - if (token.getError() == null) { - succeeded++; - } else { - failed++; - } - } + int totalTokens; + if (originalPayload != null) { + totalTokens = originalPayload.size(); + for (int index = 0; index < originalPayload.size(); index++) { + int[] outcomes = outcomesByIndex.getOrDefault(index, new int[2]); + if (outcomes[0] > 0 && outcomes[1] > 0) { + totalPartial++; + } else if (outcomes[0] > 0) { + totalTokenized++; + } else { + totalFailed++; } - if (succeeded > 0 && failed > 0) { + } + } else { + totalTokens = records != null ? records.size() : 0; + for (int[] outcomes : outcomesByIndex.values()) { + if (outcomes[0] > 0 && outcomes[1] > 0) { totalPartial++; - } else if (succeeded > 0) { + } else if (outcomes[0] > 0) { totalTokenized++; } else { - // no token groups came back, or every one of them failed totalFailed++; } } } - int totalTokens = originalPayload != null - ? originalPayload.size() - : (records != null ? records.size() : 0); return new TokenizeSummary(totalTokens, totalTokenized, totalPartial, totalFailed); } @@ -79,41 +93,33 @@ public List getRecords() { * *

Retryable means a 5xx other than 529, matching the rule used elsewhere in the SDK. The * caller's original record objects are returned unchanged — they carry no index, exactly as they - * were supplied. Records where nothing failed retryably are omitted. + * were supplied. */ public List getRecordsToRetry() { if (recordsToRetry == null) { recordsToRetry = new ArrayList<>(); if (records != null && originalPayload != null) { + Set retryableIndexes = new LinkedHashSet<>(); for (BulkTokenizeResponseRecord record : records) { + if (isRetryable(record)) { + retryableIndexes.add(record.getIndex()); + } + } + for (int index : retryableIndexes) { // the SDK assigns the index from the record's position in originalPayload, so a // positional lookup is exact - int index = record.getIndex(); - if (index < 0 || index >= originalPayload.size() || !hasRetryableFailure(record)) { - continue; + if (index >= 0 && index < originalPayload.size()) { + recordsToRetry.add(originalPayload.get(index)); } - recordsToRetry.add(originalPayload.get(index)); } } } return recordsToRetry; } - private static boolean hasRetryableFailure(BulkTokenizeResponseRecord record) { - if (record.getTokens() == null) { - return false; - } - for (TokenizeResponseToken token : record.getTokens()) { - if (isRetryable(token)) { - return true; - } - } - return false; - } - - private static boolean isRetryable(TokenizeResponseToken token) { - Integer httpCode = token.getHttpCode(); - return token.getError() != null + private static boolean isRetryable(BulkTokenizeResponseRecord record) { + Integer httpCode = record.getHttpCode(); + return record.getError() != null && httpCode != null && httpCode >= 500 && httpCode <= 599 && httpCode != 529; diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponseRecord.java index bfcae073..d9baf9fb 100644 --- a/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponseRecord.java +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponseRecord.java @@ -4,18 +4,17 @@ import com.google.gson.GsonBuilder; import com.google.gson.annotations.Expose; -import java.util.List; - /** * A {@link TokenizeResponseRecord} carrying the index of the input value it belongs to. The index - * is the one supplied on the matching {@link BulkTokenizeRequestRecord}, echoed back unchanged. + * is assigned by the SDK from the matching {@link BulkTokenizeRequestRecord}'s position. */ public class BulkTokenizeResponseRecord extends TokenizeResponseRecord { @Expose(serialize = true) private final int index; - public BulkTokenizeResponseRecord(int index, Object value, List tokens) { - super(value, tokens); + public BulkTokenizeResponseRecord(int index, Object value, String tokenGroupName, String token, + Integer httpCode, String error, String requestId) { + super(value, tokenGroupName, token, httpCode, error, requestId); this.index = index; } @@ -28,4 +27,4 @@ public String toString() { Gson gson = new GsonBuilder().serializeNulls().create(); return gson.toJson(this); } -} +} \ No newline at end of file diff --git a/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseRecord.java index 84da4cef..9ca53eec 100644 --- a/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseRecord.java +++ b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseRecord.java @@ -4,30 +4,70 @@ import com.google.gson.GsonBuilder; import com.google.gson.annotations.Expose; -import java.util.List; - /** - * The tokenization outcome for one input value: every requested token group is reported in - * {@code tokens}, whether it succeeded or failed. + * One (value, token-group) outcome. {@code token} is populated on success and {@code error} on + * failure; {@code httpCode} is present on both paths. + * + *

{@code requestId} identifies the API call this outcome came from and is set only when the + * outcome is an error, since that is when it is useful for support. */ public class TokenizeResponseRecord { @Expose(serialize = true) private final Object value; @Expose(serialize = true) - private final List tokens; + private final String tokenGroupName; + + @Expose(serialize = true) + private final String token; + + @Expose(serialize = true) + private final Integer httpCode; - public TokenizeResponseRecord(Object value, List tokens) { + @Expose(serialize = true) + private final String error; + + @Expose(serialize = true) + private final String requestId; + + public TokenizeResponseRecord(Object value, String tokenGroupName, String token, Integer httpCode, String error) { + this(value, tokenGroupName, token, httpCode, error, null); + } + + public TokenizeResponseRecord(Object value, String tokenGroupName, String token, Integer httpCode, + String error, String requestId) { this.value = value; - this.tokens = tokens; + this.tokenGroupName = tokenGroupName; + this.token = token; + this.httpCode = httpCode; + this.error = error; + // a successful outcome carries no request id, whatever the caller passed + this.requestId = error != null ? requestId : null; } public Object getValue() { return value; } - public List getTokens() { - return tokens; + public String getTokenGroupName() { + return tokenGroupName; + } + + public String getToken() { + return token; + } + + public Integer getHttpCode() { + return httpCode; + } + + public String getError() { + return error; + } + + /** The API call this outcome came from; null unless this is an error. */ + public String getRequestId() { + return requestId; } @Override @@ -35,4 +75,4 @@ public String toString() { Gson gson = new GsonBuilder().serializeNulls().create(); return gson.toJson(this); } -} +} \ No newline at end of file diff --git a/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseToken.java b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseToken.java deleted file mode 100644 index dff54b77..00000000 --- a/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseToken.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.skyflow.vault.data; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.annotations.Expose; - -/** - * One token-group outcome for a single input value. {@code token} is populated on success and - * {@code error} on failure; {@code httpCode} is present on both paths. - * - *

{@code requestId} identifies the API call this outcome came from and is set only when the - * outcome is an error, since that is when it is useful for support. Bulk requests are split into - * batches, so every error from the same batch carries the same id and errors from different - * batches carry different ones. - */ -public class TokenizeResponseToken { - @Expose(serialize = true) - private final String tokenGroupName; - - @Expose(serialize = true) - private final String token; - - @Expose(serialize = true) - private final Integer httpCode; - - @Expose(serialize = true) - private final String error; - - @Expose(serialize = true) - private final String requestId; - - public TokenizeResponseToken(String tokenGroupName, String token, Integer httpCode, String error) { - this(tokenGroupName, token, httpCode, error, null); - } - - public TokenizeResponseToken(String tokenGroupName, String token, Integer httpCode, - String error, String requestId) { - this.tokenGroupName = tokenGroupName; - this.token = token; - this.httpCode = httpCode; - this.error = error; - // a successful outcome carries no request id, whatever the caller passed - this.requestId = error != null ? requestId : null; - } - - public String getTokenGroupName() { - return tokenGroupName; - } - - public String getToken() { - return token; - } - - public Integer getHttpCode() { - return httpCode; - } - - public String getError() { - return error; - } - - /** The API call this outcome came from; null unless this is an error. */ - public String getRequestId() { - return requestId; - } - - @Override - public String toString() { - Gson gson = new GsonBuilder().serializeNulls().create(); - return gson.toJson(this); - } -} diff --git a/flowvault/src/test/java/com/skyflow/utils/FlatTokenizeResponseTests.java b/flowvault/src/test/java/com/skyflow/utils/FlatTokenizeResponseTests.java index 43d51408..c809063d 100644 --- a/flowvault/src/test/java/com/skyflow/utils/FlatTokenizeResponseTests.java +++ b/flowvault/src/test/java/com/skyflow/utils/FlatTokenizeResponseTests.java @@ -6,7 +6,6 @@ import com.skyflow.vault.data.BulkTokenizeRequestRecord; import com.skyflow.vault.data.BulkTokenizeResponse; import com.skyflow.vault.data.BulkTokenizeResponseRecord; -import com.skyflow.vault.data.TokenizeResponseToken; import org.junit.Assert; import org.junit.Test; @@ -21,8 +20,8 @@ /** * The API returns one flat row per (value, token group) instead of the nested {@code tokens} array * the generated wire type models, and a record rejected outright yields a single row regardless of - * how many groups it asked for. These cover folding those rows back onto the records that produced - * them. + * how many groups it asked for. These cover assigning each row the index of the record that + * produced it. */ public class FlatTokenizeResponseTests { @@ -59,7 +58,7 @@ private static BulkTokenizeRequestRecord byotRecord(Object value, String token, + "}"; @Test - public void testLiveResponse_threeRowsFoldOntoTwoRecords() { + public void testLiveResponse_threeRowsStayFlatWithSharedIndexes() { Map objectValue = new LinkedHashMap<>(); objectValue.put("email", "ka@yahoo.com"); objectValue.put("age", 28); @@ -71,27 +70,26 @@ public void testLiveResponse_threeRowsFoldOntoTwoRecords() { BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(LIVE_RESPONSE), sent, 0, new HashMap<>()); - // two inputs in, two records out - not three - Assert.assertEquals(2, result.getRecords().size()); + // three rows in, three flat records out - the object's two rows share its index + Assert.assertEquals(3, result.getRecords().size()); BulkTokenizeResponseRecord byot = result.getRecords().get(0); Assert.assertEquals(0, byot.getIndex()); Assert.assertEquals("byot-input-value", byot.getValue()); - Assert.assertEquals(1, byot.getTokens().size()); - Assert.assertEquals("Invalid request. BYOT token should contain one token group.", - byot.getTokens().get(0).getError()); - Assert.assertEquals(Integer.valueOf(400), byot.getTokens().get(0).getHttpCode()); + Assert.assertEquals("Invalid request. BYOT token should contain one token group.", byot.getError()); + Assert.assertEquals(Integer.valueOf(400), byot.getHttpCode()); // the API sends "" for a token that does not apply - Assert.assertNull(byot.getTokens().get(0).getToken()); - - BulkTokenizeResponseRecord object = result.getRecords().get(1); - Assert.assertEquals(1, object.getIndex()); - Assert.assertEquals(2, object.getTokens().size()); - Assert.assertEquals("deterministic_string_tg", object.getTokens().get(0).getTokenGroupName()); - Assert.assertEquals("cc1179a3-e2be-404e-9a31-4f97f27bf406", object.getTokens().get(0).getToken()); - Assert.assertNull(object.getTokens().get(0).getError()); + Assert.assertNull(byot.getToken()); + + BulkTokenizeResponseRecord objectRow1 = result.getRecords().get(1); + BulkTokenizeResponseRecord objectRow2 = result.getRecords().get(2); + Assert.assertEquals(1, objectRow1.getIndex()); + Assert.assertEquals(1, objectRow2.getIndex()); + Assert.assertEquals("deterministic_string_tg", objectRow1.getTokenGroupName()); + Assert.assertEquals("cc1179a3-e2be-404e-9a31-4f97f27bf406", objectRow1.getToken()); + Assert.assertNull(objectRow1.getError()); Assert.assertEquals("Tokenize failed. Token group emailTokenGroup is invalid. Specify a valid token group.", - object.getTokens().get(1).getError()); + objectRow2.getError()); } @Test @@ -115,10 +113,35 @@ public void testLiveResponse_summaryClassifiesByRecordNotByRow() { Assert.assertEquals(1, withPayload.getSummary().getTotalFailed()); } + // ── BYOT naming exactly one, invalid, group ───────────────────────────────── + + @Test + public void testByotWithSingleInvalidGroup_reportsTheGroupErrorNotTheByotError() { + // BYOT naming exactly one group satisfies the "one token group" rule, so a bad group name + // fails for the same reason it would on a non-BYOT record, not for naming too many groups. + String json = "{\"response\": [" + + "{\"value\": \"byot-value\", \"tokenGroupName\": null, \"token\": \"\"," + + " \"error\": \"Tokenize failed. Token group bad_group is invalid. Specify a valid token group.\"," + + " \"httpCode\": 400}" + + "]}"; + List sent = Collections.singletonList( + byotRecord("byot-value", "my-own-token", "bad_group")); + + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 0, new HashMap<>()); + + Assert.assertEquals(1, result.getRecords().size()); + BulkTokenizeResponseRecord record = result.getRecords().get(0); + Assert.assertEquals(0, record.getIndex()); + Assert.assertEquals("Tokenize failed. Token group bad_group is invalid. Specify a valid token group.", + record.getError()); + Assert.assertEquals(Integer.valueOf(400), record.getHttpCode()); + Assert.assertNull(record.getToken()); + } + // ── duplicate token groups within one record ─────────────────────────────── @Test - public void testDuplicateTokenGroupsInOneRecord_bothRowsKeptUnderOneRecord() { + public void testDuplicateTokenGroupsInOneRecord_bothRowsShareTheSameIndex() { String json = "{\"response\": [" + "{\"value\": \"v1\", \"tokenGroupName\": \"g1\", \"token\": \"tok-a\", \"httpCode\": 200}," + "{\"value\": \"v1\", \"tokenGroupName\": \"g1\", \"token\": \"tok-b\", \"httpCode\": 200}" @@ -127,13 +150,13 @@ public void testDuplicateTokenGroupsInOneRecord_bothRowsKeptUnderOneRecord() { BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 0, new HashMap<>()); - // one record asked for the same group twice; both results stay on it rather than + // one record asked for the same group twice; both rows share its index rather than // collapsing or spilling into a phantom second record - Assert.assertEquals(1, result.getRecords().size()); - List tokens = result.getRecords().get(0).getTokens(); - Assert.assertEquals(2, tokens.size()); - Assert.assertEquals("tok-a", tokens.get(0).getToken()); - Assert.assertEquals("tok-b", tokens.get(1).getToken()); + Assert.assertEquals(2, result.getRecords().size()); + Assert.assertEquals(0, result.getRecords().get(0).getIndex()); + Assert.assertEquals(0, result.getRecords().get(1).getIndex()); + Assert.assertEquals("tok-a", result.getRecords().get(0).getToken()); + Assert.assertEquals("tok-b", result.getRecords().get(1).getToken()); } // ── boundaries ───────────────────────────────────────────────────────────── @@ -151,11 +174,12 @@ public void testRecordRejectedOutright_nextRecordStillGetsItsOwnRows() { BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 0, new HashMap<>()); - Assert.assertEquals(2, result.getRecords().size()); - Assert.assertEquals(1, result.getRecords().get(0).getTokens().size()); - Assert.assertEquals("rejected", result.getRecords().get(0).getTokens().get(0).getError()); - Assert.assertEquals(2, result.getRecords().get(1).getTokens().size()); - Assert.assertEquals("tok-1", result.getRecords().get(1).getTokens().get(0).getToken()); + Assert.assertEquals(3, result.getRecords().size()); + Assert.assertEquals(0, result.getRecords().get(0).getIndex()); + Assert.assertEquals("rejected", result.getRecords().get(0).getError()); + Assert.assertEquals(1, result.getRecords().get(1).getIndex()); + Assert.assertEquals(1, result.getRecords().get(2).getIndex()); + Assert.assertEquals("tok-1", result.getRecords().get(1).getToken()); } @Test @@ -173,7 +197,7 @@ public void testIndexIsOffsetByBatchStart() { } @Test - public void testRecordTheResponseNeverMentions_stillReportedWithNoTokens() { + public void testRecordTheResponseNeverMentions_hasNoFlatRowButStillCountsAsFailed() { String json = "{\"response\": [" + "{\"value\": \"v0\", \"tokenGroupName\": \"g1\", \"token\": \"tok-0\", \"httpCode\": 200}" + "]}"; @@ -181,10 +205,12 @@ public void testRecordTheResponseNeverMentions_stillReportedWithNoTokens() { BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 0, new HashMap<>()); - // the caller must still see a record per input, so indexes stay aligned with their list - Assert.assertEquals(2, result.getRecords().size()); - Assert.assertEquals(1, result.getRecords().get(1).getIndex()); - Assert.assertTrue(result.getRecords().get(1).getTokens().isEmpty()); + // v1 got no row at all, so there is nothing flat to report for it - but the summary, + // which classifies by input index rather than by row, still counts it as failed + Assert.assertEquals(1, result.getRecords().size()); + Assert.assertEquals(0, result.getRecords().get(0).getIndex()); + Assert.assertEquals(1, + new BulkTokenizeResponse(result.getRecords(), sent).getSummary().getTotalFailed()); } @Test @@ -198,27 +224,26 @@ public void testMoreRowsThanTheRequestExplains_rowsAreKeptNotDropped() { BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 0, new HashMap<>()); Assert.assertEquals(2, result.getRecords().size()); - Assert.assertEquals("tok-9", result.getRecords().get(1).getTokens().get(0).getToken()); + Assert.assertEquals("tok-9", result.getRecords().get(1).getToken()); } @Test - public void testGroupedResponseShape_stillFoldsOneRowPerRecord() { - // if the API ever returns the nested shape the wire type models, nothing changes + public void testMultipleGroupsForOneValue_shareTheSameIndex() { String json = "{\"response\": [" - + "{\"value\": \"v0\", \"tokens\": [" - + " {\"tokenGroupName\": \"g1\", \"token\": \"tok-a\", \"httpCode\": 200}," - + " {\"tokenGroupName\": \"g2\", \"token\": \"tok-b\", \"httpCode\": 200}]}," - + "{\"value\": \"v1\", \"tokens\": [" - + " {\"tokenGroupName\": \"g1\", \"token\": \"tok-c\", \"httpCode\": 200}]}" + + "{\"value\": \"v0\", \"tokenGroupName\": \"g1\", \"token\": \"tok-a\", \"httpCode\": 200}," + + "{\"value\": \"v0\", \"tokenGroupName\": \"g2\", \"token\": \"tok-b\", \"httpCode\": 200}," + + "{\"value\": \"v1\", \"tokenGroupName\": \"g1\", \"token\": \"tok-c\", \"httpCode\": 200}" + "]}"; List sent = Arrays.asList( record("v0", "g1", "g2"), record("v1", "g1")); BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 0, new HashMap<>()); - Assert.assertEquals(2, result.getRecords().size()); - Assert.assertEquals(2, result.getRecords().get(0).getTokens().size()); - Assert.assertEquals("tok-c", result.getRecords().get(1).getTokens().get(0).getToken()); + Assert.assertEquals(3, result.getRecords().size()); + Assert.assertEquals(0, result.getRecords().get(0).getIndex()); + Assert.assertEquals(0, result.getRecords().get(1).getIndex()); + Assert.assertEquals(1, result.getRecords().get(2).getIndex()); + Assert.assertEquals("tok-c", result.getRecords().get(2).getToken()); } // ── rejected requests still describe each record in the body ─────────────── @@ -255,13 +280,12 @@ public void testRejectedRequest_reportsTheApiMessageNotJustTheStatus() { List records = Utils.handleBulkTokenizeBatchException(ex, sent, 0); - Assert.assertEquals(1, records.size()); // one entry, matching the API - not one fabricated per requested token group - Assert.assertEquals(1, records.get(0).getTokens().size()); + Assert.assertEquals(1, records.size()); Assert.assertEquals("Invalid request. BYOT token should contain one token group.", - records.get(0).getTokens().get(0).getError()); - Assert.assertNull(records.get(0).getTokens().get(0).getTokenGroupName()); - Assert.assertEquals(Integer.valueOf(400), records.get(0).getTokens().get(0).getHttpCode()); + records.get(0).getError()); + Assert.assertNull(records.get(0).getTokenGroupName()); + Assert.assertEquals(Integer.valueOf(400), records.get(0).getHttpCode()); } @Test @@ -279,8 +303,8 @@ public void testRejectedRequest_keepsPerRecordMessagesAndIndexes() { Assert.assertEquals(2, records.size()); Assert.assertEquals(20, records.get(0).getIndex()); Assert.assertEquals(21, records.get(1).getIndex()); - Assert.assertEquals(message, records.get(0).getTokens().get(0).getError()); - Assert.assertEquals(message, records.get(1).getTokens().get(0).getError()); + Assert.assertEquals(message, records.get(0).getError()); + Assert.assertEquals(message, records.get(1).getError()); } @Test @@ -292,11 +316,12 @@ public void testRejectedRequest_withNoUsableBodyFallsBackToTheStatusCode() { List records = Utils.handleBulkTokenizeBatchException(ex, sent, 0); - Assert.assertEquals(1, records.size()); - // no rows to go on, so every requested group is reported as failed - Assert.assertEquals(2, records.get(0).getTokens().size()); - Assert.assertEquals("connection reset", records.get(0).getTokens().get(0).getError()); - Assert.assertEquals(Integer.valueOf(500), records.get(0).getTokens().get(0).getHttpCode()); + // no rows to go on, so every requested group is reported as its own failed record + Assert.assertEquals(2, records.size()); + Assert.assertEquals(0, records.get(0).getIndex()); + Assert.assertEquals(0, records.get(1).getIndex()); + Assert.assertEquals("connection reset", records.get(0).getError()); + Assert.assertEquals(Integer.valueOf(500), records.get(0).getHttpCode()); } @Test @@ -309,8 +334,8 @@ public void testRejectedRequest_withUnfamiliarBodyFallsBackToTheStatusCode() { Utils.handleBulkTokenizeBatchException(rejected(504, opaque), sent, 0); Assert.assertEquals(1, records.size()); - Assert.assertEquals("gateway timeout", records.get(0).getTokens().get(0).getError()); - Assert.assertEquals(Integer.valueOf(504), records.get(0).getTokens().get(0).getHttpCode()); + Assert.assertEquals("gateway timeout", records.get(0).getError()); + Assert.assertEquals(Integer.valueOf(504), records.get(0).getHttpCode()); } @Test diff --git a/flowvault/src/test/java/com/skyflow/utils/RequestIdTests.java b/flowvault/src/test/java/com/skyflow/utils/RequestIdTests.java index 753487d8..8572ba0d 100644 --- a/flowvault/src/test/java/com/skyflow/utils/RequestIdTests.java +++ b/flowvault/src/test/java/com/skyflow/utils/RequestIdTests.java @@ -12,7 +12,6 @@ import com.skyflow.vault.data.BulkTokenizeRequestRecord; import com.skyflow.vault.data.BulkTokenizeResponse; import com.skyflow.vault.data.BulkTokenizeResponseRecord; -import com.skyflow.vault.data.TokenizeResponseToken; import okhttp3.Protocol; import okhttp3.Request; import okhttp3.Response; @@ -96,8 +95,8 @@ public void testTokenize_requestIdOnErrorsOnly() { BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse( tokenizeWire(json), sent, 0, headers(REQ_ID_A)); - TokenizeResponseToken success = result.getRecords().get(0).getTokens().get(0); - TokenizeResponseToken failure = result.getRecords().get(1).getTokens().get(0); + BulkTokenizeResponseRecord success = result.getRecords().get(0); + BulkTokenizeResponseRecord failure = result.getRecords().get(1); Assert.assertNull("a successful token must not carry a request id", success.getRequestId()); Assert.assertEquals(REQ_ID_A, failure.getRequestId()); } @@ -144,10 +143,10 @@ public void testTokenize_twoBatchesReportTwoDistinctRequestIds() { List merged = new ArrayList<>(first.getRecords()); merged.addAll(second.getRecords()); Assert.assertEquals(4, merged.size()); - Assert.assertEquals(REQ_ID_A, merged.get(0).getTokens().get(0).getRequestId()); - Assert.assertEquals(REQ_ID_A, merged.get(1).getTokens().get(0).getRequestId()); - Assert.assertEquals(REQ_ID_B, merged.get(2).getTokens().get(0).getRequestId()); - Assert.assertEquals(REQ_ID_B, merged.get(3).getTokens().get(0).getRequestId()); + Assert.assertEquals(REQ_ID_A, merged.get(0).getRequestId()); + Assert.assertEquals(REQ_ID_A, merged.get(1).getRequestId()); + Assert.assertEquals(REQ_ID_B, merged.get(2).getRequestId()); + Assert.assertEquals(REQ_ID_B, merged.get(3).getRequestId()); Assert.assertEquals(2, merged.get(2).getIndex()); Assert.assertEquals(3, merged.get(3).getIndex()); } @@ -160,7 +159,7 @@ public void testTokenize_missingHeaderLeavesRequestIdNull() { tokenizeWire(json), Collections.singletonList(tokenizeRecord("v0", "g1")), 0, headers(null)); - Assert.assertNull(result.getRecords().get(0).getTokens().get(0).getRequestId()); + Assert.assertNull(result.getRecords().get(0).getRequestId()); } @Test @@ -173,10 +172,10 @@ public void testTokenize_partialRecordStampsOnlyTheFailedGroup() { tokenizeWire(json), Collections.singletonList(tokenizeRecord("v0", "g1", "g2")), 0, headers(REQ_ID_A)); - List tokens = result.getRecords().get(0).getTokens(); - Assert.assertEquals(2, tokens.size()); - Assert.assertNull(tokens.get(0).getRequestId()); - Assert.assertEquals(REQ_ID_A, tokens.get(1).getRequestId()); + List records = result.getRecords(); + Assert.assertEquals(2, records.size()); + Assert.assertNull(records.get(0).getRequestId()); + Assert.assertEquals(REQ_ID_A, records.get(1).getRequestId()); } // ── tokenize: rejected requests ──────────────────────────────────────────── @@ -198,7 +197,7 @@ public void testTokenize_rejectedRequestStampsIdFromTheFailedCall() { List records = Utils.handleBulkTokenizeBatchException( ex, Collections.singletonList(tokenizeRecord("v0", "g1")), 0); - Assert.assertEquals(REQ_ID_B, records.get(0).getTokens().get(0).getRequestId()); + Assert.assertEquals(REQ_ID_B, records.get(0).getRequestId()); } @Test @@ -209,10 +208,9 @@ public void testTokenize_rejectedRequestWithNoBodyStillStampsEveryGroup() { List records = Utils.handleBulkTokenizeBatchException( ex, Collections.singletonList(tokenizeRecord("v0", "g1", "g2")), 0); - List tokens = records.get(0).getTokens(); - Assert.assertEquals(2, tokens.size()); - Assert.assertEquals(REQ_ID_A, tokens.get(0).getRequestId()); - Assert.assertEquals(REQ_ID_A, tokens.get(1).getRequestId()); + Assert.assertEquals(2, records.size()); + Assert.assertEquals(REQ_ID_A, records.get(0).getRequestId()); + Assert.assertEquals(REQ_ID_A, records.get(1).getRequestId()); } @Test @@ -229,7 +227,7 @@ public void testTokenize_transportFailureReportsTheInnermostCause() { List records = Utils.handleBulkTokenizeBatchException( ex, Collections.singletonList(tokenizeRecord("v0", "g1")), 0); - String error = records.get(0).getTokens().get(0).getError(); + String error = records.get(0).getError(); Assert.assertTrue("expected the DNS failure, got: " + error, error.contains("UnknownHostException")); Assert.assertTrue(error.contains("badcluster.skyvault.skyflowapis.dev")); @@ -261,8 +259,8 @@ public void testTokenize_transportFailureHasNoRequestId() { new RuntimeException("connection reset"), Collections.singletonList(tokenizeRecord("v0", "g1")), 0); - Assert.assertNull(records.get(0).getTokens().get(0).getRequestId()); - Assert.assertEquals("connection reset", records.get(0).getTokens().get(0).getError()); + Assert.assertNull(records.get(0).getRequestId()); + Assert.assertEquals("connection reset", records.get(0).getError()); } // ── delete: success carries no id, errors carry the batch's ──────────────── @@ -430,11 +428,9 @@ public void testRequestIdAppearsInToString() { private static Set tokenizeRequestIds(List records) { Set ids = new HashSet<>(); for (BulkTokenizeResponseRecord record : records) { - for (TokenizeResponseToken token : record.getTokens()) { - if (token.getError() != null) { - Assert.assertNotNull("every error must carry a request id", token.getRequestId()); - ids.add(token.getRequestId()); - } + if (record.getError() != null) { + Assert.assertNotNull("every error must carry a request id", record.getRequestId()); + ids.add(record.getRequestId()); } } return ids; diff --git a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java index 80fc8361..55f01a66 100644 --- a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java +++ b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java @@ -11,7 +11,6 @@ import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest; import com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest; import com.skyflow.generated.rest.types.FlowEnumUpdateType; -import com.skyflow.generated.rest.types.FlowTokenizeResponseObjectToken; import com.skyflow.generated.rest.types.V1DeleteTokenResponseObject; import com.skyflow.generated.rest.types.V1FlowDeleteTokenResponse; import com.skyflow.generated.rest.types.V1FlowDetokenizeResponse; @@ -39,13 +38,11 @@ import com.skyflow.vault.data.InsertRequest; import com.skyflow.vault.data.TokenGroupRedactions; import com.skyflow.vault.data.BulkTokenizeResponseRecord; -import com.skyflow.vault.data.TokenizeResponseRecord; import com.skyflow.vault.data.TokenizeRequestRecord; import com.skyflow.vault.data.TokenizeRequest; import com.skyflow.vault.data.TokenizeResponse; import com.skyflow.vault.data.UpsertOptions; import org.junit.After; -import com.skyflow.vault.data.TokenizeResponseToken; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -1554,18 +1551,17 @@ public void testHandleBulkTokenizeBatchException_apiExceptionFailsEveryTokenGrou List errors = Utils.handleBulkTokenizeBatchException( wrapper, tokenizeBatch("v1", "group1", "group2"), 0); - Assert.assertEquals(1, errors.size()); - BulkTokenizeResponseRecord record = errors.get(0); // index is derived from the batch position; the value is echoed from the request - Assert.assertEquals(0, record.getIndex()); - Assert.assertEquals("v1", record.getValue()); - // one failed token entry per requested group - Assert.assertEquals(2, record.getTokens().size()); - Assert.assertEquals("group1", record.getTokens().get(0).getTokenGroupName()); - Assert.assertEquals("invalid value", record.getTokens().get(0).getError()); - Assert.assertEquals(Integer.valueOf(400), record.getTokens().get(0).getHttpCode()); - Assert.assertEquals("group2", record.getTokens().get(1).getTokenGroupName()); - Assert.assertEquals("invalid value", record.getTokens().get(1).getError()); + // one failed record per requested group + Assert.assertEquals(2, errors.size()); + Assert.assertEquals(0, errors.get(0).getIndex()); + Assert.assertEquals(0, errors.get(1).getIndex()); + Assert.assertEquals("v1", errors.get(0).getValue()); + Assert.assertEquals("group1", errors.get(0).getTokenGroupName()); + Assert.assertEquals("invalid value", errors.get(0).getError()); + Assert.assertEquals(Integer.valueOf(400), errors.get(0).getHttpCode()); + Assert.assertEquals("group2", errors.get(1).getTokenGroupName()); + Assert.assertEquals("invalid value", errors.get(1).getError()); } @Test @@ -1578,8 +1574,8 @@ public void testHandleBulkTokenizeBatchException_genericException() { Assert.assertEquals(1, errors.size()); Assert.assertEquals(3, errors.get(0).getIndex()); - Assert.assertEquals(Integer.valueOf(500), errors.get(0).getTokens().get(0).getHttpCode()); - Assert.assertEquals("boom", errors.get(0).getTokens().get(0).getError()); + Assert.assertEquals(Integer.valueOf(500), errors.get(0).getHttpCode()); + Assert.assertEquals("boom", errors.get(0).getError()); } @Test @@ -1607,22 +1603,20 @@ public void testHandleBulkTokenizeBatchException_noTokenGroupsStillReportsOneEnt List errors = Utils.handleBulkTokenizeBatchException(ex, batch, 0); - Assert.assertEquals(1, errors.get(0).getTokens().size()); - Assert.assertNull(errors.get(0).getTokens().get(0).getTokenGroupName()); - Assert.assertEquals("boom", errors.get(0).getTokens().get(0).getError()); + Assert.assertEquals(1, errors.size()); + Assert.assertNull(errors.get(0).getTokenGroupName()); + Assert.assertEquals("boom", errors.get(0).getError()); } @Test public void testHandleBulkTokenizeBatchException_errorBodyWithResponseArrayRebuildsRecords() { // A 4xx whose body echoes the per-row "response" array is rebuilt via tokenizeRecordsFromErrorBody // rather than summarized by the bare status code. - Map tokenRow = new HashMap<>(); - tokenRow.put("tokenGroupName", "group1"); - tokenRow.put("error", "BYOT token should contain one token group"); - tokenRow.put("httpCode", 400); Map responseRow = new HashMap<>(); responseRow.put("value", "v1"); - responseRow.put("tokens", Collections.singletonList(tokenRow)); + responseRow.put("tokenGroupName", "group1"); + responseRow.put("error", "BYOT token should contain one token group"); + responseRow.put("httpCode", 400); Map body = new HashMap<>(); body.put("response", Collections.singletonList(responseRow)); ApiClientApiException apiEx = new ApiClientApiException("tokenize failed", 400, body); @@ -1633,11 +1627,9 @@ public void testHandleBulkTokenizeBatchException_errorBodyWithResponseArrayRebui Assert.assertEquals(1, errors.size()); Assert.assertEquals("v1", errors.get(0).getValue()); - Assert.assertEquals(1, errors.get(0).getTokens().size()); - Assert.assertEquals("group1", errors.get(0).getTokens().get(0).getTokenGroupName()); - Assert.assertEquals("BYOT token should contain one token group", - errors.get(0).getTokens().get(0).getError()); - Assert.assertEquals(Integer.valueOf(400), errors.get(0).getTokens().get(0).getHttpCode()); + Assert.assertEquals("group1", errors.get(0).getTokenGroupName()); + Assert.assertEquals("BYOT token should contain one token group", errors.get(0).getError()); + Assert.assertEquals(Integer.valueOf(400), errors.get(0).getHttpCode()); } @Test @@ -1654,8 +1646,8 @@ public void testHandleBulkTokenizeBatchException_errorFieldAsObjectUsesStructure wrapper, tokenizeBatch("v1", "group1"), 0); Assert.assertEquals(1, errors.size()); - Assert.assertEquals("vault not found", errors.get(0).getTokens().get(0).getError()); - Assert.assertEquals(Integer.valueOf(404), errors.get(0).getTokens().get(0).getHttpCode()); + Assert.assertEquals("vault not found", errors.get(0).getError()); + Assert.assertEquals(Integer.valueOf(404), errors.get(0).getHttpCode()); } @Test @@ -1668,8 +1660,8 @@ public void testHandleBulkTokenizeBatchException_nonMapBodyUsesApiMessage() { wrapper, tokenizeBatch("v1", "group1"), 0); Assert.assertEquals(1, errors.size()); - Assert.assertEquals("tokenize failed", errors.get(0).getTokens().get(0).getError()); - Assert.assertEquals(Integer.valueOf(500), errors.get(0).getTokens().get(0).getHttpCode()); + Assert.assertEquals("tokenize failed", errors.get(0).getError()); + Assert.assertEquals(Integer.valueOf(500), errors.get(0).getHttpCode()); } @Test @@ -1941,10 +1933,7 @@ private static V1FlowTokenizeResponse tokenizeWire(V1FlowTokenizeResponseObject. @Test public void testFormatBulkTokenizeResponse_success() { V1FlowTokenizeResponse response = tokenizeWire(V1FlowTokenizeResponseObject.builder() - .value("value1") - .tokens(Collections.singletonList(FlowTokenizeResponseObjectToken.builder() - .tokenGroupName("group1").token("tok-abc").build())) - .build()); + .value("value1").tokenGroupName("group1").token("tok-abc").build()); BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse( response, tokenizeBatch("value1", "group1"), 0, new HashMap<>()); @@ -1953,34 +1942,28 @@ public void testFormatBulkTokenizeResponse_success() { BulkTokenizeResponseRecord record = result.getRecords().get(0); Assert.assertEquals(0, record.getIndex()); Assert.assertEquals("value1", record.getValue()); - Assert.assertEquals("tok-abc", record.getTokens().get(0).getToken()); - Assert.assertNull(record.getTokens().get(0).getError()); + Assert.assertEquals("tok-abc", record.getToken()); + Assert.assertNull(record.getError()); } @Test public void testFormatBulkTokenizeResponse_tokenError() { V1FlowTokenizeResponse response = tokenizeWire(V1FlowTokenizeResponseObject.builder() - .value("value1") - .tokens(Collections.singletonList(FlowTokenizeResponseObjectToken.builder() - .tokenGroupName("group1").error("invalid value").httpCode(400).build())) - .build()); + .value("value1").tokenGroupName("group1").error("invalid value").httpCode(400).build()); BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse( response, tokenizeBatch("value1", "group1"), 0, new HashMap<>()); Assert.assertEquals(1, result.getRecords().size()); - TokenizeResponseToken token = result.getRecords().get(0).getTokens().get(0); - Assert.assertEquals(Integer.valueOf(400), token.getHttpCode()); - Assert.assertEquals("invalid value", token.getError()); + BulkTokenizeResponseRecord record = result.getRecords().get(0); + Assert.assertEquals(Integer.valueOf(400), record.getHttpCode()); + Assert.assertEquals("invalid value", record.getError()); } @Test public void testFormatBulkTokenizeResponse_derivesIndexFromBatchPosition() { V1FlowTokenizeResponse response = tokenizeWire(V1FlowTokenizeResponseObject.builder() - .value("value1") - .tokens(Collections.singletonList(FlowTokenizeResponseObjectToken.builder() - .tokenGroupName("group1").token("tok-abc").build())) - .build()); + .value("value1").tokenGroupName("group1").token("tok-abc").build()); // this batch starts at index 40 in the caller's list BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse( 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 6b4aa516..8ddc2e1b 100644 --- a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java @@ -12,7 +12,6 @@ import com.skyflow.generated.rest.core.RequestOptions; import com.skyflow.generated.rest.resources.flowservice.FlowserviceClient; import com.skyflow.generated.rest.resources.flowservice.RawFlowserviceClient; -import com.skyflow.generated.rest.types.FlowTokenizeResponseObjectToken; import com.skyflow.generated.rest.types.V1DeleteTokenResponseObject; import com.skyflow.generated.rest.types.V1FlowDeleteTokenResponse; import com.skyflow.generated.rest.types.V1FlowDetokenizeResponse; @@ -57,6 +56,7 @@ import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -392,10 +392,8 @@ public void testBulkTokenize_success() throws Exception { ApiClient mockApi = Mockito.mock(ApiClient.class); RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); - FlowTokenizeResponseObjectToken token = FlowTokenizeResponseObjectToken.builder() - .tokenGroupName("group1").token("tok-abc").build(); V1FlowTokenizeResponseObject responseObject = V1FlowTokenizeResponseObject.builder() - .value("value1").tokens(Collections.singletonList(token)).build(); + .value("value1").tokenGroupName("group1").token("tok-abc").build(); V1FlowTokenizeResponse body = V1FlowTokenizeResponse.builder() .response(Collections.singletonList(responseObject)).build(); ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); @@ -412,8 +410,42 @@ public void testBulkTokenize_success() throws Exception { Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); Assert.assertEquals(1, response.getRecords().size()); Assert.assertEquals(0, response.getRecords().get(0).getIndex()); - Assert.assertEquals("tok-abc", response.getRecords().get(0).getTokens().get(0).getToken()); - Assert.assertNull(response.getRecords().get(0).getTokens().get(0).getError()); + Assert.assertEquals("tok-abc", response.getRecords().get(0).getToken()); + Assert.assertNull(response.getRecords().get(0).getError()); + } + + @Test + public void testBulkTokenize_partialFailureWithRetryableGroupIsRetriedAsWhole() throws Exception { + // one value, two groups: group1 succeeds, group2 fails with a retryable 503 + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + V1FlowTokenizeResponseObject succeeded = V1FlowTokenizeResponseObject.builder() + .value("value1").tokenGroupName("group1").token("tok-abc").httpCode(200).build(); + V1FlowTokenizeResponseObject failed = V1FlowTokenizeResponseObject.builder() + .value("value1").error("service unavailable").httpCode(503).build(); + V1FlowTokenizeResponse body = V1FlowTokenizeResponse.builder() + .response(Arrays.asList(succeeded, failed)).build(); + ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + when(mockRaw.tokenize(any(), any())).thenReturn(httpResp); + + VaultController controller = createControllerWithMock(mockApi); + + BulkTokenizeRequestRecord requested = BulkTokenizeRequestRecord.builder().value("value1") + .tokenGroupNames(Arrays.asList("group1", "group2")).build(); + BulkTokenizeRequest request = BulkTokenizeRequest.builder() + .records(Collections.singletonList(requested)).build(); + + BulkTokenizeResponse response = controller.bulkTokenize(request); + + Assert.assertEquals(2, response.getRecords().size()); + Assert.assertEquals(0, response.getRecords().get(0).getIndex()); + Assert.assertEquals(0, response.getRecords().get(1).getIndex()); + Assert.assertEquals(1, response.getSummary().getTotalPartial()); + + List retry = response.getRecordsToRetry(); + Assert.assertEquals(1, retry.size()); + Assert.assertSame("must return the caller's own record, groups and all", requested, retry.get(0)); } @Test @@ -433,10 +465,8 @@ public void testBulkTokenizeAsync_success() throws Exception { ApiClient mockApi = Mockito.mock(ApiClient.class); RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); - FlowTokenizeResponseObjectToken token = FlowTokenizeResponseObjectToken.builder() - .tokenGroupName("group1").token("tok-abc").build(); V1FlowTokenizeResponseObject responseObject = V1FlowTokenizeResponseObject.builder() - .value("value1").tokens(Collections.singletonList(token)).build(); + .value("value1").tokenGroupName("group1").token("tok-abc").build(); V1FlowTokenizeResponse body = V1FlowTokenizeResponse.builder() .response(Collections.singletonList(responseObject)).build(); ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); @@ -452,7 +482,7 @@ public void testBulkTokenizeAsync_success() throws Exception { BulkTokenizeResponse response = controller.bulkTokenizeAsync(request).get(5, TimeUnit.SECONDS); Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); Assert.assertEquals(1, response.getRecords().size()); - Assert.assertNull(response.getRecords().get(0).getTokens().get(0).getError()); + Assert.assertNull(response.getRecords().get(0).getError()); } // ── additional bulk API-error coverage ─────────────────────────────────── @@ -548,9 +578,8 @@ public void testBulkTokenize_apiErrorCapturedInErrors() throws Exception { Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); Assert.assertEquals(1, response.getRecords().size()); Assert.assertEquals(0, response.getRecords().get(0).getIndex()); - Assert.assertEquals(Integer.valueOf(400), - response.getRecords().get(0).getTokens().get(0).getHttpCode()); - Assert.assertNotNull(response.getRecords().get(0).getTokens().get(0).getError()); + Assert.assertEquals(Integer.valueOf(400), response.getRecords().get(0).getHttpCode()); + Assert.assertNotNull(response.getRecords().get(0).getError()); } @Test @@ -571,9 +600,8 @@ public void testBulkTokenizeAsync_apiErrorCapturedInErrors() throws Exception { Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); Assert.assertEquals(1, response.getRecords().size()); Assert.assertEquals(0, response.getRecords().get(0).getIndex()); - Assert.assertEquals(Integer.valueOf(400), - response.getRecords().get(0).getTokens().get(0).getHttpCode()); - Assert.assertNotNull(response.getRecords().get(0).getTokens().get(0).getError()); + Assert.assertEquals(Integer.valueOf(400), response.getRecords().get(0).getHttpCode()); + Assert.assertNotNull(response.getRecords().get(0).getError()); } @Test @@ -896,13 +924,10 @@ private static void stubTokenizeEcho(RawFlowserviceClient mockRaw) { invocation.getArgument(0); List responseRecords = new ArrayList<>(); for (com.skyflow.generated.rest.types.V1FlowTokenizeRequestObject obj : req.getData().get()) { - FlowTokenizeResponseObjectToken token = FlowTokenizeResponseObjectToken.builder() - .tokenGroupName("group1") - .token("tok-" + obj.getValue().get()) - .build(); responseRecords.add(V1FlowTokenizeResponseObject.builder() .value(obj.getValue().get()) - .tokens(Collections.singletonList(token)) + .tokenGroupName("group1") + .token("tok-" + obj.getValue().get()) .build()); } V1FlowTokenizeResponse body = V1FlowTokenizeResponse.builder().response(responseRecords).build(); diff --git a/flowvault/src/test/java/com/skyflow/vault/data/BulkResponseTests.java b/flowvault/src/test/java/com/skyflow/vault/data/BulkResponseTests.java index 031099b4..be181eae 100644 --- a/flowvault/src/test/java/com/skyflow/vault/data/BulkResponseTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/data/BulkResponseTests.java @@ -358,16 +358,9 @@ public void testBulkDeleteTokensResponse_toStringMatchesContractShape() { // ── BulkTokenizeResponse ───────────────────────────────────────────────── - private static BulkTokenizeResponseRecord tokenizeRecord(int index, Object value, TokenizeResponseToken... tokens) { - return new BulkTokenizeResponseRecord(index, value, Arrays.asList(tokens)); - } - - private static TokenizeResponseToken okToken(String group, String token) { - return new TokenizeResponseToken(group, token, 200, null); - } - - private static TokenizeResponseToken failedToken(String group, String error) { - return new TokenizeResponseToken(group, null, 400, error); + private static BulkTokenizeResponseRecord row(int index, Object value, String group, String token, + Integer httpCode, String error) { + return new BulkTokenizeResponseRecord(index, value, group, token, httpCode, error, null); } private static List payloadOf(int size) { @@ -394,15 +387,16 @@ public void testBulkTokenizeResponse_twoArgConstructorClassifiesEachValue() { // index 1: some ok, some failed -> totalPartial // index 2: all groups failed -> totalFailed List records = Arrays.asList( - tokenizeRecord(0, "v0", okToken("g1", "tok-0")), - tokenizeRecord(1, "v1", okToken("g1", "tok-1"), failedToken("g2", "partial failure")), - tokenizeRecord(2, "v2", failedToken("g1", "full failure"))); + row(0, "v0", "g1", "tok-0", 200, null), + row(1, "v1", "g1", "tok-1", 200, null), + row(1, "v1", "g2", null, 400, "partial failure"), + row(2, "v2", "g1", null, 400, "full failure")); BulkTokenizeResponse response = new BulkTokenizeResponse(records, payloadOf(3)); TokenizeSummary summary = response.getSummary(); Assert.assertNotNull(summary); - // totalTokens counts input values submitted, not output token entries + // totalTokens counts input values submitted, not output rows Assert.assertEquals(3, summary.getTotalTokens()); Assert.assertEquals(1, summary.getTotalTokenized()); Assert.assertEquals(1, summary.getTotalPartial()); @@ -410,11 +404,8 @@ public void testBulkTokenizeResponse_twoArgConstructorClassifiesEachValue() { } @Test - public void testBulkTokenizeResponse_recordWithNoTokensCountsAsFailed() { - List records = Collections.singletonList( - new BulkTokenizeResponseRecord(0, "v0", Collections.emptyList())); - - BulkTokenizeResponse response = new BulkTokenizeResponse(records, payloadOf(1)); + public void testBulkTokenizeResponse_valueWithNoRowsCountsAsFailed() { + BulkTokenizeResponse response = new BulkTokenizeResponse(new ArrayList<>(), payloadOf(1)); Assert.assertEquals(0, response.getSummary().getTotalTokenized()); Assert.assertEquals(1, response.getSummary().getTotalFailed()); @@ -423,12 +414,11 @@ public void testBulkTokenizeResponse_recordWithNoTokensCountsAsFailed() { @Test public void testBulkTokenizeResponse_getRecordsToRetryReturnsCallerRecordUnchanged() { // one value, four groups: only the 503 is retryable, but the whole record comes back - List records = Collections.singletonList( - tokenizeRecord(0, "9999999999", - okToken("phone_group", "p1q2r3s4"), - new TokenizeResponseToken("phone_group_2", null, 503, "unavailable"), - new TokenizeResponseToken("phone_group_3", null, 400, "bad group"), - new TokenizeResponseToken("phone_group_4", null, 529, "special case"))); + List records = Arrays.asList( + row(0, "9999999999", "phone_group", "p1q2r3s4", 200, null), + row(0, "9999999999", "phone_group_2", null, 503, "unavailable"), + row(0, "9999999999", "phone_group_3", null, 400, "bad group"), + row(0, "9999999999", "phone_group_4", null, 529, "special case")); BulkTokenizeRequestRecord requested = BulkTokenizeRequestRecord.builder().value("9999999999") .tokenGroupNames(Arrays.asList( "phone_group", "phone_group_2", "phone_group_3", "phone_group_4")) @@ -449,9 +439,9 @@ public void testBulkTokenizeResponse_getRecordsToRetryReturnsCallerRecordUnchang @Test public void testBulkTokenizeResponse_getRecordsToRetryOmitsValuesWithoutRetryableFailures() { List records = Arrays.asList( - tokenizeRecord(0, "v0", okToken("g1", "tok-0")), - tokenizeRecord(1, "v1", failedToken("g1", "bad group")), // 400, not retryable - tokenizeRecord(2, "v2", new TokenizeResponseToken("g1", null, 500, "server error"))); + row(0, "v0", "g1", "tok-0", 200, null), + row(1, "v1", "g1", null, 400, "bad group"), // not retryable + row(2, "v2", "g1", null, 500, "server error")); List retry = new BulkTokenizeResponse(records, payloadOf(3)).getRecordsToRetry(); @@ -464,8 +454,8 @@ public void testBulkTokenizeResponse_getRecordsToRetryOmitsValuesWithoutRetryabl public void testBulkTokenizeResponse_getRecordsToRetryLooksUpByIndexNotResponseOrder() { // batches finish out of order, so the failing record is not first in the response List records = Arrays.asList( - tokenizeRecord(2, "v2", new TokenizeResponseToken("g1", null, 500, "server error")), - tokenizeRecord(0, "v0", okToken("g1", "tok-0"))); + row(2, "v2", "g1", null, 500, "server error"), + row(0, "v0", "g1", "tok-0", 200, null)); List retry = new BulkTokenizeResponse(records, payloadOf(3)).getRecordsToRetry(); @@ -478,9 +468,9 @@ public void testBulkTokenizeResponse_getRecordsToRetryLooksUpByIndexNotResponseO public void testBulkTokenizeResponse_getRecordsToRetrySkipsIndexOutsidePayload() { // a malformed index must not blow up with IndexOutOfBoundsException List records = Arrays.asList( - tokenizeRecord(9, "stray", new TokenizeResponseToken("g1", null, 500, "server error")), - tokenizeRecord(-1, "stray", new TokenizeResponseToken("g1", null, 500, "server error")), - tokenizeRecord(0, "v0", new TokenizeResponseToken("g1", null, 500, "server error"))); + row(9, "stray", "g1", null, 500, "server error"), + row(-1, "stray", "g1", null, 500, "server error"), + row(0, "v0", "g1", null, 500, "server error")); List retry = new BulkTokenizeResponse(records, payloadOf(1)).getRecordsToRetry(); @@ -492,7 +482,7 @@ public void testBulkTokenizeResponse_getRecordsToRetrySkipsIndexOutsidePayload() @Test public void testBulkTokenizeResponse_getRecordsToRetryCarriesByotToken() { List records = Collections.singletonList( - tokenizeRecord(0, "v0", new TokenizeResponseToken("g1", null, 500, "server error"))); + row(0, "v0", "g1", null, 500, "server error")); List payload = Collections.singletonList( BulkTokenizeRequestRecord.builder().value("v0").token("my-own-token") .tokenGroupNames(Collections.singletonList("g1")).build()); @@ -505,9 +495,9 @@ public void testBulkTokenizeResponse_getRecordsToRetryCarriesByotToken() { @Test public void testBulkTokenizeResponse_getRecordsToRetryKeepsRequestedGroupsOnBatchFailure() { - // a batch-level failure reports no group name on the token entry + // a batch-level failure reports no group name on the row List records = Collections.singletonList( - tokenizeRecord(0, "v0", new TokenizeResponseToken(null, null, 500, "server error"))); + row(0, "v0", null, null, 500, "server error")); List payload = Collections.singletonList( BulkTokenizeRequestRecord.builder().value("v0") .tokenGroupNames(Arrays.asList("g1", "g2")).build()); @@ -521,7 +511,7 @@ public void testBulkTokenizeResponse_getRecordsToRetryKeepsRequestedGroupsOnBatc @Test public void testBulkTokenizeResponse_recordsToRetryNotSerialized() { List records = Collections.singletonList( - tokenizeRecord(0, "v0", new TokenizeResponseToken("g1", null, 500, "server error"))); + row(0, "v0", "g1", null, 500, "server error")); BulkTokenizeResponse response = new BulkTokenizeResponse(records, payloadOf(1)); response.getRecordsToRetry(); // populate the lazily-derived field @@ -532,10 +522,9 @@ public void testBulkTokenizeResponse_recordsToRetryNotSerialized() { @Test public void testBulkTokenizeResponse_toStringMatchesContractShape() { List records = Arrays.asList( - tokenizeRecord(0, "john@example.com", okToken("email_group", "a1b2c3d4")), - tokenizeRecord(1, "9999999999", - okToken("phone_group", "p1q2r3s4"), - failedToken("phone_group_2", "Invalid token group configuration"))); + row(0, "john@example.com", "email_group", "a1b2c3d4", 200, null), + row(1, "9999999999", "phone_group", "p1q2r3s4", 200, null), + row(1, "9999999999", "phone_group_2", null, 400, "Invalid token group configuration")); String json = new BulkTokenizeResponse(records, payloadOf(2)).toString(); diff --git a/flowvault/src/test/java/com/skyflow/vault/data/BulkRetryAndSummaryTests.java b/flowvault/src/test/java/com/skyflow/vault/data/BulkRetryAndSummaryTests.java index d8a1437a..eb118a3a 100644 --- a/flowvault/src/test/java/com/skyflow/vault/data/BulkRetryAndSummaryTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/data/BulkRetryAndSummaryTests.java @@ -17,12 +17,8 @@ */ public class BulkRetryAndSummaryTests { - private static TokenizeResponseToken token(String group, String value, Integer httpCode, String error) { - return new TokenizeResponseToken(group, value, httpCode, error); - } - - private static BulkTokenizeResponseRecord tokenizeRecord(int index, TokenizeResponseToken... tokens) { - return new BulkTokenizeResponseRecord(index, "value" + index, Arrays.asList(tokens)); + private static BulkTokenizeResponseRecord row(int index, String group, String token, Integer httpCode, String error) { + return new BulkTokenizeResponseRecord(index, "value" + index, group, token, httpCode, error, null); } private static BulkTokenizeRequestRecord requestRecord(String value) { @@ -33,8 +29,9 @@ private static BulkTokenizeRequestRecord requestRecord(String value) { @Test public void testTokenizeSummary_allGroupsSucceededCountsAsTokenized() { - List records = Collections.singletonList( - tokenizeRecord(0, token("g1", "t1", 200, null), token("g2", "t2", 200, null))); + List records = Arrays.asList( + row(0, "g1", "t1", 200, null), + row(0, "g2", "t2", 200, null)); TokenizeSummary summary = new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) .getSummary(); @@ -47,8 +44,9 @@ public void testTokenizeSummary_allGroupsSucceededCountsAsTokenized() { @Test public void testTokenizeSummary_someGroupsFailedCountsAsPartial() { - List records = Collections.singletonList( - tokenizeRecord(0, token("g1", "t1", 200, null), token("g2", null, 400, "bad group"))); + List records = Arrays.asList( + row(0, "g1", "t1", 200, null), + row(0, "g2", null, 400, "bad group")); TokenizeSummary summary = new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) .getSummary(); @@ -60,8 +58,9 @@ public void testTokenizeSummary_someGroupsFailedCountsAsPartial() { @Test public void testTokenizeSummary_everyGroupFailedCountsAsFailed() { - List records = Collections.singletonList( - tokenizeRecord(0, token("g1", null, 500, "boom"), token("g2", null, 500, "boom"))); + List records = Arrays.asList( + row(0, "g1", null, 500, "boom"), + row(0, "g2", null, 500, "boom")); TokenizeSummary summary = new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) .getSummary(); @@ -72,31 +71,19 @@ public void testTokenizeSummary_everyGroupFailedCountsAsFailed() { } @Test - public void testTokenizeSummary_noTokensAtAllCountsAsFailed() { - // A record that came back with no token groups is a failure, not a success. - List records = Collections.singletonList( - new BulkTokenizeResponseRecord(0, "value0", null)); - - TokenizeSummary summary = new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + public void testTokenizeSummary_noRowsAtAllCountsAsFailed() { + // A value with no rows in the response at all is a failure, not a success. + TokenizeSummary summary = new BulkTokenizeResponse(new ArrayList<>(), Collections.singletonList(requestRecord("a"))) .getSummary(); Assert.assertEquals(1, summary.getTotalFailed()); } - @Test - public void testTokenizeSummary_emptyTokenListCountsAsFailed() { - List records = Collections.singletonList( - new BulkTokenizeResponseRecord(0, "value0", new ArrayList<>())); - - Assert.assertEquals(1, new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) - .getSummary().getTotalFailed()); - } - @Test public void testTokenizeSummary_totalTokensComesFromTheSubmittedPayload() { // Two values submitted, only one came back — totalTokens must reflect what was sent. List records = Collections.singletonList( - tokenizeRecord(0, token("g1", "t1", 200, null))); + row(0, "g1", "t1", 200, null)); TokenizeSummary summary = new BulkTokenizeResponse( records, Arrays.asList(requestRecord("a"), requestRecord("b"))).getSummary(); @@ -126,9 +113,9 @@ public void testTokenizeSummary_isNullWhenNoPayloadWasSupplied() { @Test public void testTokenizeRetry_only5xxFailuresAreReturned() { List records = Arrays.asList( - tokenizeRecord(0, token("g", null, 500, "server")), - tokenizeRecord(1, token("g", null, 400, "client")), - tokenizeRecord(2, token("g", "t", 200, null))); + row(0, "g", null, 500, "server"), + row(1, "g", null, 400, "client"), + row(2, "g", "t", 200, null)); List payload = Arrays.asList(requestRecord("a"), requestRecord("b"), requestRecord("c")); @@ -141,7 +128,7 @@ public void testTokenizeRetry_only5xxFailuresAreReturned() { @Test public void testTokenizeRetry_529IsNotRetried() { List records = Collections.singletonList( - tokenizeRecord(0, token("g", null, 529, "site frozen"))); + row(0, "g", null, 529, "site frozen")); Assert.assertTrue(new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) .getRecordsToRetry().isEmpty()); @@ -150,17 +137,17 @@ public void testTokenizeRetry_529IsNotRetried() { @Test public void testTokenizeRetry_599IsRetriedAnd600IsNot() { Assert.assertEquals(1, new BulkTokenizeResponse( - Collections.singletonList(tokenizeRecord(0, token("g", null, 599, "edge"))), + Collections.singletonList(row(0, "g", null, 599, "edge")), Collections.singletonList(requestRecord("a"))).getRecordsToRetry().size()); Assert.assertEquals(0, new BulkTokenizeResponse( - Collections.singletonList(tokenizeRecord(0, token("g", null, 600, "edge"))), + Collections.singletonList(row(0, "g", null, 600, "edge")), Collections.singletonList(requestRecord("a"))).getRecordsToRetry().size()); } @Test public void testTokenizeRetry_5xxWithoutAnErrorIsNotRetried() { List records = Collections.singletonList( - tokenizeRecord(0, token("g", "t", 500, null))); + row(0, "g", "t", 500, null)); Assert.assertTrue(new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) .getRecordsToRetry().isEmpty()); @@ -169,7 +156,7 @@ public void testTokenizeRetry_5xxWithoutAnErrorIsNotRetried() { @Test public void testTokenizeRetry_nullHttpCodeIsNotRetried() { List records = Collections.singletonList( - tokenizeRecord(0, token("g", null, null, "no status"))); + row(0, "g", null, null, "no status")); Assert.assertTrue(new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) .getRecordsToRetry().isEmpty()); @@ -178,27 +165,25 @@ public void testTokenizeRetry_nullHttpCodeIsNotRetried() { @Test public void testTokenizeRetry_partialFailureRetriesTheWholeRecord() { // One group failed retryably, another succeeded — the value still needs resubmitting. - List records = Collections.singletonList( - tokenizeRecord(0, token("g1", "t1", 200, null), token("g2", null, 503, "down"))); + List records = Arrays.asList( + row(0, "g1", "t1", 200, null), + row(0, "g2", null, 503, "down")); Assert.assertEquals(1, new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) .getRecordsToRetry().size()); } @Test - public void testTokenizeRetry_recordWithNullTokensIsNotRetried() { - List records = Collections.singletonList( - new BulkTokenizeResponseRecord(0, "value0", null)); - - Assert.assertTrue(new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + public void testTokenizeRetry_recordWithNoRowsIsNotRetried() { + Assert.assertTrue(new BulkTokenizeResponse(new ArrayList<>(), Collections.singletonList(requestRecord("a"))) .getRecordsToRetry().isEmpty()); } @Test public void testTokenizeRetry_outOfRangeIndexIsSkippedRatherThanThrowing() { List records = Arrays.asList( - tokenizeRecord(5, token("g", null, 500, "server")), - tokenizeRecord(-1, token("g", null, 500, "server"))); + row(5, "g", null, 500, "server"), + row(-1, "g", null, 500, "server")); Assert.assertTrue(new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) .getRecordsToRetry().isEmpty()); @@ -207,7 +192,7 @@ public void testTokenizeRetry_outOfRangeIndexIsSkippedRatherThanThrowing() { @Test public void testTokenizeRetry_withoutOriginalPayloadReturnsEmpty() { List records = Collections.singletonList( - tokenizeRecord(0, token("g", null, 500, "server"))); + row(0, "g", null, 500, "server")); Assert.assertTrue(new BulkTokenizeResponse(records).getRecordsToRetry().isEmpty()); } @@ -215,7 +200,7 @@ public void testTokenizeRetry_withoutOriginalPayloadReturnsEmpty() { @Test public void testTokenizeRetry_isMemoisedAcrossCalls() { List records = Collections.singletonList( - tokenizeRecord(0, token("g", null, 500, "server"))); + row(0, "g", null, 500, "server")); BulkTokenizeResponse response = new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))); diff --git a/flowvault/src/test/java/com/skyflow/vault/data/RequestResponseWrapperTests.java b/flowvault/src/test/java/com/skyflow/vault/data/RequestResponseWrapperTests.java index f3509eac..1b74171d 100644 --- a/flowvault/src/test/java/com/skyflow/vault/data/RequestResponseWrapperTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/data/RequestResponseWrapperTests.java @@ -109,22 +109,20 @@ public void testTokenizeRequest_defaultIsNull() { @Test public void testTokenizeResponse_gettersReturnConstructorValues() { List records = Collections.singletonList( - new TokenizeResponseRecord("value1", Collections.singletonList( - new TokenizeResponseToken("group1", "tok-abc", 200, null)))); + new TokenizeResponseRecord("value1", "group1", "tok-abc", 200, null)); TokenizeResponse response = new TokenizeResponse(records); Assert.assertEquals(records, response.getResponse()); Assert.assertEquals("value1", response.getResponse().get(0).getValue()); - Assert.assertEquals("tok-abc", response.getResponse().get(0).getTokens().get(0).getToken()); - Assert.assertNull(response.getResponse().get(0).getTokens().get(0).getError()); + Assert.assertEquals("tok-abc", response.getResponse().get(0).getToken()); + Assert.assertNull(response.getResponse().get(0).getError()); } @Test public void testTokenizeResponse_toStringSerializesNulls() { TokenizeResponse response = new TokenizeResponse(Collections.singletonList( - new TokenizeResponseRecord("value1", Collections.singletonList( - new TokenizeResponseToken("group1", "tok-abc", 200, null))))); + new TokenizeResponseRecord("value1", "group1", "tok-abc", 200, null))); Assert.assertTrue(response.toString().contains("\"error\":null")); } diff --git a/flowvault/src/test/java/com/skyflow/vault/data/ResponseComponentTests.java b/flowvault/src/test/java/com/skyflow/vault/data/ResponseComponentTests.java index b97ae191..b77d704f 100644 --- a/flowvault/src/test/java/com/skyflow/vault/data/ResponseComponentTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/data/ResponseComponentTests.java @@ -11,7 +11,7 @@ /** * Tests for the response/success/summary building-block classes that carry real - * constructor logic or toString() serialization: {@link Token}, {@link TokenizeResponseToken}, + * constructor logic or toString() serialization: {@link Token}, * {@link TokenizeResponseRecord}, {@link BulkTokenizeResponseRecord}, {@link TokenizeSummary}, * {@link DeleteTokensRecord}, {@link BulkDeleteTokensResponseRecord}, * {@link DeleteTokensSummary}, {@link DetokenizeSummary}, {@link DetokenizeMetadata}, @@ -390,46 +390,36 @@ public void testBulkSummary_toStringNotNull() { } - // ── TokenizeResponseToken ──────────────────────────────────────────────── + // ── TokenizeResponseRecord / BulkTokenizeResponseRecord ────────────────── @Test - public void testTokenizeResponseToken_successValues() { - TokenizeResponseToken token = new TokenizeResponseToken("group1", "tok-abc", 200, null); - Assert.assertEquals("group1", token.getTokenGroupName()); - Assert.assertEquals("tok-abc", token.getToken()); - Assert.assertEquals(Integer.valueOf(200), token.getHttpCode()); - Assert.assertNull(token.getError()); + public void testTokenizeResponseRecord_successValues() { + TokenizeResponseRecord record = new TokenizeResponseRecord("value1", "group1", "tok-abc", 200, null); + Assert.assertEquals("value1", record.getValue()); + Assert.assertEquals("group1", record.getTokenGroupName()); + Assert.assertEquals("tok-abc", record.getToken()); + Assert.assertEquals(Integer.valueOf(200), record.getHttpCode()); + Assert.assertNull(record.getError()); } @Test - public void testTokenizeResponseToken_errorValues() { - TokenizeResponseToken token = new TokenizeResponseToken("group2", null, 400, "bad group"); - Assert.assertNull(token.getToken()); - Assert.assertEquals("bad group", token.getError()); - Assert.assertEquals(Integer.valueOf(400), token.getHttpCode()); + public void testTokenizeResponseRecord_errorValues() { + TokenizeResponseRecord record = new TokenizeResponseRecord("value1", "group2", null, 400, "bad group"); + Assert.assertNull(record.getToken()); + Assert.assertEquals("bad group", record.getError()); + Assert.assertEquals(Integer.valueOf(400), record.getHttpCode()); } @Test - public void testTokenizeResponseToken_toStringSerializesNulls() { - Assert.assertTrue(new TokenizeResponseToken("group1", "tok-abc", 200, null) + public void testTokenizeResponseRecord_toStringSerializesNulls() { + Assert.assertTrue(new TokenizeResponseRecord("value1", "group1", "tok-abc", 200, null) .toString().contains("\"error\":null")); } - // ── TokenizeResponseRecord / BulkTokenizeResponseRecord ────────────────── - - @Test - public void testTokenizeResponseRecord_gettersReturnConstructorValues() { - List tokens = Collections.singletonList( - new TokenizeResponseToken("group1", "tok-abc", 200, null)); - TokenizeResponseRecord record = new TokenizeResponseRecord("value1", tokens); - Assert.assertEquals("value1", record.getValue()); - Assert.assertEquals(tokens, record.getTokens()); - } - @Test public void testBulkTokenizeResponseRecord_carriesIndexAndIsATokenizeResponseRecord() { - BulkTokenizeResponseRecord record = new BulkTokenizeResponseRecord(7, "value1", - Collections.singletonList(new TokenizeResponseToken("group1", "tok-abc", 200, null))); + BulkTokenizeResponseRecord record = new BulkTokenizeResponseRecord( + 7, "value1", "group1", "tok-abc", 200, null, null); Assert.assertEquals(7, record.getIndex()); Assert.assertEquals("value1", record.getValue()); Assert.assertTrue(record instanceof TokenizeResponseRecord);