Skip to content
Merged
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
20 changes: 20 additions & 0 deletions mdl/executor/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand All @@ -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))
}
Expand All @@ -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
Expand Down
90 changes: 90 additions & 0 deletions mdl/executor/validate_system_javaaction_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading