diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..1001a5f9 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,22 @@ +--- +name: CI + +on: [push, pull_request] + +jobs: + tests: + name: Tests + runs-on: ubuntu-latest + container: docker.io/library/golang:${{ matrix.go }} + strategy: + matrix: + go: ['1.13', '1.14'] + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: add gox + run: go install github.com/mitchellh/gox + - name: make binaries + run: make all + - name: perform tests + run: go test -v ./... diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index da4e8133..00000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -addons: - apt: - packages: - - pass - - gnupg - -language: go - -go: - - 1.13.x - -env: - - GO111MODULE=on - -script: - - go test ./... - - go vet -composites=false ./... - - make - - make all - - make prove 2>&1 diff --git a/Makefile b/Makefile index ae4ce347..156262c1 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,6 @@ all: rm -rf dist mkdir -p dist gox -ldflags="-w -s" -output="dist/github.com/go-jira/jira-{{.OS}}-{{.Arch}}" -osarch="darwin/amd64 linux/386 linux/amd64 windows/386 windows/amd64" ./... - _t/test_binaries.sh install: ${MAKE} GOBIN=$$HOME/bin build diff --git a/go.mod b/go.mod index acdb3f71..be35b74d 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/coryb/oreo v0.0.0-20180804211640-3e1b88fc08f1 github.com/davecgh/go-spew v1.1.0 // indirect github.com/fatih/camelcase v1.0.0 // indirect + github.com/google/go-cmp v0.5.2 github.com/google/uuid v1.1.1 // indirect github.com/guelfey/go.dbus v0.0.0-20131113121618-f6a3a2366cc3 // indirect github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c // indirect diff --git a/go.sum b/go.sum index 1eed53e8..b8979cd9 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/guelfey/go.dbus v0.0.0-20131113121618-f6a3a2366cc3 h1:fngCxKbvZdctIsWj2hYijhAt4iK0JXSSA78B36xP0yI= @@ -77,6 +79,7 @@ golang.org/x/net v0.0.0-20171102191033-01c190206fbd h1:CLQSRrSDQMOMkogMxky7XOkER golang.org/x/net v0.0.0-20171102191033-01c190206fbd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sys v0.0.0-20180727230415-bd9dbc187b6e h1:3dQ4fR8k5KugjVKO0oqSd1odxuk2yaE2CIfxWP2WarQ= golang.org/x/sys v0.0.0-20180727230415-bd9dbc187b6e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/AlecAivazis/survey.v1 v1.6.1 h1:HyWkjKGBpzhNxrpaKRLDqoa4L1f4cMVBNU4bnVmU8Mw= gopkg.in/AlecAivazis/survey.v1 v1.6.1/go.mod h1:2Ehl7OqkBl3Xb8VmC4oFW2bItAhnUfzIjrOzwRxCrOU= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= diff --git a/test/.jira.d/config.yml b/test/.jira.d/config.yml new file mode 100644 index 00000000..a616281a --- /dev/null +++ b/test/.jira.d/config.yml @@ -0,0 +1,49 @@ +config: + stop: true +endpoint: https://go-jira.atlassian.net +password-source: stdin +user: gojira +login: gojira@corybennett.org + +project: BASIC + +queries: + todo: >- + resolution = unresolved {{if .project}}AND project = '{{.project}}'{{end}} AND status = 'To Do' + +custom-commands: + - name: env + help: print the JIRA environment variables available to custom commands + script: |- + env | sort | grep JIRA + - name: print-project + help: print the name of the configured project + script: "echo $JIRA_PROJECT" + - name: jira-path + help: print the path the jira command that is running this alias + script: |- + echo {{jira}} + - name: mine + help: display issues assigned to me + script: |- + if [ -n "$JIRA_PROJECT" ]; then + # if `project: ...` configured just list the issues for current project + {{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() and project = $JIRA_PROJECT ORDER BY priority asc, created" + else + # otherwise list issues for all project + {{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() ORDER BY priority asc, created" + fi + - name: argtest + help: testing passing args + script: |- + echo {{args.ARG}} + args: + - name: ARG + help: string to echo for testing + - name: opttest + help: testing passing option flags + script: |- + echo {{options.OPT}} + options: + - name: OPT + help: string to echo for testing diff --git a/test/.jira.d/list.yml b/test/.jira.d/list.yml new file mode 100644 index 00000000..682c0a11 --- /dev/null +++ b/test/.jira.d/list.yml @@ -0,0 +1 @@ +template: list diff --git a/test/apilogin.go b/test/apilogin.go new file mode 100644 index 00000000..5880df6f --- /dev/null +++ b/test/apilogin.go @@ -0,0 +1,56 @@ +package test + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" +) + +// withApiLogin is a hack to provide an api token on every command, this means keyring +// and gpg is not necessary to run the testing suite. +// +// a buffer containing stdout will be returned to the caller if no error is encountered. +// this still expects a config file is in a parent where the test runs for project +// and endpoint details. +func withApiLogin(login string, token string, cmd *exec.Cmd) (bytes.Buffer, error) { + var buf bytes.Buffer + + cmd.Args = append(cmd.Args, "--login", login) + + diag := fmt.Sprintf("--- running command: %+v ---\n", cmd.Args) + io.WriteString(os.Stdout, diag) + + // write to stdout and also to our buffer + out := io.MultiWriter(&buf, os.Stdout) + cmd.Stdout = out + + e := io.MultiWriter(&buf, os.Stderr) + cmd.Stderr = e + + in, err := cmd.StdinPipe() + if err != nil { + return buf, err + } + + err = cmd.Start() + if err != nil { + return buf, err + } + + _, err = io.WriteString(in, token) + if err != nil { + return buf, err + } + in.Close() + + err = cmd.Wait() + if err != nil { + return buf, err + } + + diag = fmt.Sprintf("--- finished command: %+v ---\n\n", cmd.Args) + io.WriteString(os.Stdout, diag) + return buf, nil +} diff --git a/test/checks.go b/test/checks.go new file mode 100644 index 00000000..3b04d73a --- /dev/null +++ b/test/checks.go @@ -0,0 +1,98 @@ +package test + +import ( + "bytes" + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func checkDiff(t *testing.T, buf bytes.Buffer, expect string, formats ...interface{}) { + expect = fmt.Sprintf(expect, formats...) + if !cmp.Equal(expect, buf.String()) { + t.Fatal( + cmp.Diff( + buf.String(), + expect, + ), + ) + } +} + +func checkCreateIssue(t *testing.T, buf bytes.Buffer, endpoint string) string { + out := strings.Split(buf.String(), " ") + if len(out) < 3 { + t.Fatalf("unexpected split count on create output: %v", buf.String()) + } + issue := out[1] + expect := fmt.Sprintf("OK %s %s/browse/%s\n", issue, endpoint, issue) + if !cmp.Equal(expect, buf.String()) { + t.Fatal( + cmp.Diff( + buf.String(), + expect, + ), + ) + } + return issue +} + +func checkEditIssue(t *testing.T, buf bytes.Buffer, issue, endpoint string) { + out := strings.Split(buf.String(), " ") + if len(out) < 3 { + t.Fatalf("unexpected split count on create output: %v", buf.String()) + } + editedIssue := out[1] + expect := fmt.Sprintf("OK %s %s/browse/%s\n", issue, endpoint, issue) + if !cmp.Equal(expect, buf.String()) { + t.Fatal( + cmp.Diff( + buf.String(), + expect, + ), + ) + } + if !cmp.Equal(editedIssue, issue) { + t.Fatal( + cmp.Diff( + editedIssue, + issue, + ), + ) + } +} + +func checkIssueInOutput(t *testing.T, buf bytes.Buffer, issue string) { + if !strings.Contains(buf.String(), issue) { + t.Fatalf("issue %s not located in stdout: %s", issue, buf.String()) + } +} + +func checkIssueNotInOutput(t *testing.T, buf bytes.Buffer, issue string) { + if strings.Contains(buf.String(), issue) { + t.Fatalf("issue %s not located in stdout: %s", issue, buf.String()) + } +} + +func checkBlockIssue(t *testing.T, buf bytes.Buffer, issue, blocker, endpoint string) { + checkDualIssues(t, buf, blocker, issue, endpoint) +} + +func checkDupIssue(t *testing.T, buf bytes.Buffer, issue, duplicate, endpoint string) { + checkDualIssues(t, buf, issue, duplicate, endpoint) +} + +func checkDualIssues(t *testing.T, buf bytes.Buffer, first, second, endpoint string) { + lines := strings.Split(buf.String(), "\n") + if len(lines) < 2 { + t.Fatalf("unexpected split count on create output: %v", buf.String()) + } + + testBuf := bytes.NewBuffer([]byte(lines[0] + "\n")) + checkEditIssue(t, *testBuf, first, endpoint) + + testBuf = bytes.NewBuffer([]byte(lines[1] + "\n")) + checkEditIssue(t, *testBuf, second, endpoint) +} diff --git a/test/cli_test.go b/test/cli_test.go new file mode 100644 index 00000000..6dca983a --- /dev/null +++ b/test/cli_test.go @@ -0,0 +1,815 @@ +package test + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" +) + +const ( + endpoint = "https://go-jira.atlassian.net" + goJiraApiToken = "Rw1cPlKI40TJeEl1Pj88A5ED" + goJiraLogin = "gojira@corybennett.org" + mothraApiToken = "UNXrI9gq5p0LWUtblAxDA7A6" + mothraLogin = "mothra@corybennett.org" +) + +var jira string = "../dist/github.com/go-jira/jira-linux-amd64" + +func Test_CLI(t *testing.T) { + // setup the jira cli environment + jira, err := filepath.Abs(jira) + if err != nil { + t.Fatal(err) + } + if !filepath.IsAbs(jira) { + t.Fatalf("could not obtain absolute path to jira binary") + } + + if _, err := os.Stat(jira); err != nil { + t.Fatalf("could not stat %v: %v", jira, err) + } + + os.Setenv("COLUMNS", "149") + os.Setenv("JIRA_LOG_FORMAT", "%{level:-5s} %{message}") + os.Setenv("ENDPOINT", endpoint) + os.Setenv("JIRACLOUD", "1") + + t.Run("basic", test_Basic) + t.Run("pagination", test_Pagination) +} + +// test_Basic will test the basic functionality required in the cli +func test_Basic(t *testing.T) { + // we'll reassign these often, just create + // them here. + var buf bytes.Buffer + var err error + + // Create an issue + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + createIssue( + jira, + "BASIC", + "summary", + "description", + ), + ) + if err != nil { + t.Fatalf("cmd failed. stdout: %v err: %v", buf.String(), err) + } + issue := checkCreateIssue(t, buf, endpoint) + + // View the issue + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + viewIssue( + jira, + issue, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: To Do +summary: summary +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +priority: Medium +votes: 0 +description: | + description +`, issue) + + // confirm new issue shows in project list + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + listIssues( + jira, + "BASIC", + "", // empty string means do not use a named query + "", // empty string means do not use raw query + "", // empty string means do not use a template + "", // empty string means do not limit response + ), + ) + checkIssueInOutput(t, buf, issue) + + // confirm issue appears with named query + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + listIssues( + jira, + "BASIC", + "todo", + "", + "", + "", + ), + ) + checkIssueInOutput(t, buf, issue) + + // confirm issue appears with table template + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + listIssues( + jira, + "BASIC", + "", + "", + "table", + "", + ), + ) + checkIssueInOutput(t, buf, issue) + + // edit an issue + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + editIssue( + jira, + issue, + "edit comment", + "priority=High", + "", + ), + ) + checkEditIssue(t, buf, issue, endpoint) + + // edit multiple issues with query and check comments updated + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + editIssue( + jira, + issue, + "bulk edit comment", + "priority=High", + `resolution = unresolved AND project = 'BASIC' AND status = 'To Do'`, + ), + ) + checkEditIssue(t, buf, issue, endpoint) + // view the issue + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + viewIssue( + jira, + issue, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: To Do +summary: summary +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +priority: High +votes: 0 +description: | + description + +comments: + - | # GoJira, a minute ago + edit comment + - | # GoJira, a minute ago + bulk edit comment + +`, issue) + + // try invalid close of issue + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + closeIssue( + jira, + issue, + ), + ) + checkDiff(t, buf, `ERROR Invalid Transition "close" from "To Do", Available: To Do, In Progress, In Review, Done +`) + + // put issue in done state + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + doneIssue( + jira, + issue, + ), + ) + checkEditIssue(t, buf, issue, endpoint) + + // make sure our resolved issue is not present in the project + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + listIssues( + jira, + "BASIC", + "", + "", + "", + "", + ), + ) + checkIssueNotInOutput(t, buf, issue) + + // create two new issues to test duplicating + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + createIssue( + jira, + "BASIC", + "summary", + "description", + ), + ) + issue = checkCreateIssue(t, buf, endpoint) + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + createIssue( + jira, + "BASIC", + "dup", + "dup", + ), + ) + dup := checkCreateIssue(t, buf, endpoint) + + // mark issue as duplicate + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + dupIssue( + jira, + issue, + dup, + ), + ) + checkDupIssue(t, buf, issue, dup, endpoint) + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + viewIssue( + jira, + issue, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: To Do +summary: summary +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +blockers: +depends: %s[Done] +priority: Medium +votes: 0 +description: | + description +`, issue, dup) + + // check dup is resolved and not in listed issue + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + listIssues(jira, + "BASIC", + "", + "", + "", + ""), + ) + checkIssueNotInOutput(t, buf, dup) + + // create blocker issue + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + createIssue( + jira, + "BASIC", + "blocks", + "blocks", + ), + ) + blocker := checkCreateIssue(t, buf, endpoint) + + // set blocker + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + blockIssue( + jira, + blocker, + issue, + ), + ) + checkBlockIssue(t, buf, blocker, issue, endpoint) + + // confirm blocker shows up when viewing issue + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + viewIssue( + jira, + issue, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: To Do +summary: summary +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +blockers: %s[To Do] +depends: %s[Done] +priority: Medium +votes: 0 +description: | + description +`, issue, blocker, dup) + + // confirm both issues are unresolved + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + listIssues( + jira, + "BASIC", + "", + "", + "", + "", + ), + ) + checkIssueInOutput(t, buf, issue) + checkIssueInOutput(t, buf, blocker) + + // // + // begin using mothra user // + // // + + // use mothra to vote for main issue + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + voteIssue( + jira, + issue, + false, + ), + ) + checkEditIssue(t, buf, issue, endpoint) + + // view issue to confirm vote + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + viewIssue( + jira, + issue, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: To Do +summary: summary +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +blockers: %s[To Do] +depends: %s[Done] +priority: Medium +votes: 1 +description: | + description +`, issue, blocker, dup) + + // down vote and confirm + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + voteIssue( + jira, + issue, + true, // down vote true + ), + ) + checkEditIssue(t, buf, issue, endpoint) + + // view issue to confirm vote + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + viewIssue( + jira, + issue, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: To Do +summary: summary +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +blockers: %s[To Do] +depends: %s[Done] +priority: Medium +votes: 0 +description: | + description +`, issue, blocker, dup) + + // TODO(louis): skipping watcher test for now until a + // "watchers" command is implemented. + + // set blocker to "In Progress" + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + transIssue( + jira, + "In Progress", + blocker, + ), + ) + checkEditIssue(t, buf, blocker, endpoint) + + // set it back to "To Do" + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + todoIssue( + jira, + blocker, + ), + ) + checkEditIssue(t, buf, blocker, endpoint) + + // set it to "In Review" + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + transIssue( + jira, + "review", + blocker, + ), + ) + checkEditIssue(t, buf, blocker, endpoint) + + // set it back to "To Do" + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + todoIssue( + jira, + blocker, + ), + ) + checkEditIssue(t, buf, blocker, endpoint) + + // set it to in progress + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + progIssue( + jira, + blocker, + ), + ) + checkEditIssue(t, buf, blocker, endpoint) + + // set it to in done + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + doneIssue( + jira, + blocker, + ), + ) + checkEditIssue(t, buf, blocker, endpoint) + + // confirm blocker is done + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + viewIssue( + jira, + issue, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: To Do +summary: summary +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +blockers: %s[Done] +depends: %s[Done] +priority: Medium +votes: 0 +description: | + description +`, issue, blocker, dup) + + // verify we can add comment + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + commentIssue( + jira, + issue, + "Yo, Comment", + ), + ) + checkEditIssue(t, buf, issue, endpoint) + + // verify we can see comment + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + viewIssue( + jira, + issue, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: To Do +summary: summary +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +blockers: %s[Done] +depends: %s[Done] +priority: Medium +votes: 0 +description: | + description + +comments: + - | # Mothra, a minute ago + Yo, Comment + +`, issue, blocker, dup) + + // verify we can add labels + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + addLabelsIssue( + jira, + blocker, + "test-label", + "another-label", + ), + ) + checkEditIssue(t, buf, blocker, endpoint) + + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + viewIssue( + jira, + blocker, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: Done +summary: blocks +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +blockers: +depends: %s[To Do] +priority: Medium +votes: 0 +labels: another-label, test-label +description: | + blocks +`, blocker, issue) + + // verify we can remove labels + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + removeLabelsIssue( + jira, + blocker, + "another-label", + ), + ) + checkEditIssue(t, buf, blocker, endpoint) + + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + viewIssue( + jira, + blocker, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: Done +summary: blocks +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +blockers: +depends: %s[To Do] +priority: Medium +votes: 0 +labels: test-label +description: | + blocks +`, blocker, issue) + + // verify we can replace labels + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + setLabelsIssue( + jira, + blocker, + "more-label", + "better-label", + ), + ) + checkEditIssue(t, buf, blocker, endpoint) + + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + viewIssue( + jira, + blocker, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: Done +summary: blocks +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +blockers: +depends: %s[To Do] +priority: Medium +votes: 0 +labels: better-label, more-label +description: | + blocks +`, blocker, issue) + + // verify mothra can take an issue + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + takeIssue( + jira, + blocker, + ), + ) + checkEditIssue(t, buf, blocker, endpoint) + + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + viewIssue( + jira, + blocker, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: Done +summary: blocks +project: BASIC +issuetype: Bug +assignee: Mothra +reporter: GoJira +blockers: +depends: %s[To Do] +priority: Medium +votes: 0 +labels: better-label, more-label +description: | + blocks +`, blocker, issue) + + // verify martha can give the issue back + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + giveIssue( + jira, + blocker, + "gojira", + ), + ) + checkEditIssue(t, buf, blocker, endpoint) + + buf, err = withApiLogin( + mothraLogin, + mothraApiToken, + viewIssue( + jira, + blocker, + ), + ) + checkDiff(t, buf, `issue: %s +created: a minute ago +status: Done +summary: blocks +project: BASIC +issuetype: Bug +assignee: GoJira +reporter: GoJira +blockers: +depends: %s[To Do] +priority: Medium +votes: 0 +labels: better-label, more-label +description: | + blocks +`, blocker, issue) +} + +func test_Pagination(t *testing.T) { + var buf bytes.Buffer + var err error + + // note: + // we test limit+1 to handle extra newline split + + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + listIssues( + jira, + "BASIC", + "", + "project = 'BASIC' AND status = 'Done'", // query + "", + "102", + ), + ) + if err != nil { + t.Fatalf("failed to list issues. stderr:%v err: %v", buf.String(), err) + } + if len(strings.Split(buf.String(), "\n")) != 103 { + t.Fatalf("got: %v want: %v", len(strings.Split(buf.String(), "\n")), 103) + } + + buf, err = withApiLogin( + goJiraLogin, + goJiraApiToken, + listIssues( + jira, + "BASIC", + "", + "project = 'BASIC' AND status = 'Done'", // query + "", // empty string means do not use a template + "1", + ), + ) + if err != nil { + t.Fatalf("failed to list issues. stderr:%v err: %v", buf.String(), err) + } + if len(strings.Split(buf.String(), "\n")) != 2 { + t.Fatalf("got: %v want: %v", len(strings.Split(buf.String(), "\n")), 2) + } +} diff --git a/test/commands.go b/test/commands.go new file mode 100644 index 00000000..e8d024fb --- /dev/null +++ b/test/commands.go @@ -0,0 +1,226 @@ +package test + +import ( + "os/exec" +) + +func session(jira string) *exec.Cmd { + cmd := exec.Command( + jira, + "session", + ) + return cmd +} + +func createIssue(jira, project, summary, description string) *exec.Cmd { + sum := "summary=" + summary + desc := "description=" + description + cmd := exec.Command( + jira, + "create", + "--project", project, + "-o", sum, + "-o", desc, + "--noedit", + ) + return cmd +} + +func viewIssue(jira, issue string) *exec.Cmd { + return exec.Command( + jira, + "view", + issue, + ) +} + +func listIssues(jira, project, query, rawquery, template, limit string) *exec.Cmd { + cmd := exec.Command( + jira, + "ls", + "--project", + project, + ) + if query != "" { + cmd.Args = append(cmd.Args, "-n", query) + } + if rawquery != "" { + cmd.Args = append(cmd.Args, "-q", rawquery) + } + if template != "" { + cmd.Args = append(cmd.Args, "--template", template) + } + if limit != "" { + cmd.Args = append(cmd.Args, "--limit", limit) + } + return cmd +} + +func editIssue(jira, issue, message, override, query string) *exec.Cmd { + cmd := exec.Command( + jira, + "edit", + issue, + "-m", + message, + "--override", + override, + "--noedit", + ) + if query != "" { + cmd.Args = append(cmd.Args, "--query", query) + } + return cmd +} + +func closeIssue(jira, issue string) *exec.Cmd { + cmd := exec.Command( + jira, + "close", + issue, + ) + return cmd +} + +func doneIssue(jira, issue string) *exec.Cmd { + cmd := exec.Command( + jira, + "done", + issue, + ) + return cmd +} + +func dupIssue(jira, issue, duplicate string) *exec.Cmd { + cmd := exec.Command( + jira, + "dup", + duplicate, + issue, + ) + return cmd +} + +func blockIssue(jira, blocker, issue string) *exec.Cmd { + cmd := exec.Command( + jira, + "block", + blocker, + issue, + ) + return cmd +} + +func voteIssue(jira, issue string, down bool) *exec.Cmd { + cmd := exec.Command( + jira, + "vote", + issue, + ) + if down { + cmd.Args = append(cmd.Args, "--down") + } + return cmd +} + +func watchIssue(jira, issue string) *exec.Cmd { + cmd := exec.Command( + jira, + "watch", + issue, + ) + return cmd +} + +func transIssue(jira, trans, issue string) *exec.Cmd { + cmd := exec.Command( + jira, + "trans", + trans, + issue, + "--noedit", + ) + return cmd +} + +func todoIssue(jira, issue string) *exec.Cmd { + cmd := exec.Command( + jira, + "todo", + issue, + ) + return cmd +} + +func progIssue(jira, issue string) *exec.Cmd { + cmd := exec.Command( + jira, + "prog", + issue, + ) + return cmd +} + +func commentIssue(jira, issue, comment string) *exec.Cmd { + cmd := exec.Command( + jira, + "comment", + issue, + "--noedit", + "-m", + comment, + ) + return cmd +} + +func addLabelsIssue(jira, issue string, labels ...string) *exec.Cmd { + cmd := exec.Command( + jira, + "labels", + "add", + issue, + ) + cmd.Args = append(cmd.Args, labels...) + return cmd +} + +func removeLabelsIssue(jira, issue string, labels ...string) *exec.Cmd { + cmd := exec.Command( + jira, + "labels", + "remove", + issue, + ) + cmd.Args = append(cmd.Args, labels...) + return cmd +} + +func setLabelsIssue(jira, issue string, labels ...string) *exec.Cmd { + cmd := exec.Command( + jira, + "labels", + "set", + issue, + ) + cmd.Args = append(cmd.Args, labels...) + return cmd +} + +func takeIssue(jira, issue string) *exec.Cmd { + cmd := exec.Command( + jira, + "take", + issue, + ) + return cmd +} + +func giveIssue(jira, issue, taker string) *exec.Cmd { + cmd := exec.Command( + jira, + "give", + issue, + taker, + ) + return cmd +}