github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/reporting/github_test.go (about) 1 //go:build unit 2 // +build unit 3 4 package reporting 5 6 import ( 7 "context" 8 "fmt" 9 "testing" 10 11 "github.com/google/go-github/v45/github" 12 "github.com/stretchr/testify/assert" 13 ) 14 15 type scanReportlMock struct { 16 markdown []byte 17 text string 18 title string 19 failToMarkdown bool 20 } 21 22 func (s *scanReportlMock) Title() string { 23 return s.title 24 } 25 26 func (s *scanReportlMock) ToMarkdown() ([]byte, error) { 27 if s.failToMarkdown { 28 return s.markdown, fmt.Errorf("toMarkdown failure") 29 } 30 return s.markdown, nil 31 } 32 33 func (s *scanReportlMock) ToTxt() string { 34 return s.text 35 } 36 37 type ghServicesMock struct { 38 issues []*github.Issue 39 createError error 40 comment *github.IssueComment 41 createCommmentNumber int 42 createCommentError error 43 editError error 44 editNumber int 45 editRequest *github.IssueRequest 46 searchError error 47 searchOpts *github.SearchOptions 48 searchQuery string 49 searchResult []*github.Issue 50 } 51 52 func (g *ghServicesMock) Create(ctx context.Context, owner string, repo string, issueRequest *github.IssueRequest) (*github.Issue, *github.Response, error) { 53 if g.issues == nil { 54 g.issues = []*github.Issue{} 55 } 56 number := len(g.issues) + 1 57 id := int64(number) 58 assignees := []*github.User{} 59 if issueRequest.Assignees != nil { 60 for _, userName := range *issueRequest.Assignees { 61 // cannot use userName directly since variable is re-used in the loop and thus name in assignees would be pointing to final value only. 62 user := userName 63 assignees = append(assignees, &github.User{Name: &user}) 64 } 65 } 66 67 theIssue := github.Issue{ 68 ID: &id, 69 Number: &number, 70 Title: issueRequest.Title, 71 Body: issueRequest.Body, 72 Assignees: assignees, 73 } 74 g.issues = append(g.issues, &theIssue) 75 if g.createError != nil { 76 return &theIssue, &github.Response{}, g.createError 77 } 78 return &theIssue, &github.Response{}, nil 79 } 80 81 func (g *ghServicesMock) CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) { 82 g.createCommmentNumber = number 83 g.comment = comment 84 if g.createCommentError != nil { 85 return &github.IssueComment{}, &github.Response{}, g.createCommentError 86 } 87 return &github.IssueComment{}, &github.Response{}, nil 88 } 89 90 func (g *ghServicesMock) Edit(ctx context.Context, owner string, repo string, number int, issueRequest *github.IssueRequest) (*github.Issue, *github.Response, error) { 91 g.editNumber = number 92 g.editRequest = issueRequest 93 if g.editError != nil { 94 return &github.Issue{}, &github.Response{}, g.editError 95 } 96 return &github.Issue{}, &github.Response{}, nil 97 } 98 99 func (g *ghServicesMock) Issues(ctx context.Context, query string, opts *github.SearchOptions) (*github.IssuesSearchResult, *github.Response, error) { 100 g.searchOpts = opts 101 g.searchQuery = query 102 103 if g.searchError != nil { 104 return &github.IssuesSearchResult{Issues: g.searchResult}, &github.Response{}, g.searchError 105 } 106 return &github.IssuesSearchResult{Issues: g.searchResult}, &github.Response{}, nil 107 } 108 109 var ( 110 owner string = "testOwner" 111 repository string = "testRepository" 112 ) 113 114 func TestUploadSingleReport(t *testing.T) { 115 t.Parallel() 116 117 t.Run("success case", func(t *testing.T) { 118 ctx := context.Background() 119 ghMock := ghServicesMock{} 120 s := scanReportlMock{title: "The Title", markdown: []byte("# The Markdown")} 121 gh := GitHub{ 122 Owner: &owner, 123 Repository: &repository, 124 IssueService: &ghMock, 125 SearchService: &ghMock, 126 } 127 128 err := gh.UploadSingleReport(ctx, &s) 129 130 assert.NoError(t, err) 131 assert.Equal(t, s.title, ghMock.issues[0].GetTitle()) 132 assert.Equal(t, string(s.markdown), ghMock.issues[0].GetBody()) 133 }) 134 135 t.Run("error case", func(t *testing.T) { 136 ctx := context.Background() 137 ghMock := ghServicesMock{createError: fmt.Errorf("create failed")} 138 s := scanReportlMock{title: "The Title"} 139 gh := GitHub{ 140 Owner: &owner, 141 Repository: &repository, 142 IssueService: &ghMock, 143 SearchService: &ghMock, 144 } 145 146 err := gh.UploadSingleReport(ctx, &s) 147 148 assert.EqualError(t, err, "failed to upload results for 'The Title' into GitHub issue: failed to create issue: create failed") 149 }) 150 } 151 152 func TestUploadMultipleReports(t *testing.T) { 153 t.Parallel() 154 155 t.Run("success case", func(t *testing.T) { 156 ctx := context.Background() 157 ghMock := ghServicesMock{} 158 s1 := scanReportlMock{title: "The Title 1", markdown: []byte("# The Markdown 1")} 159 s2 := scanReportlMock{title: "The Title 2", markdown: []byte("# The Markdown 2")} 160 s := []IssueDetail{&s1, &s2} 161 gh := GitHub{ 162 Owner: &owner, 163 Repository: &repository, 164 IssueService: &ghMock, 165 SearchService: &ghMock, 166 } 167 168 err := gh.UploadMultipleReports(ctx, &s) 169 170 assert.NoError(t, err) 171 assert.Equal(t, s1.title, ghMock.issues[0].GetTitle()) 172 assert.Equal(t, string(s1.markdown), ghMock.issues[0].GetBody()) 173 assert.Equal(t, s2.title, ghMock.issues[1].GetTitle()) 174 assert.Equal(t, string(s2.markdown), ghMock.issues[1].GetBody()) 175 }) 176 177 t.Run("error case", func(t *testing.T) { 178 ctx := context.Background() 179 ghMock := ghServicesMock{createError: fmt.Errorf("create failed")} 180 s1 := scanReportlMock{title: "The Title 1", markdown: []byte("# The Markdown 1")} 181 s2 := scanReportlMock{title: "The Title 2", markdown: []byte("# The Markdown 2")} 182 s := []IssueDetail{&s1, &s2} 183 gh := GitHub{ 184 Owner: &owner, 185 Repository: &repository, 186 IssueService: &ghMock, 187 SearchService: &ghMock, 188 } 189 190 err := gh.UploadMultipleReports(ctx, &s) 191 192 assert.EqualError(t, err, "failed to upload results for 'The Title 1' into GitHub issue: failed to create issue: create failed") 193 }) 194 } 195 196 func TestCreateIssueOrUpdateIssueComment(t *testing.T) { 197 t.Parallel() 198 199 title := "The Title" 200 assignees := []string{"assignee1", "assignee2"} 201 202 t.Run("success case - new issue", func(t *testing.T) { 203 ctx := context.Background() 204 ghMock := ghServicesMock{} 205 gh := GitHub{ 206 Owner: &owner, 207 Repository: &repository, 208 Assignees: &assignees, 209 IssueService: &ghMock, 210 SearchService: &ghMock, 211 } 212 markdown := "# The Markdown" 213 214 err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) 215 216 assert.NoError(t, err) 217 assert.Equal(t, title, ghMock.issues[0].GetTitle()) 218 assert.Equal(t, markdown, ghMock.issues[0].GetBody()) 219 assert.Equal(t, assignees[0], ghMock.issues[0].Assignees[0].GetName()) 220 assert.Equal(t, assignees[1], ghMock.issues[0].Assignees[1].GetName()) 221 }) 222 223 t.Run("success case - update issue", func(t *testing.T) { 224 ctx := context.Background() 225 number := 1 226 state := "open" 227 title := "The Title" 228 body := "the body of the issue" 229 issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} 230 ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}} 231 gh := GitHub{ 232 Owner: &owner, 233 Repository: &repository, 234 IssueService: &ghMock, 235 SearchService: &ghMock, 236 } 237 markdown := "# The Markdown" 238 239 err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) 240 assert.NoError(t, err) 241 assert.Equal(t, number, ghMock.editNumber) 242 assert.Equal(t, markdown, ghMock.editRequest.GetBody()) 243 assert.Equal(t, number, ghMock.createCommmentNumber) 244 assert.Equal(t, "issue content has been updated", ghMock.comment.GetBody()) 245 }) 246 247 t.Run("success case - no update", func(t *testing.T) { 248 ctx := context.Background() 249 number := 1 250 state := "open" 251 title := "The Title" 252 body := "the body of the issue" 253 issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} 254 ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}} 255 gh := GitHub{ 256 Owner: &owner, 257 Repository: &repository, 258 IssueService: &ghMock, 259 SearchService: &ghMock, 260 } 261 markdown := "the body of the issue" 262 263 err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) 264 assert.NoError(t, err) 265 assert.Nil(t, ghMock.editRequest) 266 assert.Nil(t, ghMock.comment) 267 }) 268 269 t.Run("error case - lookup failed", func(t *testing.T) { 270 ctx := context.Background() 271 ghMock := ghServicesMock{searchError: fmt.Errorf("search failed")} 272 gh := GitHub{ 273 Owner: &owner, 274 Repository: &repository, 275 IssueService: &ghMock, 276 SearchService: &ghMock, 277 } 278 markdown := "# The Markdown" 279 280 err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) 281 282 assert.EqualError(t, err, "error when looking up issue: error occurred when looking for existing issue: search failed") 283 }) 284 285 t.Run("error case - issue creation failed", func(t *testing.T) { 286 ctx := context.Background() 287 ghMock := ghServicesMock{createError: fmt.Errorf("creation failed")} 288 gh := GitHub{ 289 Owner: &owner, 290 Repository: &repository, 291 IssueService: &ghMock, 292 SearchService: &ghMock, 293 } 294 markdown := "# The Markdown" 295 296 err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) 297 298 assert.EqualError(t, err, "failed to create issue: creation failed") 299 }) 300 301 t.Run("error case - issue editing failed", func(t *testing.T) { 302 ctx := context.Background() 303 number := 1 304 state := "open" 305 title := "The Title" 306 body := "the body of the issue" 307 issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} 308 ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}, editError: fmt.Errorf("edit failed")} 309 gh := GitHub{ 310 Owner: &owner, 311 Repository: &repository, 312 IssueService: &ghMock, 313 SearchService: &ghMock, 314 } 315 markdown := "# The Markdown" 316 317 err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) 318 assert.EqualError(t, err, "failed to edit issue: edit failed") 319 }) 320 321 t.Run("error case - edit comment creation failed", func(t *testing.T) { 322 ctx := context.Background() 323 number := 1 324 state := "open" 325 title := "The Title" 326 body := "the body of the issue" 327 issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} 328 ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}, createCommentError: fmt.Errorf("comment failed")} 329 gh := GitHub{ 330 Owner: &owner, 331 Repository: &repository, 332 IssueService: &ghMock, 333 SearchService: &ghMock, 334 } 335 markdown := "# The Markdown" 336 337 err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown) 338 assert.EqualError(t, err, "failed to create comment: comment failed") 339 340 }) 341 } 342 343 func TestFindExistingIssue(t *testing.T) { 344 t.Parallel() 345 346 t.Run("success case - issue found", func(t *testing.T) { 347 ctx := context.Background() 348 number := 1 349 state := "open" 350 title := "The Title" 351 body := "the body of the issue" 352 issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} 353 ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}} 354 gh := GitHub{ 355 Owner: &owner, 356 Repository: &repository, 357 IssueService: &ghMock, 358 SearchService: &ghMock, 359 } 360 361 i, b, err := gh.findExistingIssue(ctx, title) 362 363 assert.NoError(t, err) 364 assert.Equal(t, fmt.Sprintf("is:issue repo:%v/%v in:title %v", *gh.Owner, *gh.Repository, title), ghMock.searchQuery) 365 assert.Equal(t, 1, i) 366 assert.Equal(t, body, b) 367 }) 368 369 t.Run("success case - issue found, reopen", func(t *testing.T) { 370 ctx := context.Background() 371 number := 1 372 state := "closed" 373 title := "The Title" 374 body := "the body of the issue" 375 issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body} 376 ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}} 377 gh := GitHub{ 378 Owner: &owner, 379 Repository: &repository, 380 IssueService: &ghMock, 381 SearchService: &ghMock, 382 } 383 384 i, b, err := gh.findExistingIssue(ctx, "The Title") 385 386 assert.NoError(t, err) 387 assert.Equal(t, 1, ghMock.editNumber) 388 assert.Equal(t, "open", ghMock.editRequest.GetState()) 389 assert.Equal(t, 1, i) 390 assert.Equal(t, body, b) 391 }) 392 393 t.Run("success case - no issue found", func(t *testing.T) { 394 ctx := context.Background() 395 ghMock := ghServicesMock{} 396 gh := GitHub{ 397 Owner: &owner, 398 Repository: &repository, 399 IssueService: &ghMock, 400 SearchService: &ghMock, 401 } 402 403 i, body, err := gh.findExistingIssue(ctx, "The Title") 404 405 assert.NoError(t, err) 406 assert.Equal(t, 0, i) 407 assert.Equal(t, "", body) 408 }) 409 410 t.Run("error case - search failed", func(t *testing.T) { 411 ctx := context.Background() 412 ghMock := ghServicesMock{searchError: fmt.Errorf("search failed")} 413 gh := GitHub{ 414 Owner: &owner, 415 Repository: &repository, 416 IssueService: &ghMock, 417 SearchService: &ghMock, 418 } 419 420 i, _, err := gh.findExistingIssue(ctx, "The Title") 421 422 assert.EqualError(t, err, "error occurred when looking for existing issue: search failed") 423 assert.Equal(t, 0, i) 424 }) 425 426 t.Run("error case - reopen failed", func(t *testing.T) { 427 ctx := context.Background() 428 number := 1 429 state := "closed" 430 title := "The Title" 431 issue := github.Issue{Number: &number, State: &state, Title: &title} 432 ghMock := ghServicesMock{editError: fmt.Errorf("reopen failed"), searchResult: []*github.Issue{&issue}} 433 gh := GitHub{ 434 Owner: &owner, 435 Repository: &repository, 436 IssueService: &ghMock, 437 SearchService: &ghMock, 438 } 439 440 _, _, err := gh.findExistingIssue(ctx, "The Title") 441 assert.EqualError(t, err, "failed to re-open issue: reopen failed") 442 }) 443 }