Skip to content

Commit fc7ef35

Browse files
committed
test: add mock tests for 8 executor handler files
Add 49 tests across 8 new test files covering executor handlers that previously had zero test coverage: - cmd_folders: 9 tests (DropFolder + MoveFolder paths) - cmd_move: 6 tests (page moves, cross-module refs, error paths) - cmd_rename: 11 tests (entity/module/microflow/page/enum/assoc/const/nanoflow) - cmd_alter_page: 9 tests (set property, snippets, widgets, variables, layout) - cmd_features: 7 tests (ForVersion, AddedSince, connected, invalid version) - cmd_styling: 5 tests (not-connected guards for all 3 styling commands) - cmd_lint: 1 test (not-connected guard) - cmd_import: 1 test (not-connected guard) Styling, lint, and import have partial coverage (not-connected + error paths only) because their happy paths depend on filesystem or external DB connections that cannot be mocked through the current Backend interface.
1 parent 3e711b9 commit fc7ef35

8 files changed

Lines changed: 1270 additions & 0 deletions
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package executor
4+
5+
import (
6+
"fmt"
7+
"testing"
8+
9+
"github.com/mendixlabs/mxcli/mdl/ast"
10+
"github.com/mendixlabs/mxcli/mdl/backend"
11+
"github.com/mendixlabs/mxcli/mdl/backend/mock"
12+
"github.com/mendixlabs/mxcli/mdl/types"
13+
"github.com/mendixlabs/mxcli/model"
14+
"github.com/mendixlabs/mxcli/sdk/pages"
15+
)
16+
17+
// ---------------------------------------------------------------------------
18+
// Not connected
19+
// ---------------------------------------------------------------------------
20+
21+
func TestAlterPage_NotConnected(t *testing.T) {
22+
mb := &mock.MockBackend{IsConnectedFunc: func() bool { return false }}
23+
ctx, _ := newMockCtx(t, withBackend(mb))
24+
err := execAlterPage(ctx, &ast.AlterPageStmt{
25+
PageName: ast.QualifiedName{Module: "M", Name: "P"},
26+
})
27+
assertError(t, err)
28+
assertContainsStr(t, err.Error(), "not connected")
29+
}
30+
31+
// ---------------------------------------------------------------------------
32+
// Page not found
33+
// ---------------------------------------------------------------------------
34+
35+
func TestAlterPage_PageNotFound(t *testing.T) {
36+
mod := mkModule("MyModule")
37+
mb := &mock.MockBackend{
38+
IsConnectedFunc: func() bool { return true },
39+
ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil },
40+
ListFoldersFunc: func() ([]*types.FolderInfo, error) { return nil, nil },
41+
ListPagesFunc: func() ([]*pages.Page, error) { return nil, nil },
42+
}
43+
h := mkHierarchy(mod)
44+
ctx, _ := newMockCtx(t, withBackend(mb), withHierarchy(h))
45+
err := execAlterPage(ctx, &ast.AlterPageStmt{
46+
PageName: ast.QualifiedName{Module: "MyModule", Name: "Missing"},
47+
})
48+
assertError(t, err)
49+
assertContainsStr(t, err.Error(), "not found")
50+
}
51+
52+
// ---------------------------------------------------------------------------
53+
// Page happy path — SET property + Save
54+
// ---------------------------------------------------------------------------
55+
56+
func TestAlterPage_SetProperty_Success(t *testing.T) {
57+
mod := mkModule("MyModule")
58+
pg := mkPage(mod.ID, "TestPage")
59+
saved := false
60+
setPropCalled := false
61+
mb := &mock.MockBackend{
62+
IsConnectedFunc: func() bool { return true },
63+
ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil },
64+
ListFoldersFunc: func() ([]*types.FolderInfo, error) { return nil, nil },
65+
ListPagesFunc: func() ([]*pages.Page, error) { return []*pages.Page{pg}, nil },
66+
OpenPageForMutationFunc: func(unitID model.ID) (backend.PageMutator, error) {
67+
return &mock.MockPageMutator{
68+
SetWidgetPropertyFunc: func(widgetRef string, prop string, value any) error {
69+
setPropCalled = true
70+
if widgetRef != "myWidget" {
71+
t.Errorf("expected widgetRef myWidget, got %s", widgetRef)
72+
}
73+
if prop != "Caption" {
74+
t.Errorf("expected prop Caption, got %s", prop)
75+
}
76+
return nil
77+
},
78+
SaveFunc: func() error { saved = true; return nil },
79+
}, nil
80+
},
81+
}
82+
h := mkHierarchy(mod)
83+
withContainer(h, pg.ContainerID, mod.ID)
84+
ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h))
85+
assertNoError(t, execAlterPage(ctx, &ast.AlterPageStmt{
86+
PageName: ast.QualifiedName{Module: "MyModule", Name: "TestPage"},
87+
Operations: []ast.AlterPageOperation{
88+
&ast.SetPropertyOp{
89+
Target: ast.WidgetRef{Widget: "myWidget"},
90+
Properties: map[string]any{"Caption": "Hello"},
91+
},
92+
},
93+
}))
94+
if !setPropCalled {
95+
t.Error("expected SetWidgetProperty to be called")
96+
}
97+
if !saved {
98+
t.Error("expected Save to be called")
99+
}
100+
assertContainsStr(t, buf.String(), "Altered page")
101+
assertContainsStr(t, buf.String(), "MyModule.TestPage")
102+
}
103+
104+
// ---------------------------------------------------------------------------
105+
// Snippet happy path
106+
// ---------------------------------------------------------------------------
107+
108+
func TestAlterPage_Snippet_Success(t *testing.T) {
109+
mod := mkModule("MyModule")
110+
snp := mkSnippet(mod.ID, "TestSnippet")
111+
saved := false
112+
mb := &mock.MockBackend{
113+
IsConnectedFunc: func() bool { return true },
114+
ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil },
115+
ListFoldersFunc: func() ([]*types.FolderInfo, error) { return nil, nil },
116+
ListSnippetsFunc: func() ([]*pages.Snippet, error) {
117+
return []*pages.Snippet{snp}, nil
118+
},
119+
OpenPageForMutationFunc: func(unitID model.ID) (backend.PageMutator, error) {
120+
return &mock.MockPageMutator{
121+
SaveFunc: func() error { saved = true; return nil },
122+
}, nil
123+
},
124+
}
125+
h := mkHierarchy(mod)
126+
withContainer(h, snp.ContainerID, mod.ID)
127+
ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h))
128+
assertNoError(t, execAlterPage(ctx, &ast.AlterPageStmt{
129+
ContainerType: "snippet",
130+
PageName: ast.QualifiedName{Module: "MyModule", Name: "TestSnippet"},
131+
}))
132+
if !saved {
133+
t.Error("expected Save to be called")
134+
}
135+
assertContainsStr(t, buf.String(), "Altered snippet")
136+
}
137+
138+
// ---------------------------------------------------------------------------
139+
// Open mutator error
140+
// ---------------------------------------------------------------------------
141+
142+
func TestAlterPage_OpenMutatorError(t *testing.T) {
143+
mod := mkModule("MyModule")
144+
pg := mkPage(mod.ID, "TestPage")
145+
mb := &mock.MockBackend{
146+
IsConnectedFunc: func() bool { return true },
147+
ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil },
148+
ListFoldersFunc: func() ([]*types.FolderInfo, error) { return nil, nil },
149+
ListPagesFunc: func() ([]*pages.Page, error) { return []*pages.Page{pg}, nil },
150+
OpenPageForMutationFunc: func(unitID model.ID) (backend.PageMutator, error) {
151+
return nil, fmt.Errorf("lock error")
152+
},
153+
}
154+
h := mkHierarchy(mod)
155+
withContainer(h, pg.ContainerID, mod.ID)
156+
ctx, _ := newMockCtx(t, withBackend(mb), withHierarchy(h))
157+
err := execAlterPage(ctx, &ast.AlterPageStmt{
158+
PageName: ast.QualifiedName{Module: "MyModule", Name: "TestPage"},
159+
})
160+
assertError(t, err)
161+
assertContainsStr(t, err.Error(), "open page")
162+
}
163+
164+
// ---------------------------------------------------------------------------
165+
// Save error
166+
// ---------------------------------------------------------------------------
167+
168+
func TestAlterPage_SaveError(t *testing.T) {
169+
mod := mkModule("MyModule")
170+
pg := mkPage(mod.ID, "TestPage")
171+
mb := &mock.MockBackend{
172+
IsConnectedFunc: func() bool { return true },
173+
ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil },
174+
ListFoldersFunc: func() ([]*types.FolderInfo, error) { return nil, nil },
175+
ListPagesFunc: func() ([]*pages.Page, error) { return []*pages.Page{pg}, nil },
176+
OpenPageForMutationFunc: func(unitID model.ID) (backend.PageMutator, error) {
177+
return &mock.MockPageMutator{
178+
SaveFunc: func() error { return fmt.Errorf("disk full") },
179+
}, nil
180+
},
181+
}
182+
h := mkHierarchy(mod)
183+
withContainer(h, pg.ContainerID, mod.ID)
184+
ctx, _ := newMockCtx(t, withBackend(mb), withHierarchy(h))
185+
err := execAlterPage(ctx, &ast.AlterPageStmt{
186+
PageName: ast.QualifiedName{Module: "MyModule", Name: "TestPage"},
187+
})
188+
assertError(t, err)
189+
assertContainsStr(t, err.Error(), "save")
190+
}
191+
192+
// ---------------------------------------------------------------------------
193+
// DROP widget via mutator
194+
// ---------------------------------------------------------------------------
195+
196+
func TestAlterPage_DropWidget_Success(t *testing.T) {
197+
mod := mkModule("MyModule")
198+
pg := mkPage(mod.ID, "TestPage")
199+
dropCalled := false
200+
mb := &mock.MockBackend{
201+
IsConnectedFunc: func() bool { return true },
202+
ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil },
203+
ListFoldersFunc: func() ([]*types.FolderInfo, error) { return nil, nil },
204+
ListPagesFunc: func() ([]*pages.Page, error) { return []*pages.Page{pg}, nil },
205+
OpenPageForMutationFunc: func(unitID model.ID) (backend.PageMutator, error) {
206+
return &mock.MockPageMutator{
207+
DropWidgetFunc: func(refs []backend.WidgetRef) error {
208+
dropCalled = true
209+
if len(refs) != 1 || refs[0].Widget != "oldWidget" {
210+
t.Errorf("unexpected refs: %v", refs)
211+
}
212+
return nil
213+
},
214+
SaveFunc: func() error { return nil },
215+
}, nil
216+
},
217+
}
218+
h := mkHierarchy(mod)
219+
withContainer(h, pg.ContainerID, mod.ID)
220+
ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h))
221+
assertNoError(t, execAlterPage(ctx, &ast.AlterPageStmt{
222+
PageName: ast.QualifiedName{Module: "MyModule", Name: "TestPage"},
223+
Operations: []ast.AlterPageOperation{
224+
&ast.DropWidgetOp{
225+
Targets: []ast.WidgetRef{{Widget: "oldWidget"}},
226+
},
227+
},
228+
}))
229+
if !dropCalled {
230+
t.Error("expected DropWidget to be called")
231+
}
232+
assertContainsStr(t, buf.String(), "Altered page")
233+
}
234+
235+
// ---------------------------------------------------------------------------
236+
// ADD VARIABLE
237+
// ---------------------------------------------------------------------------
238+
239+
func TestAlterPage_AddVariable_Success(t *testing.T) {
240+
mod := mkModule("MyModule")
241+
pg := mkPage(mod.ID, "TestPage")
242+
addVarCalled := false
243+
mb := &mock.MockBackend{
244+
IsConnectedFunc: func() bool { return true },
245+
ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil },
246+
ListFoldersFunc: func() ([]*types.FolderInfo, error) { return nil, nil },
247+
ListPagesFunc: func() ([]*pages.Page, error) { return []*pages.Page{pg}, nil },
248+
OpenPageForMutationFunc: func(unitID model.ID) (backend.PageMutator, error) {
249+
return &mock.MockPageMutator{
250+
AddVariableFunc: func(name, dataType, defaultValue string) error {
251+
addVarCalled = true
252+
if name != "MyVar" || dataType != "String" || defaultValue != "hello" {
253+
t.Errorf("unexpected variable: %s %s %s", name, dataType, defaultValue)
254+
}
255+
return nil
256+
},
257+
SaveFunc: func() error { return nil },
258+
}, nil
259+
},
260+
}
261+
h := mkHierarchy(mod)
262+
withContainer(h, pg.ContainerID, mod.ID)
263+
ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h))
264+
assertNoError(t, execAlterPage(ctx, &ast.AlterPageStmt{
265+
PageName: ast.QualifiedName{Module: "MyModule", Name: "TestPage"},
266+
Operations: []ast.AlterPageOperation{
267+
&ast.AddVariableOp{
268+
Variable: ast.PageVariable{Name: "MyVar", DataType: "String", DefaultValue: "hello"},
269+
},
270+
},
271+
}))
272+
if !addVarCalled {
273+
t.Error("expected AddVariable to be called")
274+
}
275+
assertContainsStr(t, buf.String(), "Altered page")
276+
}
277+
278+
// ---------------------------------------------------------------------------
279+
// SET Layout on snippet — unsupported
280+
// ---------------------------------------------------------------------------
281+
282+
func TestAlterPage_SetLayout_Snippet_Unsupported(t *testing.T) {
283+
mod := mkModule("MyModule")
284+
snp := mkSnippet(mod.ID, "TestSnippet")
285+
mb := &mock.MockBackend{
286+
IsConnectedFunc: func() bool { return true },
287+
ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil },
288+
ListFoldersFunc: func() ([]*types.FolderInfo, error) { return nil, nil },
289+
ListSnippetsFunc: func() ([]*pages.Snippet, error) {
290+
return []*pages.Snippet{snp}, nil
291+
},
292+
OpenPageForMutationFunc: func(unitID model.ID) (backend.PageMutator, error) {
293+
return &mock.MockPageMutator{}, nil
294+
},
295+
}
296+
h := mkHierarchy(mod)
297+
withContainer(h, snp.ContainerID, mod.ID)
298+
ctx, _ := newMockCtx(t, withBackend(mb), withHierarchy(h))
299+
err := execAlterPage(ctx, &ast.AlterPageStmt{
300+
ContainerType: "snippet",
301+
PageName: ast.QualifiedName{Module: "MyModule", Name: "TestSnippet"},
302+
Operations: []ast.AlterPageOperation{
303+
&ast.SetLayoutOp{
304+
NewLayout: ast.QualifiedName{Module: "M", Name: "L"},
305+
},
306+
},
307+
})
308+
assertError(t, err)
309+
assertContainsStr(t, err.Error(), "not supported")
310+
}

0 commit comments

Comments
 (0)