diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 64f2b6dfd..b1fdcc46b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -33,6 +33,8 @@ jobs:
run: ./mvnw clean install -B -q -DskipITs=true
- name: Codecov
uses: codecov/codecov-action@v5.5.1
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload test report for sdk
uses: actions/upload-artifact@v5
with:
diff --git a/spring-boot-examples/workflows/patterns/pom.xml b/spring-boot-examples/workflows/patterns/pom.xml
index 5aa157ed3..4c5dfae72 100644
--- a/spring-boot-examples/workflows/patterns/pom.xml
+++ b/spring-boot-examples/workflows/patterns/pom.xml
@@ -47,6 +47,12 @@
microcks-testcontainers
test
+
+ org.testcontainers
+ testcontainers-postgresql
+ 2.0.1
+ test
+
diff --git a/spring-boot-examples/workflows/patterns/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java b/spring-boot-examples/workflows/patterns/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java
index a9ca48bfb..b49200fb4 100644
--- a/spring-boot-examples/workflows/patterns/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java
+++ b/spring-boot-examples/workflows/patterns/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java
@@ -13,9 +13,10 @@
package io.dapr.springboot.examples.wfp;
+
import io.dapr.testcontainers.Component;
import io.dapr.testcontainers.DaprContainer;
-import io.dapr.testcontainers.DaprLogLevel;
+import io.dapr.testcontainers.WorkflowDashboardContainer;
import io.github.microcks.testcontainers.MicrocksContainersEnsemble;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -26,9 +27,12 @@
import org.springframework.test.context.DynamicPropertyRegistrar;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.Network;
+import org.testcontainers.postgresql.PostgreSQLContainer;
+import org.testcontainers.utility.DockerImageName;
-import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
@@ -45,19 +49,44 @@
@TestConfiguration(proxyBeanMethods = false)
public class DaprTestContainersConfig {
+ Map postgreSQLDetails = new HashMap<>();
+
+ {{
+ postgreSQLDetails.put("host", "postgresql");
+ postgreSQLDetails.put("user", "postgres");
+ postgreSQLDetails.put("password", "postgres");
+ postgreSQLDetails.put("database", "dapr");
+ postgreSQLDetails.put("port", "5432");
+ postgreSQLDetails.put("actorStateStore", String.valueOf(true));
+
+ }}
+
+ private Component stateStoreComponent = new Component("kvstore",
+ "state.postgresql", "v2", postgreSQLDetails);
+
@Bean
@ServiceConnection
- public DaprContainer daprContainer(Network network) {
+ public DaprContainer daprContainer(Network network, PostgreSQLContainer postgreSQLContainer) {
return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
.withAppName("workflow-patterns-app")
- .withComponent(new Component("kvstore", "state.in-memory", "v1", Collections.singletonMap("actorStateStore", String.valueOf(true))))
+ .withComponent(stateStoreComponent)
.withAppPort(8080)
.withNetwork(network)
.withAppHealthCheckPath("/actuator/health")
- .withAppChannelAddress("host.testcontainers.internal");
+ .withAppChannelAddress("host.testcontainers.internal")
+ .dependsOn(postgreSQLContainer);
}
+ @Bean
+ public PostgreSQLContainer postgreSQLContainer(Network network) {
+ return new PostgreSQLContainer(DockerImageName.parse("postgres"))
+ .withNetworkAliases("postgresql")
+ .withDatabaseName("dapr")
+ .withUsername("postgres")
+ .withPassword("postgres")
+ .withNetwork(network);
+ }
@Bean
MicrocksContainersEnsemble microcksEnsemble(Network network) {
@@ -66,6 +95,14 @@ MicrocksContainersEnsemble microcksEnsemble(Network network) {
.withMainArtifacts("third-parties/remote-http-service.yaml");
}
+ @Bean
+ public WorkflowDashboardContainer workflowDashboard(Network network) {
+ return new WorkflowDashboardContainer(WorkflowDashboardContainer.getDefaultImageName())
+ .withNetwork(network)
+ .withStateStoreComponent(stateStoreComponent)
+ .withExposedPorts(8080);
+ }
+
@Bean
public DynamicPropertyRegistrar endpointsProperties(MicrocksContainersEnsemble ensemble) {
// We need to replace the default endpoints with those provided by Microcks.
diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/WorkflowDashboardContainer.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/WorkflowDashboardContainer.java
new file mode 100644
index 000000000..5f77f6798
--- /dev/null
+++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/WorkflowDashboardContainer.java
@@ -0,0 +1,103 @@
+/*
+ * 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.testcontainers;
+
+import io.dapr.testcontainers.converter.ComponentYamlConverter;
+import io.dapr.testcontainers.converter.YamlConverter;
+import io.dapr.testcontainers.converter.YamlMapperFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.images.builder.Transferable;
+import org.testcontainers.utility.DockerImageName;
+import org.yaml.snakeyaml.Yaml;
+
+/**
+ * Test container for Dapr Workflow Dashboard.
+ */
+public class WorkflowDashboardContainer extends GenericContainer {
+ private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowDashboardContainer.class);
+ private static final Yaml YAML_MAPPER = YamlMapperFactory.create();
+ private static final YamlConverter COMPONENT_CONVERTER = new ComponentYamlConverter(YAML_MAPPER);
+ public static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName
+ .parse("ghcr.io/diagridio/diagrid-dashboard:0.0.1");
+ private int dashboardPort = 8080;
+ private Component stateStoreComponent;
+
+ /**
+ * Creates a new Dapr scheduler container.
+ * @param dockerImageName Docker image name.
+ */
+ public WorkflowDashboardContainer(DockerImageName dockerImageName) {
+ super(dockerImageName);
+ dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
+ withExposedPorts(dashboardPort);
+ }
+
+ public WorkflowDashboardContainer withStateStoreComponent(Component stateStoreComponent) {
+ this.stateStoreComponent = stateStoreComponent;
+ return this;
+ }
+
+ /**
+ * Creates a new Dapr schedulers container.
+ * @param image Docker image name.
+ */
+ public WorkflowDashboardContainer(String image) {
+ this(DockerImageName.parse(image));
+ }
+
+ @Override
+ protected void configure() {
+ super.configure();
+ if (stateStoreComponent != null) {
+ String componentYaml = COMPONENT_CONVERTER.convert(stateStoreComponent);
+ withCopyToContainer(Transferable.of(componentYaml), "/app/components/" + stateStoreComponent.getName() + ".yaml");
+ withEnv("COMPONENT_FILE", "/app/components/" + stateStoreComponent.getName() + ".yaml");
+ }
+
+ }
+
+ public static DockerImageName getDefaultImageName() {
+ return DEFAULT_IMAGE_NAME;
+ }
+
+ public WorkflowDashboardContainer withPort(Integer port) {
+ this.dashboardPort = port;
+ return this;
+ }
+
+ @Override
+ public void start() {
+ super.start();
+
+ LOGGER.info("Dapr Workflow Dashboard container started.");
+ LOGGER.info("Access the Dashboard at: http://localhost:{}", this.getMappedPort(dashboardPort));
+ }
+
+ public int getPort() {
+ return dashboardPort;
+ }
+
+ // Required by spotbugs plugin
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+}
diff --git a/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprWorkflowDashboardTest.java b/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprWorkflowDashboardTest.java
new file mode 100644
index 000000000..5b914edcc
--- /dev/null
+++ b/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprWorkflowDashboardTest.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.testcontainers;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class DaprWorkflowDashboardTest {
+
+ @Test
+ public void dashboardTest() {
+ Component stateStoreComponent = new Component("kvstore",
+ "state.in-memory", "v1", Collections.singletonMap("actorStateStore", "true"));
+ try (WorkflowDashboardContainer dashboard =
+ new WorkflowDashboardContainer(WorkflowDashboardContainer.DEFAULT_IMAGE_NAME)
+ .withStateStoreComponent(stateStoreComponent)) {
+ dashboard.configure();
+ assertNotNull(dashboard.getEnvMap().get("COMPONENT_FILE"));
+ assertFalse(dashboard.getEnvMap().get("COMPONENT_FILE").isEmpty());
+ assertEquals(8080, dashboard.getPort());
+
+ }
+ }
+}