From 9fe53dc97f96e2dc29adfd776a469aa0b7f89d28 Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Mon, 5 Apr 2021 17:43:35 +0200 Subject: Reworked error handling for better and simpler tests --- config/config.go | 13 +++--- config/config_test.go | 80 ++++++++++++++++--------------------- config/error.go | 92 +++++++++++++++++++++++++++++++++++++++++++ config/error_test.go | 20 ++++++++++ config/test_data/invalid.yaml | 1 + config/test_data/invalid_yaml | 1 - go.mod | 2 +- go.sum | 10 ++++- 8 files changed, 161 insertions(+), 58 deletions(-) create mode 100644 config/error.go create mode 100644 config/error_test.go create mode 100644 config/test_data/invalid.yaml delete mode 100644 config/test_data/invalid_yaml diff --git a/config/config.go b/config/config.go index 653aeb6..f97467a 100644 --- a/config/config.go +++ b/config/config.go @@ -5,7 +5,6 @@ import ( "os" "regexp" - "github.com/pkg/errors" "gopkg.in/yaml.v3" ) @@ -23,16 +22,16 @@ func (c *Config) validate() error { // address if ip := net.ParseIP(c.Address); ip == nil { if _, err := net.LookupIP(c.Address); err != nil { - return errors.New("Invalid address " + c.Address + ", it must be a valid ipv4 address, ipv6 address, or resolvable name.") + return newInvalidAddressError(c.Address, err) } } // port if _, err := net.LookupPort("tcp", c.Port); err != nil { - return errors.New("Invalid port " + c.Port + ", it must be a valid port number or tcp service name. Got error : " + err.Error()) + return newInvalidPortError(c.Port, err) } // token if ok := validToken.MatchString(c.Token); !ok { - return errors.New("Invalid token, must be an hexadecimal string that lookslike 12345678-9abc-def0-1234-56789abcdef0, got " + c.Token + " instead.") + return newInvalidTokenError(c.Token) } return nil } @@ -42,15 +41,15 @@ func LoadFile(path string) (*Config, error) { var c *Config f, errOpen := os.Open(path) if errOpen != nil { - return nil, errors.Wrapf(errOpen, "Failed to open configuration file %s", path) + return nil, newOpenError(path, errOpen) } defer f.Close() decoder := yaml.NewDecoder(f) if err := decoder.Decode(&c); err != nil { - return nil, errors.Wrap(err, "Failed to decode configuration file") + return nil, newDecodeError(path, err) } if err := c.validate(); err != nil { - return nil, errors.Wrap(err, "Failed to validate configuration") + return nil, err } return c, nil } diff --git a/config/config_test.go b/config/config_test.go index 0b78260..3904b5d 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -3,66 +3,52 @@ package config import ( "reflect" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestLoadFile(t *testing.T) { - // Non existant file - _, err := LoadFile("test_data/non-existant") - if err == nil { - t.Fatal("non-existant config file failed without error") - } - - // Invalid yaml file - _, err = LoadFile("test_data/invalid_yaml") - if err == nil { - t.Fatal("invalid_yaml config file failed without error") - } - - // Invalid address - if _, err = LoadFile("test_data/invalid_address.yaml"); err == nil { - t.Fatal("Invalid address should fail to load") - } - - // Invalid address unreasolvable - if _, err = LoadFile("test_data/invalid_address_unresolvable.yaml"); err == nil { - t.Fatal("Unresolvable address should fail to load") - } - - // Invalid port - if _, err = LoadFile("test_data/invalid_port.yaml"); err == nil { - t.Fatal("Invalid port should fail to load") - } - - // Invalid token - if _, err = LoadFile("test_data/invalid_token.yaml"); err == nil { - t.Fatal("Invalid token should fail to load") - } - // Minimal yaml file - want := Config{ + minimalConfig := Config{ Address: "127.0.0.2", Port: "8082", Token: "12345678-9abc-def0-1234-56789abcdef0", } - config, err := LoadFile("test_data/minimal.yaml") - if err != nil { - t.Fatalf("minimal example failed with error: %v", err) - } - if config != nil && !reflect.DeepEqual(want, *config) { - t.Fatalf("minimal example failed:\nwant:%+v\ngot: %+v", want, *config) - } // Minimal yaml file with hostname resolving - want = Config{ + minimalConfigWithResolving := Config{ Address: "localhost", Port: "8082", Token: "12345678-9abc-def0-1234-56789abcdef0", } - config, err = LoadFile("test_data/minimal_with_hostname.yaml") - if err != nil { - t.Fatalf("minimal example failed with error: %v", err) - } - if config != nil && !reflect.DeepEqual(want, *config) { - t.Fatalf("minimal example failed:\nwant:%+v\ngot: %+v", want, *config) + + // Test cases + testCases := []struct { + name string + input string + expected *Config + expectedError interface{} + }{ + {"Non existant file", "test_data/non-existant", nil, &OpenError{}}, + {"Invalid file content", "test_data/invalid.yaml", nil, &DecodeError{}}, + {"Invalid address should fail to load", "test_data/invalid_address.yaml", nil, &InvalidAddressError{}}, + {"Unresolvable address should fail to load", "test_data/invalid_address_unresolvable.yaml", nil, &InvalidAddressError{}}, + {"Invalid port should fail to load", "test_data/invalid_port.yaml", nil, &InvalidPortError{}}, + {"Invalid token should fail to load", "test_data/invalid_token.yaml", nil, &InvalidTokenError{}}, + {"Minimal config", "test_data/minimal.yaml", &minimalConfig, nil}, + {"Minimal config with resolving", "test_data/minimal_with_hostname.yaml", &minimalConfigWithResolving, nil}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + valid, err := LoadFile(tc.input) + if tc.expectedError != nil { + require.Error(t, err) + assert.Equalf(t, reflect.TypeOf(err), reflect.TypeOf(tc.expectedError), "Invalid error type. Got %s but expected %s", reflect.TypeOf(err), reflect.TypeOf(tc.expectedError)) + } else { + require.NoError(t, err) + } + assert.Equal(t, tc.expected, valid, "Invalid value") + }) } } diff --git a/config/error.go b/config/error.go new file mode 100644 index 0000000..c49b6a9 --- /dev/null +++ b/config/error.go @@ -0,0 +1,92 @@ +package config + +import "fmt" + +type ErrorType int + +// file open configuration file error +type OpenError struct { + path string + err error +} + +func (e *OpenError) Error() string { + return fmt.Sprintf("Failed to open configuration file : %s", e.path) +} +func (e *OpenError) Unwrap() error { return e.err } + +func newOpenError(path string, err error) error { + return &OpenError{ + path: path, + err: err, + } +} + +// Yaml configuration file decoding error +type DecodeError struct { + path string + err error +} + +func (e *DecodeError) Error() string { + return fmt.Sprintf("Failed to decode configuration file : %s", e.path) +} +func (e *DecodeError) Unwrap() error { return e.err } + +func newDecodeError(path string, err error) error { + return &DecodeError{ + path: path, + err: err, + } +} + +// Invalid address field error +type InvalidAddressError struct { + address string + err error +} + +func (e *InvalidAddressError) Error() string { + return fmt.Sprintf("Invalid address %s : it must be a valid ipv4 address, ipv6 address, or resolvable name", e.address) +} +func (e *InvalidAddressError) Unwrap() error { return e.err } + +func newInvalidAddressError(address string, err error) error { + return &InvalidAddressError{ + address: address, + err: err, + } +} + +// Invalid port field error +type InvalidPortError struct { + port string + err error +} + +func (e *InvalidPortError) Error() string { + return fmt.Sprintf("Invalid port %s : it must be a valid port number or tcp service name", e.port) +} +func (e *InvalidPortError) Unwrap() error { return e.err } + +func newInvalidPortError(port string, err error) error { + return &InvalidPortError{ + port: port, + err: err, + } +} + +// Invalid token field error +type InvalidTokenError struct { + token string +} + +func (e *InvalidTokenError) Error() string { + return fmt.Sprintf("Invalid token %s : it must be an hexadecimal string that lookslike 12345678-9abc-def0-1234-56789abcdef0", e.token) +} + +func newInvalidTokenError(token string) error { + return &InvalidTokenError{ + token: token, + } +} diff --git a/config/error_test.go b/config/error_test.go new file mode 100644 index 0000000..f9807c1 --- /dev/null +++ b/config/error_test.go @@ -0,0 +1,20 @@ +package config + +import "testing" + +func TestErrorsCoverage(t *testing.T) { + openErr := OpenError{} + _ = openErr.Error() + _ = openErr.Unwrap() + decodeErr := DecodeError{} + _ = decodeErr.Error() + _ = decodeErr.Unwrap() + invalidAddressErr := InvalidAddressError{} + _ = invalidAddressErr.Error() + _ = invalidAddressErr.Unwrap() + invalidPortErr := InvalidPortError{} + _ = invalidPortErr.Error() + _ = invalidPortErr.Unwrap() + invalidTokenErr := InvalidTokenError{} + _ = invalidTokenErr.Error() +} diff --git a/config/test_data/invalid.yaml b/config/test_data/invalid.yaml new file mode 100644 index 0000000..db1ddad --- /dev/null +++ b/config/test_data/invalid.yaml @@ -0,0 +1 @@ +blargh(ads) diff --git a/config/test_data/invalid_yaml b/config/test_data/invalid_yaml deleted file mode 100644 index db1ddad..0000000 --- a/config/test_data/invalid_yaml +++ /dev/null @@ -1 +0,0 @@ -blargh(ads) diff --git a/go.mod b/go.mod index c7ec694..255bfa4 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.16 require ( github.com/kr/pretty v0.2.1 // indirect - github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.7.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/go.sum b/go.sum index ba9b6cd..cd0dd5e 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,18 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -- cgit v1.2.3