Skip to content

Commit 9c55970

Browse files
committed
PCSM-219: Add integration tests for the CLI
1 parent 2a0e34c commit 9c55970

File tree

7 files changed

+1336
-85
lines changed

7 files changed

+1336
-85
lines changed

config/config.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
package config
33

44
import (
5+
"math"
56
"os"
67
"slices"
78
"strings"
89

10+
"github.com/dustin/go-humanize"
911
"github.com/spf13/cobra"
1012
"github.com/spf13/viper"
1113

@@ -72,3 +74,69 @@ func UseTargetClientCompressors() []string {
7274

7375
return rv
7476
}
77+
78+
// ResolveCloneSegmentSize resolves the clone segment size from an optional HTTP value
79+
// or falls back to the CLI config value. Both sources are validated.
80+
func ResolveCloneSegmentSize(cfg *Config, value *string) (int64, error) {
81+
if value != nil {
82+
return parseAndValidateCloneSegmentSize(*value)
83+
}
84+
85+
// Fall back to CLI config value and validate it
86+
sizeBytes := cfg.Clone.SegmentSizeBytes()
87+
88+
err := ValidateCloneSegmentSize(uint64(max(sizeBytes, 0))) //nolint:gosec
89+
if err != nil {
90+
return 0, errors.Wrap(err, "config clone-segment-size")
91+
}
92+
93+
return sizeBytes, nil
94+
}
95+
96+
// ResolveCloneReadBatchSize resolves the clone read batch size from an optional HTTP value
97+
// or falls back to the CLI config value. Both sources are validated.
98+
func ResolveCloneReadBatchSize(cfg *Config, value *string) (int32, error) {
99+
if value != nil {
100+
return parseAndValidateCloneReadBatchSize(*value)
101+
}
102+
103+
// Fall back to CLI config value and validate it
104+
sizeBytes := cfg.Clone.ReadBatchSizeBytes()
105+
106+
err := ValidateCloneReadBatchSize(uint64(max(sizeBytes, 0))) //nolint:gosec
107+
if err != nil {
108+
return 0, errors.Wrap(err, "config clone-read-batch-size")
109+
}
110+
111+
return sizeBytes, nil
112+
}
113+
114+
// parseAndValidateCloneSegmentSize parses a byte size string and validates it.
115+
func parseAndValidateCloneSegmentSize(value string) (int64, error) {
116+
sizeBytes, err := humanize.ParseBytes(value)
117+
if err != nil {
118+
return 0, errors.Wrapf(err, "invalid cloneSegmentSize value: %s", value)
119+
}
120+
121+
err = ValidateCloneSegmentSize(sizeBytes)
122+
if err != nil {
123+
return 0, err
124+
}
125+
126+
return int64(min(sizeBytes, math.MaxInt64)), nil //nolint:gosec
127+
}
128+
129+
// parseAndValidateCloneReadBatchSize parses a byte size string and validates it.
130+
func parseAndValidateCloneReadBatchSize(value string) (int32, error) {
131+
sizeBytes, err := humanize.ParseBytes(value)
132+
if err != nil {
133+
return 0, errors.Wrapf(err, "invalid cloneReadBatchSize value: %s", value)
134+
}
135+
136+
err = ValidateCloneReadBatchSize(sizeBytes)
137+
if err != nil {
138+
return 0, err
139+
}
140+
141+
return int32(min(sizeBytes, math.MaxInt32)), nil //nolint:gosec
142+
}

config/config_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package config_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
"github.com/percona/percona-clustersync-mongodb/config"
9+
)
10+
11+
func TestUseTargetClientCompressors(t *testing.T) {
12+
// Subtests use t.Setenv which cannot be combined with t.Parallel
13+
// because environment variables are process-global state.
14+
15+
tests := []struct {
16+
name string
17+
envVal string
18+
want []string
19+
wantNil bool
20+
}{
21+
{
22+
name: "empty env - returns nil",
23+
envVal: "",
24+
want: nil,
25+
wantNil: true,
26+
},
27+
{
28+
name: "single valid compressor zstd",
29+
envVal: "zstd",
30+
want: []string{"zstd"},
31+
},
32+
{
33+
name: "single valid compressor zlib",
34+
envVal: "zlib",
35+
want: []string{"zlib"},
36+
},
37+
{
38+
name: "single valid compressor snappy",
39+
envVal: "snappy",
40+
want: []string{"snappy"},
41+
},
42+
{
43+
name: "multiple valid compressors",
44+
envVal: "zstd,zlib,snappy",
45+
want: []string{"zstd", "zlib", "snappy"},
46+
},
47+
{
48+
name: "compressors with spaces",
49+
envVal: " zstd , zlib , snappy ",
50+
want: []string{"zstd", "zlib", "snappy"},
51+
},
52+
{
53+
name: "invalid compressor ignored",
54+
envVal: "zstd,invalid,zlib",
55+
want: []string{"zstd", "zlib"},
56+
},
57+
{
58+
name: "all invalid compressors - returns empty slice",
59+
envVal: "invalid,gzip,lz4",
60+
want: []string{},
61+
},
62+
{
63+
name: "duplicate compressors - deduplicated",
64+
envVal: "zstd,zstd,zlib,zstd",
65+
want: []string{"zstd", "zlib"},
66+
},
67+
{
68+
name: "whitespace only - returns nil",
69+
envVal: " ",
70+
want: nil,
71+
wantNil: true,
72+
},
73+
{
74+
name: "mixed valid and invalid with spaces",
75+
envVal: " zstd , invalid , snappy ",
76+
want: []string{"zstd", "snappy"},
77+
},
78+
}
79+
80+
for _, tt := range tests {
81+
t.Run(tt.name, func(t *testing.T) {
82+
t.Setenv("PCSM_DEV_TARGET_CLIENT_COMPRESSORS", tt.envVal)
83+
84+
got := config.UseTargetClientCompressors()
85+
86+
if tt.wantNil {
87+
assert.Nil(t, got)
88+
} else {
89+
assert.Equal(t, tt.want, got)
90+
}
91+
})
92+
}
93+
}

config/schema_test.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package config_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/dustin/go-humanize"
8+
"github.com/stretchr/testify/assert"
9+
10+
"github.com/percona/percona-clustersync-mongodb/config"
11+
)
12+
13+
func TestMongoDBConfig_OperationTimeoutDuration(t *testing.T) {
14+
t.Parallel()
15+
16+
tests := []struct {
17+
name string
18+
timeout string
19+
want time.Duration
20+
}{
21+
{
22+
name: "empty string - returns default",
23+
timeout: "",
24+
want: config.DefaultMongoDBCliOperationTimeout,
25+
},
26+
{
27+
name: "valid duration 30s",
28+
timeout: "30s",
29+
want: 30 * time.Second,
30+
},
31+
{
32+
name: "valid duration 5m",
33+
timeout: "5m",
34+
want: 5 * time.Minute,
35+
},
36+
{
37+
name: "valid duration 1h",
38+
timeout: "1h",
39+
want: time.Hour,
40+
},
41+
{
42+
name: "valid duration 2h30m",
43+
timeout: "2h30m",
44+
want: 2*time.Hour + 30*time.Minute,
45+
},
46+
{
47+
name: "invalid format - returns default",
48+
timeout: "invalid",
49+
want: config.DefaultMongoDBCliOperationTimeout,
50+
},
51+
{
52+
name: "zero duration - returns default",
53+
timeout: "0s",
54+
want: config.DefaultMongoDBCliOperationTimeout,
55+
},
56+
{
57+
name: "negative duration - returns default",
58+
timeout: "-5m",
59+
want: config.DefaultMongoDBCliOperationTimeout,
60+
},
61+
}
62+
63+
for _, tt := range tests {
64+
t.Run(tt.name, func(t *testing.T) {
65+
t.Parallel()
66+
67+
cfg := &config.MongoDBConfig{
68+
OperationTimeout: tt.timeout,
69+
}
70+
71+
got := cfg.OperationTimeoutDuration()
72+
assert.Equal(t, tt.want, got)
73+
})
74+
}
75+
}
76+
77+
func TestCloneConfig_SegmentSizeBytes(t *testing.T) {
78+
t.Parallel()
79+
80+
tests := []struct {
81+
name string
82+
segmentSize string
83+
want int64
84+
}{
85+
{
86+
name: "empty string - returns auto (0)",
87+
segmentSize: "",
88+
want: config.AutoCloneSegmentSize,
89+
},
90+
{
91+
name: "valid size 1GiB",
92+
segmentSize: "1GiB",
93+
want: humanize.GiByte,
94+
},
95+
{
96+
name: "valid size 500MB",
97+
segmentSize: "500MB",
98+
want: 500 * humanize.MByte,
99+
},
100+
{
101+
name: "valid size 2GiB",
102+
segmentSize: "2GiB",
103+
want: 2 * humanize.GiByte,
104+
},
105+
{
106+
name: "invalid format - returns auto (0)",
107+
segmentSize: "invalid",
108+
want: config.AutoCloneSegmentSize,
109+
},
110+
{
111+
name: "zero value string - returns auto (0)",
112+
segmentSize: "0",
113+
want: config.AutoCloneSegmentSize,
114+
},
115+
}
116+
117+
for _, tt := range tests {
118+
t.Run(tt.name, func(t *testing.T) {
119+
t.Parallel()
120+
121+
cfg := &config.CloneConfig{
122+
SegmentSize: tt.segmentSize,
123+
}
124+
125+
got := cfg.SegmentSizeBytes()
126+
assert.Equal(t, tt.want, got)
127+
})
128+
}
129+
}
130+
131+
func TestCloneConfig_ReadBatchSizeBytes(t *testing.T) {
132+
t.Parallel()
133+
134+
tests := []struct {
135+
name string
136+
readBatchSize string
137+
want int32
138+
}{
139+
{
140+
name: "empty string - returns 0",
141+
readBatchSize: "",
142+
want: 0,
143+
},
144+
{
145+
name: "valid size 16MiB",
146+
readBatchSize: "16MiB",
147+
want: 16 * humanize.MiByte,
148+
},
149+
{
150+
name: "valid size 32MB",
151+
readBatchSize: "32MB",
152+
want: 32 * humanize.MByte,
153+
},
154+
{
155+
name: "valid size 48MB",
156+
readBatchSize: "48MB",
157+
want: 48 * humanize.MByte,
158+
},
159+
{
160+
name: "invalid format - returns 0",
161+
readBatchSize: "invalid",
162+
want: 0,
163+
},
164+
{
165+
name: "zero value string - returns 0",
166+
readBatchSize: "0",
167+
want: 0,
168+
},
169+
}
170+
171+
for _, tt := range tests {
172+
t.Run(tt.name, func(t *testing.T) {
173+
t.Parallel()
174+
175+
cfg := &config.CloneConfig{
176+
ReadBatchSize: tt.readBatchSize,
177+
}
178+
179+
got := cfg.ReadBatchSizeBytes()
180+
assert.Equal(t, tt.want, got)
181+
})
182+
}
183+
}

0 commit comments

Comments
 (0)