TO_BE_CLOSED = new LinkedList<>();
+
+ // ---------- DaprContainer builder ----------
+
+ /**
+ * Returns a pre-configured {@link DaprContainer} wired into the shared
+ * Network and Redis. Callers add components and (optionally) an app port
+ * before calling {@code .start()}.
+ */
+ protected static DaprContainer daprBuilder(String appName) {
+ SharedTestInfra.redis(); // ensure Redis is up before DaprContainer needs it
+ return new DaprContainer(DAPR_IMAGE)
+ .withAppName(appName)
+ .withNetwork(SharedTestInfra.network())
+ .withDaprLogLevel(DaprLogLevel.DEBUG)
+ // Stream daprd logs to stdout so CI surfaces app-discovery and component-load
+ // errors. Without this, the container's stdout is consumed by Testcontainers
+ // and we have no insight when actor registration or component init fails.
+ .withLogConsumer(frame -> System.out.print("[daprd] " + frame.getUtf8String()))
+ // Reuses the placement sidecar container within this JVM (Testcontainers manages it);
+ // orthogonal to SharedTestInfra's Redis `withReuse(true)`.
+ .withReusablePlacement(true);
+ }
+
+ // ---------- App lifecycle ----------
+
+ /** Pair returned by {@link #startAppAndAttach}. */
+ public record DaprAndApp(DaprContainer dapr, AppRun app) {}
+
+ /**
+ * Two-phase startup for ITs that need an app callback. Allocates the app
+ * port, exposes it to Testcontainers, starts the AppRun subprocess so it
+ * has bound the host port, then lets the caller build the
+ * DaprContainer. Binds daprd to the pre-allocated host ports so the
+ * already-running app's {@code DAPR_HTTP_PORT} / {@code DAPR_GRPC_PORT}
+ * env vars (used by callbacks like {@code registerActorTimer}) point at
+ * a reachable daprd. Returns both. Both are registered for
+ * {@code @AfterAll} cleanup via {@link #deferStop}.
+ *
+ * Order matters: starting daprd before the app causes daprd's
+ * application-channel probe to succeed against the Testcontainers SSH
+ * bridge before the JVM has actually bound the host port. Daprd then
+ * fetches {@code /dapr/config}, gets nothing, reports actor types {@code []}
+ * to placement, and never re-queries — so actor ITs hang at
+ * {@code waitForActorsReady}. Starting the app first avoids the race.
+ *
+ * @param appName used both as the Dapr app id and the AppRun name
+ * @param serviceClass the class whose {@code main(String[])} the subprocess runs
+ * @param protocol reserved for future use; AppRun currently ignores it
+ * @param daprFactory given the allocated app port, returns an UNSTARTED
+ * DaprContainer (factory body builds the container,
+ * calls {@code .withAppPort(appPort)
+ * .withAppChannelAddress("host.testcontainers.internal")}
+ * and other configuration, then returns it WITHOUT
+ * calling {@code .start()} — BaseContainerIT pins the
+ * daprd HTTP/gRPC host ports and starts the container).
+ */
+ protected static DaprAndApp startAppAndAttach(
+ String appName,
+ Class> serviceClass,
+ AppRun.AppProtocol protocol,
+ java.util.function.IntFunction daprFactory) throws Exception {
+ DaprPorts ports = DaprPorts.build(true, true, true);
+ int appPort = ports.getAppPort();
+ int daprHttpPort = ports.getHttpPort();
+ int daprGrpcPort = ports.getGrpcPort();
+
+ // Wire the SSH bridge before either side starts so daprd can resolve
+ // host.testcontainers.internal:appPort the moment its container boots.
+ Testcontainers.exposeHostPorts(appPort);
+
+ // Start the app subprocess BEFORE daprd. AppRun.start() blocks on
+ // assertListeningOnPort, so by the time it returns the JVM has bound
+ // appPort and /dapr/config will respond with the registered actor types.
+ // DAPR_HTTP_PORT/DAPR_GRPC_PORT point at the pre-allocated host ports
+ // we will pin daprd to below; this is what app-side callbacks like
+ // registerActorTimer use to dial back to the sidecar.
+ AppRun app = new AppRun(
+ ports,
+ // Empty success-message: the legacy "dapr initialized. Status: Running" string is
+ // emitted by daprd's stdout, which used to be merged into the subprocess output by
+ // the dapr CLI but is now isolated in the Docker container. Pass "" so Command.run()
+ // returns on Maven's first stdout line; AppRun.start() then waits for the app to
+ // actually bind its port via assertListeningOnPort, which is the real readiness
+ // signal in the containerized world.
+ "",
+ serviceClass,
+ 60_000,
+ daprHttpPort,
+ daprGrpcPort);
+ app.start();
+ deferStop(app);
+
+ DaprContainer dapr = daprFactory.apply(appPort);
+ // Pin daprd's host ports so they match the values the AppRun's env was
+ // already given. Must be done before .start().
+ dapr.setPortBindings(java.util.List.of(
+ daprHttpPort + ":3500",
+ daprGrpcPort + ":50001"
+ ));
+ dapr.start();
+ deferStop(dapr);
+
+ // Daprd's HTTP healthz/outbound (the wait strategy on DaprContainer) returns 2xx as
+ // soon as outbound connections are ready, but its gRPC server can be a beat behind.
+ // Tests that use the gRPC channel (method-invoke gRPC, tracing) hit "error reading
+ // server preface: EOF" if they call too soon. Prove the gRPC channel is responsive
+ // by issuing a waitForSidecar against a fresh DaprClient before returning.
+ try (DaprClient client = newDaprClient(dapr)) {
+ client.waitForSidecar(30_000).block();
+ }
+ return new DaprAndApp(dapr, app);
+ }
+
+ /**
+ * Polls daprd's metadata endpoint until at least one actor is registered. Call from
+ * {@code @BeforeAll} of actor ITs after {@link #startAppAndAttach} returns: the app
+ * subprocess takes a moment to register its actor types with daprd, and tests will
+ * fail with "did not find address for actor" if invoked too early.
+ */
+ protected static void waitForActorsReady(DaprContainer dapr) {
+ DaprWait.forActors().waitUntilReady(dapr);
+ }
+
+ // ---------- DaprClient / ActorClient factories ----------
+
+ protected static DaprClient newDaprClient(DaprContainer dapr) {
+ return newDaprClientBuilder(dapr).build();
+ }
+
+ protected static DaprClientBuilder newDaprClientBuilder(DaprContainer dapr) {
+ return new DaprClientBuilder().withPropertyOverrides(daprOverrides(dapr));
+ }
+
+ protected static ActorClient newActorClient(DaprContainer dapr) {
+ ActorClient client = new ActorClient(new Properties(daprOverrides(dapr)), null);
+ deferClose(client);
+ return client;
+ }
+
+ /**
+ * ActorClient overload that injects HTTP headers (metadata) on actor calls.
+ * Used by ITs that need to override request-level headers like Content-Length.
+ */
+ protected static ActorClient newActorClient(DaprContainer dapr, Map metadata) {
+ ActorClient client = new ActorClient(new Properties(daprOverrides(dapr)), metadata, null);
+ deferClose(client);
+ return client;
+ }
+
+ private static Map, String> daprOverrides(DaprContainer dapr) {
+ Map, String> overrides = new HashMap<>();
+ overrides.put(Properties.HTTP_ENDPOINT, "http://127.0.0.1:" + dapr.getHttpPort());
+ overrides.put(Properties.GRPC_ENDPOINT, "127.0.0.1:" + dapr.getGrpcPort());
+ overrides.put(Properties.HTTP_PORT, String.valueOf(dapr.getHttpPort()));
+ overrides.put(Properties.GRPC_PORT, String.valueOf(dapr.getGrpcPort()));
+ return overrides;
+ }
+
+ // ---------- Component helpers (Redis) ----------
+
+ protected static Component redisStateStore(String name) {
+ return new Component(name, "state.redis", "v1", Map.of(
+ "redisHost", SharedTestInfra.redisInternalHost(),
+ "redisPassword", "",
+ "actorStateStore", "true"
+ ));
+ }
+
+ protected static Component redisPubSub(String name) {
+ return new Component(name, "pubsub.redis", "v1", Map.of(
+ "redisHost", SharedTestInfra.redisInternalHost(),
+ "redisPassword", "",
+ "processingTimeout", "100ms",
+ "redeliverInterval", "100ms"
+ ));
+ }
+
+ protected static Component redisConfigStore(String name) {
+ return new Component(name, "configuration.redis", "v1", Map.of(
+ "redisHost", SharedTestInfra.redisInternalHost(),
+ "redisPassword", ""
+ ));
+ }
+
+ /**
+ * Mongo-backed state store with query API support. Lazily starts the
+ * shared Mongo container before returning the component. Used by
+ * {@code AbstractStateClientIT#saveAndQueryAndDeleteState}, which exercises
+ * the Dapr preview Query State API — Redis doesn't support that API, so a
+ * separate store is required.
+ */
+ protected static Component mongoStateStore(String name) {
+ SharedTestInfra.mongo(); // ensure Mongo is up before DaprContainer needs it
+ return new Component(name, "state.mongodb", "v1", Map.of(
+ "host", SharedTestInfra.mongoInternalHost(),
+ "databaseName", "local",
+ "collectionName", "testCollection"
+ ));
+ }
+
+ // ---------- Cleanup ----------
+
+ protected static T deferClose(T object) {
+ TO_BE_CLOSED.push(object);
+ return object;
+ }
+
+ /**
+ * Defer-stop a plain {@link Stoppable} (e.g., {@link AppRun}).
+ * Use the {@link #deferStop(org.testcontainers.containers.GenericContainer) GenericContainer overload}
+ * for Testcontainers — they aren't {@code Stoppable}.
+ */
+ protected static void deferStop(Stoppable stoppable) {
+ TO_BE_STOPPED.push(stoppable);
+ }
+
+ /**
+ * Adapter so a Testcontainer can be registered alongside AppRuns in the
+ * stop queue.
+ */
+ protected static void deferStop(org.testcontainers.containers.GenericContainer> container) {
+ TO_BE_STOPPED.push(() -> container.stop());
+ }
+
+ @AfterAll
+ protected static void cleanUp() throws Exception {
+ while (!TO_BE_STOPPED.isEmpty()) {
+ try {
+ TO_BE_STOPPED.pop().stop();
+ } catch (Exception e) {
+ // best-effort
+ e.printStackTrace();
+ }
+ }
+ while (!TO_BE_CLOSED.isEmpty()) {
+ try {
+ TO_BE_CLOSED.pop().close();
+ } catch (Exception e) {
+ // best-effort
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/containers/BaseContainerITSmokeTest.java b/sdk-tests/src/test/java/io/dapr/it/containers/BaseContainerITSmokeTest.java
new file mode 100644
index 0000000000..a779fd7788
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/containers/BaseContainerITSmokeTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package io.dapr.it.containers;
+
+import io.dapr.client.DaprClient;
+import io.dapr.testcontainers.DaprContainer;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Minimal smoke test that exercises BaseContainerIT's helpers end-to-end.
+ * Boots a no-app DaprContainer with no components and verifies that we can
+ * build a DaprClient against it and invoke a metadata call.
+ */
+class BaseContainerITSmokeTest extends BaseContainerIT {
+
+ private static DaprContainer dapr;
+
+ @BeforeAll
+ static void init() {
+ dapr = daprBuilder("smoke-test");
+ dapr.start();
+ deferStop(dapr);
+ }
+
+ @Test
+ void canBuildAndUseDaprClient() throws Exception {
+ try (DaprClient client = newDaprClient(dapr)) {
+ // waitForSidecar is a cheap healthcheck — it's fine if it returns immediately.
+ client.waitForSidecar(5000).block();
+ assertNotNull(client);
+ }
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/containers/SharedTestInfra.java b/sdk-tests/src/test/java/io/dapr/it/containers/SharedTestInfra.java
new file mode 100644
index 0000000000..e778d1f7d9
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/containers/SharedTestInfra.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package io.dapr.it.containers;
+
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * JVM-singleton holder for backing service containers shared across all
+ * migrated integration tests. Containers are started lazily on first access
+ * and reused for the lifetime of the JVM. With {@code withReuse(true)}, dev
+ * machines that opt in via ~/.testcontainers.properties also reuse across
+ * JVM runs.
+ */
+public final class SharedTestInfra {
+
+ private static final String REDIS_NETWORK_ALIAS = "redis";
+ private static final String ZIPKIN_NETWORK_ALIAS = "zipkin";
+ private static final String MONGO_NETWORK_ALIAS = "mongo";
+
+ private static volatile Network network;
+ private static volatile GenericContainer> redis;
+ private static volatile GenericContainer> zipkin;
+ private static volatile GenericContainer> mongo;
+
+ private SharedTestInfra() {}
+
+ public static synchronized Network network() {
+ if (network == null) {
+ network = Network.newNetwork();
+ }
+ return network;
+ }
+
+ public static synchronized GenericContainer> redis() {
+ if (redis == null) {
+ redis = new GenericContainer<>(DockerImageName.parse("redis:7-alpine"))
+ .withNetwork(network())
+ .withNetworkAliases(REDIS_NETWORK_ALIAS)
+ .withExposedPorts(6379)
+ .withReuse(true);
+ redis.start();
+ }
+ return redis;
+ }
+
+ public static String redisInternalHost() {
+ return REDIS_NETWORK_ALIAS + ":6379";
+ }
+
+ public static synchronized GenericContainer> zipkin() {
+ if (zipkin == null) {
+ zipkin = new GenericContainer<>(DockerImageName.parse("openzipkin/zipkin:3.4"))
+ .withNetwork(network())
+ .withNetworkAliases(ZIPKIN_NETWORK_ALIAS)
+ .withExposedPorts(9411)
+ .withReuse(true);
+ zipkin.start();
+ }
+ return zipkin;
+ }
+
+ public static String zipkinInternalEndpoint() {
+ return "http://" + ZIPKIN_NETWORK_ALIAS + ":9411/api/v2/spans";
+ }
+
+ public static synchronized GenericContainer> mongo() {
+ if (mongo == null) {
+ mongo = new GenericContainer<>(DockerImageName.parse("mongo:7"))
+ .withNetwork(network())
+ .withNetworkAliases(MONGO_NETWORK_ALIAS)
+ .withExposedPorts(27017)
+ .withReuse(true);
+ mongo.start();
+ }
+ return mongo;
+ }
+
+ public static String mongoInternalHost() {
+ return MONGO_NETWORK_ALIAS + ":27017";
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/containers/SharedTestInfraTest.java b/sdk-tests/src/test/java/io/dapr/it/containers/SharedTestInfraTest.java
new file mode 100644
index 0000000000..909b6662e1
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/containers/SharedTestInfraTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package io.dapr.it.containers;
+
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class SharedTestInfraTest {
+
+ @Test
+ void networkIsSingleton() {
+ Network n1 = SharedTestInfra.network();
+ Network n2 = SharedTestInfra.network();
+ assertSame(n1, n2);
+ }
+
+ @Test
+ void redisStartsAndIsReachable() {
+ GenericContainer> redis = SharedTestInfra.redis();
+ assertTrue(redis.isRunning());
+ assertNotNull(redis.getMappedPort(6379));
+ assertEquals("redis", redis.getNetworkAliases().get(0));
+ }
+
+ @Test
+ void redisInternalHostFormat() {
+ SharedTestInfra.redis(); // ensure started
+ assertEquals("redis:6379", SharedTestInfra.redisInternalHost());
+ }
+
+ @Test
+ void zipkinStartsAndIsReachable() {
+ GenericContainer> z = SharedTestInfra.zipkin();
+ assertTrue(z.isRunning());
+ assertNotNull(z.getMappedPort(9411));
+ assertEquals("zipkin", z.getNetworkAliases().get(0));
+ }
+
+ @Test
+ void zipkinInternalEndpointFormat() {
+ SharedTestInfra.zipkin(); // ensure started
+ assertEquals("http://zipkin:9411/api/v2/spans", SharedTestInfra.zipkinInternalEndpoint());
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeIT.java b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeIT.java
index ea94d2136e..f1be9f354c 100644
--- a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeIT.java
+++ b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/grpc/MethodInvokeIT.java
@@ -1,15 +1,28 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
package io.dapr.it.methodinvoke.grpc;
import io.dapr.client.DaprClient;
-import io.dapr.client.DaprClientBuilder;
import io.dapr.client.resiliency.ResiliencyOptions;
import io.dapr.it.AppRun;
-import io.dapr.it.BaseIT;
-import io.dapr.it.DaprRun;
import io.dapr.it.MethodInvokeServiceGrpc;
+import io.dapr.it.containers.BaseContainerIT;
+import io.dapr.testcontainers.DaprContainer;
+import io.dapr.testcontainers.DaprProtocol;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
-import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.time.Duration;
@@ -23,38 +36,41 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
-public class MethodInvokeIT extends BaseIT {
+public class MethodInvokeIT extends BaseContainerIT {
- //Number of messages to be sent: 10
+ private static final String APP_NAME = "methodinvoke-grpc-it";
private static final int NUM_MESSAGES = 10;
private static final int TIMEOUT_MS = 100;
private static final ResiliencyOptions RESILIENCY_OPTIONS = new ResiliencyOptions()
.setTimeout(Duration.ofMillis(TIMEOUT_MS));
- /**
- * Run of a Dapr application.
- */
- private DaprRun daprRun = null;
-
- @BeforeEach
- public void init() throws Exception {
- daprRun = startDaprApp(
- MethodInvokeIT.class.getSimpleName() + "grpc",
- MethodInvokeService.SUCCESS_MESSAGE,
- MethodInvokeService.class,
- AppRun.AppProtocol.GRPC, // appProtocol
- 60000);
- daprRun.waitForAppHealth(40000);
+ private static DaprContainer dapr;
+ private static AppRun app;
+
+ @BeforeAll
+ public static void init() throws Exception {
+ var pair = startAppAndAttach(
+ APP_NAME,
+ MethodInvokeService.class,
+ AppRun.AppProtocol.GRPC,
+ appPort -> {
+ DaprContainer d = daprBuilder(APP_NAME)
+ .withAppPort(appPort)
+ .withAppChannelAddress("host.testcontainers.internal")
+ .withAppProtocol(DaprProtocol.GRPC);
+ return d;
+ });
+ dapr = pair.dapr();
+ app = pair.app();
}
@Test
public void testInvoke() throws Exception {
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
+ try (DaprClient client = newDaprClient(dapr)) {
client.waitForSidecar(10000).block();
- daprRun.waitForAppHealth(10000);
MethodInvokeServiceGrpc.MethodInvokeServiceBlockingStub stub = createGrpcStub(client);
-
+
for (int i = 0; i < NUM_MESSAGES; i++) {
String message = String.format("This is message #%d", i);
PostMessageRequest req = PostMessageRequest.newBuilder().setId(i).setMessage(message).build();
@@ -81,9 +97,8 @@ public void testInvoke() throws Exception {
@Test
public void testInvokeTimeout() throws Exception {
- try (DaprClient client = daprRun.newDaprClientBuilder().withResiliencyOptions(RESILIENCY_OPTIONS).build()) {
+ try (DaprClient client = newDaprClientBuilder(dapr).withResiliencyOptions(RESILIENCY_OPTIONS).build()) {
client.waitForSidecar(10000).block();
- daprRun.waitForAppHealth(10000);
MethodInvokeServiceGrpc.MethodInvokeServiceBlockingStub stub = createGrpcStub(client);
long started = System.currentTimeMillis();
@@ -99,9 +114,8 @@ public void testInvokeTimeout() throws Exception {
@Test
public void testInvokeException() throws Exception {
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
+ try (DaprClient client = newDaprClient(dapr)) {
client.waitForSidecar(10000).block();
- daprRun.waitForAppHealth(10000);
MethodInvokeServiceGrpc.MethodInvokeServiceBlockingStub stub = createGrpcStub(client);
@@ -118,7 +132,7 @@ public void testInvokeException() throws Exception {
}
}
- private MethodInvokeServiceGrpc.MethodInvokeServiceBlockingStub createGrpcStub(DaprClient client) {
- return client.newGrpcStub(daprRun.getAppName(), MethodInvokeServiceGrpc::newBlockingStub);
+ private static MethodInvokeServiceGrpc.MethodInvokeServiceBlockingStub createGrpcStub(DaprClient client) {
+ return client.newGrpcStub(APP_NAME, MethodInvokeServiceGrpc::newBlockingStub);
}
}
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 3c1ee01b51..6a64665b9e 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
@@ -1,3 +1,16 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
package io.dapr.it.methodinvoke.http;
import com.fasterxml.jackson.databind.JsonNode;
@@ -5,10 +18,11 @@
import io.dapr.client.DaprHttp;
import io.dapr.client.domain.HttpExtension;
import io.dapr.exceptions.DaprException;
-import io.dapr.it.BaseIT;
-import io.dapr.it.DaprRun;
+import io.dapr.it.AppRun;
import io.dapr.it.MethodInvokeServiceProtos;
-import org.junit.jupiter.api.BeforeEach;
+import io.dapr.it.containers.BaseContainerIT;
+import io.dapr.testcontainers.DaprContainer;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.time.Duration;
@@ -24,76 +38,74 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
@SuppressWarnings("deprecation")
-public class MethodInvokeIT extends BaseIT {
+public class MethodInvokeIT extends BaseContainerIT {
- //Number of messages to be sent: 10
+ private static final String APP_NAME = "methodinvoke-http-it";
private static final int NUM_MESSAGES = 10;
- /**
- * Run of a Dapr application.
- */
- private DaprRun daprRun = null;
-
- @BeforeEach
- public void init() throws Exception {
- daprRun = startDaprApp(
- MethodInvokeIT.class.getSimpleName() + "http",
- MethodInvokeService.SUCCESS_MESSAGE,
- MethodInvokeService.class,
- true,
- 30000);
- daprRun.waitForAppHealth(20000);
+ private static DaprContainer dapr;
+ private static AppRun app;
+
+ @BeforeAll
+ public static void init() throws Exception {
+ var pair = startAppAndAttach(
+ APP_NAME,
+ MethodInvokeService.class,
+ AppRun.AppProtocol.HTTP,
+ appPort -> {
+ DaprContainer d = daprBuilder(APP_NAME)
+ .withAppPort(appPort)
+ .withAppChannelAddress("host.testcontainers.internal");
+ return d;
+ });
+ dapr = pair.dapr();
+ app = pair.app();
}
@Test
public void testInvoke() throws Exception {
-
- // At this point, it is guaranteed that the service above is running and all ports being listened to.
-
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
+ try (DaprClient client = newDaprClient(dapr)) {
client.waitForSidecar(10000).block();
for (int i = 0; i < NUM_MESSAGES; i++) {
String message = String.format("This is message #%d", i);
- //Publishing messages
- client.invokeMethod(daprRun.getAppName(), "messages", message.getBytes(), HttpExtension.POST).block();
+ client.invokeMethod(APP_NAME, "messages", message.getBytes(), HttpExtension.POST).block();
System.out.println("Invoke method messages : " + message);
}
- Map messages = client.invokeMethod(daprRun.getAppName(), "messages", null,
+ Map messages = client.invokeMethod(APP_NAME, "messages", null,
HttpExtension.GET, Map.class).block();
assertEquals(10, messages.size());
- client.invokeMethod(daprRun.getAppName(), "messages/1", null, HttpExtension.DELETE).block();
+ client.invokeMethod(APP_NAME, "messages/1", null, HttpExtension.DELETE).block();
- messages = client.invokeMethod(daprRun.getAppName(), "messages", null, HttpExtension.GET, Map.class).block();
+ messages = client.invokeMethod(APP_NAME, "messages", null, HttpExtension.GET, Map.class).block();
assertEquals(9, messages.size());
- client.invokeMethod(daprRun.getAppName(), "messages/2", "updated message".getBytes(), HttpExtension.PUT).block();
- messages = client.invokeMethod(daprRun.getAppName(), "messages", null, HttpExtension.GET, Map.class).block();
+ client.invokeMethod(APP_NAME, "messages/2", "updated message".getBytes(), HttpExtension.PUT).block();
+ messages = client.invokeMethod(APP_NAME, "messages", null, HttpExtension.GET, Map.class).block();
assertEquals("updated message", messages.get("2"));
}
}
@Test
public void testInvokeWithObjects() throws Exception {
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
+ try (DaprClient client = newDaprClient(dapr)) {
client.waitForSidecar(10000).block();
for (int i = 0; i < NUM_MESSAGES; i++) {
Person person = new Person();
person.setName(String.format("Name %d", i));
person.setLastName(String.format("Last Name %d", i));
person.setBirthDate(new Date());
- //Publishing messages
- client.invokeMethod(daprRun.getAppName(), "persons", person, HttpExtension.POST).block();
+ client.invokeMethod(APP_NAME, "persons", person, HttpExtension.POST).block();
System.out.println("Invoke method persons with parameter : " + person);
}
- List persons = Arrays.asList(client.invokeMethod(daprRun.getAppName(), "persons", null, HttpExtension.GET, Person[].class).block());
+ List persons = Arrays.asList(client.invokeMethod(APP_NAME, "persons", null, HttpExtension.GET, Person[].class).block());
assertEquals(10, persons.size());
- client.invokeMethod(daprRun.getAppName(), "persons/1", null, HttpExtension.DELETE).block();
+ client.invokeMethod(APP_NAME, "persons/1", null, HttpExtension.DELETE).block();
- persons = Arrays.asList(client.invokeMethod(daprRun.getAppName(), "persons", null, HttpExtension.GET, Person[].class).block());
+ persons = Arrays.asList(client.invokeMethod(APP_NAME, "persons", null, HttpExtension.GET, Person[].class).block());
assertEquals(9, persons.size());
Person person = new Person();
@@ -101,9 +113,9 @@ public void testInvokeWithObjects() throws Exception {
person.setLastName("Smith");
person.setBirthDate(Calendar.getInstance().getTime());
- client.invokeMethod(daprRun.getAppName(), "persons/2", person, HttpExtension.PUT).block();
+ client.invokeMethod(APP_NAME, "persons/2", person, HttpExtension.PUT).block();
- persons = Arrays.asList(client.invokeMethod(daprRun.getAppName(), "persons", null, HttpExtension.GET, Person[].class).block());
+ persons = Arrays.asList(client.invokeMethod(APP_NAME, "persons", null, HttpExtension.GET, Person[].class).block());
Person resultPerson = persons.get(1);
assertEquals("John", resultPerson.getName());
assertEquals("Smith", resultPerson.getLastName());
@@ -112,11 +124,11 @@ public void testInvokeWithObjects() throws Exception {
@Test
public void testInvokeTimeout() throws Exception {
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
+ try (DaprClient client = newDaprClient(dapr)) {
client.waitForSidecar(10000).block();
long started = System.currentTimeMillis();
String message = assertThrows(IllegalStateException.class, () -> {
- client.invokeMethod(daprRun.getAppName(), "sleep", 1, HttpExtension.POST)
+ client.invokeMethod(APP_NAME, "sleep", 1, HttpExtension.POST)
.block(Duration.ofMillis(10));
}).getMessage();
@@ -129,11 +141,11 @@ public void testInvokeTimeout() throws Exception {
@Test
public void testInvokeException() throws Exception {
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
+ try (DaprClient client = newDaprClient(dapr)) {
client.waitForSidecar(10000).block();
MethodInvokeServiceProtos.SleepRequest req = MethodInvokeServiceProtos.SleepRequest.newBuilder().setSeconds(-9).build();
DaprException exception = assertThrows(DaprException.class, () ->
- client.invokeMethod(daprRun.getAppName(), "sleep", -9, HttpExtension.POST).block());
+ client.invokeMethod(APP_NAME, "sleep", -9, HttpExtension.POST).block());
// TODO(artursouza): change this to INTERNAL once runtime is fixed.
assertEquals("UNKNOWN", exception.getErrorCode());
@@ -145,14 +157,14 @@ public void testInvokeException() throws Exception {
@Test
public void testInvokeQueryParamEncoding() throws Exception {
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
+ try (DaprClient client = newDaprClient(dapr)) {
client.waitForSidecar(10000).block();
String uri = "abc/pqr";
Map> queryParams = Map.of("uri", List.of(uri));
HttpExtension httpExtension = new HttpExtension(DaprHttp.HttpMethods.GET, queryParams, Map.of());
JsonNode result = client.invokeMethod(
- daprRun.getAppName(),
+ APP_NAME,
"/query",
null,
httpExtension,
diff --git a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/PubSubIT.java b/sdk-tests/src/test/java/io/dapr/it/pubsub/http/PubSubIT.java
deleted file mode 100644
index 377d51e765..0000000000
--- a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/PubSubIT.java
+++ /dev/null
@@ -1,723 +0,0 @@
-/*
- * Copyright 2021 The Dapr Authors
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package io.dapr.it.pubsub.http;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.dapr.client.DaprClient;
-import io.dapr.client.DaprClientBuilder;
-import io.dapr.client.domain.BulkPublishEntry;
-import io.dapr.client.domain.BulkPublishRequest;
-import io.dapr.client.domain.BulkPublishResponse;
-import io.dapr.client.domain.BulkSubscribeAppResponse;
-import io.dapr.client.domain.BulkSubscribeAppResponseEntry;
-import io.dapr.client.domain.BulkSubscribeAppResponseStatus;
-import io.dapr.client.domain.CloudEvent;
-import io.dapr.client.domain.HttpExtension;
-import io.dapr.client.domain.Metadata;
-import io.dapr.client.domain.PublishEventRequest;
-import io.dapr.it.BaseIT;
-import io.dapr.it.DaprRun;
-import io.dapr.serializer.DaprObjectSerializer;
-import io.dapr.utils.TypeRef;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Random;
-import java.util.Set;
-
-import static io.dapr.it.Retry.callWithRetry;
-import static io.dapr.it.TestUtils.assertThrowsDaprException;
-import static io.dapr.it.TestUtils.assertThrowsDaprExceptionWithReason;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-
-public class PubSubIT extends BaseIT {
-
- private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
-
- private static final TypeRef> CLOUD_EVENT_LIST_TYPE_REF = new TypeRef<>() {};
- private static final TypeRef>> CLOUD_EVENT_LONG_LIST_TYPE_REF = new TypeRef<>() {};
- private static final TypeRef>> CLOUD_EVENT_MYOBJECT_LIST_TYPE_REF = new TypeRef<>() {};
-
- //Number of messages to be sent: 10
- private static final int NUM_MESSAGES = 10;
-
- private static final String PUBSUB_NAME = "messagebus";
- //The title of the topic to be used for publishing
- private static final String TOPIC_NAME = "testingtopic";
-
- private static final String TOPIC_BULK = "testingbulktopic";
- private static final String TYPED_TOPIC_NAME = "typedtestingtopic";
- private static final String ANOTHER_TOPIC_NAME = "anothertopic";
- // Topic used for TTL test
- private static final String TTL_TOPIC_NAME = "ttltopic";
- // Topic to test binary data
- private static final String BINARY_TOPIC_NAME = "binarytopic";
-
- private static final String LONG_TOPIC_NAME = "testinglongvalues";
- // Topic to test bulk subscribe.
- private static final String BULK_SUB_TOPIC_NAME = "topicBulkSub";
-
- private final List runs = new ArrayList<>();
-
- private DaprRun closeLater(DaprRun run) {
- this.runs.add(run);
- return run;
- }
-
- @AfterEach
- public void tearDown() throws Exception {
- for (DaprRun run : runs) {
- run.stop();
- }
- }
-
- @Test
- public void publishPubSubNotFound() throws Exception {
- DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName(),
- 60000));
-
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
- assertThrowsDaprExceptionWithReason(
- "INVALID_ARGUMENT",
- "INVALID_ARGUMENT: pubsub unknown pubsub is not found",
- "DAPR_PUBSUB_NOT_FOUND",
- () -> client.publishEvent("unknown pubsub", "mytopic", "payload").block());
- }
- }
-
- @Test
- public void testBulkPublishPubSubNotFound() throws Exception {
- DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName(),
- 60000));
-
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
- assertThrowsDaprException(
- "INVALID_ARGUMENT",
- "INVALID_ARGUMENT: pubsub unknown pubsub is not found",
- () -> client.publishEvents("unknown pubsub", "mytopic","text/plain", "message").block());
- }
- }
-
- @Test
- public void testBulkPublish() throws Exception {
- final DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName(),
- SubscriberService.SUCCESS_MESSAGE,
- SubscriberService.class,
- true,
- 60000));
- DaprObjectSerializer serializer = new DaprObjectSerializer() {
- @Override
- public byte[] serialize(Object o) throws JsonProcessingException {
- return OBJECT_MAPPER.writeValueAsBytes(o);
- }
-
- @Override
- public T deserialize(byte[] data, TypeRef type) throws IOException {
- return (T) OBJECT_MAPPER.readValue(data, OBJECT_MAPPER.constructType(type.getType()));
- }
-
- @Override
- public String getContentType() {
- return "application/json";
- }
- };
- try (DaprClient client = daprRun.newDaprClientBuilder().withObjectSerializer(serializer).build()) {
- // Only for the gRPC test
- // Send a multiple messages on one topic in messagebus pubsub via publishEvents API.
- List messages = new ArrayList<>();
- for (int i = 0; i < NUM_MESSAGES; i++) {
- messages.add(String.format("This is message #%d on topic %s", i, TOPIC_BULK));
- }
- //Publishing 10 messages
- BulkPublishResponse response = client.publishEvents(PUBSUB_NAME, TOPIC_BULK, "", messages).block();
- System.out.println(String.format("Published %d messages to topic '%s' pubsub_name '%s'",
- NUM_MESSAGES, TOPIC_BULK, PUBSUB_NAME));
- assertNotNull(response, "expected not null bulk publish response");
- assertEquals( 0, response.getFailedEntries().size(), "expected no failures in the response");
-
- //Publishing an object.
- MyObject object = new MyObject();
- object.setId("123");
- response = client.publishEvents(PUBSUB_NAME, TOPIC_BULK,
- "application/json", Collections.singletonList(object)).block();
- System.out.println("Published one object.");
- assertNotNull(response, "expected not null bulk publish response");
- assertEquals(0, response.getFailedEntries().size(), "expected no failures in the response");
-
- //Publishing a single byte: Example of non-string based content published
- client.publishEvents(PUBSUB_NAME, TOPIC_BULK, "",
- Collections.singletonList(new byte[]{1})).block();
- System.out.println("Published one byte.");
-
- assertNotNull(response, "expected not null bulk publish response");
- assertEquals(0, response.getFailedEntries().size(), "expected no failures in the response");
-
- CloudEvent cloudEvent = new CloudEvent();
- cloudEvent.setId("1234");
- cloudEvent.setData("message from cloudevent");
- cloudEvent.setSource("test");
- cloudEvent.setSpecversion("1");
- cloudEvent.setType("myevent");
- cloudEvent.setDatacontenttype("text/plain");
- BulkPublishRequest req = new BulkPublishRequest<>(PUBSUB_NAME, TOPIC_BULK,
- Collections.singletonList(
- new BulkPublishEntry<>("1", cloudEvent, "application/cloudevents+json", null)
- ));
-
- //Publishing a cloud event.
- client.publishEvents(req).block();
- assertNotNull(response, "expected not null bulk publish response");
- assertEquals(0, response.getFailedEntries().size(), "expected no failures in the response");
-
- System.out.println("Published one cloud event.");
-
- // Introduce sleep
- Thread.sleep(10000);
-
- // Check messagebus subscription for topic testingbulktopic since it is populated only by publishEvents API call
- callWithRetry(() -> {
- System.out.println("Checking results for topic " + TOPIC_BULK + " in pubsub " + PUBSUB_NAME);
- // Validate text payload.
- final List cloudEventMessages = client.invokeMethod(
- daprRun.getAppName(),
- "messages/redis/testingbulktopic",
- null,
- HttpExtension.GET,
- CLOUD_EVENT_LIST_TYPE_REF).block();
- assertEquals(13, cloudEventMessages.size(), "expected 13 messages to be received on subscribe");
- for (int i = 0; i < NUM_MESSAGES; i++) {
- final int messageId = i;
- assertTrue(cloudEventMessages
- .stream()
- .filter(m -> m.getData() != null)
- .map(m -> m.getData())
- .filter(m -> m.equals(String.format("This is message #%d on topic %s", messageId, TOPIC_BULK)))
- .count() == 1, "expected data content to match");
- }
-
- // Validate object payload.
- assertTrue(cloudEventMessages
- .stream()
- .filter(m -> m.getData() != null)
- .filter(m -> m.getData() instanceof LinkedHashMap)
- .map(m -> (LinkedHashMap) m.getData())
- .filter(m -> "123".equals(m.get("id")))
- .count() == 1, "expected data content 123 to match");
-
- // Validate byte payload.
- assertTrue(cloudEventMessages
- .stream()
- .filter(m -> m.getData() != null)
- .map(m -> m.getData())
- .filter(m -> "AQ==".equals(m))
- .count() == 1, "expected bin data to match");
-
- // Validate cloudevent payload.
- assertTrue( cloudEventMessages
- .stream()
- .filter(m -> m.getData() != null)
- .map(m -> m.getData())
- .filter(m -> "message from cloudevent".equals(m))
- .count() == 1, "expected data to match");
- }, 2000);
- }
-
- }
-
- @Test
- public void testPubSub() throws Exception {
- final DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName(),
- SubscriberService.SUCCESS_MESSAGE,
- SubscriberService.class,
- true,
- 60000));
-
- DaprObjectSerializer serializer = new DaprObjectSerializer() {
- @Override
- public byte[] serialize(Object o) throws JsonProcessingException {
- return OBJECT_MAPPER.writeValueAsBytes(o);
- }
-
- @Override
- public T deserialize(byte[] data, TypeRef type) throws IOException {
- return (T) OBJECT_MAPPER.readValue(data, OBJECT_MAPPER.constructType(type.getType()));
- }
-
- @Override
- public String getContentType() {
- return "application/json";
- }
- };
-
- // Send a batch of messages on one topic
- try (DaprClient client = daprRun.newDaprClientBuilder().withObjectSerializer(serializer).build()) {
- for (int i = 0; i < NUM_MESSAGES; i++) {
- String message = String.format("This is message #%d on topic %s", i, TOPIC_NAME);
- //Publishing messages
- client.publishEvent(PUBSUB_NAME, TOPIC_NAME, message).block();
- System.out.println(String.format("Published message: '%s' to topic '%s' pubsub_name '%s'", message, TOPIC_NAME, PUBSUB_NAME));
- }
-
- // Send a batch of different messages on the other.
- for (int i = 0; i < NUM_MESSAGES; i++) {
- String message = String.format("This is message #%d on topic %s", i, ANOTHER_TOPIC_NAME);
- //Publishing messages
- client.publishEvent(PUBSUB_NAME, ANOTHER_TOPIC_NAME, message).block();
- System.out.println(String.format("Published message: '%s' to topic '%s' pubsub_name '%s'", message, ANOTHER_TOPIC_NAME, PUBSUB_NAME));
- }
-
- //Publishing an object.
- MyObject object = new MyObject();
- object.setId("123");
- client.publishEvent(PUBSUB_NAME, TOPIC_NAME, object).block();
- System.out.println("Published one object.");
-
- client.publishEvent(PUBSUB_NAME, TYPED_TOPIC_NAME, object).block();
- System.out.println("Published another object.");
-
- //Publishing a single byte: Example of non-string based content published
- client.publishEvent(
- PUBSUB_NAME,
- TOPIC_NAME,
- new byte[]{1}).block();
- System.out.println("Published one byte.");
-
- CloudEvent cloudEvent = new CloudEvent();
- cloudEvent.setId("1234");
- cloudEvent.setData("message from cloudevent");
- cloudEvent.setSource("test");
- cloudEvent.setSpecversion("1");
- cloudEvent.setType("myevent");
- cloudEvent.setDatacontenttype("text/plain");
-
- //Publishing a cloud event.
- client.publishEvent(new PublishEventRequest(PUBSUB_NAME, TOPIC_NAME, cloudEvent)
- .setContentType("application/cloudevents+json")).block();
- System.out.println("Published one cloud event.");
-
- {
- CloudEvent cloudEventV2 = new CloudEvent();
- cloudEventV2.setId("2222");
- cloudEventV2.setData("message from cloudevent v2");
- cloudEventV2.setSource("test");
- cloudEventV2.setSpecversion("1");
- cloudEventV2.setType("myevent.v2");
- cloudEventV2.setDatacontenttype("text/plain");
- client.publishEvent(
- new PublishEventRequest(PUBSUB_NAME, TOPIC_NAME, cloudEventV2)
- .setContentType("application/cloudevents+json")).block();
- System.out.println("Published one cloud event for v2.");
- }
-
- {
- CloudEvent cloudEventV3 = new CloudEvent();
- cloudEventV3.setId("3333");
- cloudEventV3.setData("message from cloudevent v3");
- cloudEventV3.setSource("test");
- cloudEventV3.setSpecversion("1");
- cloudEventV3.setType("myevent.v3");
- cloudEventV3.setDatacontenttype("text/plain");
- client.publishEvent(
- new PublishEventRequest(PUBSUB_NAME, TOPIC_NAME, cloudEventV3)
- .setContentType("application/cloudevents+json")).block();
- System.out.println("Published one cloud event for v3.");
- }
-
- Thread.sleep(2000);
-
- callWithRetry(() -> {
- System.out.println("Checking results for topic " + TOPIC_NAME);
- // Validate text payload.
- final List messages = client.invokeMethod(
- daprRun.getAppName(),
- "messages/testingtopic",
- null,
- HttpExtension.GET,
- CLOUD_EVENT_LIST_TYPE_REF).block();
- assertEquals(13, messages.size());
- for (int i = 0; i < NUM_MESSAGES; i++) {
- final int messageId = i;
- assertTrue(messages
- .stream()
- .filter(m -> m.getData() != null)
- .map(m -> m.getData())
- .filter(m -> m.equals(String.format("This is message #%d on topic %s", messageId, TOPIC_NAME)))
- .count() == 1);
- }
-
- // Validate object payload.
- assertTrue(messages
- .stream()
- .filter(m -> m.getData() != null)
- .filter(m -> m.getData() instanceof LinkedHashMap)
- .map(m -> (LinkedHashMap)m.getData())
- .filter(m -> "123".equals(m.get("id")))
- .count() == 1);
-
- // Validate byte payload.
- assertTrue(messages
- .stream()
- .filter(m -> m.getData() != null)
- .map(m -> m.getData())
- .filter(m -> "AQ==".equals(m))
- .count() == 1);
-
- // Validate cloudevent payload.
- assertTrue(messages
- .stream()
- .filter(m -> m.getData() != null)
- .map(m -> m.getData())
- .filter(m -> "message from cloudevent".equals(m))
- .count() == 1);
- }, 2000);
-
- callWithRetry(() -> {
- System.out.println("Checking results for topic " + TOPIC_NAME + " V2");
- // Validate text payload.
- final List messages = client.invokeMethod(
- daprRun.getAppName(),
- "messages/testingtopicV2",
- null,
- HttpExtension.GET,
- CLOUD_EVENT_LIST_TYPE_REF).block();
- assertEquals(1, messages.size());
- }, 2000);
-
- callWithRetry(() -> {
- System.out.println("Checking results for topic " + TOPIC_NAME + " V3");
- // Validate text payload.
- final List messages = client.invokeMethod(
- daprRun.getAppName(),
- "messages/testingtopicV3",
- null,
- HttpExtension.GET,
- CLOUD_EVENT_LIST_TYPE_REF).block();
- assertEquals(1, messages.size());
- }, 2000);
-
- callWithRetry(() -> {
- System.out.println("Checking results for topic " + TYPED_TOPIC_NAME);
- // Validate object payload.
- final List> messages = client.invokeMethod(
- daprRun.getAppName(),
- "messages/typedtestingtopic",
- null,
- HttpExtension.GET,
- CLOUD_EVENT_MYOBJECT_LIST_TYPE_REF).block();
-
- assertTrue(messages
- .stream()
- .filter(m -> m.getData() != null)
- .filter(m -> m.getData() instanceof MyObject)
- .map(m -> (MyObject)m.getData())
- .filter(m -> "123".equals(m.getId()))
- .count() == 1);
- }, 2000);
-
- callWithRetry(() -> {
- System.out.println("Checking results for topic " + ANOTHER_TOPIC_NAME);
- final List messages = client.invokeMethod(
- daprRun.getAppName(),
- "messages/anothertopic",
- null,
- HttpExtension.GET,
- CLOUD_EVENT_LIST_TYPE_REF).block();
- assertEquals(10, messages.size());
-
- for (int i = 0; i < NUM_MESSAGES; i++) {
- final int messageId = i;
- assertTrue(messages
- .stream()
- .filter(m -> m.getData() != null)
- .map(m -> m.getData())
- .filter(m -> m.equals(String.format("This is message #%d on topic %s", messageId, ANOTHER_TOPIC_NAME)))
- .count() == 1);
- }
- }, 2000);
- }
- }
-
- @Test
- public void testPubSubBinary() throws Exception {
- final DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName(),
- SubscriberService.SUCCESS_MESSAGE,
- SubscriberService.class,
- true,
- 60000));
-
- DaprObjectSerializer serializer = new DaprObjectSerializer() {
- @Override
- public byte[] serialize(Object o) {
- return (byte[])o;
- }
-
- @Override
- public T deserialize(byte[] data, TypeRef type) {
- return (T) data;
- }
-
- @Override
- public String getContentType() {
- return "application/octet-stream";
- }
- };
- try (DaprClient client = daprRun.newDaprClientBuilder().withObjectSerializer(serializer).build()) {
- client.publishEvent(
- PUBSUB_NAME,
- BINARY_TOPIC_NAME,
- new byte[]{1}).block();
- System.out.println("Published one byte.");
- }
-
- Thread.sleep(3000);
-
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
- callWithRetry(() -> {
- System.out.println("Checking results for topic " + BINARY_TOPIC_NAME);
- final List messages = client.invokeMethod(
- daprRun.getAppName(),
- "messages/binarytopic",
- null,
- HttpExtension.GET, CLOUD_EVENT_LIST_TYPE_REF).block();
- assertEquals(1, messages.size());
- assertNull(messages.get(0).getData());
- assertArrayEquals(new byte[]{1}, messages.get(0).getBinaryData());
- }, 2000);
- }
- }
-
- @Test
- public void testPubSubTTLMetadata() throws Exception {
- DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName(),
- 60000));
-
- // Send a batch of messages on one topic, all to be expired in 1 second.
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
- for (int i = 0; i < NUM_MESSAGES; i++) {
- String message = String.format("This is message #%d on topic %s", i, TTL_TOPIC_NAME);
- //Publishing messages
- client.publishEvent(
- PUBSUB_NAME,
- TTL_TOPIC_NAME,
- message,
- Map.of(Metadata.TTL_IN_SECONDS, "1")).block();
- System.out.println(String.format("Published message: '%s' to topic '%s' pubsub_name '%s'", message, TOPIC_NAME, PUBSUB_NAME));
- }
- }
-
- daprRun.stop();
-
- // Sleeps for two seconds to let them expire.
- Thread.sleep(2000);
-
- daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName(),
- SubscriberService.SUCCESS_MESSAGE,
- SubscriberService.class,
- true,
- 60000));
-
- // Sleeps for five seconds to give subscriber a chance to receive messages.
- Thread.sleep(5000);
-
- final String appId = daprRun.getAppName();
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
- callWithRetry(() -> {
- System.out.println("Checking results for topic " + TTL_TOPIC_NAME);
- final List messages = client.invokeMethod(appId, "messages/" + TTL_TOPIC_NAME, null, HttpExtension.GET, List.class).block();
- assertEquals(0, messages.size());
- }, 2000);
- }
-
- daprRun.stop();
- }
-
- @Test
- public void testPubSubBulkSubscribe() throws Exception {
- DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName(),
- SubscriberService.SUCCESS_MESSAGE,
- SubscriberService.class,
- true,
- 60000));
-
- // Send a batch of messages on one topic.
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
- for (int i = 0; i < NUM_MESSAGES; i++) {
- String message = String.format("This is message #%d on topic %s", i, BULK_SUB_TOPIC_NAME);
- // Publishing messages
- client.publishEvent(PUBSUB_NAME, BULK_SUB_TOPIC_NAME, message).block();
- System.out.printf("Published message: '%s' to topic '%s' pubSub_name '%s'\n",
- message, BULK_SUB_TOPIC_NAME, PUBSUB_NAME);
- }
- }
-
- // Sleeps for five seconds to give subscriber a chance to receive messages.
- Thread.sleep(5000);
-
- final String appId = daprRun.getAppName();
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
- callWithRetry(() -> {
- System.out.println("Checking results for topic " + BULK_SUB_TOPIC_NAME);
-
- @SuppressWarnings("unchecked")
- Class> clazz = (Class) List.class;
-
- final List messages = client.invokeMethod(
- appId,
- "messages/" + BULK_SUB_TOPIC_NAME,
- null,
- HttpExtension.GET,
- clazz).block();
-
- assertNotNull(messages);
- BulkSubscribeAppResponse response = OBJECT_MAPPER.convertValue(messages.get(0), BulkSubscribeAppResponse.class);
-
- // There should be a single bulk response.
- assertEquals(1, messages.size());
-
- // The bulk response should contain NUM_MESSAGES entries.
- assertEquals(NUM_MESSAGES, response.getStatuses().size());
-
- // All the entries should be SUCCESS.
- for (BulkSubscribeAppResponseEntry entry : response.getStatuses()) {
- assertEquals(entry.getStatus(), BulkSubscribeAppResponseStatus.SUCCESS);
- }
- }, 2000);
- }
-
- daprRun.stop();
- }
-
- @Test
- public void testLongValues() throws Exception {
- final DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName(),
- SubscriberService.SUCCESS_MESSAGE,
- SubscriberService.class,
- true,
- 60000));
-
- Random random = new Random(590518626939830271L);
- Set values = new HashSet<>();
- values.add(new ConvertToLong().setVal(590518626939830271L));
- ConvertToLong val;
- for (int i = 0; i < NUM_MESSAGES - 1; i++) {
- do {
- val = new ConvertToLong().setVal(random.nextLong());
- } while (values.contains(val));
- values.add(val);
- }
- Iterator valuesIt = values.iterator();
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
- for (int i = 0; i < NUM_MESSAGES; i++) {
- ConvertToLong value = valuesIt.next();
- System.out.println("The long value sent " + value.getValue());
- //Publishing messages
- client.publishEvent(
- PUBSUB_NAME,
- LONG_TOPIC_NAME,
- value,
- Map.of(Metadata.TTL_IN_SECONDS, "30")).block();
-
- try {
- Thread.sleep((long) (1000 * Math.random()));
- } catch (InterruptedException e) {
- e.printStackTrace();
- Thread.currentThread().interrupt();
- return;
- }
- }
- }
-
- Set actual = new HashSet<>();
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
- callWithRetry(() -> {
- System.out.println("Checking results for topic " + LONG_TOPIC_NAME);
- final List> messages = client.invokeMethod(
- daprRun.getAppName(),
- "messages/testinglongvalues",
- null,
- HttpExtension.GET, CLOUD_EVENT_LONG_LIST_TYPE_REF).block();
- assertNotNull(messages);
- for (CloudEvent message : messages) {
- actual.add(message.getData());
- }
- Assertions.assertEquals(values, actual);
- }, 2000);
- }
- }
-
- public static class MyObject {
- private String id;
-
- public String getId() {
- return this.id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
- }
-
- public static class ConvertToLong {
- private Long value;
-
- public ConvertToLong setVal(Long value) {
- this.value = value;
- return this;
- }
-
- public Long getValue() {
- return value;
- }
-
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- ConvertToLong that = (ConvertToLong) o;
- return Objects.equals(value, that.value);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(value);
- }
- }
-
-}
diff --git a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/SubscriberController.java b/sdk-tests/src/test/java/io/dapr/it/pubsub/http/SubscriberController.java
deleted file mode 100644
index 9fc5df3ee2..0000000000
--- a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/SubscriberController.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright 2021 The Dapr Authors
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package io.dapr.it.pubsub.http;
-
-import io.dapr.Rule;
-import io.dapr.Topic;
-import io.dapr.client.domain.BulkSubscribeAppResponse;
-import io.dapr.client.domain.BulkSubscribeAppResponseEntry;
-import io.dapr.client.domain.BulkSubscribeAppResponseStatus;
-import io.dapr.client.domain.BulkSubscribeMessage;
-import io.dapr.client.domain.BulkSubscribeMessageEntry;
-import io.dapr.client.domain.CloudEvent;
-import io.dapr.springboot.annotations.BulkSubscribe;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
-import reactor.core.publisher.Mono;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.BiFunction;
-
-/**
- * SpringBoot Controller to handle input binding.
- */
-@RestController
-public class SubscriberController {
-
- private final Map>> messagesByTopic = Collections.synchronizedMap(new HashMap<>());
-
- @GetMapping(path = "/messages/{topic}")
- public List> getMessagesByTopic(@PathVariable("topic") String topic) {
- return messagesByTopic.getOrDefault(topic, Collections.emptyList());
- }
-
- private static final List messagesReceivedBulkPublishTopic = new ArrayList();
- private static final List messagesReceivedTestingTopic = new ArrayList();
- private static final List messagesReceivedTestingTopicV2 = new ArrayList();
- private static final List messagesReceivedTestingTopicV3 = new ArrayList();
- private static final List responsesReceivedTestingTopicBulkSub = new ArrayList<>();
-
- @GetMapping(path = "/messages/redis/testingbulktopic")
- public List getMessagesReceivedBulkTopic() {
- return messagesReceivedBulkPublishTopic;
- }
-
-
-
- @GetMapping(path = "/messages/testingtopic")
- public List getMessagesReceivedTestingTopic() {
- return messagesReceivedTestingTopic;
- }
-
- @GetMapping(path = "/messages/testingtopicV2")
- public List getMessagesReceivedTestingTopicV2() {
- return messagesReceivedTestingTopicV2;
- }
-
- @GetMapping(path = "/messages/testingtopicV3")
- public List getMessagesReceivedTestingTopicV3() {
- return messagesReceivedTestingTopicV3;
- }
-
- @GetMapping(path = "/messages/topicBulkSub")
- public List getMessagesReceivedTestingTopicBulkSub() {
- return responsesReceivedTestingTopicBulkSub;
- }
-
- @Topic(name = "testingtopic", pubsubName = "messagebus")
- @PostMapping("/route1")
- public Mono handleMessage(@RequestBody(required = false) CloudEvent envelope) {
- return Mono.fromRunnable(() -> {
- try {
- String message = envelope.getData() == null ? "" : envelope.getData().toString();
- String contentType = envelope.getDatacontenttype() == null ? "" : envelope.getDatacontenttype();
- System.out.println("Testing topic Subscriber got message: " + message + "; Content-type: " + contentType);
- messagesReceivedTestingTopic.add(envelope);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- @Topic(name = "testingbulktopic", pubsubName = "messagebus")
- @PostMapping("/route1_redis")
- public Mono handleBulkTopicMessage(@RequestBody(required = false) CloudEvent envelope) {
- return Mono.fromRunnable(() -> {
- try {
- String message = envelope.getData() == null ? "" : envelope.getData().toString();
- String contentType = envelope.getDatacontenttype() == null ? "" : envelope.getDatacontenttype();
- System.out.println("Testing bulk publish topic Subscriber got message: " + message + "; Content-type: " + contentType);
- messagesReceivedBulkPublishTopic.add(envelope);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- @Topic(name = "testingtopic", pubsubName = "messagebus",
- rule = @Rule(match = "event.type == 'myevent.v2'", priority = 2))
- @PostMapping(path = "/route1_v2")
- public Mono handleMessageV2(@RequestBody(required = false) CloudEvent envelope) {
- return Mono.fromRunnable(() -> {
- try {
- String message = envelope.getData() == null ? "" : envelope.getData().toString();
- String contentType = envelope.getDatacontenttype() == null ? "" : envelope.getDatacontenttype();
- System.out.println("Testing topic Subscriber got message: " + message + "; Content-type: " + contentType);
- messagesReceivedTestingTopicV2.add(envelope);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- @Topic(name = "testingtopic", pubsubName = "messagebus",
- rule = @Rule(match = "event.type == 'myevent.v3'", priority = 1))
- @PostMapping(path = "/route1_v3")
- public Mono handleMessageV3(@RequestBody(required = false) CloudEvent envelope) {
- return Mono.fromRunnable(() -> {
- try {
- String message = envelope.getData() == null ? "" : envelope.getData().toString();
- String contentType = envelope.getDatacontenttype() == null ? "" : envelope.getDatacontenttype();
- System.out.println("Testing topic Subscriber got message: " + message + "; Content-type: " + contentType);
- messagesReceivedTestingTopicV3.add(envelope);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- @Topic(name = "typedtestingtopic", pubsubName = "messagebus")
- @PostMapping(path = "/route1b")
- public Mono handleMessageTyped(@RequestBody(required = false) CloudEvent envelope) {
- return Mono.fromRunnable(() -> {
- try {
- String id = envelope.getData() == null ? "" : envelope.getData().getId();
- String contentType = envelope.getDatacontenttype() == null ? "" : envelope.getDatacontenttype();
- System.out.println("Testing typed topic Subscriber got message with ID: " + id + "; Content-type: " + contentType);
- messagesByTopic.compute("typedtestingtopic", merge(envelope));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- @Topic(name = "binarytopic", pubsubName = "messagebus")
- @PostMapping(path = "/route2")
- public Mono handleBinaryMessage(@RequestBody(required = false) CloudEvent envelope) {
- return Mono.fromRunnable(() -> {
- try {
- String message = envelope.getData() == null ? "" : envelope.getData().toString();
- String contentType = envelope.getDatacontenttype() == null ? "" : envelope.getDatacontenttype();
- System.out.println("Binary topic Subscriber got message: " + message + "; Content-type: " + contentType);
- messagesByTopic.compute("binarytopic", merge(envelope));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- @Topic(name = "#{'another'.concat('topic')}", pubsubName = "${pubsubName:messagebus}")
- @PostMapping(path = "/route3")
- public Mono handleMessageAnotherTopic(@RequestBody(required = false) CloudEvent envelope) {
- return Mono.fromRunnable(() -> {
- try {
- String message = envelope.getData() == null ? "" : envelope.getData().toString();
- System.out.println("Another topic Subscriber got message: " + message);
- messagesByTopic.compute("anothertopic", merge(envelope));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- @Topic(name = "ttltopic", pubsubName = "messagebus")
- @PostMapping(path = "/route4")
- public Mono handleMessageTTLTopic(@RequestBody(required = false) CloudEvent envelope) {
- return Mono.fromRunnable(() -> {
- try {
- String message = envelope.getData() == null ? "" : envelope.getData().toString();
- System.out.println("TTL topic Subscriber got message: " + message);
- messagesByTopic.compute("ttltopic", merge(envelope));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- @Topic(name = "testinglongvalues", pubsubName = "messagebus")
- @PostMapping(path = "/testinglongvalues")
- public Mono handleMessageLongValues(@RequestBody(required = false) CloudEvent cloudEvent) {
- return Mono.fromRunnable(() -> {
- try {
- Long message = cloudEvent.getData().getValue();
- System.out.println("Subscriber got: " + message);
- messagesByTopic.compute("testinglongvalues", merge(cloudEvent));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- /**
- * Receive messages using the bulk subscribe API.
- * The maxBulkSubCount and maxBulkSubAwaitDurationMs are adjusted to ensure
- * that all the test messages arrive in a single batch.
- *
- * @param bulkMessage incoming bulk of messages from the message bus.
- * @return status for each message received.
- */
- @BulkSubscribe(maxMessagesCount = 100, maxAwaitDurationMs = 5000)
- @Topic(name = "topicBulkSub", pubsubName = "messagebus")
- @PostMapping(path = "/routeBulkSub")
- public Mono handleMessageBulk(
- @RequestBody(required = false) BulkSubscribeMessage> bulkMessage) {
- return Mono.fromCallable(() -> {
- if (bulkMessage.getEntries().size() == 0) {
- BulkSubscribeAppResponse response = new BulkSubscribeAppResponse(new ArrayList<>());
- responsesReceivedTestingTopicBulkSub.add(response);
- return response;
- }
-
- List entries = new ArrayList<>();
- for (BulkSubscribeMessageEntry> entry: bulkMessage.getEntries()) {
- try {
- System.out.printf("Bulk Subscriber got entry ID: %s\n", entry.getEntryId());
- entries.add(new BulkSubscribeAppResponseEntry(entry.getEntryId(), BulkSubscribeAppResponseStatus.SUCCESS));
- } catch (Exception e) {
- entries.add(new BulkSubscribeAppResponseEntry(entry.getEntryId(), BulkSubscribeAppResponseStatus.RETRY));
- }
- }
- BulkSubscribeAppResponse response = new BulkSubscribeAppResponse(entries);
- responsesReceivedTestingTopicBulkSub.add(response);
- return response;
- });
- }
-
- private BiFunction>, List>> merge(final CloudEvent> item) {
- return (key, value) -> {
- final List> list = value == null ? new ArrayList<>() : value;
- list.add(item);
- return list;
- };
- }
-
- @GetMapping(path = "/health")
- public void health() {
- }
-}
diff --git a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/SubscriberService.java b/sdk-tests/src/test/java/io/dapr/it/pubsub/http/SubscriberService.java
deleted file mode 100644
index 8667b2956e..0000000000
--- a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/SubscriberService.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2021 The Dapr Authors
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package io.dapr.it.pubsub.http;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-
-/**
- * Service for subscriber.
- */
-@SpringBootApplication
-public class SubscriberService {
-
- public static final String SUCCESS_MESSAGE = "Completed initialization in";
-
- public static void main(String[] args) throws Exception {
- int port = Integer.parseInt(args[0]);
-
- System.out.printf("Service starting on port %d ...\n", port);
-
- // Start Dapr's callback endpoint.
- start(port);
- }
-
- /**
- * Starts Dapr's callback in a given port.
- *
- * @param port Port to listen to.
- */
- private static void start(int port) {
- SpringApplication app = new SpringApplication(SubscriberService.class);
- app.run(String.format("--server.port=%d", port));
- }
-
-}
\ No newline at end of file
diff --git a/sdk-tests/src/test/java/io/dapr/it/pubsub/stream/PubSubStreamIT.java b/sdk-tests/src/test/java/io/dapr/it/pubsub/stream/PubSubStreamIT.java
deleted file mode 100644
index 9b0b78ef2f..0000000000
--- a/sdk-tests/src/test/java/io/dapr/it/pubsub/stream/PubSubStreamIT.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright 2021 The Dapr Authors
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package io.dapr.it.pubsub.stream;
-
-import io.dapr.client.DaprClient;
-import io.dapr.client.DaprPreviewClient;
-import io.dapr.client.SubscriptionListener;
-import io.dapr.client.domain.CloudEvent;
-import io.dapr.it.BaseIT;
-import io.dapr.it.DaprRun;
-import io.dapr.utils.TypeRef;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Test;
-import reactor.core.publisher.Mono;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.UUID;
-
-import static io.dapr.it.Retry.callWithRetry;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-
-public class PubSubStreamIT extends BaseIT {
-
- // Must be a large enough number, so we validate that we get more than the initial batch
- // sent by the runtime. When this was first added, the batch size in runtime was set to 10.
- private static final int NUM_MESSAGES = 100;
- private static final String TOPIC_NAME = "stream-topic";
- private static final String TOPIC_NAME_FLUX = "stream-topic-flux";
- private static final String TOPIC_NAME_CLOUDEVENT = "stream-topic-cloudevent";
- private static final String TOPIC_NAME_RAWPAYLOAD = "stream-topic-rawpayload";
- private static final String TOPIC_NAME_DLQ = "stream-topic-dlq";
- private static final String TOPIC_NAME_DLQ_DEADLETTER = "stream-topic-dlq-deadletter";
- private static final String PUBSUB_NAME = "messagebus";
-
- private final List runs = new ArrayList<>();
-
- private DaprRun closeLater(DaprRun run) {
- this.runs.add(run);
- return run;
- }
-
- @AfterEach
- public void tearDown() throws Exception {
- for (DaprRun run : runs) {
- run.stop();
- }
- }
-
- @Test
- public void testPubSub() throws Exception {
- final DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName(),
- 60000));
-
- var runId = UUID.randomUUID().toString();
- try (DaprClient client = daprRun.newDaprClient();
- DaprPreviewClient previewClient = daprRun.newDaprPreviewClient()) {
- for (int i = 0; i < NUM_MESSAGES; i++) {
- String message = String.format("This is message #%d on topic %s for run %s", i, TOPIC_NAME, runId);
- //Publishing messages
- client.publishEvent(PUBSUB_NAME, TOPIC_NAME, message).block();
- System.out.println(
- String.format("Published message: '%s' to topic '%s' pubsub_name '%s'", message, TOPIC_NAME, PUBSUB_NAME));
- }
-
- System.out.println("Starting subscription for " + TOPIC_NAME);
-
- Set messages = Collections.synchronizedSet(new HashSet<>());
- Set errors = Collections.synchronizedSet(new HashSet<>());
-
- var random = new Random(37); // predictable random.
- var listener = new SubscriptionListener() {
- @Override
- public Mono onEvent(CloudEvent event) {
- return Mono.fromCallable(() -> {
- // Useful to avoid false negatives running locally multiple times.
- if (event.getData().contains(runId)) {
- // 5% failure rate.
- var decision = random.nextInt(100);
- if (decision < 5) {
- if (decision % 2 == 0) {
- throw new RuntimeException("artificial exception on message " + event.getId());
- }
- return Status.RETRY;
- }
-
- messages.add(event.getId());
- return Status.SUCCESS;
- }
-
- return Status.DROP;
- });
- }
-
- @Override
- public void onError(RuntimeException exception) {
- errors.add(exception.getMessage());
- }
-
- };
- try(var subscription = previewClient.subscribeToEvents(PUBSUB_NAME, TOPIC_NAME, listener, TypeRef.STRING)) {
- callWithRetry(() -> {
- var messageCount = messages.size();
- System.out.println(
- String.format("Got %d messages out of %d for topic %s.", messageCount, NUM_MESSAGES, TOPIC_NAME));
- assertEquals(NUM_MESSAGES, messages.size());
- assertEquals(4, errors.size());
- }, 120000); // Time for runtime to retry messages.
-
- subscription.close();
- subscription.awaitTermination();
- }
- }
- }
-
- @Test
- public void testPubSubFlux() throws Exception {
- final DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName() + "-flux",
- 60000));
-
- var runId = UUID.randomUUID().toString();
- try (DaprClient client = daprRun.newDaprClient();
- DaprPreviewClient previewClient = daprRun.newDaprPreviewClient()) {
-
- // Publish messages
- for (int i = 0; i < NUM_MESSAGES; i++) {
- String message = String.format("Flux message #%d for run %s", i, runId);
- client.publishEvent(PUBSUB_NAME, TOPIC_NAME_FLUX, message).block();
- System.out.println(
- String.format("Published flux message: '%s' to topic '%s'", message, TOPIC_NAME_FLUX));
- }
-
- System.out.println("Starting Flux subscription for " + TOPIC_NAME_FLUX);
-
- Set messages = Collections.synchronizedSet(new HashSet<>());
-
- // subscribeToTopic returns Flux directly (raw data)
- var disposable = previewClient.subscribeToTopic(PUBSUB_NAME, TOPIC_NAME_FLUX, TypeRef.STRING)
- .doOnNext(rawMessage -> {
- // rawMessage is String directly
- if (rawMessage.contains(runId)) {
- messages.add(rawMessage);
- System.out.println("Received raw message: " + rawMessage);
- }
- })
- .subscribe();
-
- callWithRetry(() -> {
- var messageCount = messages.size();
- System.out.println(
- String.format("Got %d flux messages out of %d for topic %s.", messageCount, NUM_MESSAGES, TOPIC_NAME_FLUX));
- assertEquals(NUM_MESSAGES, messages.size());
- }, 60000);
-
- disposable.dispose();
- }
- }
-
- @Test
- public void testPubSubCloudEvent() throws Exception {
- final DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName() + "-cloudevent",
- 60000));
-
- var runId = UUID.randomUUID().toString();
- try (DaprClient client = daprRun.newDaprClient();
- DaprPreviewClient previewClient = daprRun.newDaprPreviewClient()) {
-
- // Publish messages
- for (int i = 0; i < NUM_MESSAGES; i++) {
- String message = String.format("CloudEvent message #%d for run %s", i, runId);
- client.publishEvent(PUBSUB_NAME, TOPIC_NAME_CLOUDEVENT, message).block();
- System.out.println(
- String.format("Published CloudEvent message: '%s' to topic '%s'", message, TOPIC_NAME_CLOUDEVENT));
- }
-
- System.out.println("Starting CloudEvent subscription for " + TOPIC_NAME_CLOUDEVENT);
-
- Set messageIds = Collections.synchronizedSet(new HashSet<>());
-
- // Use TypeRef> to receive full CloudEvent with metadata
- var disposable = previewClient.subscribeToTopic(PUBSUB_NAME, TOPIC_NAME_CLOUDEVENT, new TypeRef>(){})
- .doOnNext(cloudEvent -> {
- if (cloudEvent.getData() != null && cloudEvent.getData().contains(runId)) {
- messageIds.add(cloudEvent.getId());
- System.out.println("Received CloudEvent with ID: " + cloudEvent.getId()
- + ", topic: " + cloudEvent.getTopic()
- + ", data: " + cloudEvent.getData());
- }
- })
- .subscribe();
-
- callWithRetry(() -> {
- var messageCount = messageIds.size();
- System.out.println(
- String.format("Got %d CloudEvent messages out of %d for topic %s.", messageCount, NUM_MESSAGES, TOPIC_NAME_CLOUDEVENT));
- assertEquals(NUM_MESSAGES, messageIds.size());
- }, 60000);
-
- disposable.dispose();
- }
- }
-
- @Test
- public void testPubSubRawPayload() throws Exception {
- final DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName() + "-rawpayload",
- 60000));
-
- var runId = UUID.randomUUID().toString();
- try (DaprClient client = daprRun.newDaprClient();
- DaprPreviewClient previewClient = daprRun.newDaprPreviewClient()) {
-
- // Publish messages with rawPayload metadata
- for (int i = 0; i < NUM_MESSAGES; i++) {
- String message = String.format("RawPayload message #%d for run %s", i, runId);
- client.publishEvent(PUBSUB_NAME, TOPIC_NAME_RAWPAYLOAD, message, Map.of("rawPayload", "true")).block();
- System.out.println(
- String.format("Published raw payload message: '%s' to topic '%s'", message, TOPIC_NAME_RAWPAYLOAD));
- }
-
- System.out.println("Starting raw payload subscription for " + TOPIC_NAME_RAWPAYLOAD);
-
- Set messages = Collections.synchronizedSet(new HashSet<>());
- Map metadata = Map.of("rawPayload", "true");
-
- // Use subscribeToTopic with rawPayload metadata
- var disposable = previewClient.subscribeToTopic(PUBSUB_NAME, TOPIC_NAME_RAWPAYLOAD, TypeRef.STRING, metadata)
- .doOnNext(rawMessage -> {
- if (rawMessage.contains(runId)) {
- messages.add(rawMessage);
- System.out.println("Received raw payload message: " + rawMessage);
- }
- })
- .subscribe();
-
- callWithRetry(() -> {
- var messageCount = messages.size();
- System.out.println(
- String.format("Got %d raw payload messages out of %d for topic %s.", messageCount, NUM_MESSAGES, TOPIC_NAME_RAWPAYLOAD));
- assertEquals(NUM_MESSAGES, messages.size());
- }, 60000);
-
- disposable.dispose();
- }
- }
-
- @Test
- public void testPubSubDeadLetterTopic() throws Exception {
- final DaprRun daprRun = closeLater(startDaprApp(
- this.getClass().getSimpleName() + "-dlq",
- 60000));
-
- var runId = UUID.randomUUID().toString();
- try (DaprClient client = daprRun.newDaprClient();
- DaprPreviewClient previewClient = daprRun.newDaprPreviewClient()) {
-
- // Subscribe to the dead-letter topic first so we don't miss any messages.
- Set deadLetterMessageIds = Collections.synchronizedSet(new HashSet<>());
- var deadLetterListener = new SubscriptionListener() {
- @Override
- public Mono onEvent(CloudEvent event) {
- if (event.getData() != null && event.getData().contains(runId)) {
- deadLetterMessageIds.add(event.getId());
- System.out.println("Received dead-letter message ID: " + event.getId());
- }
- return Mono.just(Status.SUCCESS);
- }
-
- @Override
- public void onError(RuntimeException exception) {
- System.err.println("Dead-letter subscription error: " + exception.getMessage());
- }
- };
-
- // Subscribe to the main topic with a listener that always DROPs, which should
- // forward the messages to the dead-letter topic.
- var mainListener = new SubscriptionListener() {
- @Override
- public Mono onEvent(CloudEvent event) {
- if (event.getData() != null && event.getData().contains(runId)) {
- System.out.println("Dropping message ID: " + event.getId());
- return Mono.just(Status.DROP);
- }
- return Mono.just(Status.DROP);
- }
-
- @Override
- public void onError(RuntimeException exception) {
- System.err.println("Main subscription error: " + exception.getMessage());
- }
- };
-
- try (var deadLetterSubscription = previewClient.subscribeToEvents(
- PUBSUB_NAME, TOPIC_NAME_DLQ_DEADLETTER, deadLetterListener, TypeRef.STRING);
- var mainSubscription = previewClient.subscribeToEvents(
- PUBSUB_NAME, TOPIC_NAME_DLQ, TOPIC_NAME_DLQ_DEADLETTER, mainListener, TypeRef.STRING)) {
-
- // Publish messages to the main topic.
- for (int i = 0; i < NUM_MESSAGES; i++) {
- String message = String.format("DLQ message #%d for run %s", i, runId);
- client.publishEvent(PUBSUB_NAME, TOPIC_NAME_DLQ, message).block();
- }
-
- callWithRetry(() -> {
- var count = deadLetterMessageIds.size();
- System.out.println(
- String.format("Got %d dead-letter messages out of %d for topic %s.",
- count, NUM_MESSAGES, TOPIC_NAME_DLQ_DEADLETTER));
- assertEquals(NUM_MESSAGES, deadLetterMessageIds.size());
- }, 120000);
-
- mainSubscription.close();
- mainSubscription.awaitTermination();
- deadLetterSubscription.close();
- deadLetterSubscription.awaitTermination();
- }
- }
- }
-}
diff --git a/sdk-tests/src/test/java/io/dapr/it/resiliency/SdkResiliencyIT.java b/sdk-tests/src/test/java/io/dapr/it/resiliency/SdkResiliencyIT.java
index 05182f8d6c..d7f6eea96b 100644
--- a/sdk-tests/src/test/java/io/dapr/it/resiliency/SdkResiliencyIT.java
+++ b/sdk-tests/src/test/java/io/dapr/it/resiliency/SdkResiliencyIT.java
@@ -14,7 +14,8 @@
package io.dapr.it.resiliency;
import com.github.tomakehurst.wiremock.client.WireMock;
-import com.github.tomakehurst.wiremock.junit5.WireMockTest;
+import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
+import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import eu.rekawek.toxiproxy.Proxy;
import eu.rekawek.toxiproxy.ToxiproxyClient;
import eu.rekawek.toxiproxy.model.ToxicDirection;
@@ -35,6 +36,7 @@
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Tags;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.Network;
import org.testcontainers.toxiproxy.ToxiproxyContainer;
@@ -49,7 +51,6 @@
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.any;
-import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
@@ -57,32 +58,25 @@
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
-import static io.dapr.it.resiliency.SdkResiliencyIT.WIREMOCK_PORT;
import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
import static io.dapr.it.testcontainers.ContainerConstants.TOXI_PROXY_IMAGE_TAG;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@Testcontainers
-@WireMockTest(httpPort = WIREMOCK_PORT)
@Tags({@Tag("testcontainers"), @Tag("resiliency")})
public class SdkResiliencyIT {
- public static final int WIREMOCK_PORT = 8888;
private static final Network NETWORK = Network.newNetwork();
private static final String STATE_STORE_NAME = "kvstore";
private static final int INFINITE_RETRY = -1;
- @Container
- private static final DaprContainer DAPR_CONTAINER = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
- .withAppName("dapr-app")
- .withAppPort(WIREMOCK_PORT)
- .withDaprLogLevel(DaprLogLevel.DEBUG)
- .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("dapr-logs")))
- .withAppHealthCheckPath("/actuator/health")
- .withAppChannelAddress("host.testcontainers.internal")
- .withNetworkAliases("dapr")
- .withNetwork(NETWORK);
+ @RegisterExtension
+ static WireMockExtension wireMock = WireMockExtension.newInstance()
+ .options(WireMockConfiguration.wireMockConfig().dynamicPort())
+ .build();
+
+ private static DaprContainer daprContainer;
@Container
private static final ToxiproxyContainer TOXIPROXY = new ToxiproxyContainer(TOXI_PROXY_IMAGE_TAG)
@@ -91,41 +85,54 @@ public class SdkResiliencyIT {
private static Proxy proxy;
private void configStub() {
- stubFor(any(urlMatching("/actuator/health"))
+ wireMock.stubFor(any(urlMatching("/actuator/health"))
.willReturn(aResponse().withBody("[]").withStatus(200)));
- stubFor(any(urlMatching("/dapr/subscribe"))
+ wireMock.stubFor(any(urlMatching("/dapr/subscribe"))
.willReturn(aResponse().withBody("[]").withStatus(200)));
- stubFor(get(urlMatching("/dapr/config"))
+ wireMock.stubFor(get(urlMatching("/dapr/config"))
.willReturn(aResponse().withBody("[]").withStatus(200)));
- // create a stub for simulating dapr sidecar with timeout of 1000 ms
- stubFor(post(urlEqualTo("/dapr.proto.runtime.v1.Dapr/SaveState"))
+ wireMock.stubFor(post(urlEqualTo("/dapr.proto.runtime.v1.Dapr/SaveState"))
.willReturn(aResponse().withStatus(204).withFixedDelay(1000)));
- stubFor(any(urlMatching("/([a-z1-9]*)"))
+ wireMock.stubFor(any(urlMatching("/([a-z1-9]*)"))
.willReturn(aResponse().withBody("[]").withStatus(200)));
- configureFor("localhost", WIREMOCK_PORT);
+ WireMock.configureFor("localhost", wireMock.getPort());
}
@BeforeAll
static void configure() throws IOException {
+ int wmPort = wireMock.getPort();
+ org.testcontainers.Testcontainers.exposeHostPorts(wmPort);
+
+ daprContainer = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
+ .withAppName("dapr-app")
+ .withAppPort(wmPort)
+ .withDaprLogLevel(DaprLogLevel.DEBUG)
+ .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("dapr-logs")))
+ .withAppHealthCheckPath("/actuator/health")
+ .withAppChannelAddress("host.testcontainers.internal")
+ .withNetworkAliases("dapr")
+ .withNetwork(NETWORK);
+ daprContainer.start();
+
ToxiproxyClient toxiproxyClient = new ToxiproxyClient(TOXIPROXY.getHost(), TOXIPROXY.getControlPort());
- proxy =
- toxiproxyClient.createProxy("dapr", "0.0.0.0:8666", "dapr:3500");
+ proxy = toxiproxyClient.createProxy("dapr", "0.0.0.0:8666", "dapr:3500");
}
@AfterAll
static void afterAll() {
- WireMock.shutdownServer();
+ if (daprContainer != null) {
+ daprContainer.stop();
+ }
}
@BeforeEach
public void beforeEach() {
configStub();
- org.testcontainers.Testcontainers.exposeHostPorts(WIREMOCK_PORT);
}
@Test
@@ -189,10 +196,11 @@ public void shouldFailDueToLatencyExceedingConfigurationWithInfiniteRetry() thro
@Test
@DisplayName("should fail due to latency exceeding configuration with once retry")
public void shouldFailDueToLatencyExceedingConfigurationWithOnceRetry() throws Exception {
+ int wmPort = wireMock.getPort();
DaprClient client =
- new DaprClientBuilder().withPropertyOverride(Properties.HTTP_ENDPOINT, "http://localhost:" + WIREMOCK_PORT)
- .withPropertyOverride(Properties.GRPC_ENDPOINT, "http://localhost:" + WIREMOCK_PORT)
+ new DaprClientBuilder().withPropertyOverride(Properties.HTTP_ENDPOINT, "http://localhost:" + wmPort)
+ .withPropertyOverride(Properties.GRPC_ENDPOINT, "http://localhost:" + wmPort)
.withResiliencyOptions(new ResiliencyOptions().setTimeout(Duration.ofMillis(900))
.setMaxRetries(1))
.build();
@@ -202,7 +210,7 @@ public void shouldFailDueToLatencyExceedingConfigurationWithOnceRetry() throws E
} catch (Exception ignored) {
}
- verify(2, postRequestedFor(urlEqualTo("/dapr.proto.runtime.v1.Dapr/SaveState")));
+ wireMock.verify(2, postRequestedFor(urlEqualTo("/dapr.proto.runtime.v1.Dapr/SaveState")));
client.close();
}
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 23f05957ba..202d7b9499 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Dapr Authors
+ * Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -15,19 +15,15 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dapr.client.DaprClient;
-import io.dapr.client.DaprClientBuilder;
-import io.dapr.it.BaseIT;
-import io.dapr.it.DaprRun;
-import org.apache.commons.io.IOUtils;
+import io.dapr.it.containers.BaseContainerIT;
+import io.dapr.testcontainers.Component;
+import io.dapr.testcontainers.DaprContainer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.testcontainers.images.builder.Transferable;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -35,57 +31,44 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
-/**
- * Test Secrets Store APIs using local file.
- *
- * 1. create secret file locally:
- */
-public class SecretsClientIT extends BaseIT {
+public class SecretsClientIT extends BaseContainerIT {
- /**
- * JSON Serializer to print output.
- */
private static final ObjectMapper JSON_SERIALIZER = new ObjectMapper();
-
private static final String SECRETS_STORE_NAME = "localSecretStore";
-
- private static final String LOCAL_SECRET_FILE_PATH = "./components/secret.json";
-
+ private static final String CONTAINER_SECRET_PATH = "/dapr-secret.json";
private static final String KEY1 = UUID.randomUUID().toString();
-
private static final String KYE2 = UUID.randomUUID().toString();
- private static DaprRun daprRun;
-
-
+ private static DaprContainer dapr;
private DaprClient daprClient;
- private static File localSecretFile;
-
@BeforeAll
public static void init() throws Exception {
-
- localSecretFile = new File(LOCAL_SECRET_FILE_PATH);
- boolean existed = localSecretFile.exists();
- assertTrue(existed);
- initSecretFile();
-
- daprRun = startDaprApp(SecretsClientIT.class.getSimpleName(), 5000);
+ byte[] secretJson = JSON_SERIALIZER.writeValueAsBytes(buildSecretPayload());
+
+ dapr = daprBuilder("secrets-it")
+ .withComponent(new Component(SECRETS_STORE_NAME, "secretstores.local.file", "v1", Map.of(
+ "secretsFile", CONTAINER_SECRET_PATH,
+ "nestedSeparator", ":",
+ "multiValued", "true"
+ )))
+ .withCopyToContainer(Transferable.of(secretJson), CONTAINER_SECRET_PATH);
+ dapr.start();
+ deferStop(dapr);
}
@BeforeEach
public void setup() {
- this.daprClient = daprRun.newDaprClientBuilder().build();
+ this.daprClient = newDaprClient(dapr);
}
@AfterEach
public void tearDown() throws Exception {
daprClient.close();
- clearSecretFile();
}
@Test
- public void getSecret() throws Exception {
+ public void getSecret() {
Map data = daprClient.getSecret(SECRETS_STORE_NAME, KEY1).block();
assertEquals(2, data.size());
assertEquals("The Metrics IV", data.get("title"));
@@ -93,9 +76,8 @@ public void getSecret() throws Exception {
}
@Test
- public void getBulkSecret() throws Exception {
+ public void getBulkSecret() {
Map> data = daprClient.getBulkSecret(SECRETS_STORE_NAME).block();
- // There can be other keys from other runs or test cases, so we are good with at least two.
assertTrue(data.size() >= 2);
assertEquals(2, data.get(KEY1).size());
assertEquals("The Metrics IV", data.get(KEY1).get("title"));
@@ -114,26 +96,10 @@ public void getSecretStoreNotFound() {
assertThrows(RuntimeException.class, () -> daprClient.getSecret("unknownStore", "unknownKey").block());
}
- private static void initSecretFile() throws Exception {
- Map key2 = new HashMap(){{
- put("name", "Jon Doe");
- }};
- Map key1 = new HashMap(){{
- put("title", "The Metrics IV");
- put("year", "2020");
- }};
- Map> secret = new HashMap<>(){{
- put(KEY1, key1);
- put(KYE2, key2);
- }};
- try (FileOutputStream fos = new FileOutputStream(localSecretFile)) {
- JSON_SERIALIZER.writeValue(fos, secret);
- }
- }
-
- private static void clearSecretFile() throws IOException {
- try (FileOutputStream fos = new FileOutputStream(localSecretFile)) {
- IOUtils.write("{}", fos);
- }
+ private static Map> buildSecretPayload() {
+ return Map.of(
+ KEY1, Map.of("title", "The Metrics IV", "year", "2020"),
+ KYE2, Map.of("name", "Jon Doe")
+ );
}
}
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 255c310517..4cc9db29c9 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Dapr Authors
+ * Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -27,7 +27,7 @@
import io.dapr.client.domain.query.Sorting;
import io.dapr.client.domain.query.filters.EqFilter;
import io.dapr.exceptions.DaprException;
-import io.dapr.it.BaseIT;
+import io.dapr.it.containers.BaseContainerIT;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@@ -50,8 +50,9 @@
/**
* Common test cases for Dapr client (GRPC and HTTP).
*/
-public abstract class AbstractStateClientIT extends BaseIT {
+public abstract class AbstractStateClientIT extends BaseContainerIT {
private static final Logger logger = Logger.getLogger(AbstractStateClientIT.class.getName());
+ private static final String QUERY_STATE_STORE = MONGO_QUERY_STATE_STORE_NAME;
@Test
public void saveAndGetState() {
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 5254e0b06a..900ea7af1d 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Dapr Authors
+ * Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -13,10 +13,13 @@
package io.dapr.it.state;
+import com.google.protobuf.ByteString;
import io.dapr.client.DaprClient;
-import io.dapr.client.DaprClientBuilder;
import io.dapr.client.domain.State;
-import io.dapr.it.DaprRun;
+import io.dapr.testcontainers.DaprContainer;
+import io.dapr.v1.CommonProtos;
+import io.dapr.v1.DaprGrpc;
+import io.dapr.v1.DaprStateProtos;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -24,27 +27,31 @@
import java.util.Collections;
import static io.dapr.it.TestUtils.assertThrowsDaprException;
+import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Test State GRPC DAPR capabilities using a DAPR instance with an empty service running
*/
public class GRPCStateClientIT extends AbstractStateClientIT {
- private static DaprRun daprRun;
-
+ private static DaprContainer dapr;
private static DaprClient daprClient;
@BeforeAll
- public static void init() throws Exception {
- daprRun = startDaprApp(GRPCStateClientIT.class.getSimpleName(), 5000);
- daprClient = daprRun.newDaprClientBuilder().build();
+ public static void init() {
+ dapr = daprBuilder("grpc-state-it")
+ .withComponent(redisStateStore(STATE_STORE_NAME))
+ .withComponent(mongoStateStore(MONGO_QUERY_STATE_STORE_NAME));
+ dapr.start();
+ deferStop(dapr);
+ daprClient = newDaprClient(dapr);
}
@AfterAll
public static void tearDown() throws Exception {
daprClient.close();
}
-
+
@Override
protected DaprClient buildDaprClient() {
return daprClient;
@@ -81,4 +88,45 @@ public void getStatesStoreNotFound() {
byte[].class).block());
}
+ /**
+ * Exercises {@link DaprClient#newGrpcStub(String, java.util.function.Function)} —
+ * the public API for obtaining a raw {@code DaprGrpc.DaprBlockingStub} routed
+ * through the SDK's managed channel. Ports the only test from the legacy
+ * {@code HelloWorldClientIT}, which previously exercised this API end-to-end
+ * via {@code dapr run}. Uses the raw stub for save/get/delete to avoid the
+ * SDK's default JSON serialization wrapping the value in quotes.
+ */
+ @Test
+ public void rawGrpcStubGetAndDeleteState() {
+ final String key = "newGrpcStubKey";
+ final String value = "Hello World";
+
+ DaprGrpc.DaprBlockingStub stub = buildDaprClient().newGrpcStub("n/a", DaprGrpc::newBlockingStub);
+
+ stub.saveState(DaprStateProtos.SaveStateRequest.newBuilder()
+ .setStoreName(STATE_STORE_NAME)
+ .addStates(CommonProtos.StateItem.newBuilder()
+ .setKey(key)
+ .setValue(ByteString.copyFromUtf8(value))
+ .build())
+ .build());
+
+ DaprStateProtos.GetStateResponse before = stub.getState(DaprStateProtos.GetStateRequest.newBuilder()
+ .setStoreName(STATE_STORE_NAME)
+ .setKey(key)
+ .build());
+ assertEquals(value, before.getData().toStringUtf8());
+
+ stub.deleteState(DaprStateProtos.DeleteStateRequest.newBuilder()
+ .setStoreName(STATE_STORE_NAME)
+ .setKey(key)
+ .build());
+
+ DaprStateProtos.GetStateResponse after = stub.getState(DaprStateProtos.GetStateRequest.newBuilder()
+ .setStoreName(STATE_STORE_NAME)
+ .setKey(key)
+ .build());
+ assertEquals("", after.getData().toStringUtf8());
+ }
+
}
diff --git a/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldClientIT.java b/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldClientIT.java
deleted file mode 100644
index bdd25ae780..0000000000
--- a/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldClientIT.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2021 The Dapr Authors
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package io.dapr.it.state;
-
-import io.dapr.it.BaseIT;
-import io.dapr.it.DaprRun;
-import io.dapr.v1.DaprGrpc;
-import io.dapr.v1.DaprStateProtos;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-public class HelloWorldClientIT extends BaseIT {
-
- @Test
- public void testHelloWorldState() throws Exception {
- DaprRun daprRun = startDaprApp(
- HelloWorldClientIT.class.getSimpleName(),
- HelloWorldGrpcStateService.SUCCESS_MESSAGE,
- HelloWorldGrpcStateService.class,
- false,
- 2000
- );
- try (var client = daprRun.newDaprClientBuilder().build()) {
- var stub = client.newGrpcStub("n/a", DaprGrpc::newBlockingStub);
-
- String key = "mykey";
- {
- DaprStateProtos.GetStateRequest req = DaprStateProtos.GetStateRequest
- .newBuilder()
- .setStoreName(STATE_STORE_NAME)
- .setKey(key)
- .build();
- DaprStateProtos.GetStateResponse response = stub.getState(req);
- String value = response.getData().toStringUtf8();
- System.out.println("Got: " + value);
- Assertions.assertEquals("Hello World", value);
- }
-
- // Then, delete it.
- {
- DaprStateProtos.DeleteStateRequest req = DaprStateProtos.DeleteStateRequest
- .newBuilder()
- .setStoreName(STATE_STORE_NAME)
- .setKey(key)
- .build();
- stub.deleteState(req);
- System.out.println("Deleted!");
- }
-
- {
- DaprStateProtos.GetStateRequest req = DaprStateProtos.GetStateRequest
- .newBuilder()
- .setStoreName(STATE_STORE_NAME)
- .setKey(key)
- .build();
- DaprStateProtos.GetStateResponse response = stub.getState(req);
- String value = response.getData().toStringUtf8();
- System.out.println("Got: " + value);
- Assertions.assertEquals("", value);
- }
- }
- }
-}
diff --git a/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldGrpcStateService.java b/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldGrpcStateService.java
deleted file mode 100644
index abab918be7..0000000000
--- a/sdk-tests/src/test/java/io/dapr/it/state/HelloWorldGrpcStateService.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2021 The Dapr Authors
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package io.dapr.it.state;
-
-import com.google.protobuf.ByteString;
-import io.dapr.client.DaprClientBuilder;
-import io.dapr.config.Properties;
-import io.dapr.internal.grpc.DaprClientGrpcInterceptors;
-import io.dapr.v1.CommonProtos.StateItem;
-import io.dapr.v1.DaprGrpc;
-import io.dapr.v1.DaprGrpc.DaprBlockingStub;
-import io.dapr.v1.DaprStateProtos.SaveStateRequest;
-import io.grpc.ManagedChannel;
-import io.grpc.ManagedChannelBuilder;
-
-
-/**
- * Simple example.
- * To run manually, from repo root:
- * 1. mvn clean install
- * 2. dapr run --resources-path ./components --dapr-grpc-port 50001 -- mvn exec:java -Dexec.mainClass=io.dapr.it.state.HelloWorldGrpcStateService -Dexec.classpathScope="test" -pl=sdk
- */
-public class HelloWorldGrpcStateService {
-
- public static final String SUCCESS_MESSAGE = "Hello from " + HelloWorldGrpcStateService.class.getSimpleName();
-
- public static void main(String[] args) {
- String grpcPort = System.getenv("DAPR_GRPC_PORT");
-
- // If port string is not valid, it will throw an exception.
- int grpcPortInt = Integer.parseInt(grpcPort);
- ManagedChannel channel = ManagedChannelBuilder.forAddress(
- Properties.SIDECAR_IP.get(), grpcPortInt).usePlaintext().build();
- DaprClientGrpcInterceptors interceptors = new DaprClientGrpcInterceptors(
- Properties.API_TOKEN.get(), null);
- DaprBlockingStub client = interceptors.intercept(DaprGrpc.newBlockingStub(channel));
-
- String key = "mykey";
- // First, write key-value pair.
-
- String value = "Hello World";
- StateItem req = StateItem
- .newBuilder()
- .setKey(key)
- .setValue(ByteString.copyFromUtf8(value))
- .build();
- SaveStateRequest state = SaveStateRequest.newBuilder()
- .setStoreName("statestore")
- .addStates(req)
- .build();
- client.saveState(state);
- System.out.println("Saved!");
- channel.shutdown();
-
- System.out.println(SUCCESS_MESSAGE);
- }
-}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/jobs/DaprJobsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/jobs/DaprJobsIT.java
index 2694e32e5d..79a1d3485a 100644
--- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/jobs/DaprJobsIT.java
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/jobs/DaprJobsIT.java
@@ -25,6 +25,7 @@
import io.dapr.it.testcontainers.DaprClientConfiguration;
import io.dapr.testcontainers.DaprContainer;
import io.dapr.testcontainers.DaprLogLevel;
+import org.awaitility.Awaitility;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@@ -43,9 +44,11 @@
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Random;
+import java.util.UUID;
import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
@SpringBootTest(
webEnvironment = WebEnvironment.RANDOM_PORT,
@@ -93,63 +96,77 @@ public void setUp(){
@Test
public void testJobScheduleCreationWithDueTime() {
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.withZone(ZoneOffset.UTC);
- Instant currentTime = Instant.now();
- daprClient.scheduleJob(new ScheduleJobRequest("Job", currentTime).setOverwrite(true)).block();
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, currentTime)).block();
- GetJobResponse getJobResponse =
- daprClient.getJob(new GetJobRequest("Job")).block();
+ GetJobResponse getJobResponse = Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
+ assertNotNull(getJobResponse);
assertEquals(iso8601Formatter.format(currentTime), getJobResponse.getDueTime().toString());
- assertEquals("Job", getJobResponse.getName());
+ assertEquals(jobName, getJobResponse.getName());
}
@Test
public void testJobScheduleCreationWithSchedule() {
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.withZone(ZoneOffset.UTC);
- Instant currentTime = Instant.now();
- daprClient.scheduleJob(new ScheduleJobRequest("Job", JobSchedule.hourly())
- .setDueTime(currentTime).setOverwrite(true)).block();
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, JobSchedule.hourly())
+ .setDueTime(currentTime)).block();
- GetJobResponse getJobResponse =
- daprClient.getJob(new GetJobRequest("Job")).block();
+ GetJobResponse getJobResponse = Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
+ assertNotNull(getJobResponse);
assertEquals(iso8601Formatter.format(currentTime), getJobResponse.getDueTime().toString());
assertEquals(JobSchedule.hourly().getExpression(), getJobResponse.getSchedule().getExpression());
- assertEquals("Job", getJobResponse.getName());
+ assertEquals(jobName, getJobResponse.getName());
}
@Test
public void testJobScheduleCreationWithAllParameters() {
- Instant currentTime = Instant.now();
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.withZone(ZoneOffset.UTC);
String cronExpression = "2 * 3 * * FRI";
- daprClient.scheduleJob(new ScheduleJobRequest("Job", currentTime)
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, currentTime)
.setTtl(currentTime.plus(2, ChronoUnit.HOURS))
.setData("Job data".getBytes())
.setRepeat(3)
- .setOverwrite(true)
.setSchedule(JobSchedule.fromString(cronExpression))).block();
- GetJobResponse getJobResponse =
- daprClient.getJob(new GetJobRequest("Job")).block();
+ GetJobResponse getJobResponse = Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
+ assertNotNull(getJobResponse);
assertEquals(iso8601Formatter.format(currentTime), getJobResponse.getDueTime().toString());
assertEquals("2 * 3 * * FRI", getJobResponse.getSchedule().getExpression());
- assertEquals("Job", getJobResponse.getName());
+ assertEquals(jobName, getJobResponse.getName());
assertEquals(Integer.valueOf(3), getJobResponse.getRepeats());
assertEquals("Job data", new String(getJobResponse.getData()));
assertEquals(iso8601Formatter.format(currentTime.plus(2, ChronoUnit.HOURS)),
@@ -158,36 +175,38 @@ public void testJobScheduleCreationWithAllParameters() {
@Test
public void testJobScheduleCreationWithDropFailurePolicy() {
- Instant currentTime = Instant.now();
- DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
- .withZone(ZoneOffset.UTC);
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
String cronExpression = "2 * 3 * * FRI";
- daprClient.scheduleJob(new ScheduleJobRequest("Job", currentTime)
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, currentTime)
.setTtl(currentTime.plus(2, ChronoUnit.HOURS))
.setData("Job data".getBytes())
.setRepeat(3)
- .setFailurePolicy(new DropFailurePolicy())
+ .setFailurePolicy(new DropFailurePolicy())
.setSchedule(JobSchedule.fromString(cronExpression))).block();
- GetJobResponse getJobResponse =
- daprClient.getJob(new GetJobRequest("Job")).block();
+ GetJobResponse getJobResponse = Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
+ assertNotNull(getJobResponse);
assertEquals(FailurePolicyType.DROP, getJobResponse.getFailurePolicy().getFailurePolicyType());
}
@Test
public void testJobScheduleCreationWithConstantFailurePolicy() {
- Instant currentTime = Instant.now();
- DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
- .withZone(ZoneOffset.UTC);
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
String cronExpression = "2 * 3 * * FRI";
- daprClient.scheduleJob(new ScheduleJobRequest("Job", currentTime)
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, currentTime)
.setTtl(currentTime.plus(2, ChronoUnit.HOURS))
.setData("Job data".getBytes())
.setRepeat(3)
@@ -195,11 +214,15 @@ public void testJobScheduleCreationWithConstantFailurePolicy() {
.setDurationBetweenRetries(Duration.of(10, ChronoUnit.SECONDS)))
.setSchedule(JobSchedule.fromString(cronExpression))).block();
- GetJobResponse getJobResponse =
- daprClient.getJob(new GetJobRequest("Job")).block();
+ GetJobResponse getJobResponse = Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
+ assertNotNull(getJobResponse);
ConstantFailurePolicy jobFailurePolicyConstant = (ConstantFailurePolicy) getJobResponse.getFailurePolicy();
assertEquals(FailurePolicyType.CONSTANT, getJobResponse.getFailurePolicy().getFailurePolicyType());
assertEquals(3, (int)jobFailurePolicyConstant.getMaxRetries());
@@ -209,17 +232,23 @@ public void testJobScheduleCreationWithConstantFailurePolicy() {
@Test
public void testDeleteJobRequest() {
- Instant currentTime = Instant.now();
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
String cronExpression = "2 * 3 * * FRI";
- daprClient.scheduleJob(new ScheduleJobRequest("Job", currentTime)
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, currentTime)
.setTtl(currentTime.plus(2, ChronoUnit.HOURS))
.setData("Job data".getBytes())
.setRepeat(3)
- .setOverwrite(true)
.setSchedule(JobSchedule.fromString(cronExpression))).block();
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
+
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
}
}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/ConvertToLong.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/ConvertToLong.java
new file mode 100644
index 0000000000..56e10b7880
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/ConvertToLong.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package io.dapr.it.testcontainers.pubsub.http;
+
+import java.util.Objects;
+
+public class ConvertToLong {
+ private Long value;
+
+ public ConvertToLong setVal(Long value) {
+ this.value = value;
+ return this;
+ }
+
+ public Long getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ConvertToLong that = (ConvertToLong) o;
+ return Objects.equals(value, that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/DaprPubSubIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/DaprPubSubIT.java
index eaa0f0c99f..832dff08fe 100644
--- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/DaprPubSubIT.java
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/DaprPubSubIT.java
@@ -22,11 +22,13 @@
import io.dapr.client.domain.BulkPublishEntry;
import io.dapr.client.domain.BulkPublishRequest;
import io.dapr.client.domain.BulkPublishResponse;
+import io.dapr.client.domain.BulkSubscribeAppResponse;
+import io.dapr.client.domain.BulkSubscribeAppResponseEntry;
+import io.dapr.client.domain.BulkSubscribeAppResponseStatus;
import io.dapr.client.domain.CloudEvent;
import io.dapr.client.domain.HttpExtension;
import io.dapr.client.domain.Metadata;
import io.dapr.client.domain.PublishEventRequest;
-import io.dapr.it.pubsub.http.PubSubIT;
import io.dapr.it.testcontainers.DaprClientFactory;
import io.dapr.serializer.CustomizableObjectSerializer;
import io.dapr.serializer.DaprObjectSerializer;
@@ -96,16 +98,17 @@ public class DaprPubSubIT {
private static final String BINARY_TOPIC_NAME = "binarytopic";
private static final String TTL_TOPIC_NAME = "ttltopic";
private static final String LONG_TOPIC_NAME = "testinglongvalues";
+ private static final String BULK_SUB_TOPIC_NAME = "topicBulkSub";
private static final int NUM_MESSAGES = 10;
// typeRefs
private static final TypeRef> CLOUD_EVENT_LIST_TYPE_REF = new TypeRef<>() {
};
- private static final TypeRef>> CLOUD_EVENT_LONG_LIST_TYPE_REF =
+ private static final TypeRef>> CLOUD_EVENT_LONG_LIST_TYPE_REF =
new TypeRef<>() {
};
- private static final TypeRef>> CLOUD_EVENT_MYOBJECT_LIST_TYPE_REF =
+ private static final TypeRef>> CLOUD_EVENT_MYOBJECT_LIST_TYPE_REF =
new TypeRef<>() {
};
@@ -199,7 +202,7 @@ public void testPubSub() throws Exception {
sendBulkMessagesAsText(client, ANOTHER_TOPIC_NAME);
//Publishing an object.
- PubSubIT.MyObject object = new PubSubIT.MyObject();
+ MyObject object = new MyObject();
object.setId("123");
client.publishEvent(PUBSUB_NAME, TOPIC_NAME, object).block();
LOG.info("Published one object.");
@@ -321,7 +324,7 @@ public void testPubSub() throws Exception {
callWithRetry(() -> {
LOG.info("Checking results for topic " + TYPED_TOPIC_NAME);
- List> messages = client.invokeMethod(
+ List> messages = client.invokeMethod(
PUBSUB_APP_ID,
"messages/typedtestingtopic",
null,
@@ -332,8 +335,8 @@ public void testPubSub() throws Exception {
assertThat(messages)
.extracting(CloudEvent::getData)
.filteredOn(Objects::nonNull)
- .filteredOn(PubSubIT.MyObject.class::isInstance)
- .map(PubSubIT.MyObject::getId)
+ .filteredOn(MyObject.class::isInstance)
+ .map(MyObject::getId)
.contains("123");
}, 2000);
@@ -408,9 +411,9 @@ private static void sendBulkMessagesAsText(DaprClient client, String topicName)
}
private void publishMyObjectAsserting(DaprClient client) {
- PubSubIT.MyObject object = new PubSubIT.MyObject();
+ MyObject object = new MyObject();
object.setId("123");
- BulkPublishResponse response = client.publishEvents(
+ BulkPublishResponse response = client.publishEvents(
PUBSUB_NAME,
TOPIC_BULK,
"application/json",
@@ -537,24 +540,74 @@ public void testPubSubTTLMetadata() throws Exception {
}
}
+ @Test
+ @DisplayName("Should deliver published messages via the @BulkSubscribe handler with all SUCCESS")
+ public void testPubSubBulkSubscribe() throws Exception {
+ // Send a batch of messages on the bulk-subscribe topic.
+ try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build()) {
+ for (int i = 0; i < NUM_MESSAGES; i++) {
+ String message = String.format("This is message #%d on topic %s", i, BULK_SUB_TOPIC_NAME);
+ client.publishEvent(PUBSUB_NAME, BULK_SUB_TOPIC_NAME, message).block();
+ LOG.info("Published message: '{}' to topic '{}' pubsub_name '{}'", message, BULK_SUB_TOPIC_NAME, PUBSUB_NAME);
+ }
+ }
+
+ // Give the subscriber a chance to receive the messages.
+ Thread.sleep(5000);
+
+ try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build()) {
+ callWithRetry(() -> {
+ LOG.info("Checking results for topic " + BULK_SUB_TOPIC_NAME);
+
+ // The subscriber returns BulkSubscribeAppResponse objects, but the controller's
+ // generic List response type erases at runtime, so Jackson deserializes each
+ // element to LinkedHashMap. Take the list as List> and convertValue each item.
+ final List> messages = client.invokeMethod(
+ PUBSUB_APP_ID,
+ "messages/" + BULK_SUB_TOPIC_NAME,
+ null,
+ HttpExtension.GET,
+ List.class).block();
+
+ assertNotNull(messages);
+
+ // Bulk-subscribe batching is timing-dependent (depends on publish rate vs the
+ // controller's maxAwaitDurationMs window). With pubsub.in-memory and synchronous
+ // publishes the runtime may deliver each message in its own batch. Assert on the
+ // contract that matters: every published message reached the bulk endpoint with
+ // SUCCESS, regardless of how the runtime batched them.
+ int totalEntries = 0;
+ for (Object rawResponse : messages) {
+ BulkSubscribeAppResponse response = OBJECT_MAPPER.convertValue(
+ rawResponse, BulkSubscribeAppResponse.class);
+ for (BulkSubscribeAppResponseEntry entry : response.getStatuses()) {
+ assertThat(entry.getStatus()).isEqualTo(BulkSubscribeAppResponseStatus.SUCCESS);
+ totalEntries++;
+ }
+ }
+ assertThat(totalEntries).isEqualTo(NUM_MESSAGES);
+ }, 2000);
+ }
+ }
+
@Test
@DisplayName("Should publish long values")
public void testLongValues() throws Exception {
Random random = new Random(590518626939830271L);
- Set values = new HashSet<>();
- values.add(new PubSubIT.ConvertToLong().setVal(590518626939830271L));
- PubSubIT.ConvertToLong val;
+ Set values = new HashSet<>();
+ values.add(new ConvertToLong().setVal(590518626939830271L));
+ ConvertToLong val;
for (int i = 0; i < NUM_MESSAGES - 1; i++) {
do {
- val = new PubSubIT.ConvertToLong().setVal(random.nextLong());
+ val = new ConvertToLong().setVal(random.nextLong());
} while (values.contains(val));
values.add(val);
}
- Iterator valuesIt = values.iterator();
+ Iterator valuesIt = values.iterator();
try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build()) {
for (int i = 0; i < NUM_MESSAGES; i++) {
- PubSubIT.ConvertToLong value = valuesIt.next();
+ ConvertToLong value = valuesIt.next();
LOG.info("The long value sent " + value.getValue());
//Publishing messages
client.publishEvent(
@@ -573,17 +626,17 @@ public void testLongValues() throws Exception {
}
}
- Set actual = new HashSet<>();
+ Set actual = new HashSet<>();
try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build()) {
callWithRetry(() -> {
LOG.info("Checking results for topic " + LONG_TOPIC_NAME);
- final List> messages = client.invokeMethod(
+ final List> messages = client.invokeMethod(
PUBSUB_APP_ID,
"messages/testinglongvalues",
null,
HttpExtension.GET, CLOUD_EVENT_LONG_LIST_TYPE_REF).block();
assertNotNull(messages);
- for (CloudEvent message : messages) {
+ for (CloudEvent message : messages) {
actual.add(message.getData());
}
assertThat(values).containsAll(actual);
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/MyObject.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/MyObject.java
new file mode 100644
index 0000000000..019c537727
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/MyObject.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package io.dapr.it.testcontainers.pubsub.http;
+
+public class MyObject {
+ private String id;
+
+ public String getId() {
+ return this.id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/SubscriberController.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/SubscriberController.java
index 30e9204018..1428d85788 100644
--- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/SubscriberController.java
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/SubscriberController.java
@@ -21,7 +21,6 @@
import io.dapr.client.domain.BulkSubscribeMessage;
import io.dapr.client.domain.BulkSubscribeMessageEntry;
import io.dapr.client.domain.CloudEvent;
-import io.dapr.it.pubsub.http.PubSubIT;
import io.dapr.springboot.annotations.BulkSubscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -151,7 +150,7 @@ public Mono handleMessageV3(@RequestBody(required = false) CloudEvent enve
@Topic(name = "typedtestingtopic", pubsubName = "pubsub")
@PostMapping(path = "/route1b")
- public Mono handleMessageTyped(@RequestBody(required = false) CloudEvent envelope) {
+ public Mono handleMessageTyped(@RequestBody(required = false) CloudEvent envelope) {
return Mono.fromRunnable(() -> {
try {
String id = envelope.getData() == null ? "" : envelope.getData().getId();
@@ -208,7 +207,7 @@ public Mono handleMessageTTLTopic(@RequestBody(required = false) CloudEven
@Topic(name = "testinglongvalues", pubsubName = "pubsub")
@PostMapping(path = "/testinglongvalues")
- public Mono handleMessageLongValues(@RequestBody(required = false) CloudEvent cloudEvent) {
+ public Mono handleMessageLongValues(@RequestBody(required = false) CloudEvent cloudEvent) {
return Mono.fromRunnable(() -> {
try {
Long message = cloudEvent.getData().getValue();
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/outbox/DaprPubSubOutboxIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/outbox/DaprPubSubOutboxIT.java
index d4139bcf91..b053b5c070 100644
--- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/outbox/DaprPubSubOutboxIT.java
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/outbox/DaprPubSubOutboxIT.java
@@ -24,6 +24,7 @@
import io.dapr.testcontainers.wait.strategy.DaprWait;
import org.assertj.core.api.Assertions;
import org.awaitility.Awaitility;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@@ -36,7 +37,6 @@
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.Network;
-import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.time.Duration;
@@ -47,7 +47,7 @@
import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
-@Disabled("Unclear why this test is failing intermittently in CI")
+@Disabled("Outbox event delivery via in-memory pubsub is unreliable — suspected Dapr runtime issue. See #1603")
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
classes = {
@@ -70,7 +70,6 @@ public class DaprPubSubOutboxIT {
private static final String TOPIC_PRODUCT_CREATED = "product.created";
private static final String STATE_STORE_NAME = "kvstore";
- @Container
private static final DaprContainer DAPR_CONTAINER = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
.withAppName(PUBSUB_APP_ID)
.withNetwork(DAPR_NETWORK)
@@ -87,21 +86,20 @@ public class DaprPubSubOutboxIT {
@Autowired
private ProductWebhookController productWebhookController;
- /**
- * Expose the Dapr ports to the host.
- *
- * @param registry the dynamic property registry
- */
@DynamicPropertySource
static void daprProperties(DynamicPropertyRegistry registry) {
- registry.add("dapr.http.endpoint", DAPR_CONTAINER::getHttpEndpoint);
- registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint);
registry.add("server.port", () -> PORT);
}
@BeforeAll
- public static void beforeAll(){
+ public static void beforeAll() {
org.testcontainers.Testcontainers.exposeHostPorts(PORT);
+ DAPR_CONTAINER.start();
+ }
+
+ @AfterAll
+ public static void afterAll() {
+ DAPR_CONTAINER.stop();
}
@BeforeEach
@@ -128,7 +126,8 @@ public void shouldPublishUsingOutbox() throws Exception {
client.executeStateTransaction(transactionRequest).block();
- Awaitility.await().atMost(Duration.ofSeconds(10))
+ Awaitility.await().atMost(Duration.ofSeconds(60))
+ .pollInterval(Duration.ofMillis(500))
.ignoreExceptions()
.untilAsserted(() -> Assertions.assertThat(productWebhookController.getEventList()).isNotEmpty());
}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/stream/DaprPubSubStreamIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/stream/DaprPubSubStreamIT.java
new file mode 100644
index 0000000000..c10aef36c9
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/stream/DaprPubSubStreamIT.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package io.dapr.it.testcontainers.pubsub.stream;
+
+import io.dapr.client.DaprClient;
+import io.dapr.client.DaprPreviewClient;
+import io.dapr.client.SubscriptionListener;
+import io.dapr.client.domain.CloudEvent;
+import io.dapr.it.testcontainers.DaprClientFactory;
+import io.dapr.testcontainers.Component;
+import io.dapr.testcontainers.DaprContainer;
+import io.dapr.utils.TypeRef;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import reactor.core.publisher.Mono;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static io.dapr.it.Retry.callWithRetry;
+import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Testcontainers
+@Tag("testcontainers")
+public class DaprPubSubStreamIT {
+
+ private static final int NUM_MESSAGES = 100;
+ private static final String TOPIC_NAME = "stream-topic";
+ private static final String TOPIC_NAME_FLUX = "stream-topic-flux";
+ private static final String TOPIC_NAME_CLOUDEVENT = "stream-topic-cloudevent";
+ private static final String TOPIC_NAME_RAWPAYLOAD = "stream-topic-rawpayload";
+ private static final String TOPIC_NAME_DLQ = "stream-topic-dlq";
+ private static final String TOPIC_NAME_DLQ_DEADLETTER = "stream-topic-dlq-deadletter";
+ private static final String PUBSUB_NAME = "pubsub";
+
+ @Container
+ private static final DaprContainer DAPR_CONTAINER = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
+ .withAppName("pubsub-stream-app")
+ .withComponent(new Component(PUBSUB_NAME, "pubsub.in-memory", "v1", Collections.emptyMap()));
+
+ private void waitForSubscription(DaprClient client, String topic, CountDownLatch latch) throws InterruptedException {
+ callWithRetry(() -> {
+ client.publishEvent(PUBSUB_NAME, topic, "probe").block();
+ try {
+ assertTrue(latch.await(500, TimeUnit.MILLISECONDS), "Subscription not ready for " + topic);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ }
+ }, 60000);
+ }
+
+ @Test
+ public void testPubSub() throws Exception {
+ var runId = UUID.randomUUID().toString();
+ try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build();
+ DaprPreviewClient previewClient = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER)
+ .buildPreviewClient()) {
+
+ Set received = Collections.synchronizedSet(new HashSet<>());
+ CountDownLatch ready = new CountDownLatch(1);
+
+ var listener = new SubscriptionListener() {
+ @Override
+ public Mono onEvent(CloudEvent event) {
+ return Mono.fromCallable(() -> {
+ ready.countDown();
+ if (event.getData().contains(runId)) {
+ received.add(event.getId());
+ return Status.SUCCESS;
+ }
+ return Status.DROP;
+ });
+ }
+
+ @Override
+ public void onError(RuntimeException exception) {
+ }
+ };
+
+ try (var subscription = previewClient.subscribeToEvents(PUBSUB_NAME, TOPIC_NAME, listener, TypeRef.STRING)) {
+ waitForSubscription(client, TOPIC_NAME, ready);
+
+ for (int i = 0; i < NUM_MESSAGES; i++) {
+ String message = String.format("This is message #%d on topic %s for run %s", i, TOPIC_NAME, runId);
+ client.publishEvent(PUBSUB_NAME, TOPIC_NAME, message).block();
+ }
+
+ callWithRetry(() -> {
+ assertEquals(NUM_MESSAGES, received.size(),
+ String.format("Got %d/%d messages for topic %s", received.size(), NUM_MESSAGES, TOPIC_NAME));
+ }, 120000);
+ }
+ }
+ }
+
+ @Test
+ public void testPubSubFlux() throws Exception {
+ var runId = UUID.randomUUID().toString();
+ try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build();
+ DaprPreviewClient previewClient = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER)
+ .buildPreviewClient()) {
+
+ Set received = Collections.synchronizedSet(new HashSet<>());
+ CountDownLatch ready = new CountDownLatch(1);
+
+ var disposable = previewClient.subscribeToTopic(PUBSUB_NAME, TOPIC_NAME_FLUX, TypeRef.STRING)
+ .doOnNext(rawMessage -> {
+ ready.countDown();
+ if (rawMessage.contains(runId)) {
+ received.add(rawMessage);
+ }
+ })
+ .subscribe();
+
+ waitForSubscription(client, TOPIC_NAME_FLUX, ready);
+
+ for (int i = 0; i < NUM_MESSAGES; i++) {
+ String message = String.format("Flux message #%d for run %s", i, runId);
+ client.publishEvent(PUBSUB_NAME, TOPIC_NAME_FLUX, message).block();
+ }
+
+ callWithRetry(() -> {
+ assertEquals(NUM_MESSAGES, received.size(),
+ String.format("Got %d/%d flux messages for topic %s", received.size(), NUM_MESSAGES, TOPIC_NAME_FLUX));
+ }, 60000);
+
+ disposable.dispose();
+ }
+ }
+
+ @Test
+ public void testPubSubCloudEvent() throws Exception {
+ var runId = UUID.randomUUID().toString();
+ try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build();
+ DaprPreviewClient previewClient = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER)
+ .buildPreviewClient()) {
+
+ Set received = Collections.synchronizedSet(new HashSet<>());
+ CountDownLatch ready = new CountDownLatch(1);
+
+ var disposable = previewClient.subscribeToTopic(
+ PUBSUB_NAME, TOPIC_NAME_CLOUDEVENT, new TypeRef>() {})
+ .doOnNext(cloudEvent -> {
+ ready.countDown();
+ if (cloudEvent.getData() != null && cloudEvent.getData().contains(runId)) {
+ received.add(cloudEvent.getId());
+ }
+ })
+ .subscribe();
+
+ waitForSubscription(client, TOPIC_NAME_CLOUDEVENT, ready);
+
+ for (int i = 0; i < NUM_MESSAGES; i++) {
+ String message = String.format("CloudEvent message #%d for run %s", i, runId);
+ client.publishEvent(PUBSUB_NAME, TOPIC_NAME_CLOUDEVENT, message).block();
+ }
+
+ callWithRetry(() -> {
+ assertEquals(NUM_MESSAGES, received.size(),
+ String.format("Got %d/%d CloudEvent messages for topic %s",
+ received.size(), NUM_MESSAGES, TOPIC_NAME_CLOUDEVENT));
+ }, 60000);
+
+ disposable.dispose();
+ }
+ }
+
+ @Disabled("Streaming subscription with rawPayload metadata not supported by pubsub.in-memory")
+ @Test
+ public void testPubSubRawPayload() throws Exception {
+ var runId = UUID.randomUUID().toString();
+ try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build();
+ DaprPreviewClient previewClient = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER)
+ .buildPreviewClient()) {
+
+ Set received = Collections.synchronizedSet(new HashSet<>());
+ Map metadata = Map.of("rawPayload", "true");
+ CountDownLatch ready = new CountDownLatch(1);
+
+ var disposable = previewClient.subscribeToTopic(PUBSUB_NAME, TOPIC_NAME_RAWPAYLOAD, TypeRef.STRING, metadata)
+ .doOnNext(rawMessage -> {
+ ready.countDown();
+ if (rawMessage.contains(runId)) {
+ received.add(rawMessage);
+ }
+ })
+ .subscribe();
+
+ waitForSubscription(client, TOPIC_NAME_RAWPAYLOAD, ready);
+
+ for (int i = 0; i < NUM_MESSAGES; i++) {
+ String message = String.format("RawPayload message #%d for run %s", i, runId);
+ client.publishEvent(PUBSUB_NAME, TOPIC_NAME_RAWPAYLOAD, message, Map.of("rawPayload", "true")).block();
+ }
+
+ callWithRetry(() -> {
+ assertEquals(NUM_MESSAGES, received.size(),
+ String.format("Got %d/%d raw payload messages for topic %s",
+ received.size(), NUM_MESSAGES, TOPIC_NAME_RAWPAYLOAD));
+ }, 60000);
+
+ disposable.dispose();
+ }
+ }
+
+ /**
+ * Streaming subscription should forward DROPped messages to the configured
+ * dead-letter topic. Ports the {@code testPubSubDeadLetterTopic} test from
+ * the legacy {@code PubSubStreamIT} into the Testcontainers harness.
+ */
+ @Test
+ public void testPubSubDeadLetterTopic() throws Exception {
+ var runId = UUID.randomUUID().toString();
+ try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build();
+ DaprPreviewClient previewClient = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER)
+ .buildPreviewClient()) {
+
+ Set deadLetterMessageIds = Collections.synchronizedSet(new HashSet<>());
+ CountDownLatch deadLetterReady = new CountDownLatch(1);
+ CountDownLatch mainReady = new CountDownLatch(1);
+
+ // Subscribe to the dead-letter topic first so we don't miss any messages.
+ var deadLetterListener = new SubscriptionListener() {
+ @Override
+ public Mono onEvent(CloudEvent event) {
+ deadLetterReady.countDown();
+ if (event.getData() != null && event.getData().contains(runId)) {
+ deadLetterMessageIds.add(event.getId());
+ }
+ return Mono.just(Status.SUCCESS);
+ }
+
+ @Override
+ public void onError(RuntimeException exception) {
+ }
+ };
+
+ // Always-DROP listener on the main topic; daprd should forward each dropped
+ // message to the dead-letter topic.
+ var mainListener = new SubscriptionListener() {
+ @Override
+ public Mono onEvent(CloudEvent event) {
+ mainReady.countDown();
+ return Mono.just(Status.DROP);
+ }
+
+ @Override
+ public void onError(RuntimeException exception) {
+ }
+ };
+
+ try (var deadLetterSubscription = previewClient.subscribeToEvents(
+ PUBSUB_NAME, TOPIC_NAME_DLQ_DEADLETTER, deadLetterListener, TypeRef.STRING);
+ var mainSubscription = previewClient.subscribeToEvents(
+ PUBSUB_NAME, TOPIC_NAME_DLQ, TOPIC_NAME_DLQ_DEADLETTER, mainListener, TypeRef.STRING)) {
+
+ waitForSubscription(client, TOPIC_NAME_DLQ_DEADLETTER, deadLetterReady);
+ waitForSubscription(client, TOPIC_NAME_DLQ, mainReady);
+
+ for (int i = 0; i < NUM_MESSAGES; i++) {
+ String message = String.format("DLQ message #%d for run %s", i, runId);
+ client.publishEvent(PUBSUB_NAME, TOPIC_NAME_DLQ, message).block();
+ }
+
+ callWithRetry(() -> {
+ assertEquals(NUM_MESSAGES, deadLetterMessageIds.size(),
+ String.format("Got %d/%d dead-letter messages on topic %s",
+ deadLetterMessageIds.size(), NUM_MESSAGES, TOPIC_NAME_DLQ_DEADLETTER));
+ }, 120000);
+ }
+ }
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/secrets/DaprSecretsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/secrets/DaprSecretsIT.java
new file mode 100644
index 0000000000..3f71938286
--- /dev/null
+++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/secrets/DaprSecretsIT.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2024 The Dapr Authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.dapr.it.testcontainers.secrets;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.dapr.client.DaprClient;
+import io.dapr.client.DaprClientBuilder;
+import io.dapr.config.Properties;
+import io.dapr.testcontainers.Component;
+import io.dapr.testcontainers.DaprContainer;
+import io.dapr.testcontainers.DaprLogLevel;
+import io.dapr.testcontainers.MetadataEntry;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.Network;
+import org.testcontainers.images.builder.Transferable;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Integration tests for the Dapr Secrets API using testcontainers.
+ */
+@Disabled("Needs investigation: DaprContainer file mounting with secretstores.local.file")
+@Testcontainers
+@Tag("testcontainers")
+public class DaprSecretsIT {
+
+ private static final String SECRETS_STORE_NAME = "localSecretStore";
+ private static final String CONTAINER_SECRETS_PATH = "/tmp/secrets.json";
+ private static final ObjectMapper JSON_SERIALIZER = new ObjectMapper();
+
+ private static final String KEY1 = "movie";
+ private static final String KEY2 = "person";
+
+ private static final Network DAPR_NETWORK = Network.newNetwork();
+
+ private static DaprClient daprClient;
+
+ private static final String SECRETS_JSON = createSecretsJson();
+
+ @Container
+ private static final DaprContainer DAPR_CONTAINER = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
+ .withAppName("secrets-test-app")
+ .withNetwork(DAPR_NETWORK)
+ .withDaprLogLevel(DaprLogLevel.DEBUG)
+ .withComponent(new Component(
+ SECRETS_STORE_NAME,
+ "secretstores.local.file",
+ "v1",
+ List.of(new MetadataEntry("secretsFile", CONTAINER_SECRETS_PATH))
+ ))
+ .withCopyToContainer(Transferable.of(SECRETS_JSON), CONTAINER_SECRETS_PATH)
+ .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()));
+
+ private static String createSecretsJson() {
+ try {
+ Map secrets = new HashMap<>();
+ Map movieSecret = new HashMap<>();
+ movieSecret.put("title", "The Metrics IV");
+ movieSecret.put("year", "2020");
+ secrets.put(KEY1, movieSecret);
+
+ Map personSecret = new HashMap<>();
+ personSecret.put("name", "Jon Doe");
+ secrets.put(KEY2, personSecret);
+
+ return JSON_SERIALIZER.writeValueAsString(secrets);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @BeforeAll
+ static void setUp() {
+ daprClient = new DaprClientBuilder()
+ .withPropertyOverride(Properties.HTTP_ENDPOINT, DAPR_CONTAINER.getHttpEndpoint())
+ .withPropertyOverride(Properties.GRPC_ENDPOINT, DAPR_CONTAINER.getGrpcEndpoint())
+ .build();
+ }
+
+ @AfterAll
+ static void tearDown() throws Exception {
+ if (daprClient != null) {
+ daprClient.close();
+ }
+ }
+
+ @Test
+ public void testGetSecret() {
+ Map data = daprClient.getSecret(SECRETS_STORE_NAME, KEY1).block();
+
+ assertNotNull(data);
+ assertEquals(2, data.size());
+ assertEquals("The Metrics IV", data.get("title"));
+ assertEquals("2020", data.get("year"));
+ }
+
+ @Test
+ public void testGetBulkSecret() {
+ Map> data = daprClient.getBulkSecret(SECRETS_STORE_NAME).block();
+
+ assertNotNull(data);
+ assertTrue(data.size() >= 2);
+ assertEquals(2, data.get(KEY1).size());
+ assertEquals("The Metrics IV", data.get(KEY1).get("title"));
+ assertEquals("2020", data.get(KEY1).get("year"));
+ assertEquals(1, data.get(KEY2).size());
+ assertEquals("Jon Doe", data.get(KEY2).get("name"));
+ }
+
+ @Test
+ public void testGetSecretKeyNotFound() {
+ assertThrows(RuntimeException.class, () ->
+ daprClient.getSecret(SECRETS_STORE_NAME, "unknownKey").block()
+ );
+ }
+
+ @Test
+ public void testGetSecretStoreNotFound() {
+ assertThrows(RuntimeException.class, () ->
+ daprClient.getSecret("unknownStore", "unknownKey").block()
+ );
+ }
+}
diff --git a/sdk-tests/src/test/java/io/dapr/it/tracing/OpenTelemetry.java b/sdk-tests/src/test/java/io/dapr/it/tracing/OpenTelemetry.java
index 08a6ea88f7..649a18f9f9 100644
--- a/sdk-tests/src/test/java/io/dapr/it/tracing/OpenTelemetry.java
+++ b/sdk-tests/src/test/java/io/dapr/it/tracing/OpenTelemetry.java
@@ -32,6 +32,27 @@ public class OpenTelemetry {
private static final String ENDPOINT_V2_SPANS = "/api/v2/spans";
+ /**
+ * Creates an opentelemetry instance using an explicit Zipkin endpoint URL.
+ * Skips the local Zipkin readiness probe — callers (e.g., Testcontainers-backed ITs)
+ * are responsible for ensuring the Zipkin endpoint is reachable before invocation.
+ * @param serviceName Name of the service in Zipkin (informational; not consumed here).
+ * @param zipkinEndpointUrl Full Zipkin spans endpoint URL (e.g., http://host:port/api/v2/spans).
+ * @return OpenTelemetry.
+ */
+ public static io.opentelemetry.api.OpenTelemetry createOpenTelemetry(String serviceName, String zipkinEndpointUrl) {
+ ZipkinSpanExporter zipkinExporter = ZipkinSpanExporter.builder().setEndpoint(zipkinEndpointUrl).build();
+
+ SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
+ .addSpanProcessor(SimpleSpanProcessor.create(zipkinExporter))
+ .build();
+
+ return OpenTelemetrySdk.builder()
+ .setTracerProvider(sdkTracerProvider)
+ .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
+ .build();
+ }
+
/**
* Creates an opentelemetry instance.
* @param serviceName Name of the service in Zipkin
diff --git a/sdk-tests/src/test/java/io/dapr/it/tracing/Validation.java b/sdk-tests/src/test/java/io/dapr/it/tracing/Validation.java
index 0f0adf3fed..52884f72bf 100644
--- a/sdk-tests/src/test/java/io/dapr/it/tracing/Validation.java
+++ b/sdk-tests/src/test/java/io/dapr/it/tracing/Validation.java
@@ -49,6 +49,27 @@ public final class Validation {
public static final String JSONPATH_SLEEP_SPAN_ID =
"$..[?(@.parentId=='%s' && @.duration > 1000000 && @.name=='%s')]['id']";
+ public static void validate(String spanName, String sleepSpanName, String zipkinTracesUrl) throws Exception {
+ // Must wait for some time to make sure Zipkin receives all spans.
+ Thread.sleep(10000);
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .GET()
+ .uri(URI.create(zipkinTracesUrl))
+ .build();
+
+ HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
+ DocumentContext documentContext = JsonPath.parse(response.body());
+ String mainSpanId = readOne(documentContext, String.format(JSONPATH_MAIN_SPAN_ID, spanName)).toString();
+
+ assertNotNull(mainSpanId);
+
+ String sleepSpanId = readOne(documentContext, String.format(JSONPATH_SLEEP_SPAN_ID, mainSpanId, sleepSpanName))
+ .toString();
+
+ assertNotNull(sleepSpanId);
+ }
+
public static void validate(String spanName, String sleepSpanName) throws Exception {
// Must wait for some time to make sure Zipkin receives all spans.
Thread.sleep(10000);
diff --git a/sdk-tests/src/test/java/io/dapr/it/tracing/grpc/TracingIT.java b/sdk-tests/src/test/java/io/dapr/it/tracing/grpc/TracingIT.java
index 43f982eed4..523bfa762b 100644
--- a/sdk-tests/src/test/java/io/dapr/it/tracing/grpc/TracingIT.java
+++ b/sdk-tests/src/test/java/io/dapr/it/tracing/grpc/TracingIT.java
@@ -1,18 +1,35 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
package io.dapr.it.tracing.grpc;
import io.dapr.client.DaprClient;
-import io.dapr.client.DaprClientBuilder;
import io.dapr.client.domain.HttpExtension;
import io.dapr.it.AppRun;
-import io.dapr.it.BaseIT;
-import io.dapr.it.DaprRun;
+import io.dapr.it.containers.BaseContainerIT;
+import io.dapr.it.containers.SharedTestInfra;
import io.dapr.it.tracing.Validation;
+import io.dapr.testcontainers.Configuration;
+import io.dapr.testcontainers.DaprContainer;
+import io.dapr.testcontainers.DaprProtocol;
+import io.dapr.testcontainers.TracingConfigurationSettings;
+import io.dapr.testcontainers.ZipkinTracingConfigurationSettings;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
-import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.UUID;
@@ -22,37 +39,60 @@
import static io.dapr.it.tracing.OpenTelemetry.getReactorContext;
@SuppressWarnings("deprecation")
-public class TracingIT extends BaseIT {
+public class TracingIT extends BaseContainerIT {
+
+ private static final String APP_NAME = "tracing-grpc-it";
- /**
- * Run of a Dapr application.
- */
- private DaprRun daprRun = null;
+ private static DaprContainer dapr;
+ private static AppRun app;
+ private static String zipkinHostUrl;
+ private static String zipkinTracesUrl;
- @BeforeEach
- public void setup() throws Exception {
- daprRun = startDaprApp(
- TracingIT.class.getSimpleName() + "grpc",
- Service.SUCCESS_MESSAGE,
- Service.class,
- AppRun.AppProtocol.GRPC, // appProtocol
- 60000);
+ @BeforeAll
+ public static void setup() throws Exception {
+ SharedTestInfra.zipkin();
+ String zipkinHost = SharedTestInfra.zipkin().getHost();
+ int zipkinPort = SharedTestInfra.zipkin().getMappedPort(9411);
+ zipkinHostUrl = "http://" + zipkinHost + ":" + zipkinPort + "/api/v2/spans";
+ zipkinTracesUrl = "http://" + zipkinHost + ":" + zipkinPort + "/api/v2/traces?limit=100";
- daprRun.waitForAppHealth(10000);
+ var pair = startAppAndAttach(
+ APP_NAME,
+ Service.class,
+ AppRun.AppProtocol.GRPC,
+ appPort -> {
+ DaprContainer d = daprBuilder(APP_NAME)
+ .withAppPort(appPort)
+ .withAppChannelAddress("host.testcontainers.internal")
+ .withAppProtocol(DaprProtocol.GRPC)
+ .withConfiguration(new Configuration(
+ "tracing",
+ new TracingConfigurationSettings(
+ "1",
+ true,
+ null,
+ new ZipkinTracingConfigurationSettings(SharedTestInfra.zipkinInternalEndpoint())
+ ),
+ null
+ ));
+ return d;
+ });
+ dapr = pair.dapr();
+ app = pair.app();
}
@Test
public void testInvoke() throws Exception {
- OpenTelemetry openTelemetry = createOpenTelemetry("service over grpc");
+ OpenTelemetry openTelemetry = createOpenTelemetry("service over grpc", zipkinHostUrl);
Tracer tracer = openTelemetry.getTracer("grpc integration test tracer");
String spanName = UUID.randomUUID().toString();
Span span = tracer.spanBuilder(spanName).setSpanKind(SpanKind.CLIENT).startSpan();
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
+ try (DaprClient client = newDaprClient(dapr)) {
client.waitForSidecar(10000).block();
try (Scope scope = span.makeCurrent()) {
SleepRequest req = SleepRequest.newBuilder().setSeconds(1).build();
- client.invokeMethod(daprRun.getAppName(), "sleepOverGRPC", req.toByteArray(), HttpExtension.POST)
+ client.invokeMethod(APP_NAME, "sleepOverGRPC", req.toByteArray(), HttpExtension.POST)
.contextWrite(getReactorContext(openTelemetry))
.block();
}
@@ -60,6 +100,6 @@ public void testInvoke() throws Exception {
span.end();
- Validation.validate(spanName, "calllocal/tracingitgrpc-service/sleepovergrpc");
+ Validation.validate(spanName, "calllocal/" + APP_NAME + "/sleepovergrpc", zipkinTracesUrl);
}
}
diff --git a/sdk-tests/src/test/java/io/dapr/it/tracing/http/TracingIT.java b/sdk-tests/src/test/java/io/dapr/it/tracing/http/TracingIT.java
index b133ce7213..ec1eb77e79 100644
--- a/sdk-tests/src/test/java/io/dapr/it/tracing/http/TracingIT.java
+++ b/sdk-tests/src/test/java/io/dapr/it/tracing/http/TracingIT.java
@@ -1,17 +1,34 @@
+/*
+ * Copyright 2025 The Dapr Authors
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
package io.dapr.it.tracing.http;
import io.dapr.client.DaprClient;
-import io.dapr.client.DaprClientBuilder;
import io.dapr.client.domain.HttpExtension;
-import io.dapr.it.BaseIT;
-import io.dapr.it.DaprRun;
+import io.dapr.it.AppRun;
+import io.dapr.it.containers.BaseContainerIT;
+import io.dapr.it.containers.SharedTestInfra;
import io.dapr.it.tracing.Validation;
+import io.dapr.testcontainers.Configuration;
+import io.dapr.testcontainers.DaprContainer;
+import io.dapr.testcontainers.TracingConfigurationSettings;
+import io.dapr.testcontainers.ZipkinTracingConfigurationSettings;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
-import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.UUID;
@@ -20,21 +37,46 @@
import static io.dapr.it.tracing.OpenTelemetry.getReactorContext;
@SuppressWarnings("deprecation")
-public class TracingIT extends BaseIT {
+public class TracingIT extends BaseContainerIT {
+
+ private static final String APP_NAME = "tracing-http-it";
- /**
- * Run of a Dapr application.
- */
- private DaprRun daprRun = null;
+ private static DaprContainer dapr;
+ private static AppRun app;
+ private static String zipkinHostUrl;
+ private static String zipkinTracesUrl;
- @BeforeEach
- public void setup() throws Exception {
- daprRun = startDaprApp(
- TracingIT.class.getSimpleName() + "http",
- Service.SUCCESS_MESSAGE,
- Service.class,
- true,
- 30000);
+ @BeforeAll
+ public static void setup() throws Exception {
+ // Start Zipkin first so we can wire its endpoint into both Dapr and the test's OpenTelemetry SDK.
+ SharedTestInfra.zipkin();
+ String zipkinHost = SharedTestInfra.zipkin().getHost();
+ int zipkinPort = SharedTestInfra.zipkin().getMappedPort(9411);
+ zipkinHostUrl = "http://" + zipkinHost + ":" + zipkinPort + "/api/v2/spans";
+ zipkinTracesUrl = "http://" + zipkinHost + ":" + zipkinPort + "/api/v2/traces?limit=100";
+
+ var pair = startAppAndAttach(
+ APP_NAME,
+ Service.class,
+ AppRun.AppProtocol.HTTP,
+ appPort -> {
+ DaprContainer d = daprBuilder(APP_NAME)
+ .withAppPort(appPort)
+ .withAppChannelAddress("host.testcontainers.internal")
+ .withConfiguration(new Configuration(
+ "tracing",
+ new TracingConfigurationSettings(
+ "1",
+ true,
+ null,
+ new ZipkinTracingConfigurationSettings(SharedTestInfra.zipkinInternalEndpoint())
+ ),
+ null
+ ));
+ return d;
+ });
+ dapr = pair.dapr();
+ app = pair.app();
// Wait since service might be ready even after port is available.
Thread.sleep(2000);
@@ -42,15 +84,15 @@ public void setup() throws Exception {
@Test
public void testInvoke() throws Exception {
- OpenTelemetry openTelemetry = createOpenTelemetry(OpenTelemetryConfig.SERVICE_NAME);
+ OpenTelemetry openTelemetry = createOpenTelemetry(OpenTelemetryConfig.SERVICE_NAME, zipkinHostUrl);
Tracer tracer = openTelemetry.getTracer(OpenTelemetryConfig.TRACER_NAME);
String spanName = UUID.randomUUID().toString();
Span span = tracer.spanBuilder(spanName).setSpanKind(SpanKind.CLIENT).startSpan();
- try (DaprClient client = daprRun.newDaprClientBuilder().build()) {
+ try (DaprClient client = newDaprClient(dapr)) {
client.waitForSidecar(10000).block();
try (Scope scope = span.makeCurrent()) {
- client.invokeMethod(daprRun.getAppName(), "sleep", 1, HttpExtension.POST)
+ client.invokeMethod(APP_NAME, "sleep", 1, HttpExtension.POST)
.contextWrite(getReactorContext(openTelemetry))
.block();
}
@@ -58,7 +100,6 @@ public void testInvoke() throws Exception {
span.end();
- Validation.validate(spanName, "calllocal/tracingithttp-service/sleep");
+ Validation.validate(spanName, "calllocal/" + APP_NAME + "/sleep", zipkinTracesUrl);
}
-
}
diff --git a/sdk/src/main/java/io/dapr/client/Subscription.java b/sdk/src/main/java/io/dapr/client/Subscription.java
index 2f85128474..f42c04de81 100644
--- a/sdk/src/main/java/io/dapr/client/Subscription.java
+++ b/sdk/src/main/java/io/dapr/client/Subscription.java
@@ -88,6 +88,7 @@ public class Subscription implements Closeable {
});
this.receiver = new Thread(() -> {
+ long backoffMs = 1000L;
while (running.get()) {
var stream = asyncStub.subscribeTopicEventsAlpha1(new StreamObserver<>() {
@Override
@@ -124,6 +125,7 @@ public void onNext(DaprPubsubProtos.SubscribeTopicEventsResponseAlpha1 topicEven
@Override
public void onError(Throwable throwable) {
listener.onError(DaprException.propagate(throwable));
+ receiverStateChange.release();
}
@Override
@@ -142,6 +144,17 @@ public void onCompleted() {
Thread.currentThread().interrupt();
running.set(false);
}
+
+ if (running.get()) {
+ try {
+ Thread.sleep(backoffMs);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ running.set(false);
+ }
+ // Double the backoff for the next reconnect, capped at 30s.
+ backoffMs = Math.min(backoffMs * 2, 30_000L);
+ }
}
});
}
@@ -186,6 +199,10 @@ void start() {
public void close() {
running.set(false);
receiverStateChange.release();
+ // Interrupt both threads so that any in-flight Thread.sleep (e.g., the
+ // receiver's reconnect backoff, up to 30s) returns immediately instead
+ // of blocking shutdown.
+ this.receiver.interrupt();
this.acker.interrupt();
}
diff --git a/spring-boot-4-sdk-tests/src/test/java/io/dapr/it/springboot4/testcontainers/jobs/DaprJobsIT.java b/spring-boot-4-sdk-tests/src/test/java/io/dapr/it/springboot4/testcontainers/jobs/DaprJobsIT.java
index 686c7eb01f..92f2d09aa5 100644
--- a/spring-boot-4-sdk-tests/src/test/java/io/dapr/it/springboot4/testcontainers/jobs/DaprJobsIT.java
+++ b/spring-boot-4-sdk-tests/src/test/java/io/dapr/it/springboot4/testcontainers/jobs/DaprJobsIT.java
@@ -25,6 +25,7 @@
import io.dapr.it.springboot4.testcontainers.DaprClientConfiguration;
import io.dapr.testcontainers.DaprContainer;
import io.dapr.testcontainers.DaprLogLevel;
+import org.awaitility.Awaitility;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@@ -43,9 +44,11 @@
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Random;
+import java.util.UUID;
import static io.dapr.it.springboot4.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
@SpringBootTest(
webEnvironment = WebEnvironment.RANDOM_PORT,
@@ -93,63 +96,77 @@ public void setUp(){
@Test
public void testJobScheduleCreationWithDueTime() {
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.withZone(ZoneOffset.UTC);
- Instant currentTime = Instant.now();
- daprClient.scheduleJob(new ScheduleJobRequest("Job", currentTime).setOverwrite(true)).block();
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, currentTime)).block();
- GetJobResponse getJobResponse =
- daprClient.getJob(new GetJobRequest("Job")).block();
+ GetJobResponse getJobResponse = Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
+ assertNotNull(getJobResponse);
assertEquals(iso8601Formatter.format(currentTime), getJobResponse.getDueTime().toString());
- assertEquals("Job", getJobResponse.getName());
+ assertEquals(jobName, getJobResponse.getName());
}
@Test
public void testJobScheduleCreationWithSchedule() {
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.withZone(ZoneOffset.UTC);
- Instant currentTime = Instant.now();
- daprClient.scheduleJob(new ScheduleJobRequest("Job", JobSchedule.hourly())
- .setDueTime(currentTime).setOverwrite(true)).block();
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, JobSchedule.hourly())
+ .setDueTime(currentTime)).block();
- GetJobResponse getJobResponse =
- daprClient.getJob(new GetJobRequest("Job")).block();
+ GetJobResponse getJobResponse = Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
+ assertNotNull(getJobResponse);
assertEquals(iso8601Formatter.format(currentTime), getJobResponse.getDueTime().toString());
assertEquals(JobSchedule.hourly().getExpression(), getJobResponse.getSchedule().getExpression());
- assertEquals("Job", getJobResponse.getName());
+ assertEquals(jobName, getJobResponse.getName());
}
@Test
public void testJobScheduleCreationWithAllParameters() {
- Instant currentTime = Instant.now();
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.withZone(ZoneOffset.UTC);
String cronExpression = "2 * 3 * * FRI";
- daprClient.scheduleJob(new ScheduleJobRequest("Job", currentTime)
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, currentTime)
.setTtl(currentTime.plus(2, ChronoUnit.HOURS))
.setData("Job data".getBytes())
.setRepeat(3)
- .setOverwrite(true)
.setSchedule(JobSchedule.fromString(cronExpression))).block();
- GetJobResponse getJobResponse =
- daprClient.getJob(new GetJobRequest("Job")).block();
+ GetJobResponse getJobResponse = Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
+ assertNotNull(getJobResponse);
assertEquals(iso8601Formatter.format(currentTime), getJobResponse.getDueTime().toString());
assertEquals("2 * 3 * * FRI", getJobResponse.getSchedule().getExpression());
- assertEquals("Job", getJobResponse.getName());
+ assertEquals(jobName, getJobResponse.getName());
assertEquals(Integer.valueOf(3), getJobResponse.getRepeats());
assertEquals("Job data", new String(getJobResponse.getData()));
assertEquals(iso8601Formatter.format(currentTime.plus(2, ChronoUnit.HOURS)),
@@ -158,36 +175,38 @@ public void testJobScheduleCreationWithAllParameters() {
@Test
public void testJobScheduleCreationWithDropFailurePolicy() {
- Instant currentTime = Instant.now();
- DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
- .withZone(ZoneOffset.UTC);
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
String cronExpression = "2 * 3 * * FRI";
- daprClient.scheduleJob(new ScheduleJobRequest("Job", currentTime)
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, currentTime)
.setTtl(currentTime.plus(2, ChronoUnit.HOURS))
.setData("Job data".getBytes())
.setRepeat(3)
- .setFailurePolicy(new DropFailurePolicy())
+ .setFailurePolicy(new DropFailurePolicy())
.setSchedule(JobSchedule.fromString(cronExpression))).block();
- GetJobResponse getJobResponse =
- daprClient.getJob(new GetJobRequest("Job")).block();
+ GetJobResponse getJobResponse = Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
+ assertNotNull(getJobResponse);
assertEquals(FailurePolicyType.DROP, getJobResponse.getFailurePolicy().getFailurePolicyType());
}
@Test
public void testJobScheduleCreationWithConstantFailurePolicy() {
- Instant currentTime = Instant.now();
- DateTimeFormatter iso8601Formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
- .withZone(ZoneOffset.UTC);
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
String cronExpression = "2 * 3 * * FRI";
- daprClient.scheduleJob(new ScheduleJobRequest("Job", currentTime)
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, currentTime)
.setTtl(currentTime.plus(2, ChronoUnit.HOURS))
.setData("Job data".getBytes())
.setRepeat(3)
@@ -195,11 +214,15 @@ public void testJobScheduleCreationWithConstantFailurePolicy() {
.setDurationBetweenRetries(Duration.of(10, ChronoUnit.SECONDS)))
.setSchedule(JobSchedule.fromString(cronExpression))).block();
- GetJobResponse getJobResponse =
- daprClient.getJob(new GetJobRequest("Job")).block();
+ GetJobResponse getJobResponse = Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
+ assertNotNull(getJobResponse);
ConstantFailurePolicy jobFailurePolicyConstant = (ConstantFailurePolicy) getJobResponse.getFailurePolicy();
assertEquals(FailurePolicyType.CONSTANT, getJobResponse.getFailurePolicy().getFailurePolicyType());
assertEquals(3, (int)jobFailurePolicyConstant.getMaxRetries());
@@ -209,17 +232,23 @@ public void testJobScheduleCreationWithConstantFailurePolicy() {
@Test
public void testDeleteJobRequest() {
- Instant currentTime = Instant.now();
+ String jobName = "Job-" + UUID.randomUUID().toString().substring(0, 8);
+ Instant currentTime = Instant.now().plus(10, ChronoUnit.MINUTES);
String cronExpression = "2 * 3 * * FRI";
- daprClient.scheduleJob(new ScheduleJobRequest("Job", currentTime)
+ daprClient.scheduleJob(new ScheduleJobRequest(jobName, currentTime)
.setTtl(currentTime.plus(2, ChronoUnit.HOURS))
.setData("Job data".getBytes())
.setRepeat(3)
- .setOverwrite(true)
.setSchedule(JobSchedule.fromString(cronExpression))).block();
- daprClient.deleteJob(new DeleteJobRequest("Job")).block();
+ Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(300))
+ .ignoreExceptions()
+ .until(() -> daprClient.getJob(new GetJobRequest(jobName)).block(), r -> r != null);
+
+ daprClient.deleteJob(new DeleteJobRequest(jobName)).block();
}
}