Skip to content

Commit f7b3edf

Browse files
committed
test(workflow): add BSON parse/write unit tests and MDL round-trip test
- Add workflow_parse_test.go: parse WorkflowBaseline BSON fixtures - Add workflow_write_test.go: write workflow BSON and verify structure - Add BSON fixtures: WorkflowBaseline.Workflow.bson, Sub_Workflow.bson - Add visitor_workflow_test.go: test all workflow activity AST visitors - Add roundtrip_workflow_test.go: CREATE WORKFLOW → DESCRIBE WORKFLOW comparison - Fix expected values: TRUE/FALSE capitalization, RETURNS keyword
1 parent 3cdac49 commit f7b3edf

7 files changed

Lines changed: 1255 additions & 0 deletions

File tree

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
//go:build integration
4+
5+
package executor
6+
7+
import (
8+
"strings"
9+
"testing"
10+
)
11+
12+
// TestRoundtripWorkflow_Comprehensive tests all workflow MDL syntax in a single roundtrip.
13+
//
14+
// Activity types covered:
15+
// - ANNOTATION
16+
// - USER TASK (PAGE, TARGETING MICROFLOW, DUE DATE, OUTCOMES with nested, BOUNDARY EVENT x2)
17+
// - MULTI USER TASK (PAGE, TARGETING MICROFLOW, OUTCOMES)
18+
// - CALL MICROFLOW (WITH params, OUTCOMES TRUE/FALSE)
19+
// - DECISION (expression, OUTCOMES TRUE/FALSE with nested JUMP TO and WAIT FOR TIMER)
20+
// - PARALLEL SPLIT (PATH 1 with USER TASK, PATH 2 with CALL WORKFLOW)
21+
// - WAIT FOR TIMER (with ISO 8601 delay)
22+
// - WAIT FOR NOTIFICATION (with BOUNDARY EVENT NON INTERRUPTING TIMER)
23+
// - JUMP TO (inside DECISION outcome)
24+
// - CALL WORKFLOW (sub-workflow with parameter expression)
25+
func TestRoundtripWorkflow_Comprehensive(t *testing.T) {
26+
env := setupTestEnv(t)
27+
defer env.teardown()
28+
29+
mod := testModule
30+
31+
// --- Prerequisites ---
32+
33+
// Context entity for both workflows
34+
if err := env.executeMDL(`CREATE OR MODIFY PERSISTENT ENTITY ` + mod + `.WfCtxEntity (
35+
Score: Integer,
36+
IsApproved: Boolean DEFAULT false
37+
);`); err != nil {
38+
t.Fatalf("create WfCtxEntity: %v", err)
39+
}
40+
41+
// Microflow: single-user targeting
42+
if err := env.executeMDL(`CREATE MICROFLOW ` + mod + `.GetSingleReviewer () RETURNS String BEGIN END;`); err != nil {
43+
t.Fatalf("create GetSingleReviewer: %v", err)
44+
}
45+
46+
// Microflow: multi-user targeting
47+
if err := env.executeMDL(`CREATE MICROFLOW ` + mod + `.GetMultiReviewers () RETURNS String BEGIN END;`); err != nil {
48+
t.Fatalf("create GetMultiReviewers: %v", err)
49+
}
50+
51+
// Microflow: called by CALL MICROFLOW (returns Boolean)
52+
if err := env.executeMDL(`CREATE MICROFLOW ` + mod + `.ScoreCalc (Score: Integer) RETURNS Boolean BEGIN END;`); err != nil {
53+
t.Fatalf("create ScoreCalc: %v", err)
54+
}
55+
56+
// Sub-workflow for CALL WORKFLOW
57+
if err := env.executeMDL(`CREATE WORKFLOW ` + mod + `.SubApprovalFlow
58+
PARAMETER $WorkflowContext: ` + mod + `.WfCtxEntity
59+
BEGIN
60+
USER TASK SubTask 'Sub-Approval'
61+
PAGE ` + mod + `.SubPage
62+
OUTCOMES 'Done' { };
63+
END WORKFLOW;`); err != nil {
64+
t.Fatalf("create SubApprovalFlow: %v", err)
65+
}
66+
67+
// --- Main comprehensive workflow ---
68+
createMDL := `CREATE WORKFLOW ` + mod + `.ComprehensiveFlow
69+
PARAMETER $WorkflowContext: ` + mod + `.WfCtxEntity
70+
BEGIN
71+
72+
ANNOTATION 'Comprehensive workflow covering all MDL syntax';
73+
74+
USER TASK ReviewTask 'Review Request'
75+
PAGE ` + mod + `.ReviewPage
76+
TARGETING MICROFLOW ` + mod + `.GetSingleReviewer
77+
OUTCOMES
78+
'Approve' { }
79+
'Reject' { }
80+
BOUNDARY EVENT INTERRUPTING TIMER '${PT24H}' NON INTERRUPTING TIMER '${PT1H}';
81+
82+
MULTI USER TASK MultiReviewTask 'Multi-Person Review'
83+
PAGE ` + mod + `.MultiReviewPage
84+
TARGETING MICROFLOW ` + mod + `.GetMultiReviewers
85+
OUTCOMES 'Complete' { };
86+
87+
CALL MICROFLOW ` + mod + `.ScoreCalc
88+
WITH (Score = '$WorkflowContext/Score')
89+
OUTCOMES
90+
TRUE -> { }
91+
FALSE -> { };
92+
93+
DECISION '$WorkflowContext/IsApproved'
94+
OUTCOMES
95+
TRUE -> {
96+
WAIT FOR TIMER '${PT2H}';
97+
}
98+
FALSE -> {
99+
JUMP TO ReviewTask;
100+
};
101+
102+
PARALLEL SPLIT
103+
PATH 1 {
104+
USER TASK FinalApprove 'Final Approval'
105+
PAGE ` + mod + `.ApprovePage
106+
OUTCOMES 'Approved' { };
107+
}
108+
PATH 2 {
109+
CALL WORKFLOW ` + mod + `.SubApprovalFlow;
110+
};
111+
112+
WAIT FOR NOTIFICATION;
113+
114+
ANNOTATION 'End of flow';
115+
116+
END WORKFLOW;`
117+
118+
if err := env.executeMDL(createMDL); err != nil {
119+
t.Fatalf("create ComprehensiveFlow: %v", err)
120+
}
121+
122+
output, err := env.describeMDL(`DESCRIBE WORKFLOW ` + mod + `.ComprehensiveFlow;`)
123+
if err != nil {
124+
t.Fatalf("describe ComprehensiveFlow: %v", err)
125+
}
126+
127+
t.Logf("DESCRIBE output:\n%s", output)
128+
129+
checks := []struct {
130+
label string
131+
keyword string
132+
}{
133+
{"annotation activity", "ANNOTATION 'Comprehensive workflow"},
134+
{"user task", "USER TASK ReviewTask"},
135+
{"outcome approve", "'Approve'"},
136+
{"outcome reject", "'Reject'"},
137+
{"boundary interrupting", "BOUNDARY EVENT INTERRUPTING TIMER '${PT24H}'"},
138+
{"boundary non interrupting", "BOUNDARY EVENT NON INTERRUPTING TIMER '${PT1H}'"},
139+
{"multi user task", "MULTI USER TASK MultiReviewTask"},
140+
{"call microflow with", "CALL MICROFLOW " + mod + ".ScoreCalc WITH (Score ="},
141+
{"outcomes true", "TRUE ->"},
142+
{"outcomes false", "FALSE ->"},
143+
{"decision", "DECISION '$WorkflowContext/IsApproved'"},
144+
{"wait for timer", "WAIT FOR TIMER '${PT2H}'"},
145+
{"jump to", "JUMP TO ReviewTask"},
146+
{"parallel split", "PARALLEL SPLIT"},
147+
{"path 1", "PATH 1"},
148+
{"path 2", "PATH 2"},
149+
{"call workflow", "CALL WORKFLOW " + mod + ".SubApprovalFlow"},
150+
{"wait for notification", "WAIT FOR NOTIFICATION"},
151+
{"trailing annotation", "ANNOTATION 'End of flow'"},
152+
{"parameter", "PARAMETER $WorkflowContext: " + mod + ".WfCtxEntity"},
153+
}
154+
155+
var failed []string
156+
for _, c := range checks {
157+
if !strings.Contains(output, c.keyword) {
158+
failed = append(failed, c.label+": "+c.keyword)
159+
}
160+
}
161+
if len(failed) > 0 {
162+
t.Errorf("DESCRIBE output missing %d expected keywords:\n %s\n\nFull output:\n%s",
163+
len(failed), strings.Join(failed, "\n "), output)
164+
}
165+
}
166+
167+
func TestRoundtripWorkflow_BoundaryEventInterrupting(t *testing.T) {
168+
env := setupTestEnv(t)
169+
defer env.teardown()
170+
171+
createMDL := `CREATE WORKFLOW ` + testModule + `.WfBoundaryInt
172+
PARAMETER $WorkflowContext: ` + testModule + `.TestEntitySimple
173+
BEGIN
174+
USER TASK act1 'Review'
175+
PAGE ` + testModule + `.ReviewPage
176+
OUTCOMES 'Approve' { }
177+
BOUNDARY EVENT INTERRUPTING TIMER '${PT1H}'
178+
;
179+
END WORKFLOW;`
180+
181+
if err := env.executeMDL(`CREATE OR MODIFY PERSISTENT ENTITY ` + testModule + `.TestEntitySimple (Name: String(100));`); err != nil {
182+
t.Fatalf("Failed to create entity: %v", err)
183+
}
184+
185+
if err := env.executeMDL(createMDL); err != nil {
186+
t.Fatalf("Failed to create workflow: %v", err)
187+
}
188+
189+
output, err := env.describeMDL(`DESCRIBE WORKFLOW ` + testModule + `.WfBoundaryInt;`)
190+
if err != nil {
191+
t.Fatalf("Failed to describe workflow: %v", err)
192+
}
193+
194+
if !strings.Contains(output, "BOUNDARY EVENT INTERRUPTING TIMER") {
195+
t.Errorf("Expected DESCRIBE output to contain 'BOUNDARY EVENT INTERRUPTING TIMER', got:\n%s", output)
196+
}
197+
}
198+
199+
func TestRoundtripWorkflow_BoundaryEventNonInterrupting(t *testing.T) {
200+
env := setupTestEnv(t)
201+
defer env.teardown()
202+
203+
createMDL := `CREATE WORKFLOW ` + testModule + `.WfBoundaryNonInt
204+
PARAMETER $WorkflowContext: ` + testModule + `.TestEntitySimple2
205+
BEGIN
206+
USER TASK act1 'Review'
207+
PAGE ` + testModule + `.ReviewPage
208+
OUTCOMES 'Approve' { }
209+
BOUNDARY EVENT NON INTERRUPTING TIMER '${PT2H}'
210+
;
211+
END WORKFLOW;`
212+
213+
if err := env.executeMDL(`CREATE OR MODIFY PERSISTENT ENTITY ` + testModule + `.TestEntitySimple2 (Name: String(100));`); err != nil {
214+
t.Fatalf("Failed to create entity: %v", err)
215+
}
216+
217+
if err := env.executeMDL(createMDL); err != nil {
218+
t.Fatalf("Failed to create workflow: %v", err)
219+
}
220+
221+
output, err := env.describeMDL(`DESCRIBE WORKFLOW ` + testModule + `.WfBoundaryNonInt;`)
222+
if err != nil {
223+
t.Fatalf("Failed to describe workflow: %v", err)
224+
}
225+
226+
if !strings.Contains(output, "BOUNDARY EVENT NON INTERRUPTING TIMER") {
227+
t.Errorf("Expected DESCRIBE output to contain 'BOUNDARY EVENT NON INTERRUPTING TIMER', got:\n%s", output)
228+
}
229+
}
230+
231+
func TestRoundtripWorkflow_MultiUserTask(t *testing.T) {
232+
env := setupTestEnv(t)
233+
defer env.teardown()
234+
235+
createMDL := `CREATE WORKFLOW ` + testModule + `.WfMultiUser
236+
PARAMETER $WorkflowContext: ` + testModule + `.TestEntityMulti
237+
BEGIN
238+
MULTI USER TASK act1 'Caption'
239+
PAGE ` + testModule + `.ReviewPage
240+
OUTCOMES 'Approve' { }
241+
;
242+
END WORKFLOW;`
243+
244+
if err := env.executeMDL(`CREATE OR MODIFY PERSISTENT ENTITY ` + testModule + `.TestEntityMulti (Name: String(100));`); err != nil {
245+
t.Fatalf("Failed to create entity: %v", err)
246+
}
247+
248+
if err := env.executeMDL(createMDL); err != nil {
249+
t.Fatalf("Failed to create workflow: %v", err)
250+
}
251+
252+
output, err := env.describeMDL(`DESCRIBE WORKFLOW ` + testModule + `.WfMultiUser;`)
253+
if err != nil {
254+
t.Fatalf("Failed to describe workflow: %v", err)
255+
}
256+
257+
if !strings.Contains(output, "MULTI USER TASK") {
258+
t.Errorf("Expected DESCRIBE output to contain 'MULTI USER TASK', got:\n%s", output)
259+
}
260+
}
261+
262+
func TestRoundtripWorkflow_AnnotationActivity(t *testing.T) {
263+
env := setupTestEnv(t)
264+
defer env.teardown()
265+
266+
createMDL := `CREATE WORKFLOW ` + testModule + `.WfAnnotation
267+
PARAMETER $WorkflowContext: ` + testModule + `.TestEntityAnnot
268+
BEGIN
269+
ANNOTATION 'This is a workflow note';
270+
END WORKFLOW;`
271+
272+
if err := env.executeMDL(`CREATE OR MODIFY PERSISTENT ENTITY ` + testModule + `.TestEntityAnnot (Name: String(100));`); err != nil {
273+
t.Fatalf("Failed to create entity: %v", err)
274+
}
275+
276+
if err := env.executeMDL(createMDL); err != nil {
277+
t.Fatalf("Failed to create workflow: %v", err)
278+
}
279+
280+
output, err := env.describeMDL(`DESCRIBE WORKFLOW ` + testModule + `.WfAnnotation;`)
281+
if err != nil {
282+
t.Fatalf("Failed to describe workflow: %v", err)
283+
}
284+
285+
if !strings.Contains(output, "ANNOTATION 'This is a workflow note'") {
286+
t.Errorf("Expected DESCRIBE output to contain \"ANNOTATION 'This is a workflow note'\", got:\n%s", output)
287+
}
288+
}
289+
290+
func TestRoundtripWorkflow_CallMicroflowWithParams(t *testing.T) {
291+
env := setupTestEnv(t)
292+
defer env.teardown()
293+
294+
createMDL := `CREATE WORKFLOW ` + testModule + `.WfCallMf
295+
PARAMETER $WorkflowContext: ` + testModule + `.TestEntityCallMf
296+
BEGIN
297+
CALL MICROFLOW ` + testModule + `.SomeMicroflow WITH (Amount = '$WorkflowContext/Amount')
298+
OUTCOMES TRUE -> { } FALSE -> { };
299+
END WORKFLOW;`
300+
301+
if err := env.executeMDL(`CREATE OR MODIFY PERSISTENT ENTITY ` + testModule + `.TestEntityCallMf (Amount: Decimal);`); err != nil {
302+
t.Fatalf("Failed to create entity: %v", err)
303+
}
304+
305+
if err := env.executeMDL(`CREATE MICROFLOW ` + testModule + `.SomeMicroflow (Amount: Decimal) RETURNS Boolean BEGIN END;`); err != nil {
306+
t.Fatalf("Failed to create microflow: %v", err)
307+
}
308+
309+
if err := env.executeMDL(createMDL); err != nil {
310+
t.Fatalf("Failed to create workflow: %v", err)
311+
}
312+
313+
output, err := env.describeMDL(`DESCRIBE WORKFLOW ` + testModule + `.WfCallMf;`)
314+
if err != nil {
315+
t.Fatalf("Failed to describe workflow: %v", err)
316+
}
317+
318+
if !strings.Contains(output, "WITH (") {
319+
t.Errorf("Expected DESCRIBE output to contain 'WITH (', got:\n%s", output)
320+
}
321+
}

0 commit comments

Comments
 (0)