diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ac858eb3..21bdeb110 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: DAPR_RUNTIME_VER: 1.0.0-rc.1 DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/a60221e96406a145ab22e454eec6642961725f5c/install/install.sh DAPR_CLI_REF: - DAPR_REF: 83f5c45362b0c577139b1887276d7cf1b7308506 + DAPR_REF: 79688c8ce802b3b31382da97422db63a27821599 OSSRH_USER_TOKEN: ${{ secrets.OSSRH_USER_TOKEN }} OSSRH_PWD_TOKEN: ${{ secrets.OSSRH_PWD_TOKEN }} GPG_KEY: ${{ secrets.GPG_KEY }} diff --git a/README.md b/README.md index 7a3ed7013..9a8175ddd 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ Try the following examples to learn more about Dapr's Java SDK: * [Actors over Http](./examples/src/main/java/io/dapr/examples/actors/http) * [Secrets management](./examples/src/main/java/io/dapr/examples/secrets) * [Distributed tracing with OpenTelemetry SDK](./examples/src/main/java/io/dapr/examples/tracing) +* [Exception handling](./examples/src/main/java/io/dapr/examples/exception) #### API Documentation @@ -221,30 +222,12 @@ Now you can go to your IDE (like Eclipse, for example) and debug your Java appli Calls to Dapr's APIs on `http://127.0.0.1:3500/*` should work now and trigger breakpoints in your code. -#### Creating and publishing the artifacts to Nexus Repository -In case you need to publish Dapr's SDK to a private Nexus repo, run the command below from the project's root directory: +#### Exception handling -```sh -mvn package -mvn deploy:deploy-file -DgeneratePom=false -DrepositoryId=nexus -Durl=http://localhost:8081/repository/maven-releases -DpomFile=pom.xml -Dfile=target/dapr-sdk-0.3.0.jar -``` - -For more documentation reference: - -https://maven.apache.org/plugins/maven-deploy-plugin - -https://help.sonatype.com/repomanager3/user-interface/uploading-components +All exceptions thrown from the SDK are instances of `DaprException`. `DaprException` extends from `RuntimeException`, making it compatible with Project Reactor. See [example](./examples/src/main/java/io/dapr/examples/exception) for more details. ### Development -#### Maven Module version management -When releasing a new version of this SDK you must increase the version of all modules and pom files, so run the following commands: - -```sh -mvn versions:set -DnewVersion="0.1.0-preview02" -mvn versions:commit -``` - #### Update proto files Change the properties below in [pom.xml](./pom.xml) to point to the desired reference URL in Git. Avoid pointing to master branch since it can change over time and create unpredictable behavior in the build. diff --git a/examples/src/main/java/io/dapr/examples/bindings/http/OutputBindingExample.java b/examples/src/main/java/io/dapr/examples/bindings/http/OutputBindingExample.java index 3844d91dc..2a91022a4 100644 --- a/examples/src/main/java/io/dapr/examples/bindings/http/OutputBindingExample.java +++ b/examples/src/main/java/io/dapr/examples/bindings/http/OutputBindingExample.java @@ -8,8 +8,6 @@ package io.dapr.examples.bindings.http; import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; -import java.io.IOException; - /** * Service for output binding example. * 1. From your repo root, build and install jars: @@ -38,7 +36,7 @@ public class OutputBindingExample { * @param args Not used. */ @SuppressWarnings("checkstyle:AbbreviationAsWordInName") - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { int count = 0; diff --git a/examples/src/main/java/io/dapr/examples/bindings/http/README.md b/examples/src/main/java/io/dapr/examples/bindings/http/README.md index 27dd2bbee..572c53fb9 100644 --- a/examples/src/main/java/io/dapr/examples/bindings/http/README.md +++ b/examples/src/main/java/io/dapr/examples/bindings/http/README.md @@ -109,7 +109,7 @@ public class OutputBindingExample{ static final String BINDING_OPERATION = "create"; ///... - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { int count = 0; diff --git a/examples/src/main/java/io/dapr/examples/exception/Client.java b/examples/src/main/java/io/dapr/examples/exception/Client.java new file mode 100644 index 000000000..97aa07f68 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/exception/Client.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.exception; + +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.exceptions.DaprException; + +/** + * 1. Build and install jars: + * mvn clean install + * 2. Go into examples: + * cd examples + * 3. send a message to be saved as state: + * dapr run --components-path ./components --dapr-http-port 3006 -- \ + * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.exception.Client + */ +public class Client { + + /** + * Executes the sate actions. + * @param args messages to be sent as state value. + */ + public static void main(String[] args) throws Exception { + try (DaprClient client = new DaprClientBuilder().build()) { + + try { + client.getState("Unknown state store", "myKey", String.class).block(); + } catch (DaprException exception) { + System.out.println("Error code: " + exception.getErrorCode()); + System.out.println("Error message: " + exception.getMessage()); + + exception.printStackTrace(); + } + + System.out.println("Done"); + } + } +} diff --git a/examples/src/main/java/io/dapr/examples/exception/README.md b/examples/src/main/java/io/dapr/examples/exception/README.md new file mode 100644 index 000000000..6a669ad08 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/exception/README.md @@ -0,0 +1,68 @@ +## Exception handling sample + +This sample illustrates how to handle exceptions in Dapr. + +## Pre-requisites + +* [Dapr and Dapr Cli](https://docs.dapr.io/getting-started/install-dapr/). +* Java JDK 11 (or greater): [Oracle JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) or [OpenJDK](https://jdk.java.net/13/). +* [Apache Maven](https://maven.apache.org/install.html) version 3.x. + +### Checking out the code + +Clone this repository: + +```sh +git clone https://github.com/dapr/java-sdk.git +cd java-sdk +``` + +Then build the Maven project: + +```sh +# make sure you are in the `java-sdk` directory. +mvn install +``` + +Then get into the examples directory: +```sh +cd examples +``` + +### Running the StateClient +This example uses the Java SDK Dapr client in order perform an invalid operartion, causing Dapr runtime to return an error. See the code snippet bellow: + +```java +public class Client { + + public static void main(String[] args) throws Exception { + try (DaprClient client = new DaprClientBuilder().build()) { + + try { + client.getState("Unknown state store", "myKey", String.class).block(); + } catch (DaprException exception) { + System.out.println("Error code: " + exception.getErrorCode()); + System.out.println("Error message: " + exception.getMessage()); + + exception.printStackTrace(); + } + + System.out.println("Done"); + } + } + +} +``` +The code uses the `DaprClient` created by the `DaprClientBuilder`. It tries to get a state from state store but provides an unknown state store. It causes Dapr sidecar to return error, which is converted to a `DaprException` to the application. To be compatible with Project Reactor, `DaprException` extends from `RuntimeException` - making it an unchecked exception. + +The Dapr client is also within a try-with-resource block to properly close the client at the end. + +### Running the example + +Run this example with the following command: +```sh +dapr run --components-path ./components --dapr-http-port 3006 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.exception.Client +``` +Once running, the OutputBindingExample should print the output as follows: + +![stateouput](../../../../../resources/img/exception.png) diff --git a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java index d8c9a921a..1a7c6a3e0 100644 --- a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java +++ b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java @@ -9,8 +9,6 @@ import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; import io.dapr.client.domain.HttpExtension; -import java.io.IOException; - /** * 1. Build and install jars: * mvn clean install @@ -25,7 +23,7 @@ public class HelloWorldClient { * * @param args Array of messages to be sent. */ - public static void main(String[] args) throws InterruptedException, IOException { + public static void main(String[] args) throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { String serviceAppId = "hellogrpc"; diff --git a/examples/src/main/java/io/dapr/examples/invoke/grpc/README.md b/examples/src/main/java/io/dapr/examples/invoke/grpc/README.md index e1338742f..c86ce3de3 100644 --- a/examples/src/main/java/io/dapr/examples/invoke/grpc/README.md +++ b/examples/src/main/java/io/dapr/examples/invoke/grpc/README.md @@ -86,7 +86,7 @@ The other component is the client. It will send one message per second to the se ```java private static class HelloWorldClient { ///... - public static void main(String[] args) { + public static void main(String[] args) throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { String serviceAppId = "hellogrpc"; diff --git a/examples/src/main/java/io/dapr/examples/invoke/http/InvokeClient.java b/examples/src/main/java/io/dapr/examples/invoke/http/InvokeClient.java index 9c467da2a..796550656 100644 --- a/examples/src/main/java/io/dapr/examples/invoke/http/InvokeClient.java +++ b/examples/src/main/java/io/dapr/examples/invoke/http/InvokeClient.java @@ -9,8 +9,6 @@ import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; import io.dapr.client.domain.HttpExtension; -import java.io.IOException; - /** * 1. Build and install jars: * mvn clean install @@ -31,7 +29,7 @@ public class InvokeClient { * * @param args Messages to be sent as request for the invoke API. */ - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws Exception { try (DaprClient client = (new DaprClientBuilder()).build()) { for (String message : args) { byte[] response = client.invokeService(SERVICE_APP_ID, "say", message, HttpExtension.POST, null, diff --git a/examples/src/main/java/io/dapr/examples/invoke/http/README.md b/examples/src/main/java/io/dapr/examples/invoke/http/README.md index 8a414766c..9434733d6 100644 --- a/examples/src/main/java/io/dapr/examples/invoke/http/README.md +++ b/examples/src/main/java/io/dapr/examples/invoke/http/README.md @@ -110,7 +110,7 @@ public class InvokeClient { private static final String SERVICE_APP_ID = "invokedemo"; ///... -public static void main(String[] args) throws IOException { +public static void main(String[] args) throws Exception { try (DaprClient client = (new DaprClientBuilder()).build()) { for (String message : args) { byte[] response = client.invokeService(SERVICE_APP_ID, "say", message, HttpExtension.POST, null, diff --git a/examples/src/main/java/io/dapr/examples/state/README.md b/examples/src/main/java/io/dapr/examples/state/README.md index 2d6e4ad5c..9cba5a35b 100644 --- a/examples/src/main/java/io/dapr/examples/state/README.md +++ b/examples/src/main/java/io/dapr/examples/state/README.md @@ -41,7 +41,7 @@ public class StateClient { private static final String SECOND_KEY_NAME = "myKey2"; ///... - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { String message = args.length == 0 ? " " : args[0]; diff --git a/examples/src/main/java/io/dapr/examples/state/StateClient.java b/examples/src/main/java/io/dapr/examples/state/StateClient.java index 0f03ab13d..ad8535bbb 100644 --- a/examples/src/main/java/io/dapr/examples/state/StateClient.java +++ b/examples/src/main/java/io/dapr/examples/state/StateClient.java @@ -11,7 +11,6 @@ import io.dapr.client.domain.State; import io.dapr.client.domain.TransactionalStateOperation; import reactor.core.publisher.Mono; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -45,7 +44,7 @@ public class StateClient { * Executes the sate actions. * @param args messages to be sent as state value. */ - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { String message = args.length == 0 ? " " : args[0]; diff --git a/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java b/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java index 4fbec63cf..3deaa16de 100644 --- a/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java +++ b/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java @@ -18,8 +18,6 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.sdk.OpenTelemetrySdk; -import java.io.IOException; - /** * 1. Build and install jars: * mvn clean install @@ -40,7 +38,7 @@ public class InvokeClient { * * @param args Messages to be sent as request for the invoke API. */ - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws Exception { Tracer tracer = OpenTelemetryConfig.createTracer(InvokeClient.class.getCanonicalName()); Span span = tracer.spanBuilder("Example's Main").setSpanKind(Span.Kind.CLIENT).startSpan(); diff --git a/examples/src/main/java/io/dapr/examples/tracing/README.md b/examples/src/main/java/io/dapr/examples/tracing/README.md index ced0a8f1b..f473d714f 100644 --- a/examples/src/main/java/io/dapr/examples/tracing/README.md +++ b/examples/src/main/java/io/dapr/examples/tracing/README.md @@ -140,7 +140,7 @@ public class InvokeClient { private static final String SERVICE_APP_ID = "invokedemo"; ///... - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws Exception { Tracer tracer = OpenTelemetryConfig.createTracer(InvokeClient.class.getCanonicalName()); Span span = tracer.spanBuilder("Example's Main").setSpanKind(Span.Kind.CLIENT).startSpan(); diff --git a/examples/src/main/resources/img/exception.png b/examples/src/main/resources/img/exception.png new file mode 100644 index 000000000..7af9d5954 Binary files /dev/null and b/examples/src/main/resources/img/exception.png differ diff --git a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java index 586ddb92b..e19ad7caf 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java +++ b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java @@ -18,6 +18,8 @@ import io.grpc.ManagedChannelBuilder; import java.io.Closeable; import java.lang.reflect.Proxy; +import static io.dapr.exceptions.DaprException.throwIllegalArgumentException; + /** * Builder to generate an ActorProxy instance. Builder can be reused for multiple instances. */ @@ -74,10 +76,10 @@ public class ActorProxyBuilder implements Closeable { */ public ActorProxyBuilder(String actorType, Class actorTypeClass) { if ((actorType == null) || actorType.isEmpty()) { - throw new IllegalArgumentException("ActorType is required."); + throwIllegalArgumentException("ActorType is required."); } if (actorTypeClass == null) { - throw new IllegalArgumentException("ActorTypeClass is required."); + throwIllegalArgumentException("ActorTypeClass is required."); } this.useGrpc = Properties.USE_GRPC.get(); @@ -96,7 +98,7 @@ public class ActorProxyBuilder implements Closeable { */ public ActorProxyBuilder withObjectSerializer(DaprObjectSerializer objectSerializer) { if (objectSerializer == null) { - throw new IllegalArgumentException("Serializer is required."); + throwIllegalArgumentException("Serializer is required."); } this.objectSerializer = objectSerializer; @@ -111,7 +113,7 @@ public class ActorProxyBuilder implements Closeable { */ public T build(ActorId actorId) { if (actorId == null) { - throw new IllegalArgumentException("Cannot instantiate an Actor without Id."); + throwIllegalArgumentException("Cannot instantiate an Actor without Id."); } ActorProxyImpl proxy = new ActorProxyImpl( @@ -167,7 +169,7 @@ public class ActorProxyBuilder implements Closeable { int port = Properties.GRPC_PORT.get(); if (port <= 0) { - throw new IllegalStateException("Invalid port."); + throwIllegalArgumentException("Invalid port."); } return ManagedChannelBuilder.forAddress(Properties.SIDECAR_IP.get(), port).usePlaintext().build(); diff --git a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java index cd2e21e5c..d26e50754 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java +++ b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java @@ -7,6 +7,7 @@ package io.dapr.actors.client; import io.dapr.actors.ActorId; import io.dapr.actors.ActorMethod; +import io.dapr.exceptions.DaprException; import io.dapr.serializer.DaprObjectSerializer; import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; @@ -173,7 +174,8 @@ class ActorProxyImpl implements ActorProxy, InvocationHandler { try { return this.serializer.deserialize(response, type); } catch (IOException e) { - throw new RuntimeException(e); + DaprException.wrap(e); + return null; } } @@ -188,7 +190,8 @@ class ActorProxyImpl implements ActorProxy, InvocationHandler { try { return this.serializer.serialize(request); } catch (IOException e) { - throw new RuntimeException(e); + DaprException.wrap(e); + return null; } } } diff --git a/sdk-actors/src/main/java/io/dapr/actors/client/DaprGrpcClient.java b/sdk-actors/src/main/java/io/dapr/actors/client/DaprGrpcClient.java index 1e9991578..3b64b1a80 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/client/DaprGrpcClient.java +++ b/sdk-actors/src/main/java/io/dapr/actors/client/DaprGrpcClient.java @@ -7,10 +7,14 @@ package io.dapr.actors.client; import com.google.common.util.concurrent.ListenableFuture; import com.google.protobuf.ByteString; +import io.dapr.exceptions.DaprException; import io.dapr.v1.DaprGrpc; import io.dapr.v1.DaprProtos; +import io.opentelemetry.context.Context; import reactor.core.publisher.Mono; +import java.util.concurrent.Callable; + /** * A DaprClient over GRPC for Actor. */ @@ -37,7 +41,7 @@ class DaprGrpcClient implements DaprClient { */ @Override public Mono invokeActorMethod(String actorType, String actorId, String methodName, byte[] jsonPayload) { - return Mono.fromCallable(() -> { + return Mono.fromCallable(DaprException.wrap(() -> { DaprProtos.InvokeActorRequest req = DaprProtos.InvokeActorRequest.newBuilder() .setActorType(actorType) @@ -46,8 +50,17 @@ class DaprGrpcClient implements DaprClient { .setData(jsonPayload == null ? ByteString.EMPTY : ByteString.copyFrom(jsonPayload)) .build(); - ListenableFuture futureResponse = client.invokeActor(req); - return futureResponse.get(); - }).map(r -> r.getData().toByteArray()); + return get(client.invokeActor(req)); + })).map(r -> r.getData().toByteArray()); + } + + private static V get(ListenableFuture future) { + try { + return future.get(); + } catch (Exception e) { + DaprException.wrap(e); + } + + return null; } } diff --git a/sdk-actors/src/test/java/io/dapr/actors/TestUtils.java b/sdk-actors/src/test/java/io/dapr/actors/TestUtils.java new file mode 100644 index 000000000..e79f79cf4 --- /dev/null +++ b/sdk-actors/src/test/java/io/dapr/actors/TestUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors; + +import io.dapr.exceptions.DaprException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.function.Executable; + +public final class TestUtils { + + private TestUtils() {} + + public static void assertThrowsDaprException(Class expectedType, Executable executable) { + Throwable cause = Assertions.assertThrows(DaprException.class, executable).getCause(); + Assertions.assertNotNull(cause); + Assertions.assertEquals(expectedType, cause.getClass()); + } + + public static void assertThrowsDaprException(String expectedErrorCode, Executable executable) { + DaprException daprException = Assertions.assertThrows(DaprException.class, executable); + Assertions.assertNull(daprException.getCause()); + Assertions.assertEquals(expectedErrorCode, daprException.getErrorCode()); + } + + public static void assertThrowsDaprException( + String expectedErrorCode, + String expectedErrorMessage, + Executable executable) { + DaprException daprException = Assertions.assertThrows(DaprException.class, executable); + Assertions.assertNull(daprException.getCause()); + Assertions.assertEquals(expectedErrorCode, daprException.getErrorCode()); + Assertions.assertEquals(expectedErrorMessage, daprException.getMessage()); + } + + public static void assertThrowsDaprException( + Class expectedType, + String expectedErrorCode, + String expectedErrorMessage, + Executable executable) { + DaprException daprException = Assertions.assertThrows(DaprException.class, executable); + Assertions.assertNotNull(daprException.getCause()); + Assertions.assertEquals(expectedType, daprException.getCause().getClass()); + Assertions.assertEquals(expectedErrorCode, daprException.getErrorCode()); + Assertions.assertEquals(expectedErrorMessage, daprException.getMessage()); + } +} diff --git a/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyBuilderTest.java b/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyBuilderTest.java index be8df0637..900ecad46 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyBuilderTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyBuilderTest.java @@ -7,33 +7,34 @@ package io.dapr.actors.client; import io.dapr.actors.ActorId; import io.dapr.actors.ActorType; +import io.dapr.exceptions.DaprException; import org.junit.Assert; import org.junit.Test; public class ActorProxyBuilderTest { - @Test(expected = IllegalArgumentException.class) + @Test(expected = DaprException.class) public void buildWithNullActorId() { new ActorProxyBuilder("test", Object.class) .build(null); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = DaprException.class) public void buildWithEmptyActorType() { new ActorProxyBuilder("", Object.class) .build(new ActorId("100")); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = DaprException.class) public void buildWithNullActorType() { new ActorProxyBuilder(null, Object.class) .build(new ActorId("100")); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = DaprException.class) public void buildWithNullSerializer() { new ActorProxyBuilder("MyActor", Object.class) .withObjectSerializer(null) diff --git a/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java b/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java index 6600fc1e5..5bbe2213d 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java @@ -7,6 +7,7 @@ package io.dapr.actors.client; import io.dapr.actors.ActorId; import io.dapr.actors.ActorMethod; +import io.dapr.exceptions.DaprException; import io.dapr.serializer.DaprObjectSerializer; import io.dapr.serializer.DefaultObjectSerializer; import org.junit.Assert; @@ -309,7 +310,7 @@ public class ActorProxyImplTest { } - @Test(expected = RuntimeException.class) + @Test(expected = DaprException.class) public void invokeActorMethodSavingDataWithIncorrectReturnType() { final DaprClient daprClient = mock(DaprClient.class); when(daprClient.invokeActorMethod(anyString(), anyString(), anyString(), Mockito.isNotNull())) @@ -355,7 +356,7 @@ public class ActorProxyImplTest { } - @Test(expected = RuntimeException.class) + @Test(expected = DaprException.class) public void invokeActorMethodSavingDataWithIncorrectInputType() { final DaprClient daprClient = mock(DaprClient.class); when(daprClient.invokeActorMethod(anyString(), anyString(), anyString(), Mockito.isNotNull())) @@ -402,7 +403,7 @@ public class ActorProxyImplTest { } - @Test(expected = RuntimeException.class) + @Test(expected = DaprException.class) public void invokeActorMethodWithDataWithVoidIncorrectInputType() { MyData saveData = new MyData(); saveData.setPropertyA("valueA"); diff --git a/sdk-actors/src/test/java/io/dapr/actors/client/DaprGrpcClientTest.java b/sdk-actors/src/test/java/io/dapr/actors/client/DaprGrpcClientTest.java index 1e7f2f29f..a43f8adf1 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/client/DaprGrpcClientTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/client/DaprGrpcClientTest.java @@ -14,6 +14,9 @@ import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Mono; +import java.util.concurrent.ExecutionException; + +import static io.dapr.actors.TestUtils.assertThrowsDaprException; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -89,8 +92,12 @@ public class DaprGrpcClientTest { return true; }))).thenReturn(settableFuture); Mono result = client.invokeActorMethod(ACTOR_TYPE, ACTOR_ID, methodName, null); - Exception exception = assertThrows(Exception.class, () -> result.block()); - assertTrue(exception.getCause().getCause() instanceof ArithmeticException); + + assertThrowsDaprException( + ExecutionException.class, + "UNKNOWN", + "UNKNOWN: java.lang.ArithmeticException", + () -> result.block()); } @Test diff --git a/sdk-actors/src/test/java/io/dapr/actors/client/DaprHttpClientTest.java b/sdk-actors/src/test/java/io/dapr/actors/client/DaprHttpClientTest.java index b988da1fb..2fabe7ced 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/client/DaprHttpClientTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/client/DaprHttpClientTest.java @@ -8,14 +8,16 @@ import io.dapr.client.DaprHttp; import io.dapr.client.DaprHttpProxy; import io.dapr.config.Properties; import okhttp3.OkHttpClient; +import okhttp3.ResponseBody; import okhttp3.mock.Behavior; +import okhttp3.mock.MediaTypes; import okhttp3.mock.MockInterceptor; import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Mono; +import static io.dapr.actors.TestUtils.assertThrowsDaprException; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; public class DaprHttpClientTest { @@ -35,15 +37,33 @@ public class DaprHttpClientTest { @Test public void invokeActorMethod() { - DaprHttp daprHttpMock = mock(DaprHttp.class); mockInterceptor.addRule() - .post("http://127.0.0.1:3000/v1.0/actors/DemoActor/1/method/Payment") - .respond(EXPECTED_RESULT); + .post("http://127.0.0.1:3000/v1.0/actors/DemoActor/1/method/Payment") + .respond(EXPECTED_RESULT); DaprHttp daprHttp = new DaprHttpProxy(Properties.SIDECAR_IP.get(), 3000, okHttpClient); DaprHttpClient = new DaprHttpClient(daprHttp); Mono mono = - DaprHttpClient.invokeActorMethod("DemoActor", "1", "Payment", "".getBytes()); + DaprHttpClient.invokeActorMethod("DemoActor", "1", "Payment", "".getBytes()); assertEquals(new String(mono.block()), EXPECTED_RESULT); } + @Test + public void invokeActorMethodError() { + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/actors/DemoActor/1/method/Payment") + .respond(404, + ResponseBody.create("" + + "{\"errorCode\":\"ERR_SOMETHING\"," + + "\"message\":\"error message\"}", MediaTypes.MEDIATYPE_JSON)); + DaprHttp daprHttp = new DaprHttpProxy(Properties.SIDECAR_IP.get(), 3000, okHttpClient); + DaprHttpClient = new DaprHttpClient(daprHttp); + Mono mono = + DaprHttpClient.invokeActorMethod("DemoActor", "1", "Payment", "".getBytes()); + + assertThrowsDaprException( + "ERR_SOMETHING", + "ERR_SOMETHING: error message", + () -> mono.block()); + } + } \ No newline at end of file diff --git a/sdk-tests/src/test/java/io/dapr/it/TestUtils.java b/sdk-tests/src/test/java/io/dapr/it/TestUtils.java new file mode 100644 index 000000000..40366a785 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/TestUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.it; + +import io.dapr.exceptions.DaprException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.function.Executable; + +public final class TestUtils { + + private TestUtils() {} + + public static void assertThrowsDaprException(Class expectedType, Executable executable) { + Throwable cause = Assertions.assertThrows(DaprException.class, executable).getCause(); + Assertions.assertNotNull(cause); + Assertions.assertEquals(expectedType, cause.getClass()); + } + + public static void assertThrowsDaprException(String expectedErrorCode, Executable executable) { + DaprException daprException = Assertions.assertThrows(DaprException.class, executable); + Assertions.assertNull(daprException.getCause()); + Assertions.assertEquals(expectedErrorCode, daprException.getErrorCode()); + } + + public static void assertThrowsDaprException( + String expectedErrorCode, + String expectedErrorMessage, + Executable executable) { + DaprException daprException = Assertions.assertThrows(DaprException.class, executable); + Assertions.assertEquals(expectedErrorCode, daprException.getErrorCode()); + Assertions.assertEquals(expectedErrorMessage, daprException.getMessage()); + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java index 9085a2896..fd1f26dbf 100644 --- a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java @@ -66,7 +66,7 @@ public class MethodInvokeIT extends BaseIT { } @Test - public void testInvoke() throws IOException { + public void testInvoke() throws Exception { // At this point, it is guaranteed that the service above is running and all ports being listened to. @@ -94,7 +94,7 @@ public class MethodInvokeIT extends BaseIT { } @Test - public void testInvokeWithObjects() throws IOException { + public void testInvokeWithObjects() throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { for (int i = 0; i < NUM_MESSAGES; i++) { Person person = new Person(); diff --git a/sdk-tests/src/test/java/io/dapr/it/secrets/SecretsClientIT.java b/sdk-tests/src/test/java/io/dapr/it/secrets/SecretsClientIT.java index d5081ce51..d6fd45dcd 100644 --- a/sdk-tests/src/test/java/io/dapr/it/secrets/SecretsClientIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/secrets/SecretsClientIT.java @@ -96,7 +96,7 @@ public class SecretsClientIT extends BaseIT { } @After - public void tearDown() throws IOException { + public void tearDown() throws Exception { daprClient.close(); } diff --git a/sdk-tests/src/test/java/io/dapr/it/state/AbstractStateClientIT.java b/sdk-tests/src/test/java/io/dapr/it/state/AbstractStateClientIT.java index 18523fad8..eaf59c73b 100644 --- a/sdk-tests/src/test/java/io/dapr/it/state/AbstractStateClientIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/state/AbstractStateClientIT.java @@ -18,9 +18,12 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.concurrent.ExecutionException; +import static io.dapr.it.TestUtils.assertThrowsDaprException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Common test cases for Dapr client (GRPC and HTTP). @@ -60,6 +63,22 @@ public abstract class AbstractStateClientIT extends BaseIT { Assert.assertEquals("data in property B", myDataResponse.getValue().getPropertyB()); } + @Test + public void getStateKeyNotFound() { + final String stateKey = "unknownKey"; + + DaprClient daprClient = buildDaprClient(); + + State state = (State) + daprClient.getState(STATE_STORE_NAME, new State(stateKey), String.class).block(); + Assert.assertNotNull(state); + Assert.assertEquals("unknownKey", state.getKey()); + Assert.assertNull(state.getValue()); + // gRPC returns empty eTag while HTTP returns null. + // TODO(artursouza): https://github.com/dapr/java-sdk/issues/405 + Assert.assertTrue(state.getEtag() == null || state.getEtag().isEmpty()); + } + @Test public void saveAndGetBulkStates() { final String stateKeyOne = UUID.randomUUID().toString(); diff --git a/sdk-tests/src/test/java/io/dapr/it/state/GRPCStateClientIT.java b/sdk-tests/src/test/java/io/dapr/it/state/GRPCStateClientIT.java index cfc4637de..40f12b4f6 100644 --- a/sdk-tests/src/test/java/io/dapr/it/state/GRPCStateClientIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/state/GRPCStateClientIT.java @@ -8,12 +8,16 @@ package io.dapr.it.state; import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; import io.dapr.client.DaprClientGrpc; +import io.dapr.client.domain.State; import io.dapr.it.DaprRun; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Test; import java.io.IOException; +import java.util.Collections; +import static io.dapr.it.TestUtils.assertThrowsDaprException; import static org.junit.Assert.assertTrue; /** @@ -35,7 +39,7 @@ public class GRPCStateClientIT extends AbstractStateClientIT { } @AfterClass - public static void tearDown() throws IOException { + public static void tearDown() throws Exception { daprClient.close(); } @@ -44,7 +48,45 @@ public class GRPCStateClientIT extends AbstractStateClientIT { return daprClient; } + /** Tests where HTTP and GRPC behavior differ in Dapr runtime. **/ + @Test + public void getStateStoreNotFound() { + final String stateKey = "key"; + DaprClient daprClient = buildDaprClient(); + // DaprException is guaranteed in the Dapr SDK but getCause() is null in HTTP while present in GRPC implementation. + assertThrowsDaprException( + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: state store unknown state store is not found", + () -> daprClient.getState("unknown state store", new State(stateKey), byte[].class).block()); + } + + @Test + public void getStatesStoreNotFound() { + final String stateKey = "key"; + + DaprClient daprClient = buildDaprClient(); + + // DaprException is guaranteed in the Dapr SDK but getCause() is null in HTTP while present in GRPC implementation. + assertThrowsDaprException( + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: state store unknown state store is not found", + () -> daprClient.getStates( + "unknown state store", + Collections.singletonList(stateKey), + byte[].class).block()); + } + + @Test + public void publishPubSubNotFound() { + DaprClient daprClient = buildDaprClient(); + + // DaprException is guaranteed in the Dapr SDK but getCause() is null in HTTP while present in GRPC implementation. + assertThrowsDaprException( + "NOT_FOUND", + "NOT_FOUND: pubsub 'unknown pubsub' not found", + () -> daprClient.publishEvent("unknown pubsub", "mytopic", "payload").block()); + } } diff --git a/sdk-tests/src/test/java/io/dapr/it/state/HttpStateClientIT.java b/sdk-tests/src/test/java/io/dapr/it/state/HttpStateClientIT.java index 25746d4d1..62f29aefb 100644 --- a/sdk-tests/src/test/java/io/dapr/it/state/HttpStateClientIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/state/HttpStateClientIT.java @@ -8,12 +8,16 @@ package io.dapr.it.state; import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; import io.dapr.client.DaprClientHttp; +import io.dapr.client.domain.State; import io.dapr.it.DaprRun; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Test; import java.io.IOException; +import java.util.Collections; +import static io.dapr.it.TestUtils.assertThrowsDaprException; import static org.junit.Assert.assertTrue; /** @@ -34,7 +38,7 @@ public class HttpStateClientIT extends AbstractStateClientIT { } @AfterClass - public static void tearDown() throws IOException { + public static void tearDown() throws Exception { daprClient.close(); } @@ -43,4 +47,45 @@ public class HttpStateClientIT extends AbstractStateClientIT { return daprClient; } + /** Tests where HTTP and GRPC behavior differ in Dapr runtime. **/ + + @Test + public void getStateStoreNotFound() { + final String stateKey = "key"; + + DaprClient daprClient = buildDaprClient(); + + // DaprException is guaranteed in the Dapr SDK but getCause() is null in HTTP while present in GRPC implementation. + assertThrowsDaprException( + "ERR_STATE_STORE_NOT_FOUND", + "ERR_STATE_STORE_NOT_FOUND: state store unknown%20state%20store is not found", + () -> daprClient.getState("unknown state store", new State(stateKey), byte[].class).block()); + } + + @Test + public void getStatesStoreNotFound() { + final String stateKey = "key"; + + DaprClient daprClient = buildDaprClient(); + + // DaprException is guaranteed in the Dapr SDK but getCause() is null in HTTP while present in GRPC implementation. + assertThrowsDaprException( + "ERR_STATE_STORE_NOT_FOUND", + "ERR_STATE_STORE_NOT_FOUND: state store unknown%20state%20store is not found", + () -> daprClient.getStates( + "unknown state store", + Collections.singletonList(stateKey), + byte[].class).block()); + } + + @Test + public void publishPubSubNotFound() { + DaprClient daprClient = buildDaprClient(); + + // DaprException is guaranteed in the Dapr SDK but getCause() is null in HTTP while present in GRPC implementation. + assertThrowsDaprException( + "ERR_PUBSUB_NOT_FOUND", + "ERR_PUBSUB_NOT_FOUND: pubsub 'unknown%20pubsub' not found", + () -> daprClient.publishEvent("unknown pubsub", "mytopic", "payload").block()); + } } diff --git a/sdk/src/main/java/io/dapr/client/DaprClient.java b/sdk/src/main/java/io/dapr/client/DaprClient.java index 7d1fa8a98..81a438845 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClient.java +++ b/sdk/src/main/java/io/dapr/client/DaprClient.java @@ -31,7 +31,7 @@ import java.util.Map; * * @see io.dapr.client.DaprClientBuilder for information on how to make instance for this interface. */ -public interface DaprClient extends Closeable { +public interface DaprClient extends AutoCloseable { /** * Publish an event. diff --git a/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java b/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java index df2e22590..64f659362 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java @@ -14,6 +14,8 @@ import io.grpc.ManagedChannelBuilder; import java.io.Closeable; +import static io.dapr.exceptions.DaprException.throwIllegalArgumentException; + /** * A builder for the DaprClient, * Currently only and HTTP Client will be supported. @@ -63,11 +65,11 @@ public class DaprClientBuilder { */ public DaprClientBuilder withObjectSerializer(DaprObjectSerializer objectSerializer) { if (objectSerializer == null) { - throw new IllegalArgumentException("Object serializer is required"); + throwIllegalArgumentException("Object serializer is required"); } if (objectSerializer.getContentType() == null || objectSerializer.getContentType().isEmpty()) { - throw new IllegalArgumentException("Content Type should not be null or empty"); + throwIllegalArgumentException("Content Type should not be null or empty"); } this.objectSerializer = objectSerializer; @@ -83,7 +85,7 @@ public class DaprClientBuilder { */ public DaprClientBuilder withStateSerializer(DaprObjectSerializer stateSerializer) { if (stateSerializer == null) { - throw new IllegalArgumentException("State serializer is required"); + throwIllegalArgumentException("State serializer is required"); } this.stateSerializer = stateSerializer; @@ -113,7 +115,7 @@ public class DaprClientBuilder { private DaprClient buildDaprClientGrpc() { int port = Properties.GRPC_PORT.get(); if (port <= 0) { - throw new IllegalStateException("Invalid port."); + throwIllegalArgumentException("Invalid port."); } ManagedChannel channel = ManagedChannelBuilder.forAddress( Properties.SIDECAR_IP.get(), port).usePlaintext().build(); diff --git a/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java b/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java index 07024908c..4bb010da3 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java @@ -9,7 +9,6 @@ import com.google.common.base.Strings; import com.google.common.util.concurrent.ListenableFuture; import com.google.protobuf.Any; import com.google.protobuf.ByteString; -import com.google.protobuf.Empty; import io.dapr.client.domain.DeleteStateRequest; import io.dapr.client.domain.ExecuteStateTransactionRequest; import io.dapr.client.domain.GetSecretRequest; @@ -25,6 +24,7 @@ import io.dapr.client.domain.State; import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.TransactionalStateOperation; import io.dapr.config.Properties; +import io.dapr.exceptions.DaprException; import io.dapr.serializer.DaprObjectSerializer; import io.dapr.utils.TypeRef; import io.dapr.v1.CommonProtos; @@ -149,12 +149,11 @@ public class DaprClientGrpc extends AbstractDaprClient { .setData(ByteString.copyFrom(objectSerializer.serialize(data))).build(); return Mono.fromCallable(wrap(context, () -> { - ListenableFuture futureEmpty = client.publishEvent(envelope); - futureEmpty.get(); + get(client.publishEvent(envelope)); return null; })); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -178,10 +177,10 @@ public class DaprClientGrpc extends AbstractDaprClient { ListenableFuture futureResponse = client.invokeService(envelope); - return objectSerializer.deserialize(futureResponse.get().getData().getValue().toByteArray(), type); + return objectSerializer.deserialize(get(futureResponse).getData().getValue().toByteArray(), type); })).map(r -> new Response<>(context, r)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -216,10 +215,10 @@ public class DaprClientGrpc extends AbstractDaprClient { DaprProtos.InvokeBindingRequest envelope = builder.build(); return Mono.fromCallable(wrap(context, () -> { ListenableFuture futureResponse = client.invokeBinding(envelope); - return objectSerializer.deserialize(futureResponse.get().getData().toByteArray(), type); + return objectSerializer.deserialize(get(futureResponse).getData().toByteArray(), type); })).map(r -> new Response<>(context, r)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -253,16 +252,10 @@ public class DaprClientGrpc extends AbstractDaprClient { DaprProtos.GetStateRequest envelope = builder.build(); return Mono.fromCallable(wrap(context, () -> { ListenableFuture futureResponse = client.getState(envelope); - DaprProtos.GetStateResponse response = null; - try { - response = futureResponse.get(); - } catch (NullPointerException npe) { - return null; - } - return buildStateKeyValue(response, key, options, type); + return buildStateKeyValue(get(futureResponse), key, options, type); })).map(s -> new Response<>(context, s)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -294,12 +287,7 @@ public class DaprClientGrpc extends AbstractDaprClient { DaprProtos.GetBulkStateRequest envelope = builder.build(); return Mono.fromCallable(wrap(context, () -> { ListenableFuture futureResponse = client.getBulkState(envelope); - DaprProtos.GetBulkStateResponse response = null; - try { - response = futureResponse.get(); - } catch (NullPointerException npe) { - return null; - } + DaprProtos.GetBulkStateResponse response = get(futureResponse); return response .getItemsList() @@ -307,14 +295,15 @@ public class DaprClientGrpc extends AbstractDaprClient { .map(b -> { try { return buildStateKeyValue(b, type); - } catch (IOException e) { - throw new RuntimeException(e); + } catch (Exception e) { + DaprException.wrap(e); + return null; } }) .collect(Collectors.toList()); })).map(s -> new Response<>(context, s)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -374,16 +363,10 @@ public class DaprClientGrpc extends AbstractDaprClient { } DaprProtos.ExecuteStateTransactionRequest req = builder.build(); - return Mono.fromCallable(wrap(context, () -> client.executeStateTransaction(req))).flatMap(f -> { - try { - f.get(); - } catch (Exception e) { - return Mono.error(e); - } - return Mono.empty(); - }).thenReturn(new Response<>(context, null)); - } catch (IOException e) { - return Mono.error(e); + return Mono.fromCallable(wrap(context, () -> client.executeStateTransaction(req))).map(f -> get(f)) + .thenReturn(new Response<>(context, null)); + } catch (Exception e) { + return DaprException.wrapMono(e); } } @@ -406,16 +389,10 @@ public class DaprClientGrpc extends AbstractDaprClient { } DaprProtos.SaveStateRequest req = builder.build(); - return Mono.fromCallable(wrap(context, () -> client.saveState(req))).flatMap(f -> { - try { - f.get(); - } catch (Exception ex) { - return Mono.error(ex); - } - return Mono.empty(); - }).thenReturn(new Response<>(context, null)); + return Mono.fromCallable(wrap(context, () -> client.saveState(req))).map(f -> get(f)) + .thenReturn(new Response<>(context, null)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -492,16 +469,10 @@ public class DaprClientGrpc extends AbstractDaprClient { } DaprProtos.DeleteStateRequest req = builder.build(); - return Mono.fromCallable(wrap(context, () -> client.deleteState(req))).flatMap(f -> { - try { - f.get(); - } catch (Exception ex) { - return Mono.error(ex); - } - return Mono.empty(); - }).thenReturn(new Response<>(context, null)); + return Mono.fromCallable(wrap(context, () -> client.deleteState(req))).map(f -> get(f)) + .thenReturn(new Response<>(context, null)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -563,7 +534,7 @@ public class DaprClientGrpc extends AbstractDaprClient { throw new IllegalArgumentException("Secret key cannot be null or empty."); } } catch (Exception e) { - return Mono.error(e); + return DaprException.wrapMono(e); } DaprProtos.GetSecretRequest.Builder requestBuilder = DaprProtos.GetSecretRequest.newBuilder() @@ -576,7 +547,7 @@ public class DaprClientGrpc extends AbstractDaprClient { return Mono.fromCallable(wrap(context, () -> { DaprProtos.GetSecretRequest req = requestBuilder.build(); ListenableFuture future = client.getSecret(req); - return future.get(); + return get(future); })).map(future -> new Response<>(context, future.getDataMap())); } @@ -586,9 +557,12 @@ public class DaprClientGrpc extends AbstractDaprClient { * @throws IOException on exception. */ @Override - public void close() throws IOException { + public void close() throws Exception { if (channel != null) { - channel.close(); + DaprException.wrap(() -> { + channel.close(); + return true; + }).call(); } } @@ -658,15 +632,25 @@ public class DaprClientGrpc extends AbstractDaprClient { } }); } catch (SpanContextParseException e) { - throw new RuntimeException(e); + throw new DaprException(e); } } private static Callable wrap(Context context, Callable callable) { if (context == null) { - return callable; + return DaprException.wrap(callable); } - return context.wrap(callable); + return DaprException.wrap(context.wrap(callable)); + } + + private static V get(ListenableFuture future) { + try { + return future.get(); + } catch (Exception e) { + DaprException.wrap(e); + } + + return null; } } diff --git a/sdk/src/main/java/io/dapr/client/DaprClientHttp.java b/sdk/src/main/java/io/dapr/client/DaprClientHttp.java index 6652ac276..18fd07fa9 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientHttp.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientHttp.java @@ -23,6 +23,7 @@ import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.TransactionalStateOperation; import io.dapr.client.domain.TransactionalStateRequest; import io.dapr.config.Properties; +import io.dapr.exceptions.DaprException; import io.dapr.serializer.DaprObjectSerializer; import io.dapr.serializer.DefaultObjectSerializer; import io.dapr.utils.TypeRef; @@ -159,7 +160,7 @@ public class DaprClientHttp extends AbstractDaprClient { DaprHttp.HttpMethods.POST.name(), url.toString(), null, serializedEvent, metadata, context) .thenReturn(new Response<>(context, null)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -198,11 +199,11 @@ public class DaprClientHttp extends AbstractDaprClient { return Mono.just(object); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } }).map(r -> new Response<>(context, r)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -266,11 +267,11 @@ public class DaprClientHttp extends AbstractDaprClient { return Mono.just(object); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } }).map(r -> new Response<>(context, r)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -307,13 +308,13 @@ public class DaprClientHttp extends AbstractDaprClient { try { return Mono.just(buildStates(s, type)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } }) .map(r -> new Response<>(context, r)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -358,12 +359,12 @@ public class DaprClientHttp extends AbstractDaprClient { try { return Mono.just(buildState(s, key, options, type)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } }) .map(r -> new Response<>(context, r)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -383,19 +384,12 @@ public class DaprClientHttp extends AbstractDaprClient { if (operations == null || operations.isEmpty()) { return Mono.empty(); } - final Map headers = new HashMap<>(); - final String etag = operations.stream() - .filter(op -> null != op.getRequest().getEtag() && !op.getRequest().getEtag().trim().isEmpty()) - .findFirst() - .orElse(new TransactionalStateOperation<>(null, new State<>(null,null, null, null))) - .getRequest() - .getEtag(); - if (etag != null && !etag.trim().isEmpty()) { - headers.put(HEADER_HTTP_ETAG_ID, etag); - } final String url = String.format(TRANSACTION_URL_FORMAT, stateStoreName); List> internalOperationObjects = new ArrayList<>(operations.size()); for (TransactionalStateOperation operation : operations) { + if (operation == null) { + continue; + } State state = operation.getRequest(); if (state == null) { continue; @@ -416,10 +410,10 @@ public class DaprClientHttp extends AbstractDaprClient { TransactionalStateRequest req = new TransactionalStateRequest<>(internalOperationObjects, metadata); byte[] serializedOperationBody = INTERNAL_SERIALIZER.serialize(req); return this.client.invokeApi( - DaprHttp.HttpMethods.POST.name(), url, null, serializedOperationBody, headers, context) + DaprHttp.HttpMethods.POST.name(), url, null, serializedOperationBody, null, context) .thenReturn(new Response<>(context, null)); - } catch (IOException e) { - return Mono.error(e); + } catch (Exception e) { + return DaprException.wrapMono(e); } } @@ -438,12 +432,6 @@ public class DaprClientHttp extends AbstractDaprClient { if (states == null || states.isEmpty()) { return Mono.empty(); } - final Map headers = new HashMap<>(); - final String etag = states.stream().filter(state -> null != state.getEtag() && !state.getEtag().trim().isEmpty()) - .findFirst().orElse(new State<>(null, null, null, null)).getEtag(); - if (etag != null && !etag.trim().isEmpty()) { - headers.put(HEADER_HTTP_ETAG_ID, etag); - } final String url = STATE_PATH + "/" + stateStoreName; List> internalStateObjects = new ArrayList<>(states.size()); for (State state : states) { @@ -466,10 +454,10 @@ public class DaprClientHttp extends AbstractDaprClient { } byte[] serializedStateBody = INTERNAL_SERIALIZER.serialize(internalStateObjects); return this.client.invokeApi( - DaprHttp.HttpMethods.POST.name(), url, null, serializedStateBody, headers, context) + DaprHttp.HttpMethods.POST.name(), url, null, serializedStateBody, null, context) .thenReturn(new Response<>(context, null)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -506,7 +494,7 @@ public class DaprClientHttp extends AbstractDaprClient { DaprHttp.HttpMethods.DELETE.name(), url, urlParameters, headers, context) .thenReturn(new Response<>(context, null)); } catch (Exception ex) { - return Mono.error(ex); + return DaprException.wrapMono(ex); } } @@ -581,7 +569,7 @@ public class DaprClientHttp extends AbstractDaprClient { throw new IllegalArgumentException("Secret key cannot be null or empty."); } } catch (Exception e) { - return Mono.error(e); + return DaprException.wrapMono(e); } String url = SECRETS_PATH + "/" + secretStoreName + "/" + key; @@ -596,7 +584,7 @@ public class DaprClientHttp extends AbstractDaprClient { return Mono.just(m); } catch (IOException e) { - return Mono.error(e); + return DaprException.wrapMono(e); } }) .map(m -> (Map)m) @@ -604,7 +592,11 @@ public class DaprClientHttp extends AbstractDaprClient { } @Override - public void close() throws IOException { - client.close(); + public void close() { + try { + client.close(); + } catch (Exception e) { + DaprException.wrap(e); + } } } diff --git a/sdk/src/main/java/io/dapr/client/DaprHttp.java b/sdk/src/main/java/io/dapr/client/DaprHttp.java index 15a492d01..2e0205eec 100644 --- a/sdk/src/main/java/io/dapr/client/DaprHttp.java +++ b/sdk/src/main/java/io/dapr/client/DaprHttp.java @@ -12,7 +12,6 @@ import io.dapr.exceptions.DaprException; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.propagation.HttpTraceContext; import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapPropagator; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -22,7 +21,6 @@ import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import reactor.core.publisher.Mono; -import java.io.Closeable; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -32,7 +30,7 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; -public class DaprHttp implements Closeable { +public class DaprHttp implements AutoCloseable { /** * Dapr API used in this client. @@ -221,7 +219,7 @@ public class DaprHttp implements Closeable { * @see OkHttpClient */ @Override - public void close() throws IOException { + public void close() { // No code needed } @@ -297,7 +295,7 @@ public class DaprHttp implements Closeable { throw new DaprException(error); } - throw new IllegalStateException("Unknown Dapr error. HTTP status code: " + response.code()); + throw new DaprException("UNKNOWN", "HTTP status code: " + response.code()); } Map mapHeaders = new HashMap<>(); diff --git a/sdk/src/main/java/io/dapr/client/domain/GetStatesRequestBuilder.java b/sdk/src/main/java/io/dapr/client/domain/GetStatesRequestBuilder.java index 19479523f..f74eb177c 100644 --- a/sdk/src/main/java/io/dapr/client/domain/GetStatesRequestBuilder.java +++ b/sdk/src/main/java/io/dapr/client/domain/GetStatesRequestBuilder.java @@ -26,12 +26,12 @@ public class GetStatesRequestBuilder { public GetStatesRequestBuilder(String stateStoreName, List keys) { this.stateStoreName = stateStoreName; - this.keys = Collections.unmodifiableList(keys); + this.keys = keys == null ? null : Collections.unmodifiableList(keys); } public GetStatesRequestBuilder(String stateStoreName, String... keys) { this.stateStoreName = stateStoreName; - this.keys = Collections.unmodifiableList(Arrays.asList(keys)); + this.keys = keys == null ? null : Collections.unmodifiableList(Arrays.asList(keys)); } public GetStatesRequestBuilder withParallelism(int parallelism) { diff --git a/sdk/src/main/java/io/dapr/client/domain/HttpExtension.java b/sdk/src/main/java/io/dapr/client/domain/HttpExtension.java index d16bd4e11..ebba85632 100644 --- a/sdk/src/main/java/io/dapr/client/domain/HttpExtension.java +++ b/sdk/src/main/java/io/dapr/client/domain/HttpExtension.java @@ -6,6 +6,7 @@ package io.dapr.client.domain; import io.dapr.client.DaprHttp; +import io.dapr.exceptions.DaprException; import java.util.Collections; import java.util.HashMap; @@ -73,13 +74,18 @@ public final class HttpExtension { * @throws IllegalArgumentException on null method or queryString. */ public HttpExtension(DaprHttp.HttpMethods method, Map queryString) { - if (method == null) { - throw new IllegalArgumentException("HttpExtension method cannot be null"); - } else if (queryString == null) { - throw new IllegalArgumentException("HttpExtension queryString map cannot be null"); + try { + if (method == null) { + throw new IllegalArgumentException("HttpExtension method cannot be null"); + } else if (queryString == null) { + throw new IllegalArgumentException("HttpExtension queryString map cannot be null"); + } + + this.method = method; + this.queryString = Collections.unmodifiableMap(queryString); + } catch (RuntimeException e) { + DaprException.wrap(e); } - this.method = method; - this.queryString = Collections.unmodifiableMap(queryString); } public DaprHttp.HttpMethods getMethod() { diff --git a/sdk/src/main/java/io/dapr/exceptions/DaprException.java b/sdk/src/main/java/io/dapr/exceptions/DaprException.java index bcefdc9a7..f9d25457b 100644 --- a/sdk/src/main/java/io/dapr/exceptions/DaprException.java +++ b/sdk/src/main/java/io/dapr/exceptions/DaprException.java @@ -5,6 +5,13 @@ package io.dapr.exceptions; +import io.grpc.StatusRuntimeException; +import reactor.core.publisher.Mono; + +import java.lang.reflect.Executable; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; + /** * A Dapr's specific exception. */ @@ -32,10 +39,18 @@ public class DaprException extends RuntimeException { * permitted, and indicates that the cause is nonexistent or * unknown.) */ - public DaprException(DaprError daprError, Throwable cause) { + public DaprException(DaprError daprError, Exception cause) { this(daprError.getErrorCode(), daprError.getMessage(), cause); } + /** + * Wraps an exception into a DaprException. + * @param exception the exception to be wrapped. + */ + public DaprException(Exception exception) { + this("UNKNOWN", exception.getMessage(), exception); + } + /** * New Exception from a client-side generated error code and message. * @@ -56,8 +71,8 @@ public class DaprException extends RuntimeException { * permitted, and indicates that the cause is nonexistent or * unknown.) */ - public DaprException(String errorCode, String message, Throwable cause) { - super(String.format("%s: %s", errorCode, message), cause); + public DaprException(String errorCode, String message, Exception cause) { + super(String.format("%s: %s", errorCode, emptyIfNull(message)), cause); this.errorCode = errorCode; } @@ -69,4 +84,88 @@ public class DaprException extends RuntimeException { public String getErrorCode() { return this.errorCode; } + + /** + * Convenience to throw an wrapped IllegalArgumentException. + * @param message Message for exception. + */ + public static void throwIllegalArgumentException(String message) { + try { + throw new IllegalArgumentException(message); + } catch (Exception e) { + wrap(e); + } + } + + /** + * Wraps an exception into DaprException (if not already DaprException). + * + * @param exception Exception to be wrapped. + * @return DaprException. + */ + public static DaprException wrap(Exception exception) { + if (exception == null) { + return null; + } + + if (exception instanceof DaprException) { + throw (DaprException) exception; + } + + Throwable e = exception; + while (e != null) { + if (e instanceof StatusRuntimeException) { + StatusRuntimeException statusRuntimeException = (StatusRuntimeException) e; + throw new DaprException( + statusRuntimeException.getStatus().getCode().toString(), + statusRuntimeException.getStatus().getDescription(), + exception); + } + + e = e.getCause(); + } + + throw new DaprException(exception); + } + + /** + * Wraps a callable with a try-catch to throw DaprException. + * @param callable callable to be invoked. + * @param type to be returned + * @return object of type T. + */ + public static Callable wrap(Callable callable) { + return () -> { + try { + return callable.call(); + } catch (Exception e) { + return (T) wrap(e); + } + }; + } + + /** + * Wraps an exception into DaprException (if not already DaprException). + * + * @param exception Exception to be wrapped. + * @param Mono's response type. + * @return Mono containing DaprException. + */ + public static Mono wrapMono(Exception exception) { + try { + wrap(exception); + } catch (Exception e) { + return Mono.error(e); + } + + return Mono.empty(); + } + + private static String emptyIfNull(String str) { + if (str == null) { + return ""; + } + + return str; + } } diff --git a/sdk/src/test/java/io/dapr/client/DaprClientBuilderTest.java b/sdk/src/test/java/io/dapr/client/DaprClientBuilderTest.java index b235e0799..d9db53aae 100644 --- a/sdk/src/test/java/io/dapr/client/DaprClientBuilderTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprClientBuilderTest.java @@ -5,6 +5,7 @@ package io.dapr.client; +import io.dapr.exceptions.DaprException; import io.dapr.serializer.DaprObjectSerializer; import org.junit.Test; @@ -26,17 +27,17 @@ public class DaprClientBuilderTest { assertNotNull(daprClient); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = DaprException.class) public void noObjectSerializer() { new DaprClientBuilder().withObjectSerializer(null); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = DaprException.class) public void blankContentTypeInObjectSerializer() { new DaprClientBuilder().withObjectSerializer(mock(DaprObjectSerializer.class)); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = DaprException.class) public void noStateSerializer() { new DaprClientBuilder().withStateSerializer(null); } diff --git a/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java b/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java index b3d29300d..42db5d3c2 100644 --- a/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java @@ -29,6 +29,9 @@ import io.dapr.utils.TypeRef; import io.dapr.v1.CommonProtos; import io.dapr.v1.DaprGrpc; import io.dapr.v1.DaprProtos; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.StatusRuntimeException; import org.checkerframework.checker.nullness.compatqual.NullableDecl; import org.junit.After; import org.junit.Before; @@ -44,9 +47,11 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; import static com.google.common.util.concurrent.Futures.addCallback; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static io.dapr.utils.TestUtils.assertThrowsDaprException; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -85,34 +90,43 @@ public class DaprClientGrpcTest { } @After - public void tearDown() throws IOException { + public void tearDown() throws Exception { adapter.close(); verify(closeable).close(); verifyNoMoreInteractions(closeable); } - @Test(expected = RuntimeException.class) + @Test public void publishEventExceptionThrownTest() { when(client.publishEvent(any(DaprProtos.PublishEventRequest.class))) - .thenThrow(RuntimeException.class); - Mono result = adapter.publishEvent("pubsubname","topic", "object"); - result.block(); + .thenThrow(newStatusRuntimeException("INVALID_ARGUMENT", "bad bad argument")); + + assertThrowsDaprException( + StatusRuntimeException.class, + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: bad bad argument", + () -> adapter.publishEvent("pubsubname","topic", "object").block()); } - @Test(expected = RuntimeException.class) + @Test public void publishEventCallbackExceptionThrownTest() { SettableFuture settableFuture = SettableFuture.create(); - RuntimeException ex = new RuntimeException("An Exception"); + RuntimeException ex = newStatusRuntimeException("INVALID_ARGUMENT", "bad bad argument"); MockCallback callback = new MockCallback<>(ex); addCallback(settableFuture, callback, directExecutor()); when(client.publishEvent(any(DaprProtos.PublishEventRequest.class))) .thenReturn(settableFuture); Mono result = adapter.publishEvent("pubsubname","topic", "object"); settableFuture.setException(ex); - result.block(); + + assertThrowsDaprException( + ExecutionException.class, + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: bad bad argument", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void publishEventSerializeException() throws IOException { DaprObjectSerializer mockSerializer = mock(DaprObjectSerializer.class); adapter = new DaprClientGrpc(closeable, client, mockSerializer, new DefaultObjectSerializer()); @@ -121,7 +135,12 @@ public class DaprClientGrpcTest { .thenReturn(settableFuture); when(mockSerializer.serialize(any())).thenThrow(IOException.class); Mono result = adapter.publishEvent("pubsubname","topic", "{invalid-json"); - result.block(); + + assertThrowsDaprException( + IOException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } @Test @@ -168,25 +187,25 @@ public class DaprClientGrpcTest { @Test public void invokeBindingIllegalArgumentExceptionTest() { - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty binding name adapter.invokeBinding("", "MyOperation", "request".getBytes(), Collections.EMPTY_MAP).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null binding name adapter.invokeBinding(null, "MyOperation", "request".getBytes(), Collections.EMPTY_MAP).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null binding operation adapter.invokeBinding("BindingName", null, "request".getBytes(), Collections.EMPTY_MAP).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty binding operation adapter.invokeBinding("BindingName", "", "request".getBytes(), Collections.EMPTY_MAP).block(); }); } - @Test(expected = RuntimeException.class) + @Test public void invokeBindingSerializeException() throws IOException { DaprObjectSerializer mockSerializer = mock(DaprObjectSerializer.class); adapter = new DaprClientGrpc(closeable, client, mockSerializer, new DefaultObjectSerializer()); @@ -195,18 +214,28 @@ public class DaprClientGrpcTest { .thenReturn(settableFuture); when(mockSerializer.serialize(any())).thenThrow(IOException.class); Mono result = adapter.invokeBinding("BindingName", "MyOperation", "request".getBytes(), Collections.EMPTY_MAP); - result.block(); + + assertThrowsDaprException( + IOException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void invokeBindingExceptionThrownTest() { - when(client.invokeService(any(DaprProtos.InvokeServiceRequest.class))) + when(client.invokeBinding(any(DaprProtos.InvokeBindingRequest.class))) .thenThrow(RuntimeException.class); Mono result = adapter.invokeBinding("BindingName", "MyOperation", "request"); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void invokeBindingCallbackExceptionThrownTest() { SettableFuture settableFuture = SettableFuture.create(); RuntimeException ex = new RuntimeException("An Exception"); @@ -217,7 +246,12 @@ public class DaprClientGrpcTest { when(client.invokeBinding(any(DaprProtos.InvokeBindingRequest.class))) .thenReturn(settableFuture); Mono result = adapter.invokeBinding("BindingName", "MyOperation", "request"); - result.block(); + + assertThrowsDaprException( + ExecutionException.class, + "UNKNOWN", + "UNKNOWN: java.lang.RuntimeException: An Exception", + () -> result.block()); } @Test @@ -314,33 +348,48 @@ public class DaprClientGrpcTest { assertFalse(callback.wasCalled); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceVoidExceptionThrownTest() { when(client.invokeService(any(DaprProtos.InvokeServiceRequest.class))) .thenThrow(RuntimeException.class); Mono result = adapter.invokeService("appId", "method", "request", HttpExtension.NONE); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceIllegalArgumentExceptionThrownTest() { SettableFuture settableFuture = SettableFuture.create(); when(client.invokeService(any(DaprProtos.InvokeServiceRequest.class))) .thenReturn(settableFuture); // HttpExtension cannot be null Mono result = adapter.invokeService("appId", "method", "request", null); - result.block(); + + assertThrowsDaprException( + IllegalArgumentException.class, + "UNKNOWN", + "UNKNOWN: HttpExtension cannot be null. Use HttpExtension.NONE instead.", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceEmptyRequestVoidExceptionThrownTest() { when(client.invokeService(any(DaprProtos.InvokeServiceRequest.class))) .thenThrow(RuntimeException.class); Mono result = adapter.invokeService("appId", "method", HttpExtension.NONE, (Map)null); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceVoidCallbackExceptionThrownTest() { SettableFuture settableFuture = SettableFuture.create(); RuntimeException ex = new RuntimeException("An Exception"); @@ -350,7 +399,12 @@ public class DaprClientGrpcTest { when(client.invokeService(any(DaprProtos.InvokeServiceRequest.class))) .thenReturn(settableFuture); Mono result = adapter.invokeService("appId", "method", "request", HttpExtension.NONE); - result.block(); + + assertThrowsDaprException( + ExecutionException.class, + "UNKNOWN", + "UNKNOWN: java.lang.RuntimeException: An Exception", + () -> result.block()); } @Test @@ -384,31 +438,46 @@ public class DaprClientGrpcTest { assertTrue(callback.wasCalled); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceExceptionThrownTest() { when(client.invokeService(any(DaprProtos.InvokeServiceRequest.class))) .thenThrow(RuntimeException.class); Mono result = adapter.invokeService("appId", "method", "request", HttpExtension.NONE, null, String.class); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceNoRequestClassExceptionThrownTest() { when(client.invokeService(any(DaprProtos.InvokeServiceRequest.class))) .thenThrow(RuntimeException.class); Mono result = adapter.invokeService("appId", "method", HttpExtension.NONE, (Map)null, String.class); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceNoRequestTypeRefExceptionThrownTest() { when(client.invokeService(any(DaprProtos.InvokeServiceRequest.class))) .thenThrow(RuntimeException.class); Mono result = adapter.invokeService("appId", "method", HttpExtension.NONE, (Map)null, TypeRef.STRING); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceCallbackExceptionThrownTest() { SettableFuture settableFuture = SettableFuture.create(); RuntimeException ex = new RuntimeException("An Exception"); @@ -418,7 +487,12 @@ public class DaprClientGrpcTest { .thenReturn(settableFuture); Mono result = adapter.invokeService("appId", "method", "request", HttpExtension.NONE, null, String.class); settableFuture.setException(ex); - result.block(); + + assertThrowsDaprException( + ExecutionException.class, + "UNKNOWN", + "UNKNOWN: java.lang.RuntimeException: An Exception", + () -> result.block()); } @Test @@ -482,15 +556,20 @@ public class DaprClientGrpcTest { assertEquals(object.value, resultObject.value); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceNoRequestBodyExceptionThrownTest() { when(client.invokeService(any(DaprProtos.InvokeServiceRequest.class))) .thenThrow(RuntimeException.class); Mono result = adapter.invokeService("appId", "method", (Object)null, HttpExtension.NONE, String.class); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceNoRequestCallbackExceptionThrownTest() { SettableFuture settableFuture = SettableFuture.create(); RuntimeException ex = new RuntimeException("An Exception"); @@ -500,7 +579,12 @@ public class DaprClientGrpcTest { .thenReturn(settableFuture); Mono result = adapter.invokeService("appId", "method", (Object)null, HttpExtension.NONE, String.class); settableFuture.setException(ex); - result.block(); + + assertThrowsDaprException( + ExecutionException.class, + "UNKNOWN", + "UNKNOWN: java.lang.RuntimeException: An Exception", + () -> result.block()); } @Test @@ -536,17 +620,22 @@ public class DaprClientGrpcTest { assertEquals(object.value, resultObject.value); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceByteRequestExceptionThrownTest() throws IOException { when(client.invokeService(any(DaprProtos.InvokeServiceRequest.class))) .thenThrow(RuntimeException.class); String request = "Request"; byte[] byteRequest = serializer.serialize(request); Mono result = adapter.invokeService("appId", "method", byteRequest, HttpExtension.NONE, byte[].class); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceByteRequestCallbackExceptionThrownTest() throws IOException { SettableFuture settableFuture = SettableFuture.create(); RuntimeException ex = new RuntimeException("An Exception"); @@ -559,7 +648,12 @@ public class DaprClientGrpcTest { Mono result = adapter.invokeService("appId", "method", byteRequest, HttpExtension.NONE,(HashMap) null); settableFuture.setException(ex); - result.block(); + + assertThrowsDaprException( + ExecutionException.class, + "UNKNOWN", + "UNKNOWN: java.lang.RuntimeException: An Exception", + () -> result.block()); } @Test @@ -598,15 +692,20 @@ public class DaprClientGrpcTest { assertEquals(resultObj, serializer.deserialize(byteOutput, MyObject.class)); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceNoRequestNoClassBodyExceptionThrownTest() { when(client.invokeService(any(DaprProtos.InvokeServiceRequest.class))) .thenThrow(RuntimeException.class); Mono result = adapter.invokeService("appId", "method", (Object)null, HttpExtension.NONE); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void invokeServiceNoRequestNoClassCallbackExceptionThrownTest() { SettableFuture settableFuture = SettableFuture.create(); RuntimeException ex = new RuntimeException("An Exception"); @@ -616,7 +715,12 @@ public class DaprClientGrpcTest { .thenReturn(settableFuture); Mono result = adapter.invokeService("appId", "method", (Object)null, HttpExtension.NONE); settableFuture.setException(ex); - result.block(); + + assertThrowsDaprException( + ExecutionException.class, + "UNKNOWN", + "UNKNOWN: java.lang.RuntimeException: An Exception", + () -> result.block()); } @Test @@ -670,33 +774,38 @@ public class DaprClientGrpcTest { @Test public void getStateIllegalArgumentExceptionTest() { State key = buildStateKey(null, "Key1", "ETag1", null); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty state store name adapter.getState("", key, String.class).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null state store name adapter.getState(null, key, String.class).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null key adapter.getState(STATE_STORE_NAME, (String)null, String.class).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty key adapter.getState(STATE_STORE_NAME, "", String.class).block(); }); } - @Test(expected = RuntimeException.class) + @Test public void getStateExceptionThrownTest() { when(client.getState(any(io.dapr.v1.DaprProtos.GetStateRequest.class))).thenThrow(RuntimeException.class); State key = buildStateKey(null, "Key1", "ETag1", null); Mono> result = adapter.getState(STATE_STORE_NAME, key, String.class); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void getStateCallbackExceptionThrownTest() { SettableFuture settableFuture = SettableFuture.create(); RuntimeException ex = new RuntimeException("An Exception"); @@ -708,7 +817,12 @@ public class DaprClientGrpcTest { State key = buildStateKey(null, "Key1", "ETag1", null); Mono> result = adapter.getState(STATE_STORE_NAME, key, String.class); settableFuture.setException(ex); - result.block(); + + assertThrowsDaprException( + ExecutionException.class, + "UNKNOWN", + "UNKNOWN: java.lang.RuntimeException: An Exception", + () -> result.block()); } @Test @@ -829,20 +943,20 @@ public class DaprClientGrpcTest { @Test public void getStatesIllegalArgumentExceptionTest() { State key = buildStateKey(null, "Key1", "ETag1", null); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty state store name adapter.getStates("", Collections.singletonList("100"), String.class).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null state store name adapter.getStates(null, Collections.singletonList("100"), String.class).block(); }); - assertThrows(NullPointerException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null key // null pointer exception due to keys being converted to an unmodifiable list adapter.getStates(STATE_STORE_NAME, null, String.class).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty key list adapter.getStates(STATE_STORE_NAME, Collections.emptyList(), String.class).block(); }); @@ -850,7 +964,7 @@ public class DaprClientGrpcTest { GetStatesRequest req = new GetStatesRequestBuilder(STATE_STORE_NAME, Collections.singletonList("100")) .withParallelism(-1) .build(); - assertThrows(IllegalArgumentException.class, () -> adapter.getStates(req, TypeRef.BOOLEAN).block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> adapter.getStates(req, TypeRef.BOOLEAN).block()); } @Test @@ -1029,36 +1143,41 @@ public class DaprClientGrpcTest { assertEquals("not found", result.stream().skip(1).findFirst().get().getError()); } - @Test(expected = RuntimeException.class) + @Test public void deleteStateExceptionThrowTest() { when(client.deleteState(any(io.dapr.v1.DaprProtos.DeleteStateRequest.class))).thenThrow(RuntimeException.class); State key = buildStateKey(null, "Key1", "ETag1", null); Mono result = adapter.deleteState(STATE_STORE_NAME, key.getKey(), key.getEtag(), key.getOptions()); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } @Test public void deleteStateIllegalArgumentExceptionTest() { State key = buildStateKey(null, "Key1", "ETag1", null); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty state store name adapter.deleteState("", key.getKey(), "etag", null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null state store name adapter.deleteState(null, key.getKey(), "etag", null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null state store name adapter.deleteState(STATE_STORE_NAME, null, "etag", null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null state store name adapter.deleteState(STATE_STORE_NAME, "", "etag", null).block(); }); } - @Test(expected = RuntimeException.class) + @Test public void deleteStateCallbackExcpetionThrownTest() { SettableFuture settableFuture = SettableFuture.create(); RuntimeException ex = new RuntimeException("An Exception"); @@ -1069,7 +1188,12 @@ public class DaprClientGrpcTest { State key = buildStateKey(null, "Key1", "ETag1", null); Mono result = adapter.deleteState(STATE_STORE_NAME, key.getKey(), key.getEtag(), key.getOptions()); settableFuture.setException(ex); - result.block(); + + assertThrowsDaprException( + ExecutionException.class, + "UNKNOWN", + "UNKNOWN: java.lang.RuntimeException: An Exception", + () -> result.block()); } @Test @@ -1190,17 +1314,17 @@ public class DaprClientGrpcTest { TransactionalStateOperation upsertOperation = new TransactionalStateOperation<>( TransactionalStateOperation.OperationType.UPSERT, key); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty state store name adapter.executeTransaction("", Collections.singletonList(upsertOperation)).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null state store name adapter.executeTransaction(null, Collections.singletonList(upsertOperation)).block(); }); } - @Test(expected = RuntimeException.class) + @Test public void executeTransactionSerializerExceptionTest() throws IOException { DaprObjectSerializer mockSerializer = mock(DaprObjectSerializer.class); adapter = new DaprClientGrpc(closeable, client, mockSerializer, mockSerializer); @@ -1220,7 +1344,12 @@ public class DaprClientGrpcTest { .withTransactionalStates(upsertOperation) .build(); Mono> result = adapter.executeTransaction(request); - result.block(); + + assertThrowsDaprException( + IOException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } @Test @@ -1278,7 +1407,7 @@ public class DaprClientGrpcTest { assertTrue(callback.wasCalled); } - @Test(expected = RuntimeException.class) + @Test public void executeTransactionExceptionThrownTest() { String etag = "ETag1"; String key = "key1"; @@ -1291,10 +1420,15 @@ public class DaprClientGrpcTest { TransactionalStateOperation.OperationType.UPSERT, stateKey); Mono result = adapter.executeTransaction(STATE_STORE_NAME, Collections.singletonList(operation)); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void executeTransactionCallbackExceptionTest() { String etag = "ETag1"; String key = "key1"; @@ -1312,32 +1446,42 @@ public class DaprClientGrpcTest { stateKey); Mono result = adapter.executeTransaction(STATE_STORE_NAME, Collections.singletonList(operation)); settableFuture.setException(ex); - result.block(); + + assertThrowsDaprException( + ExecutionException.class, + "UNKNOWN", + "UNKNOWN: java.lang.RuntimeException: ex", + () -> result.block()); } @Test public void saveStatesIllegalArgumentExceptionTest() { - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty state store name adapter.saveStates("", Collections.emptyList()).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty state store name adapter.saveStates(null, Collections.emptyList()).block(); }); } - @Test(expected = RuntimeException.class) + @Test public void saveStateExceptionThrownTest() { String key = "key1"; String etag = "ETag1"; String value = "State value"; when(client.saveState(any(io.dapr.v1.DaprProtos.SaveStateRequest.class))).thenThrow(RuntimeException.class); Mono result = adapter.saveState(STATE_STORE_NAME, key, etag, value, null); - result.block(); + + assertThrowsDaprException( + RuntimeException.class, + "UNKNOWN", + "UNKNOWN: ", + () -> result.block()); } - @Test(expected = RuntimeException.class) + @Test public void saveStateCallbackExceptionThrownTest() { String key = "key1"; String etag = "ETag1"; @@ -1349,7 +1493,12 @@ public class DaprClientGrpcTest { when(client.saveState(any(io.dapr.v1.DaprProtos.SaveStateRequest.class))).thenReturn(settableFuture); Mono result = adapter.saveState(STATE_STORE_NAME, key, etag, value, null); settableFuture.setException(ex); - result.block(); + + assertThrowsDaprException( + ExecutionException.class, + "UNKNOWN", + "UNKNOWN: java.lang.RuntimeException: An Exception", + () -> result.block()); } @Test @@ -1478,22 +1627,25 @@ public class DaprClientGrpcTest { */ @Test - public void getStateDeleteStateThenBlockDeleteThenBlockGet() throws Exception { + public void getStateThenDelete() throws Exception { String etag = "ETag1"; String key1 = "key1"; String expectedValue1 = "Expected state 1"; String key2 = "key2"; String expectedValue2 = "Expected state 2"; State expectedState1 = buildStateKey(expectedValue1, key1, etag, new HashMap<>(), null); + State expectedState2 = buildStateKey(expectedValue2, key2, etag, new HashMap<>(), null); Map> futuresMap = new HashMap<>(); futuresMap.put(key1, buildFutureGetStateEnvelop(expectedValue1, etag)); futuresMap.put(key2, buildFutureGetStateEnvelop(expectedValue2, etag)); when(client.getState(argThat(new GetStateRequestKeyMatcher(key1)))).thenReturn(futuresMap.get(key1)); + when(client.getState(argThat(new GetStateRequestKeyMatcher(key2)))).thenReturn(futuresMap.get(key2)); State keyRequest1 = buildStateKey(null, key1, etag, null); Mono> resultGet1 = adapter.getState(STATE_STORE_NAME, keyRequest1, String.class); assertEquals(expectedState1, resultGet1.block()); State keyRequest2 = buildStateKey(null, key2, etag, null); Mono> resultGet2 = adapter.getState(STATE_STORE_NAME, keyRequest2, String.class); + assertEquals(expectedState2, resultGet2.block()); SettableFuture settableFutureDelete = SettableFuture.create(); MockCallback callbackDelete = new MockCallback<>(Empty.newBuilder().build()); @@ -1505,11 +1657,6 @@ public class DaprClientGrpcTest { settableFutureDelete.set(Empty.newBuilder().build()); resultDelete.block(); assertTrue(callbackDelete.wasCalled); - futuresMap.replace(key2, null); - when(client.getState(argThat(new GetStateRequestKeyMatcher(key2)))).thenReturn(futuresMap.get(key2)); - - State state2 = resultGet2.block(); - assertNull(state2); } @Test @@ -1575,24 +1722,24 @@ public class DaprClientGrpcTest { return settableFuture; }); - assertThrows(RuntimeException.class, () -> adapter.getSecret(SECRET_STORE_NAME, "key").block()); + assertThrowsDaprException(ExecutionException.class, () -> adapter.getSecret(SECRET_STORE_NAME, "key").block()); } @Test public void getSecretsIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty secret store name adapter.getSecret("", "key").block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null secret store name adapter.getSecret(null, "key").block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty key adapter.getSecret(SECRET_STORE_NAME, "").block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null key adapter.getSecret(SECRET_STORE_NAME, null).block(); }); @@ -1821,4 +1968,8 @@ public class DaprClientGrpcTest { return ""; } } + + private static StatusRuntimeException newStatusRuntimeException(String status, String message) { + return new StatusRuntimeException(Status.fromCode(Status.Code.valueOf(status)).withDescription(message)); + } } diff --git a/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java b/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java index a99c4e0c8..b902b7d20 100644 --- a/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java @@ -4,8 +4,10 @@ */ package io.dapr.client; +import com.fasterxml.jackson.core.JsonParseException; import io.dapr.client.domain.DeleteStateRequestBuilder; import io.dapr.client.domain.GetStateRequestBuilder; +import io.dapr.client.domain.GetStatesRequestBuilder; import io.dapr.client.domain.HttpExtension; import io.dapr.client.domain.Response; import io.dapr.client.domain.State; @@ -14,12 +16,16 @@ import io.dapr.client.domain.TransactionalStateOperation; import io.dapr.config.Properties; import io.dapr.utils.TypeRef; import okhttp3.OkHttpClient; +import okhttp3.ResponseBody; import okhttp3.mock.Behavior; +import okhttp3.mock.MediaTypes; import okhttp3.mock.MockInterceptor; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import reactor.core.publisher.Mono; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -28,6 +34,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import static io.dapr.utils.TestUtils.assertThrowsDaprException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -53,7 +60,7 @@ public class DaprClientHttpTest { private MockInterceptor mockInterceptor; @Before - public void setUp() throws Exception { + public void setUp() { mockInterceptor = new MockInterceptor(Behavior.UNORDERED); okHttpClient = new OkHttpClient.Builder().addInterceptor(mockInterceptor).build(); } @@ -82,16 +89,15 @@ public class DaprClientHttpTest { assertNull(mono.block()); } - @Test(expected = IllegalArgumentException.class) - public void publishEventIfTopicIsNull() { - mockInterceptor.addRule() - .post("http://127.0.0.1:3000/v1.0/publish/mypubsubname/A") - .respond(EXPECTED_RESULT); + @Test + public void publishEventIfTopicIsNullOrEmpty() { String event = "{ \"message\": \"This is a test\" }"; daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - Mono mono = daprClientHttp.publishEvent("mypubsubname", "", event); - assertNull(mono.block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.publishEvent("mypubsubname", null, event).block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.publishEvent("mypubsubname", "", event).block()); } @Test @@ -106,7 +112,7 @@ public class DaprClientHttpTest { // Should not throw exception because did not call block() on mono above. } - @Test(expected = IllegalArgumentException.class) + @Test public void invokeServiceVerbNull() { mockInterceptor.addRule() .post("http://127.0.0.1:3000/v1.0/publish/A") @@ -114,46 +120,49 @@ public class DaprClientHttpTest { String event = "{ \"message\": \"This is a test\" }"; daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - Mono mono = daprClientHttp.invokeService(null, "", "", null, null, (Class)null); - assertNull(mono.block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.invokeService(null, "", "", null, null, (Class)null).block()); } @Test public void invokeServiceIllegalArgumentException() { mockInterceptor.addRule() - .post("http://127.0.0.1:3000/v1.0/publish/A") - .respond(EXPECTED_RESULT); - String event = "{ \"message\": \"This is a test\" }"; + .get("http://127.0.0.1:3000/v1.0/invoke/41/method/badorder") + .respond("INVALID JSON"); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null HttpMethod daprClientHttp.invokeService("1", "2", "3", new HttpExtension(null, null), null, (Class)null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null HttpExtension daprClientHttp.invokeService("1", "2", "3", null, null, (Class)null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty appId daprClientHttp.invokeService("", "1", null, HttpExtension.GET, null, (Class)null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null appId, empty method daprClientHttp.invokeService(null, "", null, HttpExtension.POST, null, (Class)null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // empty method daprClientHttp.invokeService("1", "", null, HttpExtension.PUT, null, (Class)null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { // null method daprClientHttp.invokeService("1", null, null, HttpExtension.DELETE, null, (Class)null).block(); }); + assertThrowsDaprException(JsonParseException.class, () -> { + // invalid JSON response + daprClientHttp.invokeService("41", "badorder", null, HttpExtension.GET, null, String.class).block(); + }); } - @Test(expected = IllegalArgumentException.class) + @Test public void invokeServiceMethodNull() { mockInterceptor.addRule() .post("http://127.0.0.1:3000/v1.0/publish/A") @@ -161,24 +170,34 @@ public class DaprClientHttpTest { String event = "{ \"message\": \"This is a test\" }"; daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - Mono mono = daprClientHttp.invokeService("1", "", null, HttpExtension.POST, null, (Class)null); - assertNull(mono.block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.invokeService("1", "", null, HttpExtension.POST, null, (Class)null).block()); } @Test public void invokeService() { mockInterceptor.addRule() - .get("http://127.0.0.1:3000/v1.0/invoke/41/method/neworder") - .respond("\"hello world\""); + .get("http://127.0.0.1:3000/v1.0/invoke/41/method/neworder") + .respond("\"hello world\""); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); Mono mono = daprClientHttp.invokeService("41", "neworder", null, HttpExtension.GET, null, String.class); assertEquals("hello world", mono.block()); } + @Test + public void invokeServiceNullResponse() { + mockInterceptor.addRule() + .get("http://127.0.0.1:3000/v1.0/invoke/41/method/neworder") + .respond(new byte[0]); + daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + Mono mono = daprClientHttp.invokeService("41", "neworder", null, HttpExtension.GET, null, String.class); + assertNull(mono.block()); + } + @Test public void simpleInvokeService() { - Map map = new HashMap<>(); mockInterceptor.addRule() .get("http://127.0.0.1:3000/v1.0/invoke/41/method/neworder") .respond(EXPECTED_RESULT); @@ -256,14 +275,62 @@ public class DaprClientHttpTest { public void invokeBinding() { Map map = new HashMap<>(); mockInterceptor.addRule() - .post("http://127.0.0.1:3000/v1.0/bindings/sample-topic") - .respond(""); + .post("http://127.0.0.1:3000/v1.0/bindings/sample-topic") + .respond(""); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", ""); assertNull(mono.block()); } + @Test + public void invokeBindingNullData() { + Map map = new HashMap<>(); + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/bindings/sample-topic") + .respond(""); + daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", null); + assertNull(mono.block()); + } + + @Test + public void invokeBindingErrors() { + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/bindings/sample-topic") + .respond("NOT VALID JSON"); + daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.invokeBinding(null, "myoperation", "").block(); + }); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.invokeBinding("", "myoperation", "").block(); + }); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.invokeBinding("topic", null, "").block(); + }); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.invokeBinding("topic", "", "").block(); + }); + assertThrowsDaprException(JsonParseException.class, () -> { + daprClientHttp.invokeBinding("sample-topic", "op", "data", String.class).block(); + }); + } + + @Test + public void invokeBindingResponseNull() { + Map map = new HashMap<>(); + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/bindings/sample-topic") + .respond(new byte[0]); + daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, String.class); + assertNull(mono.block()); + } + @Test public void invokeBindingResponseObject() { Map map = new HashMap<>(); @@ -284,7 +351,7 @@ public class DaprClientHttpTest { .respond("1.5"); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, double.class); + Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", map, double.class); assertEquals(1.5, mono.block(), 0.0001); } @@ -348,7 +415,7 @@ public class DaprClientHttpTest { assertEquals(1, (int)mono.block()); } - @Test(expected = IllegalArgumentException.class) + @Test public void invokeBindingNullName() { Map map = new HashMap<>(); mockInterceptor.addRule() @@ -356,11 +423,11 @@ public class DaprClientHttpTest { .respond(EXPECTED_RESULT); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - Mono mono = daprClientHttp.invokeBinding(null, "myoperation", ""); - assertNull(mono.block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.invokeBinding(null, "myoperation", "").block()); } - @Test(expected = IllegalArgumentException.class) + @Test public void invokeBindingNullOpName() { Map map = new HashMap<>(); mockInterceptor.addRule() @@ -368,8 +435,8 @@ public class DaprClientHttpTest { .respond(EXPECTED_RESULT); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - Mono mono = daprClientHttp.invokeBinding("sample-topic", null, ""); - assertNull(mono.block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.invokeBinding("sample-topic", null, "").block()); } @Test @@ -384,6 +451,37 @@ public class DaprClientHttpTest { // No exception is thrown because did not call block() on mono above. } + @Test + public void getStatesErrors() { + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/state/MyStateStore/bulk") + .respond("NOT VALID JSON"); + daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.getStates(STATE_STORE_NAME, null, String.class).block(); + }); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.getStates(STATE_STORE_NAME, new ArrayList<>(), String.class).block(); + }); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.getStates(null, Arrays.asList("100", "200"), String.class).block(); + }); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.getStates("", Arrays.asList("100", "200"), String.class).block(); + }); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.getStates( + new GetStatesRequestBuilder(STATE_STORE_NAME, "100").withParallelism(-1).build(), + TypeRef.get(String.class)).block(); + }); + assertThrowsDaprException(JsonParseException.class, () -> { + daprClientHttp.getStates( + new GetStatesRequestBuilder(STATE_STORE_NAME, "100").build(), + TypeRef.get(String.class)).block(); + }); + } + @Test public void getStatesString() { mockInterceptor.addRule() @@ -504,14 +602,31 @@ public class DaprClientHttpTest { StateOptions stateOptions = mock(StateOptions.class); State stateKeyValue = new State<>("value", "key", "etag", stateOptions); State stateKeyNull = new State<>("value", null, "etag", stateOptions); + State stateKeyEmpty = new State<>("value", "", "etag", stateOptions); + State stateKeyBadPayload = new State<>("value", "keyBadPayload", "etag", stateOptions); mockInterceptor.addRule() - .get("http://127.0.0.1:3000/v1.0/state/MyStateStore/key") - .respond("\"" + EXPECTED_RESULT + "\""); + .get("http://127.0.0.1:3000/v1.0/state/MyStateStore/key") + .respond("\"" + EXPECTED_RESULT + "\""); + mockInterceptor.addRule() + .get("http://127.0.0.1:3000/v1.0/state/MyStateStore/keyBadPayload") + .respond("NOT VALID"); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { daprClientHttp.getState(STATE_STORE_NAME, stateKeyNull, String.class).block(); }); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.getState(STATE_STORE_NAME, stateKeyEmpty, String.class).block(); + }); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.getState(null, stateKeyValue, String.class).block(); + }); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.getState("", stateKeyValue, String.class).block(); + }); + assertThrowsDaprException(JsonParseException.class, () -> { + daprClientHttp.getState(STATE_STORE_NAME, stateKeyBadPayload, String.class).block(); + }); Mono> mono = daprClientHttp.getState(STATE_STORE_NAME, stateKeyValue, String.class); State result = mono.block(); assertNotNull(result); @@ -582,20 +697,19 @@ public class DaprClientHttpTest { assertNull(mono.block()); } - @Test(expected = IllegalArgumentException.class) - public void saveStateNullStateStoreName() { + @Test + public void saveStatesErrors() { daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - Mono mono = daprClientHttp.saveStates(null, null); - assertNull(mono.block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.saveStates(null, null).block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.saveStates("", null).block()); } @Test public void saveStatesNull() { List> stateKeyValueList = new ArrayList<>(); - mockInterceptor.addRule() - .post("http://127.0.0.1:3000/v1.0/state/MyStateStore") - .respond(EXPECTED_RESULT); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); Mono mono = daprClientHttp.saveStates(STATE_STORE_NAME, null); @@ -604,6 +718,19 @@ public class DaprClientHttpTest { assertNull(mono1.block()); } + @Test + public void saveStatesNullState() { + List> stateKeyValueList = new ArrayList<>(); + stateKeyValueList.add(null); + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/state/MyStateStore") + .respond(EXPECTED_RESULT); + daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + Mono mono1 = daprClientHttp.saveStates(STATE_STORE_NAME, stateKeyValueList); + assertNull(mono1.block()); + } + @Test public void saveStatesEtagNull() { State stateKeyValue = new State<>("value", "key", null, null); @@ -678,14 +805,94 @@ public class DaprClientHttpTest { assertNull(mono.block()); } - @Test(expected = IllegalArgumentException.class) - public void executeTransactionNullStateStoreName() { + @Test + public void simpleExecuteTransactionNullEtag() { + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/state/MyStateStore/transaction") + .respond(EXPECTED_RESULT); + String etag = null; + String key = "key1"; + String data = "my data"; + StateOptions stateOptions = mock(StateOptions.class); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - Mono mono = daprClientHttp.executeTransaction(null, null); + + State stateKey = new State<>(data, key, etag, stateOptions); + TransactionalStateOperation upsertOperation = new TransactionalStateOperation<>( + TransactionalStateOperation.OperationType.UPSERT, + stateKey); + TransactionalStateOperation deleteOperation = new TransactionalStateOperation<>( + TransactionalStateOperation.OperationType.DELETE, + new State<>("deleteKey")); + Mono mono = daprClientHttp.executeTransaction(STATE_STORE_NAME, Arrays.asList(upsertOperation, + deleteOperation)); assertNull(mono.block()); } + @Test + public void simpleExecuteTransactionEmptyEtag() { + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/state/MyStateStore/transaction") + .respond(EXPECTED_RESULT); + String etag = "empty"; + String key = "key1"; + String data = "my data"; + StateOptions stateOptions = mock(StateOptions.class); + daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + + State stateKey = new State<>(data, key, etag, stateOptions); + TransactionalStateOperation upsertOperation = new TransactionalStateOperation<>( + TransactionalStateOperation.OperationType.UPSERT, + stateKey); + TransactionalStateOperation deleteOperation = new TransactionalStateOperation<>( + TransactionalStateOperation.OperationType.DELETE, + new State<>("deleteKey")); + Mono mono = daprClientHttp.executeTransaction(STATE_STORE_NAME, Arrays.asList(upsertOperation, + deleteOperation)); + assertNull(mono.block()); + } + + @Test + public void simpleExecuteTransactionNullOperationAndNullState() { + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/state/MyStateStore/transaction") + .respond(EXPECTED_RESULT); + String etag = null; + String key = "key1"; + String data = "my data"; + StateOptions stateOptions = mock(StateOptions.class); + daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + + State stateKey = new State<>(data, key, etag, stateOptions); + TransactionalStateOperation upsertOperation = new TransactionalStateOperation<>( + TransactionalStateOperation.OperationType.UPSERT, + stateKey); + TransactionalStateOperation deleteOperation = new TransactionalStateOperation<>( + TransactionalStateOperation.OperationType.DELETE, + new State<>("deleteKey")); + TransactionalStateOperation nullStateOperation = new TransactionalStateOperation<>( + TransactionalStateOperation.OperationType.DELETE, + null); + Mono mono = daprClientHttp.executeTransaction(STATE_STORE_NAME, Arrays.asList( + null, + nullStateOperation, + upsertOperation, + deleteOperation)); + assertNull(mono.block()); + } + + @Test + public void executeTransactionErrors() { + daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.executeTransaction(null, null).block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.executeTransaction("", null).block()); + } + @Test public void simpleExecuteTransactionNull() { mockInterceptor.addRule() @@ -699,10 +906,6 @@ public class DaprClientHttpTest { assertNull(mono.block()); } - /* - - */ - @Test public void deleteState() { StateOptions stateOptions = mock(StateOptions.class); @@ -779,22 +982,22 @@ public class DaprClientHttpTest { .respond(EXPECTED_RESULT); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { daprClientHttp.deleteState(STATE_STORE_NAME, null, null, null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { daprClientHttp.deleteState(STATE_STORE_NAME, "", null, null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { daprClientHttp.deleteState(STATE_STORE_NAME, " ", null, null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { daprClientHttp.deleteState(null, "key", null, null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { daprClientHttp.deleteState("", "key", null, null).block(); }); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { daprClientHttp.deleteState(" ", "key", null, null).block(); }); } @@ -806,7 +1009,7 @@ public class DaprClientHttpTest { .respond("{ \"mysecretkey\": \"mysecretvalue\"}"); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { daprClientHttp.getSecret(SECRET_STORE_NAME, null).block(); }); Map secret = daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block(); @@ -822,7 +1025,7 @@ public class DaprClientHttpTest { .respond(""); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - assertThrows(IllegalArgumentException.class, () -> { + assertThrowsDaprException(IllegalArgumentException.class, () -> { daprClientHttp.getSecret(SECRET_STORE_NAME, null).block(); }); Map secret = daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block(); @@ -833,20 +1036,50 @@ public class DaprClientHttpTest { @Test public void getSecrets404() { mockInterceptor.addRule() - .get("http://127.0.0.1:3000/v1.0/secrets/MySecretStore/key") - .respond(404); + .get("http://127.0.0.1:3000/v1.0/secrets/MySecretStore/key") + .respond(404); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - assertThrows(IllegalStateException.class, () -> { - daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block(); - }); + assertThrowsDaprException("UNKNOWN", () -> + daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block() + ); } - @Test(expected = IllegalArgumentException.class) - public void getSecretsNullStoreName() { + @Test + public void getSecrets404WithErrorCode() { + mockInterceptor.addRule() + .get("http://127.0.0.1:3000/v1.0/secrets/MySecretStore/key") + .respond(404, + ResponseBody.create("" + + "{\"errorCode\":\"ERR_SECRET_STORE_NOT_FOUND\"," + + "\"message\":\"error message\"}", MediaTypes.MEDIATYPE_JSON)); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - daprClientHttp.getSecret(null, "key").block(); + assertThrowsDaprException("ERR_SECRET_STORE_NOT_FOUND", "ERR_SECRET_STORE_NOT_FOUND: error message", () -> + daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block() + ); + } + + @Test + public void getSecretsErrors() { + mockInterceptor.addRule() + .get("http://127.0.0.1:3000/v1.0/secrets/MySecretStore/key") + .respond("INVALID JSON"); + daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.getSecret(null, "key").block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> + daprClientHttp.getSecret("", "key").block()); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.getSecret(SECRET_STORE_NAME, null).block(); + }); + assertThrowsDaprException(IllegalArgumentException.class, () -> { + daprClientHttp.getSecret(SECRET_STORE_NAME, "").block(); + }); + assertThrowsDaprException(JsonParseException.class, () -> { + daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block(); + }); } @Test @@ -859,10 +1092,6 @@ public class DaprClientHttpTest { .respond("{ \"mysecretkey2\": \"mysecretvalue2\"}"); daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.getSecret(SECRET_STORE_NAME, null).block(); - }); - { Map secret = daprClientHttp.getSecret( SECRET_STORE_NAME, @@ -884,6 +1113,26 @@ public class DaprClientHttpTest { } } + @Test + public void closeException() { + DaprHttp daprHttp = Mockito.mock(DaprHttp.class); + Mockito.doThrow(new IllegalStateException()).when(daprHttp).close(); + + // This method does not throw DaprException because IOException is expected by the Closeable interface. + daprClientHttp = new DaprClientHttp(daprHttp); + assertThrowsDaprException(IllegalStateException.class, () -> daprClientHttp.close()); + } + + @Test + public void close() { + DaprHttp daprHttp = Mockito.mock(DaprHttp.class); + Mockito.doNothing().when(daprHttp).close(); + + // This method does not throw DaprException because IOException is expected by the Closeable interface. + daprClientHttp = new DaprClientHttp(daprHttp); + daprClientHttp.close(); + } + public static class MyObject { private Integer id; private String value; diff --git a/sdk/src/test/java/io/dapr/client/DaprHttpStub.java b/sdk/src/test/java/io/dapr/client/DaprHttpStub.java index 5fdd7f060..c8dd83d09 100644 --- a/sdk/src/test/java/io/dapr/client/DaprHttpStub.java +++ b/sdk/src/test/java/io/dapr/client/DaprHttpStub.java @@ -57,6 +57,6 @@ public class DaprHttpStub extends DaprHttp { * {@inheritDoc} */ @Override - public void close() throws IOException { + public void close() { } } diff --git a/sdk/src/test/java/io/dapr/utils/TestUtils.java b/sdk/src/test/java/io/dapr/utils/TestUtils.java new file mode 100644 index 000000000..d29a77be6 --- /dev/null +++ b/sdk/src/test/java/io/dapr/utils/TestUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.utils; + +import io.dapr.exceptions.DaprException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.function.Executable; + +public final class TestUtils { + + private TestUtils() {} + + public static void assertThrowsDaprException(Class expectedType, Executable executable) { + Throwable cause = Assertions.assertThrows(DaprException.class, executable).getCause(); + Assertions.assertNotNull(cause); + Assertions.assertEquals(expectedType, cause.getClass()); + } + + public static void assertThrowsDaprException(String expectedErrorCode, Executable executable) { + DaprException daprException = Assertions.assertThrows(DaprException.class, executable); + Assertions.assertNull(daprException.getCause()); + Assertions.assertEquals(expectedErrorCode, daprException.getErrorCode()); + } + + public static void assertThrowsDaprException( + String expectedErrorCode, + String expectedErrorMessage, + Executable executable) { + DaprException daprException = Assertions.assertThrows(DaprException.class, executable); + Assertions.assertNull(daprException.getCause()); + Assertions.assertEquals(expectedErrorCode, daprException.getErrorCode()); + Assertions.assertEquals(expectedErrorMessage, daprException.getMessage()); + } + + public static void assertThrowsDaprException( + Class expectedType, + String expectedErrorCode, + String expectedErrorMessage, + Executable executable) { + DaprException daprException = Assertions.assertThrows(DaprException.class, executable); + Assertions.assertNotNull(daprException.getCause()); + Assertions.assertEquals(expectedType, daprException.getCause().getClass()); + Assertions.assertEquals(expectedErrorCode, daprException.getErrorCode()); + Assertions.assertEquals(expectedErrorMessage, daprException.getMessage()); + } +}