Skip to content

Commit 3b203aa

Browse files
committed
Change uniqueId to jsonPointer; replace tests with mock
Signed-off-by: Ricardo Zanini <ricardozanini@gmail.com>
1 parent 952f593 commit 3b203aa

File tree

3 files changed

+59
-156
lines changed

3 files changed

+59
-156
lines changed

experimental/fluent/func/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
<artifactId>junit-jupiter-api</artifactId>
4040
<scope>test</scope>
4141
</dependency>
42+
<dependency>
43+
<groupId>org.mockito</groupId>
44+
<artifactId>mockito-core</artifactId>
45+
<version>${version.org.mockito}</version>
46+
<scope>test</scope>
47+
</dependency>
4248
</dependencies>
4349

4450
</project>

experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.serverlessworkflow.fluent.func.configurers.FuncTaskConfigurer;
2828
import io.serverlessworkflow.fluent.func.configurers.SwitchCaseConfigurer;
2929
import io.serverlessworkflow.fluent.func.dsl.internal.CommonFuncOps;
30+
import io.serverlessworkflow.impl.TaskContextData;
3031
import io.serverlessworkflow.impl.WorkflowContextData;
3132
import java.util.Collection;
3233
import java.util.List;
@@ -383,10 +384,18 @@ public static <T, R> FuncCallStep<T, R> withInstanceId(
383384
return new FuncCallStep<>(name, jcf, in);
384385
}
385386

387+
/**
388+
* Builds a composition of the current workflow instance id and the definition of the task
389+
* position as a JSON pointer.
390+
*/
391+
static String defaultUniqueId(WorkflowContextData wctx, TaskContextData tctx) {
392+
return String.format("%s-%s", wctx.instanceData().id(), tctx.position().jsonPointer());
393+
}
394+
386395
/**
387396
* Build a call step for functions that expect a composition with the workflow instance id and the
388-
* task name as the first parameter. The instance ID is extracted from the runtime context, the
389-
* task name from the definition.
397+
* task position as the first parameter. The instance ID is extracted from the runtime context,
398+
* the task position from the definition.
390399
*
391400
* <p>Signature expected: {@code (uniqueId, payload) -> result}
392401
*
@@ -399,8 +408,7 @@ public static <T, R> FuncCallStep<T, R> withInstanceId(
399408
public static <T, R> FuncCallStep<T, R> withUniqueId(
400409
String name, UniqueIdBiFunction<T, R> fn, Class<T> in) {
401410
JavaFilterFunction<T, R> jff =
402-
(payload, wctx, tctx) ->
403-
fn.apply(String.format("%s-%s", wctx.instanceData().id(), tctx.taskName()), payload);
411+
(payload, wctx, tctx) -> fn.apply(defaultUniqueId(wctx, tctx), payload);
404412
return new FuncCallStep<>(name, jff, in);
405413
}
406414

@@ -735,7 +743,7 @@ public static <T> FuncTaskConfigurer switchWhenOrElse(
735743
* switchWhenOrElse(".approved == true", "sendEmail", FlowDirectiveEnum.END)
736744
* </pre>
737745
*
738-
* The JQ expression is evaluated against the task input at runtime.
746+
* <p>The JQ expression is evaluated against the task input at runtime.
739747
*/
740748
public static FuncTaskConfigurer switchWhenOrElse(
741749
String jqExpression, String thenTask, FlowDirectiveEnum otherwise) {
@@ -756,7 +764,7 @@ public static FuncTaskConfigurer switchWhenOrElse(
756764
* switchWhenOrElse(".score >= 80", "pass", "fail")
757765
* </pre>
758766
*
759-
* The JQ expression is evaluated against the task input at runtime.
767+
* <p>The JQ expression is evaluated against the task input at runtime.
760768
*/
761769
public static FuncTaskConfigurer switchWhenOrElse(
762770
String jqExpression, String thenTask, String otherwiseTask) {

experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLUniqueIdTest.java

Lines changed: 39 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,27 @@
1717

1818
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.agent;
1919
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.withUniqueId;
20-
import static org.junit.jupiter.api.Assertions.assertEquals;
21-
import static org.junit.jupiter.api.Assertions.assertNotNull;
22-
import static org.junit.jupiter.api.Assertions.fail;
20+
import static org.junit.jupiter.api.Assertions.*;
21+
import static org.mockito.Mockito.*;
2322

2423
import io.serverlessworkflow.api.types.Task;
25-
import io.serverlessworkflow.api.types.TaskBase;
2624
import io.serverlessworkflow.api.types.TaskItem;
2725
import io.serverlessworkflow.api.types.Workflow;
2826
import io.serverlessworkflow.api.types.func.CallJava;
2927
import io.serverlessworkflow.api.types.func.JavaFilterFunction;
3028
import io.serverlessworkflow.fluent.func.dsl.UniqueIdBiFunction;
3129
import io.serverlessworkflow.impl.TaskContextData;
3230
import io.serverlessworkflow.impl.WorkflowContextData;
33-
import io.serverlessworkflow.impl.WorkflowDefinitionData;
3431
import io.serverlessworkflow.impl.WorkflowInstanceData;
35-
import io.serverlessworkflow.impl.WorkflowModel;
3632
import io.serverlessworkflow.impl.WorkflowPosition;
37-
import io.serverlessworkflow.impl.WorkflowStatus;
38-
import java.time.Instant;
3933
import java.util.List;
4034
import java.util.concurrent.atomic.AtomicReference;
4135
import org.junit.jupiter.api.DisplayName;
4236
import org.junit.jupiter.api.Test;
4337

4438
/**
4539
* Verifies that withUniqueId/agent wrap the user's function so that, at runtime, the first argument
46-
* is a "unique id" composed as instanceId + "-" + taskName.
40+
* is a "unique id" composed as instanceId + "-" + jsonPointer (e.g., inst-123-/do/0/task).
4741
*/
4842
class FuncDSLUniqueIdTest {
4943

@@ -57,12 +51,12 @@ private static JavaFilterFunction<Object, Object> extractJavaFilterFunction(Call
5751
}
5852

5953
@Test
60-
@DisplayName("withUniqueId(name, fn, in) composes uniqueId = instanceId-taskName and passes it")
61-
void withUniqueId_named_composes_and_passes_unique_id() throws Exception {
54+
@DisplayName(
55+
"withUniqueId(name, fn, in) composes uniqueId = instanceId-jsonPointer and passes it")
56+
void withUniqueId_uses_json_pointer_for_unique_id() throws Exception {
6257
AtomicReference<String> receivedUniqueId = new AtomicReference<>();
6358
AtomicReference<String> receivedPayload = new AtomicReference<>();
6459

65-
// (uniqueId, payload) -> result; we capture inputs for assertion
6660
UniqueIdBiFunction<String, String> fn =
6761
(uniqueId, payload) -> {
6862
receivedUniqueId.set(uniqueId);
@@ -72,39 +66,45 @@ void withUniqueId_named_composes_and_passes_unique_id() throws Exception {
7266

7367
Workflow wf =
7468
FuncWorkflowBuilder.workflow("wf-unique-named")
75-
.tasks(
76-
// important: NAME is provided → should appear in the uniqueId
77-
withUniqueId("notify", fn, String.class))
69+
.tasks(withUniqueId("notify", fn, String.class))
7870
.build();
7971

8072
List<TaskItem> items = wf.getDo();
8173
assertEquals(1, items.size(), "one task expected");
8274
Task t = items.get(0).getTask();
8375
assertNotNull(t.getCallTask(), "CallTask expected");
8476

85-
String taskName = items.get(0).getName();
86-
assertEquals("notify", taskName, "task name should be set on the step");
87-
8877
CallJava cj = (CallJava) t.getCallTask().get();
8978
var jff = extractJavaFilterFunction(cj);
9079
assertNotNull(jff, "JavaFilterFunction must be present for withUniqueId");
9180

92-
// Invoke the wrapped function "as runtime" with fake contexts
93-
var wctx = new FakeWorkflowContextData("inst-123");
94-
var tctx = new FakeTaskContextData(taskName);
81+
// Mockito stubs for runtime contexts
82+
WorkflowInstanceData inst = mock(WorkflowInstanceData.class);
83+
when(inst.id()).thenReturn("inst-123");
84+
85+
WorkflowContextData wctx = mock(WorkflowContextData.class);
86+
when(wctx.instanceData()).thenReturn(inst);
87+
88+
// Use JSON Pointer for the unique component instead of task name
89+
final String pointer = "/do/0/task";
90+
WorkflowPosition pos = mock(WorkflowPosition.class);
91+
when(pos.jsonPointer()).thenReturn(pointer);
92+
93+
TaskContextData tctx = mock(TaskContextData.class);
94+
when(tctx.position()).thenReturn(pos);
9595

96-
// The JavaFilterFunction signature in your impl is (payload, wctx, tctx) -> result
9796
Object result = jff.apply("hello", wctx, tctx);
9897

99-
assertEquals("inst-123-notify", receivedUniqueId.get(), "uniqueId must be instanceId-taskName");
98+
assertEquals(
99+
"inst-123-" + pointer, receivedUniqueId.get(), "uniqueId must be instanceId-jsonPointer");
100100
assertEquals(
101101
"hello", receivedPayload.get(), "payload should be forwarded to the user function");
102102
assertEquals("HELLO", result, "wrapped function result should be returned");
103103
}
104104

105105
@Test
106-
@DisplayName("agent(fn, in) is sugar for withUniqueId(fn, in) and passes instanceId-taskName")
107-
void agent_unnamed_composes_and_passes_unique_id() throws Exception {
106+
@DisplayName("agent(fn, in) composes uniqueId = instanceId-jsonPointer and passes it")
107+
void agent_uses_json_pointer_for_unique_id() throws Exception {
108108
AtomicReference<String> receivedUniqueId = new AtomicReference<>();
109109
AtomicReference<Integer> receivedPayload = new AtomicReference<>();
110110

@@ -115,146 +115,35 @@ void agent_unnamed_composes_and_passes_unique_id() throws Exception {
115115
return payload + 1;
116116
};
117117

118-
Workflow wf =
119-
FuncWorkflowBuilder.workflow("wf-agent")
120-
.tasks(
121-
// No explicit name here; builder should still set a task name,
122-
// which participates in the uniqueId (instanceId-taskName)
123-
agent(fn, Integer.class))
124-
.build();
118+
Workflow wf = FuncWorkflowBuilder.workflow("wf-agent").tasks(agent(fn, Integer.class)).build();
125119

126120
List<TaskItem> items = wf.getDo();
127121
assertEquals(1, items.size(), "one task expected");
128122
Task t = items.get(0).getTask();
129123
assertNotNull(t.getCallTask(), "CallTask expected");
130-
String taskName = items.get(0).getName();
131-
assertNotNull(taskName, "task name should be assigned even if not explicitly provided");
132124

133125
CallJava cj = (CallJava) t.getCallTask().get();
134126
var jff = extractJavaFilterFunction(cj);
135127
assertNotNull(jff, "JavaFilterFunction must be present for agent/withUniqueId");
136128

137-
WorkflowContextData wctx = new FakeWorkflowContextData("wf-999");
138-
TaskContextData tctx = new FakeTaskContextData(taskName);
129+
WorkflowInstanceData inst = mock(WorkflowInstanceData.class);
130+
when(inst.id()).thenReturn("wf-999");
131+
132+
WorkflowContextData wctx = mock(WorkflowContextData.class);
133+
when(wctx.instanceData()).thenReturn(inst);
134+
135+
final String pointer = "/do/0/task";
136+
WorkflowPosition pos = mock(WorkflowPosition.class);
137+
when(pos.jsonPointer()).thenReturn(pointer);
138+
139+
TaskContextData tctx = mock(TaskContextData.class);
140+
when(tctx.position()).thenReturn(pos);
139141

140142
Object result = jff.apply(41, wctx, tctx);
141143

142144
assertEquals(
143-
"wf-999-" + taskName,
144-
receivedUniqueId.get(),
145-
"agent should compose uniqueId as instanceId-taskName");
145+
"wf-999-" + pointer, receivedUniqueId.get(), "uniqueId must be instanceId-jsonPointer");
146146
assertEquals(41, receivedPayload.get(), "payload should be forwarded to the user function");
147147
assertEquals(42, result, "wrapped function result should be returned");
148148
}
149-
150-
/**
151-
* Minimal test doubles to satisfy the JavaFilterFunction call path. We only implement the members
152-
* used by the DSL composition: - wctx.instanceData().id() - tctx.taskName()
153-
*/
154-
static final class FakeWorkflowContextData implements WorkflowContextData {
155-
private final String id;
156-
157-
FakeWorkflowContextData(String id) {
158-
this.id = id;
159-
}
160-
161-
@Override
162-
public WorkflowInstanceData instanceData() {
163-
// Provide just the id() accessor
164-
return new WorkflowInstanceData() {
165-
@Override
166-
public String id() {
167-
return id;
168-
}
169-
170-
@Override
171-
public Instant startedAt() {
172-
return null;
173-
}
174-
175-
@Override
176-
public Instant completedAt() {
177-
return null;
178-
}
179-
180-
@Override
181-
public WorkflowModel input() {
182-
return null;
183-
}
184-
185-
@Override
186-
public WorkflowStatus status() {
187-
return null;
188-
}
189-
190-
@Override
191-
public WorkflowModel output() {
192-
return null;
193-
}
194-
195-
@Override
196-
public WorkflowModel context() {
197-
return null;
198-
}
199-
200-
@Override
201-
public <T> T outputAs(Class<T> clazz) {
202-
return null;
203-
}
204-
};
205-
}
206-
207-
@Override
208-
public WorkflowModel context() {
209-
return null;
210-
}
211-
212-
@Override
213-
public WorkflowDefinitionData definition() {
214-
return null;
215-
}
216-
}
217-
218-
record FakeTaskContextData(String taskName) implements TaskContextData {
219-
220-
@Override
221-
public WorkflowModel input() {
222-
return null;
223-
}
224-
225-
@Override
226-
public WorkflowModel rawInput() {
227-
return null;
228-
}
229-
230-
@Override
231-
public TaskBase task() {
232-
return null;
233-
}
234-
235-
@Override
236-
public WorkflowModel rawOutput() {
237-
return null;
238-
}
239-
240-
@Override
241-
public WorkflowModel output() {
242-
return null;
243-
}
244-
245-
@Override
246-
public WorkflowPosition position() {
247-
return null;
248-
}
249-
250-
@Override
251-
public Instant startedAt() {
252-
return null;
253-
}
254-
255-
@Override
256-
public Instant completedAt() {
257-
return null;
258-
}
259-
}
260149
}

0 commit comments

Comments
 (0)