Skip to content

Commit 9762d4a

Browse files
authored
Introduce query methods to DSL (#1312)
* Introduce query methods to FuncHttp Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com> * Apply pull request suggestions Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com> --------- Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
1 parent e898756 commit 9762d4a

5 files changed

Lines changed: 334 additions & 3 deletions

File tree

experimental/test/pom.xml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
</parent>
88
<artifactId>serverlessworkflow-experimental-test</artifactId>
99
<name>Serverless Workflow :: Experimental :: Test</name>
10+
<properties>
11+
<version.org.glassfish.jersey>4.0.2</version.org.glassfish.jersey>
12+
</properties>
1013
<dependencies>
1114
<dependency>
1215
<groupId>org.junit.jupiter</groupId>
@@ -43,11 +46,43 @@
4346
<artifactId>serverlessworkflow-experimental-lambda</artifactId>
4447
<scope>test</scope>
4548
</dependency>
49+
<dependency>
50+
<groupId>io.serverlessworkflow</groupId>
51+
<artifactId>serverlessworkflow-impl-http</artifactId>
52+
<version>${project.version}</version>
53+
<scope>test</scope>
54+
</dependency>
4655
<dependency>
4756
<groupId>io.serverlessworkflow</groupId>
4857
<artifactId>serverlessworkflow-impl-model</artifactId>
4958
<version>${project.version}</version>
5059
<scope>test</scope>
5160
</dependency>
61+
<dependency>
62+
<groupId>com.squareup.okhttp3</groupId>
63+
<artifactId>mockwebserver</artifactId>
64+
</dependency>
65+
<dependency>
66+
<groupId>io.serverlessworkflow</groupId>
67+
<artifactId>serverlessworkflow-impl-template-resolver</artifactId>
68+
<version>${project.version}</version>
69+
<scope>test</scope>
70+
</dependency>
71+
<dependency>
72+
<groupId>io.serverlessworkflow</groupId>
73+
<artifactId>serverlessworkflow-impl-jq</artifactId>
74+
</dependency>
75+
<dependency>
76+
<groupId>org.glassfish.jersey.media</groupId>
77+
<artifactId>jersey-media-json-jackson</artifactId>
78+
<version>${version.org.glassfish.jersey}</version>
79+
<scope>test</scope>
80+
</dependency>
81+
<dependency>
82+
<groupId>org.glassfish.jersey.core</groupId>
83+
<artifactId>jersey-client</artifactId>
84+
<version>${version.org.glassfish.jersey}</version>
85+
<scope>test</scope>
86+
</dependency>
5287
</dependencies>
5388
</project>
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.fluent.test;
17+
18+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.http;
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.SoftAssertions.assertSoftly;
21+
22+
import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder;
23+
import io.serverlessworkflow.impl.WorkflowApplication;
24+
import io.serverlessworkflow.impl.WorkflowInstance;
25+
import java.io.IOException;
26+
import java.util.Map;
27+
import java.util.concurrent.TimeUnit;
28+
import mockwebserver3.MockResponse;
29+
import mockwebserver3.MockWebServer;
30+
import mockwebserver3.RecordedRequest;
31+
import okhttp3.Headers;
32+
import org.junit.jupiter.api.AfterEach;
33+
import org.junit.jupiter.api.BeforeEach;
34+
import org.junit.jupiter.api.DisplayName;
35+
import org.junit.jupiter.api.Test;
36+
37+
public class FuncHttpTest {
38+
39+
private WorkflowApplication app;
40+
private MockWebServer mockServer;
41+
42+
@BeforeEach
43+
void setup() throws IOException {
44+
app = WorkflowApplication.builder().build();
45+
mockServer = new MockWebServer();
46+
mockServer.start(0);
47+
mockServer.enqueue(new MockResponse(204, Headers.of("Content-Type", "application/json"), ""));
48+
}
49+
50+
@AfterEach
51+
void cleanup() {
52+
mockServer.close();
53+
app.close();
54+
}
55+
56+
private RecordedRequest takeRequestOrFail() throws Exception {
57+
RecordedRequest request = mockServer.takeRequest(150, TimeUnit.MILLISECONDS);
58+
assertThat(request)
59+
.as("Expected an HTTP request to be received by MockWebServer within 300 ms")
60+
.isNotNull();
61+
return request;
62+
}
63+
64+
@Test
65+
@DisplayName("Query method with single key-value pair")
66+
void test_query_with_single_key_value() throws Exception {
67+
var workflow =
68+
FuncWorkflowBuilder.workflow("test-query-single")
69+
.tasks(
70+
http("callHttp")
71+
.GET()
72+
.uri(mockServer.url("/api/endpoint").toString())
73+
.query("param1", "value1"))
74+
.build();
75+
76+
WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of());
77+
78+
instance.start().join();
79+
80+
RecordedRequest request = takeRequestOrFail();
81+
82+
assertSoftly(
83+
softly -> {
84+
softly.assertThat(request.getUrl().toString()).contains("param1=value1");
85+
softly.assertThat(request.getMethod()).isEqualTo("GET");
86+
});
87+
}
88+
89+
@Test
90+
@DisplayName("Query method with multiple single key-value pairs (individually tested)")
91+
void test_query_with_multiple_single_values() throws Exception {
92+
var workflow =
93+
FuncWorkflowBuilder.workflow("test-query-single-multi")
94+
.tasks(
95+
http("callHttp")
96+
.GET()
97+
.uri(mockServer.url("/api/endpoint").toString())
98+
.query("param1", "value1")
99+
.query("param2", "value2")
100+
.query("param3", "value3"))
101+
.build();
102+
103+
WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of());
104+
instance.start().join();
105+
106+
RecordedRequest request = takeRequestOrFail();
107+
String url = request.getUrl().toString();
108+
109+
assertSoftly(
110+
softly -> {
111+
softly.assertThat(url).contains("param1=value1").isNotEmpty();
112+
softly.assertThat(url).contains("param2=value2").isNotEmpty();
113+
softly.assertThat(url).contains("param3=value3").isNotEmpty();
114+
});
115+
}
116+
117+
@Test
118+
@DisplayName("Query method with Map of parameters")
119+
void test_query_with_map() throws Exception {
120+
121+
var workflow =
122+
FuncWorkflowBuilder.workflow("test-query-map")
123+
.tasks(
124+
http("callHttp")
125+
.GET()
126+
.uri(mockServer.url("/api/endpoint").toString())
127+
.query(Map.of("userId", "123", "userName", "john", "status", "active")))
128+
.build();
129+
130+
WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of());
131+
instance.start().join();
132+
133+
RecordedRequest request = takeRequestOrFail();
134+
String url = request.getUrl().toString();
135+
136+
assertSoftly(
137+
softly -> {
138+
softly.assertThat(url).contains("userId=123");
139+
softly.assertThat(url).contains("userName=john");
140+
softly.assertThat(url).contains("status=active");
141+
});
142+
}
143+
144+
@Test
145+
@DisplayName("Query method with expression string")
146+
void test_query_with_expression() throws Exception {
147+
var workflow =
148+
FuncWorkflowBuilder.workflow("test-query-expression")
149+
.tasks(
150+
http("callHttp")
151+
.GET()
152+
.uri(mockServer.url("/api/endpoint").toString())
153+
.query("enabled", "${ .enabled }"))
154+
.build();
155+
156+
WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of("enabled", true));
157+
instance.start().join();
158+
159+
RecordedRequest request = takeRequestOrFail();
160+
161+
assertThat(request.getUrl().query()).contains("enabled=true");
162+
}
163+
164+
@Test
165+
@DisplayName("Query method with empty Map")
166+
void test_query_with_empty_map() throws Exception {
167+
var workflow =
168+
FuncWorkflowBuilder.workflow("test-query-empty-map")
169+
.tasks(
170+
http("callHttp")
171+
.GET()
172+
.uri(mockServer.url("/api/endpoint").toString())
173+
.query(Map.of()))
174+
.build();
175+
176+
WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of());
177+
instance.start().join();
178+
179+
RecordedRequest request = takeRequestOrFail();
180+
181+
assertSoftly(
182+
softly -> {
183+
softly.assertThat(request.getUrl().encodedPath()).isEqualTo("/api/endpoint");
184+
softly.assertThat(request.getUrl().encodedQuery()).isNull();
185+
});
186+
}
187+
188+
@Test
189+
@DisplayName("Query method with special characters in values")
190+
void test_query_with_special_characters() throws Exception {
191+
var workflow =
192+
FuncWorkflowBuilder.workflow("test-query-special-chars")
193+
.tasks(
194+
http("callHttp")
195+
.GET()
196+
.uri(mockServer.url("/api/endpoint").toString())
197+
.query("email", "user@example.com"))
198+
.build();
199+
200+
WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of());
201+
instance.start().join();
202+
203+
RecordedRequest request = takeRequestOrFail();
204+
205+
assertSoftly(
206+
softly -> {
207+
softly.assertThat(request.getUrl().queryParameter("email")).isEqualTo("user@example.com");
208+
softly.assertThat(request.getUrl().encodedQuery()).contains("email=user%40example.com");
209+
});
210+
}
211+
212+
@Test
213+
@DisplayName("Query method overload - Map with multiple values")
214+
void test_query_map_multiple_values() throws Exception {
215+
var workflow =
216+
FuncWorkflowBuilder.workflow("test-query-map-multi")
217+
.tasks(
218+
http("callHttp")
219+
.GET()
220+
.uri(mockServer.url("/api/endpoint").toString())
221+
.query(Map.of("limit", "50", "offset", "0", "sort", "name")))
222+
.build();
223+
224+
WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of());
225+
instance.start().join();
226+
227+
RecordedRequest request = takeRequestOrFail();
228+
String url = request.getUrl().toString();
229+
230+
assertSoftly(
231+
softly -> {
232+
softly.assertThat(url).contains("limit=50");
233+
softly.assertThat(url).contains("offset=0");
234+
softly.assertThat(url).contains("sort=name");
235+
});
236+
}
237+
238+
@Test
239+
@DisplayName("Query method with headers and query parameters")
240+
void test_query_with_headers_and_query() throws Exception {
241+
var workflow =
242+
FuncWorkflowBuilder.workflow("test-query-with-headers")
243+
.tasks(
244+
http("callHttp")
245+
.GET()
246+
.uri(mockServer.url("/api/endpoint").toString())
247+
.header("Authorization", "Bearer token123")
248+
.header("Accept", "application/json")
249+
.query("userId", "123"))
250+
.build();
251+
252+
WorkflowInstance instance = app.workflowDefinition(workflow).instance(Map.of());
253+
instance.start().join();
254+
255+
RecordedRequest request = takeRequestOrFail();
256+
String url = request.getUrl().toString();
257+
assertThat(url).contains("userId=123");
258+
}
259+
}

fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/BaseCallHttpSpec.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,21 @@ default SELF headers(Map<String, String> headers) {
107107
return self();
108108
}
109109

110+
default SELF query(String queryExpr) {
111+
steps().add(c -> c.query(queryExpr));
112+
return self();
113+
}
114+
115+
default SELF query(Map<String, String> query) {
116+
steps().add(c -> c.query(query));
117+
return self();
118+
}
119+
120+
default SELF query(String name, String value) {
121+
steps().add(c -> c.query(q -> q.query(name, value)));
122+
return self();
123+
}
124+
110125
default void accept(CallHttpTaskFluent<?> b) {
111126
for (var s : steps()) {
112127
s.accept(b);

fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpTaskFluent.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,19 +148,30 @@ default SELF query(String expr) {
148148
}
149149

150150
default SELF query(Consumer<HTTPQueryBuilder> consumer) {
151-
HTTPQueryBuilder queryBuilder = new HTTPQueryBuilder();
151+
HTTPQueryBuilder queryBuilder = createHttpQueryFromExisting();
152152
consumer.accept(queryBuilder);
153153
((CallHTTP) this.self().getTask()).getWith().setQuery(queryBuilder.build());
154154
return self();
155155
}
156156

157157
default SELF query(Map<String, String> query) {
158-
HTTPQueryBuilder httpQueryBuilder = new HTTPQueryBuilder();
158+
HTTPQueryBuilder httpQueryBuilder = createHttpQueryFromExisting();
159159
httpQueryBuilder.queries(query);
160160
((CallHTTP) this.self().getTask()).getWith().setQuery(httpQueryBuilder.build());
161161
return self();
162162
}
163163

164+
private HTTPQueryBuilder createHttpQueryFromExisting() {
165+
HTTPQueryBuilder httpQueryBuilder = new HTTPQueryBuilder();
166+
Query existingQuery = ((CallHTTP) this.self().getTask()).getWith().getQuery();
167+
if (existingQuery != null
168+
&& existingQuery.getHTTPQuery() != null
169+
&& existingQuery.getHTTPQuery().getAdditionalProperties() != null) {
170+
existingQuery.getHTTPQuery().getAdditionalProperties().forEach(httpQueryBuilder::query);
171+
}
172+
return httpQueryBuilder;
173+
}
174+
164175
default SELF redirect(boolean redirect) {
165176
((CallHTTP) this.self().getTask()).getWith().setRedirect(redirect);
166177
return self();

fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ void testDoTaskCallHTTPHeadersConsumerAndMap() {
575575
}
576576

577577
@Test
578-
void testDoTaskCallHTTPQueryConsumerAndMap() {
578+
void testDoTaskCallHTTPQueryMap() {
579579
Workflow wf =
580580
WorkflowBuilder.workflow("flowCallQuery")
581581
.tasks(
@@ -615,6 +615,17 @@ void testDoTaskCallHTTPQueryConsumerAndMap() {
615615
assertEquals("y", hq2.getAdditionalProperties().get("q2"));
616616
}
617617

618+
@Test
619+
void testDoTaskCallHTTPQuerySingleKeyValue() {
620+
Workflow wf =
621+
WorkflowBuilder.workflow("flowCallQuerySingle")
622+
.tasks(d -> d.http("qryOne", http().GET().endpoint("http://uri").query("id", "42")))
623+
.build();
624+
HTTPQuery hq =
625+
wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith().getQuery().getHTTPQuery();
626+
assertEquals("42", hq.getAdditionalProperties().get("id"));
627+
}
628+
618629
@Test
619630
void testDoTaskCallHTTPRedirectAndOutput() {
620631
Workflow wf =

0 commit comments

Comments
 (0)