From 0114b185057c1782ae67942358faaacad19939bc Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Mon, 27 Apr 2026 08:56:42 +0200 Subject: [PATCH 1/2] fix: preserve microflow-call BSON field order Symptom: rebuilding a microflow call with parameter mappings could produce an MPR that Studio Pro reported as CE0117 expression errors on the call activity even though the textual MDL roundtripped unchanged. Root cause: the writer emitted MicroflowCallAction and nested MicroflowCall fields in an order that differs from the shape Studio Pro writes for this model version. The parameter mapping also placed Parameter before Argument. Fix: emit the nested MicroflowCall before result-variable fields, place ParameterMappings before QueueSettings, and write each MicroflowCallParameterMapping as Argument then Parameter. Tests: add a BSON-level writer regression test with generic microflow names that asserts the stable field order for the action, nested call, and parameter mapping. --- sdk/mpr/microflow_call_writer_test.go | 89 +++++++++++++++++++++++++++ sdk/mpr/writer_microflow_actions.go | 10 +-- 2 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 sdk/mpr/microflow_call_writer_test.go diff --git a/sdk/mpr/microflow_call_writer_test.go b/sdk/mpr/microflow_call_writer_test.go new file mode 100644 index 00000000..4df11fda --- /dev/null +++ b/sdk/mpr/microflow_call_writer_test.go @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 + +package mpr + +import ( + "reflect" + "testing" + + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/microflows" + "go.mongodb.org/mongo-driver/bson" +) + +func TestMicroflowCallAction_WritesStableFieldOrder(t *testing.T) { + action := µflows.MicroflowCallAction{ + BaseElement: model.BaseElement{ID: "action-id"}, + ErrorHandlingType: microflows.ErrorHandlingTypeRollback, + MicroflowCall: µflows.MicroflowCall{ + BaseElement: model.BaseElement{ID: "call-id"}, + Microflow: "Demo.UpdateRecord", + ParameterMappings: []*microflows.MicroflowCallParameterMapping{ + { + BaseElement: model.BaseElement{ID: "mapping-id"}, + Argument: "$Record/Name", + Parameter: "Demo.UpdateRecord.Name", + }, + }, + }, + UseReturnVariable: true, + } + + doc := serializeMicroflowAction(action) + assertBSONKeys(t, doc, []string{ + "$ID", + "$Type", + "ErrorHandlingType", + "MicroflowCall", + "ResultVariableName", + "UseReturnVariable", + }) + + callDoc, ok := bsonValue(doc, "MicroflowCall").(bson.D) + if !ok { + t.Fatalf("MicroflowCall type = %T, want bson.D", bsonValue(doc, "MicroflowCall")) + } + assertBSONKeys(t, callDoc, []string{ + "$ID", + "$Type", + "Microflow", + "ParameterMappings", + "QueueSettings", + }) + + mappings, ok := bsonValue(callDoc, "ParameterMappings").(bson.A) + if !ok || len(mappings) != 2 { + t.Fatalf("ParameterMappings = %#v, want marker plus one mapping", bsonValue(callDoc, "ParameterMappings")) + } + mappingDoc, ok := mappings[1].(bson.D) + if !ok { + t.Fatalf("mapping type = %T, want bson.D", mappings[1]) + } + assertBSONKeys(t, mappingDoc, []string{ + "$ID", + "$Type", + "Argument", + "Parameter", + }) +} + +func assertBSONKeys(t *testing.T, doc bson.D, want []string) { + t.Helper() + + var got []string + for _, elem := range doc { + got = append(got, elem.Key) + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("BSON keys = %#v, want %#v", got, want) + } +} + +func bsonValue(doc bson.D, key string) any { + for _, elem := range doc { + if elem.Key == key { + return elem.Value + } + } + return nil +} diff --git a/sdk/mpr/writer_microflow_actions.go b/sdk/mpr/writer_microflow_actions.go index 25c7ab08..304baed9 100644 --- a/sdk/mpr/writer_microflow_actions.go +++ b/sdk/mpr/writer_microflow_actions.go @@ -202,8 +202,6 @@ func serializeMicroflowAction(action microflows.MicroflowAction) bson.D { {Key: "$ID", Value: idToBsonBinary(string(a.ID))}, {Key: "$Type", Value: "Microflows$MicroflowCallAction"}, {Key: "ErrorHandlingType", Value: stringOrDefault(string(a.ErrorHandlingType), "Rollback")}, - {Key: "ResultVariableName", Value: a.ResultVariableName}, - {Key: "UseReturnVariable", Value: a.UseReturnVariable}, } // Serialize nested MicroflowCall structure if a.MicroflowCall != nil { @@ -211,7 +209,6 @@ func serializeMicroflowAction(action microflows.MicroflowAction) bson.D { {Key: "$ID", Value: idToBsonBinary(string(a.MicroflowCall.ID))}, {Key: "$Type", Value: "Microflows$MicroflowCall"}, {Key: "Microflow", Value: a.MicroflowCall.Microflow}, - {Key: "QueueSettings", Value: nil}, } // Serialize parameter mappings within MicroflowCall if len(a.MicroflowCall.ParameterMappings) > 0 { @@ -221,8 +218,8 @@ func serializeMicroflowAction(action microflows.MicroflowAction) bson.D { mapping := bson.D{ {Key: "$ID", Value: idToBsonBinary(string(pm.ID))}, {Key: "$Type", Value: "Microflows$MicroflowCallParameterMapping"}, - {Key: "Parameter", Value: pm.Parameter}, {Key: "Argument", Value: pm.Argument}, + {Key: "Parameter", Value: pm.Parameter}, } mappings = append(mappings, mapping) } @@ -230,8 +227,13 @@ func serializeMicroflowAction(action microflows.MicroflowAction) bson.D { } else { mfCall = append(mfCall, bson.E{Key: "ParameterMappings", Value: bson.A{int32(2)}}) // Empty array with marker } + mfCall = append(mfCall, bson.E{Key: "QueueSettings", Value: nil}) doc = append(doc, bson.E{Key: "MicroflowCall", Value: mfCall}) } + doc = append(doc, + bson.E{Key: "ResultVariableName", Value: a.ResultVariableName}, + bson.E{Key: "UseReturnVariable", Value: a.UseReturnVariable}, + ) return doc case *microflows.JavaActionCallAction: From a2b2afaf65d17e8485ec33299b62638cd31a1239 Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Mon, 27 Apr 2026 16:54:53 +0200 Subject: [PATCH 2/2] test: add bug-test reproducer for microflow-call BSON field order fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an MDL script under mdl-examples/bug-tests/ exercising a microflow call with one parameter mapping. After exec, `mx check` reports 0 errors and the describe → exec → describe cycle is a fixpoint, confirming the writer emits MicroflowCall before result fields, ParameterMappings before QueueSettings, and Argument before Parameter inside each mapping. Co-Authored-By: Claude Opus 4.7 --- .../341-microflow-call-bson-field-order.mdl | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 mdl-examples/bug-tests/341-microflow-call-bson-field-order.mdl diff --git a/mdl-examples/bug-tests/341-microflow-call-bson-field-order.mdl b/mdl-examples/bug-tests/341-microflow-call-bson-field-order.mdl new file mode 100644 index 00000000..b4d75fac --- /dev/null +++ b/mdl-examples/bug-tests/341-microflow-call-bson-field-order.mdl @@ -0,0 +1,61 @@ +-- ============================================================================ +-- Bug #341: Microflow call BSON field order produced CE0117 in Studio Pro +-- ============================================================================ +-- +-- Symptom (before fix): +-- `mxcli exec` could roundtrip the textual MDL of a microflow call +-- (`$Result = call microflow Module.Target (Param = $Var);`) unchanged, +-- but Studio Pro flagged the resulting activity with +-- [CE0117] expression error on the call activity +-- even though the MDL source had not been edited. The cause was BSON +-- field order: +-- - The writer emitted result-variable fields BEFORE the nested +-- `MicroflowCall` document. +-- - Inside `MicroflowCall`, `QueueSettings` was emitted BEFORE +-- `ParameterMappings`. +-- - Each `MicroflowCallParameterMapping` was emitted with `Parameter` +-- BEFORE `Argument`. +-- Studio Pro's expression validator depends on this order being stable +-- to resolve parameter references. +-- +-- After fix: +-- `serializeMicroflowAction` now emits: +-- - `MicroflowCallAction`: $ID, $Type, ErrorHandlingType, MicroflowCall, +-- ResultVariableName, UseReturnVariable (call payload before result fields). +-- - `MicroflowCall`: $ID, $Type, Microflow, ParameterMappings, QueueSettings. +-- - `MicroflowCallParameterMapping`: $ID, $Type, Argument, Parameter. +-- +-- Usage: +-- mxcli exec mdl-examples/bug-tests/341-microflow-call-bson-field-order.mdl -p app.mpr +-- mxcli -p app.mpr -c "describe microflow BugTest341.MF_Caller" +-- `mx check` against the resulting MPR must report 0 errors and the +-- call activity must not surface CE0117 in Studio Pro. +-- ============================================================================ + +create module BugTest341; + +create entity BugTest341.Item ( + Name : string(100) +); +/ + +-- Callee with one parameter, exercising the parameter-mapping serialization. +create microflow BugTest341.MF_RenameItem ( + $Item: BugTest341.Item, + $NewName: string +) +begin + change $Item (Name = $NewName); +end; +/ + +-- Caller — produces a MicroflowCallAction whose nested MicroflowCall +-- contains ParameterMappings. Field order in the serialized BSON must +-- match Studio Pro's expectation. +create microflow BugTest341.MF_Caller ( + $Item: BugTest341.Item +) +begin + call microflow BugTest341.MF_RenameItem (Item = $Item, NewName = 'updated'); +end; +/