From 02db81390d1d606b22645ba94929daa5f9aae8e5 Mon Sep 17 00:00:00 2001 From: Andrew Vasilyev Date: Sun, 19 Apr 2026 15:06:53 +0200 Subject: [PATCH] feat: add comprehensive mock-based handler tests (189 tests, 33 files) Phase 1-8: 85 read/write handler tests with mock backend infrastructure Error paths: 49 tests covering backend error propagation for all handler groups Not-connected: 29 tests verifying Connected/ConnectedForWrite guards JSON format: 26 tests validating JSON output for show/list handlers Production code changes: - Added Func fields to MockBackend for 8 agent-editor write methods - Fixed ContainerID parameter semantics in withContainer and all call sites --- internal/marketplace/types.go | 2 +- mdl/backend/mock/backend.go | 16 +- mdl/backend/mock/mock_infrastructure.go | 61 +- mdl/executor/cmd_agenteditor_mock_test.go | 252 +++++++++ mdl/executor/cmd_agenteditor_models.go | 20 +- mdl/executor/cmd_agenteditor_write.go | 20 +- mdl/executor/cmd_associations_mock_test.go | 77 +++ mdl/executor/cmd_businessevents_mock_test.go | 79 +++ mdl/executor/cmd_constants_mock_test.go | 106 ++++ mdl/executor/cmd_datatransformer_mock_test.go | 60 ++ mdl/executor/cmd_dbconnection_mock_test.go | 60 ++ mdl/executor/cmd_entities_mock_test.go | 58 ++ mdl/executor/cmd_enumerations_mock_test.go | 164 +++--- mdl/executor/cmd_error_mock_test.go | 474 ++++++++++++++++ mdl/executor/cmd_export_mappings_mock_test.go | 34 ++ mdl/executor/cmd_fragments_mock_test.go | 31 + .../cmd_imagecollections_mock_test.go | 61 ++ mdl/executor/cmd_import_mappings_mock_test.go | 34 ++ mdl/executor/cmd_javaactions_mock_test.go | 58 ++ .../cmd_javascript_actions_mock_test.go | 59 ++ mdl/executor/cmd_json_mock_test.go | 528 ++++++++++++++++++ mdl/executor/cmd_jsonstructures_mock_test.go | 35 ++ mdl/executor/cmd_mermaid_mock_test.go | 52 ++ mdl/executor/cmd_microflows_mock_test.go | 113 ++++ mdl/executor/cmd_misc_mock_test.go | 35 ++ mdl/executor/cmd_modules_mock_test.go | 46 ++ mdl/executor/cmd_navigation_mock_test.go | 92 +++ mdl/executor/cmd_notconnected_mock_test.go | 181 ++++++ mdl/executor/cmd_odata_mock_test.go | 126 +++++ mdl/executor/cmd_pages_mock_test.go | 93 +++ mdl/executor/cmd_published_rest_mock_test.go | 70 +++ mdl/executor/cmd_rest_clients_mock_test.go | 63 +++ mdl/executor/cmd_security_mock_test.go | 174 ++++++ mdl/executor/cmd_settings_mock_test.go | 48 ++ mdl/executor/cmd_workflows_mock_test.go | 52 ++ mdl/executor/cmd_write_handlers_mock_test.go | 328 +++++++++++ mdl/executor/mock_test_helpers_test.go | 273 +++++++++ mdl/visitor/visitor_agenteditor.go | 1 - 38 files changed, 3904 insertions(+), 132 deletions(-) create mode 100644 mdl/executor/cmd_agenteditor_mock_test.go create mode 100644 mdl/executor/cmd_associations_mock_test.go create mode 100644 mdl/executor/cmd_businessevents_mock_test.go create mode 100644 mdl/executor/cmd_constants_mock_test.go create mode 100644 mdl/executor/cmd_datatransformer_mock_test.go create mode 100644 mdl/executor/cmd_dbconnection_mock_test.go create mode 100644 mdl/executor/cmd_entities_mock_test.go create mode 100644 mdl/executor/cmd_error_mock_test.go create mode 100644 mdl/executor/cmd_export_mappings_mock_test.go create mode 100644 mdl/executor/cmd_fragments_mock_test.go create mode 100644 mdl/executor/cmd_imagecollections_mock_test.go create mode 100644 mdl/executor/cmd_import_mappings_mock_test.go create mode 100644 mdl/executor/cmd_javaactions_mock_test.go create mode 100644 mdl/executor/cmd_javascript_actions_mock_test.go create mode 100644 mdl/executor/cmd_json_mock_test.go create mode 100644 mdl/executor/cmd_jsonstructures_mock_test.go create mode 100644 mdl/executor/cmd_mermaid_mock_test.go create mode 100644 mdl/executor/cmd_microflows_mock_test.go create mode 100644 mdl/executor/cmd_misc_mock_test.go create mode 100644 mdl/executor/cmd_modules_mock_test.go create mode 100644 mdl/executor/cmd_navigation_mock_test.go create mode 100644 mdl/executor/cmd_notconnected_mock_test.go create mode 100644 mdl/executor/cmd_odata_mock_test.go create mode 100644 mdl/executor/cmd_pages_mock_test.go create mode 100644 mdl/executor/cmd_published_rest_mock_test.go create mode 100644 mdl/executor/cmd_rest_clients_mock_test.go create mode 100644 mdl/executor/cmd_security_mock_test.go create mode 100644 mdl/executor/cmd_settings_mock_test.go create mode 100644 mdl/executor/cmd_workflows_mock_test.go create mode 100644 mdl/executor/cmd_write_handlers_mock_test.go create mode 100644 mdl/executor/mock_test_helpers_test.go diff --git a/internal/marketplace/types.go b/internal/marketplace/types.go index b73c1d7..37d54a9 100644 --- a/internal/marketplace/types.go +++ b/internal/marketplace/types.go @@ -18,7 +18,7 @@ var BaseURL = "https://marketplace-api.mendix.com" type Content struct { ContentID int `json:"contentId"` Publisher string `json:"publisher"` - Type string `json:"type"` // "Module", "Widget", "Theme", "Starter App", ... + Type string `json:"type"` // "Module", "Widget", "Theme", "Starter App", ... Categories []Category `json:"categories"` SupportCategory string `json:"supportCategory"` // "Platform", "Community", "Deprecated", ... LicenseURL string `json:"licenseUrl,omitempty"` diff --git a/mdl/backend/mock/backend.go b/mdl/backend/mock/backend.go index a330aa1..b6dc8a3 100644 --- a/mdl/backend/mock/backend.go +++ b/mdl/backend/mock/backend.go @@ -260,8 +260,16 @@ type MockBackend struct { FindAllCustomWidgetTypesFunc func(widgetID string) ([]*mpr.RawCustomWidgetType, error) // AgentEditorBackend - ListAgentEditorModelsFunc func() ([]*agenteditor.Model, error) - ListAgentEditorKnowledgeBasesFunc func() ([]*agenteditor.KnowledgeBase, error) - ListAgentEditorConsumedMCPServicesFunc func() ([]*agenteditor.ConsumedMCPService, error) - ListAgentEditorAgentsFunc func() ([]*agenteditor.Agent, error) + ListAgentEditorModelsFunc func() ([]*agenteditor.Model, error) + ListAgentEditorKnowledgeBasesFunc func() ([]*agenteditor.KnowledgeBase, error) + ListAgentEditorConsumedMCPServicesFunc func() ([]*agenteditor.ConsumedMCPService, error) + ListAgentEditorAgentsFunc func() ([]*agenteditor.Agent, error) + CreateAgentEditorModelFunc func(m *agenteditor.Model) error + DeleteAgentEditorModelFunc func(id string) error + CreateAgentEditorKnowledgeBaseFunc func(kb *agenteditor.KnowledgeBase) error + DeleteAgentEditorKnowledgeBaseFunc func(id string) error + CreateAgentEditorConsumedMCPServiceFunc func(svc *agenteditor.ConsumedMCPService) error + DeleteAgentEditorConsumedMCPServiceFunc func(id string) error + CreateAgentEditorAgentFunc func(a *agenteditor.Agent) error + DeleteAgentEditorAgentFunc func(id string) error } diff --git a/mdl/backend/mock/mock_infrastructure.go b/mdl/backend/mock/mock_infrastructure.go index e575f69..f0b6f9b 100644 --- a/mdl/backend/mock/mock_infrastructure.go +++ b/mdl/backend/mock/mock_infrastructure.go @@ -188,13 +188,58 @@ func (m *MockBackend) ListAgentEditorAgents() ([]*agenteditor.Agent, error) { return nil, nil } -func (m *MockBackend) CreateAgentEditorModel(_ *agenteditor.Model) error { return nil } -func (m *MockBackend) DeleteAgentEditorModel(_ string) error { return nil } -func (m *MockBackend) CreateAgentEditorKnowledgeBase(_ *agenteditor.KnowledgeBase) error { return nil } -func (m *MockBackend) DeleteAgentEditorKnowledgeBase(_ string) error { return nil } -func (m *MockBackend) CreateAgentEditorConsumedMCPService(_ *agenteditor.ConsumedMCPService) error { +func (m *MockBackend) CreateAgentEditorModel(model *agenteditor.Model) error { + if m.CreateAgentEditorModelFunc != nil { + return m.CreateAgentEditorModelFunc(model) + } + return nil +} + +func (m *MockBackend) DeleteAgentEditorModel(id string) error { + if m.DeleteAgentEditorModelFunc != nil { + return m.DeleteAgentEditorModelFunc(id) + } + return nil +} + +func (m *MockBackend) CreateAgentEditorKnowledgeBase(kb *agenteditor.KnowledgeBase) error { + if m.CreateAgentEditorKnowledgeBaseFunc != nil { + return m.CreateAgentEditorKnowledgeBaseFunc(kb) + } + return nil +} + +func (m *MockBackend) DeleteAgentEditorKnowledgeBase(id string) error { + if m.DeleteAgentEditorKnowledgeBaseFunc != nil { + return m.DeleteAgentEditorKnowledgeBaseFunc(id) + } + return nil +} + +func (m *MockBackend) CreateAgentEditorConsumedMCPService(svc *agenteditor.ConsumedMCPService) error { + if m.CreateAgentEditorConsumedMCPServiceFunc != nil { + return m.CreateAgentEditorConsumedMCPServiceFunc(svc) + } + return nil +} + +func (m *MockBackend) DeleteAgentEditorConsumedMCPService(id string) error { + if m.DeleteAgentEditorConsumedMCPServiceFunc != nil { + return m.DeleteAgentEditorConsumedMCPServiceFunc(id) + } + return nil +} + +func (m *MockBackend) CreateAgentEditorAgent(a *agenteditor.Agent) error { + if m.CreateAgentEditorAgentFunc != nil { + return m.CreateAgentEditorAgentFunc(a) + } + return nil +} + +func (m *MockBackend) DeleteAgentEditorAgent(id string) error { + if m.DeleteAgentEditorAgentFunc != nil { + return m.DeleteAgentEditorAgentFunc(id) + } return nil } -func (m *MockBackend) DeleteAgentEditorConsumedMCPService(_ string) error { return nil } -func (m *MockBackend) CreateAgentEditorAgent(_ *agenteditor.Agent) error { return nil } -func (m *MockBackend) DeleteAgentEditorAgent(_ string) error { return nil } diff --git a/mdl/executor/cmd_agenteditor_mock_test.go b/mdl/executor/cmd_agenteditor_mock_test.go new file mode 100644 index 0000000..7467511 --- /dev/null +++ b/mdl/executor/cmd_agenteditor_mock_test.go @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/agenteditor" +) + +func TestShowAgentEditorModels_Mock(t *testing.T) { + mod := mkModule("M") + m1 := &agenteditor.Model{ + BaseElement: model.BaseElement{ID: nextID("aem")}, + ContainerID: mod.ID, + Name: "GPT4", + Provider: "MxCloudGenAI", + DisplayName: "GPT-4 Turbo", + Key: &agenteditor.ConstantRef{QualifiedName: "M.APIKey"}, + } + + h := mkHierarchy(mod) + withContainer(h, m1.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorModelsFunc: func() ([]*agenteditor.Model, error) { return []*agenteditor.Model{m1}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showAgentEditorModels(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Qualified Name") + assertContainsStr(t, out, "Module") + assertContainsStr(t, out, "Provider") + assertContainsStr(t, out, "Key Constant") + assertContainsStr(t, out, "Display Name") + assertContainsStr(t, out, "M.GPT4") + assertContainsStr(t, out, "MxCloudGenAI") + assertContainsStr(t, out, "M.APIKey") + assertContainsStr(t, out, "GPT-4 Turbo") +} + +func TestDescribeAgentEditorModel_Mock(t *testing.T) { + mod := mkModule("M") + m1 := &agenteditor.Model{ + BaseElement: model.BaseElement{ID: nextID("aem")}, + ContainerID: mod.ID, + Name: "GPT4", + Provider: "MxCloudGenAI", + Key: &agenteditor.ConstantRef{QualifiedName: "M.APIKey"}, + } + + h := mkHierarchy(mod) + withContainer(h, m1.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorModelsFunc: func() ([]*agenteditor.Model, error) { return []*agenteditor.Model{m1}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeAgentEditorModel(ctx, ast.QualifiedName{Module: "M", Name: "GPT4"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE MODEL") + assertContainsStr(t, out, "Provider") + assertContainsStr(t, out, "Key") +} + +func TestShowAgentEditorAgents_Mock(t *testing.T) { + mod := mkModule("M") + a1 := &agenteditor.Agent{ + BaseElement: model.BaseElement{ID: nextID("aea")}, + ContainerID: mod.ID, + Name: "MyAgent", + UsageType: "Chat", + Model: &agenteditor.DocRef{QualifiedName: "M.GPT4"}, + Tools: []agenteditor.AgentTool{{ID: "t1", Enabled: true}}, + KBTools: []agenteditor.AgentKBTool{{ID: "kb1", Enabled: true}}, + } + + h := mkHierarchy(mod) + withContainer(h, a1.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorAgentsFunc: func() ([]*agenteditor.Agent, error) { return []*agenteditor.Agent{a1}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showAgentEditorAgents(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Qualified Name") + assertContainsStr(t, out, "Usage") + assertContainsStr(t, out, "Model") + assertContainsStr(t, out, "Tools") + assertContainsStr(t, out, "KBs") + assertContainsStr(t, out, "M.MyAgent") + assertContainsStr(t, out, "Chat") + assertContainsStr(t, out, "M.GPT4") +} + +func TestDescribeAgentEditorAgent_Mock(t *testing.T) { + mod := mkModule("M") + a1 := &agenteditor.Agent{ + BaseElement: model.BaseElement{ID: nextID("aea")}, + ContainerID: mod.ID, + Name: "MyAgent", + UsageType: "Chat", + Model: &agenteditor.DocRef{QualifiedName: "M.GPT4"}, + } + + h := mkHierarchy(mod) + withContainer(h, a1.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorAgentsFunc: func() ([]*agenteditor.Agent, error) { return []*agenteditor.Agent{a1}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeAgentEditorAgent(ctx, ast.QualifiedName{Module: "M", Name: "MyAgent"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE AGENT") + assertContainsStr(t, out, "UsageType") + assertContainsStr(t, out, "Model") +} + +func TestShowAgentEditorKnowledgeBases_Mock(t *testing.T) { + mod := mkModule("M") + kb := &agenteditor.KnowledgeBase{ + BaseElement: model.BaseElement{ID: nextID("aekb")}, + ContainerID: mod.ID, + Name: "MyKB", + Provider: "MxCloudGenAI", + Key: &agenteditor.ConstantRef{QualifiedName: "M.KBKey"}, + ModelDisplayName: "text-embedding-ada-002", + } + + h := mkHierarchy(mod) + withContainer(h, kb.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorKnowledgeBasesFunc: func() ([]*agenteditor.KnowledgeBase, error) { return []*agenteditor.KnowledgeBase{kb}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showAgentEditorKnowledgeBases(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Qualified Name") + assertContainsStr(t, out, "Provider") + assertContainsStr(t, out, "Key Constant") + assertContainsStr(t, out, "Embedding Model") + assertContainsStr(t, out, "M.MyKB") + assertContainsStr(t, out, "MxCloudGenAI") + assertContainsStr(t, out, "M.KBKey") + assertContainsStr(t, out, "text-embedding-ada-002") +} + +func TestDescribeAgentEditorKnowledgeBase_Mock(t *testing.T) { + mod := mkModule("M") + kb := &agenteditor.KnowledgeBase{ + BaseElement: model.BaseElement{ID: nextID("aekb")}, + ContainerID: mod.ID, + Name: "MyKB", + Provider: "MxCloudGenAI", + Key: &agenteditor.ConstantRef{QualifiedName: "M.KBKey"}, + } + + h := mkHierarchy(mod) + withContainer(h, kb.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorKnowledgeBasesFunc: func() ([]*agenteditor.KnowledgeBase, error) { return []*agenteditor.KnowledgeBase{kb}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeAgentEditorKnowledgeBase(ctx, ast.QualifiedName{Module: "M", Name: "MyKB"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE KNOWLEDGE BASE") + assertContainsStr(t, out, "Provider") +} + +func TestShowAgentEditorConsumedMCPServices_Mock(t *testing.T) { + mod := mkModule("M") + svc := &agenteditor.ConsumedMCPService{ + BaseElement: model.BaseElement{ID: nextID("aemcp")}, + ContainerID: mod.ID, + Name: "MySvc", + ProtocolVersion: "2025-03-26", + Version: "1.0.0", + ConnectionTimeoutSeconds: 30, + } + + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorConsumedMCPServicesFunc: func() ([]*agenteditor.ConsumedMCPService, error) { return []*agenteditor.ConsumedMCPService{svc}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showAgentEditorConsumedMCPServices(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Qualified Name") + assertContainsStr(t, out, "Protocol") + assertContainsStr(t, out, "Version") + assertContainsStr(t, out, "Timeout") + assertContainsStr(t, out, "M.MySvc") + assertContainsStr(t, out, "2025-03-26") + assertContainsStr(t, out, "1.0.0") +} + +func TestDescribeAgentEditorConsumedMCPService_Mock(t *testing.T) { + mod := mkModule("M") + svc := &agenteditor.ConsumedMCPService{ + BaseElement: model.BaseElement{ID: nextID("aemcp")}, + ContainerID: mod.ID, + Name: "MySvc", + ProtocolVersion: "2025-03-26", + Version: "1.0.0", + ConnectionTimeoutSeconds: 30, + } + + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorConsumedMCPServicesFunc: func() ([]*agenteditor.ConsumedMCPService, error) { return []*agenteditor.ConsumedMCPService{svc}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeAgentEditorConsumedMCPService(ctx, ast.QualifiedName{Module: "M", Name: "MySvc"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE CONSUMED MCP SERVICE") + assertContainsStr(t, out, "ProtocolVersion") +} diff --git a/mdl/executor/cmd_agenteditor_models.go b/mdl/executor/cmd_agenteditor_models.go index a43e59d..cefd65e 100644 --- a/mdl/executor/cmd_agenteditor_models.go +++ b/mdl/executor/cmd_agenteditor_models.go @@ -159,17 +159,17 @@ func execCreateAgentEditorModel(ctx *ExecContext, s *ast.CreateModelStmt) error } m := &agenteditor.Model{ - ContainerID: module.ID, - Name: s.Name.Name, + ContainerID: module.ID, + Name: s.Name.Name, Documentation: s.Documentation, - Provider: provider, - Key: keyRef, - DisplayName: s.DisplayName, - KeyName: s.KeyName, - KeyID: s.KeyID, - Environment: s.Environment, - ResourceName: s.ResourceName, - DeepLinkURL: s.DeepLinkURL, + Provider: provider, + Key: keyRef, + DisplayName: s.DisplayName, + KeyName: s.KeyName, + KeyID: s.KeyID, + Environment: s.Environment, + ResourceName: s.ResourceName, + DeepLinkURL: s.DeepLinkURL, } if err := ctx.Backend.CreateAgentEditorModel(m); err != nil { diff --git a/mdl/executor/cmd_agenteditor_write.go b/mdl/executor/cmd_agenteditor_write.go index 851f1c9..656ac55 100644 --- a/mdl/executor/cmd_agenteditor_write.go +++ b/mdl/executor/cmd_agenteditor_write.go @@ -144,17 +144,17 @@ func execCreateAgent(ctx *ExecContext, s *ast.CreateAgentStmt) error { } a := &agenteditor.Agent{ - ContainerID: module.ID, - Name: s.Name.Name, + ContainerID: module.ID, + Name: s.Name.Name, Documentation: s.Documentation, - Description: s.Description, - SystemPrompt: s.SystemPrompt, - UserPrompt: s.UserPrompt, - UsageType: s.UsageType, - MaxTokens: s.MaxTokens, - ToolChoice: s.ToolChoice, - Temperature: s.Temperature, - TopP: s.TopP, + Description: s.Description, + SystemPrompt: s.SystemPrompt, + UserPrompt: s.UserPrompt, + UsageType: s.UsageType, + MaxTokens: s.MaxTokens, + ToolChoice: s.ToolChoice, + Temperature: s.Temperature, + TopP: s.TopP, } // Resolve Model reference diff --git a/mdl/executor/cmd_associations_mock_test.go b/mdl/executor/cmd_associations_mock_test.go new file mode 100644 index 0000000..1d56e67 --- /dev/null +++ b/mdl/executor/cmd_associations_mock_test.go @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/domainmodel" +) + +func TestShowAssociations_Mock(t *testing.T) { + mod := mkModule("MyModule") + ent1 := mkEntity(mod.ID, "Order") + ent2 := mkEntity(mod.ID, "Customer") + assoc := mkAssociation(mod.ID, "Order_Customer", ent1.ID, ent2.ID) + + dm := &domainmodel.DomainModel{ + BaseElement: model.BaseElement{ID: nextID("dm")}, + ContainerID: mod.ID, + Entities: []*domainmodel.Entity{ent1, ent2}, + Associations: []*domainmodel.Association{assoc}, + } + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil }, + ListDomainModelsFunc: func() ([]*domainmodel.DomainModel, error) { return []*domainmodel.DomainModel{dm}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showAssociations(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "MyModule.Order_Customer") + assertContainsStr(t, out, "MyModule.Order") + assertContainsStr(t, out, "MyModule.Customer") + assertContainsStr(t, out, "Reference") + assertContainsStr(t, out, "(1 associations)") +} + +func TestShowAssociations_Mock_FilterByModule(t *testing.T) { + mod1 := mkModule("Sales") + mod2 := mkModule("HR") + ent1 := mkEntity(mod1.ID, "Order") + ent2 := mkEntity(mod1.ID, "Product") + ent3 := mkEntity(mod2.ID, "Employee") + ent4 := mkEntity(mod2.ID, "Department") + + dm1 := &domainmodel.DomainModel{ + BaseElement: model.BaseElement{ID: nextID("dm")}, + ContainerID: mod1.ID, + Entities: []*domainmodel.Entity{ent1, ent2}, + Associations: []*domainmodel.Association{mkAssociation(mod1.ID, "Order_Product", ent1.ID, ent2.ID)}, + } + dm2 := &domainmodel.DomainModel{ + BaseElement: model.BaseElement{ID: nextID("dm")}, + ContainerID: mod2.ID, + Entities: []*domainmodel.Entity{ent3, ent4}, + Associations: []*domainmodel.Association{mkAssociation(mod2.ID, "Employee_Dept", ent3.ID, ent4.ID)}, + } + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod1, mod2}, nil }, + ListDomainModelsFunc: func() ([]*domainmodel.DomainModel, error) { return []*domainmodel.DomainModel{dm1, dm2}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showAssociations(ctx, "HR")) + + out := buf.String() + assertNotContainsStr(t, out, "Sales.Order_Product") + assertContainsStr(t, out, "HR.Employee_Dept") + assertContainsStr(t, out, "(1 associations)") +} diff --git a/mdl/executor/cmd_businessevents_mock_test.go b/mdl/executor/cmd_businessevents_mock_test.go new file mode 100644 index 0000000..c874b61 --- /dev/null +++ b/mdl/executor/cmd_businessevents_mock_test.go @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" +) + +func TestShowBusinessEventServices_Mock(t *testing.T) { + mod := mkModule("MyModule") + svc := &model.BusinessEventService{ + BaseElement: model.BaseElement{ID: nextID("bes")}, + ContainerID: mod.ID, + Name: "OrderEvents", + } + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListBusinessEventServicesFunc: func() ([]*model.BusinessEventService, error) { + return []*model.BusinessEventService{svc}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showBusinessEventServices(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "QualifiedName") + assertContainsStr(t, out, "MyModule.OrderEvents") +} + +func TestShowBusinessEventClients_Mock(t *testing.T) { + ctx, buf := newMockCtx(t) + assertNoError(t, showBusinessEventClients(ctx, "")) + assertContainsStr(t, buf.String(), "not yet implemented") +} + +func TestDescribeBusinessEventService_Mock(t *testing.T) { + mod := mkModule("MyModule") + svc := &model.BusinessEventService{ + BaseElement: model.BaseElement{ID: nextID("bes")}, + ContainerID: mod.ID, + Name: "OrderEvents", + Definition: &model.BusinessEventDefinition{ + ServiceName: "com.example.orders", + EventNamePrefix: "order", + Channels: []*model.BusinessEventChannel{ + { + ChannelName: "ch1", + Messages: []*model.BusinessEventMessage{ + {MessageName: "OrderCreated"}, + }, + }, + }, + }, + } + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListBusinessEventServicesFunc: func() ([]*model.BusinessEventService, error) { + return []*model.BusinessEventService{svc}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeBusinessEventService(ctx, ast.QualifiedName{Module: "MyModule", Name: "OrderEvents"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE OR REPLACE BUSINESS EVENT SERVICE") + assertContainsStr(t, out, "MyModule.OrderEvents") +} diff --git a/mdl/executor/cmd_constants_mock_test.go b/mdl/executor/cmd_constants_mock_test.go new file mode 100644 index 0000000..d18ebb1 --- /dev/null +++ b/mdl/executor/cmd_constants_mock_test.go @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" +) + +func TestShowConstants_Mock(t *testing.T) { + mod := mkModule("MyModule") + c1 := mkConstant(mod.ID, "AppURL", "String", "https://example.com") + c2 := mkConstant(mod.ID, "MaxRetries", "Integer", "3") + c2.ExposedToClient = true + + h := mkHierarchy(mod) + withContainer(h, c1.ContainerID, mod.ID) + withContainer(h, c2.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConstantsFunc: func() ([]*model.Constant, error) { return []*model.Constant{c1, c2}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showConstants(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "MyModule.AppURL") + assertContainsStr(t, out, "MyModule.MaxRetries") + assertContainsStr(t, out, "https://example.com") + assertContainsStr(t, out, "Yes") + assertContainsStr(t, out, "(2 constants)") +} + +func TestShowConstants_Mock_FilterByModule(t *testing.T) { + mod1 := mkModule("Alpha") + mod2 := mkModule("Beta") + c1 := mkConstant(mod1.ID, "Key1", "String", "val1") + c2 := mkConstant(mod2.ID, "Key2", "Integer", "42") + + h := mkHierarchy(mod1, mod2) + withContainer(h, c1.ContainerID, mod1.ID) + withContainer(h, c2.ContainerID, mod2.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConstantsFunc: func() ([]*model.Constant, error) { return []*model.Constant{c1, c2}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showConstants(ctx, "Beta")) + + out := buf.String() + assertNotContainsStr(t, out, "Alpha.Key1") + assertContainsStr(t, out, "Beta.Key2") + assertContainsStr(t, out, "(1 constants)") +} + +func TestShowConstants_Mock_Empty(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConstantsFunc: func() ([]*model.Constant, error) { return nil, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(mkHierarchy())) + assertNoError(t, showConstants(ctx, "")) + assertContainsStr(t, buf.String(), "No constants found") +} + +func TestDescribeConstant_Mock(t *testing.T) { + mod := mkModule("MyModule") + c := mkConstant(mod.ID, "AppURL", "String", "https://example.com") + + h := mkHierarchy(mod) + withContainer(h, c.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConstantsFunc: func() ([]*model.Constant, error) { return []*model.Constant{c}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeConstant(ctx, ast.QualifiedName{Module: "MyModule", Name: "AppURL"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE OR MODIFY CONSTANT MyModule.AppURL") + assertContainsStr(t, out, "String") +} + +func TestDescribeConstant_Mock_NotFound(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConstantsFunc: func() ([]*model.Constant, error) { return nil, nil }, + } + + ctx, _ := newMockCtx(t, withBackend(mb), withHierarchy(h)) + err := describeConstant(ctx, ast.QualifiedName{Module: "MyModule", Name: "Missing"}) + assertError(t, err) +} diff --git a/mdl/executor/cmd_datatransformer_mock_test.go b/mdl/executor/cmd_datatransformer_mock_test.go new file mode 100644 index 0000000..fdf2998 --- /dev/null +++ b/mdl/executor/cmd_datatransformer_mock_test.go @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" +) + +func TestListDataTransformers_Mock(t *testing.T) { + mod := mkModule("ETL") + dt := &model.DataTransformer{ + BaseElement: model.BaseElement{ID: nextID("dt")}, + ContainerID: mod.ID, + Name: "TransformOrders", + SourceType: "Entity", + } + + h := mkHierarchy(mod) + withContainer(h, dt.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListDataTransformersFunc: func() ([]*model.DataTransformer, error) { return []*model.DataTransformer{dt}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, listDataTransformers(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Qualified Name") + assertContainsStr(t, out, "ETL.TransformOrders") +} + +func TestDescribeDataTransformer_Mock(t *testing.T) { + mod := mkModule("ETL") + dt := &model.DataTransformer{ + BaseElement: model.BaseElement{ID: nextID("dt")}, + ContainerID: mod.ID, + Name: "TransformOrders", + SourceType: "Entity", + } + + h := mkHierarchy(mod) + withContainer(h, dt.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListDataTransformersFunc: func() ([]*model.DataTransformer, error) { return []*model.DataTransformer{dt}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeDataTransformer(ctx, ast.QualifiedName{Module: "ETL", Name: "TransformOrders"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE DATA TRANSFORMER") +} diff --git a/mdl/executor/cmd_dbconnection_mock_test.go b/mdl/executor/cmd_dbconnection_mock_test.go new file mode 100644 index 0000000..bfd5460 --- /dev/null +++ b/mdl/executor/cmd_dbconnection_mock_test.go @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" +) + +func TestShowDatabaseConnections_Mock(t *testing.T) { + mod := mkModule("DataMod") + conn := &model.DatabaseConnection{ + BaseElement: model.BaseElement{ID: nextID("dbc")}, + ContainerID: mod.ID, + Name: "MyDB", + DatabaseType: "PostgreSQL", + } + + h := mkHierarchy(mod) + withContainer(h, conn.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListDatabaseConnectionsFunc: func() ([]*model.DatabaseConnection, error) { return []*model.DatabaseConnection{conn}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showDatabaseConnections(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Qualified Name") + assertContainsStr(t, out, "DataMod.MyDB") +} + +func TestDescribeDatabaseConnection_Mock(t *testing.T) { + mod := mkModule("DataMod") + conn := &model.DatabaseConnection{ + BaseElement: model.BaseElement{ID: nextID("dbc")}, + ContainerID: mod.ID, + Name: "MyDB", + DatabaseType: "PostgreSQL", + } + + h := mkHierarchy(mod) + withContainer(h, conn.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListDatabaseConnectionsFunc: func() ([]*model.DatabaseConnection, error) { return []*model.DatabaseConnection{conn}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeDatabaseConnection(ctx, ast.QualifiedName{Module: "DataMod", Name: "MyDB"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE DATABASE CONNECTION") +} diff --git a/mdl/executor/cmd_entities_mock_test.go b/mdl/executor/cmd_entities_mock_test.go new file mode 100644 index 0000000..556e1b8 --- /dev/null +++ b/mdl/executor/cmd_entities_mock_test.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/domainmodel" +) + +func TestShowEntities_Mock(t *testing.T) { + mod := mkModule("MyModule") + ent1 := mkEntity(mod.ID, "Customer") + ent2 := mkEntity(mod.ID, "Order") + + dm := mkDomainModel(mod.ID, ent1, ent2) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil }, + ListDomainModelsFunc: func() ([]*domainmodel.DomainModel, error) { return []*domainmodel.DomainModel{dm}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showEntities(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "MyModule.Customer") + assertContainsStr(t, out, "MyModule.Order") + assertContainsStr(t, out, "Persistent") + assertContainsStr(t, out, "(2 entities)") +} + +func TestShowEntities_Mock_FilterByModule(t *testing.T) { + mod1 := mkModule("Sales") + mod2 := mkModule("HR") + ent1 := mkEntity(mod1.ID, "Product") + ent2 := mkEntity(mod2.ID, "Employee") + + dm1 := mkDomainModel(mod1.ID, ent1) + dm2 := mkDomainModel(mod2.ID, ent2) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod1, mod2}, nil }, + ListDomainModelsFunc: func() ([]*domainmodel.DomainModel, error) { return []*domainmodel.DomainModel{dm1, dm2}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showEntities(ctx, "HR")) + + out := buf.String() + assertNotContainsStr(t, out, "Sales.Product") + assertContainsStr(t, out, "HR.Employee") + assertContainsStr(t, out, "(1 entities)") +} diff --git a/mdl/executor/cmd_enumerations_mock_test.go b/mdl/executor/cmd_enumerations_mock_test.go index 57ef46d..19f6cc6 100644 --- a/mdl/executor/cmd_enumerations_mock_test.go +++ b/mdl/executor/cmd_enumerations_mock_test.go @@ -3,129 +3,97 @@ package executor import ( - "bytes" - "context" - "strings" "testing" + "github.com/mendixlabs/mxcli/mdl/ast" "github.com/mendixlabs/mxcli/mdl/backend/mock" "github.com/mendixlabs/mxcli/model" ) -// TestShowEnumerations_Mock demonstrates testing a handler with a MockBackend -// instead of a real .mpr file. The handler under test is showEnumerations, -// which calls ctx.Backend.ListEnumerations() and writes a table to ctx.Output. func TestShowEnumerations_Mock(t *testing.T) { - modID := model.ID("mod-1") - enumID := model.ID("enum-1") + mod := mkModule("MyModule") + enum := mkEnumeration(mod.ID, "Color", "Red", "Green", "Blue") - mb := &mock.MockBackend{ - IsConnectedFunc: func() bool { return true }, - ListEnumerationsFunc: func() ([]*model.Enumeration, error) { - return []*model.Enumeration{ - { - BaseElement: model.BaseElement{ID: enumID}, - ContainerID: modID, - Name: "Color", - Values: []model.EnumerationValue{ - {Name: "Red"}, - {Name: "Green"}, - {Name: "Blue"}, - }, - }, - }, nil - }, - } + h := mkHierarchy(mod) + withContainer(h, enum.ContainerID, mod.ID) - // Pre-populate hierarchy so getHierarchy skips the e.reader path. - hierarchy := &ContainerHierarchy{ - moduleIDs: map[model.ID]bool{modID: true}, - moduleNames: map[model.ID]string{modID: "MyModule"}, - containerParent: map[model.ID]model.ID{enumID: modID}, - folderNames: map[model.ID]string{}, - } - - var buf bytes.Buffer - ctx := &ExecContext{ - Context: context.Background(), - Backend: mb, - Output: &buf, - Format: FormatTable, - Cache: &executorCache{hierarchy: hierarchy}, + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListEnumerationsFunc: func() ([]*model.Enumeration, error) { return []*model.Enumeration{enum}, nil }, } - if err := showEnumerations(ctx, ""); err != nil { - t.Fatalf("showEnumerations returned error: %v", err) - } + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showEnumerations(ctx, "")) out := buf.String() - - // Verify table contains our enumeration data. - if !strings.Contains(out, "MyModule.Color") { - t.Errorf("expected qualified name 'MyModule.Color' in output, got:\n%s", out) - } - if !strings.Contains(out, "3") { - t.Errorf("expected value count '3' in output, got:\n%s", out) - } - if !strings.Contains(out, "(1 enumerations)") { - t.Errorf("expected summary '(1 enumerations)' in output, got:\n%s", out) - } + assertContainsStr(t, out, "MyModule.Color") + assertContainsStr(t, out, "| 3") + assertContainsStr(t, out, "(1 enumerations)") } -// TestShowEnumerations_Mock_FilterByModule verifies that passing a module name -// filters the output to only that module's enumerations. func TestShowEnumerations_Mock_FilterByModule(t *testing.T) { - mod1 := model.ID("mod-1") - mod2 := model.ID("mod-2") + mod1 := mkModule("Alpha") + mod2 := mkModule("Beta") + e1 := mkEnumeration(mod1.ID, "Color", "Red") + e2 := mkEnumeration(mod2.ID, "Size", "S", "M") + + h := mkHierarchy(mod1, mod2) + withContainer(h, e1.ContainerID, mod1.ID) + withContainer(h, e2.ContainerID, mod2.ID) mb := &mock.MockBackend{ - IsConnectedFunc: func() bool { return true }, - ListEnumerationsFunc: func() ([]*model.Enumeration, error) { - return []*model.Enumeration{ - { - BaseElement: model.BaseElement{ID: model.ID("e1")}, - ContainerID: mod1, - Name: "Color", - Values: []model.EnumerationValue{{Name: "Red"}}, - }, - { - BaseElement: model.BaseElement{ID: model.ID("e2")}, - ContainerID: mod2, - Name: "Size", - Values: []model.EnumerationValue{{Name: "S"}, {Name: "M"}}, - }, - }, nil - }, + IsConnectedFunc: func() bool { return true }, + ListEnumerationsFunc: func() ([]*model.Enumeration, error) { return []*model.Enumeration{e1, e2}, nil }, } - hierarchy := &ContainerHierarchy{ - moduleIDs: map[model.ID]bool{mod1: true, mod2: true}, - moduleNames: map[model.ID]string{mod1: "Alpha", mod2: "Beta"}, - containerParent: map[model.ID]model.ID{}, - folderNames: map[model.ID]string{}, - } + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showEnumerations(ctx, "Beta")) - var buf bytes.Buffer - ctx := &ExecContext{ - Context: context.Background(), - Backend: mb, - Output: &buf, - Format: FormatTable, - Cache: &executorCache{hierarchy: hierarchy}, + out := buf.String() + assertNotContainsStr(t, out, "Alpha.Color") + assertContainsStr(t, out, "Beta.Size") + assertContainsStr(t, out, "(1 enumerations)") +} + +func TestDescribeEnumeration_Mock(t *testing.T) { + mod := mkModule("MyModule") + enum := &model.Enumeration{ + BaseElement: model.BaseElement{ID: nextID("enum")}, + ContainerID: mod.ID, + Name: "Status", + Values: []model.EnumerationValue{ + {BaseElement: model.BaseElement{ID: nextID("ev")}, Name: "Active", Caption: &model.Text{Translations: map[string]string{"en_US": "Active"}}}, + {BaseElement: model.BaseElement{ID: nextID("ev")}, Name: "Inactive", Caption: &model.Text{Translations: map[string]string{"en_US": "Inactive"}}}, + }, } - if err := showEnumerations(ctx, "Beta"); err != nil { - t.Fatalf("showEnumerations returned error: %v", err) + h := mkHierarchy(mod) + withContainer(h, enum.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListEnumerationsFunc: func() ([]*model.Enumeration, error) { return []*model.Enumeration{enum}, nil }, } + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeEnumeration(ctx, ast.QualifiedName{Module: "MyModule", Name: "Status"})) + out := buf.String() - if strings.Contains(out, "Alpha.Color") { - t.Errorf("should not contain Alpha.Color when filtering by Beta:\n%s", out) - } - if !strings.Contains(out, "Beta.Size") { - t.Errorf("expected Beta.Size in output:\n%s", out) - } - if !strings.Contains(out, "(1 enumerations)") { - t.Errorf("expected 1 enumeration in summary:\n%s", out) + assertContainsStr(t, out, "CREATE OR MODIFY ENUMERATION MyModule.Status") + assertContainsStr(t, out, "Active") + assertContainsStr(t, out, "Inactive") +} + +func TestDescribeEnumeration_Mock_NotFound(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListEnumerationsFunc: func() ([]*model.Enumeration, error) { return nil, nil }, } + + ctx, _ := newMockCtx(t, withBackend(mb), withHierarchy(h)) + err := describeEnumeration(ctx, ast.QualifiedName{Module: "MyModule", Name: "Missing"}) + assertError(t, err) } diff --git a/mdl/executor/cmd_error_mock_test.go b/mdl/executor/cmd_error_mock_test.go new file mode 100644 index 0000000..d60fa2d --- /dev/null +++ b/mdl/executor/cmd_error_mock_test.go @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "fmt" + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/agenteditor" + "github.com/mendixlabs/mxcli/sdk/microflows" + "github.com/mendixlabs/mxcli/sdk/mpr" + "github.com/mendixlabs/mxcli/sdk/pages" + "github.com/mendixlabs/mxcli/sdk/security" + "github.com/mendixlabs/mxcli/sdk/workflows" +) + +// errBackend is a sentinel used in backend-error tests. +var errBackend = fmt.Errorf("backend failure") + +func TestShowEnumerations_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListEnumerationsFunc: func() ([]*model.Enumeration, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showEnumerations(ctx, "")) +} + +func TestShowConstants_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConstantsFunc: func() ([]*model.Constant, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showConstants(ctx, "")) +} + +func TestShowMicroflows_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showMicroflows(ctx, "")) +} + +func TestShowNanoflows_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListNanoflowsFunc: func() ([]*microflows.Nanoflow, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showNanoflows(ctx, "")) +} + +func TestShowPages_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPagesFunc: func() ([]*pages.Page, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showPages(ctx, "")) +} + +func TestShowSnippets_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListSnippetsFunc: func() ([]*pages.Snippet, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showSnippets(ctx, "")) +} + +func TestShowLayouts_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListLayoutsFunc: func() ([]*pages.Layout, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showLayouts(ctx, "")) +} + +func TestShowWorkflows_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListWorkflowsFunc: func() ([]*workflows.Workflow, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showWorkflows(ctx, "")) +} + +func TestShowODataClients_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConsumedODataServicesFunc: func() ([]*model.ConsumedODataService, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showODataClients(ctx, "")) +} + +func TestShowODataServices_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPublishedODataServicesFunc: func() ([]*model.PublishedODataService, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showODataServices(ctx, "")) +} + +func TestShowRestClients_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConsumedRestServicesFunc: func() ([]*model.ConsumedRestService, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showRestClients(ctx, "")) +} + +func TestShowPublishedRestServices_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPublishedRestServicesFunc: func() ([]*model.PublishedRestService, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showPublishedRestServices(ctx, "")) +} + +func TestShowJavaActions_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListJavaActionsFunc: func() ([]*mpr.JavaAction, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showJavaActions(ctx, "")) +} + +func TestShowJavaScriptActions_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListJavaScriptActionsFunc: func() ([]*mpr.JavaScriptAction, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showJavaScriptActions(ctx, "")) +} + +func TestShowDatabaseConnections_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListDatabaseConnectionsFunc: func() ([]*model.DatabaseConnection, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showDatabaseConnections(ctx, "")) +} + +func TestShowImageCollections_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListImageCollectionsFunc: func() ([]*mpr.ImageCollection, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showImageCollections(ctx, "")) +} + +func TestShowJsonStructures_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListJsonStructuresFunc: func() ([]*mpr.JsonStructure, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showJsonStructures(ctx, "")) +} + +func TestShowNavigation_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetNavigationFunc: func() (*mpr.NavigationDocument, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showNavigation(ctx)) +} + +func TestShowProjectSecurity_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showProjectSecurity(ctx)) +} + +func TestShowModuleRoles_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModuleSecurityFunc: func() ([]*security.ModuleSecurity, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showModuleRoles(ctx, "")) +} + +func TestShowUserRoles_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showUserRoles(ctx)) +} + +func TestShowDemoUsers_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showDemoUsers(ctx)) +} + +func TestShowBusinessEventServices_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListBusinessEventServicesFunc: func() ([]*model.BusinessEventService, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showBusinessEventServices(ctx, "")) +} + +func TestShowAgentEditorModels_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorModelsFunc: func() ([]*agenteditor.Model, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showAgentEditorModels(ctx, "")) +} + +func TestShowAgentEditorAgents_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorAgentsFunc: func() ([]*agenteditor.Agent, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showAgentEditorAgents(ctx, "")) +} + +func TestShowAgentEditorKnowledgeBases_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorKnowledgeBasesFunc: func() ([]*agenteditor.KnowledgeBase, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showAgentEditorKnowledgeBases(ctx, "")) +} + +func TestShowAgentEditorMCPServices_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorConsumedMCPServicesFunc: func() ([]*agenteditor.ConsumedMCPService, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showAgentEditorConsumedMCPServices(ctx, "")) +} + +func TestListDataTransformers_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListDataTransformersFunc: func() ([]*model.DataTransformer, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, listDataTransformers(ctx, "")) +} + +func TestShowExportMappings_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListExportMappingsFunc: func() ([]*model.ExportMapping, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showExportMappings(ctx, "")) +} + +func TestShowImportMappings_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListImportMappingsFunc: func() ([]*model.ImportMapping, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showImportMappings(ctx, "")) +} + +func TestShowSettings_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSettingsFunc: func() (*model.ProjectSettings, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, showSettings(ctx)) +} + +// Describe handler backend errors + +func TestDescribeEnumeration_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListEnumerationsFunc: func() ([]*model.Enumeration, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeEnumeration(ctx, ast.QualifiedName{Module: "M", Name: "E"})) +} + +func TestDescribeConstant_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConstantsFunc: func() ([]*model.Constant, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeConstant(ctx, ast.QualifiedName{Module: "M", Name: "C"})) +} + +func TestDescribeMicroflow_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeMicroflow(ctx, ast.QualifiedName{Module: "M", Name: "F"})) +} + +func TestDescribeWorkflow_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListWorkflowsFunc: func() ([]*workflows.Workflow, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeWorkflow(ctx, ast.QualifiedName{Module: "M", Name: "W"})) +} + +func TestDescribeNavigation_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetNavigationFunc: func() (*mpr.NavigationDocument, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeNavigation(ctx, ast.QualifiedName{Module: "M", Name: "N"})) +} + +func TestDescribeODataClient_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConsumedODataServicesFunc: func() ([]*model.ConsumedODataService, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeODataClient(ctx, ast.QualifiedName{Module: "M", Name: "C"})) +} + +func TestDescribeODataService_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPublishedODataServicesFunc: func() ([]*model.PublishedODataService, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeODataService(ctx, ast.QualifiedName{Module: "M", Name: "S"})) +} + +func TestDescribeRestClient_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConsumedRestServicesFunc: func() ([]*model.ConsumedRestService, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeRestClient(ctx, ast.QualifiedName{Module: "M", Name: "R"})) +} + +func TestDescribeImageCollection_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListImageCollectionsFunc: func() ([]*mpr.ImageCollection, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeImageCollection(ctx, ast.QualifiedName{Module: "M", Name: "I"})) +} + +func TestDescribeDatabaseConnection_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListDatabaseConnectionsFunc: func() ([]*model.DatabaseConnection, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeDatabaseConnection(ctx, ast.QualifiedName{Module: "M", Name: "D"})) +} + +func TestDescribeModuleRole_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModuleSecurityFunc: func() ([]*security.ModuleSecurity, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeModuleRole(ctx, ast.QualifiedName{Module: "M", Name: "R"})) +} + +func TestDescribeUserRole_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeUserRole(ctx, ast.QualifiedName{Module: "", Name: "Admin"})) +} + +func TestDescribeDemoUser_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, describeDemoUser(ctx, "demo")) +} + +// Write handler backend errors + +func TestExecCreateModule_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, execCreateModule(ctx, &ast.CreateModuleStmt{Name: "M"})) +} + +func TestExecCreateEnumeration_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListEnumerationsFunc: func() ([]*model.Enumeration, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, execCreateEnumeration(ctx, &ast.CreateEnumerationStmt{ + Name: ast.QualifiedName{Module: "M", Name: "E"}, + })) +} + +func TestExecDropMicroflow_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, execDropMicroflow(ctx, &ast.DropMicroflowStmt{ + Name: ast.QualifiedName{Module: "M", Name: "F"}, + })) +} + +func TestExecDropPage_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPagesFunc: func() ([]*pages.Page, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, execDropPage(ctx, &ast.DropPageStmt{ + Name: ast.QualifiedName{Module: "M", Name: "P"}, + })) +} + +func TestExecDropSnippet_Mock_BackendError(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListSnippetsFunc: func() ([]*pages.Snippet, error) { return nil, errBackend }, + } + ctx, _ := newMockCtx(t, withBackend(mb)) + assertError(t, execDropSnippet(ctx, &ast.DropSnippetStmt{ + Name: ast.QualifiedName{Module: "M", Name: "S"}, + })) +} diff --git a/mdl/executor/cmd_export_mappings_mock_test.go b/mdl/executor/cmd_export_mappings_mock_test.go new file mode 100644 index 0000000..3746286 --- /dev/null +++ b/mdl/executor/cmd_export_mappings_mock_test.go @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" +) + +func TestShowExportMappings_Mock(t *testing.T) { + mod := mkModule("Integration") + em := &model.ExportMapping{ + BaseElement: model.BaseElement{ID: nextID("em")}, + ContainerID: mod.ID, + Name: "ExportOrders", + } + + h := mkHierarchy(mod) + withContainer(h, em.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListExportMappingsFunc: func() ([]*model.ExportMapping, error) { return []*model.ExportMapping{em}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showExportMappings(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Export Mapping") + assertContainsStr(t, out, "Integration.ExportOrders") +} diff --git a/mdl/executor/cmd_fragments_mock_test.go b/mdl/executor/cmd_fragments_mock_test.go new file mode 100644 index 0000000..c759a4e --- /dev/null +++ b/mdl/executor/cmd_fragments_mock_test.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" +) + +func TestShowFragments_Mock(t *testing.T) { + ctx, buf := newMockCtx(t) + ctx.Fragments = map[string]*ast.DefineFragmentStmt{ + "myFrag": {Name: "myFrag"}, + } + + assertNoError(t, showFragments(ctx)) + + out := buf.String() + assertContainsStr(t, out, "myFrag") +} + +func TestShowFragments_Empty_Mock(t *testing.T) { + ctx, buf := newMockCtx(t) + ctx.Fragments = map[string]*ast.DefineFragmentStmt{} + + assertNoError(t, showFragments(ctx)) + + out := buf.String() + assertContainsStr(t, out, "No fragments defined.") +} diff --git a/mdl/executor/cmd_imagecollections_mock_test.go b/mdl/executor/cmd_imagecollections_mock_test.go new file mode 100644 index 0000000..9b5f754 --- /dev/null +++ b/mdl/executor/cmd_imagecollections_mock_test.go @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/mpr" +) + +func TestShowImageCollections_Mock(t *testing.T) { + mod := mkModule("Icons") + ic := &mpr.ImageCollection{ + BaseElement: model.BaseElement{ID: nextID("ic")}, + ContainerID: mod.ID, + Name: "AppIcons", + ExportLevel: "Hidden", + } + + h := mkHierarchy(mod) + withContainer(h, ic.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListImageCollectionsFunc: func() ([]*mpr.ImageCollection, error) { return []*mpr.ImageCollection{ic}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showImageCollections(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Image Collection") + assertContainsStr(t, out, "Icons.AppIcons") +} + +func TestDescribeImageCollection_Mock(t *testing.T) { + mod := mkModule("Icons") + ic := &mpr.ImageCollection{ + BaseElement: model.BaseElement{ID: nextID("ic")}, + ContainerID: mod.ID, + Name: "AppIcons", + ExportLevel: "Hidden", + } + + h := mkHierarchy(mod) + withContainer(h, ic.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListImageCollectionsFunc: func() ([]*mpr.ImageCollection, error) { return []*mpr.ImageCollection{ic}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeImageCollection(ctx, ast.QualifiedName{Module: "Icons", Name: "AppIcons"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE OR REPLACE IMAGE COLLECTION") +} diff --git a/mdl/executor/cmd_import_mappings_mock_test.go b/mdl/executor/cmd_import_mappings_mock_test.go new file mode 100644 index 0000000..1d9eeab --- /dev/null +++ b/mdl/executor/cmd_import_mappings_mock_test.go @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" +) + +func TestShowImportMappings_Mock(t *testing.T) { + mod := mkModule("Integration") + im := &model.ImportMapping{ + BaseElement: model.BaseElement{ID: nextID("im")}, + ContainerID: mod.ID, + Name: "ImportOrders", + } + + h := mkHierarchy(mod) + withContainer(h, im.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListImportMappingsFunc: func() ([]*model.ImportMapping, error) { return []*model.ImportMapping{im}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showImportMappings(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Import Mapping") + assertContainsStr(t, out, "Integration.ImportOrders") +} diff --git a/mdl/executor/cmd_javaactions_mock_test.go b/mdl/executor/cmd_javaactions_mock_test.go new file mode 100644 index 0000000..bd33113 --- /dev/null +++ b/mdl/executor/cmd_javaactions_mock_test.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "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/mpr" +) + +func TestShowJavaActions_Mock(t *testing.T) { + mod := mkModule("MyModule") + ja := &mpr.JavaAction{ + BaseElement: model.BaseElement{ID: nextID("ja")}, + ContainerID: mod.ID, + Name: "DoSomething", + } + + h := mkHierarchy(mod) + withContainer(h, ja.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListJavaActionsFunc: func() ([]*mpr.JavaAction, error) { return []*mpr.JavaAction{ja}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showJavaActions(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Qualified Name") + assertContainsStr(t, out, "MyModule.DoSomething") +} + +func TestDescribeJavaAction_Mock(t *testing.T) { + mod := mkModule("MyModule") + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ReadJavaActionByNameFunc: func(qn string) (*javaactions.JavaAction, error) { + return &javaactions.JavaAction{ + BaseElement: model.BaseElement{ID: nextID("ja")}, + ContainerID: mod.ID, + Name: "DoSomething", + }, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, describeJavaAction(ctx, ast.QualifiedName{Module: "MyModule", Name: "DoSomething"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE JAVA ACTION") +} diff --git a/mdl/executor/cmd_javascript_actions_mock_test.go b/mdl/executor/cmd_javascript_actions_mock_test.go new file mode 100644 index 0000000..dd47c04 --- /dev/null +++ b/mdl/executor/cmd_javascript_actions_mock_test.go @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/mpr" +) + +func TestShowJavaScriptActions_Mock(t *testing.T) { + mod := mkModule("WebMod") + jsa := &mpr.JavaScriptAction{ + BaseElement: model.BaseElement{ID: nextID("jsa")}, + ContainerID: mod.ID, + Name: "ShowAlert", + Platform: "Web", + } + + h := mkHierarchy(mod) + withContainer(h, jsa.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListJavaScriptActionsFunc: func() ([]*mpr.JavaScriptAction, error) { return []*mpr.JavaScriptAction{jsa}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showJavaScriptActions(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Qualified Name") + assertContainsStr(t, out, "WebMod.ShowAlert") +} + +func TestDescribeJavaScriptAction_Mock(t *testing.T) { + mod := mkModule("WebMod") + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ReadJavaScriptActionByNameFunc: func(qn string) (*mpr.JavaScriptAction, error) { + return &mpr.JavaScriptAction{ + BaseElement: model.BaseElement{ID: nextID("jsa")}, + ContainerID: mod.ID, + Name: "ShowAlert", + Platform: "Web", + }, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, describeJavaScriptAction(ctx, ast.QualifiedName{Module: "WebMod", Name: "ShowAlert"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE JAVASCRIPT ACTION") +} diff --git a/mdl/executor/cmd_json_mock_test.go b/mdl/executor/cmd_json_mock_test.go new file mode 100644 index 0000000..9986fe5 --- /dev/null +++ b/mdl/executor/cmd_json_mock_test.go @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/agenteditor" + "github.com/mendixlabs/mxcli/sdk/microflows" + "github.com/mendixlabs/mxcli/sdk/mpr" + "github.com/mendixlabs/mxcli/sdk/pages" + "github.com/mendixlabs/mxcli/sdk/security" + "github.com/mendixlabs/mxcli/sdk/workflows" +) + +func TestShowEnumerations_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + enum := mkEnumeration(mod.ID, "Status", "Active", "Inactive") + withContainer(h, enum.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListEnumerationsFunc: func() ([]*model.Enumeration, error) { return []*model.Enumeration{enum}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showEnumerations(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "Status") +} + +func TestShowConstants_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + c := mkConstant(mod.ID, "Timeout", "Integer", "30") + withContainer(h, c.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConstantsFunc: func() ([]*model.Constant, error) { return []*model.Constant{c}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showConstants(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "Timeout") +} + +func TestShowMicroflows_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + mf := mkMicroflow(mod.ID, "ACT_DoStuff") + withContainer(h, mf.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { return []*microflows.Microflow{mf}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showMicroflows(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "ACT_DoStuff") +} + +func TestShowNanoflows_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + nf := mkNanoflow(mod.ID, "NF_Validate") + withContainer(h, nf.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListNanoflowsFunc: func() ([]*microflows.Nanoflow, error) { return []*microflows.Nanoflow{nf}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showNanoflows(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "NF_Validate") +} + +func TestShowPages_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + pg := mkPage(mod.ID, "Page_Home") + withContainer(h, pg.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPagesFunc: func() ([]*pages.Page, error) { return []*pages.Page{pg}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showPages(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "Page_Home") +} + +func TestShowSnippets_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + snp := mkSnippet(mod.ID, "Snippet_Header") + withContainer(h, snp.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListSnippetsFunc: func() ([]*pages.Snippet, error) { return []*pages.Snippet{snp}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showSnippets(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "Snippet_Header") +} + +func TestShowLayouts_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + lay := mkLayout(mod.ID, "Layout_Main") + withContainer(h, lay.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListLayoutsFunc: func() ([]*pages.Layout, error) { return []*pages.Layout{lay}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showLayouts(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "Layout_Main") +} + +func TestShowWorkflows_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + wf := mkWorkflow(mod.ID, "WF_Approve") + withContainer(h, wf.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListWorkflowsFunc: func() ([]*workflows.Workflow, error) { return []*workflows.Workflow{wf}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showWorkflows(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "WF_Approve") +} + +func TestShowODataClients_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + svc := &model.ConsumedODataService{ + BaseElement: model.BaseElement{ID: nextID("cos")}, + ContainerID: mod.ID, + Name: "ExtService", + } + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConsumedODataServicesFunc: func() ([]*model.ConsumedODataService, error) { return []*model.ConsumedODataService{svc}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showODataClients(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "ExtService") +} + +func TestShowODataServices_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + svc := &model.PublishedODataService{ + BaseElement: model.BaseElement{ID: nextID("pos")}, + ContainerID: mod.ID, + Name: "PubOData", + } + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPublishedODataServicesFunc: func() ([]*model.PublishedODataService, error) { return []*model.PublishedODataService{svc}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showODataServices(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "PubOData") +} + +func TestShowRestClients_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + svc := &model.ConsumedRestService{ + BaseElement: model.BaseElement{ID: nextID("crs")}, + ContainerID: mod.ID, + Name: "RestClient1", + } + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConsumedRestServicesFunc: func() ([]*model.ConsumedRestService, error) { return []*model.ConsumedRestService{svc}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showRestClients(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "RestClient1") +} + +func TestShowPublishedRestServices_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + svc := &model.PublishedRestService{ + BaseElement: model.BaseElement{ID: nextID("prs")}, + ContainerID: mod.ID, + Name: "PubRest1", + } + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPublishedRestServicesFunc: func() ([]*model.PublishedRestService, error) { return []*model.PublishedRestService{svc}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showPublishedRestServices(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "PubRest1") +} + +func TestShowJavaActions_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + ja := &mpr.JavaAction{ + BaseElement: model.BaseElement{ID: nextID("ja")}, + ContainerID: mod.ID, + Name: "MyJavaAction", + } + withContainer(h, ja.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListJavaActionsFunc: func() ([]*mpr.JavaAction, error) { return []*mpr.JavaAction{ja}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showJavaActions(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "MyJavaAction") +} + +func TestShowJavaScriptActions_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + jsa := &mpr.JavaScriptAction{ + BaseElement: model.BaseElement{ID: nextID("jsa")}, + ContainerID: mod.ID, + Name: "MyJSAction", + } + withContainer(h, jsa.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListJavaScriptActionsFunc: func() ([]*mpr.JavaScriptAction, error) { return []*mpr.JavaScriptAction{jsa}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showJavaScriptActions(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "MyJSAction") +} + +func TestShowDatabaseConnections_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + dc := &model.DatabaseConnection{ + BaseElement: model.BaseElement{ID: nextID("dc")}, + ContainerID: mod.ID, + Name: "MyDB", + } + withContainer(h, dc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListDatabaseConnectionsFunc: func() ([]*model.DatabaseConnection, error) { return []*model.DatabaseConnection{dc}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showDatabaseConnections(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "MyDB") +} + +func TestShowImageCollections_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + ic := &mpr.ImageCollection{ + BaseElement: model.BaseElement{ID: nextID("ic")}, + ContainerID: mod.ID, + Name: "Icons", + } + withContainer(h, ic.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListImageCollectionsFunc: func() ([]*mpr.ImageCollection, error) { return []*mpr.ImageCollection{ic}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showImageCollections(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "Icons") +} + +func TestShowJsonStructures_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + js := &mpr.JsonStructure{ + BaseElement: model.BaseElement{ID: nextID("js")}, + ContainerID: mod.ID, + Name: "OrderSchema", + } + withContainer(h, js.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListJsonStructuresFunc: func() ([]*mpr.JsonStructure, error) { return []*mpr.JsonStructure{js}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showJsonStructures(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "OrderSchema") +} + +func TestShowUserRoles_Mock_JSON(t *testing.T) { + ps := &security.ProjectSecurity{ + BaseElement: model.BaseElement{ID: nextID("ps")}, + UserRoles: []*security.UserRole{ + {Name: "Administrator"}, + }, + } + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { return ps, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON)) + assertNoError(t, showUserRoles(ctx)) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "Administrator") +} + +func TestShowModuleRoles_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + ms := &security.ModuleSecurity{ + BaseElement: model.BaseElement{ID: nextID("ms")}, + ContainerID: mod.ID, + ModuleRoles: []*security.ModuleRole{ + {Name: "User"}, + }, + } + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModuleSecurityFunc: func() ([]*security.ModuleSecurity, error) { return []*security.ModuleSecurity{ms}, nil }, + } + + h := mkHierarchy(mod) + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showModuleRoles(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "User") +} + +func TestShowDemoUsers_Mock_JSON(t *testing.T) { + ps := &security.ProjectSecurity{ + BaseElement: model.BaseElement{ID: nextID("ps")}, + EnableDemoUsers: true, + DemoUsers: []*security.DemoUser{ + {UserName: "demo_admin"}, + }, + } + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { return ps, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON)) + assertNoError(t, showDemoUsers(ctx)) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "demo_admin") +} + +func TestShowBusinessEventServices_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + svc := &model.BusinessEventService{ + BaseElement: model.BaseElement{ID: nextID("bes")}, + ContainerID: mod.ID, + Name: "OrderEvents", + } + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListBusinessEventServicesFunc: func() ([]*model.BusinessEventService, error) { return []*model.BusinessEventService{svc}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showBusinessEventServices(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "OrderEvents") +} + +func TestShowAgentEditorModels_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + m1 := &agenteditor.Model{ + BaseElement: model.BaseElement{ID: nextID("aem")}, + ContainerID: mod.ID, + Name: "GPT4o", + } + withContainer(h, m1.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorModelsFunc: func() ([]*agenteditor.Model, error) { return []*agenteditor.Model{m1}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showAgentEditorModels(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "GPT4o") +} + +func TestShowAgentEditorAgents_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + a1 := &agenteditor.Agent{ + BaseElement: model.BaseElement{ID: nextID("aea")}, + ContainerID: mod.ID, + Name: "Helper", + } + withContainer(h, a1.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorAgentsFunc: func() ([]*agenteditor.Agent, error) { return []*agenteditor.Agent{a1}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showAgentEditorAgents(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "Helper") +} + +func TestShowAgentEditorKnowledgeBases_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + kb := &agenteditor.KnowledgeBase{ + BaseElement: model.BaseElement{ID: nextID("aek")}, + ContainerID: mod.ID, + Name: "FAQ", + } + withContainer(h, kb.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorKnowledgeBasesFunc: func() ([]*agenteditor.KnowledgeBase, error) { return []*agenteditor.KnowledgeBase{kb}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showAgentEditorKnowledgeBases(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "FAQ") +} + +func TestShowAgentEditorMCPServices_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + svc := &agenteditor.ConsumedMCPService{ + BaseElement: model.BaseElement{ID: nextID("aes")}, + ContainerID: mod.ID, + Name: "ToolSvc", + } + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListAgentEditorConsumedMCPServicesFunc: func() ([]*agenteditor.ConsumedMCPService, error) { return []*agenteditor.ConsumedMCPService{svc}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, showAgentEditorConsumedMCPServices(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "ToolSvc") +} + +func TestListDataTransformers_Mock_JSON(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + dt := &model.DataTransformer{ + BaseElement: model.BaseElement{ID: nextID("dt")}, + ContainerID: mod.ID, + Name: "Transform1", + } + withContainer(h, dt.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListDataTransformersFunc: func() ([]*model.DataTransformer, error) { return []*model.DataTransformer{dt}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withFormat(FormatJSON), withHierarchy(h)) + assertNoError(t, listDataTransformers(ctx, "")) + assertValidJSON(t, buf.String()) + assertContainsStr(t, buf.String(), "Transform1") +} diff --git a/mdl/executor/cmd_jsonstructures_mock_test.go b/mdl/executor/cmd_jsonstructures_mock_test.go new file mode 100644 index 0000000..4440973 --- /dev/null +++ b/mdl/executor/cmd_jsonstructures_mock_test.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/mpr" +) + +func TestShowJsonStructures_Mock(t *testing.T) { + mod := mkModule("API") + js := &mpr.JsonStructure{ + BaseElement: model.BaseElement{ID: nextID("js")}, + ContainerID: mod.ID, + Name: "OrderSchema", + } + + h := mkHierarchy(mod) + withContainer(h, js.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListJsonStructuresFunc: func() ([]*mpr.JsonStructure, error) { return []*mpr.JsonStructure{js}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showJsonStructures(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "JSON Structure") + assertContainsStr(t, out, "API.OrderSchema") +} diff --git a/mdl/executor/cmd_mermaid_mock_test.go b/mdl/executor/cmd_mermaid_mock_test.go new file mode 100644 index 0000000..cb0af0d --- /dev/null +++ b/mdl/executor/cmd_mermaid_mock_test.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/domainmodel" +) + +func TestDescribeMermaid_DomainModel_Mock(t *testing.T) { + mod := mkModule("MyModule") + + // Build domain model first to get its ID, then create entities with dm as container. + dm := &domainmodel.DomainModel{ + BaseElement: model.BaseElement{ID: nextID("dm")}, + ContainerID: mod.ID, + } + ent1 := mkEntity(dm.ID, "Customer") + ent2 := mkEntity(dm.ID, "Order") + dm.Entities = []*domainmodel.Entity{ent1, ent2} + dm.Associations = []*domainmodel.Association{ + mkAssociation(mod.ID, "Order_Customer", ent2.ID, ent1.ID), + } + + // Hierarchy: entity.ContainerID (dm.ID) -> mod.ID (module) + // Entities are contained by the domain model; the domain model is contained by the module. + h := mkHierarchy(mod) + withContainer(h, dm.ID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil }, + GetDomainModelFunc: func(moduleID model.ID) (*domainmodel.DomainModel, error) { + return dm, nil + }, + ListDomainModelsFunc: func() ([]*domainmodel.DomainModel, error) { + return []*domainmodel.DomainModel{dm}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeMermaid(ctx, "DOMAINMODEL", "MyModule")) + + out := buf.String() + assertContainsStr(t, out, "erDiagram") + assertContainsStr(t, out, "Customer") + assertContainsStr(t, out, "Order") + assertContainsStr(t, out, "Order_Customer") +} diff --git a/mdl/executor/cmd_microflows_mock_test.go b/mdl/executor/cmd_microflows_mock_test.go new file mode 100644 index 0000000..89eef61 --- /dev/null +++ b/mdl/executor/cmd_microflows_mock_test.go @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/domainmodel" + "github.com/mendixlabs/mxcli/sdk/microflows" +) + +func TestShowMicroflows_Mock(t *testing.T) { + mod := mkModule("MyModule") + mf := mkMicroflow(mod.ID, "ACT_CreateOrder") + + h := mkHierarchy(mod) + withContainer(h, mf.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { return []*microflows.Microflow{mf}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showMicroflows(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "MyModule.ACT_CreateOrder") + assertContainsStr(t, out, "(1 microflows)") +} + +func TestShowMicroflows_Mock_FilterByModule(t *testing.T) { + mod1 := mkModule("Sales") + mod2 := mkModule("HR") + mf1 := mkMicroflow(mod1.ID, "ACT_Sell") + mf2 := mkMicroflow(mod2.ID, "ACT_Hire") + + h := mkHierarchy(mod1, mod2) + withContainer(h, mf1.ContainerID, mod1.ID) + withContainer(h, mf2.ContainerID, mod2.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { return []*microflows.Microflow{mf1, mf2}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showMicroflows(ctx, "HR")) + + out := buf.String() + assertNotContainsStr(t, out, "Sales.ACT_Sell") + assertContainsStr(t, out, "HR.ACT_Hire") +} + +func TestShowNanoflows_Mock(t *testing.T) { + mod := mkModule("MyModule") + nf := mkNanoflow(mod.ID, "NF_Validate") + + h := mkHierarchy(mod) + withContainer(h, nf.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListNanoflowsFunc: func() ([]*microflows.Nanoflow, error) { return []*microflows.Nanoflow{nf}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showNanoflows(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "MyModule.NF_Validate") + assertContainsStr(t, out, "(1 nanoflows)") +} + +func TestDescribeMicroflow_Mock_Minimal(t *testing.T) { + mod := mkModule("MyModule") + mf := mkMicroflow(mod.ID, "ACT_DoSomething") + + h := mkHierarchy(mod) + withContainer(h, mf.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { return []*microflows.Microflow{mf}, nil }, + ListDomainModelsFunc: func() ([]*domainmodel.DomainModel, error) { return nil, nil }, + ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeMicroflow(ctx, ast.QualifiedName{Module: "MyModule", Name: "ACT_DoSomething"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE OR MODIFY MICROFLOW MyModule.ACT_DoSomething") +} + +func TestDescribeMicroflow_Mock_NotFound(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { return nil, nil }, + ListDomainModelsFunc: func() ([]*domainmodel.DomainModel, error) { return nil, nil }, + ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil }, + } + + ctx, _ := newMockCtx(t, withBackend(mb), withHierarchy(h)) + err := describeMicroflow(ctx, ast.QualifiedName{Module: "MyModule", Name: "Missing"}) + assertError(t, err) +} diff --git a/mdl/executor/cmd_misc_mock_test.go b/mdl/executor/cmd_misc_mock_test.go new file mode 100644 index 0000000..7ade837 --- /dev/null +++ b/mdl/executor/cmd_misc_mock_test.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/sdk/mpr/version" +) + +func TestShowVersion_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ProjectVersionFunc: func() *version.ProjectVersion { + return &version.ProjectVersion{ + ProductVersion: "10.18.0", + BuildVersion: "10.18.0.12345", + FormatVersion: 2, + SchemaHash: "abc123def456", + } + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showVersion(ctx)) + + out := buf.String() + assertContainsStr(t, out, "Mendix Version") + assertContainsStr(t, out, "10.18.0") + assertContainsStr(t, out, "Build Version") + assertContainsStr(t, out, "MPR Format") + assertContainsStr(t, out, "Schema Hash") + assertContainsStr(t, out, "abc123def456") +} diff --git a/mdl/executor/cmd_modules_mock_test.go b/mdl/executor/cmd_modules_mock_test.go new file mode 100644 index 0000000..39119b1 --- /dev/null +++ b/mdl/executor/cmd_modules_mock_test.go @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/domainmodel" + "github.com/mendixlabs/mxcli/sdk/mpr" +) + +func TestShowModules_Mock(t *testing.T) { + mod1 := mkModule("MyModule") + mod2 := mkModule("System") + + // showModules uses ListUnits to count documents per module. + // Provide a unit belonging to mod1 so the count is non-zero. + unitID := nextID("unit") + units := []*mpr.UnitInfo{{ID: unitID, ContainerID: mod1.ID}} + + // Need a hierarchy for getHierarchy — provide modules + units + folders + h := mkHierarchy(mod1, mod2) + withContainer(h, unitID, mod1.ID) + + // Provide one domain model for mod1 with one entity + ent := mkEntity(mod1.ID, "Customer") + dm := mkDomainModel(mod1.ID, ent) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod1, mod2}, nil }, + ListUnitsFunc: func() ([]*mpr.UnitInfo, error) { return units, nil }, + ListDomainModelsFunc: func() ([]*domainmodel.DomainModel, error) { return []*domainmodel.DomainModel{dm}, nil }, + // All other list functions return nil (zero counts) via MockBackend defaults. + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showModules(ctx)) + + out := buf.String() + assertContainsStr(t, out, "MyModule") + assertContainsStr(t, out, "System") + assertContainsStr(t, out, "(2 modules)") +} diff --git a/mdl/executor/cmd_navigation_mock_test.go b/mdl/executor/cmd_navigation_mock_test.go new file mode 100644 index 0000000..322e4ad --- /dev/null +++ b/mdl/executor/cmd_navigation_mock_test.go @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/sdk/mpr" +) + +func TestShowNavigation_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetNavigationFunc: func() (*mpr.NavigationDocument, error) { + return &mpr.NavigationDocument{ + Profiles: []*mpr.NavigationProfile{{ + Name: "Responsive", + Kind: "Responsive", + MenuItems: []*mpr.NavMenuItem{ + {Caption: "Home"}, + {Caption: "Admin"}, + {Caption: "Settings"}, + }, + }}, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showNavigation(ctx)) + + out := buf.String() + assertContainsStr(t, out, "Profile") + assertContainsStr(t, out, "Responsive") +} + +func TestShowNavigationMenu_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetNavigationFunc: func() (*mpr.NavigationDocument, error) { + return &mpr.NavigationDocument{ + Profiles: []*mpr.NavigationProfile{{ + Name: "Responsive", + Kind: "Responsive", + MenuItems: []*mpr.NavMenuItem{ + {Caption: "Dashboard", Page: "MyModule.Dashboard"}, + }, + }}, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showNavigationMenu(ctx, nil)) + assertContainsStr(t, buf.String(), "Dashboard") +} + +func TestShowNavigationHomes_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetNavigationFunc: func() (*mpr.NavigationDocument, error) { + return &mpr.NavigationDocument{ + Profiles: []*mpr.NavigationProfile{{ + Name: "Responsive", + Kind: "Responsive", + HomePage: &mpr.NavHomePage{Page: "MyModule.Home"}, + }}, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showNavigationHomes(ctx)) + assertContainsStr(t, buf.String(), "Default Home:") +} + +func TestDescribeNavigation_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetNavigationFunc: func() (*mpr.NavigationDocument, error) { + return &mpr.NavigationDocument{ + Profiles: []*mpr.NavigationProfile{{ + Name: "Responsive", + Kind: "Responsive", + HomePage: &mpr.NavHomePage{Page: "MyModule.Home"}, + }}, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, describeNavigation(ctx, ast.QualifiedName{Name: "Responsive"})) + assertContainsStr(t, buf.String(), "CREATE OR REPLACE NAVIGATION") +} diff --git a/mdl/executor/cmd_notconnected_mock_test.go b/mdl/executor/cmd_notconnected_mock_test.go new file mode 100644 index 0000000..ea8b3e1 --- /dev/null +++ b/mdl/executor/cmd_notconnected_mock_test.go @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" +) + +// disconnectedBackend returns a MockBackend that reports not connected. +func disconnectedBackend() *mock.MockBackend { + return &mock.MockBackend{ + IsConnectedFunc: func() bool { return false }, + } +} + +func TestShowModules_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, showModules(ctx)) +} + +func TestShowSettings_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, showSettings(ctx)) +} + +func TestShowVersion_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, showVersion(ctx)) +} + +func TestShowExportMappings_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, showExportMappings(ctx, "")) +} + +func TestShowImportMappings_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, showImportMappings(ctx, "")) +} + +func TestShowBusinessEventServices_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, showBusinessEventServices(ctx, "")) +} + +func TestShowAgentEditorModels_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, showAgentEditorModels(ctx, "")) +} + +func TestShowAgentEditorAgents_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, showAgentEditorAgents(ctx, "")) +} + +func TestShowAgentEditorKnowledgeBases_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, showAgentEditorKnowledgeBases(ctx, "")) +} + +func TestShowAgentEditorMCPServices_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, showAgentEditorConsumedMCPServices(ctx, "")) +} + +func TestDescribeAgentEditorModel_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, describeAgentEditorModel(ctx, ast.QualifiedName{Module: "M", Name: "X"})) +} + +func TestDescribeAgentEditorAgent_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, describeAgentEditorAgent(ctx, ast.QualifiedName{Module: "M", Name: "X"})) +} + +func TestDescribeAgentEditorKnowledgeBase_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, describeAgentEditorKnowledgeBase(ctx, ast.QualifiedName{Module: "M", Name: "X"})) +} + +func TestDescribeAgentEditorMCPService_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, describeAgentEditorConsumedMCPService(ctx, ast.QualifiedName{Module: "M", Name: "X"})) +} + +func TestDescribeMermaid_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, describeMermaid(ctx, "domainmodel", "MyModule")) +} + +func TestDescribeSettings_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, describeSettings(ctx)) +} + +func TestDescribeBusinessEventService_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, describeBusinessEventService(ctx, ast.QualifiedName{Module: "M", Name: "S"})) +} + +func TestDescribeDataTransformer_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, describeDataTransformer(ctx, ast.QualifiedName{Module: "M", Name: "D"})) +} + +func TestDescribePublishedRestService_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, describePublishedRestService(ctx, ast.QualifiedName{Module: "M", Name: "R"})) +} + +func TestExecCreateModule_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, execCreateModule(ctx, &ast.CreateModuleStmt{Name: "M"})) +} + +func TestExecCreateEnumeration_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, execCreateEnumeration(ctx, &ast.CreateEnumerationStmt{ + Name: ast.QualifiedName{Module: "M", Name: "E"}, + })) +} + +func TestExecDropEnumeration_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, execDropEnumeration(ctx, &ast.DropEnumerationStmt{ + Name: ast.QualifiedName{Module: "M", Name: "E"}, + })) +} + +func TestExecDropEntity_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, execDropEntity(ctx, &ast.DropEntityStmt{ + Name: ast.QualifiedName{Module: "M", Name: "E"}, + })) +} + +func TestExecDropMicroflow_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, execDropMicroflow(ctx, &ast.DropMicroflowStmt{ + Name: ast.QualifiedName{Module: "M", Name: "F"}, + })) +} + +func TestExecDropPage_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, execDropPage(ctx, &ast.DropPageStmt{ + Name: ast.QualifiedName{Module: "M", Name: "P"}, + })) +} + +func TestExecDropSnippet_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, execDropSnippet(ctx, &ast.DropSnippetStmt{ + Name: ast.QualifiedName{Module: "M", Name: "S"}, + })) +} + +func TestExecDropAssociation_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, execDropAssociation(ctx, &ast.DropAssociationStmt{ + Name: ast.QualifiedName{Module: "M", Name: "A"}, + })) +} + +func TestExecDropJavaAction_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, execDropJavaAction(ctx, &ast.DropJavaActionStmt{ + Name: ast.QualifiedName{Module: "M", Name: "J"}, + })) +} + +func TestExecDropFolder_Mock_NotConnected(t *testing.T) { + ctx, _ := newMockCtx(t, withBackend(disconnectedBackend())) + assertError(t, execDropFolder(ctx, &ast.DropFolderStmt{ + FolderPath: "Resources/Images", + Module: "M", + })) +} diff --git a/mdl/executor/cmd_odata_mock_test.go b/mdl/executor/cmd_odata_mock_test.go new file mode 100644 index 0000000..a8fc592 --- /dev/null +++ b/mdl/executor/cmd_odata_mock_test.go @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" +) + +func TestShowODataClients_Mock(t *testing.T) { + mod := mkModule("MyModule") + svc := &model.ConsumedODataService{ + BaseElement: model.BaseElement{ID: nextID("cos")}, + ContainerID: mod.ID, + Name: "PetStoreClient", + MetadataUrl: "https://example.com/$metadata", + Version: "1.0", + ODataVersion: "4.0", + } + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConsumedODataServicesFunc: func() ([]*model.ConsumedODataService, error) { + return []*model.ConsumedODataService{svc}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showODataClients(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "QualifiedName") + assertContainsStr(t, out, "MyModule.PetStoreClient") +} + +func TestShowODataServices_Mock(t *testing.T) { + mod := mkModule("MyModule") + svc := &model.PublishedODataService{ + BaseElement: model.BaseElement{ID: nextID("pos")}, + ContainerID: mod.ID, + Name: "CatalogService", + Path: "/odata/v1", + Version: "1.0", + ODataVersion: "4.0", + } + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPublishedODataServicesFunc: func() ([]*model.PublishedODataService, error) { + return []*model.PublishedODataService{svc}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showODataServices(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "QualifiedName") + assertContainsStr(t, out, "MyModule.CatalogService") +} + +func TestDescribeODataClient_Mock(t *testing.T) { + mod := mkModule("MyModule") + svc := &model.ConsumedODataService{ + BaseElement: model.BaseElement{ID: nextID("cos")}, + ContainerID: mod.ID, + Name: "PetStoreClient", + MetadataUrl: "https://example.com/$metadata", + Version: "2.0", + ODataVersion: "4.0", + } + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConsumedODataServicesFunc: func() ([]*model.ConsumedODataService, error) { + return []*model.ConsumedODataService{svc}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeODataClient(ctx, ast.QualifiedName{Module: "MyModule", Name: "PetStoreClient"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE ODATA CLIENT") + assertContainsStr(t, out, "MyModule.PetStoreClient") + assertContainsStr(t, out, "https://example.com/$metadata") + assertContainsStr(t, out, "2.0") +} + +func TestDescribeODataService_Mock(t *testing.T) { + mod := mkModule("MyModule") + svc := &model.PublishedODataService{ + BaseElement: model.BaseElement{ID: nextID("pos")}, + ContainerID: mod.ID, + Name: "CatalogService", + Path: "/odata/v1", + Version: "1.0", + ODataVersion: "4.0", + Namespace: "MyApp", + } + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPublishedODataServicesFunc: func() ([]*model.PublishedODataService, error) { + return []*model.PublishedODataService{svc}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeODataService(ctx, ast.QualifiedName{Module: "MyModule", Name: "CatalogService"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE ODATA SERVICE") + assertContainsStr(t, out, "MyModule.CatalogService") +} diff --git a/mdl/executor/cmd_pages_mock_test.go b/mdl/executor/cmd_pages_mock_test.go new file mode 100644 index 0000000..9f45671 --- /dev/null +++ b/mdl/executor/cmd_pages_mock_test.go @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/sdk/pages" +) + +func TestShowPages_Mock(t *testing.T) { + mod := mkModule("MyModule") + pg := mkPage(mod.ID, "Home") + + h := mkHierarchy(mod) + withContainer(h, pg.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPagesFunc: func() ([]*pages.Page, error) { return []*pages.Page{pg}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showPages(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "MyModule.Home") + assertContainsStr(t, out, "(1 pages)") +} + +func TestShowPages_Mock_FilterByModule(t *testing.T) { + mod1 := mkModule("Sales") + mod2 := mkModule("HR") + pg1 := mkPage(mod1.ID, "OrderList") + pg2 := mkPage(mod2.ID, "EmployeeList") + + h := mkHierarchy(mod1, mod2) + withContainer(h, pg1.ContainerID, mod1.ID) + withContainer(h, pg2.ContainerID, mod2.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPagesFunc: func() ([]*pages.Page, error) { return []*pages.Page{pg1, pg2}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showPages(ctx, "HR")) + + out := buf.String() + assertNotContainsStr(t, out, "Sales.OrderList") + assertContainsStr(t, out, "HR.EmployeeList") +} + +func TestShowSnippets_Mock(t *testing.T) { + mod := mkModule("MyModule") + snp := mkSnippet(mod.ID, "Header") + + h := mkHierarchy(mod) + withContainer(h, snp.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListSnippetsFunc: func() ([]*pages.Snippet, error) { return []*pages.Snippet{snp}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showSnippets(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "MyModule.Header") + assertContainsStr(t, out, "(1 snippets)") +} + +func TestShowLayouts_Mock(t *testing.T) { + mod := mkModule("MyModule") + lay := mkLayout(mod.ID, "Atlas_Default") + + h := mkHierarchy(mod) + withContainer(h, lay.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListLayoutsFunc: func() ([]*pages.Layout, error) { return []*pages.Layout{lay}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showLayouts(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "MyModule.Atlas_Default") + assertContainsStr(t, out, "(1 layouts)") +} diff --git a/mdl/executor/cmd_published_rest_mock_test.go b/mdl/executor/cmd_published_rest_mock_test.go new file mode 100644 index 0000000..9342339 --- /dev/null +++ b/mdl/executor/cmd_published_rest_mock_test.go @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" +) + +func TestShowPublishedRestServices_Mock(t *testing.T) { + mod := mkModule("MyModule") + svc := &model.PublishedRestService{ + BaseElement: model.BaseElement{ID: nextID("prs")}, + ContainerID: mod.ID, + Name: "OrderAPI", + Path: "/rest/orders/v1", + Version: "1.0", + Resources: []*model.PublishedRestResource{ + {Name: "Orders"}, + {Name: "Items"}, + }, + } + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPublishedRestServicesFunc: func() ([]*model.PublishedRestService, error) { + return []*model.PublishedRestService{svc}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showPublishedRestServices(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "QualifiedName") + assertContainsStr(t, out, "MyModule.OrderAPI") + assertContainsStr(t, out, "(1 published REST services)") +} + +func TestDescribePublishedRestService_Mock(t *testing.T) { + mod := mkModule("MyModule") + svc := &model.PublishedRestService{ + BaseElement: model.BaseElement{ID: nextID("prs")}, + ContainerID: mod.ID, + Name: "OrderAPI", + Path: "/rest/orders/v1", + Version: "1.0", + } + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPublishedRestServicesFunc: func() ([]*model.PublishedRestService, error) { + return []*model.PublishedRestService{svc}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describePublishedRestService(ctx, ast.QualifiedName{Module: "MyModule", Name: "OrderAPI"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE PUBLISHED REST SERVICE") + assertContainsStr(t, out, "MyModule.OrderAPI") +} diff --git a/mdl/executor/cmd_rest_clients_mock_test.go b/mdl/executor/cmd_rest_clients_mock_test.go new file mode 100644 index 0000000..6670f58 --- /dev/null +++ b/mdl/executor/cmd_rest_clients_mock_test.go @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" +) + +func TestShowRestClients_Mock(t *testing.T) { + mod := mkModule("MyModule") + svc := &model.ConsumedRestService{ + BaseElement: model.BaseElement{ID: nextID("crs")}, + ContainerID: mod.ID, + Name: "WeatherAPI", + BaseUrl: "https://api.weather.com", + } + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConsumedRestServicesFunc: func() ([]*model.ConsumedRestService, error) { + return []*model.ConsumedRestService{svc}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showRestClients(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "QualifiedName") + assertContainsStr(t, out, "MyModule.WeatherAPI") +} + +func TestDescribeRestClient_Mock(t *testing.T) { + mod := mkModule("MyModule") + svc := &model.ConsumedRestService{ + BaseElement: model.BaseElement{ID: nextID("crs")}, + ContainerID: mod.ID, + Name: "WeatherAPI", + BaseUrl: "https://api.weather.com", + } + h := mkHierarchy(mod) + withContainer(h, svc.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListConsumedRestServicesFunc: func() ([]*model.ConsumedRestService, error) { + return []*model.ConsumedRestService{svc}, nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeRestClient(ctx, ast.QualifiedName{Module: "MyModule", Name: "WeatherAPI"})) + + out := buf.String() + assertContainsStr(t, out, "CREATE REST CLIENT") + assertContainsStr(t, out, "MyModule.WeatherAPI") +} diff --git a/mdl/executor/cmd_security_mock_test.go b/mdl/executor/cmd_security_mock_test.go new file mode 100644 index 0000000..75f23fd --- /dev/null +++ b/mdl/executor/cmd_security_mock_test.go @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/sdk/security" +) + +func TestShowProjectSecurity_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { + return &security.ProjectSecurity{ + SecurityLevel: "CheckEverything", + EnableDemoUsers: true, + AdminUserName: "MxAdmin", + UserRoles: []*security.UserRole{{Name: "Admin"}, {Name: "User"}}, + DemoUsers: []*security.DemoUser{{UserName: "demo_admin"}}, + PasswordPolicy: &security.PasswordPolicy{MinimumLength: 8}, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showProjectSecurity(ctx)) + + out := buf.String() + assertContainsStr(t, out, "Security Level:") + assertContainsStr(t, out, "MxAdmin") + assertContainsStr(t, out, "Demo Users Enabled:") +} + +func TestShowModuleRoles_Mock(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModuleSecurityFunc: func() ([]*security.ModuleSecurity, error) { + return []*security.ModuleSecurity{{ + ContainerID: mod.ID, + ModuleRoles: []*security.ModuleRole{ + {Name: "Admin"}, + {Name: "User"}, + }, + }}, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showModuleRoles(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Qualified Name") + assertContainsStr(t, out, "Role") + assertContainsStr(t, out, "Admin") + assertContainsStr(t, out, "User") +} + +func TestShowUserRoles_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { + return &security.ProjectSecurity{ + UserRoles: []*security.UserRole{ + {Name: "Administrator", ModuleRoles: []string{"MyModule.Admin"}}, + {Name: "NormalUser", ModuleRoles: []string{"MyModule.User"}}, + }, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showUserRoles(ctx)) + + out := buf.String() + assertContainsStr(t, out, "Name") + assertContainsStr(t, out, "Module Roles") + assertContainsStr(t, out, "Administrator") + assertContainsStr(t, out, "NormalUser") +} + +func TestShowDemoUsers_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { + return &security.ProjectSecurity{ + EnableDemoUsers: true, + DemoUsers: []*security.DemoUser{ + {UserName: "demo_admin", UserRoles: []string{"Administrator"}}, + }, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showDemoUsers(ctx)) + + out := buf.String() + assertContainsStr(t, out, "User Name") + assertContainsStr(t, out, "demo_admin") +} + +func TestShowDemoUsers_Disabled_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { + return &security.ProjectSecurity{ + EnableDemoUsers: false, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showDemoUsers(ctx)) + assertContainsStr(t, buf.String(), "Demo users are disabled.") +} + +func TestDescribeModuleRole_Mock(t *testing.T) { + mod := mkModule("MyModule") + h := mkHierarchy(mod) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModuleSecurityFunc: func() ([]*security.ModuleSecurity, error) { + return []*security.ModuleSecurity{{ + ContainerID: mod.ID, + ModuleRoles: []*security.ModuleRole{{Name: "Admin", Description: "Full access"}}, + }}, nil + }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { + return &security.ProjectSecurity{ + UserRoles: []*security.UserRole{ + {Name: "Administrator", ModuleRoles: []string{"MyModule.Admin"}}, + }, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeModuleRole(ctx, ast.QualifiedName{Module: "MyModule", Name: "Admin"})) + assertContainsStr(t, buf.String(), "CREATE MODULE ROLE") +} + +func TestDescribeUserRole_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { + return &security.ProjectSecurity{ + UserRoles: []*security.UserRole{ + {Name: "Administrator", ModuleRoles: []string{"MyModule.Admin"}}, + }, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, describeUserRole(ctx, ast.QualifiedName{Name: "Administrator"})) + assertContainsStr(t, buf.String(), "CREATE USER ROLE") +} + +func TestDescribeDemoUser_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSecurityFunc: func() (*security.ProjectSecurity, error) { + return &security.ProjectSecurity{ + EnableDemoUsers: true, + DemoUsers: []*security.DemoUser{ + {UserName: "demo_admin", UserRoles: []string{"Administrator"}}, + }, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, describeDemoUser(ctx, "demo_admin")) + assertContainsStr(t, buf.String(), "CREATE DEMO USER") +} diff --git a/mdl/executor/cmd_settings_mock_test.go b/mdl/executor/cmd_settings_mock_test.go new file mode 100644 index 0000000..22d5788 --- /dev/null +++ b/mdl/executor/cmd_settings_mock_test.go @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" +) + +func TestShowSettings_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSettingsFunc: func() (*model.ProjectSettings, error) { + return &model.ProjectSettings{ + Model: &model.ModelSettings{ + HashAlgorithm: "BCrypt", + JavaVersion: "17", + }, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, showSettings(ctx)) + + out := buf.String() + assertContainsStr(t, out, "Section") + assertContainsStr(t, out, "Key Values") +} + +func TestDescribeSettings_Mock(t *testing.T) { + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + GetProjectSettingsFunc: func() (*model.ProjectSettings, error) { + return &model.ProjectSettings{ + Model: &model.ModelSettings{ + HashAlgorithm: "BCrypt", + JavaVersion: "17", + RoundingMode: "HalfUp", + }, + }, nil + }, + } + ctx, buf := newMockCtx(t, withBackend(mb)) + assertNoError(t, describeSettings(ctx)) + assertContainsStr(t, buf.String(), "ALTER SETTINGS") +} diff --git a/mdl/executor/cmd_workflows_mock_test.go b/mdl/executor/cmd_workflows_mock_test.go new file mode 100644 index 0000000..42437ab --- /dev/null +++ b/mdl/executor/cmd_workflows_mock_test.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/sdk/workflows" +) + +func TestShowWorkflows_Mock(t *testing.T) { + mod := mkModule("Sales") + wf := mkWorkflow(mod.ID, "ApproveOrder") + + h := mkHierarchy(mod) + withContainer(h, wf.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListWorkflowsFunc: func() ([]*workflows.Workflow, error) { return []*workflows.Workflow{wf}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, showWorkflows(ctx, "")) + + out := buf.String() + assertContainsStr(t, out, "Qualified Name") + assertContainsStr(t, out, "Sales.ApproveOrder") +} + +func TestDescribeWorkflow_Mock(t *testing.T) { + mod := mkModule("Sales") + wf := mkWorkflow(mod.ID, "ApproveOrder") + wf.Parameter = &workflows.WorkflowParameter{EntityRef: "Sales.Order"} + + h := mkHierarchy(mod) + withContainer(h, wf.ContainerID, mod.ID) + + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListWorkflowsFunc: func() ([]*workflows.Workflow, error) { return []*workflows.Workflow{wf}, nil }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + assertNoError(t, describeWorkflow(ctx, ast.QualifiedName{Module: "Sales", Name: "ApproveOrder"})) + + out := buf.String() + assertContainsStr(t, out, "WORKFLOW") + assertContainsStr(t, out, "Sales.ApproveOrder") +} diff --git a/mdl/executor/cmd_write_handlers_mock_test.go b/mdl/executor/cmd_write_handlers_mock_test.go new file mode 100644 index 0000000..6d1130a --- /dev/null +++ b/mdl/executor/cmd_write_handlers_mock_test.go @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "testing" + + "github.com/mendixlabs/mxcli/mdl/ast" + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/domainmodel" + "github.com/mendixlabs/mxcli/sdk/microflows" + "github.com/mendixlabs/mxcli/sdk/mpr" + "github.com/mendixlabs/mxcli/sdk/pages" +) + +func TestExecCreateModule_Mock(t *testing.T) { + called := false + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { + return nil, nil // no existing modules + }, + CreateModuleFunc: func(m *model.Module) error { + called = true + return nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + err := execCreateModule(ctx, &ast.CreateModuleStmt{Name: "NewModule"}) + assertNoError(t, err) + assertContainsStr(t, buf.String(), "Created module: NewModule") + if !called { + t.Fatal("CreateModuleFunc was not called") + } +} + +func TestExecDropEnumeration_Mock(t *testing.T) { + mod := mkModule("MyModule") + enum := mkEnumeration(mod.ID, "Status", "Active", "Inactive") + + called := false + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListEnumerationsFunc: func() ([]*model.Enumeration, error) { + return []*model.Enumeration{enum}, nil + }, + ListModulesFunc: func() ([]*model.Module, error) { + return []*model.Module{mod}, nil + }, + DeleteEnumerationFunc: func(id model.ID) error { + called = true + return nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + err := execDropEnumeration(ctx, &ast.DropEnumerationStmt{ + Name: ast.QualifiedName{Module: "MyModule", Name: "Status"}, + }) + assertNoError(t, err) + assertContainsStr(t, buf.String(), "Dropped enumeration:") + if !called { + t.Fatal("DeleteEnumerationFunc was not called") + } +} + +func TestExecCreateEnumeration_Mock(t *testing.T) { + mod := mkModule("MyModule") + + h := mkHierarchy(mod) + + called := false + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { + return []*model.Module{mod}, nil + }, + ListEnumerationsFunc: func() ([]*model.Enumeration, error) { + return nil, nil // no duplicates + }, + CreateEnumerationFunc: func(e *model.Enumeration) error { + called = true + return nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + err := execCreateEnumeration(ctx, &ast.CreateEnumerationStmt{ + Name: ast.QualifiedName{Module: "MyModule", Name: "Color"}, + Values: []ast.EnumValue{{Name: "Red", Caption: "Red"}, {Name: "Blue", Caption: "Blue"}}, + }) + assertNoError(t, err) + assertContainsStr(t, buf.String(), "Created enumeration:") + if !called { + t.Fatal("CreateEnumerationFunc was not called") + } +} + +func TestExecDropEntity_Mock(t *testing.T) { + mod := mkModule("MyModule") + ent := mkEntity(mod.ID, "Customer") + dm := mkDomainModel(mod.ID, ent) + + called := false + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { + return []*model.Module{mod}, nil + }, + GetDomainModelFunc: func(moduleID model.ID) (*domainmodel.DomainModel, error) { + return dm, nil + }, + DeleteEntityFunc: func(domainModelID model.ID, entityID model.ID) error { + called = true + return nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + err := execDropEntity(ctx, &ast.DropEntityStmt{ + Name: ast.QualifiedName{Module: "MyModule", Name: "Customer"}, + }) + assertNoError(t, err) + assertContainsStr(t, buf.String(), "Dropped entity:") + if !called { + t.Fatal("DeleteEntityFunc was not called") + } +} + +func TestExecDropMicroflow_Mock(t *testing.T) { + mod := mkModule("MyModule") + mf := mkMicroflow(mod.ID, "DoSomething") + + h := mkHierarchy(mod) + withContainer(h, mf.ContainerID, mod.ID) + + called := false + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { + return []*microflows.Microflow{mf}, nil + }, + DeleteMicroflowFunc: func(id model.ID) error { + called = true + return nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + err := execDropMicroflow(ctx, &ast.DropMicroflowStmt{ + Name: ast.QualifiedName{Module: "MyModule", Name: "DoSomething"}, + }) + assertNoError(t, err) + assertContainsStr(t, buf.String(), "Dropped microflow:") + if !called { + t.Fatal("DeleteMicroflowFunc was not called") + } +} + +func TestExecDropPage_Mock(t *testing.T) { + mod := mkModule("MyModule") + pg := mkPage(mod.ID, "HomePage") + + h := mkHierarchy(mod) + withContainer(h, pg.ContainerID, mod.ID) + + called := false + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListPagesFunc: func() ([]*pages.Page, error) { + return []*pages.Page{pg}, nil + }, + DeletePageFunc: func(id model.ID) error { + called = true + return nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + err := execDropPage(ctx, &ast.DropPageStmt{ + Name: ast.QualifiedName{Module: "MyModule", Name: "HomePage"}, + }) + assertNoError(t, err) + assertContainsStr(t, buf.String(), "Dropped page") + if !called { + t.Fatal("DeletePageFunc was not called") + } +} + +func TestExecDropSnippet_Mock(t *testing.T) { + mod := mkModule("MyModule") + snp := mkSnippet(mod.ID, "HeaderSnippet") + + h := mkHierarchy(mod) + withContainer(h, snp.ContainerID, mod.ID) + + called := false + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListSnippetsFunc: func() ([]*pages.Snippet, error) { + return []*pages.Snippet{snp}, nil + }, + DeleteSnippetFunc: func(id model.ID) error { + called = true + return nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + err := execDropSnippet(ctx, &ast.DropSnippetStmt{ + Name: ast.QualifiedName{Module: "MyModule", Name: "HeaderSnippet"}, + }) + assertNoError(t, err) + assertContainsStr(t, buf.String(), "Dropped snippet") + if !called { + t.Fatal("DeleteSnippetFunc was not called") + } +} + +func TestExecDropAssociation_Mock(t *testing.T) { + mod := mkModule("MyModule") + ent1 := mkEntity(mod.ID, "Order") + ent2 := mkEntity(mod.ID, "Customer") + assoc := mkAssociation(mod.ID, "Order_Customer", ent1.ID, ent2.ID) + + dm := mkDomainModel(mod.ID, ent1, ent2) + dm.Associations = []*domainmodel.Association{assoc} + + called := false + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { + return []*model.Module{mod}, nil + }, + GetDomainModelFunc: func(moduleID model.ID) (*domainmodel.DomainModel, error) { + return dm, nil + }, + DeleteAssociationFunc: func(domainModelID model.ID, assocID model.ID) error { + called = true + return nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb)) + err := execDropAssociation(ctx, &ast.DropAssociationStmt{ + Name: ast.QualifiedName{Module: "MyModule", Name: "Order_Customer"}, + }) + assertNoError(t, err) + assertContainsStr(t, buf.String(), "Dropped association:") + if !called { + t.Fatal("DeleteAssociationFunc was not called") + } +} + +func TestExecDropJavaAction_Mock(t *testing.T) { + mod := mkModule("MyModule") + jaID := nextID("ja") + ja := &mpr.JavaAction{ + BaseElement: model.BaseElement{ID: jaID}, + ContainerID: mod.ID, + Name: "MyAction", + } + + h := mkHierarchy(mod) + + called := false + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListJavaActionsFunc: func() ([]*mpr.JavaAction, error) { + return []*mpr.JavaAction{ja}, nil + }, + DeleteJavaActionFunc: func(id model.ID) error { + called = true + return nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + err := execDropJavaAction(ctx, &ast.DropJavaActionStmt{ + Name: ast.QualifiedName{Module: "MyModule", Name: "MyAction"}, + }) + assertNoError(t, err) + assertContainsStr(t, buf.String(), "Dropped java action:") + if !called { + t.Fatal("DeleteJavaActionFunc was not called") + } +} + +func TestExecDropFolder_Mock(t *testing.T) { + mod := mkModule("MyModule") + folderID := nextID("folder") + folder := &mpr.FolderInfo{ + ID: folderID, + ContainerID: mod.ID, + Name: "Resources", + } + + h := mkHierarchy(mod) + withContainer(h, folderID, mod.ID) + + called := false + mb := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { + return []*model.Module{mod}, nil + }, + ListFoldersFunc: func() ([]*mpr.FolderInfo, error) { + return []*mpr.FolderInfo{folder}, nil + }, + DeleteFolderFunc: func(id model.ID) error { + called = true + return nil + }, + } + + ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h)) + err := execDropFolder(ctx, &ast.DropFolderStmt{ + FolderPath: "Resources", + Module: "MyModule", + }) + assertNoError(t, err) + assertContainsStr(t, buf.String(), "Dropped folder:") + if !called { + t.Fatal("DeleteFolderFunc was not called") + } +} diff --git a/mdl/executor/mock_test_helpers_test.go b/mdl/executor/mock_test_helpers_test.go new file mode 100644 index 0000000..288290d --- /dev/null +++ b/mdl/executor/mock_test_helpers_test.go @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "bytes" + "context" + "encoding/json" + "strconv" + "strings" + "sync/atomic" + "testing" + + "github.com/mendixlabs/mxcli/mdl/backend/mock" + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/domainmodel" + "github.com/mendixlabs/mxcli/sdk/microflows" + "github.com/mendixlabs/mxcli/sdk/pages" + "github.com/mendixlabs/mxcli/sdk/workflows" +) + +// --- Context construction --- + +type mockCtxOption func(*ExecContext) + +// newMockCtx creates an ExecContext backed by a MockBackend with a bytes.Buffer +// as output. Returns the context and the buffer for output assertions. +// The default format is FormatTable. Pass options to override. +func newMockCtx(t *testing.T, opts ...mockCtxOption) (*ExecContext, *bytes.Buffer) { + t.Helper() + var buf bytes.Buffer + ctx := &ExecContext{ + Context: context.Background(), + Backend: &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + }, + Output: &buf, + Format: FormatTable, + } + for _, opt := range opts { + opt(ctx) + } + return ctx, &buf +} + +func withBackend(b *mock.MockBackend) mockCtxOption { + return func(ctx *ExecContext) { ctx.Backend = b } +} + +func withFormat(f OutputFormat) mockCtxOption { + return func(ctx *ExecContext) { ctx.Format = f } +} + +func withQuiet() mockCtxOption { + return func(ctx *ExecContext) { ctx.Quiet = true } +} + +func withCache(c *executorCache) mockCtxOption { + return func(ctx *ExecContext) { ctx.Cache = c } +} + +func withHierarchy(h *ContainerHierarchy) mockCtxOption { + return func(ctx *ExecContext) { + if ctx.Cache == nil { + ctx.Cache = &executorCache{} + } + ctx.Cache.hierarchy = h + } +} + +func withMprPath(p string) mockCtxOption { + return func(ctx *ExecContext) { ctx.MprPath = p } +} + +func withSettings(s map[string]any) mockCtxOption { + return func(ctx *ExecContext) { ctx.Settings = s } +} + +// --- Hierarchy construction --- + +// mkHierarchy builds a ContainerHierarchy from modules. After creation, use +// withContainer to register container-parent links for documents (entities, +// enumerations, etc.) so that FindModuleID can walk up to the owning module. +func mkHierarchy(modules ...*model.Module) *ContainerHierarchy { + h := &ContainerHierarchy{ + moduleIDs: make(map[model.ID]bool), + moduleNames: make(map[model.ID]string), + containerParent: make(map[model.ID]model.ID), + folderNames: make(map[model.ID]string), + } + for _, m := range modules { + h.moduleIDs[m.ID] = true + h.moduleNames[m.ID] = m.Name + } + return h +} + +// withContainer registers a container-parent link in the hierarchy so that +// ContainerHierarchy.FindModuleID can walk from containerID up to a module. +// In production, FindModuleID is always called with an element's ContainerID +// field (not the element's own ID). For elements whose ContainerID is already +// a module ID, this call is technically redundant (the module is found directly +// in moduleIDs), but it keeps test setup explicit about parentage. For +// intermediate containers (folders, units) this call is required. +func withContainer(h *ContainerHierarchy, containerID, parentID model.ID) { + h.containerParent[containerID] = parentID +} + +// --- Model factories --- + +// idCounter generates unique IDs across all tests in the package. IDs are +// non-deterministic across runs (depend on test execution order), which is +// fine for string-contains assertions but would break exact-value assertions. +var idCounter atomic.Int64 + +func nextID(prefix string) model.ID { + n := idCounter.Add(1) + return model.ID(prefix + "-" + strconv.FormatInt(n, 10)) +} + +func mkModule(name string) *model.Module { + return &model.Module{ + BaseElement: model.BaseElement{ID: nextID("mod")}, + Name: name, + } +} + +func mkEnumeration(containerID model.ID, name string, values ...string) *model.Enumeration { + e := &model.Enumeration{ + BaseElement: model.BaseElement{ID: nextID("enum")}, + ContainerID: containerID, + Name: name, + } + for _, v := range values { + e.Values = append(e.Values, model.EnumerationValue{ + BaseElement: model.BaseElement{ID: nextID("ev")}, + Name: v, + }) + } + return e +} + +func mkConstant(containerID model.ID, name string, typ string, defaultVal string) *model.Constant { + return &model.Constant{ + BaseElement: model.BaseElement{ID: nextID("const")}, + ContainerID: containerID, + Name: name, + Type: model.ConstantDataType{Kind: typ}, + DefaultValue: defaultVal, + } +} + +func mkEntity(containerID model.ID, name string) *domainmodel.Entity { + return &domainmodel.Entity{ + BaseElement: model.BaseElement{ID: nextID("ent")}, + ContainerID: containerID, + Name: name, + Persistable: true, + } +} + +func mkDomainModel(containerID model.ID, entities ...*domainmodel.Entity) *domainmodel.DomainModel { + return &domainmodel.DomainModel{ + BaseElement: model.BaseElement{ID: nextID("dm")}, + ContainerID: containerID, + Entities: entities, + } +} + +func mkAssociation(containerID model.ID, name string, parentID, childID model.ID) *domainmodel.Association { + return &domainmodel.Association{ + BaseElement: model.BaseElement{ID: nextID("assoc")}, + ContainerID: containerID, + Name: name, + ParentID: parentID, + ChildID: childID, + Type: "Reference", + Owner: "Default", + } +} + +func mkMicroflow(containerID model.ID, name string) *microflows.Microflow { + return µflows.Microflow{ + BaseElement: model.BaseElement{ID: nextID("mf")}, + ContainerID: containerID, + Name: name, + } +} + +func mkNanoflow(containerID model.ID, name string) *microflows.Nanoflow { + return µflows.Nanoflow{ + BaseElement: model.BaseElement{ID: nextID("nf")}, + ContainerID: containerID, + Name: name, + } +} + +func mkPage(containerID model.ID, name string) *pages.Page { + return &pages.Page{ + BaseElement: model.BaseElement{ID: nextID("pg")}, + ContainerID: containerID, + Name: name, + } +} + +func mkSnippet(containerID model.ID, name string) *pages.Snippet { + return &pages.Snippet{ + BaseElement: model.BaseElement{ID: nextID("snp")}, + ContainerID: containerID, + Name: name, + } +} + +func mkLayout(containerID model.ID, name string) *pages.Layout { + return &pages.Layout{ + BaseElement: model.BaseElement{ID: nextID("lay")}, + ContainerID: containerID, + Name: name, + } +} + +func mkWorkflow(containerID model.ID, name string) *workflows.Workflow { + return &workflows.Workflow{ + BaseElement: model.BaseElement{ID: nextID("wf")}, + ContainerID: containerID, + Name: name, + } +} + +// --- Assertion helpers --- + +func assertNoError(t *testing.T, err error) { + t.Helper() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func assertError(t *testing.T, err error) { + t.Helper() + if err == nil { + t.Fatal("expected error, got nil") + } +} + +func assertContainsStr(t *testing.T, got, want string) { + t.Helper() + if !strings.Contains(got, want) { + t.Errorf("output should contain %q, got:\n%s", want, got) + } +} + +func assertNotContainsStr(t *testing.T, got, unwanted string) { + t.Helper() + if strings.Contains(got, unwanted) { + t.Errorf("output should NOT contain %q, got:\n%s", unwanted, got) + } +} + +// assertValidJSON checks that s is valid JSON starting with '{' or '['. +// Unlike json.Valid alone, this rejects scalar JSON values (true, 123, null) +// which would not be valid handler output. +func assertValidJSON(t *testing.T, s string) { + t.Helper() + trimmed := strings.TrimSpace(s) + if len(trimmed) == 0 || (trimmed[0] != '{' && trimmed[0] != '[') { + t.Errorf("expected JSON array or object, got:\n%s", s) + return + } + if !json.Valid([]byte(trimmed)) { + t.Errorf("expected valid JSON, got:\n%s", s) + } +} diff --git a/mdl/visitor/visitor_agenteditor.go b/mdl/visitor/visitor_agenteditor.go index d095f0a..156c874 100644 --- a/mdl/visitor/visitor_agenteditor.go +++ b/mdl/visitor/visitor_agenteditor.go @@ -282,7 +282,6 @@ func (b *Builder) ExitCreateAgentStatement(ctx *parser.CreateAgentStatementConte b.statements = append(b.statements, stmt) } - // parseQualifiedNameString splits "Module.Name" into a QualifiedName. func parseQualifiedNameString(s string) ast.QualifiedName { parts := strings.SplitN(s, ".", 2)