diff --git a/app/module_validator.go b/app/module_validator.go index bf594164..cee8bce2 100644 --- a/app/module_validator.go +++ b/app/module_validator.go @@ -5,55 +5,75 @@ import ( "os" "github.com/logrusorgru/aurora" + "github.com/wtfutil/wtf/cfg" "github.com/wtfutil/wtf/wtf" ) type ModuleValidator struct{} +type widgetError struct { + name string + validationErrors []cfg.Validatable +} + func NewModuleValidator() *ModuleValidator { - val := &ModuleValidator{} - return val + return &ModuleValidator{} } // Validate rolls through all the enabled widgets and looks for configuration errors. // If it finds any it stringifies them, writes them to the console, and kills the app gracefully func (val *ModuleValidator) Validate(widgets []wtf.Wtfable) { - var errStr string - hasErrors := false + validationErrors := validate(widgets) - for _, widget := range widgets { - var widgetErrStr string - - for _, val := range widget.CommonSettings().Validations() { - if val.HasError() { - hasErrors = true - widgetErrStr += fmt.Sprintf(" - %s\t%s %v\n", val, aurora.Red("Error:"), val.Error()) + if len(validationErrors) > 0 { + fmt.Println() + for _, error := range validationErrors { + for _, message := range error.errorMessages() { + fmt.Println(message) } } - - if widgetErrStr != "" { - errStr += fmt.Sprintf( - "%s\n", - fmt.Sprintf( - "%s in %s configuration", - aurora.Red("Errors"), - aurora.Yellow( - fmt.Sprintf( - "%s.position", - widget.Name(), - ), - ), - ), - ) - - errStr += widgetErrStr + "\n" - } - } - - if hasErrors { fmt.Println() - fmt.Println(errStr) os.Exit(1) } } + +func validate(widgets []wtf.Wtfable) (widgetErrors []widgetError) { + for _, widget := range widgets { + error := widgetError{name: widget.Name()} + + for _, val := range widget.CommonSettings().Validations() { + if val.HasError() { + error.validationErrors = append(error.validationErrors, val) + } + } + + if len(error.validationErrors) > 0 { + widgetErrors = append(widgetErrors, error) + } + } + + return widgetErrors +} + +func (err widgetError) errorMessages() (messages []string) { + widgetMessage := fmt.Sprintf( + "%s in %s configuration", + aurora.Red("Errors"), + aurora.Yellow( + fmt.Sprintf( + "%s.position", + err.name, + ), + ), + ) + messages = append(messages, widgetMessage) + + for _, e := range err.validationErrors { + configMessage := fmt.Sprintf(" - %s\t%s %v", e.String(), aurora.Red("Error:"), e.Error()) + + messages = append(messages, configMessage) + } + + return messages +} diff --git a/app/module_validator_test.go b/app/module_validator_test.go new file mode 100644 index 00000000..83108e6b --- /dev/null +++ b/app/module_validator_test.go @@ -0,0 +1,102 @@ +package app + +import ( + "fmt" + "testing" + + "github.com/logrusorgru/aurora" + "github.com/olebedev/config" + "github.com/stretchr/testify/assert" + "github.com/wtfutil/wtf/wtf" +) + +const ( + valid = ` +wtf: + mods: + clocks: + enabled: true + position: + top: 0 + left: 0 + height: 1 + width: 1 + refreshInterval: 30` + + invalid = ` +wtf: + mods: + clocks: + enabled: true + position: + top: abc + left: 0 + height: 1 + width: 1 + refreshInterval: 30` +) + +func Test_NewModuleValidator(t *testing.T) { + assert.IsType(t, &ModuleValidator{}, NewModuleValidator()) +} + +func Test_validate(t *testing.T) { + tests := []struct { + name string + moduleName string + config *config.Config + expected []string + }{ + { + name: "valid config", + moduleName: "clocks", + config: func() *config.Config { + cfg, _ := config.ParseYaml(valid) + return cfg + }(), + expected: []string{}, + }, + { + name: "invalid config", + moduleName: "clocks", + config: func() *config.Config { + cfg, _ := config.ParseYaml(invalid) + return cfg + }(), + expected: []string{ + fmt.Sprintf("%s in %s configuration", aurora.Red("Errors"), aurora.Yellow("clocks.position")), + fmt.Sprintf( + " - Invalid value for %s: 0 %s strconv.ParseInt: parsing \"abc\": invalid syntax", + aurora.Yellow("top"), + aurora.Red("Error:"), + ), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + widget := MakeWidget(nil, nil, tt.moduleName, tt.config) + + if widget == nil { + t.Logf("Failed to create widget %s", tt.moduleName) + t.FailNow() + } + + errs := validate([]wtf.Wtfable{widget}) + + if len(tt.expected) == 0 { + assert.Empty(t, errs) + } else { + assert.NotEmpty(t, errs) + + var actual []string + for _, err := range errs { + actual = append(actual, err.errorMessages()...) + } + + assert.Equal(t, tt.expected, actual) + } + }) + } +} diff --git a/cfg/config_files.go b/cfg/config_files.go index cf65dec3..9e7ceb40 100644 --- a/cfg/config_files.go +++ b/cfg/config_files.go @@ -29,7 +29,7 @@ const ( // CreateFile creates the named file in the config directory, if it does not already exist. // If the file exists it does not recreate it. -// If successful, eturns the absolute path to the file +// If successful, returns the absolute path to the file // If unsuccessful, returns an error func CreateFile(fileName string) (string, error) { configDir, err := WtfConfigDir() diff --git a/go.mod b/go.mod index c431daec..8cfdb8f2 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,8 @@ require ( github.com/briandowns/openweathermap v0.0.0-20180804155945-5f41b7c9d92d github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e - github.com/digitalocean/godo v1.46.0 github.com/creack/pty v1.1.11 + github.com/digitalocean/godo v1.46.0 github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/docker v1.13.1 github.com/docker/docker-credential-helpers v0.6.3 diff --git a/go.sum b/go.sum index 12e5016e..c1ec6c28 100644 --- a/go.sum +++ b/go.sum @@ -925,8 +925,6 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443 h1:X18bCaipMcoJGm27Nv7zr4XYPKGUy92GtqboKC2Hxaw= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=