add duration test
This commit is contained in:
parent
202de3a05e
commit
11f1b9eacd
|
|
@ -1,87 +0,0 @@
|
||||||
package options
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("Common", func() {
|
|
||||||
Context("Duration", func() {
|
|
||||||
type marshalJSONTableInput struct {
|
|
||||||
duration Duration
|
|
||||||
expectedJSON string
|
|
||||||
}
|
|
||||||
|
|
||||||
DescribeTable("MarshalJSON",
|
|
||||||
func(in marshalJSONTableInput) {
|
|
||||||
data, err := in.duration.MarshalJSON()
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(string(data)).To(Equal(in.expectedJSON))
|
|
||||||
|
|
||||||
var d Duration
|
|
||||||
Expect(json.Unmarshal(data, &d)).To(Succeed())
|
|
||||||
Expect(d).To(Equal(in.duration))
|
|
||||||
},
|
|
||||||
Entry("30 seconds", marshalJSONTableInput{
|
|
||||||
duration: Duration(30 * time.Second),
|
|
||||||
expectedJSON: "\"30s\"",
|
|
||||||
}),
|
|
||||||
Entry("1 minute", marshalJSONTableInput{
|
|
||||||
duration: Duration(1 * time.Minute),
|
|
||||||
expectedJSON: "\"1m0s\"",
|
|
||||||
}),
|
|
||||||
Entry("1 hour 15 minutes", marshalJSONTableInput{
|
|
||||||
duration: Duration(75 * time.Minute),
|
|
||||||
expectedJSON: "\"1h15m0s\"",
|
|
||||||
}),
|
|
||||||
Entry("A zero Duration", marshalJSONTableInput{
|
|
||||||
duration: Duration(0),
|
|
||||||
expectedJSON: "\"0s\"",
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
type unmarshalJSONTableInput struct {
|
|
||||||
json string
|
|
||||||
expectedErr error
|
|
||||||
expectedDuration Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
DescribeTable("UnmarshalJSON",
|
|
||||||
func(in unmarshalJSONTableInput) {
|
|
||||||
// A duration must be initialised pointer before UnmarshalJSON will work.
|
|
||||||
zero := Duration(0)
|
|
||||||
d := &zero
|
|
||||||
|
|
||||||
err := d.UnmarshalJSON([]byte(in.json))
|
|
||||||
if in.expectedErr != nil {
|
|
||||||
Expect(err).To(MatchError(in.expectedErr.Error()))
|
|
||||||
} else {
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
}
|
|
||||||
Expect(d).ToNot(BeNil())
|
|
||||||
Expect(*d).To(Equal(in.expectedDuration))
|
|
||||||
},
|
|
||||||
Entry("1m", unmarshalJSONTableInput{
|
|
||||||
json: "\"1m\"",
|
|
||||||
expectedDuration: Duration(1 * time.Minute),
|
|
||||||
}),
|
|
||||||
Entry("30s", unmarshalJSONTableInput{
|
|
||||||
json: "\"30s\"",
|
|
||||||
expectedDuration: Duration(30 * time.Second),
|
|
||||||
}),
|
|
||||||
Entry("1h15m", unmarshalJSONTableInput{
|
|
||||||
json: "\"1h15m\"",
|
|
||||||
expectedDuration: Duration(75 * time.Minute),
|
|
||||||
}),
|
|
||||||
Entry("am", unmarshalJSONTableInput{
|
|
||||||
json: "\"am\"",
|
|
||||||
expectedErr: errors.New("time: invalid duration \"am\""),
|
|
||||||
expectedDuration: Duration(0),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecode(t *testing.T) {
|
||||||
|
type result struct {
|
||||||
|
Duration time.Duration `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input map[string]interface{}
|
||||||
|
out result
|
||||||
|
expected time.Duration
|
||||||
|
expectedErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid String Duration with single unit",
|
||||||
|
input: map[string]interface{}{"duration": "3s"},
|
||||||
|
out: result{},
|
||||||
|
expected: 3 * time.Second,
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid String Duration with multiple units",
|
||||||
|
input: map[string]interface{}{"duration": "1h20m30s"},
|
||||||
|
out: result{},
|
||||||
|
expected: 1*time.Hour + 20*time.Minute + 30*time.Second,
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid Float Duration",
|
||||||
|
input: map[string]interface{}{"duration": 2.5},
|
||||||
|
out: result{},
|
||||||
|
expected: 2500 * time.Millisecond,
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid Int64 Duration",
|
||||||
|
input: map[string]interface{}{"duration": int64(5000000000)},
|
||||||
|
out: result{},
|
||||||
|
expected: 5 * time.Second,
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid String",
|
||||||
|
input: map[string]interface{}{"duration": "invalid"},
|
||||||
|
out: result{},
|
||||||
|
expected: 0,
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unsupported Type",
|
||||||
|
input: map[string]interface{}{"duration": true},
|
||||||
|
out: result{},
|
||||||
|
expected: 0,
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var result struct {
|
||||||
|
Duration time.Duration `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := Decode(tt.input, &result)
|
||||||
|
if (err != nil) != tt.expectedErr {
|
||||||
|
t.Errorf("expected error: %v, got: %v", tt.expectedErr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.expectedErr {
|
||||||
|
if result.Duration != tt.expected {
|
||||||
|
t.Errorf("expected: %v, got: %v", tt.expected, result.Duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -69,16 +69,25 @@ func LoadYAML(configFileName string, opts interface{}) error {
|
||||||
return fmt.Errorf("error unmarshalling config: %w", err)
|
return fmt.Errorf("error unmarshalling config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Decode(intermediate, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Decode(input interface{}, result interface{}) error {
|
|
||||||
// Using mapstructure to decode arbitrary yaml structure into options and
|
// Using mapstructure to decode arbitrary yaml structure into options and
|
||||||
// merge with existing values instead of overwriting everything. This is especially
|
// merge with existing values instead of overwriting everything. This is especially
|
||||||
// important as we have a lot of default values for boolean which are supposed to be
|
// important as we have a lot of default values for boolean which are supposed to be
|
||||||
// true by default. Normally by just parsing through yaml all booleans that aren't
|
// true by default. Normally by just parsing through yaml all booleans that aren't
|
||||||
// referenced in the config file would be parsed as false and we cannot identify after
|
// referenced in the config file would be parsed as false and we cannot identify after
|
||||||
// the fact if they have been explicitly set to false or have not been referenced.
|
// the fact if they have been explicitly set to false or have not been referenced.
|
||||||
|
return Decode(intermediate, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode processes an input map and decodes it into a given struct while preserving default values.
|
||||||
|
// It ensures proper conversion of duration values from strings, floats, and int64 into time.Duration.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - input: A map[string]interface{} representing the input data.
|
||||||
|
// - result: A pointer to a struct where the decoded values will be stored.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - An error if decoding fails or if there are unmapped keys.
|
||||||
|
func Decode(input interface{}, result interface{}) error {
|
||||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
DecodeHook: mapstructure.ComposeDecodeHookFunc(toDurationHookFunc()),
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(toDurationHookFunc()),
|
||||||
Metadata: nil, // Don't track any metadata
|
Metadata: nil, // Don't track any metadata
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue