From 7437ed0d12ab51713f1ac54c49b087b2faa527c6 Mon Sep 17 00:00:00 2001 From: Eric Hu Date: Thu, 12 Jul 2018 04:45:55 +0700 Subject: [PATCH 1/9] WIP: initial testing for orb publish subcommand --- cmd/orb_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 13bda98d6..c7f72dd4e 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -191,5 +191,65 @@ var _ = Describe("Orb", func() { }) }) + + Describe("when publishing an orb version", func() { + It("works", func() { + By("setting up a mock server") + err := orb.write(`some orb`) + Expect(err).ToNot(HaveOccurred()) + + gqlResponse := `{ + "orbConfig": { + "orb": { + id: + orb: + version: + source: + notes: + createdAt: + } + "errors": [] + } + }` + // :PublishOrbPayload + // {:description "Payload sent after publishing an orb" + // :fields {:errors {:type (non-null (list (non-null :ConfigError)))} + // :orb {:type :OrbVersion}}} + + // :OrbVersion + // {:description "A specific version of an orb and its source" + // :fields {:id {:type (non-null :UUID)} + // :orb {:type (non-null :Orb) + // :resolve :version-orb} + // :version {:type (non-null String)} + // :source {:type (non-null String)} + // :notes {:type String} + // :createdAt {:type (non-null :Date)}}}} + + expectedRequestJson := ` { + "query": "\n\t\tmutation PublishOrbVersion ($config: String!) {\n\t\t\torbConfig(orbYaml: $config) {\n\t\t\t\tvalid,\n\t\t\t\terrors { message },\n\t\t\t\tsourceYaml,\n\t\t\t\toutputYaml\n\t\t\t}\n\t\t}", + "variables": { + "config": "some orb" + } + }` + // :publishOrb {:type (non-null :PublishOrbPayload) + // :args {:orbId {:type (non-null :UUID)} + // :version {:type (non-null String)} + // :orbYaml {:type (non-null String)}} + // :resolve :publish-orb}} + + appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse) + + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Out).Should(gbytes.Say("hello world")) + Eventually(session).Should(gexec.Exit(0)) + }) + + It("prints errors if invalid", func() { + }) + }) }) }) From ff6ff70a28ebf4433d16dbe4429aca86999054f9 Mon Sep 17 00:00:00 2001 From: Eric Hu Date: Thu, 12 Jul 2018 04:45:55 +0700 Subject: [PATCH 2/9] WIP: initial testing for orb publish subcommand --- cmd/orb_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 13bda98d6..c7f72dd4e 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -191,5 +191,65 @@ var _ = Describe("Orb", func() { }) }) + + Describe("when publishing an orb version", func() { + It("works", func() { + By("setting up a mock server") + err := orb.write(`some orb`) + Expect(err).ToNot(HaveOccurred()) + + gqlResponse := `{ + "orbConfig": { + "orb": { + id: + orb: + version: + source: + notes: + createdAt: + } + "errors": [] + } + }` + // :PublishOrbPayload + // {:description "Payload sent after publishing an orb" + // :fields {:errors {:type (non-null (list (non-null :ConfigError)))} + // :orb {:type :OrbVersion}}} + + // :OrbVersion + // {:description "A specific version of an orb and its source" + // :fields {:id {:type (non-null :UUID)} + // :orb {:type (non-null :Orb) + // :resolve :version-orb} + // :version {:type (non-null String)} + // :source {:type (non-null String)} + // :notes {:type String} + // :createdAt {:type (non-null :Date)}}}} + + expectedRequestJson := ` { + "query": "\n\t\tmutation PublishOrbVersion ($config: String!) {\n\t\t\torbConfig(orbYaml: $config) {\n\t\t\t\tvalid,\n\t\t\t\terrors { message },\n\t\t\t\tsourceYaml,\n\t\t\t\toutputYaml\n\t\t\t}\n\t\t}", + "variables": { + "config": "some orb" + } + }` + // :publishOrb {:type (non-null :PublishOrbPayload) + // :args {:orbId {:type (non-null :UUID)} + // :version {:type (non-null String)} + // :orbYaml {:type (non-null String)}} + // :resolve :publish-orb}} + + appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse) + + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Out).Should(gbytes.Say("hello world")) + Eventually(session).Should(gexec.Exit(0)) + }) + + It("prints errors if invalid", func() { + }) + }) }) }) From eb5f0fc64b7c677ae05a040f8d9c38fadb5476da Mon Sep 17 00:00:00 2001 From: Eric Hu Date: Tue, 17 Jul 2018 04:47:45 +0800 Subject: [PATCH 3/9] WIP: adding publish command --- api/api.go | 42 ++++++++++++++++++++++++++ cmd/orb.go | 32 ++++++++++++++++++++ cmd/orb_test.go | 80 +++++++++++++++++++++++-------------------------- 3 files changed, 112 insertions(+), 42 deletions(-) diff --git a/api/api.go b/api/api.go index a21cb40e8..a0b7d0c31 100644 --- a/api/api.go +++ b/api/api.go @@ -101,3 +101,45 @@ func OrbQuery(ctx context.Context, logger *logger.Logger, configPath string) (*C } }`) } + +func publishOrbQuery(ctx context.Context, logger *logger.Logger, configPath string, response interface{}, query string) error { + config, err := loadYaml(configPath) + if err != nil { + return err + } + + request := client.NewAuthorizedRequest(viper.GetString("token"), query) + request.Var("config", config) + + request.Var("orbId", "bb604b45-b6b0-4b81-ad80-796f15eddf87") + graphQLclient := client.NewClient(viper.GetString("endpoint"), logger) + + err = graphQLclient.Run(ctx, request, response) + + if err != nil { + return errors.Wrap(err, "Unable to validate config") + } + + return nil +} + +// OrbPublish publishes a new version of an orb +func OrbPublish(ctx context.Context, logger *logger.Logger, + configPath string) (*ConfigResponse, error) { + var response struct { + OrbConfig struct { + ConfigResponse + } + } + + return &response.OrbConfig.ConfigResponse, publishOrbQuery(ctx, logger, configPath, &response, ` + mutation($config: String!, $orbId: $String!) { + publishOrb( + orbId: $orbId, + orbYaml: $config, + version: "" + ) + } + `) + +} diff --git a/cmd/orb.go b/cmd/orb.go index 89ff9e0ca..b2e23caaf 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "fmt" "github.com/CircleCI-Public/circleci-cli/api" "github.com/CircleCI-Public/circleci-cli/client" @@ -34,6 +35,13 @@ func newOrbCommand() *cobra.Command { RunE: expandOrb, } + orbPublishCommand := &cobra.Command{ + Use: "publish", + Short: "publish a version of an orb", + RunE: publishOrb, + } + orbPublishCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file") + orbCommand := &cobra.Command{ Use: "orb", Short: "Operate on orbs", @@ -47,6 +55,8 @@ func newOrbCommand() *cobra.Command { orbExpandCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file") orbCommand.AddCommand(orbExpandCommand) + orbCommand.AddCommand(orbPublishCommand) + return orbCommand } @@ -150,3 +160,25 @@ func expandOrb(cmd *cobra.Command, args []string) error { Logger.Info(response.OutputYaml) return nil } + +func publishOrb(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + response, err := api.OrbPublish(ctx, Logger, orbPath) + + if err != nil { + return err + } + + if !response.Valid { + // fmt.Print("response invalid") + // print("response invalid") + // fmt.Println(">>>>>>>>>>>>>>>>>>>> response invalid >>>>>>>>>>>>>>>>>>>>", response.ToError()) + fmt.Println(">>>>>>>>>>>>>>>>>>>> response invalid >>>>>>>>>>>>>>>>>>>>", response.ToError().Error()) + return response.ToError() + } + + // Logger.Info(response.OutputYaml) + Logger.Info("Orb published") + return nil +} diff --git a/cmd/orb_test.go b/cmd/orb_test.go index c7f72dd4e..3aa4d9107 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -17,6 +17,8 @@ var _ = Describe("Orb", func() { var ( testServer *ghttp.Server orb tmpFile + token string + command *exec.Cmd ) BeforeEach(func() { @@ -33,11 +35,6 @@ var _ = Describe("Orb", func() { }) Describe("when validating orb", func() { - var ( - token string - command *exec.Cmd - ) - BeforeEach(func() { token = "testtoken" command = exec.Command(pathCLI, @@ -193,50 +190,49 @@ var _ = Describe("Orb", func() { }) Describe("when publishing an orb version", func() { - It("works", func() { + JustBeforeEach(func() { + token = "testtoken" + command = exec.Command(pathCLI, + "orb", "publish", + "-t", token, + "-e", testServer.URL(), + "-p", orb.Path, + "--verbose", "", + ) + }) + + FIt("works", func() { + + // TODO: factor out common test setup into a top-level JustBeforeEach. Rely + // on BeforeEach in each block to specify server mocking. By("setting up a mock server") + // write to test file err := orb.write(`some orb`) + // assert write to test file successful Expect(err).ToNot(HaveOccurred()) gqlResponse := `{ - "orbConfig": { - "orb": { - id: - orb: - version: - source: - notes: - createdAt: - } - "errors": [] - } - }` - // :PublishOrbPayload - // {:description "Payload sent after publishing an orb" - // :fields {:errors {:type (non-null (list (non-null :ConfigError)))} - // :orb {:type :OrbVersion}}} - - // :OrbVersion - // {:description "A specific version of an orb and its source" - // :fields {:id {:type (non-null :UUID)} - // :orb {:type (non-null :Orb) - // :resolve :version-orb} - // :version {:type (non-null String)} - // :source {:type (non-null String)} - // :notes {:type String} - // :createdAt {:type (non-null :Date)}}}} + "publishOrb": { + "errors": [], + "valid": true, + "orb": { + "createdAt": "2018-07-16T18:03:18.961Z", + "version": "0.0.1" + } + } + }` - expectedRequestJson := ` { - "query": "\n\t\tmutation PublishOrbVersion ($config: String!) {\n\t\t\torbConfig(orbYaml: $config) {\n\t\t\t\tvalid,\n\t\t\t\terrors { message },\n\t\t\t\tsourceYaml,\n\t\t\t\toutputYaml\n\t\t\t}\n\t\t}", + // expectedRequestJson := `{ + // "query": '{}", version: "0.0.1") { orb { version createdAt } errors { message } } } ' + // }` + + expectedRequestJson := `{ + "query": "\n\t\tmutation($config: String!, $orbId: $String!) {\n\t\t\tpublishOrb(\n\t\t\t\torbId: $orbId,\n\t\t\t\torbYaml: $config,\n\t\t\t\tversion: \"\"\n\t\t\t)\n\t\t}\n\t", "variables": { - "config": "some orb" + "config": "some orb", + "orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87" } - }` - // :publishOrb {:type (non-null :PublishOrbPayload) - // :args {:orbId {:type (non-null :UUID)} - // :version {:type (non-null String)} - // :orbYaml {:type (non-null String)}} - // :resolve :publish-orb}} + }` appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse) @@ -244,7 +240,7 @@ var _ = Describe("Orb", func() { session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) Expect(err).ShouldNot(HaveOccurred()) - Eventually(session.Out).Should(gbytes.Say("hello world")) + Eventually(session.Out).Should(gbytes.Say("response invalid")) Eventually(session).Should(gexec.Exit(0)) }) From c6d4706548d89c7031157743685c94a45ff97337 Mon Sep 17 00:00:00 2001 From: Eric Hu Date: Wed, 18 Jul 2018 05:18:31 +0800 Subject: [PATCH 4/9] WIP: orb publishing --- api/api.go | 44 ++++++++++++++++++++++++++++++++++++-------- cmd/orb.go | 14 +++++++------- cmd/orb_test.go | 15 ++++++++------- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/api/api.go b/api/api.go index a0b7d0c31..1af2533ad 100644 --- a/api/api.go +++ b/api/api.go @@ -24,6 +24,19 @@ type ConfigResponse struct { } } +// The PublishOrbResponse type matches the data shape of the GQL response for +// publishing an orb. +type PublishOrbResponse struct { + Orb struct { + CreatedAt string + Version string + } + + Errors []struct { + Message string + } +} + // ToError returns an error created from any error messages, or nil. func (response ConfigResponse) ToError() error { messages := []string{} @@ -35,6 +48,18 @@ func (response ConfigResponse) ToError() error { return errors.New(strings.Join(messages, ": ")) } +// ToError (PublishOrbResponse) returns all errors that the GraphQL API returns +// as part of a publish orb request. +func (response PublishOrbResponse) ToError() error { + messages := []string{} + + for i := range response.Errors { + messages = append(messages, response.Errors[i].Message) + } + + return errors.New(strings.Join(messages, ": ")) +} + func loadYaml(path string) (string, error) { config, err := ioutil.ReadFile(path) @@ -102,7 +127,8 @@ func OrbQuery(ctx context.Context, logger *logger.Logger, configPath string) (*C }`) } -func publishOrbQuery(ctx context.Context, logger *logger.Logger, configPath string, response interface{}, query string) error { +func publishOrbQuery(ctx context.Context, logger *logger.Logger, configPath string, + orbVersion string, orbID string, response interface{}, query string) error { config, err := loadYaml(configPath) if err != nil { return err @@ -110,8 +136,9 @@ func publishOrbQuery(ctx context.Context, logger *logger.Logger, configPath stri request := client.NewAuthorizedRequest(viper.GetString("token"), query) request.Var("config", config) + request.Var("orbId", orbID) + request.Var("version", orbVersion) - request.Var("orbId", "bb604b45-b6b0-4b81-ad80-796f15eddf87") graphQLclient := client.NewClient(viper.GetString("endpoint"), logger) err = graphQLclient.Run(ctx, request, response) @@ -125,19 +152,20 @@ func publishOrbQuery(ctx context.Context, logger *logger.Logger, configPath stri // OrbPublish publishes a new version of an orb func OrbPublish(ctx context.Context, logger *logger.Logger, - configPath string) (*ConfigResponse, error) { + configPath string, orbVersion string, orbID string) (*PublishOrbResponse, error) { var response struct { - OrbConfig struct { - ConfigResponse + PublishOrb struct { + PublishOrbResponse } } - return &response.OrbConfig.ConfigResponse, publishOrbQuery(ctx, logger, configPath, &response, ` - mutation($config: String!, $orbId: $String!) { + return &response.PublishOrb.PublishOrbResponse, publishOrbQuery(ctx, logger, + configPath, orbVersion, orbID, &response, ` + mutation($config: String!, $orbId: $String!, $version: $String!) { publishOrb( orbId: $orbId, orbYaml: $config, - version: "" + version: $version ) } `) diff --git a/cmd/orb.go b/cmd/orb.go index b2e23caaf..7a61ebf02 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -14,6 +14,8 @@ import ( ) var orbPath string +var orbVersion string +var orbId string func newOrbCommand() *cobra.Command { @@ -41,6 +43,8 @@ func newOrbCommand() *cobra.Command { RunE: publishOrb, } orbPublishCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file") + orbPublishCommand.PersistentFlags().StringVarP(&orbVersion, "orb-version", "o", "", "version of orb to publish") + orbPublishCommand.PersistentFlags().StringVarP(&orbId, "orb-id", "i", "", "version of orb to publish") orbCommand := &cobra.Command{ Use: "orb", @@ -164,21 +168,17 @@ func expandOrb(cmd *cobra.Command, args []string) error { func publishOrb(cmd *cobra.Command, args []string) error { ctx := context.Background() - response, err := api.OrbPublish(ctx, Logger, orbPath) + response, err := api.OrbPublish(ctx, Logger, orbPath, orbVersion, orbId) if err != nil { + fmt.Println("response error") return err } - if !response.Valid { - // fmt.Print("response invalid") - // print("response invalid") - // fmt.Println(">>>>>>>>>>>>>>>>>>>> response invalid >>>>>>>>>>>>>>>>>>>>", response.ToError()) - fmt.Println(">>>>>>>>>>>>>>>>>>>> response invalid >>>>>>>>>>>>>>>>>>>>", response.ToError().Error()) + if len(response.Errors) > 0 { return response.ToError() } - // Logger.Info(response.OutputYaml) Logger.Info("Orb published") return nil } diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 3aa4d9107..6be19a80a 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -190,18 +190,19 @@ var _ = Describe("Orb", func() { }) Describe("when publishing an orb version", func() { - JustBeforeEach(func() { + BeforeEach(func() { token = "testtoken" command = exec.Command(pathCLI, "orb", "publish", "-t", token, "-e", testServer.URL(), "-p", orb.Path, - "--verbose", "", + "--orb-version", "0.0.1", + "--orb-id", "bb604b45-b6b0-4b81-ad80-796f15eddf87", ) }) - FIt("works", func() { + It("works", func() { // TODO: factor out common test setup into a top-level JustBeforeEach. Rely // on BeforeEach in each block to specify server mocking. @@ -214,7 +215,6 @@ var _ = Describe("Orb", func() { gqlResponse := `{ "publishOrb": { "errors": [], - "valid": true, "orb": { "createdAt": "2018-07-16T18:03:18.961Z", "version": "0.0.1" @@ -227,10 +227,11 @@ var _ = Describe("Orb", func() { // }` expectedRequestJson := `{ - "query": "\n\t\tmutation($config: String!, $orbId: $String!) {\n\t\t\tpublishOrb(\n\t\t\t\torbId: $orbId,\n\t\t\t\torbYaml: $config,\n\t\t\t\tversion: \"\"\n\t\t\t)\n\t\t}\n\t", + "query": "\n\t\tmutation($config: String!, $orbId: UUID!, $version: String!) {\n\t\t\tpublishOrb(\n\t\t\t\torbId: $orbId,\n\t\t\t\torbYaml: $config,\n\t\t\t\tversion: $version) {orb {\n\t\t\tversion\n\t\t\tcreatedAt\n\t\t}\n\t\t}\n\t\terrors { message }\n\t", "variables": { "config": "some orb", - "orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87" + "orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87", + "version": "0.0.1" } }` @@ -240,7 +241,7 @@ var _ = Describe("Orb", func() { session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) Expect(err).ShouldNot(HaveOccurred()) - Eventually(session.Out).Should(gbytes.Say("response invalid")) + Eventually(session.Out).Should(gbytes.Say("Orb published")) Eventually(session).Should(gexec.Exit(0)) }) From 399bc318d70040b4ec010ba31753b64d79a1bc93 Mon Sep 17 00:00:00 2001 From: Eric Hu Date: Thu, 19 Jul 2018 01:54:01 +0800 Subject: [PATCH 5/9] WIP for pairing discussion --- api/api.go | 10 ++++++++-- cmd/orb.go | 6 +++--- cmd/orb_test.go | 2 ++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/api/api.go b/api/api.go index 1af2533ad..028fa07b9 100644 --- a/api/api.go +++ b/api/api.go @@ -161,12 +161,18 @@ func OrbPublish(ctx context.Context, logger *logger.Logger, return &response.PublishOrb.PublishOrbResponse, publishOrbQuery(ctx, logger, configPath, orbVersion, orbID, &response, ` - mutation($config: String!, $orbId: $String!, $version: $String!) { + mutation($config: String!, $orbId: UUID!, $version: String!) { publishOrb( orbId: $orbId, orbYaml: $config, version: $version - ) + ) { + orb { + version + createdAt + } + errors { message } + } } `) diff --git a/cmd/orb.go b/cmd/orb.go index 7a61ebf02..20c5e3824 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -15,7 +15,7 @@ import ( var orbPath string var orbVersion string -var orbId string +var orbID string func newOrbCommand() *cobra.Command { @@ -44,7 +44,7 @@ func newOrbCommand() *cobra.Command { } orbPublishCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file") orbPublishCommand.PersistentFlags().StringVarP(&orbVersion, "orb-version", "o", "", "version of orb to publish") - orbPublishCommand.PersistentFlags().StringVarP(&orbId, "orb-id", "i", "", "version of orb to publish") + orbPublishCommand.PersistentFlags().StringVarP(&orbID, "orb-id", "i", "", "version of orb to publish") orbCommand := &cobra.Command{ Use: "orb", @@ -168,7 +168,7 @@ func expandOrb(cmd *cobra.Command, args []string) error { func publishOrb(cmd *cobra.Command, args []string) error { ctx := context.Background() - response, err := api.OrbPublish(ctx, Logger, orbPath, orbVersion, orbId) + response, err := api.OrbPublish(ctx, Logger, orbPath, orbVersion, orbID) if err != nil { fmt.Println("response error") diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 6be19a80a..3cae475cb 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -226,6 +226,8 @@ var _ = Describe("Orb", func() { // "query": '{}", version: "0.0.1") { orb { version createdAt } errors { message } } } ' // }` + // "query": "mutation($config: String!, $orbId: UUID!, $version: String!) {publishOrb(orbId: $orbId,orbYaml: $config,version: $version)}", + // "query": "mutation($config: String!, $orbId: UUID!, $version: String!) {publishOrb(orbId: $orbId,orbYaml: $config,version: $version) {orb {versioncreatedAt}}errors { message }", expectedRequestJson := `{ "query": "\n\t\tmutation($config: String!, $orbId: UUID!, $version: String!) {\n\t\t\tpublishOrb(\n\t\t\t\torbId: $orbId,\n\t\t\t\torbYaml: $config,\n\t\t\t\tversion: $version) {orb {\n\t\t\tversion\n\t\t\tcreatedAt\n\t\t}\n\t\t}\n\t\terrors { message }\n\t", "variables": { From 5071f4f4fce2db00d79a11bd613382fb1454f058 Mon Sep 17 00:00:00 2001 From: Eric Hu Date: Thu, 19 Jul 2018 03:26:48 +0800 Subject: [PATCH 6/9] Wrap up orb publish tests --- cmd/orb.go | 4 +--- cmd/orb_test.go | 44 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/cmd/orb.go b/cmd/orb.go index 20c5e3824..58231b488 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -2,7 +2,6 @@ package cmd import ( "context" - "fmt" "github.com/CircleCI-Public/circleci-cli/api" "github.com/CircleCI-Public/circleci-cli/client" @@ -44,7 +43,7 @@ func newOrbCommand() *cobra.Command { } orbPublishCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file") orbPublishCommand.PersistentFlags().StringVarP(&orbVersion, "orb-version", "o", "", "version of orb to publish") - orbPublishCommand.PersistentFlags().StringVarP(&orbID, "orb-id", "i", "", "version of orb to publish") + orbPublishCommand.PersistentFlags().StringVarP(&orbID, "orb-id", "i", "", "id of orb to publish") orbCommand := &cobra.Command{ Use: "orb", @@ -171,7 +170,6 @@ func publishOrb(cmd *cobra.Command, args []string) error { response, err := api.OrbPublish(ctx, Logger, orbPath, orbVersion, orbID) if err != nil { - fmt.Println("response error") return err } diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 3cae475cb..25aeac859 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -13,7 +13,7 @@ import ( ) var _ = Describe("Orb", func() { - Describe("with an api and orb.yml", func() { + Describe("behavior with an api and an orb.yml provided", func() { var ( testServer *ghttp.Server orb tmpFile @@ -222,14 +222,8 @@ var _ = Describe("Orb", func() { } }` - // expectedRequestJson := `{ - // "query": '{}", version: "0.0.1") { orb { version createdAt } errors { message } } } ' - // }` - - // "query": "mutation($config: String!, $orbId: UUID!, $version: String!) {publishOrb(orbId: $orbId,orbYaml: $config,version: $version)}", - // "query": "mutation($config: String!, $orbId: UUID!, $version: String!) {publishOrb(orbId: $orbId,orbYaml: $config,version: $version) {orb {versioncreatedAt}}errors { message }", expectedRequestJson := `{ - "query": "\n\t\tmutation($config: String!, $orbId: UUID!, $version: String!) {\n\t\t\tpublishOrb(\n\t\t\t\torbId: $orbId,\n\t\t\t\torbYaml: $config,\n\t\t\t\tversion: $version) {orb {\n\t\t\tversion\n\t\t\tcreatedAt\n\t\t}\n\t\t}\n\t\terrors { message }\n\t", + "query": "\n\t\tmutation($config: String!, $orbId: UUID!, $version: String!) {\n\t\t\tpublishOrb(\n\t\t\t\torbId: $orbId,\n\t\t\t\torbYaml: $config,\n\t\t\t\tversion: $version\n\t\t\t) {\n\t\t\t\torb {\n\t\t\t\t\tversion\n\t\t\t\t\tcreatedAt\n\t\t\t\t}\n\t\t\t\terrors { message }\n\t\t\t}\n\t\t}\n\t", "variables": { "config": "some orb", "orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87", @@ -247,7 +241,39 @@ var _ = Describe("Orb", func() { Eventually(session).Should(gexec.Exit(0)) }) - It("prints errors if invalid", func() { + It("prints all errors returned by the GraphQL API", func() { + By("setting up a mock server") + err := orb.write(`some orb`) + Expect(err).ToNot(HaveOccurred()) + + gqlResponse := `{ + "publishOrb": { + "errors": [ + {"message": "error1"}, + {"message": "error2"} + ], + "orb": null + } + }` + + expectedRequestJson := `{ + "query": "\n\t\tmutation($config: String!, $orbId: UUID!, $version: String!) {\n\t\t\tpublishOrb(\n\t\t\t\torbId: $orbId,\n\t\t\t\torbYaml: $config,\n\t\t\t\tversion: $version\n\t\t\t) {\n\t\t\t\torb {\n\t\t\t\t\tversion\n\t\t\t\t\tcreatedAt\n\t\t\t\t}\n\t\t\t\terrors { message }\n\t\t\t}\n\t\t}\n\t", + "variables": { + "config": "some orb", + "orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87", + "version": "0.0.1" + } + }` + + appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse) + + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Err).Should(gbytes.Say("Error: error1: error2")) + Eventually(session).ShouldNot(gexec.Exit(0)) + }) }) }) From faf683d8d423efb06d9999aaae0eed2c69fa60e3 Mon Sep 17 00:00:00 2001 From: Eric Hu Date: Fri, 20 Jul 2018 03:41:54 +0800 Subject: [PATCH 7/9] Refactor: eliminate copy-pasted single-use test --- api/api.go | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/api/api.go b/api/api.go index 028fa07b9..2c977cbbd 100644 --- a/api/api.go +++ b/api/api.go @@ -127,29 +127,6 @@ func OrbQuery(ctx context.Context, logger *logger.Logger, configPath string) (*C }`) } -func publishOrbQuery(ctx context.Context, logger *logger.Logger, configPath string, - orbVersion string, orbID string, response interface{}, query string) error { - config, err := loadYaml(configPath) - if err != nil { - return err - } - - request := client.NewAuthorizedRequest(viper.GetString("token"), query) - request.Var("config", config) - request.Var("orbId", orbID) - request.Var("version", orbVersion) - - graphQLclient := client.NewClient(viper.GetString("endpoint"), logger) - - err = graphQLclient.Run(ctx, request, response) - - if err != nil { - return errors.Wrap(err, "Unable to validate config") - } - - return nil -} - // OrbPublish publishes a new version of an orb func OrbPublish(ctx context.Context, logger *logger.Logger, configPath string, orbVersion string, orbID string) (*PublishOrbResponse, error) { @@ -159,8 +136,12 @@ func OrbPublish(ctx context.Context, logger *logger.Logger, } } - return &response.PublishOrb.PublishOrbResponse, publishOrbQuery(ctx, logger, - configPath, orbVersion, orbID, &response, ` + config, err := loadYaml(configPath) + if err != nil { + return nil, err + } + + query := ` mutation($config: String!, $orbId: UUID!, $version: String!) { publishOrb( orbId: $orbId, @@ -174,6 +155,19 @@ func OrbPublish(ctx context.Context, logger *logger.Logger, errors { message } } } - `) + ` + request := client.NewAuthorizedRequest(viper.GetString("token"), query) + request.Var("config", config) + request.Var("orbId", orbID) + request.Var("version", orbVersion) + + graphQLclient := client.NewClient(viper.GetString("endpoint"), logger) + + err = graphQLclient.Run(ctx, request, &response) + + if err != nil { + err = errors.Wrap(err, "Unable to publish orb") + } + return &response.PublishOrb.PublishOrbResponse, err } From e227b64b8f6a32c3671e1ff7e2bcaf4af816202e Mon Sep 17 00:00:00 2001 From: Eric Hu Date: Fri, 20 Jul 2018 05:42:48 +0800 Subject: [PATCH 8/9] Extract common test setup --- cmd/orb_test.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 25aeac859..fd016553d 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -12,12 +12,12 @@ import ( "github.com/onsi/gomega/ghttp" ) -var _ = Describe("Orb", func() { - Describe("behavior with an api and an orb.yml provided", func() { +var _ = Describe("Orb integration tests", func() { + Describe("CLI behavior with a stubbed api and an orb.yml provided", func() { var ( testServer *ghttp.Server orb tmpFile - token string + token string = "testtoken" command *exec.Cmd ) @@ -36,7 +36,6 @@ var _ = Describe("Orb", func() { Describe("when validating orb", func() { BeforeEach(func() { - token = "testtoken" command = exec.Command(pathCLI, "orb", "validate", "-t", token, @@ -109,13 +108,7 @@ var _ = Describe("Orb", func() { }) Describe("when expanding orb", func() { - var ( - token string - command *exec.Cmd - ) - BeforeEach(func() { - token = "testtoken" command = exec.Command(pathCLI, "orb", "expand", "-t", token, @@ -191,7 +184,6 @@ var _ = Describe("Orb", func() { Describe("when publishing an orb version", func() { BeforeEach(func() { - token = "testtoken" command = exec.Command(pathCLI, "orb", "publish", "-t", token, From 642314a0a6cb835051e923ed221d81a802508f87 Mon Sep 17 00:00:00 2001 From: Eric Hu Date: Fri, 20 Jul 2018 06:29:14 +0800 Subject: [PATCH 9/9] Refactor shared GQL error structs and error handlers --- api/api.go | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/api/api.go b/api/api.go index 2c977cbbd..1d3832563 100644 --- a/api/api.go +++ b/api/api.go @@ -11,6 +11,14 @@ import ( "github.com/spf13/viper" ) +// GQLResponseErrors is a slice of errors returned by the GraphQL server. Each +// error message is a key-value pair with the structure "Message: string" +type GQLResponseErrors struct { + Errors []struct { + Message string + } +} + // ConfigResponse is a structure that matches the result of the GQL // query, so that we can use mapstructure to convert from // nested maps to a strongly typed struct. @@ -19,9 +27,7 @@ type ConfigResponse struct { SourceYaml string OutputYaml string - Errors []struct { - Message string - } + GQLResponseErrors } // The PublishOrbResponse type matches the data shape of the GQL response for @@ -32,25 +38,12 @@ type PublishOrbResponse struct { Version string } - Errors []struct { - Message string - } -} - -// ToError returns an error created from any error messages, or nil. -func (response ConfigResponse) ToError() error { - messages := []string{} - - for i := range response.Errors { - messages = append(messages, response.Errors[i].Message) - } - - return errors.New(strings.Join(messages, ": ")) + GQLResponseErrors } -// ToError (PublishOrbResponse) returns all errors that the GraphQL API returns -// as part of a publish orb request. -func (response PublishOrbResponse) ToError() error { +// ToError returns all GraphQL errors for a single response concatenated, or +// nil. +func (response GQLResponseErrors) ToError() error { messages := []string{} for i := range response.Errors {