git.iko.soy/typeable/oauth2-proxy
on master help sign in
add duration test

Signed-off-by: Jan Larwig <jan@larwig.com>

commit6720d8da608774fdc6c435f91dd9564af4ee5ba9 authortuunit <jan@larwig.com> dateSun 9 Feb 2025, 16:44 (2 years ago) parent676f56a
3 files changed+95−91
pkg/apis/options/common_test.go−87 pkg/apis/options/duration_test.go+82 pkg/apis/options/load.go+13−4
pkg/apis/options/common_test.godeleted−87
@@ -1,87 +0,0 @@
1−package options
2−
3−import (
4− "encoding/json"
5− "errors"
6− "time"
7−
8− . "github.com/onsi/ginkgo/v2"
9− . "github.com/onsi/gomega"
10−)
11−
12−var _ = Describe("Common", func() {
13− Context("Duration", func() {
14− type marshalJSONTableInput struct {
15− duration Duration
16− expectedJSON string
17− }
18−
19− DescribeTable("MarshalJSON",
20− func(in marshalJSONTableInput) {
21− data, err := in.duration.MarshalJSON()
22− Expect(err).ToNot(HaveOccurred())
23− Expect(string(data)).To(Equal(in.expectedJSON))
24−
25− var d Duration
26− Expect(json.Unmarshal(data, &d)).To(Succeed())
27− Expect(d).To(Equal(in.duration))
28− },
29− Entry("30 seconds", marshalJSONTableInput{
30− duration: Duration(30 * time.Second),
31− expectedJSON: "\"30s\"",
32− }),
33− Entry("1 minute", marshalJSONTableInput{
34− duration: Duration(1 * time.Minute),
35− expectedJSON: "\"1m0s\"",
36− }),
37− Entry("1 hour 15 minutes", marshalJSONTableInput{
38− duration: Duration(75 * time.Minute),
39− expectedJSON: "\"1h15m0s\"",
40− }),
41− Entry("A zero Duration", marshalJSONTableInput{
42− duration: Duration(0),
43− expectedJSON: "\"0s\"",
44− }),
45− )
46−
47− type unmarshalJSONTableInput struct {
48− json string
49− expectedErr error
50− expectedDuration Duration
51− }
52−
53− DescribeTable("UnmarshalJSON",
54− func(in unmarshalJSONTableInput) {
55− // A duration must be initialised pointer before UnmarshalJSON will work.
56− zero := Duration(0)
57− d := &zero
58−
59− err := d.UnmarshalJSON([]byte(in.json))
60− if in.expectedErr != nil {
61− Expect(err).To(MatchError(in.expectedErr.Error()))
62− } else {
63− Expect(err).ToNot(HaveOccurred())
64− }
65− Expect(d).ToNot(BeNil())
66− Expect(*d).To(Equal(in.expectedDuration))
67− },
68− Entry("1m", unmarshalJSONTableInput{
69− json: "\"1m\"",
70− expectedDuration: Duration(1 * time.Minute),
71− }),
72− Entry("30s", unmarshalJSONTableInput{
73− json: "\"30s\"",
74− expectedDuration: Duration(30 * time.Second),
75− }),
76− Entry("1h15m", unmarshalJSONTableInput{
77− json: "\"1h15m\"",
78− expectedDuration: Duration(75 * time.Minute),
79− }),
80− Entry("am", unmarshalJSONTableInput{
81− json: "\"am\"",
82− expectedErr: errors.New("time: invalid duration \"am\""),
83− expectedDuration: Duration(0),
84− }),
85− )
86− })
87−})
pkg/apis/options/duration_test.gonew file+82
@@ -0,0 +1,82 @@
1+package options
2+
3+import (
4+ "testing"
5+ "time"
6+)
7+
8+func TestDecode(t *testing.T) {
9+ type result struct {
10+ Duration time.Duration `json:"duration"`
11+ }
12+
13+ tests := []struct {
14+ name string
15+ input map[string]interface{}
16+ out result
17+ expected time.Duration
18+ expectedErr bool
19+ }{
20+ {
21+ name: "Valid String Duration with single unit",
22+ input: map[string]interface{}{"duration": "3s"},
23+ out: result{},
24+ expected: 3 * time.Second,
25+ expectedErr: false,
26+ },
27+ {
28+ name: "Valid String Duration with multiple units",
29+ input: map[string]interface{}{"duration": "1h20m30s"},
30+ out: result{},
31+ expected: 1*time.Hour + 20*time.Minute + 30*time.Second,
32+ expectedErr: false,
33+ },
34+ {
35+ name: "Valid Float Duration",
36+ input: map[string]interface{}{"duration": 2.5},
37+ out: result{},
38+ expected: 2500 * time.Millisecond,
39+ expectedErr: false,
40+ },
41+ {
42+ name: "Valid Int64 Duration",
43+ input: map[string]interface{}{"duration": int64(5000000000)},
44+ out: result{},
45+ expected: 5 * time.Second,
46+ expectedErr: false,
47+ },
48+ {
49+ name: "Invalid String",
50+ input: map[string]interface{}{"duration": "invalid"},
51+ out: result{},
52+ expected: 0,
53+ expectedErr: true,
54+ },
55+ {
56+ name: "Unsupported Type",
57+ input: map[string]interface{}{"duration": true},
58+ out: result{},
59+ expected: 0,
60+ expectedErr: true,
61+ },
62+ }
63+
64+ for _, tt := range tests {
65+ t.Run(tt.name, func(t *testing.T) {
66+ var result struct {
67+ Duration time.Duration `json:"duration"`
68+ }
69+
70+ err := Decode(tt.input, &result)
71+ if (err != nil) != tt.expectedErr {
72+ t.Errorf("expected error: %v, got: %v", tt.expectedErr, err)
73+ }
74+
75+ if !tt.expectedErr {
76+ if result.Duration != tt.expected {
77+ t.Errorf("expected: %v, got: %v", tt.expected, result.Duration)
78+ }
79+ }
80+ })
81+ }
82+}
pkg/apis/options/load.go+13−4
@@ -69,16 +69,25 @@ func LoadYAML(configFileName string, opts interface{}) error {
6969 return fmt.Errorf("error unmarshalling config: %w", err)
7070 }
7171
72− return Decode(intermediate, opts)
73−}
74−
75−func Decode(input interface{}, result interface{}) error {
7672 // Using mapstructure to decode arbitrary yaml structure into options and
7773 // merge with existing values instead of overwriting everything. This is especially
7874 // important as we have a lot of default values for boolean which are supposed to be
7975 // true by default. Normally by just parsing through yaml all booleans that aren't
8076 // referenced in the config file would be parsed as false and we cannot identify after
8177 // the fact if they have been explicitly set to false or have not been referenced.
78+ return Decode(intermediate, opts)
79+}
80+
81+// Decode processes an input map and decodes it into a given struct while preserving default values.
82+// It ensures proper conversion of duration values from strings, floats, and int64 into time.Duration.
83+//
84+// Parameters:
85+// - input: A map[string]interface{} representing the input data.
86+// - result: A pointer to a struct where the decoded values will be stored.
87+//
88+// Returns:
89+// - An error if decoding fails or if there are unmapped keys.
90+func Decode(input interface{}, result interface{}) error {
8291 decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
8392 DecodeHook: mapstructure.ComposeDecodeHookFunc(toDurationHookFunc()),
8493 Metadata: nil, // Don't track any metadata