Skip to content

Commit 18203c1

Browse files
committed
A small refactoring with a few unit tests
1 parent 7693bcd commit 18203c1

File tree

3 files changed

+169
-79
lines changed

3 files changed

+169
-79
lines changed

README.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,19 @@
1818
```
1919
Usage of ./jitic:
2020
-issues string
21-
Message to retrieve the issues from.
21+
Message to retrieve the issues from.
2222
-pass string
23-
JIRA Password.
23+
JIRA Password.
2424
-stdin
25-
If set to true you can stream "-issues" to stdin instead of an argument. If set "-issues" will be ignored.
25+
If set to true you can stream "-issues" to stdin instead of an argument. If set "-issues" will be ignored.
2626
-url string
27-
JIRA instance URL (format: scheme://[username[:password]@]host[:port]/).
27+
JIRA instance URL (format: scheme://[username[:password]@]host[:port]/).
2828
-user string
29-
JIRA Username.
29+
JIRA Username.
3030
-verbose
31-
If activated more information will be written to stdout .
31+
If activated more information will be written to stdout .
3232
-version
33-
Outputs the version number and exits.
33+
Outputs the version number and exits.
3434
```
3535

3636
### Examples
@@ -52,8 +52,8 @@ Exit code: 0
5252
Check if a fake issue MESOS-123456 exists in [https://issues.apache.org/jira/](https://issues.apache.org/jira/) from parameter:
5353

5454
```bash
55-
$ ./jitic -url="https://issues.apache.org/jira/" -issues="MESOS-123456 - Not existing issue" -verbose
56-
2015/09/03 16:25:33 Issue MESOS-123456: 404 Not Found
55+
$ ./jitic -url="https://issues.apache.org/jira/" -issues="MESOS-123456 - Not existing issue" -verbose; echo "Exit code: $?"
56+
2017/11/28 15:41:26 JIRA Request for issue MESOS-123456 returned 404 Not Found (404)
5757
```
5858

5959
## Use cases

main.go

+89-69
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,14 @@ import (
88
"log"
99
"os"
1010
"regexp"
11+
"strings"
1112

1213
"gopkg.in/andygrunwald/go-jira.v1"
1314
)
1415

1516
const (
16-
// Version of jitic
17-
Version = "0.2.2"
18-
)
19-
20-
var (
21-
logger *log.Logger
17+
// Version reflects the version of jitic
18+
Version = "1.0.0"
2219
)
2320

2421
func main() {
@@ -33,90 +30,51 @@ func main() {
3330
)
3431
flag.Parse()
3532

36-
// Set logger (throw messages away)
37-
logger = log.New(ioutil.Discard, "", log.LstdFlags)
33+
// Set logger (throw messages away).
34+
// In a verbose mode we output the messages to stdout
35+
logger := log.New(ioutil.Discard, "", log.LstdFlags)
3836
if *flagVerbose {
3937
logger = log.New(os.Stdout, "", log.LstdFlags)
4038
}
4139

42-
// Output the version and exit
40+
// Output the version, exit
4341
if *flagVersion {
4442
fmt.Printf("jitic v%s\n", Version)
4543
os.Exit(0)
4644
}
4745

48-
// If we don`t get a JIRA instance, exit
46+
// If we don`t have a JIRA instance URL, exit
4947
if len(*jiraURL) <= 0 {
5048
logger.Fatal("No JIRA Instance provided. Please set the URL of the JIRA instance by -url parameter.")
5149
}
5250

53-
// Collect all issue keys
54-
var issues []string
55-
if len(*issueMessage) > 0 {
56-
issues = GetIssuesOutOfMessage(*issueMessage)
57-
}
51+
issueText := getTextToAnalyze(*issueMessage, *inputStdin)
52+
issues := getIssuesOutOfMessage(issueText)
5853

59-
// If we don`t get any issue, we will just exit here.
60-
if *inputStdin == false && len(issues) == 0 {
61-
logger.Fatal("No JIRA-Issue(s) found.")
54+
// If we don`t get any issues, exit
55+
if len(issues) == 0 {
56+
logger.Fatalf("No JIRA-Issue(s) found in text '%s'.", issueText)
6257
}
6358

64-
// Get the JIRA client
65-
jiraInstance, err := jira.NewClient(nil, *jiraURL)
59+
jiraClient, err := getJIRAClient(*jiraURL, *jiraUsername, *jiraPassword)
6660
if err != nil {
67-
logger.Fatalf("JIRA client can`t be initialized: %s", err)
61+
logger.Fatal(err)
6862
}
6963

70-
// Only provide authentification if a username and password was applied
71-
if len(*jiraUsername) > 0 && len(*jiraPassword) > 0 {
72-
ok, err := jiraInstance.Authentication.AcquireSessionCookie(*jiraUsername, *jiraPassword)
73-
if ok == false || err != nil {
74-
logger.Fatalf("jitic can`t authentificate user %s against the JIRA instance %s: %s", *jiraUsername, *jiraURL, err)
64+
// Loop over all issues and check if they are correct / valid
65+
for _, issueFromUser := range issues {
66+
err := checkIfIssue(issueFromUser, jiraClient)
67+
if err != nil {
68+
logger.Fatal(err)
7569
}
7670
}
7771

78-
// If the issues will be applied by argument
79-
if *inputStdin == false {
80-
IssueLoop(issues, jiraInstance)
81-
}
82-
83-
// If the issues will be applied by stdin
84-
if *inputStdin {
85-
ReadIssuesFromStdin(jiraInstance)
86-
}
87-
8872
os.Exit(0)
8973
}
9074

91-
// ReadIssuesFromStdin will read content vom standard input and search for JIRA issue keys
92-
// If an issue key was found a check with the incoming jiraInstance will be done.
93-
func ReadIssuesFromStdin(jiraInstance *jira.Client) {
94-
scanner := bufio.NewScanner(os.Stdin)
95-
for scanner.Scan() {
96-
issues := GetIssuesOutOfMessage(scanner.Text())
97-
// If no issue can be found
98-
if len(issues) == 0 {
99-
logger.Fatal("No JIRA-Issue(s) found.")
100-
}
101-
IssueLoop(issues, jiraInstance)
102-
}
103-
}
104-
105-
// IssueLoop will loop over issues and request jiraInstance to check if the issue exists.
106-
func IssueLoop(issues []string, jiraInstance *jira.Client) {
107-
for _, incomingIssue := range issues {
108-
issue, response, err := jiraInstance.Issue.Get(incomingIssue, nil)
109-
if c := response.StatusCode; err != nil || (c < 200 && c > 299) {
110-
logger.Fatalf("Issue %s: %s", incomingIssue, response.Status)
111-
}
112-
if incomingIssue != issue.Key {
113-
logger.Fatalf("Issue %s is not the same as %s (provided by JIRA)", incomingIssue, issue.Key)
114-
}
115-
}
116-
}
117-
118-
// GetIssuesOutOfMessage will retrieve all JIRA issue keys out of a text.
75+
// getIssuesOutOfMessage will retrieve all JIRA issue keys out of a text.
11976
// A text can be everything, but a use case is e.g. a commit message.
77+
//
12078
// Example:
12179
// Text: WEB-22861 remove authentication prod build for now
12280
// Result: WEB-22861
@@ -126,14 +84,11 @@ func IssueLoop(issues []string, jiraInstance *jira.Client) {
12684
//
12785
// @link https://confluence.atlassian.com/display/STASHKB/Integrating+with+custom+JIRA+issue+key
12886
// @link https://answers.atlassian.com/questions/325865/regex-pattern-to-match-jira-issue-key
129-
func GetIssuesOutOfMessage(issueMessage string) []string {
130-
// Normally i would use
131-
// ((?<!([A-Z]{1,10})-?)[A-Z]+-\d+)
132-
// See http://stackoverflow.com/questions/26771592/negative-look-ahead-go-regular-expressions
87+
func getIssuesOutOfMessage(message string) []string {
13388
var issues []string
13489
re := regexp.MustCompile("(?i)([A-Z]+)-(\\d+)")
13590

136-
parts := re.FindAllStringSubmatch(issueMessage, -1)
91+
parts := re.FindAllStringSubmatch(message, -1)
13792
for _, v := range parts {
13893
// If the issue number > 0 (to avoid matches for PSR-0)
13994
if v[2] > "0" {
@@ -143,3 +98,68 @@ func GetIssuesOutOfMessage(issueMessage string) []string {
14398

14499
return issues
145100
}
101+
102+
// checkIfIssue checks if issue exists in the JIRA instance.
103+
// If not an error will be returned.
104+
func checkIfIssue(issue string, jiraClient *jira.Client) error {
105+
JIRAIssue, resp, err := jiraClient.Issue.Get(issue, nil)
106+
if c := resp.StatusCode; err != nil || (c < 200 || c > 299) {
107+
return fmt.Errorf("JIRA Request for issue %s returned %s (%d)", issue, resp.Status, resp.StatusCode)
108+
}
109+
110+
// Make issues uppercase to be able to compare Web-1234 with WEB-1234
111+
upperIssue := strings.ToUpper(issue)
112+
upperJIRAIssue := strings.ToUpper(JIRAIssue.Key)
113+
if upperIssue != upperJIRAIssue {
114+
return fmt.Errorf("Issue %s is not the same as %s (provided by JIRA)", upperIssue, upperJIRAIssue)
115+
}
116+
117+
return nil
118+
}
119+
120+
// getJIRAClient will return a valid JIRA api client.
121+
func getJIRAClient(instanceURL, username, password string) (*jira.Client, error) {
122+
client, err := jira.NewClient(nil, instanceURL)
123+
if err != nil {
124+
return nil, fmt.Errorf("JIRA client can`t be initialized: %s", err)
125+
}
126+
127+
// Only provide authentification if a username and password was applied
128+
if len(username) > 0 && len(password) > 0 {
129+
ok, err := client.Authentication.AcquireSessionCookie(username, password)
130+
if ok == false || err != nil {
131+
return nil, fmt.Errorf("jitic can`t authentificate user %s against the JIRA instance %s: %s", username, instanceURL, err)
132+
}
133+
}
134+
135+
return client, nil
136+
}
137+
138+
// readStdin will read content from stdin and return the content.
139+
func readStdin() string {
140+
var text string
141+
scanner := bufio.NewScanner(os.Stdin)
142+
for scanner.Scan() {
143+
text += scanner.Text()
144+
}
145+
146+
return text
147+
}
148+
149+
// getTextToAnalyze will return the text jitic should analyze.
150+
// From command line argument or/and stdin
151+
func getTextToAnalyze(argText string, inputStdin bool) string {
152+
var text string
153+
154+
// We get a message via cmd argument
155+
if len(argText) > 0 {
156+
text = argText
157+
}
158+
159+
// If stdin is activated
160+
if inputStdin {
161+
text += readStdin()
162+
}
163+
164+
return text
165+
}

main_test.go

+71-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,59 @@
11
package main
22

33
import (
4+
"net/http"
5+
"net/http/httptest"
46
"reflect"
57
"testing"
68
)
79

10+
var (
11+
// mux is the HTTP request multiplexer used with the test server.
12+
mux *http.ServeMux
13+
14+
// server is a test HTTP server used to provide mock API responses.
15+
server *httptest.Server
16+
)
17+
18+
// setup sets up a test HTTP server along with a github.Client that is
19+
// configured to talk to that test server. Tests should register handlers on
20+
// mux which provide mock responses for the API method being tested.
21+
func setup() {
22+
// test server
23+
mux = http.NewServeMux()
24+
25+
// We want to ensure that tests catch mistakes where the endpoint URL is
26+
// specified as absolute rather than relative. It only makes a difference
27+
// when there's a non-empty base URL path. So, use that. See issue #752.
28+
apiHandler := http.NewServeMux()
29+
30+
server = httptest.NewServer(apiHandler)
31+
}
32+
33+
// teardown closes the test HTTP server.
34+
func teardown() {
35+
server.Close()
36+
}
37+
38+
func TestGetJIRAClient_IssueNotExists(t *testing.T) {
39+
setup()
40+
defer teardown()
41+
42+
c, err := getJIRAClient(server.URL, "", "")
43+
if err != nil {
44+
t.Errorf("Failed to create a JIRA client: %s", err)
45+
}
46+
47+
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
48+
w.WriteHeader(http.StatusNotFound)
49+
})
50+
51+
err = checkIfIssue("WEB-1234", c)
52+
if err == nil {
53+
t.Error("No error occuered. Expected a 404")
54+
}
55+
}
56+
857
func TestGetIssuesOutOfMessage(t *testing.T) {
958
dataProvider := []struct {
1059
Message string
@@ -16,12 +65,33 @@ func TestGetIssuesOutOfMessage(t *testing.T) {
1665
{"[SCC-27] Replace deprecated autoloader strategy PSR-0 with PSR-4", []string{"SCC-27", "PSR-4"}},
1766
{"WeB-4711 sys-1234 PRD-5678 remove authentication prod build for now", []string{"WeB-4711", "sys-1234", "PRD-5678"}},
1867
{"TASKLESS: Removes duplicated comment code.", nil},
68+
{"This is a commit message and we applied the PHP standard PSR-0 to the codebase", nil},
1969
}
2070

2171
for _, data := range dataProvider {
22-
res := GetIssuesOutOfMessage(data.Message)
72+
res := getIssuesOutOfMessage(data.Message)
2373
if reflect.DeepEqual(data.Result, res) == false {
2474
t.Errorf("Test failed, expected: '%+v' (%d), got: '%+v' (%d)", data.Result, len(data.Result), res, len(res))
2575
}
2676
}
2777
}
78+
79+
func TestGetTextToAnalyze(t *testing.T) {
80+
dataProvider := []struct {
81+
Message string
82+
Stdin bool
83+
Result string
84+
}{
85+
{"", false, ""},
86+
{"From arguments without stdin", false, "From arguments without stdin"},
87+
{"From arguments with stdin", true, "From arguments with stdin"},
88+
// TODO: Test stdin
89+
}
90+
91+
for _, data := range dataProvider {
92+
text := getTextToAnalyze(data.Message, data.Stdin)
93+
if text != data.Result {
94+
t.Errorf("Test failed, expected: '%+v', got: '%+v'", data.Result, text)
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)