Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions mdl-examples/bug-tests/341-microflow-call-bson-field-order.mdl
Original file line number Diff line number Diff line change
@@ -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;
/
89 changes: 89 additions & 0 deletions sdk/mpr/microflow_call_writer_test.go
Original file line number Diff line number Diff line change
@@ -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 := &microflows.MicroflowCallAction{
BaseElement: model.BaseElement{ID: "action-id"},
ErrorHandlingType: microflows.ErrorHandlingTypeRollback,
MicroflowCall: &microflows.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
}
10 changes: 6 additions & 4 deletions sdk/mpr/writer_microflow_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,16 +202,13 @@ 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 {
mfCall := 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 {
Expand All @@ -221,17 +218,22 @@ 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)
}
mfCall = append(mfCall, bson.E{Key: "ParameterMappings", Value: mappings})
} 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:
Expand Down
Loading