Skip to content

Commit 113ff44

Browse files
committed
refactor(ufw):
- added tests for create implementation - fixed -p and -o flags that were used for something else
1 parent 2d5bcd4 commit 113ff44

2 files changed

Lines changed: 267 additions & 2 deletions

File tree

internal/cmd/ufw/rules/create/create.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,15 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
113113
}
114114

115115
func configureFlags(cmd *cobra.Command) {
116-
cmd.Flags().StringP(productFlag, "p", "", "The source service (e.g., Load Balancer, Redis) where you want to attach a rule")
116+
cmd.Flags().String(productFlag, "", "The source service (e.g., Load Balancer, Redis) where you want to attach a rule")
117117
cmd.Flags().StringP(typeFlag, "t", "", "Type (ACL/SecurityRule/SecurityGroup/PublicIP) You can check /provider-options route for them")
118118
cmd.Flags().StringP(sourceIpFlag, "s", "", "The IP (CIDR) to which the rule applies (e.g. 192.168.0.1/32)")
119119
cmd.Flags().StringP(instanceIdFlag, "i", "", "Instance ID that will have attached your rule")
120120
cmd.Flags().StringP(directionFlag, "d", "", "Direction (the direction of the traffic, typically ingress or egress, for security rules type)")
121121
cmd.Flags().StringP(descriptionFlag, "D", "", "Description")
122122
cmd.Flags().StringP(etherTypeFlag, "e", "", "Specifies the bound of the rule (for security rules type)")
123123
cmd.Flags().StringP(portRangeFlag, "r", "", "Port range (the Port range to which the rule applies, for security rules type)")
124-
cmd.Flags().StringP(protocolFlag, "o", "", "The network protocol (e.g. TCP, UDP, ICMP, for security rules type)")
124+
cmd.Flags().String(protocolFlag, "", "The network protocol (e.g. TCP, UDP, ICMP, for security rules type)")
125125
cmd.Flags().Int32P(offsetFlag, "f", -1, "Offset - Position in the ACL list of an instance, will be ignored at creation")
126126
cmd.Flags().StringP(securityGroupIdFlag, "g", "", "Security group ID - The ID of the Security Group")
127127

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,266 @@
11
package create
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+
testInstanceId = uuid.NewString()
25+
)
26+
27+
const (
28+
testRegion = "eu01"
29+
testProduct = "redis"
30+
testType = "ACL"
31+
testSourceIp = "1.1.1.1/32"
32+
)
33+
34+
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
35+
flagValues := map[string]string{
36+
globalflags.ProjectIdFlag: testProjectId,
37+
globalflags.RegionFlag: testRegion,
38+
productFlag: testProduct,
39+
typeFlag: testType,
40+
sourceIpFlag: testSourceIp,
41+
instanceIdFlag: testInstanceId,
42+
directionFlag: "ingress",
43+
descriptionFlag: "example-description",
44+
etherTypeFlag: "IPv4",
45+
portRangeFlag: "80-443",
46+
protocolFlag: "TCP",
47+
offsetFlag: "10",
48+
securityGroupIdFlag: "example-sec-group",
49+
}
50+
for _, mod := range mods {
51+
mod(flagValues)
52+
}
53+
return flagValues
54+
}
55+
56+
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
57+
model := &inputModel{
58+
GlobalFlagModel: &globalflags.GlobalFlagModel{
59+
ProjectId: testProjectId,
60+
Region: testRegion,
61+
Verbosity: globalflags.VerbosityDefault,
62+
},
63+
Product: new(testProduct),
64+
Type: new(testType),
65+
SourceIp: new(testSourceIp),
66+
InstanceId: new(testInstanceId),
67+
Direction: new("ingress"),
68+
Description: new("example-description"),
69+
EtherType: new("IPv4"),
70+
PortRange: new("80-443"),
71+
Protocol: new("TCP"),
72+
Offset: new(int32(10)),
73+
SecurityGroupId: new("example-sec-group"),
74+
}
75+
for _, mod := range mods {
76+
mod(model)
77+
}
78+
return model
79+
}
80+
81+
func fixtureRequest(mods ...func(request *ufw.ApiCreateRuleRequest)) ufw.ApiCreateRuleRequest {
82+
request := testClient.DefaultAPI.CreateRule(testCtx, testProjectId, testRegion)
83+
request = request.CreateRulePayload(ufw.CreateRulePayload{
84+
Product: testProduct,
85+
Type: testType,
86+
SourceIP: testSourceIp,
87+
InstanceId: testInstanceId,
88+
Direction: new("ingress"),
89+
Description: new("example-description"),
90+
EtherType: new("IPv4"),
91+
PortRange: new("80-443"),
92+
Protocol: new("TCP"),
93+
Offset: new(int32(10)),
94+
SecurityGroupId: new("example-sec-group"),
95+
})
96+
for _, mod := range mods {
97+
mod(&request)
98+
}
99+
return request
100+
}
101+
102+
func TestParseInput(t *testing.T) {
103+
tests := []struct {
104+
description string
105+
flagValues map[string]string
106+
isValid bool
107+
expectedModel *inputModel
108+
}{
109+
{
110+
description: "base",
111+
flagValues: fixtureFlagValues(),
112+
isValid: true,
113+
expectedModel: fixtureInputModel(),
114+
},
115+
{
116+
description: "no values",
117+
flagValues: map[string]string{},
118+
isValid: false,
119+
},
120+
{
121+
description: "required fields only",
122+
flagValues: map[string]string{
123+
globalflags.ProjectIdFlag: testProjectId,
124+
globalflags.RegionFlag: testRegion,
125+
productFlag: testProduct,
126+
typeFlag: testType,
127+
sourceIpFlag: testSourceIp,
128+
instanceIdFlag: testInstanceId,
129+
},
130+
isValid: true,
131+
expectedModel: &inputModel{
132+
GlobalFlagModel: &globalflags.GlobalFlagModel{
133+
ProjectId: testProjectId,
134+
Region: testRegion,
135+
Verbosity: globalflags.VerbosityDefault,
136+
},
137+
Product: new(testProduct),
138+
Type: new(testType),
139+
SourceIp: new(testSourceIp),
140+
InstanceId: new(testInstanceId),
141+
},
142+
},
143+
{
144+
description: "project id missing",
145+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
146+
delete(flagValues, globalflags.ProjectIdFlag)
147+
}),
148+
isValid: false,
149+
},
150+
{
151+
description: "project id invalid 1",
152+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
153+
flagValues[globalflags.ProjectIdFlag] = ""
154+
}),
155+
isValid: false,
156+
},
157+
{
158+
description: "project id invalid 2",
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+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
167+
delete(flagValues, globalflags.RegionFlag)
168+
}),
169+
isValid: false,
170+
},
171+
}
172+
173+
for _, tt := range tests {
174+
t.Run(tt.description, func(t *testing.T) {
175+
parseInputWrapper := func(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
176+
return parseInput(p, cmd)
177+
}
178+
testutils.TestParseInput(t, NewCmd, parseInputWrapper, tt.expectedModel, nil, tt.flagValues, tt.isValid)
179+
})
180+
}
181+
}
182+
183+
func TestBuildRequest(t *testing.T) {
184+
tests := []struct {
185+
description string
186+
model *inputModel
187+
expectedRequest ufw.ApiCreateRuleRequest
188+
}{
189+
{
190+
description: "base",
191+
model: fixtureInputModel(),
192+
expectedRequest: fixtureRequest(),
193+
},
194+
{
195+
description: "required fields only",
196+
model: &inputModel{
197+
GlobalFlagModel: &globalflags.GlobalFlagModel{
198+
ProjectId: testProjectId,
199+
Region: testRegion,
200+
Verbosity: globalflags.VerbosityDefault,
201+
},
202+
Product: new(testProduct),
203+
Type: new(testType),
204+
SourceIp: new(testSourceIp),
205+
InstanceId: new(testInstanceId),
206+
},
207+
expectedRequest: testClient.DefaultAPI.CreateRule(testCtx, testProjectId, testRegion).
208+
CreateRulePayload(ufw.CreateRulePayload{
209+
Product: testProduct,
210+
Type: testType,
211+
SourceIP: testSourceIp,
212+
InstanceId: testInstanceId,
213+
}),
214+
},
215+
}
216+
217+
for _, tt := range tests {
218+
t.Run(tt.description, func(t *testing.T) {
219+
request := buildRequest(testCtx, tt.model, testClient)
220+
221+
diff := cmp.Diff(request, tt.expectedRequest,
222+
cmp.AllowUnexported(tt.expectedRequest),
223+
cmpopts.EquateComparable(testCtx, ufw.DefaultAPIService{}),
224+
)
225+
if diff != "" {
226+
t.Fatalf("Data does not match: %s", diff)
227+
}
228+
})
229+
}
230+
}
231+
232+
func TestOutputResult(t *testing.T) {
233+
type args struct {
234+
outputFormat string
235+
async bool
236+
projectLabel string
237+
rule *ufw.CreateRuleResponse
238+
}
239+
tests := []struct {
240+
name string
241+
args args
242+
wantErr bool
243+
}{
244+
{
245+
name: "empty",
246+
args: args{},
247+
wantErr: true,
248+
},
249+
{
250+
name: "set empty response",
251+
args: args{
252+
rule: &ufw.CreateRuleResponse{},
253+
},
254+
wantErr: false,
255+
},
256+
}
257+
258+
params := testparams.NewTestParams()
259+
for _, tt := range tests {
260+
t.Run(tt.name, func(t *testing.T) {
261+
if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.async, tt.args.projectLabel, tt.args.rule); (err != nil) != tt.wantErr {
262+
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
263+
}
264+
})
265+
}
266+
}

0 commit comments

Comments
 (0)