diff --git a/mdl/executor/validate.go b/mdl/executor/validate.go index 700d52a6..e66773dd 100644 --- a/mdl/executor/validate.go +++ b/mdl/executor/validate.go @@ -478,6 +478,14 @@ func validateFlowBodyReferences(ctx *ExecContext, body []ast.MicroflowStatement, if len(refs.javaActions) > 0 { known := buildJavaActionQualifiedNames(ctx) for _, ref := range refs.javaActions { + // System.* Java actions (e.g. System.VerifyPassword, + // System.GenerateRandomString) are runtime-provided and never + // appear in the project's MPR. Skip them to avoid false + // positives — Studio Pro's `mx check` resolves these against + // the runtime, which `mxcli check` cannot reach. + if isBuiltinModuleEntity(qualifiedNameModule(ref)) { + continue + } if !known[ref] { errors = append(errors, fmt.Sprintf("java action not found: %s (referenced by call java action)", ref)) } @@ -487,6 +495,9 @@ func validateFlowBodyReferences(ctx *ExecContext, body []ast.MicroflowStatement, if len(refs.javaScriptActions) > 0 { known := buildJavaScriptActionQualifiedNames(ctx) for _, ref := range refs.javaScriptActions { + if isBuiltinModuleEntity(qualifiedNameModule(ref)) { + continue + } if !known[ref] { errors = append(errors, fmt.Sprintf("javascript action not found: %s (referenced by call javascript action)", ref)) } @@ -505,6 +516,15 @@ func validateFlowBodyReferences(ctx *ExecContext, body []ast.MicroflowStatement, return errors } +// qualifiedNameModule returns the module portion of a "Module.Name" qualified +// name. It returns an empty string when the input has no dot. +func qualifiedNameModule(qn string) string { + if i := strings.Index(qn, "."); i >= 0 { + return qn[:i] + } + return "" +} + // flowRefCollector collects qualified name references from flow body statements. type flowRefCollector struct { pages []string diff --git a/mdl/executor/validate_system_javaaction_test.go b/mdl/executor/validate_system_javaaction_test.go new file mode 100644 index 00000000..137ea4ab --- /dev/null +++ b/mdl/executor/validate_system_javaaction_test.go @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "strings" + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/javaactions" + "github.com/mendixlabs/mxcli/sdk/microflows" +) + +// `mxcli check --references` must not flag System.* Java actions +// (System.VerifyPassword, System.GenerateRandomString, etc.) as missing. +// They are runtime-provided and never appear in the project MPR; flagging +// them produces false positives on any microflow that uses Mendix +// built-ins, including the Administration module's password flows. +func TestValidateMicroflowReferencesSkipsSystemJavaAction(t *testing.T) { + moduleID := model.ID("module-1") + backend := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { + return []*model.Module{{ + BaseElement: model.BaseElement{ID: moduleID}, + Name: "Administration", + }}, nil + }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { + return nil, nil + }, + ListJavaActionsFunc: nil, + } + ctx, _ := newMockCtx(t, withBackend(backend)) + + stmt := &ast.CreateMicroflowStmt{ + Name: ast.QualifiedName{Module: "Administration", Name: "ChangeMyPassword"}, + Body: []ast.MicroflowStatement{ + &ast.CallJavaActionStmt{ + ActionName: ast.QualifiedName{Module: "System", Name: "VerifyPassword"}, + }, + }, + } + + if err := validate(ctx, stmt); err != nil { + t.Fatalf("System.* Java action reference must not error, got: %v", err) + } +} + +// User-module Java action references are still validated. A missing one +// must error out so genuine typos don't slip through. +func TestValidateMicroflowReferencesReportsMissingUserJavaAction(t *testing.T) { + moduleID := model.ID("module-1") + backend := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { + return []*model.Module{{ + BaseElement: model.BaseElement{ID: moduleID}, + Name: "Administration", + }}, nil + }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { + return nil, nil + }, + ListJavaActionsFunc: nil, + ReadJavaActionByNameFunc: func(qn string) (*javaactions.JavaAction, error) { + return nil, nil + }, + } + ctx, _ := newMockCtx(t, withBackend(backend)) + + stmt := &ast.CreateMicroflowStmt{ + Name: ast.QualifiedName{Module: "Administration", Name: "BadCall"}, + Body: []ast.MicroflowStatement{ + &ast.CallJavaActionStmt{ + ActionName: ast.QualifiedName{Module: "Administration", Name: "NonExistentJavaAction"}, + }, + }, + } + + err := validate(ctx, stmt) + if err == nil { + t.Fatal("expected missing user Java action reference error") + } + if !strings.Contains(err.Error(), "java action not found: Administration.NonExistentJavaAction") { + t.Fatalf("unexpected error: %v", err) + } +}