From a951dd2d758b7992921b5bc8a8821537dcc58039 Mon Sep 17 00:00:00 2001 From: "Justin \"J.R.\" Hill" Date: Tue, 7 Nov 2023 17:25:24 -0800 Subject: [PATCH] test: add tests on batchCheck() --- .../api/client/ClientBatchCheckResponse.java | 80 +++++++++++++++++++ .../openfga/sdk/api/client/OpenFgaClient.java | 29 ++++--- .../sdk/api/client/OpenFgaClientTest.java | 53 ++++++++++++ 3 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 src/main/java/dev/openfga/sdk/api/client/ClientBatchCheckResponse.java diff --git a/src/main/java/dev/openfga/sdk/api/client/ClientBatchCheckResponse.java b/src/main/java/dev/openfga/sdk/api/client/ClientBatchCheckResponse.java new file mode 100644 index 0000000..4dda625 --- /dev/null +++ b/src/main/java/dev/openfga/sdk/api/client/ClientBatchCheckResponse.java @@ -0,0 +1,80 @@ +/* + * OpenFGA + * A high performance and flexible authorization/permission engine built for developers and inspired by Google Zanzibar. + * + * The version of the OpenAPI document: 0.1 + * Contact: community@openfga.dev + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package dev.openfga.sdk.api.client; + +import dev.openfga.sdk.api.model.CheckResponse; +import java.util.List; +import java.util.Map; + +public class ClientBatchCheckResponse extends CheckResponse { + private final ClientCheckRequest request; + private final Throwable throwable; + private final int statusCode; + private final Map> headers; + private final String rawResponse; + + public ClientBatchCheckResponse( + ClientCheckRequest request, ClientCheckResponse clientCheckResponse, Throwable throwable) { + this.request = request; + this.throwable = throwable; + + this.statusCode = clientCheckResponse.getStatusCode(); + this.headers = clientCheckResponse.getHeaders(); + this.rawResponse = clientCheckResponse.getRawResponse(); + this.setAllowed(clientCheckResponse.getAllowed()); + this.setResolution(clientCheckResponse.getResolution()); + } + + public ClientCheckRequest getRequest() { + return request; + } + + /** + * Returns the result of the check. + *

+ * If the HTTP request was unsuccessful, this result will be null. If this is the case, you can examine the + * original request with {@link ClientBatchCheckResponse#getRequest()} and the exception with + * {@link ClientBatchCheckResponse#getThrowable()}. + * + * @return the check result. Is null if the HTTP request was unsuccessful. + */ + @Override + public Boolean getAllowed() { + return super.getAllowed(); + } + + /** + * Returns the caught exception if the HTTP request was unsuccessful. + *

+ * If the HTTP request was unsuccessful, this result will be null. If this is the case, you can examine the + * original request with {@link ClientBatchCheckResponse#getRequest()} and the exception with + * {@link ClientBatchCheckResponse#getThrowable()}. + * + * @return the caught exception. Is null if the HTTP request was successful. + */ + public Throwable getThrowable() { + return throwable; + } + + public int getStatusCode() { + return statusCode; + } + + public Map> getHeaders() { + return headers; + } + + public String getRawResponse() { + return rawResponse; + } +} diff --git a/src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java b/src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java index 3635e88..d14be26 100644 --- a/src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java +++ b/src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java @@ -20,9 +20,7 @@ import dev.openfga.sdk.errors.*; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Executors; +import java.util.concurrent.*; import java.util.function.Consumer; public class OpenFgaClient { @@ -387,26 +385,37 @@ public CompletableFuture check(ClientCheckRequest request, * * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace */ - public CompletableFuture> batchCheck( - List requests, ClientBatchCheckOptions options) { + public CompletableFuture> batchCheck( + List requests, ClientBatchCheckOptions options) throws FgaInvalidParameterException { + configuration.assertValid(); + configuration.assertValidStoreId(); + int maxParallelRequests = options.getMaxParallelRequests() != null ? options.getMaxParallelRequests() : DEFAULT_MAX_METHOD_PARALLEL_REQS; var executor = Executors.newWorkStealingPool(maxParallelRequests); + var latch = new CountDownLatch(requests.size()); - var responses = new ConcurrentLinkedQueue(); + var responses = new ConcurrentLinkedQueue(); final var clientCheckOptions = options.asClientCheckOptions(); Consumer singleClientCheckRequest = - request -> call(() -> this.check(request, clientCheckOptions)).thenApply(responses::add); + request -> call(() -> this.check(request, clientCheckOptions)).handle((response, exception) -> { + try { + responses.add(new ClientBatchCheckResponse(request, response, exception)); + } finally { + latch.countDown(); + } + return true; + }); requests.forEach(request -> executor.execute(() -> singleClientCheckRequest.accept(request))); try { - executor.wait(); + latch.await(); return CompletableFuture.completedFuture(new ArrayList<>(responses)); - } catch (InterruptedException e) { + } catch (Exception e) { return CompletableFuture.failedFuture(e); } } @@ -573,7 +582,7 @@ private interface CheckedInvocation { private CompletableFuture call(CheckedInvocation action) { try { return action.call(); - } catch (FgaInvalidParameterException | ApiException exception) { + } catch (Exception exception) { return CompletableFuture.failedFuture(exception); } } diff --git a/src/test/java/dev/openfga/sdk/api/client/OpenFgaClientTest.java b/src/test/java/dev/openfga/sdk/api/client/OpenFgaClientTest.java index d0cf513..5c5c957 100644 --- a/src/test/java/dev/openfga/sdk/api/client/OpenFgaClientTest.java +++ b/src/test/java/dev/openfga/sdk/api/client/OpenFgaClientTest.java @@ -25,6 +25,8 @@ import java.time.Duration; import java.util.List; import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -1309,6 +1311,57 @@ public void check_500() throws Exception { "{\"code\":\"internal_error\",\"message\":\"Internal Server Error\"}", exception.getResponseData()); } + /** + * Check whether a user is authorized to access an object. + */ + @Test + public void batchCheck() throws Exception { + // Given + String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID); + String expectedBody = String.format( + "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}", + DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}"); + ClientCheckRequest request = new ClientCheckRequest() + ._object(DEFAULT_OBJECT) + .relation(DEFAULT_RELATION) + .user(DEFAULT_USER); + ClientBatchCheckOptions options = new ClientBatchCheckOptions().authorizationModelId(DEFAULT_AUTH_MODEL_ID); + + // When + List response = + fga.batchCheck(List.of(request), options).get(); + + // Then + mockHttpClient.verify().post(postUrl).withBody(is(expectedBody)).called(1); + assertEquals(Boolean.TRUE, response.get(0).getAllowed()); + } + + @Test + public void batchCheck_twentyTimes() throws Exception { + // Given + String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID); + String expectedBody = String.format( + "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}", + DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER); + mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}"); + List requests = IntStream.range(0, 20) + .mapToObj(ignored -> new ClientCheckRequest() + ._object(DEFAULT_OBJECT) + .relation(DEFAULT_RELATION) + .user(DEFAULT_USER)) + .collect(Collectors.toList()); + ClientBatchCheckOptions options = new ClientBatchCheckOptions().authorizationModelId(DEFAULT_AUTH_MODEL_ID); + + // When + List responses = + fga.batchCheck(requests, options).get(); + + // Then + mockHttpClient.verify().post(postUrl).withBody(is(expectedBody)).called(20); + responses.forEach(response -> assertEquals(Boolean.TRUE, response.getAllowed())); + } + /** * Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason * about and debug a certain relationship.