Skip to content

Commit

Permalink
Circle 13219 Fix orb list infinite loop (#71)
Browse files Browse the repository at this point in the history
Make sure we get the cursor when listing orbs to handle pagination
  • Loading branch information
eric-hu authored and Zachary Scott committed Aug 23, 2018
1 parent 9788d75 commit 16fae3d
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/orb.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ query ListOrbs ($after: String!) {
orbs(first: 20, after: $after) {
totalCount,
edges {
cursor
node {
name
versions(count: 1) {
Expand Down
43 changes: 43 additions & 0 deletions cmd/orb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd_test

import (
"encoding/json"
"io/ioutil"
"net/http"
"os"
"os/exec"
Expand Down Expand Up @@ -569,5 +570,47 @@ var _ = Describe("Orb integration tests", func() {
})
})

Describe("when listing orbs", func() {
BeforeEach(func() {
command = exec.Command(pathCLI,
"orb", "list", orb.Path,
"--host", testServer.URL(),
"--verbose",
)
})

It("sends multiple requests when there are more than 1 page of orbs", func() {
By("setting up a mock server")

tmpBytes, err := ioutil.ReadFile(filepath.Join("testdata/gql_orb_list", "first_response.json"))
Expect(err).ShouldNot(HaveOccurred())
firstGqlResponse := string(tmpBytes)

tmpBytes, err = ioutil.ReadFile(filepath.Join("testdata/gql_orb_list", "second_response.json"))
Expect(err).ShouldNot(HaveOccurred())
secondGqlResponse := string(tmpBytes)

// Use Gomega's default matcher instead of our custom appendPostHandler
// since this query doesn't pass in a token.
// Skip checking the content type field to make this test simpler.
testServer.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/graphql-unstable"),
ghttp.RespondWith(http.StatusOK, firstGqlResponse),
),
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/graphql-unstable"),
ghttp.RespondWith(http.StatusOK, secondGqlResponse),
),
)

By("running the command")
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

Expect(err).ShouldNot(HaveOccurred())
Eventually(session).Should(gexec.Exit(0))
Expect(testServer.ReceivedRequests()).Should(HaveLen(2))
})
})
})
})
192 changes: 192 additions & 0 deletions cmd/testdata/gql_orb_list/first_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
{
"data": {
"orbs": {
"totalCount": 22,
"edges": [
{
"cursor": "ahgfsdlkfhjaklshdkfhaksldjfkasdlfjas/foo",
"node": {
"name": "ahgfsdlkfhjaklshdkfhaksldjfkasdlfjas/foo",
"versions": []
}
},
{
"cursor": "ahgfsdlkfhjaklshdkfhaksldjfkasdlfjas/foobar",
"node": {
"name": "ahgfsdlkfhjaklshdkfhaksldjfkasdlfjas/foobar",
"versions": []
}
},
{
"cursor": "ahgfsdlkfhjaklshdkfhaksldjfkasdlfjas/foobarrr",
"node": {
"name": "ahgfsdlkfhjaklshdkfhaksldjfkasdlfjas/foobarrr",
"versions": []
}
},
{
"cursor": "circleci/aws-cli",
"node": {
"name": "circleci/aws-cli",
"versions": []
}
},
{
"cursor": "circleci/aws-code-deploy",
"node": {
"name": "circleci/aws-code-deploy",
"versions": []
}
},
{
"cursor": "circleci/codecov-clojure",
"node": {
"name": "circleci/codecov-clojure",
"versions": [
{
"version": "0.0.3",
"source": "{}"
}
]
}
},
{
"cursor": "circleci/gradle",
"node": {
"name": "circleci/gradle",
"versions": [
{
"version": "0.0.1",
"source": "executor:\n gradle:\n description: The docker container to use when running Gradle builds\n docker:\n - image: circleci/openjdk:8-jdk-node\n environment:\n # Disable fancy terminal updates\n TERM: dumb\n\ncommands:\n with_cache:\n description: |\n Run a set of steps with gradle dependencies cached.\n\n This command will first restore a cache of gradle dependencies, if one was\n saved by a previous build. The provided `steps` will then be executed, and\n if successful, then a fresh cache will be saved, if required.\n\n The contents of the `~/.gradle` directory is cached, which will substantially\n improve build times for projects with many dependencies.\n\n The cache-key is generated from any files named `build.gradle` that are\n present in the `working_directory`.\n parameters:\n steps:\n type: steps\n steps:\n - run:\n name: Generate Cache Checksum\n command: find . -name 'build.gradle' -exec cat {} + | shasum | awk '{print $1}' > /tmp/gradle_cache_seed\n - restore_cache:\n key: gradle-{{ checksum \"/tmp/gradle_cache_seed\" }}\n - << parameters.steps >>\n - save_cache:\n paths:\n - ~/.gradle\n key: gradle-{{ checksum \"/tmp/gradle_cache_seed\" }}\n\njobs:\n test:\n description: |\n Checkout, build and test a gradle project.\n executor: gradle\n parameters:\n test_command:\n type: string\n default: test\n steps:\n - checkout\n - with_cache:\n steps:\n - run:\n name: Run Tests\n command: ./gradlew << parameters.test_command >>\n"
}
]
}
},
{
"cursor": "circleci/heroku",
"node": {
"name": "circleci/heroku",
"versions": []
}
},
{
"cursor": "eric-hu/delivery-test",
"node": {
"name": "eric-hu/delivery-test",
"versions": [
{
"version": "1.0.4",
"source": "{}\n"
}
]
}
},
{
"cursor": "eric-hu/test",
"node": {
"name": "eric-hu/test",
"versions": []
}
},
{
"cursor": "foo/aws-s3",
"node": {
"name": "foo/aws-s3",
"versions": []
}
},
{
"cursor": "foo/elp",
"node": {
"name": "foo/elp",
"versions": []
}
},
{
"cursor": "foo/s3",
"node": {
"name": "foo/s3",
"versions": []
}
},
{
"cursor": "ndintenfass/build-utils",
"node": {
"name": "ndintenfass/build-utils",
"versions": [
{
"version": "0.0.2",
"source": "version: 2\ncommands:\n install-circleci-cli:\n parameters:\n root-url:\n description: the root URL used to generate the download link. You almost certainly can rely on the default value.\n type: string\n default: \"https://github.com/CircleCI-Public/circleci-cli/releases/download/\"\n install-dir:\n description: the directory into which the binary will be installed. The default of /usr/local/bin should work in most cases.\n type: string\n default: \"/usr/local/bin\"\n tag:\n description: When not empty string, get the specific tag release. When empty (default), retrieve latest.\n type: string\n default: \"\"\n steps:\n - run:\n name: \"Install `circleci` CLI\"\n command: |\n echoerr ()\n {\n echo \"$@\" >&2\n }\n if [ $(uname) == \"Darwin\" ]; then\n OS=darwin\n elif [ $(expr substr $(uname -s) 1 5) == \"Linux\" ]; then\n OS=linux\n else\n echoerr \"This installer is only supported on Linux and MacOS\"\n exit 1\n fi\n ARCH=\"$(uname -m)\"\n if [ $ARCH == \"x86_64\" ]; then\n ARCH=amd64\n # we are not currently publishing an arm release anyway.\n # elif [[ $ARCH == arm* ]]; then\n # ARCH=arm\n else\n echoerr \"This installer does not support your architecture: $ARCH\"\n exit 1\n fi\n CIRCLECI_CLI_RELEASE_API_ROOT=\"https://api.github.com/repos/CircleCI-Public/circleci-cli/releases/\"\n if [ \"<< parameters.tag >>\" == \"\" ]; then\n CIRCLECI_CLI_APPEND_VERSION=\"latest\"\n else\n CIRCLECI_CLI_APPEND_VERSION=\"tags/<< parameters.tag >>\"\n fi\n CIRCLECI_CLI_RELEASE_API_ENDPOINT=${CIRCLECI_CLI_RELEASE_API_ROOT}${CIRCLECI_CLI_APPEND_VERSION}\n echo \"Retrieve the latest version by looking for tag_name in ${CIRCLECI_CLI_RELEASE_API_ENDPOINT} \"\n CIRCLECI_CLI_INSTALL_VERSION=`curl --silent \"${CIRCLECI_CLI_RELEASE_API_ENDPOINT}\" | grep '\"tag_name\":' | sed -E 's/.*\"([^\"]+)\".*/\\1/'`\n if [ ! $CIRCLECI_CLI_INSTALL_VERSION ]; then\n echoerr \"The tag requested does not appear to be valid for the circleci CLI\"\n exit 1\n fi\n CIRCLECI_CLI_RELEASE_NAME=\"circleci-cli_${CIRCLECI_CLI_INSTALL_VERSION#v}_${OS}_${ARCH}\"\n download_url=<< parameters.root-url >>${CIRCLECI_CLI_INSTALL_VERSION}/${CIRCLECI_CLI_RELEASE_NAME}.tar.gz\n echo \"Download ${download_url}, untar it, then move it to << parameters.install-dir >>\"\n curl -L ${download_url} | tar -xvzf -\n mv ${CIRCLECI_CLI_RELEASE_NAME}/circleci << parameters.install-dir >>\n echo \"Make sure the CLI is now installed and is ready for use.\"\n if [ ! type circleci &>/dev/null ]; then\n echoerr \"Something went wrong installing the circleci CLI\"\n exit 1\n fi\n echo \"Run circleci help\"\n circleci help\n"
}
]
}
},
{
"cursor": "test1/foo",
"node": {
"name": "test1/foo",
"versions": [
{
"version": "0.0.0",
"source": "version: \"2.1\"\n\nexecutors:\n python:\n docker:\n - image: circleci/python:3\n - image: rabbitmq:3.6-management-alpine\n environment:\n ENV: ci\n TESTS: all\n shell: /bin/bash\n working_directory: ~/project\n\njobs:\n build:\n docker:\n - image: circleci/python:2\n - image: postgres:9.6\n executor: python\n environment:\n TESTS: unit\n steps:\n - run: echo required\n working_directory: ~/tests\n"
}
]
}
},
{
"cursor": "test1/new-orb",
"node": {
"name": "test1/new-orb",
"versions": [
{
"version": "0.0.1",
"source": "{}"
}
]
}
},
{
"cursor": "test1/so-fly",
"node": {
"name": "test1/so-fly",
"versions": [
{
"version": "0.0.0",
"source": "{}"
}
]
}
},
{
"cursor": "test/here-we-go",
"node": {
"name": "test/here-we-go",
"versions": []
}
},
{
"cursor": "test/new-orb",
"node": {
"name": "test/new-orb",
"versions": []
}
},
{
"cursor": "test/test",
"node": {
"name": "test/test",
"versions": [
{
"version": "0.0.3",
"source": "{}\n"
}
]
}
}
],
"pageInfo": {
"hasNextPage": true
}
}
}
}
26 changes: 26 additions & 0 deletions cmd/testdata/gql_orb_list/second_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"data": {
"orbs": {
"totalCount": 22,
"edges": [
{
"cursor": "test/trying-here",
"node": {
"name": "test/trying-here",
"versions": []
}
},
{
"cursor": "testy-mctesterson-boo/dummy",
"node": {
"name": "testy-mctesterson-boo/dummy",
"versions": []
}
}
],
"pageInfo": {
"hasNextPage": false
}
}
}
}

0 comments on commit 16fae3d

Please sign in to comment.