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 6cc7c74e88..b81d2f5e60 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 @@ -27,8 +27,7 @@ public void constructorActorProxyTest() { Assert.assertEquals(actorProxy.getActorType(), "myActorType"); } - //@Test() - // TODO: review this test. + @Test() public void invokeActorMethodWithoutDataWithReturnType() { final DaprClient daprClient = mock(DaprClient.class); when(daprClient.invokeActorMethod(anyString(), anyString(), anyString(), Mockito.isNull())) @@ -88,8 +87,7 @@ public void invokeActorMethodWithIncorrectReturnType() { } - //@Test() - // TODO: review this test. + @Test() public void invokeActorMethodSavingDataWithReturnType() { final DaprClient daprClient = mock(DaprClient.class); when(daprClient.invokeActorMethod(anyString(), anyString(), anyString(), Mockito.isNotNull())) @@ -262,7 +260,7 @@ public String getPropertyB() { return propertyB; } - public void setPropertyB(String propActorProxyBuilderTestertyB) { + public void setPropertyB(String propertyB) { this.propertyB = propertyB; } diff --git a/sdk-springboot/pom.xml b/sdk-springboot/pom.xml index 75f03f2824..af3aaab7e5 100644 --- a/sdk-springboot/pom.xml +++ b/sdk-springboot/pom.xml @@ -63,6 +63,7 @@ org.springframework.boot spring-boot-maven-plugin + 2.2.2.RELEASE diff --git a/sdk/pom.xml b/sdk/pom.xml index f96ef80265..d723db3c42 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -62,14 +62,25 @@ mockito-core test + + commons-cli + commons-cli + 1.4 + test + com.github.gmazzo okhttp-mock 1.3.2 test + + com.github.stefanbirkner + system-rules + 1.19.0 + test + - true @@ -112,11 +123,22 @@ + verify + verify integration-test verify + + integration-test + integration-test + + integration-test + verify + + + diff --git a/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java b/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java index e880bdb6fe..d9d6368b3c 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java @@ -77,6 +77,7 @@ private DaprClient buildDaprClientGrpc() { * @return */ private DaprClient buildDaprClientHttp() { + int port=DaprClientBuilder.getEnvPortOrDefault(); if (port <= 0) { throw new IllegalStateException("Invalid port."); } diff --git a/sdk/src/main/java/io/dapr/client/DaprClientHttpAdapter.java b/sdk/src/main/java/io/dapr/client/DaprClientHttpAdapter.java index 8f936c58e7..504a5ce4c7 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientHttpAdapter.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientHttpAdapter.java @@ -13,10 +13,7 @@ import java.io.IOException; import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * An adapter for the HTTP Client. @@ -183,13 +180,13 @@ public Mono> getState(StateKeyValue state, StateOptions StringBuilder url = new StringBuilder(Constants.STATE_PATH) .append("/") .append(state.getKey()); - Map urlParameters = stateOptions.getStateOptionsAsMap(); + Map urlParameters = Optional.ofNullable(stateOptions).map(options -> options.getStateOptionsAsMap() ).orElse( new HashMap<>());; return this.client .invokeAPI(DaprHttp.HttpMethods.GET.name(), url.toString(), urlParameters, headers) .flatMap(s -> { try { return Mono.just(buildStateKeyValue(s, state.getKey(), clazz)); - } catch (Exception ex) { + }catch (Exception ex){ return Mono.error(ex); } }); @@ -207,14 +204,14 @@ public Mono saveStates(List> states, StateOptions opt if (states == null || states.isEmpty()) { return Mono.empty(); } - Map headers = new HashMap<>(); - String etag = states.stream().filter(state -> null != state.getEtag() && !state.getEtag().trim().isEmpty()) + final Map headers = new HashMap<>(); + final String etag = states.stream().filter(state -> null != state.getEtag() && !state.getEtag().trim().isEmpty()) .findFirst().orElse(new StateKeyValue<>(null, null, null)).getEtag(); if (etag != null && !etag.trim().isEmpty()) { headers.put(Constants.HEADER_HTTP_ETAG_ID, etag); } - String url = Constants.STATE_PATH; - Map urlParameter = options.getStateOptionsAsMap(); + final String url = Constants.STATE_PATH; + Map urlParameter = Optional.ofNullable(options).map(stateOptions -> stateOptions.getStateOptionsAsMap() ).orElse( new HashMap<>()); byte[] serializedStateBody = objectSerializer.serialize(states); return this.client.invokeAPI( DaprHttp.HttpMethods.POST.name(), url, urlParameter, serializedStateBody, headers).then(); @@ -249,7 +246,7 @@ public Mono deleteState(StateKeyValue state, StateOptions options) headers.put(Constants.HEADER_HTTP_ETAG_ID, state.getEtag()); } String url = Constants.STATE_PATH + "/" + state.getKey(); - Map urlParameters = options.getStateOptionsAsMap(); + Map urlParameters = Optional.ofNullable(options).map(stateOptions -> stateOptions.getStateOptionsAsMap() ).orElse( new HashMap<>());; return this.client.invokeAPI(DaprHttp.HttpMethods.DELETE.name(), url, urlParameters, headers).then(); } catch (Exception ex) { return Mono.error(ex); diff --git a/sdk/src/main/java/io/dapr/utils/ObjectSerializer.java b/sdk/src/main/java/io/dapr/utils/ObjectSerializer.java index 6446c392af..477b9209e6 100644 --- a/sdk/src/main/java/io/dapr/utils/ObjectSerializer.java +++ b/sdk/src/main/java/io/dapr/utils/ObjectSerializer.java @@ -131,6 +131,9 @@ public T deserialize(Object value, Class clazz) throws IOException { // Not string, not primitive, not byte[], so it is a complex type: we use JSON for that. if (value instanceof byte[]) { + if (((byte[]) value).length==0) { + return null; + } return OBJECT_MAPPER.readValue((byte[]) value, clazz); } diff --git a/sdk/src/test/java/io/dapr/it/BaseIT.java b/sdk/src/test/java/io/dapr/it/BaseIT.java new file mode 100644 index 0000000000..bea550d901 --- /dev/null +++ b/sdk/src/test/java/io/dapr/it/BaseIT.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.it; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.contrib.java.lang.system.EnvironmentVariables; + +import java.util.Optional; + +import static io.dapr.it.DaprIntegrationTestingRunner.DAPR_FREEPORTS; + +public class BaseIT { + + protected static DaprIntegrationTestingRunner daprIntegrationTestingRunner; + + + @ClassRule + public static final EnvironmentVariables environmentVariables = new EnvironmentVariables(); + + @BeforeClass + public static void setEnvironmentVariables(){ + environmentVariables.set("DAPR_HTTP_PORT", String.valueOf(DAPR_FREEPORTS.getHttpPort())); + } + + public static DaprIntegrationTestingRunner createDaprIntegrationTestingRunner(String successMessage, Class serviceClass, Boolean useAppPort, int sleepTime) { + return new DaprIntegrationTestingRunner(successMessage, serviceClass, useAppPort, sleepTime); + } + + @AfterClass + public static void cleanUp() { + Optional.ofNullable(daprIntegrationTestingRunner).ifPresent(daprRunner -> daprRunner.destroyDapr()); + } + + public static class MyData { + + /// Gets or sets the value for PropertyA. + private String propertyA; + + /// Gets or sets the value for PropertyB. + private String propertyB; + + private MyData myData; + + public String getPropertyB() { + return propertyB; + } + + public void setPropertyB(String propertyB) { + this.propertyB = propertyB; + } + + public String getPropertyA() { + return propertyA; + } + + public void setPropertyA(String propertyA) { + this.propertyA = propertyA; + } + + @Override + public String toString() { + return "MyData{" + + "propertyA='" + propertyA + '\'' + + ", propertyB='" + propertyB + '\'' + + '}'; + } + + public MyData getMyData() { + return myData; + } + + public void setMyData(MyData myData) { + this.myData = myData; + } + } +} diff --git a/sdk/src/test/java/io/dapr/it/DaprIntegrationTestingRunner.java b/sdk/src/test/java/io/dapr/it/DaprIntegrationTestingRunner.java new file mode 100644 index 0000000000..504b24722f --- /dev/null +++ b/sdk/src/test/java/io/dapr/it/DaprIntegrationTestingRunner.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.it; + +import org.junit.Assert; + +import java.io.*; +import java.net.ServerSocket; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.*; + + +public class DaprIntegrationTestingRunner { + + public static DaprIntegrationTestingRunner.DaprFreePorts DAPR_FREEPORTS; + + static { + try { + DAPR_FREEPORTS = new DaprIntegrationTestingRunner.DaprFreePorts().initPorts(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private Runtime rt = Runtime.getRuntime(); + private Process proc; + + private String successMessage; + private Class serviceClass; + private Boolean useAppPort; + private int sleepTime; + private String appName; + + DaprIntegrationTestingRunner(String successMessage, Class serviceClass, Boolean useAppPort, int sleepTime) { + this.successMessage = successMessage; + this.serviceClass = serviceClass; + this.useAppPort = useAppPort; + this.sleepTime = sleepTime; + this.generateAppName(); + } + + public DaprFreePorts initializeDapr() throws Exception { + String daprCommand=this.buildDaprCommand(); + System.out.println(daprCommand); + proc= rt.exec(daprCommand); + + final Runnable stuffToDo = new Thread(() -> { + try { + try (InputStream stdin = proc.getInputStream()) { + try(InputStreamReader isr = new InputStreamReader(stdin)) { + try (BufferedReader br = new BufferedReader(isr)){ + String line; + while ((line = br.readLine()) != null) { + System.out.println(line); + if (line.contains(successMessage)) { + break; + } + } + } + } + + } + } catch (IOException ex) { + Assert.fail(ex.getMessage()); + } + }); + + final ExecutorService executor = Executors.newSingleThreadExecutor(); + final Future future = executor.submit(stuffToDo); + executor.shutdown(); // This does not cancel the already-scheduled task. + future.get(1, TimeUnit.MINUTES); + Thread.sleep(sleepTime); + return DAPR_FREEPORTS; + } + + private static final String DAPR_RUN = "dapr run --app-id %s "; + private static final String DAPR_COMMAND = " -- mvn exec:java -D exec.mainClass=%s -D exec.classpathScope=\"test\" -Dexec.args=\"-p %d -grpcPort %d -httpPort %d\""; + + private String buildDaprCommand(){ + StringBuilder stringBuilder= new StringBuilder(String.format(DAPR_RUN, this.appName)) + .append(this.useAppPort ? "--app-port " + this.DAPR_FREEPORTS.appPort : "") + .append(" --grpc-port ") + .append(this.DAPR_FREEPORTS.grpcPort) + .append(" --port ") + .append(this.DAPR_FREEPORTS.httpPort) + .append(String.format(DAPR_COMMAND, this.serviceClass.getCanonicalName(),this.DAPR_FREEPORTS.appPort, this.DAPR_FREEPORTS.grpcPort, this.DAPR_FREEPORTS.httpPort)); + return stringBuilder.toString(); + } + + private void generateAppName(){ + + this.appName=UUID.randomUUID().toString(); + } + + private static Integer findRandomOpenPortOnAllLocalInterfaces() throws Exception { + try ( + ServerSocket socket = new ServerSocket(0) + ) { + return socket.getLocalPort(); + + } + } + + public void destroyDapr() { + Optional.ofNullable(rt).ifPresent( runtime -> { + try { + runtime.exec("dapr stop --app-id " + this.appName); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + Optional.ofNullable(proc).ifPresent(process -> process.destroy()); + } + + public static class DaprFreePorts + { + public DaprFreePorts initPorts() throws Exception { + this.appPort= findRandomOpenPortOnAllLocalInterfaces(); + this.grpcPort= findRandomOpenPortOnAllLocalInterfaces(); + this.httpPort= findRandomOpenPortOnAllLocalInterfaces(); + return this; + } + + private int grpcPort; + + public int getGrpcPort() { + return grpcPort; + } + + public int getHttpPort() { + return httpPort; + } + + public int getAppPort() { + return appPort; + } + + private int httpPort; + + private int appPort; + } +} diff --git a/sdk/src/test/java/io/dapr/client/DaprHttpAsyncClientIT.java b/sdk/src/test/java/io/dapr/it/actor/DaprHttpAsyncClientIT.java similarity index 87% rename from sdk/src/test/java/io/dapr/client/DaprHttpAsyncClientIT.java rename to sdk/src/test/java/io/dapr/it/actor/DaprHttpAsyncClientIT.java index 9b2ead69e6..d2ac154d0c 100644 --- a/sdk/src/test/java/io/dapr/client/DaprHttpAsyncClientIT.java +++ b/sdk/src/test/java/io/dapr/it/actor/DaprHttpAsyncClientIT.java @@ -2,9 +2,12 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. */ -package io.dapr.client; +package io.dapr.it.actor; +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; import io.dapr.exceptions.DaprException; +import io.dapr.it.BaseIT; import org.junit.Assert; import org.junit.Test; @@ -13,7 +16,7 @@ *

* Requires Dapr running. */ -public class DaprHttpAsyncClientIT { +public class DaprHttpAsyncClientIT extends BaseIT { /** * Checks if the error is correctly parsed when trying to invoke a function on diff --git a/sdk/src/test/java/io/dapr/it/services/EmptyService.java b/sdk/src/test/java/io/dapr/it/services/EmptyService.java new file mode 100644 index 0000000000..9f6f6ecee0 --- /dev/null +++ b/sdk/src/test/java/io/dapr/it/services/EmptyService.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.it.services; + +/** + * Use this class in order to run DAPR with any needed services, like states. + */ +public class EmptyService { + public static void main(String[] args) { + + } +} diff --git a/sdk/src/test/java/io/dapr/it/services/HelloWorldGrpcStateService.java b/sdk/src/test/java/io/dapr/it/services/HelloWorldGrpcStateService.java new file mode 100644 index 0000000000..cc8cf63a01 --- /dev/null +++ b/sdk/src/test/java/io/dapr/it/services/HelloWorldGrpcStateService.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.it.services; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import io.dapr.DaprGrpc; +import io.dapr.DaprGrpc.DaprBlockingStub; +import io.dapr.DaprProtos.SaveStateEnvelope; +import io.dapr.DaprProtos.StateRequest; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import org.apache.commons.cli.*; + + +/** + * Simple example, to run: + * mvn clean install + * dapr run --grpc-port 50001 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.Example + */ +public class HelloWorldGrpcStateService { + + public static void main(String[] args) throws ParseException { + Options options = new Options(); + options.addRequiredOption("grpcPort", "grpcPort", true, "Dapr GRPC."); + options.addRequiredOption("httpPort", "httpPort", true, "Dapr HTTP port."); + options.addRequiredOption("p", "port", true, "Port Dapr will listen to."); + + CommandLineParser parser = new DefaultParser(); + CommandLine cmd = parser.parse(options, args); + + // If port string is not valid, it will throw an exception. + int port = Integer.parseInt(cmd.getOptionValue("grpcPort")); + ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build(); + DaprBlockingStub client = DaprGrpc.newBlockingStub(channel); + + String key = "mykey"; + // First, write key-value pair. + + String value = "Hello World"; + StateRequest req = StateRequest + .newBuilder() + .setKey(key) + .setValue(Any.newBuilder().setValue(ByteString.copyFromUtf8(value)).build()) + .build(); + SaveStateEnvelope state = SaveStateEnvelope.newBuilder() + .addRequests(req) + .build(); + client.saveState(state); + System.out.println("Saved!"); + channel.shutdown(); + } +} diff --git a/sdk/src/test/java/io/dapr/it/state/HelloWorldClientIT.java b/sdk/src/test/java/io/dapr/it/state/HelloWorldClientIT.java new file mode 100644 index 0000000000..637e692c4a --- /dev/null +++ b/sdk/src/test/java/io/dapr/it/state/HelloWorldClientIT.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.it.state; + +import io.dapr.DaprGrpc; +import io.dapr.DaprProtos; +import io.dapr.it.BaseIT; +import io.dapr.it.DaprIntegrationTestingRunner; +import io.dapr.it.services.HelloWorldGrpcStateService; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import static io.dapr.it.DaprIntegrationTestingRunner.DAPR_FREEPORTS; + +public class HelloWorldClientIT extends BaseIT { + + private static DaprIntegrationTestingRunner daprIntegrationTestingRunner; + + @BeforeClass + public static void init() throws Exception { + daprIntegrationTestingRunner = + createDaprIntegrationTestingRunner( + "BUILD SUCCESS", + HelloWorldGrpcStateService.class, + false, + 2000 + ); + daprIntegrationTestingRunner.initializeDapr(); + } + + @Test + public void testHelloWorldState(){ + ManagedChannel channel = + ManagedChannelBuilder.forAddress("localhost", DAPR_FREEPORTS.getGrpcPort()).usePlaintext().build(); + DaprGrpc.DaprBlockingStub client = DaprGrpc.newBlockingStub(channel); + + String key = "mykey"; + { + DaprProtos.GetStateEnvelope req = DaprProtos.GetStateEnvelope + .newBuilder() + .setKey(key) + .build(); + DaprProtos.GetStateResponseEnvelope response = client.getState(req); + String value = response.getData().getValue().toStringUtf8(); + System.out.println("Got: " + value); + Assert.assertEquals("Hello World",value); + } + + // Then, delete it. + { + DaprProtos.DeleteStateEnvelope req = DaprProtos.DeleteStateEnvelope + .newBuilder() + .setKey(key) + .build(); + client.deleteState(req); + System.out.println("Deleted!"); + } + + { + DaprProtos.GetStateEnvelope req = DaprProtos.GetStateEnvelope + .newBuilder() + .setKey(key) + .build(); + DaprProtos.GetStateResponseEnvelope response = client.getState(req); + String value = response.getData().getValue().toStringUtf8(); + System.out.println("Got: " + value); + Assert.assertEquals("",value); + } + } +} diff --git a/sdk/src/test/java/io/dapr/it/state/HttpStateClientIT.java b/sdk/src/test/java/io/dapr/it/state/HttpStateClientIT.java new file mode 100644 index 0000000000..454c01164a --- /dev/null +++ b/sdk/src/test/java/io/dapr/it/state/HttpStateClientIT.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.it.state; + +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.client.domain.StateKeyValue; +import io.dapr.it.BaseIT; +import io.dapr.it.services.EmptyService; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import reactor.core.publisher.Mono; + +/** + * Test State HTTP DAPR capabilities using a DAPR instance with an empty service running + */ +public class HttpStateClientIT extends BaseIT { + + @BeforeClass + public static void init() throws Exception { + daprIntegrationTestingRunner = + createDaprIntegrationTestingRunner( + "BUILD SUCCESS", + EmptyService.class, + false, + 0 + ); + daprIntegrationTestingRunner.initializeDapr(); + } + + @Test + public void saveAndGetState() { + + final String stateKey= "myKey"; + + DaprClient daprClient= new DaprClientBuilder().build(); + MyData data= new MyData(); + data.setPropertyA("data in property A"); + data.setPropertyB("data in property B"); + Mono saveResponse= daprClient.saveState(stateKey,null,data, null); + saveResponse.block(); + + Mono> response= daprClient.getState( new StateKeyValue(null,stateKey,null),null,MyData.class); + StateKeyValue myDataResponse=response.block(); + + Assert.assertEquals("data in property A",myDataResponse.getValue().getPropertyA()); + Assert.assertEquals("data in property B",myDataResponse.getValue().getPropertyB()); + } + + @Test + public void saveUpdateAndGetState() { + final String stateKey= "keyToBeUpdated"; + + DaprClient daprClient= new DaprClientBuilder().build(); + MyData data= new MyData(); + data.setPropertyA("data in property A"); + data.setPropertyB("data in property B"); + Mono saveResponse= daprClient.saveState(stateKey,null,data, null); + saveResponse.block(); + + data.setPropertyA("data in property A"); + data.setPropertyB("data in property B2"); + saveResponse= daprClient.saveState(stateKey,null,data, null); + saveResponse.block(); + + Mono> response= daprClient.getState( new StateKeyValue(null,stateKey,null),null,MyData.class); + StateKeyValue myDataResponse=response.block(); + + Assert.assertEquals("data in property A",myDataResponse.getValue().getPropertyA()); + Assert.assertEquals("data in property B2",myDataResponse.getValue().getPropertyB()); + } + + @Test + public void saveAndDeleteState() { + final String stateKey= "myeKeyToBeDeleted"; + + DaprClient daprClient= new DaprClientBuilder().build(); + MyData data= new MyData(); + data.setPropertyA("data in property A"); + data.setPropertyB("data in property B"); + Mono saveResponse= daprClient.saveState(stateKey,null,data, null); + saveResponse.block(); + + Mono> response= daprClient.getState( new StateKeyValue(null,stateKey,null),null,MyData.class); + StateKeyValue myDataResponse=response.block(); + + Assert.assertEquals("data in property A",myDataResponse.getValue().getPropertyA()); + Assert.assertEquals("data in property B",myDataResponse.getValue().getPropertyB()); + + Mono deleteResponse= daprClient.deleteState( new StateKeyValue(null,stateKey,null),null); + deleteResponse.block(); + + response= daprClient.getState( new StateKeyValue(null,stateKey,null),null,MyData.class); + myDataResponse=response.block(); + + Assert.assertNull(myDataResponse.getValue()); + } + +}