Skip to content

Commit 6925778

Browse files
authored
test: increase coverage from 82.5% to 86.2% (phase 2) (#503)
## Summary - **Core package**: 90.1% → 93.0% (+2.9pp) — scheduler, clock, composejob, docker SDK provider, resilience, performance metrics - **CLI package**: 78.9% → 84.2% (+5.3pp) — config parsing, daemon, doctor, docker handler, validate, init, progress, hashpw - **Middlewares package**: 88.1% → 97.5% (+9.4pp) — mail dedup, preset loading/caching, restore, save, webhook config/security - **Overall**: 82.5% → 86.2% (+3.7pp) **27 new test files** with ~7,000 lines of test code covering previously uncovered edge cases. ### Bug fixes included - `fix(test)`: Remove `t.Parallel()` from `TestValidateExecuteValidFile` — modifies global `os.Stdout`, races with parallel tests - `fix(test)`: Remove `t.Parallel()` from `TestPresetLoader_LoadFromURL` — modifies global URL validator, races with `SetGlobalSecurityConfig` ## Test plan - [x] All tests pass locally (`go test ./... -count=1 -short`) - [x] `golangci-lint run ./...` — 0 issues - [x] Coverage verified via `go tool cover -func` - [ ] CI passes on GitHub Actions
2 parents e49b2e7 + 01df1b2 commit 6925778

29 files changed

+7235
-7
lines changed

cli/config_cov_ext_test.go

Lines changed: 654 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
// Copyright (c) 2025-2026 Netresearch DTT GmbH
2+
// SPDX-License-Identifier: MIT
3+
4+
package cli
5+
6+
import (
7+
"strings"
8+
"testing"
9+
"time"
10+
11+
"github.com/go-playground/validator/v10"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
// --- validateDurationGTE ---
17+
18+
func TestValidateDurationGTE_TimeDuration(t *testing.T) {
19+
t.Parallel()
20+
21+
validate := validator.New()
22+
err := validate.RegisterValidation("duration_gte", validateDurationGTE)
23+
require.NoError(t, err)
24+
25+
type testStruct struct {
26+
Duration time.Duration `validate:"duration_gte=1s"`
27+
}
28+
29+
tests := []struct {
30+
name string
31+
dur time.Duration
32+
wantErr bool
33+
}{
34+
{"above threshold", 5 * time.Second, false},
35+
{"equal threshold", 1 * time.Second, false},
36+
{"below threshold", 500 * time.Millisecond, true},
37+
{"zero", 0, true},
38+
}
39+
40+
for _, tt := range tests {
41+
t.Run(tt.name, func(t *testing.T) {
42+
t.Parallel()
43+
s := testStruct{Duration: tt.dur}
44+
err := validate.Struct(s)
45+
if tt.wantErr {
46+
assert.Error(t, err)
47+
} else {
48+
assert.NoError(t, err)
49+
}
50+
})
51+
}
52+
}
53+
54+
func TestValidateDurationGTE_InvalidParam_Coverage(t *testing.T) {
55+
t.Parallel()
56+
57+
validate := validator.New()
58+
err := validate.RegisterValidation("duration_gte_cov", validateDurationGTE)
59+
require.NoError(t, err)
60+
61+
type testStruct struct {
62+
Duration time.Duration `validate:"duration_gte_cov=not-a-duration"`
63+
}
64+
65+
s := testStruct{Duration: 5 * time.Second}
66+
err = validate.Struct(s)
67+
// Should fail because param can't be parsed
68+
assert.Error(t, err)
69+
}
70+
71+
func TestValidateDurationGTE_OtherType(t *testing.T) {
72+
t.Parallel()
73+
74+
validate := validator.New()
75+
err := validate.RegisterValidation("duration_gte", validateDurationGTE)
76+
require.NoError(t, err)
77+
78+
// String fields should always pass (not a duration type)
79+
type testStruct struct {
80+
Name string `validate:"duration_gte=1s"`
81+
}
82+
83+
s := testStruct{Name: "test"}
84+
err = validate.Struct(s)
85+
assert.NoError(t, err)
86+
}
87+
88+
// --- formatValidationError ---
89+
90+
func TestFormatValidationError_CronAndDockerImage(t *testing.T) {
91+
t.Parallel()
92+
93+
validate := validator.New()
94+
_ = validate.RegisterValidation("cron_cov", validateCron)
95+
_ = validate.RegisterValidation("dockerimage_cov", validateDockerImage)
96+
_ = validate.RegisterValidation("duration_gte_fmt", validateDurationGTE)
97+
98+
type testStruct struct {
99+
CronField string `validate:"cron_cov"`
100+
ImageField string `validate:"dockerimage_cov"`
101+
DurField time.Duration `validate:"duration_gte_fmt=1h"`
102+
}
103+
104+
s := testStruct{
105+
CronField: "invalid cron",
106+
ImageField: "!!!invalid!!!",
107+
DurField: 1 * time.Second,
108+
}
109+
110+
err := validate.Struct(s)
111+
require.Error(t, err)
112+
113+
validationErrors, ok := err.(validator.ValidationErrors)
114+
require.True(t, ok)
115+
116+
for _, ve := range validationErrors {
117+
msg := formatValidationError(ve)
118+
assert.NotEmpty(t, msg)
119+
assert.Contains(t, msg, ve.Field())
120+
}
121+
}
122+
123+
func TestFormatValidationError_DefaultCase(t *testing.T) {
124+
t.Parallel()
125+
126+
validate := validator.New()
127+
_ = validate.RegisterValidation("custom_check", func(fl validator.FieldLevel) bool {
128+
return false
129+
})
130+
131+
type testStruct struct {
132+
Value string `validate:"custom_check"`
133+
}
134+
135+
s := testStruct{Value: "test"}
136+
err := validate.Struct(s)
137+
require.Error(t, err)
138+
139+
validationErrors, ok := err.(validator.ValidationErrors)
140+
require.True(t, ok)
141+
for _, ve := range validationErrors {
142+
msg := formatValidationError(ve)
143+
assert.Contains(t, msg, "custom_check")
144+
assert.Contains(t, msg, "validation")
145+
}
146+
}
147+
148+
// --- findClosestMatch ---
149+
150+
func TestFindClosestMatch_EmptyCandidates(t *testing.T) {
151+
t.Parallel()
152+
153+
result := findClosestMatch("test", []string{})
154+
assert.Empty(t, result)
155+
}
156+
157+
func TestFindClosestMatch_ExactMatch(t *testing.T) {
158+
t.Parallel()
159+
160+
result := findClosestMatch("schedule", []string{"schedule", "command", "image"})
161+
assert.Equal(t, "schedule", result)
162+
}
163+
164+
func TestFindClosestMatch_CloseMatch(t *testing.T) {
165+
t.Parallel()
166+
167+
result := findClosestMatch("scheduel", []string{"schedule", "command", "image"})
168+
assert.Equal(t, "schedule", result)
169+
}
170+
171+
func TestFindClosestMatch_NoCloseMatch(t *testing.T) {
172+
t.Parallel()
173+
174+
result := findClosestMatch("zzzzzzzzzzz", []string{"schedule", "command", "image"})
175+
assert.Empty(t, result)
176+
}
177+
178+
func TestFindClosestMatch_LongKey(t *testing.T) {
179+
t.Parallel()
180+
181+
// For keys > 5 chars, threshold = len*2/5
182+
result := findClosestMatch("web-addresss", []string{"web-address", "web-auth", "web-port"})
183+
assert.Equal(t, "web-address", result)
184+
}
185+
186+
// --- validateDockerImage ---
187+
188+
func TestValidateDockerImage_RegistryWithPort(t *testing.T) {
189+
t.Parallel()
190+
191+
validate := validator.New()
192+
_ = validate.RegisterValidation("dockerimage", validateDockerImage)
193+
194+
type testStruct struct {
195+
Image string `validate:"dockerimage"`
196+
}
197+
198+
tests := []struct {
199+
name string
200+
image string
201+
wantErr bool
202+
}{
203+
{"registry with port", "registry.example.com:5000/image:tag", false},
204+
{"invalid port chars", "registry.example.com:abc/image:tag", true},
205+
{"standard image", "alpine:latest", false},
206+
{"image with digest", "alpine@sha256:" + strings.Repeat("a", 64), false},
207+
{"empty", "", false}, // Empty is valid (required check handles this)
208+
}
209+
210+
for _, tt := range tests {
211+
t.Run(tt.name, func(t *testing.T) {
212+
t.Parallel()
213+
s := testStruct{Image: tt.image}
214+
err := validate.Struct(s)
215+
if tt.wantErr {
216+
assert.Error(t, err)
217+
} else {
218+
assert.NoError(t, err)
219+
}
220+
})
221+
}
222+
}
223+
224+
// --- validateCron edge cases ---
225+
226+
func TestValidateCron_SpecialExpressions(t *testing.T) {
227+
t.Parallel()
228+
229+
validate := validator.New()
230+
_ = validate.RegisterValidation("cron", validateCron)
231+
232+
type testStruct struct {
233+
Cron string `validate:"cron"`
234+
}
235+
236+
tests := []struct {
237+
name string
238+
cron string
239+
wantErr bool
240+
}{
241+
{"@triggered", "@triggered", false},
242+
{"@manual", "@manual", false},
243+
{"@none", "@none", false},
244+
{"@every 1h30m", "@every 1h30m", false},
245+
{"@every invalid", "@every notaduration", true},
246+
{"invalid @prefix", "@invalid", true},
247+
{"6 field cron", "0 30 14 * * 1-5", false},
248+
{"too many fields", "0 0 0 0 0 0 0", true},
249+
{"too few fields", "0 0 0 0", true},
250+
{"invalid chars", "a b c d e", true},
251+
}
252+
253+
for _, tt := range tests {
254+
t.Run(tt.name, func(t *testing.T) {
255+
t.Parallel()
256+
s := testStruct{Cron: tt.cron}
257+
err := validate.Struct(s)
258+
if tt.wantErr {
259+
assert.Error(t, err)
260+
} else {
261+
assert.NoError(t, err)
262+
}
263+
})
264+
}
265+
}

0 commit comments

Comments
 (0)