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  }