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()); + + } + } +}