diff --git a/config/clients/java/config.overrides.json b/config/clients/java/config.overrides.json index 0c66510c..b3274014 100644 --- a/config/clients/java/config.overrides.json +++ b/config/clients/java/config.overrides.json @@ -181,6 +181,14 @@ "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientWriteResponse.java", "templateType": "SupportingFiles" }, + "src/main/api/client/model/ClientWriteSingleResponse.java.mustache": { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientWriteSingleResponse.java", + "templateType": "SupportingFiles" + }, + "src/main/api/client/model/ClientWriteStatus.java.mustache": { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientWriteStatus.java", + "templateType": "SupportingFiles" + }, "src/main/api/client/ClientAssertion.java.mustache": { "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientAssertion.java", "templateType": "SupportingFiles" @@ -429,6 +437,14 @@ "destinationFilename": "src/test/java/dev/openfga/sdk/api/client/OpenFgaClientTest.java", "templateType": "SupportingFiles" }, + "src/test/api/client/model/ClientWriteStatusTest.java.mustache": { + "destinationFilename": "src/test/java/dev/openfga/sdk/api/client/model/ClientWriteStatusTest.java", + "templateType": "SupportingFiles" + }, + "src/test/api/client/model/ClientWriteSingleResponseTest.java.mustache": { + "destinationFilename": "src/test/java/dev/openfga/sdk/api/client/model/ClientWriteSingleResponseTest.java", + "templateType": "SupportingFiles" + }, "src/test/api/configuration/ClientCredentialsTest.java.mustache": { "destinationFilename": "src/test/java/dev/openfga/sdk/api/configuration/ClientCredentialsTest.java", "templateType": "SupportingFiles" diff --git a/config/clients/java/template/example/example1/src/main/java/dev/openfga/sdk/example/Example1.java b/config/clients/java/template/example/example1/src/main/java/dev/openfga/sdk/example/Example1.java index 931b97e9..3961f84f 100644 --- a/config/clients/java/template/example/example1/src/main/java/dev/openfga/sdk/example/Example1.java +++ b/config/clients/java/template/example/example1/src/main/java/dev/openfga/sdk/example/Example1.java @@ -18,9 +18,9 @@ public void run(String apiUrl) throws Exception { if (System.getenv("FGA_CLIENT_ID") != null) { credentials = new Credentials(new ClientCredentials() .apiAudience(System.getenv("FGA_API_AUDIENCE")) - .apiTokenIssuer(System.getenv("FGA_TOKEN_ISSUER")) - .clientId("FGA_CLIENT_ID") - .clientSecret("FGA_CLIENT_SECRET")); + .apiTokenIssuer(System.getenv("FGA_API_TOKEN_ISSUER")) + .clientId(System.getenv("FGA_CLIENT_ID")) + .clientSecret(System.getenv("FGA_CLIENT_SECRET"))); } else { System.out.println("Proceeding with no credentials (expecting localhost)"); } @@ -102,10 +102,20 @@ public void run(String apiUrl) throws Exception { fgaClient .write( new ClientWriteRequest() - .writes(List.of(new ClientTupleKey() - .user("user:anne") - .relation("writer") - ._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"))), + .writes(List.of( + new ClientTupleKey() + .user("user:anne") + .relation("writer") + ._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"), + new ClientTupleKey() + .user("user:anne") + .relation("writer") + ._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"), // duplicate + new ClientTupleKey() + .user("user:anne") + .relation("owner") + ._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a") // different relation + )), new ClientWriteOptions() .disableTransactions(true) .authorizationModelId(authorizationModel.getAuthorizationModelId())) diff --git a/config/clients/java/template/settings.gradle.mustache b/config/clients/java/template/settings.gradle.mustache index 448dc076..5a406db8 100644 --- a/config/clients/java/template/settings.gradle.mustache +++ b/config/clients/java/template/settings.gradle.mustache @@ -1 +1,8 @@ +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} + rootProject.name = '{{artifactId}}' \ No newline at end of file diff --git a/config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache b/config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache index 630a0ed8..e0fa7062 100644 --- a/config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache +++ b/config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache @@ -342,7 +342,51 @@ public class OpenFgaClient { /** * Write - Create or delete relationship tuples + * + *

This method can operate in two modes depending on the options provided:

+ * + *

Transactional Mode (default)

+ *

When {@code options.disableTransactions()} is false or not set:

+ * + * + *

Non-Transactional Mode

+ *

When {@code options.disableTransactions()} is true:

+ * + * + *

Non-Transactional Success Scenarios:

+ * + * + *

Non-Transactional Exception Scenarios:

+ * + * + *

Caller Responsibilities:

+ * * + * @param request The write request containing tuples to create or delete + * @return A CompletableFuture containing the write response with individual tuple results * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace */ public CompletableFuture write(ClientWriteRequest request) @@ -352,7 +396,52 @@ public class OpenFgaClient { /** * Write - Create or delete relationship tuples + * + *

This method can operate in two modes depending on the options provided:

+ * + *

Transactional Mode (default)

+ *

When {@code options.disableTransactions()} is false or not set:

+ * + * + *

Non-Transactional Mode

+ *

When {@code options.disableTransactions()} is true:

+ * + * + *

Non-Transactional Success Scenarios:

+ * + * + *

Non-Transactional Exception Scenarios:

+ * + * + *

Caller Responsibilities:

+ * * + * @param request The write request containing tuples to create or delete + * @param options Write options including transaction mode and chunk size settings + * @return A CompletableFuture containing the write response with individual tuple results * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace */ public CompletableFuture write(ClientWriteRequest request, ClientWriteOptions options) @@ -391,9 +480,51 @@ public class OpenFgaClient { var overrides = new ConfigurationOverride().addHeaders(options); - return call(() -> api.write(storeId, body, overrides)).thenApply(ClientWriteResponse::new); + return call(() -> api.write(storeId, body, overrides)).thenApply(apiResponse -> { + // For transaction-based writes, all tuples are successful if the call succeeds + List writeResponses = writeTuples != null ? + writeTuples.stream() + .map(tuple -> new ClientWriteSingleResponse(tuple.asTupleKey(), ClientWriteStatus.SUCCESS)) + .collect(Collectors.toList()) : new ArrayList<>(); + + List deleteResponses = deleteTuples != null ? + deleteTuples.stream() + .map(tuple -> new ClientWriteSingleResponse( + new TupleKey().user(tuple.getUser()).relation(tuple.getRelation())._object(tuple.getObject()), + ClientWriteStatus.SUCCESS)) + .collect(Collectors.toList()) : new ArrayList<>(); + + return new ClientWriteResponse(writeResponses, deleteResponses); + }); } + /** + * Non-transactional write implementation that processes tuples in parallel chunks. + * + *

This method implements the error isolation behavior where individual chunk failures + * do not prevent other chunks from being processed. It performs the following steps:

+ * + *
    + *
  1. Splits writes and deletes into chunks based on {@code transactionChunkSize}
  2. + *
  3. Processes each chunk as an independent transaction in parallel
  4. + *
  5. Collects results from all chunks, marking individual tuples as SUCCESS or FAILURE
  6. + *
  7. Re-throws authentication errors immediately to stop all processing
  8. + *
  9. Converts other errors to FAILURE status for affected tuples
  10. + *
+ * + *

The method guarantees that:

+ *
    + *
  • Authentication errors are never swallowed (they stop all processing)
  • + *
  • Other errors are isolated to their respective chunks
  • + *
  • The response always contains a result for every input tuple
  • + *
  • The order of results matches the order of input tuples
  • + *
+ * + * @param storeId The store ID to write to + * @param request The write request containing tuples to process + * @param writeOptions Options including chunk size and headers + * @return CompletableFuture with results for all tuples, marking each as SUCCESS or FAILURE + */ private CompletableFuture writeNonTransaction( String storeId, ClientWriteRequest request, ClientWriteOptions writeOptions) { @@ -409,29 +540,95 @@ public class OpenFgaClient { .putIfAbsent(CLIENT_BULK_REQUEST_ID_HEADER, randomUUID().toString()); int chunkSize = options.getTransactionChunkSize(); - var writeTransactions = chunksOf(chunkSize, request.getWrites()).map(ClientWriteRequest::ofWrites); - var deleteTransactions = chunksOf(chunkSize, request.getDeletes()).map(ClientWriteRequest::ofDeletes); - - var transactions = Stream.concat(writeTransactions, deleteTransactions).collect(Collectors.toList()); - - if (transactions.isEmpty()) { - var emptyTransaction = new ClientWriteRequest().writes(null).deletes(null); - return this.writeTransactions(storeId, emptyTransaction, writeOptions); + + List>> writeFutures = new ArrayList<>(); + List>> deleteFutures = new ArrayList<>(); + + // Handle writes + if (request.getWrites() != null && !request.getWrites().isEmpty()) { + var writeChunks = chunksOf(chunkSize, request.getWrites()).collect(Collectors.toList()); + + for (List chunk : writeChunks) { + CompletableFuture> chunkFuture = + this.writeTransactions(storeId, ClientWriteRequest.ofWrites(chunk), options) + .thenApply(response -> { + // On success, mark all tuples in this chunk as successful + return chunk.stream() + .map(tuple -> new ClientWriteSingleResponse(tuple.asTupleKey(), ClientWriteStatus.SUCCESS)) + .collect(Collectors.toList()); + }) + .exceptionally(exception -> { + // Re-throw authentication errors to stop all processing + Throwable cause = exception instanceof CompletionException ? exception.getCause() : exception; + if (cause instanceof FgaApiAuthenticationError) { + throw new CompletionException(cause); + } + + // On failure, mark all tuples in this chunk as failed, but continue processing other chunks + return chunk.stream() + .map(tuple -> new ClientWriteSingleResponse(tuple.asTupleKey(), ClientWriteStatus.FAILURE, + cause instanceof Exception ? (Exception) cause : new Exception(cause))) + .collect(Collectors.toList()); + }); + + writeFutures.add(chunkFuture); + } } - var futureResponse = this.writeTransactions(storeId, transactions.get(0), options); - - for (int i = 1; i < transactions.size(); i++) { - final int index = i; // Must be final in this scope for closure. - - // The resulting completable future of this chain will result in either: - // 1. The first exception thrown in a failed completion. Other thenCompose() will not be evaluated. - // 2. The final successful ClientWriteResponse. - futureResponse = futureResponse.thenCompose( - _response -> this.writeTransactions(storeId, transactions.get(index), options)); + // Handle deletes + if (request.getDeletes() != null && !request.getDeletes().isEmpty()) { + var deleteChunks = chunksOf(chunkSize, request.getDeletes()).collect(Collectors.toList()); + + for (List chunk : deleteChunks) { + CompletableFuture> chunkFuture = + this.writeTransactions(storeId, ClientWriteRequest.ofDeletes(chunk), options) + .thenApply(response -> { + // On success, mark all tuples in this chunk as successful + return chunk.stream() + .map(tuple -> new ClientWriteSingleResponse( + new TupleKey().user(tuple.getUser()).relation(tuple.getRelation())._object(tuple.getObject()), + ClientWriteStatus.SUCCESS)) + .collect(Collectors.toList()); + }) + .exceptionally(exception -> { + // Re-throw authentication errors to stop all processing + Throwable cause = exception instanceof CompletionException ? exception.getCause() : exception; + if (cause instanceof FgaApiAuthenticationError) { + throw new CompletionException(cause); + } + + // On failure, mark all tuples in this chunk as failed, but continue processing other chunks + return chunk.stream() + .map(tuple -> new ClientWriteSingleResponse( + new TupleKey().user(tuple.getUser()).relation(tuple.getRelation())._object(tuple.getObject()), + ClientWriteStatus.FAILURE, + cause instanceof Exception ? (Exception) cause : new Exception(cause))) + .collect(Collectors.toList()); + }); + + deleteFutures.add(chunkFuture); + } } - return futureResponse; + // Combine all futures + CompletableFuture> allWritesFuture = + writeFutures.isEmpty() ? CompletableFuture.completedFuture(new ArrayList<>()) : + CompletableFuture.allOf(writeFutures.toArray(new CompletableFuture[0])) + .thenApply(v -> writeFutures.stream() + .map(CompletableFuture::join) + .flatMap(List::stream) + .collect(Collectors.toList())); + + CompletableFuture> allDeletesFuture = + deleteFutures.isEmpty() ? CompletableFuture.completedFuture(new ArrayList<>()) : + CompletableFuture.allOf(deleteFutures.toArray(new CompletableFuture[0])) + .thenApply(v -> deleteFutures.stream() + .map(CompletableFuture::join) + .flatMap(List::stream) + .collect(Collectors.toList())); + + return CompletableFuture.allOf(allWritesFuture, allDeletesFuture) + .thenApply(v -> new ClientWriteResponse(allWritesFuture.join(), allDeletesFuture.join())); } private Stream> chunksOf(int chunkSize, List list) { @@ -483,7 +680,12 @@ public class OpenFgaClient { var overrides = new ConfigurationOverride().addHeaders(options); - return call(() -> api.write(storeId, body, overrides)).thenApply(ClientWriteResponse::new); + return call(() -> api.write(storeId, body, overrides)).thenApply(apiResponse -> { + List writeResponses = tupleKeys.stream() + .map(tuple -> new ClientWriteSingleResponse(tuple.asTupleKey(), ClientWriteStatus.SUCCESS)) + .collect(Collectors.toList()); + return new ClientWriteResponse(writeResponses, new ArrayList<>()); + }); } /** @@ -518,7 +720,14 @@ public class OpenFgaClient { var overrides = new ConfigurationOverride().addHeaders(options); - return call(() -> api.write(storeId, body, overrides)).thenApply(ClientWriteResponse::new); + return call(() -> api.write(storeId, body, overrides)).thenApply(apiResponse -> { + List deleteResponses = tupleKeys.stream() + .map(tuple -> new ClientWriteSingleResponse( + new TupleKey().user(tuple.getUser()).relation(tuple.getRelation())._object(tuple.getObject()), + ClientWriteStatus.SUCCESS)) + .collect(Collectors.toList()); + return new ClientWriteResponse(new ArrayList<>(), deleteResponses); + }); } /* ********************** diff --git a/config/clients/java/template/src/main/api/client/model/ClientWriteResponse.java.mustache b/config/clients/java/template/src/main/api/client/model/ClientWriteResponse.java.mustache index 1883f647..7535d6a2 100644 --- a/config/clients/java/template/src/main/api/client/model/ClientWriteResponse.java.mustache +++ b/config/clients/java/template/src/main/api/client/model/ClientWriteResponse.java.mustache @@ -4,16 +4,29 @@ package {{clientPackage}}.model; import {{clientPackage}}.ApiResponse; import java.util.List; import java.util.Map; +import java.util.Collections; public class ClientWriteResponse { private final int statusCode; private final Map> headers; private final String rawResponse; + private final List writes; + private final List deletes; public ClientWriteResponse(ApiResponse apiResponse) { this.statusCode = apiResponse.getStatusCode(); this.headers = apiResponse.getHeaders(); this.rawResponse = apiResponse.getRawResponse(); + this.writes = Collections.emptyList(); + this.deletes = Collections.emptyList(); + } + + public ClientWriteResponse(List writes, List deletes) { + this.statusCode = 200; + this.headers = Collections.emptyMap(); + this.rawResponse = ""; + this.writes = writes != null ? writes : Collections.emptyList(); + this.deletes = deletes != null ? deletes : Collections.emptyList(); } public int getStatusCode() { @@ -27,4 +40,12 @@ public class ClientWriteResponse { public String getRawResponse() { return rawResponse; } + + public List getWrites() { + return writes; + } + + public List getDeletes() { + return deletes; + } } diff --git a/config/clients/java/template/src/main/api/client/model/ClientWriteSingleResponse.java.mustache b/config/clients/java/template/src/main/api/client/model/ClientWriteSingleResponse.java.mustache new file mode 100644 index 00000000..5b682f31 --- /dev/null +++ b/config/clients/java/template/src/main/api/client/model/ClientWriteSingleResponse.java.mustache @@ -0,0 +1,32 @@ +{{>licenseInfo}} +package {{clientPackage}}.model; + +import {{modelPackage}}.TupleKey; + +public class ClientWriteSingleResponse { + private final TupleKey tupleKey; + private final ClientWriteStatus status; + private final Exception error; + + public ClientWriteSingleResponse(TupleKey tupleKey, ClientWriteStatus status) { + this(tupleKey, status, null); + } + + public ClientWriteSingleResponse(TupleKey tupleKey, ClientWriteStatus status, Exception error) { + this.tupleKey = tupleKey; + this.status = status; + this.error = error; + } + + public TupleKey getTupleKey() { + return tupleKey; + } + + public ClientWriteStatus getStatus() { + return status; + } + + public Exception getError() { + return error; + } +} diff --git a/config/clients/java/template/src/main/api/client/model/ClientWriteStatus.java.mustache b/config/clients/java/template/src/main/api/client/model/ClientWriteStatus.java.mustache new file mode 100644 index 00000000..248962a4 --- /dev/null +++ b/config/clients/java/template/src/main/api/client/model/ClientWriteStatus.java.mustache @@ -0,0 +1,22 @@ +{{>licenseInfo}} +package {{clientPackage}}.model; + +public enum ClientWriteStatus { + SUCCESS("success"), + FAILURE("failure"); + + private final String value; + + ClientWriteStatus(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/config/clients/java/template/src/test/api/client/OpenFgaClientTest.java.mustache b/config/clients/java/template/src/test/api/client/OpenFgaClientTest.java.mustache index 6960e776..b51e165e 100644 --- a/config/clients/java/template/src/test/api/client/OpenFgaClientTest.java.mustache +++ b/config/clients/java/template/src/test/api/client/OpenFgaClientTest.java.mustache @@ -1144,6 +1144,13 @@ public class OpenFgaClientTest { // Then mockHttpClient.verify().post(postPath).withBody(is(expectedBody)).called(1); assertEquals(200, response.getStatusCode()); + + // Verify new response structure for transaction-based writes + assertEquals(1, response.getWrites().size()); + assertEquals(0, response.getDeletes().size()); + assertEquals(ClientWriteStatus.SUCCESS, response.getWrites().get(0).getStatus()); + assertEquals(DEFAULT_USER, response.getWrites().get(0).getTupleKey().getUser()); + assertNull(response.getWrites().get(0).getError()); } /** @@ -1245,18 +1252,18 @@ public class OpenFgaClientTest { } @Test - public void writeTest_nonTransactionsWithFailure() { + public void writeTest_nonTransactionsWithFailure() throws Exception { // Given String postPath = "https://api.fga.example/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write"; String firstUser = "user:first"; String failedUser = "user:SECOND"; - String skippedUser = "user:third"; + String thirdUser = "user:third"; Function writeBody = user -> String.format( "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}", user, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); mockHttpClient .onPost(postPath) - .withBody(isOneOf(writeBody.apply(firstUser), writeBody.apply(skippedUser))) + .withBody(isOneOf(writeBody.apply(firstUser), writeBody.apply(thirdUser))) .withHeader(CLIENT_METHOD_HEADER, "Write") .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) .doReturn(200, EMPTY_RESPONSE_BODY); @@ -1267,7 +1274,7 @@ public class OpenFgaClientTest { .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) .doReturn(400, "{\"code\":\"validation_error\",\"message\":\"Generic validation error\"}"); ClientWriteRequest request = new ClientWriteRequest() - .writes(Stream.of(firstUser, failedUser, skippedUser) + .writes(Stream.of(firstUser, failedUser, thirdUser) .map(user -> new ClientTupleKey() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) @@ -1278,8 +1285,7 @@ public class OpenFgaClientTest { new ClientWriteOptions().disableTransactions(true).transactionChunkSize(1); // When - var execException = assertThrows( - ExecutionException.class, () -> fga.write(request, options).get()); + ClientWriteResponse response = fga.write(request, options).get(); // Then mockHttpClient @@ -1299,12 +1305,28 @@ public class OpenFgaClientTest { mockHttpClient .verify() .post(postPath) - .withBody(is(writeBody.apply(skippedUser))) + .withBody(is(writeBody.apply(thirdUser))) .withHeader(CLIENT_METHOD_HEADER, "Write") .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) - .called(0); - var exception = assertInstanceOf(FgaApiValidationError.class, execException.getCause()); - assertEquals(400, exception.getStatusCode()); + .called(1); + + // Verify response structure + assertEquals(3, response.getWrites().size()); + assertEquals(0, response.getDeletes().size()); + + // Check individual tuple statuses + var writes = response.getWrites(); + assertEquals(ClientWriteStatus.SUCCESS, writes.get(0).getStatus()); + assertEquals(firstUser, writes.get(0).getTupleKey().getUser()); + assertNull(writes.get(0).getError()); + + assertEquals(ClientWriteStatus.FAILURE, writes.get(1).getStatus()); + assertEquals(failedUser, writes.get(1).getTupleKey().getUser()); + assertNotNull(writes.get(1).getError()); + + assertEquals(ClientWriteStatus.SUCCESS, writes.get(2).getStatus()); + assertEquals(thirdUser, writes.get(2).getTupleKey().getUser()); + assertNull(writes.get(2).getError()); } @Test diff --git a/config/clients/java/template/src/test/api/client/model/ClientWriteSingleResponseTest.java.mustache b/config/clients/java/template/src/test/api/client/model/ClientWriteSingleResponseTest.java.mustache new file mode 100644 index 00000000..74beffa0 --- /dev/null +++ b/config/clients/java/template/src/test/api/client/model/ClientWriteSingleResponseTest.java.mustache @@ -0,0 +1,38 @@ +{{>licenseInfo}} +package {{clientPackage}}.model; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import {{modelPackage}}.TupleKey; + +public class ClientWriteSingleResponseTest { + + @Test + public void testSuccessfulResponse() { + TupleKey tupleKey = new TupleKey() + .user("user:alice") + .relation("viewer") + ._object("document:test"); + + ClientWriteSingleResponse response = new ClientWriteSingleResponse(tupleKey, ClientWriteStatus.SUCCESS); + + assertEquals(tupleKey, response.getTupleKey()); + assertEquals(ClientWriteStatus.SUCCESS, response.getStatus()); + assertNull(response.getError()); + } + + @Test + public void testFailedResponse() { + TupleKey tupleKey = new TupleKey() + .user("user:bob") + .relation("editor") + ._object("document:test"); + Exception error = new RuntimeException("Test error"); + + ClientWriteSingleResponse response = new ClientWriteSingleResponse(tupleKey, ClientWriteStatus.FAILURE, error); + + assertEquals(tupleKey, response.getTupleKey()); + assertEquals(ClientWriteStatus.FAILURE, response.getStatus()); + assertEquals(error, response.getError()); + } +} diff --git a/config/clients/java/template/src/test/api/client/model/ClientWriteStatusTest.java.mustache b/config/clients/java/template/src/test/api/client/model/ClientWriteStatusTest.java.mustache new file mode 100644 index 00000000..cac26aa0 --- /dev/null +++ b/config/clients/java/template/src/test/api/client/model/ClientWriteStatusTest.java.mustache @@ -0,0 +1,20 @@ +{{>licenseInfo}} +package {{clientPackage}}.model; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +public class ClientWriteStatusTest { + + @Test + public void testSuccessValue() { + assertEquals("success", ClientWriteStatus.SUCCESS.getValue()); + assertEquals("success", ClientWriteStatus.SUCCESS.toString()); + } + + @Test + public void testFailureValue() { + assertEquals("failure", ClientWriteStatus.FAILURE.getValue()); + assertEquals("failure", ClientWriteStatus.FAILURE.toString()); + } +}