diff --git a/cli/printer.go b/cli/printer.go index e1d33a2..14352e5 100644 --- a/cli/printer.go +++ b/cli/printer.go @@ -1,6 +1,7 @@ package cli import ( + "bytes" "encoding/json" ) @@ -24,6 +25,16 @@ func (jp JsonPrinter) Print(obj interface{}) error { return nil } +func (jp JsonPrinter) PrintError(b []byte) error { + var out bytes.Buffer + err := json.Indent(&out, b, "", " ") + if err != nil { + return err + } + jp.Log.Error(out.String()) + return nil +} + type TestPrinter struct { CallData map[string]interface{} } diff --git a/cli/printer_test.go b/cli/printer_test.go index 068fc8b..edc31f6 100644 --- a/cli/printer_test.go +++ b/cli/printer_test.go @@ -1,33 +1,53 @@ package cli_test import ( + "bytes" . "code.cloudfoundry.org/uaa-cli/cli" - + "encoding/json" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gbytes" - "io/ioutil" ) var _ = Describe("JsonPrinter", func() { - It("prints things to the Robots log", func() { - logBuf := NewBuffer() - printer := NewJsonPrinter(NewLogger(ioutil.Discard, logBuf, ioutil.Discard, ioutil.Discard)) - - printer.Print(struct { - Foo string - Bar string - }{"foo", "bar"}) + var infoLogBuf, robotLogBuf, warnLogBuf, errorLogBuf *Buffer + var printer JsonPrinter - Expect(logBuf.Contents()).To(MatchJSON(`{"Foo":"foo","Bar":"bar"}`)) + BeforeEach(func() { + infoLogBuf, robotLogBuf, warnLogBuf, errorLogBuf = NewBuffer(), NewBuffer(), NewBuffer(), NewBuffer() + printer = NewJsonPrinter(NewLogger(infoLogBuf, robotLogBuf, warnLogBuf, errorLogBuf)) + }) + Describe("Print", func() { + It("prints things to the Robots log", func() { + printer.Print(struct { + Foo string + Bar string + }{"foo", "bar"}) + + Expect(robotLogBuf.Contents()).To(MatchJSON(`{"Foo":"foo","Bar":"bar"}`)) + }) + + It("returns error when cannot marhsal into json", func() { + unJsonifiableObj := make(chan bool) + err := printer.Print(unJsonifiableObj) + + Expect(err).To(HaveOccurred()) + }) }) - It("returns error when cannot marhsal into json", func() { - printer := NewJsonPrinter(NewLogger(ioutil.Discard, ioutil.Discard, ioutil.Discard, ioutil.Discard)) + Describe("PrintError", func () { + It("prints a json buffer to Error log", func() { + jsonData := struct { + Foo string + Bar string + }{"foo", "bar"} + jsonRaw, _ := json.Marshal(jsonData) + var out bytes.Buffer + _ = json.Indent(&out, jsonRaw, "", " ") - unJsonifiableObj := make(chan bool) - err := printer.Print(unJsonifiableObj) + printer.PrintError(jsonRaw) - Expect(err).To(HaveOccurred()) + Expect(string(errorLogBuf.Contents())).To(ContainSubstring(out.String())) + }) }) }) diff --git a/cmd/create_client_test.go b/cmd/create_client_test.go index ec4378b..44aabf1 100644 --- a/cmd/create_client_test.go +++ b/cmd/create_client_test.go @@ -231,7 +231,7 @@ var _ = Describe("CreateClient", func() { "--clone", "shiny", "--client_secret", "secretsecret") - Expect(session.Err).To(Say("An unknown error occurred while calling")) + Expect(session.Err).To(Say("An error occurred while calling")) Expect(session).Should(Exit(1)) }) @@ -313,7 +313,7 @@ var _ = Describe("CreateClient", func() { "--authorities", "notifications.write,notifications.read", ) - Eventually(session.Err).Should(Say("An unknown error occurred while calling")) + Eventually(session.Err).Should(Say("An error occurred while calling")) Eventually(session).Should(Exit(1)) }) }) diff --git a/cmd/delete_client_test.go b/cmd/delete_client_test.go index 22c471c..15a07fc 100644 --- a/cmd/delete_client_test.go +++ b/cmd/delete_client_test.go @@ -75,7 +75,7 @@ var _ = Describe("DeleteClient", func() { session := runCommand("delete-client", "clientid") - Expect(session.Err).To(Say("An unknown error occurred while calling " + server.URL() + "/oauth/clients/clientid")) + Expect(session.Err).To(Say("An error occurred while calling " + server.URL() + "/oauth/clients/clientid")) Eventually(session).Should(Exit(1)) }) }) diff --git a/cmd/errors.go b/cmd/errors.go index a9460bc..9429271 100644 --- a/cmd/errors.go +++ b/cmd/errors.go @@ -5,6 +5,7 @@ import ( "code.cloudfoundry.org/uaa-cli/config" "errors" "fmt" + "github.com/cloudfoundry-community/go-uaa" "github.com/spf13/cobra" "os" ) @@ -47,7 +48,13 @@ func NotifyValidationErrors(err error, cmd *cobra.Command, log cli.Logger) { func NotifyErrorsWithRetry(err error, log cli.Logger) { if err != nil { - log.Error(err.Error()) + switch t := err.(type) { + case uaa.RequestError: + log.Error(err.Error()) + cli.NewJsonPrinter(log).PrintError(t.ErrorResponse) + default: + log.Error(err.Error()) + } VerboseRetryMsg(GetSavedConfig()) os.Exit(1) } diff --git a/cmd/get_client_test.go b/cmd/get_client_test.go index e06f042..4d1e70b 100644 --- a/cmd/get_client_test.go +++ b/cmd/get_client_test.go @@ -76,7 +76,7 @@ var _ = Describe("GetClient", func() { session := runCommand("get-client", "clientid") - Expect(session.Err).To(Say("An unknown error occurred while calling " + server.URL() + "/oauth/clients/clientid")) + Expect(session.Err).To(Say("An error occurred while calling " + server.URL() + "/oauth/clients/clientid")) Eventually(session).Should(Exit(1)) }) }) diff --git a/cmd/info_test.go b/cmd/info_test.go index d0cdd12..49b942d 100644 --- a/cmd/info_test.go +++ b/cmd/info_test.go @@ -38,7 +38,7 @@ var _ = Describe("Info", func() { session := runCommand("info") Eventually(session).Should(Exit(1)) - Expect(session.Err).To(Say("An unknown error occurred while calling " + server.URL() + "/info")) + Expect(session.Err).To(Say("An error occurred while calling " + server.URL() + "/info")) }) Context("with --verbose", func() { diff --git a/cmd/list_clients_test.go b/cmd/list_clients_test.go index c5a1cb5..bc7a262 100644 --- a/cmd/list_clients_test.go +++ b/cmd/list_clients_test.go @@ -92,7 +92,7 @@ var _ = Describe("ListClients", func() { It("handles request errors", func() { session := runCommand("list-clients") - Expect(session.Err).To(Say("An unknown error occurred while calling " + server.URL() + "/oauth/clients")) + Expect(session.Err).To(Say("An error occurred while calling " + server.URL() + "/oauth/clients")) Eventually(session).Should(Exit(1)) }) }) diff --git a/cmd/list_group_mappings_test.go b/cmd/list_group_mappings_test.go index f11a828..4363ab3 100644 --- a/cmd/list_group_mappings_test.go +++ b/cmd/list_group_mappings_test.go @@ -18,29 +18,48 @@ var _ = Describe("ListGroupMappings", func() { err := config.WriteConfig(cfg) Expect(err).NotTo(HaveOccurred()) }) + Describe("By default", func() { - It("requests group mappings from the backend with default parameters", func() { - server.RouteToHandler("GET", "/Groups/External", CombineHandlers( - VerifyRequest("GET", "/Groups/External", "startIndex=1&count=100"), - RespondWith(http.StatusOK, ExternalGroupsApiResponse, contentTypeJson), - )) + It("requests group mappings from the backend with default parameters", func() { + server.RouteToHandler("GET", "/Groups/External", CombineHandlers( + VerifyRequest("GET", "/Groups/External", "startIndex=1&count=100"), + RespondWith(http.StatusOK, ExternalGroupsApiResponse, contentTypeJson), + )) - session := runCommand("list-group-mappings") + session := runCommand("list-group-mappings") - Eventually(session).Should(Exit(0)) + Eventually(session).Should(Exit(0)) - // We can't verify that the right JSON was output - // There seems to be a gap in the tooling. - // We can test a regex against a buffer - // We can test JSON against a string - // But we can't test JSON against a buffer - Eventually(session.Out).Should(gbytes.Say("organizations.acme")) + // We can't verify that the right JSON was output + // There seems to be a gap in the tooling. + // We can test a regex against a buffer + // We can test JSON against a string + // But we can't test JSON against a buffer + Eventually(session.Out).Should(gbytes.Say("organizations.acme")) + }) + + It("prints a useful description in the help menu", func() { + session := runCommand("list-group-mappings", "-h") + + Eventually(session).Should(Exit(0)) + Eventually(session.Out).Should(gbytes.Say("List all the mappings between uaa scopes and external groups")) + }) }) + Describe("When the context has insufficient scope", func() { + It("returns an error", func() { + server.RouteToHandler("GET", "/Groups/External", CombineHandlers( + VerifyRequest("GET", "/Groups/External", "startIndex=1&count=100"), + RespondWith(http.StatusForbidden, ExternalGroupsApiResponseInsufficientScope, contentTypeJson), + )) - It("prints a useful description in the help menu", func() { - session := runCommand("list-group-mappings", "-h") + session := runCommand("list-group-mappings") - Eventually(session).Should(Exit(0)) - Eventually(session.Out).Should(gbytes.Say("List all the mappings between uaa scopes and external groups")) + Eventually(session).Should(Exit(1)) + Eventually(session.Err).Should(gbytes.Say(`An error occurred while calling http://127.0.0.1:(\d+)/Groups/External\?count=100&startIndex=1`)) + Eventually(session.Err).Should(gbytes.Say(`"error": "insufficient_scope"`)) + Eventually(session.Err).Should(gbytes.Say(`"error_description": "Insufficient scope for this resource"`)) + Eventually(session.Err).Should(gbytes.Say(`"scope": "uaa.admin scim.read zones.uaa.admin"`)) + Eventually(session.Out).Should(gbytes.Say("Retry with --verbose for more information.")) + }) }) }) diff --git a/cmd/userinfo_test.go b/cmd/userinfo_test.go index 65ff91f..867399a 100644 --- a/cmd/userinfo_test.go +++ b/cmd/userinfo_test.go @@ -54,7 +54,7 @@ var _ = Describe("Userinfo", func() { session := runCommand("userinfo") Eventually(session).Should(Exit(1)) - Expect(session.Err).To(Say("An unknown error occurred while calling " + server.URL() + "/userinfo")) + Expect(session.Err).To(Say("An error occurred while calling " + server.URL() + "/userinfo")) }) }) diff --git a/fixtures/group-mappings.go b/fixtures/group_mappings_fixtures.go similarity index 66% rename from fixtures/group-mappings.go rename to fixtures/group_mappings_fixtures.go index 8b9e43e..fafb335 100644 --- a/fixtures/group-mappings.go +++ b/fixtures/group_mappings_fixtures.go @@ -16,3 +16,9 @@ const ExternalGroupsApiResponse = `{ "urn:scim:schemas:core:1.0" ] }` + +const ExternalGroupsApiResponseInsufficientScope = `{ + "error": "insufficient_scope", + "error_description": "Insufficient scope for this resource", + "scope": "uaa.admin scim.read zones.uaa.admin" +}` diff --git a/go.mod b/go.mod index d7172dc..9c24900 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module code.cloudfoundry.org/uaa-cli go 1.12 require ( - github.com/cloudfoundry-community/go-uaa v0.2.4 + github.com/cloudfoundry-community/go-uaa v0.2.5 github.com/fatih/color v1.7.0 github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/mattn/go-runewidth v0.0.2 // indirect diff --git a/go.sum b/go.sum index e976cc3..f299607 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/cloudfoundry-community/go-uaa v0.2.3 h1:fsUpl/IK/pARdZj2EmPTMyiYEQ4t8 github.com/cloudfoundry-community/go-uaa v0.2.3/go.mod h1:8xbs4AE7Onyn0879a757RewaLbRrUw41CPNxn8EdmRM= github.com/cloudfoundry-community/go-uaa v0.2.4 h1:Lc0jP1DoDS7rxXLeMSuVQF9cKt93TzhFvp6W+Hfj1w0= github.com/cloudfoundry-community/go-uaa v0.2.4/go.mod h1:8xbs4AE7Onyn0879a757RewaLbRrUw41CPNxn8EdmRM= +github.com/cloudfoundry-community/go-uaa v0.2.5 h1:wn/f4n4hDxPF4NtpjG6dT5yFyAxYp/fSBvLSPFyG8E0= +github.com/cloudfoundry-community/go-uaa v0.2.5/go.mod h1:8xbs4AE7Onyn0879a757RewaLbRrUw41CPNxn8EdmRM= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=