From 0f13cec12f04104f5133e90e29afe8682b8600bb Mon Sep 17 00:00:00 2001 From: salaboy Date: Mon, 14 Jul 2025 12:12:25 +0100 Subject: [PATCH 1/7] supporting placement and scheduler custom images Signed-off-by: salaboy --- .../io/dapr/testcontainers/DaprContainer.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java index 2a1d905fd0..9459dcd706 100644 --- a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java @@ -68,8 +68,8 @@ public class DaprContainer extends GenericContainer { private String appChannelAddress = "localhost"; private String placementService = "placement"; private String schedulerService = "scheduler"; - private String placementDockerImageName = DAPR_PLACEMENT_IMAGE_TAG; - private String schedulerDockerImageName = DAPR_SCHEDULER_IMAGE_TAG; + private DockerImageName placementDockerImageName = DockerImageName.parse(DAPR_PLACEMENT_IMAGE_TAG); + private DockerImageName schedulerDockerImageName = DockerImageName.parse(DAPR_SCHEDULER_IMAGE_TAG); private Configuration configuration; private DaprPlacementContainer placementContainer; @@ -166,16 +166,26 @@ public DaprContainer withHttpEndpoint(HttpEndpoint httpEndpoint) { return this; } - public DaprContainer withPlacementImage(String placementDockerImageName) { + public DaprContainer withPlacementImage(DockerImageName placementDockerImageName) { this.placementDockerImageName = placementDockerImageName; return this; } - public DaprContainer withSchedulerImage(String schedulerDockerImageName) { + public DaprContainer withPlacementImage(String placementDockerImageName) { + this.placementDockerImageName = DockerImageName.parse(placementDockerImageName); + return this; + } + + public DaprContainer withSchedulerImage(DockerImageName schedulerDockerImageName) { this.schedulerDockerImageName = schedulerDockerImageName; return this; } + public DaprContainer withSchedulerImage(String schedulerDockerImageName) { + this.schedulerDockerImageName = DockerImageName.parse(schedulerDockerImageName); + return this; + } + public DaprContainer withReusablePlacement(boolean shouldReusePlacement) { this.shouldReusePlacement = shouldReusePlacement; return this; From 3fd4b1e54ba557b8a8fe25214c1c8a35ff23c95e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 07:36:45 -0700 Subject: [PATCH 2/7] Bump org.apache.commons:commons-lang3 from 3.9 to 3.18.0 (#1446) Bumps org.apache.commons:commons-lang3 from 3.9 to 3.18.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 dependency-version: 3.18.0 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: artur-ciocanu Signed-off-by: salaboy --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fdb0df5d5f..c10fc9e262 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ 3.9.1 2.1.1 1.3.2 - 3.9 + 3.18.0 1.9.0 2.14.0 3.4.0 From 722a7c53ac8f9ac23e8299e99c883c97c2a6b636 Mon Sep 17 00:00:00 2001 From: Marc Duiker Date: Mon, 14 Jul 2025 18:01:06 +0200 Subject: [PATCH 3/7] Update dapr docs for Hugo upgrade (#1443) Signed-off-by: Marc Duiker Co-authored-by: Cassie Coyle Signed-off-by: salaboy --- .../java-contributing.md | 4 +-- daprdocs/content/en/java-sdk-docs/_index.md | 24 ++++++------- .../en/java-sdk-docs/java-ai/_index.md | 2 +- .../en/java-sdk-docs/java-ai/java-ai-howto.md | 4 +-- .../en/java-sdk-docs/java-client/_index.md | 36 +++++++++---------- .../en/java-sdk-docs/java-jobs/_index.md | 2 +- .../java-jobs/java-jobs-howto.md | 4 +-- .../java-workflow/java-workflow-howto.md | 4 +-- .../en/java-sdk-docs/spring-boot/_index.md | 2 +- 9 files changed, 41 insertions(+), 41 deletions(-) diff --git a/daprdocs/content/en/java-sdk-contributing/java-contributing.md b/daprdocs/content/en/java-sdk-contributing/java-contributing.md index 938f4d7123..03ba6d4e51 100644 --- a/daprdocs/content/en/java-sdk-contributing/java-contributing.md +++ b/daprdocs/content/en/java-sdk-contributing/java-contributing.md @@ -12,14 +12,14 @@ When contributing to the [Java SDK](https://github.com/dapr/java-sdk) the follow The `examples` directory contains code samples for users to run to try out specific functionality of the various Java SDK packages and extensions. When writing new and updated samples keep in mind: -- All examples should be runnable on Windows, Linux, and MacOS. While Java code is consistent among operating systems, any pre/post example commands should provide options through [codetabs]({{< ref "contributing-docs.md#tabbed-content" >}}) +- All examples should be runnable on Windows, Linux, and MacOS. While Java code is consistent among operating systems, any pre/post example commands should provide options through [tabpane]({{% ref "contributing-docs.md#tabbed-content" %}}) - Contain steps to download/install any required pre-requisites. Someone coming in with a fresh OS install should be able to start on the example and complete it without an error. Links to external download pages are fine. ## Docs The `daprdocs` directory contains the markdown files that are rendered into the [Dapr Docs](https://docs.dapr.io) website. When the documentation website is built, this repo is cloned and configured so that its contents are rendered with the docs content. When writing docs, keep in mind: - - All rules in the [docs guide]({{< ref contributing-docs.md >}}) should be followed in addition to these. + - All rules in the [docs guide]({{% ref contributing-docs.md %}}) should be followed in addition to these. - All files and directories should be prefixed with `java-` to ensure all file/directory names are globally unique across all Dapr documentation. ## Github Dapr Bot Commands diff --git a/daprdocs/content/en/java-sdk-docs/_index.md b/daprdocs/content/en/java-sdk-docs/_index.md index b2a0c68570..eb6eab9175 100644 --- a/daprdocs/content/en/java-sdk-docs/_index.md +++ b/daprdocs/content/en/java-sdk-docs/_index.md @@ -15,8 +15,8 @@ Dapr offers a variety of packages to help with the development of Java applicati ## Prerequisites -- [Dapr CLI]({{< ref install-dapr-cli.md >}}) installed -- Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}}) +- [Dapr CLI]({{% ref install-dapr-cli.md %}}) installed +- Initialized [Dapr environment]({{% ref install-dapr-selfhost.md %}}) - JDK 11 or above - the published jars are compatible with Java 8: - [AdoptOpenJDK 11 - LTS](https://adoptopenjdk.net/) - [Oracle's JDK 15](https://www.oracle.com/java/technologies/javase-downloads.html) @@ -30,9 +30,9 @@ Dapr offers a variety of packages to help with the development of Java applicati Next, import the Java SDK packages to get started. Select your preferred build tool to learn how to import. -{{< tabs Maven Gradle >}} +{{< tabpane text=true >}} -{{% codetab %}} +{{% tab header="Maven" %}} For a Maven project, add the following to your `pom.xml` file: @@ -65,9 +65,9 @@ For a Maven project, add the following to your `pom.xml` file: ... ``` -{{% /codetab %}} +{{% /tab %}} -{{% codetab %}} +{{% tab header="Gradle" %}} For a Gradle project, add the following to your `build.gradle` file: @@ -84,9 +84,9 @@ dependencies { } ``` -{{% /codetab %}} +{{% /tab %}} -{{< /tabs >}} +{{< /tabpane >}} If you are also using Spring Boot, you may run into a common issue where the `OkHttp` version that the Dapr SDK uses conflicts with the one specified in the Spring Boot _Bill of Materials_. @@ -106,7 +106,7 @@ Put the Dapr Java SDK to the test. Walk through the Java quickstarts and tutoria | SDK samples | Description | | ----------- | ----------- | -| [Quickstarts]({{< ref quickstarts >}}) | Experience Dapr's API building blocks in just a few minutes using the Java SDK. | +| [Quickstarts]({{% ref quickstarts %}}) | Experience Dapr's API building blocks in just a few minutes using the Java SDK. | | [SDK samples](https://github.com/dapr/java-sdk/tree/master/examples) | Clone the SDK repo to try out some examples and get started. | ```java @@ -122,7 +122,7 @@ try (DaprClient client = (new DaprClientBuilder()).build()) { } ``` -- For a full guide on output bindings visit [How-To: Output bindings]({{< ref howto-bindings.md >}}). +- For a full guide on output bindings visit [How-To: Output bindings]({{% ref howto-bindings.md %}}). - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/bindings/http) for code samples and instructions to try out output bindings. ## Available packages @@ -132,14 +132,14 @@ try (DaprClient client = (new DaprClientBuilder()).build()) {
Client

Create Java clients that interact with a Dapr sidecar and other Dapr applications.

- +
Workflow

Create and manage workflows that work with other Dapr APIs in Java.

- +
diff --git a/daprdocs/content/en/java-sdk-docs/java-ai/_index.md b/daprdocs/content/en/java-sdk-docs/java-ai/_index.md index f0543de56f..904edfc111 100644 --- a/daprdocs/content/en/java-sdk-docs/java-ai/_index.md +++ b/daprdocs/content/en/java-sdk-docs/java-ai/_index.md @@ -3,5 +3,5 @@ type: docs title: "AI" linkTitle: "AI" weight: 3000 -description: With the Dapr Conversation AI package, you can interact with the Dapr AI workloads from a Java application. To get started, walk through the [Dapr AI]({{< ref java-ai-howto.md >}}) how-to guide. +description: With the Dapr Conversation AI package, you can interact with the Dapr AI workloads from a Java application. To get started, walk through the [Dapr AI]({{% ref java-ai-howto.md %}}) how-to guide. --- \ No newline at end of file diff --git a/daprdocs/content/en/java-sdk-docs/java-ai/java-ai-howto.md b/daprdocs/content/en/java-sdk-docs/java-ai/java-ai-howto.md index f28e28c3ac..39970d5218 100644 --- a/daprdocs/content/en/java-sdk-docs/java-ai/java-ai-howto.md +++ b/daprdocs/content/en/java-sdk-docs/java-ai/java-ai-howto.md @@ -101,5 +101,5 @@ component for testing, which simply returns the input message. When integrated with LLMs like OpenAI or Claude, you’ll receive meaningful responses instead of echoed input. ## Next steps -- [Learn more about Conversation AI]({{< ref conversation-overview.md >}}) -- [Conversation AI API reference]({{< ref conversation_api.md >}}) \ No newline at end of file +- [Learn more about Conversation AI]({{% ref conversation-overview.md %}}) +- [Conversation AI API reference]({{% ref conversation_api.md %}}) \ No newline at end of file diff --git a/daprdocs/content/en/java-sdk-docs/java-client/_index.md b/daprdocs/content/en/java-sdk-docs/java-client/_index.md index fec16cdda4..8199824a26 100644 --- a/daprdocs/content/en/java-sdk-docs/java-client/_index.md +++ b/daprdocs/content/en/java-sdk-docs/java-client/_index.md @@ -9,13 +9,13 @@ description: How to get up and running with the Dapr Java SDK The Dapr client package allows you to interact with other Dapr applications from a Java application. {{% alert title="Note" color="primary" %}} -If you haven't already, [try out one of the quickstarts]({{< ref quickstarts >}}) for a quick walk-through on how to use the Dapr Java SDK with an API building block. +If you haven't already, [try out one of the quickstarts]({{% ref quickstarts %}}) for a quick walk-through on how to use the Dapr Java SDK with an API building block. {{% /alert %}} ## Prerequisites -[Complete initial setup and import the Java SDK into your project]({{< ref java >}}) +[Complete initial setup and import the Java SDK into your project]({{% ref java %}}) ## Initializing the client You can initialize a Dapr client as so: @@ -24,7 +24,7 @@ You can initialize a Dapr client as so: DaprClient client = new DaprClientBuilder().build() ``` -This will connect to the default Dapr gRPC endpoint `localhost:50001`. For information about configuring the client using environment variables and system properties, see [Properties]({{< ref properties.md >}}). +This will connect to the default Dapr gRPC endpoint `localhost:50001`. For information about configuring the client using environment variables and system properties, see [Properties]({{% ref properties.md %}}). #### Error Handling @@ -52,7 +52,7 @@ Example of handling the DaprException and consuming the error details when using ## Building blocks -The Java SDK allows you to interface with all of the [Dapr building blocks]({{< ref building-blocks >}}). +The Java SDK allows you to interface with all of the [Dapr building blocks]({{% ref building-blocks %}}). ### Invoke a service @@ -76,7 +76,7 @@ try (DaprClient client = (new DaprClientBuilder()).build()) { } ``` -- For a full guide on service invocation visit [How-To: Invoke a service]({{< ref howto-invoke-discover-services.md >}}). +- For a full guide on service invocation visit [How-To: Invoke a service]({{% ref howto-invoke-discover-services.md %}}). - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/invoke) for code samples and instructions to try out service invocation ### Save & get application state @@ -99,7 +99,7 @@ try (DaprClient client = (new DaprClientBuilder()).build()) { } ``` -- For a full list of state operations visit [How-To: Get & save state]({{< ref howto-get-save-state.md >}}). +- For a full list of state operations visit [How-To: Get & save state]({{% ref howto-get-save-state.md %}}). - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/state) for code samples and instructions to try out state management ### Publish & subscribe to messages @@ -225,7 +225,7 @@ class Solution { } ``` -- For a full guide on publishing messages and subscribing to a topic [How-To: Publish & subscribe]({{< ref howto-publish-subscribe.md >}}). +- For a full guide on publishing messages and subscribing to a topic [How-To: Publish & subscribe]({{% ref howto-publish-subscribe.md %}}). - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/pubsub/http) for code samples and instructions to try out pub/sub ### Interact with output bindings @@ -243,7 +243,7 @@ try (DaprClient client = (new DaprClientBuilder()).build()) { } ``` -- For a full guide on output bindings visit [How-To: Output bindings]({{< ref howto-bindings.md >}}). +- For a full guide on output bindings visit [How-To: Output bindings]({{% ref howto-bindings.md %}}). - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/bindings/http) for code samples and instructions to try out output bindings. ### Interact with input bindings @@ -265,7 +265,7 @@ public class myClass { } ``` -- For a full guide on input bindings, visit [How-To: Input bindings]({{< ref howto-triggers >}}). +- For a full guide on input bindings, visit [How-To: Input bindings]({{% ref howto-triggers %}}). - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/bindings/http) for code samples and instructions to try out input bindings. ### Retrieve secrets @@ -282,7 +282,7 @@ try (DaprClient client = (new DaprClientBuilder()).build()) { } ``` -- For a full guide on secrets visit [How-To: Retrieve secrets]({{< ref howto-secrets.md >}}). +- For a full guide on secrets visit [How-To: Retrieve secrets]({{% ref howto-secrets.md %}}). - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/secrets) for code samples and instructions to try out retrieving secrets ### Actors @@ -308,7 +308,7 @@ public interface DemoActor { } ``` -- For a full guide on actors visit [How-To: Use virtual actors in Dapr]({{< ref howto-actors.md >}}). +- For a full guide on actors visit [How-To: Use virtual actors in Dapr]({{% ref howto-actors.md %}}). - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/actors) for code samples and instructions to try actors ### Get & Subscribe to application configurations @@ -341,7 +341,7 @@ try (DaprPreviewClient client = (new DaprClientBuilder()).buildPreviewClient()) } ``` -- For a full list of configuration operations visit [How-To: Manage configuration from a store]({{< ref howto-manage-configuration.md >}}). +- For a full list of configuration operations visit [How-To: Manage configuration from a store]({{% ref howto-manage-configuration.md %}}). - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/configuration) for code samples and instructions to try out different configuration operations. ### Query saved state @@ -411,7 +411,7 @@ try (DaprClient client = builder.build(); DaprPreviewClient previewClient = buil } } ``` -- For a full how-to on query state, visit [How-To: Query state]({{< ref howto-state-query-api.md >}}). +- For a full how-to on query state, visit [How-To: Query state]({{% ref howto-state-query-api.md %}}). - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/querystate) for complete code sample. ### Distributed lock @@ -477,7 +477,7 @@ public class DistributedLockGrpcClient { } ``` -- For a full how-to on distributed lock, visit [How-To: Use a Lock]({{< ref howto-use-distributed-lock.md >}}) +- For a full how-to on distributed lock, visit [How-To: Use a Lock]({{% ref howto-use-distributed-lock.md %}}) - Visit [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/lock) for complete code sample. ### Workflow @@ -598,9 +598,9 @@ public class DemoWorkflowClient { ``` - For a full guide on workflows, visit: - - [How-To: Author workflows]({{< ref howto-author-workflow.md >}}). - - [How-To: Manage workflows]({{< ref howto-manage-workflow.md >}}). -- [Learn more about how to use workflows with the Java SDK]({{< ref java-workflow.md >}}). + - [How-To: Author workflows]({{% ref howto-author-workflow.md %}}). + - [How-To: Manage workflows]({{% ref howto-manage-workflow.md %}}). +- [Learn more about how to use workflows with the Java SDK]({{% ref java-workflow.md %}}). ## Sidecar APIs @@ -635,4 +635,4 @@ Learn more about the [Dapr Java SDK packages available to add to your Java appli ## Related links - [Java SDK examples](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples) -For a full list of SDK properties and how to configure them, visit [Properties]({{< ref properties.md >}}). +For a full list of SDK properties and how to configure them, visit [Properties]({{% ref properties.md %}}). diff --git a/daprdocs/content/en/java-sdk-docs/java-jobs/_index.md b/daprdocs/content/en/java-sdk-docs/java-jobs/_index.md index 40c1a23ebf..9d017f7770 100644 --- a/daprdocs/content/en/java-sdk-docs/java-jobs/_index.md +++ b/daprdocs/content/en/java-sdk-docs/java-jobs/_index.md @@ -3,5 +3,5 @@ type: docs title: "Jobs" linkTitle: "Jobs" weight: 3000 -description: With the Dapr Jobs package, you can interact with the Dapr Jobs APIs from a Java application to trigger future operations to run according to a predefined schedule with an optional payload. To get started, walk through the [Dapr Jobs]({{< ref java-jobs-howto.md >}}) how-to guide. +description: With the Dapr Jobs package, you can interact with the Dapr Jobs APIs from a Java application to trigger future operations to run according to a predefined schedule with an optional payload. To get started, walk through the [Dapr Jobs]({{% ref java-jobs-howto.md %}}) how-to guide. --- diff --git a/daprdocs/content/en/java-sdk-docs/java-jobs/java-jobs-howto.md b/daprdocs/content/en/java-sdk-docs/java-jobs/java-jobs-howto.md index f68a2f8d59..e7c6346289 100644 --- a/daprdocs/content/en/java-sdk-docs/java-jobs/java-jobs-howto.md +++ b/daprdocs/content/en/java-sdk-docs/java-jobs/java-jobs-howto.md @@ -160,5 +160,5 @@ public class DemoJobsClient { ``` ## Next steps -- [Learn more about Jobs]({{< ref jobs-overview.md >}}) -- [Jobs API reference]({{< ref jobs_api.md >}}) \ No newline at end of file +- [Learn more about Jobs]({{% ref jobs-overview.md %}}) +- [Jobs API reference]({{% ref jobs_api.md %}}) \ No newline at end of file diff --git a/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md b/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md index 2acf112527..1c12aa50c2 100644 --- a/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md +++ b/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md @@ -245,5 +245,5 @@ Exiting DemoWorkflowClient. 1. The worfklow client is then exited. ## Next steps -- [Learn more about Dapr workflow]({{< ref workflow-overview.md >}}) -- [Workflow API reference]({{< ref workflow_api.md >}}) \ No newline at end of file +- [Learn more about Dapr workflow]({{% ref workflow-overview.md %}}) +- [Workflow API reference]({{% ref workflow_api.md %}}) \ No newline at end of file diff --git a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md index b1aad76111..a0edba4f22 100644 --- a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md +++ b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md @@ -122,7 +122,7 @@ Besides the previous configuration (`DaprTestContainersConfig`) your tests shoul ## Leveraging Spring & Spring Boot programming model with Dapr -The Java SDK allows you to interface with all of the [Dapr building blocks]({{< ref building-blocks >}}). +The Java SDK allows you to interface with all of the [Dapr building blocks]({{% ref building-blocks %}}). But if you want to leverage the Spring and Spring Boot programming model you can use the `dapr-spring-boot-starter` integration. This includes implementations of Spring Data (`KeyValueTemplate` and `CrudRepository`) as well as a `DaprMessagingTemplate` for producing and consuming messages (similar to [Spring Kafka](https://spring.io/projects/spring-kafka), [Spring Pulsar](https://spring.io/projects/spring-pulsar) and [Spring AMQP for RabbitMQ](https://spring.io/projects/spring-amqp)) and Dapr workflows. From 31e9c29e502053ea0e215235355dec9a75b9c308 Mon Sep 17 00:00:00 2001 From: Josh van Leeuwen Date: Mon, 14 Jul 2025 14:02:01 -0300 Subject: [PATCH 4/7] Adds note about workflow start time (#1444) Signed-off-by: joshvanl Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Co-authored-by: artur-ciocanu Signed-off-by: salaboy --- .../java/io/dapr/workflows/client/NewWorkflowOptions.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/NewWorkflowOptions.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/NewWorkflowOptions.java index e772835505..f808c19024 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/client/NewWorkflowOptions.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/NewWorkflowOptions.java @@ -63,8 +63,10 @@ public NewWorkflowOptions setInput(Object input) { /** * Sets the start time of the new workflow. * - *

By default, new workflow instances start executing immediately. This method can be used - * to start them at a specific time in the future. + *

By default, new workflow instances start executing immediately. This + * method can be used to start them at a specific time in the future. If set, + * Dapr will not wait for the workflow to "start" which can improve + * throughput of creating many workflows. * * @param startTime the start time of the new workflow * @return this {@link NewWorkflowOptions} object From e94efdd3511cf9122d757724655d996fdb3f2663 Mon Sep 17 00:00:00 2001 From: salaboy Date: Tue, 15 Jul 2025 10:43:15 +0100 Subject: [PATCH 5/7] adding test to validate canonical names with substitutes Signed-off-by: salaboy --- .../io/dapr/testcontainers/DaprContainer.java | 31 +++++++++++++------ .../testcontainers/DaprContainerTest.java | 29 +++++++++++++++++ 2 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprContainerTest.java diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java index 9459dcd706..3ed80f0a29 100644 --- a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java @@ -56,10 +56,10 @@ public class DaprContainer extends GenericContainer { private static final YamlConverter SUBSCRIPTION_CONVERTER = new SubscriptionYamlConverter(YAML_MAPPER); private static final YamlConverter HTTPENDPOINT_CONVERTER = new HttpEndpointYamlConverter(YAML_MAPPER); private static final YamlConverter CONFIGURATION_CONVERTER = new ConfigurationYamlConverter( - YAML_MAPPER); + YAML_MAPPER); private static final WaitStrategy WAIT_STRATEGY = Wait.forHttp("/v1.0/healthz/outbound") - .forPort(DAPRD_DEFAULT_HTTP_PORT) - .forStatusCodeMatching(statusCode -> statusCode >= 200 && statusCode <= 399); + .forPort(DAPRD_DEFAULT_HTTP_PORT) + .forStatusCodeMatching(statusCode -> statusCode >= 200 && statusCode <= 399); private final Set components = new HashSet<>(); private final Set subscriptions = new HashSet<>(); @@ -82,6 +82,7 @@ public class DaprContainer extends GenericContainer { /** * Creates a new Dapr container. + * * @param dockerImageName Docker image name. */ public DaprContainer(DockerImageName dockerImageName) { @@ -94,6 +95,7 @@ public DaprContainer(DockerImageName dockerImageName) { /** * Creates a new Dapr container. + * * @param image Docker image name. */ public DaprContainer(String image) { @@ -213,6 +215,7 @@ public DaprContainer withComponent(Component component) { /** * Adds a Dapr component from a YAML file. + * * @param path Path to the YAML file. * @return This container. */ @@ -227,7 +230,7 @@ public DaprContainer withComponent(Path path) { String type = (String) spec.get("type"); String version = (String) spec.get("version"); List> specMetadata = - (List>) spec.getOrDefault("metadata", Collections.emptyList()); + (List>) spec.getOrDefault("metadata", Collections.emptyList()); ArrayList metadataEntries = new ArrayList<>(); @@ -268,17 +271,17 @@ protected void configure() { if (this.placementContainer == null) { this.placementContainer = new DaprPlacementContainer(this.placementDockerImageName) - .withNetwork(getNetwork()) - .withNetworkAliases(placementService) - .withReuse(this.shouldReusePlacement); + .withNetwork(getNetwork()) + .withNetworkAliases(placementService) + .withReuse(this.shouldReusePlacement); this.placementContainer.start(); } if (this.schedulerContainer == null) { this.schedulerContainer = new DaprSchedulerContainer(this.schedulerDockerImageName) - .withNetwork(getNetwork()) - .withNetworkAliases(schedulerService) - .withReuse(this.shouldReuseScheduler); + .withNetwork(getNetwork()) + .withNetworkAliases(schedulerService) + .withReuse(this.shouldReuseScheduler); this.schedulerContainer.start(); } @@ -394,6 +397,14 @@ public static DockerImageName getDefaultImageName() { return DEFAULT_IMAGE_NAME; } + public DockerImageName getPlacementDockerImageName() { + return placementDockerImageName; + } + + public DockerImageName getSchedulerDockerImageName() { + return schedulerDockerImageName; + } + // Required by spotbugs plugin @Override public boolean equals(Object o) { diff --git a/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprContainerTest.java b/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprContainerTest.java new file mode 100644 index 0000000000..a6d1053fcb --- /dev/null +++ b/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprContainerTest.java @@ -0,0 +1,29 @@ +package io.dapr.testcontainers; + +import org.junit.jupiter.api.Test; +import org.testcontainers.utility.DockerImageName; + +import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DaprContainerTest { + + @Test + public void schedulerAndPlacementCustomImagesTest() { + + DaprContainer dapr = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("dapr-app") + .withSchedulerImage(DockerImageName.parse("custom/scheduler:1.15.4") + .asCompatibleSubstituteFor("daprio/scheduler:1.15.4")) + .withPlacementImage(DockerImageName.parse("custom/placement:1.15.4") + .asCompatibleSubstituteFor("daprio/placement:1.15.4")) + .withAppPort(8081) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withAppChannelAddress("host.testcontainers.internal"); + + + assertEquals("custom/placement:1.15.4", dapr.getPlacementDockerImageName().asCanonicalNameString()); + assertEquals("custom/scheduler:1.15.4", dapr.getSchedulerDockerImageName().asCanonicalNameString()); + + } +} From ee3babb8e7866b527d7608ee35645630e05113a8 Mon Sep 17 00:00:00 2001 From: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com> Date: Mon, 14 Jul 2025 19:00:42 -0300 Subject: [PATCH 6/7] Migrate PubSub removing flaky test (#1407) * Migrate PubSub removing flaky test Signed-off-by: Matheus Cruz * Adjust assertion Signed-off-by: Matheus Cruz * Change assert Signed-off-by: Matheus Cruz * Apply pull request suggestions Signed-off-by: Matheus Cruz * Use custom ObjectSerializer Signed-off-by: Matheus Cruz --------- Signed-off-by: Matheus Cruz Co-authored-by: artur-ciocanu Signed-off-by: salaboy --- .../it/testcontainers/DaprClientFactory.java | 14 + .../pubsub/http/DaprPubSubIT.java | 633 ++++++++++++++++++ .../pubsub/http/SubscriberController.java | 274 ++++++++ .../pubsub/http/TestPubSubApplication.java | 23 + 4 files changed, 944 insertions(+) create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprClientFactory.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/DaprPubSubIT.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/SubscriberController.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/TestPubSubApplication.java diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprClientFactory.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprClientFactory.java new file mode 100644 index 0000000000..2a706af3c0 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprClientFactory.java @@ -0,0 +1,14 @@ +package io.dapr.it.testcontainers; + +import io.dapr.client.DaprClientBuilder; +import io.dapr.config.Properties; +import io.dapr.testcontainers.DaprContainer; + +public interface DaprClientFactory { + + static DaprClientBuilder createDaprClientBuilder(DaprContainer daprContainer) { + return new DaprClientBuilder() + .withPropertyOverride(Properties.HTTP_ENDPOINT, "http://localhost:" + daprContainer.getHttpPort()) + .withPropertyOverride(Properties.GRPC_ENDPOINT, "http://localhost:" + daprContainer.getGrpcPort()); + } +} 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 new file mode 100644 index 0000000000..8d7111952d --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/DaprPubSubIT.java @@ -0,0 +1,633 @@ +/* + * 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 com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.dapr.client.DaprClient; +import io.dapr.client.DaprPreviewClient; +import io.dapr.client.domain.BulkPublishEntry; +import io.dapr.client.domain.BulkPublishRequest; +import io.dapr.client.domain.BulkPublishResponse; +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; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; +import io.dapr.utils.TypeRef; +import org.assertj.core.api.SoftAssertions; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +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 io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, + classes = { + TestPubSubApplication.class + } +) +@Testcontainers +@Tag("testcontainers") +public class DaprPubSubIT { + + private static final Logger LOG = LoggerFactory.getLogger(DaprPubSubIT.class); + private static final Network DAPR_NETWORK = Network.newNetwork(); + private static final Random RANDOM = new Random(); + private static final int PORT = RANDOM.nextInt(1000) + 8000; + private static final String APP_FOUND_MESSAGE_PATTERN = ".*application discovered on port.*"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final String PUBSUB_APP_ID = "pubsub-dapr-app"; + private static final String PUBSUB_NAME = "pubsub"; + + // topics + private static final String TOPIC_BULK = "testingbulktopic"; + private static final String TOPIC_NAME = "testingtopic"; + private static final String ANOTHER_TOPIC_NAME = "anothertopic"; + private static final String TYPED_TOPIC_NAME = "typedtestingtopic"; + 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 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 = + new TypeRef<>() { + }; + private static final TypeRef>> CLOUD_EVENT_MYOBJECT_LIST_TYPE_REF = + new TypeRef<>() { + }; + + @Container + private static final DaprContainer DAPR_CONTAINER = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName(PUBSUB_APP_ID) + .withNetwork(DAPR_NETWORK) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withLogConsumer(outputFrame -> LOG.info(outputFrame.getUtf8String())) + .withAppChannelAddress("host.testcontainers.internal") + .withAppPort(PORT); + + /** + * 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); + } + + + @BeforeEach + public void setUp() { + org.testcontainers.Testcontainers.exposeHostPorts(PORT); + } + + @Test + @DisplayName("Should receive INVALID_ARGUMENT when the specified Pub/Sub name does not exist") + public void shouldReceiveInvalidArgument() throws Exception { + Wait.forLogMessage(APP_FOUND_MESSAGE_PATTERN, 1).waitUntilReady(DAPR_CONTAINER); + + try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build()) { + assertThrowsDaprExceptionWithReason( + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: pubsub unknown pubsub is not found", + "DAPR_PUBSUB_NOT_FOUND", + () -> client.publishEvent("unknown pubsub", "mytopic", "payload").block()); + } + } + + @Test + @DisplayName("Should receive INVALID_ARGUMENT using bulk publish when the specified Pub/Sub name does not exist") + public void shouldReceiveInvalidArgumentWithBulkPublish() throws Exception { + try (DaprPreviewClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).buildPreviewClient()) { + assertThrowsDaprException( + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: pubsub unknown pubsub is not found", + () -> client.publishEvents("unknown pubsub", "mytopic", "text/plain", "message").block()); + } + } + + @Test + @DisplayName("Should publish some payload types successfully") + public void shouldPublishSomePayloadTypesWithNoError() throws Exception { + + try ( + DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build(); + DaprPreviewClient previewClient = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).withObjectSerializer( + createJacksonObjectSerializer()) + .buildPreviewClient() + ) { + + publishBulkStringsAsserting(previewClient); + + publishMyObjectAsserting(previewClient); + + publishByteAsserting(previewClient); + + publishCloudEventAsserting(previewClient); + + Thread.sleep(10000); + + callWithRetry(() -> validatePublishedMessages(client), 2000); + } + } + + @Test + @DisplayName("Should publish various payload types to different topics") + public void testPubSub() throws Exception { + + // Send a batch of messages on one topic + try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).withObjectSerializer( + createJacksonObjectSerializer() + ).build()) { + + sendBulkMessagesAsText(client, TOPIC_NAME); + + sendBulkMessagesAsText(client, ANOTHER_TOPIC_NAME); + + //Publishing an object. + PubSubIT.MyObject object = new PubSubIT.MyObject(); + object.setId("123"); + client.publishEvent(PUBSUB_NAME, TOPIC_NAME, object).block(); + LOG.info("Published one object."); + + client.publishEvent(PUBSUB_NAME, TYPED_TOPIC_NAME, object).block(); + LOG.info("Published another object."); + + //Publishing a single byte: Example of non-string based content published + publishOneByteSync(client, TOPIC_NAME); + + 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(); + LOG.info("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(); + LOG.info("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(); + LOG.info("Published one cloud event for v3."); + } + + Thread.sleep(2000); + + callWithRetry(() -> { + LOG.info("Checking results for topic " + TOPIC_NAME); + + List messages = client.invokeMethod( + PUBSUB_APP_ID, + "messages/testingtopic", + null, + HttpExtension.GET, + CLOUD_EVENT_LIST_TYPE_REF + ).block(); + + assertThat(messages) + .hasSize(13) + .extracting(CloudEvent::getData) + .filteredOn(Objects::nonNull) + .contains( + "AQ==", + "message from cloudevent" + ); + + for (int i = 0; i < NUM_MESSAGES; i++) { + String expectedMessage = String.format("This is message #%d on topic %s", i, TOPIC_NAME); + assertThat(messages) + .extracting(CloudEvent::getData) + .filteredOn(Objects::nonNull) + .anyMatch(expectedMessage::equals); + } + + assertThat(messages) + .extracting(CloudEvent::getData) + .filteredOn(LinkedHashMap.class::isInstance) + .map(data -> (String) ((LinkedHashMap) data).get("id")) + .contains("123"); + }, 2000); + + callWithRetry(() -> { + LOG.info("Checking results for topic " + TOPIC_NAME + " V2"); + + List messages = client.invokeMethod( + PUBSUB_APP_ID, + "messages/testingtopicV2", + null, + HttpExtension.GET, + CLOUD_EVENT_LIST_TYPE_REF + ).block(); + + assertThat(messages) + .hasSize(1); + }, 2000); + + callWithRetry(() -> { + LOG.info("Checking results for topic " + TOPIC_NAME + " V3"); + + List messages = client.invokeMethod( + PUBSUB_APP_ID, + "messages/testingtopicV3", + null, + HttpExtension.GET, + CLOUD_EVENT_LIST_TYPE_REF + ).block(); + + assertThat(messages) + .hasSize(1); + }, 2000); + + callWithRetry(() -> { + LOG.info("Checking results for topic " + TYPED_TOPIC_NAME); + + List> messages = client.invokeMethod( + PUBSUB_APP_ID, + "messages/typedtestingtopic", + null, + HttpExtension.GET, + CLOUD_EVENT_MYOBJECT_LIST_TYPE_REF + ).block(); + + assertThat(messages) + .extracting(CloudEvent::getData) + .filteredOn(Objects::nonNull) + .filteredOn(PubSubIT.MyObject.class::isInstance) + .map(PubSubIT.MyObject::getId) + .contains("123"); + }, 2000); + + callWithRetry(() -> { + LOG.info("Checking results for topic " + ANOTHER_TOPIC_NAME); + + List messages = client.invokeMethod( + PUBSUB_APP_ID, + "messages/anothertopic", + null, + HttpExtension.GET, + CLOUD_EVENT_LIST_TYPE_REF + ).block(); + + assertThat(messages) + .hasSize(10); + + for (int i = 0; i < NUM_MESSAGES; i++) { + String expectedMessage = String.format("This is message #%d on topic %s", i, ANOTHER_TOPIC_NAME); + assertThat(messages) + .extracting(CloudEvent::getData) + .filteredOn(Objects::nonNull) + .anyMatch(expectedMessage::equals); + } + }, 2000); + + } + } + + @Test + @DisplayName("Should publish binary payload type successfully") + public void shouldPublishBinary() throws Exception { + + DaprObjectSerializer serializer = createBinaryObjectSerializer(); + + try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).withObjectSerializer(serializer).build()) { + publishOneByteSync(client, BINARY_TOPIC_NAME); + } + + Thread.sleep(3000); + + try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build()) { + callWithRetry(() -> { + LOG.info("Checking results for topic " + BINARY_TOPIC_NAME); + final List messages = client.invokeMethod( + PUBSUB_APP_ID, + "messages/binarytopic", + null, + HttpExtension.GET, CLOUD_EVENT_LIST_TYPE_REF).block(); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(messages.size()).isEqualTo(1); + softly.assertThat(messages.get(0).getData()).isNull(); + softly.assertThat(messages.get(0).getBinaryData()).isEqualTo(new byte[] {1}); + }); + }, 2000); + } + } + + private static void publishOneByteSync(DaprClient client, String topicName) { + client.publishEvent( + PUBSUB_NAME, + topicName, + new byte[] {1}).block(); + } + + private static void sendBulkMessagesAsText(DaprClient client, String topicName) { + for (int i = 0; i < NUM_MESSAGES; i++) { + String message = String.format("This is message #%d on topic %s", i, topicName); + client.publishEvent(PUBSUB_NAME, topicName, message).block(); + } + } + + private void publishMyObjectAsserting(DaprPreviewClient previewClient) { + PubSubIT.MyObject object = new PubSubIT.MyObject(); + object.setId("123"); + BulkPublishResponse response = previewClient.publishEvents( + PUBSUB_NAME, + TOPIC_BULK, + "application/json", + Collections.singletonList(object) + ).block(); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(response).isNotNull(); + softly.assertThat(response.getFailedEntries().size()).isZero(); + }); + } + + private void publishBulkStringsAsserting(DaprPreviewClient previewClient) { + 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)); + } + BulkPublishResponse response = previewClient.publishEvents(PUBSUB_NAME, TOPIC_BULK, "", messages).block(); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(response).isNotNull(); + softly.assertThat(response.getFailedEntries().size()).isZero(); + }); + } + + private void publishByteAsserting(DaprPreviewClient previewClient) { + BulkPublishResponse response = previewClient.publishEvents( + PUBSUB_NAME, + TOPIC_BULK, + "", + Collections.singletonList(new byte[] {1}) + ).block(); + SoftAssertions.assertSoftly(softly -> { + assertThat(response).isNotNull(); + softly.assertThat(response.getFailedEntries().size()).isZero(); + }); + } + + private void publishCloudEventAsserting(DaprPreviewClient previewClient) { + 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) + ) + ); + BulkPublishResponse> response = previewClient.publishEvents(req).block(); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(response).isNotNull(); + softly.assertThat(response.getFailedEntries().size()).isZero(); + }); + } + + private void validatePublishedMessages(DaprClient client) { + List cloudEventMessages = client.invokeMethod( + PUBSUB_APP_ID, + "messages/redis/testingbulktopic", + null, + HttpExtension.GET, + CLOUD_EVENT_LIST_TYPE_REF + ).block(); + + assertThat(cloudEventMessages) + .as("expected non-null list of cloud events") + .isNotNull() + .hasSize(13); + + for (int i = 0; i < NUM_MESSAGES; i++) { + String expectedMessage = String.format("This is message #%d on topic %s", i, TOPIC_BULK); + assertThat(cloudEventMessages) + .as("expected text payload to match for message %d", i) + .anySatisfy(event -> assertThat(event.getData()).isEqualTo(expectedMessage)); + } + + assertThat(cloudEventMessages) + .filteredOn(event -> event.getData() instanceof LinkedHashMap) + .map(event -> (LinkedHashMap) event.getData()) + .anySatisfy(map -> assertThat(map.get("id")).isEqualTo("123")); + + assertThat(cloudEventMessages) + .map(CloudEvent::getData) + .anySatisfy(data -> assertThat(data).isEqualTo("AQ==")); + + assertThat(cloudEventMessages) + .map(CloudEvent::getData) + .anySatisfy(data -> assertThat(data).isEqualTo("message from cloudevent")); + } + + @Test + @DisplayName("Should publish with TTL") + public void testPubSubTTLMetadata() throws Exception { + + // Send a batch of messages on one topic, all to be expired in 1 second. + 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, TTL_TOPIC_NAME); + //Publishing messages + client.publishEvent( + PUBSUB_NAME, + TTL_TOPIC_NAME, + message, + Map.of(Metadata.TTL_IN_SECONDS, "1")) + .block(); + LOG.info("Published message: '{}' to topic '{}' pubsub_name '{}'\n", message, TOPIC_NAME, PUBSUB_NAME); + } + } + + // Sleeps for two seconds to let them expire. + Thread.sleep(2000); + + try (DaprClient client = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build()) { + callWithRetry(() -> { + LOG.info("Checking results for topic " + TTL_TOPIC_NAME); + final List + messages = client.invokeMethod(PUBSUB_APP_ID, "messages/" + TTL_TOPIC_NAME, null, HttpExtension.GET, List.class).block(); + assertThat(messages).hasSize(0); + }, 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; + for (int i = 0; i < NUM_MESSAGES - 1; i++) { + do { + val = new PubSubIT.ConvertToLong().setVal(random.nextLong()); + } while (values.contains(val)); + values.add(val); + } + 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(); + LOG.info("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 = DaprClientFactory.createDaprClientBuilder(DAPR_CONTAINER).build()) { + callWithRetry(() -> { + LOG.info("Checking results for topic " + LONG_TOPIC_NAME); + 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) { + actual.add(message.getData()); + } + assertThat(values).containsAll(actual); + }, 2000); + } + } + + private @NotNull DaprObjectSerializer createBinaryObjectSerializer() { + return 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"; + } + }; + } + + private DaprObjectSerializer createJacksonObjectSerializer() { + return 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 OBJECT_MAPPER.readValue(data, OBJECT_MAPPER.constructType(type.getType())); + } + + @Override + public String getContentType() { + return "application/json"; + } + }; + } +} 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 new file mode 100644 index 0000000000..30e9204018 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/SubscriberController.java @@ -0,0 +1,274 @@ +/* + * 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.testcontainers.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.it.pubsub.http.PubSubIT; +import io.dapr.springboot.annotations.BulkSubscribe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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<>()); + private static final Logger LOG = LoggerFactory.getLogger(SubscriberController.class); + + @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() { + LOG.info("res size: " + responsesReceivedTestingTopicBulkSub.size()); + return responsesReceivedTestingTopicBulkSub; + } + + @Topic(name = "testingtopic", pubsubName = "pubsub") + @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(); + LOG.info("Testing topic Subscriber got message: " + message + "; Content-type: " + contentType); + messagesReceivedTestingTopic.add(envelope); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @Topic(name = "testingbulktopic", pubsubName = "pubsub") + @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(); + LOG.info("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 = "pubsub", + 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 = "pubsub", + 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(); + LOG.info("Testing topic Subscriber got message: " + message + "; Content-type: " + contentType); + messagesReceivedTestingTopicV3.add(envelope); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @Topic(name = "typedtestingtopic", pubsubName = "pubsub") + @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(); + LOG.info("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 = "pubsub") + @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(); + LOG.info("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:pubsub}") + @PostMapping(path = "/route3") + public Mono handleMessageAnotherTopic(@RequestBody(required = false) CloudEvent envelope) { + return Mono.fromRunnable(() -> { + try { + String message = envelope.getData() == null ? "" : envelope.getData().toString(); + LOG.info("Another topic Subscriber got message: " + message); + messagesByTopic.compute("anothertopic", merge(envelope)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @PostMapping(path = "/route4") + public Mono handleMessageTTLTopic(@RequestBody(required = false) CloudEvent envelope) { + return Mono.fromRunnable(() -> { + try { + String message = envelope.getData() == null ? "" : envelope.getData().toString(); + LOG.info("TTL topic Subscriber got message: " + message); + messagesByTopic.compute("ttltopic", merge(envelope)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @Topic(name = "testinglongvalues", pubsubName = "pubsub") + @PostMapping(path = "/testinglongvalues") + public Mono handleMessageLongValues(@RequestBody(required = false) CloudEvent cloudEvent) { + return Mono.fromRunnable(() -> { + try { + Long message = cloudEvent.getData().getValue(); + LOG.info("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 = 100) + @Topic(name = "topicBulkSub", pubsubName = "pubsub") + @PostMapping(path = "/routeBulkSub") + public Mono handleMessageBulk( + @RequestBody(required = false) BulkSubscribeMessage> bulkMessage) { + return Mono.fromCallable(() -> { + LOG.info("bulkMessage: " + bulkMessage.getEntries().size()); + + if (bulkMessage.getEntries().size() == 0) { + BulkSubscribeAppResponse response = new BulkSubscribeAppResponse(new ArrayList<>()); + responsesReceivedTestingTopicBulkSub.add(response); + System.out.println("res size: " + responsesReceivedTestingTopicBulkSub.size()); + return response; + } + + List entries = new ArrayList<>(); + for (BulkSubscribeMessageEntry entry: bulkMessage.getEntries()) { + try { + LOG.info("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); + LOG.info("res size: " + responsesReceivedTestingTopicBulkSub.size()); + + 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/testcontainers/pubsub/http/TestPubSubApplication.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/TestPubSubApplication.java new file mode 100644 index 0000000000..2ed550cac7 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/pubsub/http/TestPubSubApplication.java @@ -0,0 +1,23 @@ +/* + * 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 org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TestPubSubApplication { + public static void main(String[] args) { + SpringApplication.run(TestPubSubApplication.class, args); + } +} From 9257432d9fa08bd5769b46ba93fb287651045cfe Mon Sep 17 00:00:00 2001 From: salaboy Date: Tue, 15 Jul 2025 15:53:31 +0100 Subject: [PATCH 7/7] adding tests for coverage Signed-off-by: salaboy --- .../dapr/testcontainers/DaprContainerTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprContainerTest.java b/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprContainerTest.java index a6d1053fcb..680b1f4eef 100644 --- a/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprContainerTest.java +++ b/testcontainers-dapr/src/test/java/io/dapr/testcontainers/DaprContainerTest.java @@ -26,4 +26,21 @@ public void schedulerAndPlacementCustomImagesTest() { assertEquals("custom/scheduler:1.15.4", dapr.getSchedulerDockerImageName().asCanonicalNameString()); } + + @Test + public void schedulerAndPlacementCustomImagesStringTest() { + + DaprContainer dapr = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("dapr-app") + .withSchedulerImage("daprio/scheduler:1.15.4") + .withPlacementImage("daprio/placement:1.15.4") + .withAppPort(8081) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withAppChannelAddress("host.testcontainers.internal"); + + + assertEquals("daprio/placement:1.15.4", dapr.getPlacementDockerImageName().asCanonicalNameString()); + assertEquals("daprio/scheduler:1.15.4", dapr.getSchedulerDockerImageName().asCanonicalNameString()); + + } }