From 3d2554ff64d67f1f8504cc1907e5374a1575729d Mon Sep 17 00:00:00 2001 From: Thibault Pensec <39826516+thibauult@users.noreply.github.com> Date: Sat, 2 Dec 2023 12:22:52 +0100 Subject: [PATCH] Improved the overall Code Coverage (#25) Improved the overall Code Coverage of the OpenHue CLI project. Added a new `make coverage` that ease verifying the code coverage. --- .github/workflows/build.yml | 2 +- .gitignore | 1 + Makefile | 13 +++++- README.md | 11 +++++ cmd/root.go | 9 ++-- cmd/setup/auth.go | 60 ++++++++++++++++---------- cmd/setup/auth_test.go | 15 +++++++ cmd/setup/configure.go | 26 +++++++---- cmd/setup/configure_test.go | 11 +++-- cmd/setup/discover.go | 37 ++++++++-------- cmd/setup/discover_test.go | 15 +++++++ cmd/setup/doc.go | 2 + cmd/version/version_test.go | 8 ++-- go.mod | 15 +++---- go.sum | 22 ++++++---- openhue/{build.go => buildinfo.go} | 6 +-- openhue/buildinfo_test.go | 15 +++++++ openhue/config.go | 53 ++++++++++++++++------- openhue/context_test.go | 18 ++++++++ openhue/io.go | 4 +- openhue/io_test.go | 48 +++++++++++++++++++++ openhue/test/assert.go | 10 ----- openhue/test/assert/assert.go | 39 +++++++++++++++++ util/color/color_test.go | 20 +++++++++ util/color/conversion.go | 2 +- util/utils_test.go | 69 +++++++++++++++++++++++++----- 26 files changed, 405 insertions(+), 126 deletions(-) create mode 100644 cmd/setup/auth_test.go create mode 100644 cmd/setup/discover_test.go create mode 100644 cmd/setup/doc.go rename openhue/{build.go => buildinfo.go} (83%) create mode 100644 openhue/buildinfo_test.go create mode 100644 openhue/context_test.go create mode 100644 openhue/io_test.go delete mode 100644 openhue/test/assert.go create mode 100644 openhue/test/assert/assert.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5531306..f624a19 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: env: CC_TEST_REPORTER_ID: 64da8b499e15dc84234cbe4ff019d5d533718ca6768807d2aa0057f56add2e8c with: - coverageCommand: make test + coverageCommand: make coverage debug: true prefix: ${{ github.event.repository.name }} coverageLocations: diff --git a/.gitignore b/.gitignore index 8bd91ee..17e9639 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ fabric.properties # Output of the go coverage tool, specifically when used with LiteIDE *.out +coverage.html # Go workspace file go.work diff --git a/Makefile b/Makefile index 5147790..4ceb810 100644 --- a/Makefile +++ b/Makefile @@ -29,10 +29,19 @@ tidy: ## Tidy makes sure go.mod matches the source code in the module @echo "\n${GREEN}${BOLD}go.mod successfully cleaned šŸ§½${RESET}" .PHONY: test -test: ## Tidy makes sure go.mod matches the source code in the module - @$(GO) test ./... -coverprofile=c.out +test: ## Run the tests + @$(GO) test ./... @echo "\n${GREEN}${BOLD}all tests successfully passed āœ… ${RESET}" +.PHONY: coverage +coverage: ## Run the tests with coverage. Usage: make coverage [html=true] + @$(GO) test ./... -coverprofile=c.out +ifdef html + @$(GO) tool cover -html="c.out" +else + @$(GO) tool cover -func="c.out" +endif + @echo "\n${GREEN}${BOLD}all tests successfully passed āœ… ${RESET}" .PHONY: clean clean: ## diff --git a/README.md b/README.md index 340c8ab..5809785 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,17 @@ cd openhue-cli make build ``` +### Test +Run the following command to execute all the tests and calculate the code coverage: +```shell +make test +``` +If you want, you can also run the following command to visualize the coverage analysis in your browser: +```shell +make coverage +``` +> or use `make coverage html=true` to visualize the HTML report in your default web browser + ### Generate the OpenHue API Client Run the following command to generate the [OpenHue API Client](https://github.com/openhue/openhue-api): ```shell diff --git a/cmd/root.go b/cmd/root.go index aac5d77..21c84d2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -30,11 +30,11 @@ func Execute(buildInfo *openhue.BuildInfo) { // load the configuration c := openhue.Config{} - c.LoadConfig() + c.Load() // get the API Client api := c.NewOpenHueClient() - ctx := openhue.NewContext(openhue.NewIOSteams(), buildInfo, api) + ctx := openhue.NewContext(openhue.NewIOStreams(), buildInfo, api) // create the root command root := NewCmdOpenHue() @@ -44,9 +44,10 @@ func Execute(buildInfo *openhue.BuildInfo) { // add sub commands root.AddCommand(version.NewCmdVersion(ctx)) - root.AddCommand(setup.NewCmdAuth()) - root.AddCommand(setup.NewCmdDiscover()) + root.AddCommand(setup.NewCmdAuth(ctx.Io)) + root.AddCommand(setup.NewCmdDiscover(ctx.Io)) root.AddCommand(setup.NewCmdConfigure()) + root.AddCommand(set.NewCmdSet(ctx)) root.AddCommand(get.NewCmdGet(ctx)) diff --git a/cmd/setup/auth.go b/cmd/setup/auth.go index 663b87c..30e4f24 100644 --- a/cmd/setup/auth.go +++ b/cmd/setup/auth.go @@ -9,47 +9,61 @@ import ( "os" ) -var ( +type CmdAuthOptions struct { bridge string deviceType string generateClientKey bool -) +} // NewCmdAuth creates the auth command -func NewCmdAuth() *cobra.Command { +func NewCmdAuth(streams openhue.IOStreams) *cobra.Command { + + o := CmdAuthOptions{} cmd := &cobra.Command{ Use: "auth", GroupID: "config", Short: "Retrieve the Hue Application Key", - Long: `Authenticate to retrieve the Hue Application Key. Requires to go and press the button on the bridge`, - Run: func(cmd *cobra.Command, args []string) { - - client := openhue.NewOpenHueClientNoAuth(bridge) + Long: `Authenticate to retrieve the Hue Application Key. - body := new(gen.AuthenticateJSONRequestBody) - body.Devicetype = &deviceType - body.Generateclientkey = &generateClientKey - resp, err := client.AuthenticateWithResponse(context.Background(), *body) - cobra.CheckErr(err) + Requires to go and press the button on the bridge. - auth := (*resp.JSON200)[0] - if auth.Error != nil { - fmt.Println("\nšŸ–²ļø", *auth.Error.Description) - } else { - fmt.Println("\nYour hue-application-key ->", *auth.Success.Username) - } +You can use the 'openhue discover' command to lookup the IP of your bridge. +`, + Run: func(cmd *cobra.Command, args []string) { + RunCmdAuth(streams, o.bridge, o.deviceType, o.generateClientKey) }, } - cmd.Flags().StringVarP(&bridge, "bridge", "b", "", "Bridge IP (example '192.168.1.23')") + cmd.Flags().StringVarP(&o.bridge, "bridge", "b", "", "Bridge IP (example '192.168.1.23')") cmd.MarkFlagRequired("bridge") - hostname, err := os.Hostname() - cobra.CheckErr(err) - cmd.Flags().StringVarP(&deviceType, "devicetype", "d", hostname, "Device identifier") + cmd.Flags().StringVarP(&o.deviceType, "devicetype", "d", getHostName(), "Device identifier") - cmd.Flags().BoolVarP(&generateClientKey, "generateclientkey", "k", true, "Generate the client key") + cmd.Flags().BoolVarP(&o.generateClientKey, "generateclientkey", "k", true, "Generate the client key") return cmd } + +func RunCmdAuth(streams openhue.IOStreams, bridge string, deviceType string, generateClientKey bool) { + client := openhue.NewOpenHueClientNoAuth(bridge) + + body := gen.AuthenticateJSONRequestBody{} + body.Devicetype = &deviceType + body.Generateclientkey = &generateClientKey + resp, err := client.AuthenticateWithResponse(context.Background(), body) + cobra.CheckErr(err) + + auth := (*resp.JSON200)[0] + if auth.Error != nil { + fmt.Fprintln(streams.Out, "\n", *auth.Error.Description) + } else { + fmt.Fprintln(streams.Out, "\nYour hue-application-key ->", *auth.Success.Username) + } +} + +func getHostName() string { + hostname, err := os.Hostname() + cobra.CheckErr(err) + return hostname +} diff --git a/cmd/setup/auth_test.go b/cmd/setup/auth_test.go new file mode 100644 index 0000000..90acac9 --- /dev/null +++ b/cmd/setup/auth_test.go @@ -0,0 +1,15 @@ +package setup + +import ( + "openhue-cli/openhue" + "openhue-cli/openhue/test/assert" + "testing" +) + +func TestNewCmdAuth(t *testing.T) { + + cmd := NewCmdAuth(openhue.NewTestIOStreamsDiscard()) + + assert.ThatCmdUseIs(t, cmd, "auth") + assert.ThatCmdGroupIs(t, cmd, "config") +} diff --git a/cmd/setup/configure.go b/cmd/setup/configure.go index e6f761b..85ef293 100644 --- a/cmd/setup/configure.go +++ b/cmd/setup/configure.go @@ -2,7 +2,7 @@ package setup import ( "github.com/spf13/cobra" - "github.com/spf13/viper" + "openhue-cli/openhue" ) const ( @@ -13,30 +13,38 @@ The setup command must be run as a prerequisite for all resource related command It allows to store your Philips Hue Bridge IP and application key in the configuration file (~/.openhue/config.yaml).` ) +type Options struct { + bridge string + key string +} + // NewCmdConfigure creates the configure command func NewCmdConfigure() *cobra.Command { + o := Options{} + cmd := &cobra.Command{ Use: "configure", GroupID: "config", Short: docShortConfigure, Long: docLongConfigure, Run: func(cmd *cobra.Command, args []string) { - err := viper.SafeWriteConfig() - if err != nil { - err := viper.WriteConfig() - cobra.CheckErr(err) + + c := openhue.Config{ + Bridge: o.bridge, + Key: o.key, } + + err := c.Save() + cobra.CheckErr(err) }, } - cmd.Flags().StringP("bridge", "b", "", "The local IP of your Philips Hue Bridge (example '192.168.1.68')") + cmd.Flags().StringVarP(&o.bridge, "bridge", "b", "", "The local IP of your Philips Hue Bridge (example '192.168.1.68')") _ = cmd.MarkFlagRequired("bridge") - _ = viper.BindPFlag("bridge", cmd.Flags().Lookup("bridge")) - cmd.Flags().StringP("key", "k", "", "Your Hue Application Key") + cmd.Flags().StringVarP(&o.key, "key", "k", "", "Your Hue Application Key") _ = cmd.MarkFlagRequired("key") - _ = viper.BindPFlag("key", cmd.Flags().Lookup("key")) return cmd } diff --git a/cmd/setup/configure_test.go b/cmd/setup/configure_test.go index 2697a59..ad3ac32 100644 --- a/cmd/setup/configure_test.go +++ b/cmd/setup/configure_test.go @@ -1,12 +1,15 @@ package setup -import "testing" +import ( + "openhue-cli/openhue/test/assert" + "testing" +) func TestNewCmdConfigure(t *testing.T) { cmd := NewCmdConfigure() - if cmd.Use != "configure" { - t.Fatalf("The configure 'command' name has changed to '%s'", cmd.Use) - } + assert.ThatCmdUseIs(t, cmd, "configure") + assert.ThatCmdGroupIs(t, cmd, "config") + } diff --git a/cmd/setup/discover.go b/cmd/setup/discover.go index 4c4fe1c..8567f06 100644 --- a/cmd/setup/discover.go +++ b/cmd/setup/discover.go @@ -1,11 +1,11 @@ package setup import ( - "context" "fmt" "github.com/brutella/dnssd" - "github.com/fatih/color" "github.com/spf13/cobra" + "golang.org/x/net/context" + "openhue-cli/openhue" "os" "strings" ) @@ -14,7 +14,7 @@ const serviceName = "_hue._tcp" const domain = ".local" // NewCmdDiscover represents the discover command -func NewCmdDiscover() *cobra.Command { +func NewCmdDiscover(io openhue.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "discover", @@ -22,25 +22,26 @@ func NewCmdDiscover() *cobra.Command { Short: "Hue Bridge discovery", Long: `Discover your Hue Bridge on your local network using the mDNS Service Discovery`, Run: func(cmd *cobra.Command, args []string) { + DiscoverBridge(io) + }, + } - service := fmt.Sprintf("%s.%s.", strings.Trim(serviceName, "."), strings.Trim(domain, ".")) + return cmd +} - foundFn := func(e dnssd.BrowseEntry) { +func DiscoverBridge(io openhue.IOStreams) { + service := fmt.Sprintf("%s.%s.", strings.Trim(serviceName, "."), strings.Trim(domain, ".")) - for _, ip := range e.IPs { - if ip.To4() != nil { // we want to display IPv4 address only - c := color.New(color.FgGreen).Add(color.Bold) - c.Printf("\nBridge '%s' found with IP '%s'\n", strings.Replace(e.Name, "\\", "", 3), ip) - os.Exit(0) - } - } - } + foundFn := func(e dnssd.BrowseEntry) { - err := dnssd.LookupType(context.Background(), service, foundFn, nil) - cobra.CheckErr(err) - }, + for _, ip := range e.IPs { + if ip.To4() != nil { // we want to display IPv4 address only + fmt.Fprintf(io.Out, "\nFound '%s' with IP '%s'\n", strings.Replace(e.Name, "\\", "", 3), ip) + os.Exit(0) + } + } } - return cmd - + err := dnssd.LookupType(context.Background(), service, foundFn, nil) + cobra.CheckErr(err) } diff --git a/cmd/setup/discover_test.go b/cmd/setup/discover_test.go new file mode 100644 index 0000000..5b2d19d --- /dev/null +++ b/cmd/setup/discover_test.go @@ -0,0 +1,15 @@ +package setup + +import ( + "openhue-cli/openhue" + "openhue-cli/openhue/test/assert" + "testing" +) + +func TestNewCmdDiscover(t *testing.T) { + + cmd := NewCmdDiscover(openhue.NewTestIOStreamsDiscard()) + + assert.ThatCmdUseIs(t, cmd, "discover") + assert.ThatCmdGroupIs(t, cmd, "config") +} diff --git a/cmd/setup/doc.go b/cmd/setup/doc.go new file mode 100644 index 0000000..e86c749 --- /dev/null +++ b/cmd/setup/doc.go @@ -0,0 +1,2 @@ +// Package setup contains the commands that are used to configure and initialize the OpenHue CLI context +package setup diff --git a/cmd/version/version_test.go b/cmd/version/version_test.go index 4d8b99a..d829581 100644 --- a/cmd/version/version_test.go +++ b/cmd/version/version_test.go @@ -2,7 +2,7 @@ package version import ( "openhue-cli/openhue" - "openhue-cli/openhue/test" + "openhue-cli/openhue/test/assert" "strings" "testing" ) @@ -25,8 +25,8 @@ func TestNewCmdVersion(t *testing.T) { lines := strings.Split(out.String(), "\n") - test.AssertThatLineEqualsTo(t, lines, 1, Line1) - test.AssertThatLineEqualsTo(t, lines, 2, Line2) - test.AssertThatLineEqualsTo(t, lines, 3, Line3) + assert.ThatLineEqualsTo(t, lines, 1, Line1) + assert.ThatLineEqualsTo(t, lines, 2, Line2) + assert.ThatLineEqualsTo(t, lines, 3, Line3) } diff --git a/go.mod b/go.mod index 29f091b..3ba4ea2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.21 require ( github.com/brutella/dnssd v1.2.10 github.com/deepmap/oapi-codegen v1.16.2 - github.com/fatih/color v1.16.0 github.com/oapi-codegen/runtime v1.1.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 @@ -14,29 +13,27 @@ require ( require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/google/uuid v1.4.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/miekg/dns v1.1.54 // indirect + github.com/miekg/dns v1.1.57 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/mod v0.13.0 // indirect + golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/tools v0.16.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index aa81f88..889eacd 100644 --- a/go.sum +++ b/go.sum @@ -65,12 +65,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -154,13 +154,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM= @@ -179,12 +176,16 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= @@ -263,6 +264,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -318,6 +321,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -353,9 +357,7 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -421,6 +423,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/openhue/build.go b/openhue/buildinfo.go similarity index 83% rename from openhue/build.go rename to openhue/buildinfo.go index df0e6e5..ffc9a24 100644 --- a/openhue/build.go +++ b/openhue/buildinfo.go @@ -17,9 +17,5 @@ func NewBuildInfo(version string, commit string, date string) *BuildInfo { // NewTestBuildInfo returns a valid openhue.BuildInfo just for testing func NewTestBuildInfo() *BuildInfo { - return &BuildInfo{ - Version: "1.0.0", - Commit: "1234", - Date: "now", - } + return NewBuildInfo("1.0.0", "1234", "now") } diff --git a/openhue/buildinfo_test.go b/openhue/buildinfo_test.go new file mode 100644 index 0000000..47cb808 --- /dev/null +++ b/openhue/buildinfo_test.go @@ -0,0 +1,15 @@ +package openhue + +import ( + "openhue-cli/openhue/test/assert" + "testing" +) + +func TestNewTestBuildInfo(t *testing.T) { + + info := NewTestBuildInfo() + + assert.Equals(t, "1.0.0", info.Version) + assert.Equals(t, "1234", info.Commit) + assert.Equals(t, "now", info.Date) +} diff --git a/openhue/config.go b/openhue/config.go index 4356b9c..fa11e4d 100644 --- a/openhue/config.go +++ b/openhue/config.go @@ -2,8 +2,9 @@ package openhue import ( "crypto/tls" + "errors" + "fmt" sp "github.com/deepmap/oapi-codegen/pkg/securityprovider" - "github.com/fatih/color" "github.com/spf13/cobra" "github.com/spf13/viper" "net/http" @@ -13,14 +14,17 @@ import ( "slices" ) +// CommandsWithNoConfig contains the list of commands that don't require the configuration to exist +var CommandsWithNoConfig = []string{"configure", "help", "discover", "auth", "version", "completion"} + type Config struct { - // The IP of the Philips HUE bridge - bridge string + // The IP of the Philips HUE Bridge + Bridge string // The HUE Application Key - key string + Key string } -func (c *Config) LoadConfig() { +func (c *Config) Load() { // Find home directory. home, err := os.UserHomeDir() @@ -34,35 +38,54 @@ func (c *Config) LoadConfig() { viper.SetConfigName("config") viper.SetConfigType("yaml") - // List of commands that does not require configuration - ignoredCmds := []string{"setup", "help", "discover", "auth"} - // When trying to run CLI without configuration - if err := viper.ReadInConfig(); err != nil && !slices.Contains(ignoredCmds, os.Args[1]) { - color.New(color.FgRed).Add(color.Bold).Println("\nopenhue-cli not configured yet, please run the 'setup' command") + configDoesNotExist := viper.ReadInConfig() != nil + if configDoesNotExist && len(os.Args) > 1 && !slices.Contains(CommandsWithNoConfig, os.Args[1]) { + fmt.Println("\nopenhue-cli not configured yet, please run the 'configure' command") os.Exit(0) } - c.bridge = viper.GetString("bridge") - c.key = viper.GetString("key") + c.Bridge = viper.GetString("Bridge") + c.Key = viper.GetString("Key") +} + +func (c *Config) Save() error { + + if len(c.Bridge) == 0 { + return errors.New("'bridge' value not set in config") + } + + if len(c.Key) == 0 { + return errors.New("'key' value not set in config") + } + + viper.Set("Bridge", c.Bridge) + viper.Set("Key", c.Key) + + err := viper.SafeWriteConfig() + if err != nil { + return viper.WriteConfig() + } + + return nil } // NewOpenHueClient Creates a new NewClientWithResponses for a given server and hueApplicationKey. // This function will also skip SSL verification, as the Philips HUE Bridge exposes a self-signed certificate. func (c *Config) NewOpenHueClient() *gen.ClientWithResponses { - p, err := sp.NewSecurityProviderApiKey("header", "hue-application-key", c.key) + p, err := sp.NewSecurityProviderApiKey("header", "hue-application-Key", c.Key) cobra.CheckErr(err) // skip SSL Verification http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - client, err := gen.NewClientWithResponses("https://"+c.bridge, gen.WithRequestEditorFn(p.Intercept)) + client, err := gen.NewClientWithResponses("https://"+c.Bridge, gen.WithRequestEditorFn(p.Intercept)) cobra.CheckErr(err) return client } -// NewOpenHueClientNoAuth Creates a new NewClientWithResponses for a given server and no application key. +// NewOpenHueClientNoAuth Creates a new NewClientWithResponses for a given server and no application Key. // This function will also skip SSL verification, as the Philips HUE Bridge exposes a self-signed certificate. func NewOpenHueClientNoAuth(ip string) *gen.ClientWithResponses { diff --git a/openhue/context_test.go b/openhue/context_test.go new file mode 100644 index 0000000..69df58b --- /dev/null +++ b/openhue/context_test.go @@ -0,0 +1,18 @@ +package openhue + +import ( + "openhue-cli/openhue/gen" + "openhue-cli/openhue/test/assert" + "testing" +) + +func TestNewContext(t *testing.T) { + ctx := NewContext(NewTestIOStreamsDiscard(), NewTestBuildInfo(), &gen.ClientWithResponses{}) + assert.NotNil(t, ctx) +} + +func TestNewTestContextWithoutApi(t *testing.T) { + ctx, out := NewTestContextWithoutApi() + assert.NotNil(t, ctx) + assert.NotNil(t, out) +} diff --git a/openhue/io.go b/openhue/io.go index efbb622..32ffe0a 100644 --- a/openhue/io.go +++ b/openhue/io.go @@ -17,8 +17,8 @@ type IOStreams struct { ErrOut io.Writer } -// NewIOSteams returns a valid IOStreams with the default os.Stdin, os.Stdout and os.Stderr thinks -func NewIOSteams() IOStreams { +// NewIOStreams returns a valid IOStreams with the default os.Stdin, os.Stdout and os.Stderr thinks +func NewIOStreams() IOStreams { return IOStreams{ In: os.Stdin, Out: os.Stdout, diff --git a/openhue/io_test.go b/openhue/io_test.go new file mode 100644 index 0000000..5389e43 --- /dev/null +++ b/openhue/io_test.go @@ -0,0 +1,48 @@ +package openhue + +import ( + "io" + "os" + "testing" +) + +func TestNewIOSteams(t *testing.T) { + streams := NewIOStreams() + if streams.In != os.Stdin { + t.Fatalf("In should be os.Stdin") + } + if streams.Out != os.Stdout { + t.Fatalf("Out should be os.Stdout") + } + if streams.ErrOut != os.Stderr { + t.Fatalf("ErrOut should be os.Stderr") + } +} + +func TestNewTestIOStreams(t *testing.T) { + + streams, in, out, err := NewTestIOStreams() + + if streams.In != in { + t.Fatalf("invalid in") + } + if streams.Out != out { + t.Fatalf("invalid out") + } + if streams.ErrOut != err { + t.Fatalf("invalid err") + } +} + +func TestNewTestIOStreamsDiscard(t *testing.T) { + streams := NewTestIOStreamsDiscard() + if streams.In == nil { + t.Fatalf("In should be set") + } + if streams.Out != io.Discard { + t.Fatalf("Out should be io.Discard") + } + if streams.ErrOut != io.Discard { + t.Fatalf("ErrOut should be io.Discard") + } +} diff --git a/openhue/test/assert.go b/openhue/test/assert.go deleted file mode 100644 index dc99bfe..0000000 --- a/openhue/test/assert.go +++ /dev/null @@ -1,10 +0,0 @@ -package test - -import "testing" - -// AssertThatLineEqualsTo verifies that an `idx` line contained in the `lines` slice should be equal to the `expected` value -func AssertThatLineEqualsTo(t *testing.T, lines []string, idx int, expected string) { - if lines[idx] != expected { - t.Fatalf("expected \"%s\", obtained \"%s\"", expected, lines[idx]) - } -} diff --git a/openhue/test/assert/assert.go b/openhue/test/assert/assert.go new file mode 100644 index 0000000..00dc862 --- /dev/null +++ b/openhue/test/assert/assert.go @@ -0,0 +1,39 @@ +package assert + +import ( + "github.com/spf13/cobra" + "testing" +) + +// ThatLineEqualsTo verifies that an `idx` line contained in the `lines` slice should be equal to the `expected` value +func ThatLineEqualsTo(t *testing.T, lines []string, idx int, expected string) { + if lines[idx] != expected { + t.Fatalf("expected \"%s\", obtained \"%s\"", expected, lines[idx]) + } +} + +// ThatCmdUseIs verifies that the command Use is valid +func ThatCmdUseIs(t *testing.T, cmd *cobra.Command, expectedUse string) { + if cmd.Use != expectedUse { + t.Fatalf("wrong command use. Expected '%s', got '%s'", expectedUse, cmd.Use) + } +} + +// ThatCmdGroupIs verifies that the command GroupID is valid +func ThatCmdGroupIs(t *testing.T, cmd *cobra.Command, expectedGroup string) { + if cmd.GroupID != expectedGroup { + t.Fatalf("wrong command group. Expected '%s', got '%s'", expectedGroup, cmd.Use) + } +} + +func Equals(t *testing.T, expected string, actual string) { + if expected != actual { + t.Fatalf("expected '%s' but got '%s'", expected, actual) + } +} + +func NotNil(t *testing.T, value interface{}) { + if value == nil { + t.Fatalf("value is nil") + } +} diff --git a/util/color/color_test.go b/util/color/color_test.go index 3f0da8b..da8adc6 100644 --- a/util/color/color_test.go +++ b/util/color/color_test.go @@ -1 +1,21 @@ package color + +import "testing" + +func TestConvertHexToXY(t *testing.T) { + + var c RGBHex = "#123456" + xy := c.toXY() + if xy == nil { + t.Fatalf("unable to convert %s to XY", c) + } +} + +func TestFailToConvertHexToXY(t *testing.T) { + + var c RGBHex = "#foo" + xy := c.toXY() + if xy != nil { + t.Fatalf("it should not be possible to convert %s to XY", c) + } +} diff --git a/util/color/conversion.go b/util/color/conversion.go index aea54ef..6184b6d 100644 --- a/util/color/conversion.go +++ b/util/color/conversion.go @@ -36,7 +36,7 @@ func (c *RGB) ToXY() *XY { // Example: #FF0000 will return RGB{255, 0, 0} func NewRGBFomHex(hex string) (*RGB, error) { - if !(hex[0:1] == "#") && len(hex) != 7 { + if !(hex[0:1] == "#") || len(hex) != 7 { return nil, errors.New("wrong format for the input hexadecimal string") } diff --git a/util/utils_test.go b/util/utils_test.go index f66e824..9c2edf6 100644 --- a/util/utils_test.go +++ b/util/utils_test.go @@ -2,32 +2,81 @@ package util import ( "openhue-cli/openhue" - "openhue-cli/openhue/test" + "openhue-cli/openhue/test/assert" "strings" "testing" ) type Light struct { - id string - name string + Id string `json:"id"` + Name string `json:"name"` } func PrintLightLineProcessor(light Light) string { - return light.id + "\t" + light.name + return light.Id + "\t" + light.Name } func TestPrintTable(t *testing.T) { - lights := []Light{{id: "1234", name: "Light 1"}, {id: "4321", name: "Light 2"}} + lights := []Light{{Id: "1234", Name: "Light 1"}, {Id: "4321", Name: "Light 2"}} streams, _, out, _ := openhue.NewTestIOStreams() - PrintTable(streams, lights, PrintLightLineProcessor, "id", "name") + PrintTable(streams, lights, PrintLightLineProcessor, "Id", "Name") lines := strings.Split(out.String(), "\n") - test.AssertThatLineEqualsTo(t, lines, 0, "id name ") - test.AssertThatLineEqualsTo(t, lines, 1, "---- ---- ") - test.AssertThatLineEqualsTo(t, lines, 2, "1234 Light 1") - test.AssertThatLineEqualsTo(t, lines, 3, "4321 Light 2") + assert.ThatLineEqualsTo(t, lines, 0, "Id Name ") + assert.ThatLineEqualsTo(t, lines, 1, "---- ---- ") + assert.ThatLineEqualsTo(t, lines, 2, "1234 Light 1") + assert.ThatLineEqualsTo(t, lines, 3, "4321 Light 2") +} + +func TestPrintJsonArrayWithOneElement(t *testing.T) { + + expected := `{ + "id": "1234", + "name": "Light 1" +} +` + + lights := []Light{ + {Id: "1234", Name: "Light 1"}, + } + + streams, _, out, _ := openhue.NewTestIOStreams() + + PrintJsonArray(streams, lights) + + if out.String() != expected { + t.Fatalf("Output is: \n%s\nExpected:\n%s", out.String(), expected) + } +} + +func TestPrintJsonArrayWithTwoElements(t *testing.T) { + + expected := `[ + { + "id": "1234", + "name": "Light 1" + }, + { + "id": "4321", + "name": "Light 2" + } +] +` + + lights := []Light{ + {Id: "1234", Name: "Light 1"}, + {Id: "4321", Name: "Light 2"}, + } + + streams, _, out, _ := openhue.NewTestIOStreams() + + PrintJsonArray(streams, lights) + + if out.String() != expected { + t.Fatalf("Output is: \n%s\nExpected:\n%s", out.String(), expected) + } }