Skip to content

Commit b3f6256

Browse files
committed
refactor(ufw):
- added tests for update implementation - fixed -o flag that was already used by the system
1 parent 113ff44 commit b3f6256

2 files changed

Lines changed: 272 additions & 1 deletion

File tree

‎internal/cmd/ufw/rules/update/update.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func configureFlags(cmd *cobra.Command) {
110110
cmd.Flags().StringP(descriptionFlag, "D", "", "Description")
111111
cmd.Flags().StringP(etherTypeFlag, "e", "", "Specifies the bound of the rule (for security rules type)")
112112
cmd.Flags().StringP(portRangeFlag, "r", "", "Port range (the Port range to which the rule applies, for security rules type)")
113-
cmd.Flags().StringP(protocolFlag, "o", "", "The network protocol (e.g. TCP, UDP, ICMP, for security rules type)")
113+
cmd.Flags().String(protocolFlag, "", "The network protocol (e.g. TCP, UDP, ICMP, for security rules type)")
114114

115115
err := flags.MarkFlagsRequired(cmd, sourceIpFlag)
116116
cobra.CheckErr(err)
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,272 @@
11
package update
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/google/go-cmp/cmp/cmpopts"
9+
"github.com/google/uuid"
10+
"github.com/spf13/cobra"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/testparams"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
15+
ufw "github.com/stackitcloud/stackit-sdk-go/services/ufw/v1api"
16+
)
17+
18+
type testCtxKey struct{}
19+
20+
var (
21+
testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
22+
testClient = &ufw.APIClient{DefaultAPI: &ufw.DefaultAPIService{}}
23+
testProjectId = uuid.NewString()
24+
testRuleRefId = uuid.NewString()
25+
)
26+
27+
const (
28+
testRegion = "eu01"
29+
testSourceIp = "1.1.1.1/32"
30+
)
31+
32+
func fixtureArgValues(mods ...func(argValues []string)) []string {
33+
argValues := []string{
34+
testRuleRefId,
35+
}
36+
for _, mod := range mods {
37+
mod(argValues)
38+
}
39+
return argValues
40+
}
41+
42+
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
43+
flagValues := map[string]string{
44+
globalflags.ProjectIdFlag: testProjectId,
45+
globalflags.RegionFlag: testRegion,
46+
sourceIpFlag: testSourceIp,
47+
directionFlag: "ingress",
48+
descriptionFlag: "example-description",
49+
etherTypeFlag: "IPv4",
50+
portRangeFlag: "80-443",
51+
protocolFlag: "TCP",
52+
}
53+
for _, mod := range mods {
54+
mod(flagValues)
55+
}
56+
return flagValues
57+
}
58+
59+
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
60+
model := &inputModel{
61+
GlobalFlagModel: &globalflags.GlobalFlagModel{
62+
ProjectId: testProjectId,
63+
Region: testRegion,
64+
Verbosity: globalflags.VerbosityDefault,
65+
},
66+
RuleRefId: "", // Left blank because parseInput in the source file currently does not populate it
67+
SourceIp: new(testSourceIp),
68+
Direction: new("ingress"),
69+
Description: new("example-description"),
70+
EtherType: new("IPv4"),
71+
PortRange: new("80-443"),
72+
Protocol: new("TCP"),
73+
}
74+
for _, mod := range mods {
75+
mod(model)
76+
}
77+
return model
78+
}
79+
80+
func fixtureRequest(mods ...func(request *ufw.ApiUpdateRuleRequest)) ufw.ApiUpdateRuleRequest {
81+
request := testClient.DefaultAPI.UpdateRule(testCtx, testProjectId, testRegion, testRuleRefId)
82+
request = request.UpdateRulePayload(ufw.UpdateRulePayload{
83+
SourceIP: testSourceIp,
84+
Direction: new("ingress"),
85+
EtherType: new("IPv4"),
86+
PortRange: new("80-443"),
87+
Protocol: new("TCP"),
88+
})
89+
for _, mod := range mods {
90+
mod(&request)
91+
}
92+
return request
93+
}
94+
95+
func TestParseInput(t *testing.T) {
96+
tests := []struct {
97+
description string
98+
argValues []string
99+
flagValues map[string]string
100+
isValid bool
101+
expectedModel *inputModel
102+
}{
103+
{
104+
description: "base",
105+
argValues: fixtureArgValues(),
106+
flagValues: fixtureFlagValues(),
107+
isValid: true,
108+
expectedModel: fixtureInputModel(),
109+
},
110+
{
111+
description: "no values",
112+
argValues: []string{},
113+
flagValues: map[string]string{},
114+
isValid: false,
115+
},
116+
{
117+
description: "no flag values",
118+
argValues: fixtureArgValues(),
119+
flagValues: map[string]string{},
120+
isValid: false,
121+
},
122+
{
123+
description: "required flags only",
124+
argValues: fixtureArgValues(),
125+
flagValues: map[string]string{
126+
globalflags.ProjectIdFlag: testProjectId,
127+
globalflags.RegionFlag: testRegion,
128+
sourceIpFlag: testSourceIp,
129+
},
130+
isValid: true,
131+
expectedModel: &inputModel{
132+
GlobalFlagModel: &globalflags.GlobalFlagModel{
133+
ProjectId: testProjectId,
134+
Region: testRegion,
135+
Verbosity: globalflags.VerbosityDefault,
136+
},
137+
SourceIp: new(testSourceIp),
138+
},
139+
},
140+
{
141+
description: "project id missing",
142+
argValues: fixtureArgValues(),
143+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
144+
delete(flagValues, globalflags.ProjectIdFlag)
145+
}),
146+
isValid: false,
147+
},
148+
{
149+
description: "project id invalid 1",
150+
argValues: fixtureArgValues(),
151+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
152+
flagValues[globalflags.ProjectIdFlag] = ""
153+
}),
154+
isValid: false,
155+
},
156+
{
157+
description: "project id invalid 2",
158+
argValues: fixtureArgValues(),
159+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
160+
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
161+
}),
162+
isValid: false,
163+
},
164+
{
165+
description: "region missing",
166+
argValues: fixtureArgValues(),
167+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
168+
delete(flagValues, globalflags.RegionFlag)
169+
}),
170+
isValid: false,
171+
},
172+
{
173+
description: "source IP missing",
174+
argValues: fixtureArgValues(),
175+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
176+
delete(flagValues, sourceIpFlag)
177+
}),
178+
isValid: false,
179+
},
180+
}
181+
182+
for _, tt := range tests {
183+
t.Run(tt.description, func(t *testing.T) {
184+
parseInputWrapper := func(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
185+
return parseInput(p, cmd)
186+
}
187+
testutils.TestParseInput(t, NewCmd, parseInputWrapper, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
188+
})
189+
}
190+
}
191+
192+
func TestBuildRequest(t *testing.T) {
193+
tests := []struct {
194+
description string
195+
model *inputModel
196+
expectedRequest ufw.ApiUpdateRuleRequest
197+
}{
198+
{
199+
description: "base",
200+
model: fixtureInputModel(func(model *inputModel) {
201+
model.RuleRefId = testRuleRefId // Inject the ID that parseInput currently skips
202+
}),
203+
expectedRequest: fixtureRequest(),
204+
},
205+
{
206+
description: "required fields only",
207+
model: &inputModel{
208+
GlobalFlagModel: &globalflags.GlobalFlagModel{
209+
ProjectId: testProjectId,
210+
Region: testRegion,
211+
Verbosity: globalflags.VerbosityDefault,
212+
},
213+
RuleRefId: testRuleRefId,
214+
SourceIp: new(testSourceIp),
215+
},
216+
expectedRequest: testClient.DefaultAPI.UpdateRule(testCtx, testProjectId, testRegion, testRuleRefId).
217+
UpdateRulePayload(ufw.UpdateRulePayload{
218+
SourceIP: testSourceIp,
219+
}),
220+
},
221+
}
222+
223+
for _, tt := range tests {
224+
t.Run(tt.description, func(t *testing.T) {
225+
request := buildRequest(testCtx, tt.model, testClient)
226+
227+
diff := cmp.Diff(request, tt.expectedRequest,
228+
cmp.AllowUnexported(tt.expectedRequest),
229+
cmpopts.EquateComparable(testCtx, ufw.DefaultAPIService{}),
230+
)
231+
if diff != "" {
232+
t.Fatalf("Data does not match: %s", diff)
233+
}
234+
})
235+
}
236+
}
237+
238+
func TestOutputResult(t *testing.T) {
239+
type args struct {
240+
outputFormat string
241+
async bool
242+
projectLabel string
243+
rule *ufw.UpdateRuleResponse
244+
}
245+
tests := []struct {
246+
name string
247+
args args
248+
wantErr bool
249+
}{
250+
{
251+
name: "empty",
252+
args: args{},
253+
wantErr: true,
254+
},
255+
{
256+
name: "set empty response",
257+
args: args{
258+
rule: &ufw.UpdateRuleResponse{},
259+
},
260+
wantErr: false,
261+
},
262+
}
263+
264+
params := testparams.NewTestParams()
265+
for _, tt := range tests {
266+
t.Run(tt.name, func(t *testing.T) {
267+
if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.async, tt.args.projectLabel, tt.args.rule); (err != nil) != tt.wantErr {
268+
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
269+
}
270+
})
271+
}
272+
}

0 commit comments

Comments
 (0)