github.com/friedemannf/reviewdog@v0.14.0/service/github/github_test.go (about)

     1  package github
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"net/url"
     9  	"os"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/google/go-github/v37/github"
    14  	"github.com/kylelemons/godebug/pretty"
    15  	"golang.org/x/oauth2"
    16  
    17  	"github.com/friedemannf/reviewdog"
    18  	"github.com/friedemannf/reviewdog/filter"
    19  	"github.com/friedemannf/reviewdog/proto/rdf"
    20  	"github.com/friedemannf/reviewdog/service/commentutil"
    21  )
    22  
    23  const notokenSkipTestMes = "skipping test (requires actual Personal access tokens. export REVIEWDOG_TEST_GITHUB_API_TOKEN=<GitHub Personal Access Token>)"
    24  
    25  func setupGitHubClient() *github.Client {
    26  	token := os.Getenv("REVIEWDOG_TEST_GITHUB_API_TOKEN")
    27  	if token == "" {
    28  		return nil
    29  	}
    30  	ts := oauth2.StaticTokenSource(
    31  		&oauth2.Token{AccessToken: token},
    32  	)
    33  	tc := oauth2.NewClient(context.TODO(), ts)
    34  	return github.NewClient(tc)
    35  }
    36  
    37  func setupEnvs() (cleanup func()) {
    38  	var cleanEnvs = []string{
    39  		"GITHUB_ACTIONS",
    40  	}
    41  	saveEnvs := make(map[string]string)
    42  	for _, key := range cleanEnvs {
    43  		saveEnvs[key] = os.Getenv(key)
    44  		os.Unsetenv(key)
    45  	}
    46  	return func() {
    47  		for key, value := range saveEnvs {
    48  			os.Setenv(key, value)
    49  		}
    50  	}
    51  }
    52  
    53  func moveToRootDir() {
    54  	os.Chdir("../..")
    55  }
    56  
    57  func TestGitHubPullRequest_Post(t *testing.T) {
    58  	t.Skip("skipping test which post comments actually")
    59  	client := setupGitHubClient()
    60  	if client == nil {
    61  		t.Skip(notokenSkipTestMes)
    62  	}
    63  
    64  	// https://github.com/friedemannf/reviewdog/pull/2
    65  	owner := "haya14busa"
    66  	repo := "reviewdog"
    67  	pr := 2
    68  	sha := "cce89afa9ac5519a7f5b1734db2e3aa776b138a7"
    69  
    70  	g, err := NewGitHubPullRequest(client, owner, repo, pr, sha)
    71  	if err != nil {
    72  		t.Fatal(err)
    73  	}
    74  	comment := &reviewdog.Comment{
    75  		Result: &filter.FilteredDiagnostic{
    76  			Diagnostic: &rdf.Diagnostic{
    77  				Location: &rdf.Location{
    78  					Path: "watchdogs.go",
    79  				},
    80  				Message: "[reviewdog] test",
    81  			},
    82  			InDiffContext: true,
    83  		},
    84  	}
    85  	// https://github.com/friedemannf/reviewdog/pull/2/files#diff-ed1d019a10f54464cfaeaf6a736b7d27L20
    86  	if err := g.Post(context.Background(), comment); err != nil {
    87  		t.Error(err)
    88  	}
    89  	if err := g.Flush(context.Background()); err != nil {
    90  		t.Error(err)
    91  	}
    92  }
    93  
    94  func TestGitHubPullRequest_Diff(t *testing.T) {
    95  	if testing.Short() {
    96  		t.Skip("skipping test which contains actual API requests in short mode")
    97  	}
    98  	client := setupGitHubClient()
    99  	if client == nil {
   100  		t.Skip(notokenSkipTestMes)
   101  	}
   102  
   103  	want := `diff --git a/diff.go b/diff.go
   104  index b380b67..6abc0f1 100644
   105  --- a/diff.go
   106  +++ b/diff.go
   107  @@ -4,6 +4,9 @@ import (
   108   	"os/exec"
   109   )
   110   
   111  +func TestNewExportedFunc() {
   112  +}
   113  +
   114   var _ DiffService = &DiffString{}
   115   
   116   type DiffString struct {
   117  diff --git a/reviewdog.go b/reviewdog.go
   118  index 61450f3..f63f149 100644
   119  --- a/reviewdog.go
   120  +++ b/reviewdog.go
   121  @@ -10,18 +10,18 @@ import (
   122   	"github.com/friedemannf/reviewdog/diff"
   123   )
   124   
   125  +var TestExportedVarWithoutComment = 1
   126  +
   127  +func NewReviewdog(p Parser, c CommentService, d DiffService) *Reviewdog {
   128  +	return &Reviewdog{p: p, c: c, d: d}
   129  +}
   130  +
   131   type Reviewdog struct {
   132   	p Parser
   133   	c CommentService
   134   	d DiffService
   135   }
   136   
   137  -func NewReviewdog(p Parser, c CommentService, d DiffService) *Reviewdog {
   138  -	return &Reviewdog{p: p, c: c, d: d}
   139  -}
   140  -
   141  -// CheckResult represents a checked result of static analysis tools.
   142  -// :h error-file-format
   143   type CheckResult struct {
   144   	Path    string   // file path
   145   	Lnum    int      // line number
   146  `
   147  
   148  	// https://github.com/friedemannf/reviewdog/pull/2
   149  	owner := "haya14busa"
   150  	repo := "reviewdog"
   151  	pr := 2
   152  	g, err := NewGitHubPullRequest(client, owner, repo, pr, "")
   153  	if err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	b, err := g.Diff(context.Background())
   157  	if err != nil {
   158  		t.Fatal(err)
   159  	}
   160  	if got := string(b); got != want {
   161  		t.Errorf("got:\n%v\nwant:\n%v", got, want)
   162  	}
   163  }
   164  
   165  func TestGitHubPullRequest_comment(t *testing.T) {
   166  	if testing.Short() {
   167  		t.Skip("skipping test which contains actual API requests in short mode")
   168  	}
   169  	client := setupGitHubClient()
   170  	if client == nil {
   171  		t.Skip(notokenSkipTestMes)
   172  	}
   173  	// https://github.com/friedemannf/reviewdog/pull/2
   174  	owner := "haya14busa"
   175  	repo := "reviewdog"
   176  	pr := 2
   177  	g, err := NewGitHubPullRequest(client, owner, repo, pr, "")
   178  	if err != nil {
   179  		t.Fatal(err)
   180  	}
   181  	comments, err := g.comment(context.Background())
   182  	if err != nil {
   183  		t.Fatal(err)
   184  	}
   185  	for _, c := range comments {
   186  		t.Log("---")
   187  		t.Log(*c.Body)
   188  		t.Log(*c.Path)
   189  		if c.Position != nil {
   190  			t.Log(*c.Position)
   191  		}
   192  		t.Log(*c.CommitID)
   193  	}
   194  }
   195  
   196  func TestGitHubPullRequest_Post_Flush_review_api(t *testing.T) {
   197  	cwd, _ := os.Getwd()
   198  	defer os.Chdir(cwd)
   199  	moveToRootDir()
   200  	defer setupEnvs()()
   201  
   202  	listCommentsAPICalled := 0
   203  	postCommentsAPICalled := 0
   204  	mux := http.NewServeMux()
   205  	mux.HandleFunc("/repos/o/r/pulls/14/comments", func(w http.ResponseWriter, r *http.Request) {
   206  		listCommentsAPICalled++
   207  		if r.Method != http.MethodGet {
   208  			t.Errorf("unexpected access: %v %v", r.Method, r.URL)
   209  		}
   210  		switch r.URL.Query().Get("page") {
   211  		default:
   212  			cs := []*github.PullRequestComment{
   213  				{
   214  					Path: github.String("reviewdog.go"),
   215  					Line: github.Int(2),
   216  					Body: github.String(commentutil.BodyPrefix + "already commented"),
   217  				},
   218  			}
   219  			w.Header().Add("Link", `<https://api.github.com/repos/o/r/pulls/14/comments?page=2>; rel="next"`)
   220  			if err := json.NewEncoder(w).Encode(cs); err != nil {
   221  				t.Fatal(err)
   222  			}
   223  		case "2":
   224  			cs := []*github.PullRequestComment{
   225  				{
   226  					Path: github.String("reviewdog.go"),
   227  					Line: github.Int(15),
   228  					Body: github.String(commentutil.BodyPrefix + "already commented 2"),
   229  				},
   230  				{
   231  					Path:      github.String("reviewdog.go"),
   232  					StartLine: github.Int(15),
   233  					Line:      github.Int(16),
   234  					Body:      github.String(commentutil.BodyPrefix + "multiline existing comment"),
   235  				},
   236  				{
   237  					Path:      github.String("reviewdog.go"),
   238  					StartLine: github.Int(15),
   239  					Line:      github.Int(17),
   240  					Body:      github.String(commentutil.BodyPrefix + "multiline existing comment (line-break)"),
   241  				},
   242  			}
   243  			if err := json.NewEncoder(w).Encode(cs); err != nil {
   244  				t.Fatal(err)
   245  			}
   246  		}
   247  	})
   248  	mux.HandleFunc("/repos/o/r/pulls/14/reviews", func(w http.ResponseWriter, r *http.Request) {
   249  		postCommentsAPICalled++
   250  		if r.Method != http.MethodPost {
   251  			t.Errorf("unexpected access: %v %v", r.Method, r.URL)
   252  		}
   253  		var req github.PullRequestReviewRequest
   254  		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
   255  			t.Error(err)
   256  		}
   257  		if *req.Event != "COMMENT" {
   258  			t.Errorf("PullRequestReviewRequest.Event = %v, want COMMENT", *req.Event)
   259  		}
   260  		if req.Body != nil && *req.Body != "" {
   261  			t.Errorf("PullRequestReviewRequest.Body = %v, want empty", *req.Body)
   262  		}
   263  		if *req.CommitID != "sha" {
   264  			t.Errorf("PullRequestReviewRequest.Body = %v, want empty", *req.Body)
   265  		}
   266  		want := []*github.DraftReviewComment{
   267  			{
   268  				Path: github.String("reviewdog.go"),
   269  				Side: github.String("RIGHT"),
   270  				Line: github.Int(15),
   271  				Body: github.String(commentutil.BodyPrefix + "new comment"),
   272  			},
   273  			{
   274  				Path:      github.String("reviewdog.go"),
   275  				Side:      github.String("RIGHT"),
   276  				StartSide: github.String("RIGHT"),
   277  				StartLine: github.Int(15),
   278  				Line:      github.Int(16),
   279  				Body:      github.String(commentutil.BodyPrefix + "multiline new comment"),
   280  			},
   281  			{
   282  				Path:      github.String("reviewdog.go"),
   283  				Side:      github.String("RIGHT"),
   284  				StartSide: github.String("RIGHT"),
   285  				StartLine: github.Int(15),
   286  				Line:      github.Int(16),
   287  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   288  					"multiline suggestion comment",
   289  					"```suggestion",
   290  					"line1",
   291  					"line2",
   292  					"line3",
   293  					"```",
   294  				}, "\n") + "\n"),
   295  			},
   296  			{
   297  				Path: github.String("reviewdog.go"),
   298  				Side: github.String("RIGHT"),
   299  				Line: github.Int(15),
   300  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   301  					"singleline suggestion comment",
   302  					"```suggestion",
   303  					"line1",
   304  					"line2",
   305  					"```",
   306  				}, "\n") + "\n"),
   307  			},
   308  			{
   309  				Path:      github.String("reviewdog.go"),
   310  				Side:      github.String("RIGHT"),
   311  				StartSide: github.String("RIGHT"),
   312  				StartLine: github.Int(15),
   313  				Line:      github.Int(16),
   314  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   315  					"invalid lines suggestion comment",
   316  					invalidSuggestionPre + "GitHub comment range and suggestion line range must be same. L15-L16 v.s. L16-L17" + invalidSuggestionPost,
   317  				}, "\n") + "\n"),
   318  			},
   319  			{
   320  				Path:      github.String("reviewdog.go"),
   321  				Side:      github.String("RIGHT"),
   322  				StartSide: github.String("RIGHT"),
   323  				StartLine: github.Int(14),
   324  				Line:      github.Int(16),
   325  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   326  					"Use suggestion range as GitHub comment range if the suggestion is in diff context",
   327  					"```suggestion",
   328  					"line1",
   329  					"line2",
   330  					"line3",
   331  					"```",
   332  				}, "\n") + "\n"),
   333  			},
   334  			{
   335  				Path:      github.String("reviewdog.go"),
   336  				Side:      github.String("RIGHT"),
   337  				StartSide: github.String("RIGHT"),
   338  				StartLine: github.Int(14),
   339  				Line:      github.Int(16),
   340  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   341  					"Partially invalid suggestions",
   342  					"```suggestion",
   343  					"line1",
   344  					"line2",
   345  					"line3",
   346  					"```",
   347  					invalidSuggestionPre + "GitHub comment range and suggestion line range must be same. L14-L16 v.s. L14-L14" + invalidSuggestionPost,
   348  				}, "\n") + "\n"),
   349  			},
   350  			{
   351  				Path:      github.String("reviewdog.go"),
   352  				Side:      github.String("RIGHT"),
   353  				StartSide: github.String("RIGHT"),
   354  				StartLine: github.Int(15),
   355  				Line:      github.Int(16),
   356  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   357  					"non-line based suggestion comment (no source lines)",
   358  					invalidSuggestionPre + "source lines are not available" + invalidSuggestionPost,
   359  				}, "\n") + "\n"),
   360  			},
   361  			{
   362  				Path: github.String("reviewdog.go"),
   363  				Side: github.String("RIGHT"),
   364  				Line: github.Int(15),
   365  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   366  					"range suggestion (single line)",
   367  					"```suggestion",
   368  					"haya14busa",
   369  					"```",
   370  				}, "\n") + "\n"),
   371  			},
   372  			{
   373  				Path:      github.String("reviewdog.go"),
   374  				Side:      github.String("RIGHT"),
   375  				StartSide: github.String("RIGHT"),
   376  				StartLine: github.Int(15),
   377  				Line:      github.Int(16),
   378  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   379  					"range suggestion (multi-line)",
   380  					"```suggestion",
   381  					"haya14busa (multi-line)",
   382  					"```",
   383  				}, "\n") + "\n"),
   384  			},
   385  			{
   386  				Path:      github.String("reviewdog.go"),
   387  				Side:      github.String("RIGHT"),
   388  				StartSide: github.String("RIGHT"),
   389  				StartLine: github.Int(15),
   390  				Line:      github.Int(17),
   391  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   392  					"range suggestion (line-break, remove)",
   393  					"```suggestion",
   394  					"line 15 (content at line 15)",
   395  					"```",
   396  				}, "\n") + "\n"),
   397  			},
   398  			{
   399  				Path: github.String("reviewdog.go"),
   400  				Side: github.String("RIGHT"),
   401  				Line: github.Int(15),
   402  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   403  					"range suggestion (insert)",
   404  					"```suggestion",
   405  					"haya14busa",
   406  					"```",
   407  				}, "\n") + "\n"),
   408  			},
   409  			{
   410  				Path: github.String("reviewdog.go"),
   411  				Side: github.String("RIGHT"),
   412  				Line: github.Int(15),
   413  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   414  					"multiple suggestions",
   415  					"```suggestion",
   416  					"haya1busa",
   417  					"```",
   418  					"```suggestion",
   419  					"haya4busa",
   420  					"```",
   421  					"```suggestion",
   422  					"haya14busa",
   423  					"```",
   424  				}, "\n") + "\n"),
   425  			},
   426  			{
   427  				Path: github.String("reviewdog.go"),
   428  				Side: github.String("RIGHT"),
   429  				Line: github.Int(15),
   430  				Body: github.String(commentutil.BodyPrefix + strings.Join([]string{
   431  					"range suggestion with start only location",
   432  					"```suggestion",
   433  					"haya14busa",
   434  					"```",
   435  				}, "\n") + "\n"),
   436  			},
   437  		}
   438  		if diff := pretty.Compare(want, req.Comments); diff != "" {
   439  			t.Errorf("req.Comments diff: (-got +want)\n%s", diff)
   440  		}
   441  	})
   442  	ts := httptest.NewServer(mux)
   443  	defer ts.Close()
   444  
   445  	cli := github.NewClient(nil)
   446  	cli.BaseURL, _ = url.Parse(ts.URL + "/")
   447  	g, err := NewGitHubPullRequest(cli, "o", "r", 14, "sha")
   448  	if err != nil {
   449  		t.Fatal(err)
   450  	}
   451  	comments := []*reviewdog.Comment{
   452  		{
   453  			Result: &filter.FilteredDiagnostic{
   454  				Diagnostic: &rdf.Diagnostic{
   455  					Location: &rdf.Location{
   456  						Path: "reviewdog.go",
   457  						Range: &rdf.Range{
   458  							Start: &rdf.Position{
   459  								Line: 2,
   460  							},
   461  						},
   462  					},
   463  					Message: "already commented",
   464  				},
   465  				InDiffContext: true,
   466  			},
   467  		},
   468  		{
   469  			Result: &filter.FilteredDiagnostic{
   470  				Diagnostic: &rdf.Diagnostic{
   471  					Location: &rdf.Location{
   472  						Path: "reviewdog.go",
   473  						Range: &rdf.Range{
   474  							Start: &rdf.Position{
   475  								Line: 15,
   476  							},
   477  						},
   478  					},
   479  					Message: "already commented 2",
   480  				},
   481  				InDiffContext: true,
   482  			},
   483  		},
   484  		{
   485  			Result: &filter.FilteredDiagnostic{
   486  				Diagnostic: &rdf.Diagnostic{
   487  					Location: &rdf.Location{
   488  						Path: "reviewdog.go",
   489  						Range: &rdf.Range{
   490  							Start: &rdf.Position{
   491  								Line: 15,
   492  							},
   493  						},
   494  					},
   495  					Message: "new comment",
   496  				},
   497  				InDiffContext: true,
   498  			},
   499  		},
   500  		{
   501  			Result: &filter.FilteredDiagnostic{
   502  				Diagnostic: &rdf.Diagnostic{
   503  					Location: &rdf.Location{
   504  						Path: "reviewdog.go",
   505  						Range: &rdf.Range{
   506  							Start: &rdf.Position{
   507  								Line: 15,
   508  							},
   509  							End: &rdf.Position{
   510  								Line: 16,
   511  							},
   512  						},
   513  					},
   514  					Message: "multiline existing comment",
   515  				},
   516  				InDiffContext: true,
   517  			},
   518  		},
   519  		{
   520  			Result: &filter.FilteredDiagnostic{
   521  				Diagnostic: &rdf.Diagnostic{
   522  					Location: &rdf.Location{
   523  						Path: "reviewdog.go",
   524  						Range: &rdf.Range{
   525  							Start: &rdf.Position{
   526  								Line:   15,
   527  								Column: 1,
   528  							},
   529  							End: &rdf.Position{
   530  								Line:   17,
   531  								Column: 1,
   532  							},
   533  						},
   534  					},
   535  					Message: "multiline existing comment (line-break)",
   536  				},
   537  				InDiffContext: true,
   538  			},
   539  		},
   540  		{
   541  			Result: &filter.FilteredDiagnostic{
   542  				Diagnostic: &rdf.Diagnostic{
   543  					Location: &rdf.Location{
   544  						Path: "reviewdog.go",
   545  						Range: &rdf.Range{
   546  							Start: &rdf.Position{
   547  								Line: 15,
   548  							},
   549  							End: &rdf.Position{
   550  								Line: 16,
   551  							},
   552  						},
   553  					},
   554  					Message: "multiline new comment",
   555  				},
   556  				InDiffContext: true,
   557  			},
   558  		},
   559  		{
   560  			Result: &filter.FilteredDiagnostic{
   561  				Diagnostic: &rdf.Diagnostic{
   562  					Location: &rdf.Location{
   563  						Path: "reviewdog.go",
   564  						// No Line
   565  					},
   566  					Message: "should not be reported via GitHub Review API",
   567  				},
   568  			},
   569  		},
   570  		{
   571  			Result: &filter.FilteredDiagnostic{
   572  				Diagnostic: &rdf.Diagnostic{
   573  					Location: &rdf.Location{
   574  						Path: "reviewdog.go",
   575  						Range: &rdf.Range{
   576  							Start: &rdf.Position{
   577  								Line: 15,
   578  							},
   579  							End: &rdf.Position{
   580  								Line: 16,
   581  							},
   582  						},
   583  					},
   584  					Suggestions: []*rdf.Suggestion{
   585  						{
   586  							Range: &rdf.Range{
   587  								Start: &rdf.Position{
   588  									Line: 15,
   589  								},
   590  								End: &rdf.Position{
   591  									Line: 16,
   592  								},
   593  							},
   594  							Text: "line1\nline2\nline3",
   595  						},
   596  					},
   597  					Message: "multiline suggestion comment",
   598  				},
   599  				InDiffContext: true,
   600  			},
   601  		},
   602  		{
   603  			Result: &filter.FilteredDiagnostic{
   604  				Diagnostic: &rdf.Diagnostic{
   605  					Location: &rdf.Location{
   606  						Path: "reviewdog.go",
   607  						Range: &rdf.Range{
   608  							Start: &rdf.Position{
   609  								Line: 15,
   610  							},
   611  						},
   612  					},
   613  					Suggestions: []*rdf.Suggestion{
   614  						{
   615  							Range: &rdf.Range{
   616  								Start: &rdf.Position{
   617  									Line: 15,
   618  								},
   619  							},
   620  							Text: "line1\nline2",
   621  						},
   622  					},
   623  					Message: "singleline suggestion comment",
   624  				},
   625  				InDiffContext: true,
   626  			},
   627  		},
   628  		{
   629  			Result: &filter.FilteredDiagnostic{
   630  				Diagnostic: &rdf.Diagnostic{
   631  					Location: &rdf.Location{
   632  						Path: "reviewdog.go",
   633  						Range: &rdf.Range{
   634  							Start: &rdf.Position{
   635  								Line: 15,
   636  							},
   637  							End: &rdf.Position{
   638  								Line: 16,
   639  							},
   640  						},
   641  					},
   642  					Suggestions: []*rdf.Suggestion{
   643  						{
   644  							Range: &rdf.Range{
   645  								Start: &rdf.Position{
   646  									Line: 16,
   647  								},
   648  								End: &rdf.Position{
   649  									Line: 17,
   650  								},
   651  							},
   652  							Text: "line1\nline2\nline3",
   653  						},
   654  					},
   655  					Message: "invalid lines suggestion comment",
   656  				},
   657  				InDiffContext:                true,
   658  				FirstSuggestionInDiffContext: false,
   659  			},
   660  		},
   661  		{
   662  			Result: &filter.FilteredDiagnostic{
   663  				SourceLines: map[int]string{
   664  					14: "line 14 before",
   665  					15: "line 15 before",
   666  					16: "line 16 before",
   667  				},
   668  				Diagnostic: &rdf.Diagnostic{
   669  					Location: &rdf.Location{
   670  						Path: "reviewdog.go",
   671  						Range: &rdf.Range{
   672  							Start: &rdf.Position{
   673  								Line: 15,
   674  							},
   675  						},
   676  					},
   677  					Suggestions: []*rdf.Suggestion{
   678  						{
   679  							Range: &rdf.Range{
   680  								Start: &rdf.Position{
   681  									Line: 14,
   682  								},
   683  								End: &rdf.Position{
   684  									Line: 16,
   685  								},
   686  							},
   687  							Text: "line1\nline2\nline3",
   688  						},
   689  					},
   690  					Message: "Use suggestion range as GitHub comment range if the suggestion is in diff context",
   691  				},
   692  				InDiffContext:                true,
   693  				FirstSuggestionInDiffContext: true,
   694  			},
   695  		},
   696  		{
   697  			Result: &filter.FilteredDiagnostic{
   698  				SourceLines: map[int]string{
   699  					14: "line 14 before",
   700  					15: "line 15 before",
   701  					16: "line 16 before",
   702  				},
   703  				Diagnostic: &rdf.Diagnostic{
   704  					Location: &rdf.Location{
   705  						Path: "reviewdog.go",
   706  						Range: &rdf.Range{
   707  							Start: &rdf.Position{
   708  								Line: 15,
   709  							},
   710  						},
   711  					},
   712  					Suggestions: []*rdf.Suggestion{
   713  						{
   714  							Range: &rdf.Range{
   715  								Start: &rdf.Position{
   716  									Line: 14,
   717  								},
   718  								End: &rdf.Position{
   719  									Line: 16,
   720  								},
   721  							},
   722  							Text: "line1\nline2\nline3",
   723  						},
   724  						{
   725  							Range: &rdf.Range{
   726  								Start: &rdf.Position{
   727  									Line: 14,
   728  								},
   729  								End: &rdf.Position{
   730  									Line: 14,
   731  								},
   732  							},
   733  							Text: "line1\nline2",
   734  						},
   735  					},
   736  					Message: "Partially invalid suggestions",
   737  				},
   738  				InDiffContext:                true,
   739  				FirstSuggestionInDiffContext: true,
   740  			},
   741  		},
   742  		{
   743  			Result: &filter.FilteredDiagnostic{
   744  				Diagnostic: &rdf.Diagnostic{
   745  					Location: &rdf.Location{
   746  						Path: "reviewdog.go",
   747  						Range: &rdf.Range{
   748  							Start: &rdf.Position{
   749  								Line: 15,
   750  							},
   751  							End: &rdf.Position{
   752  								Line: 16,
   753  							},
   754  						},
   755  					},
   756  					Suggestions: []*rdf.Suggestion{
   757  						{
   758  							Range: &rdf.Range{
   759  								Start: &rdf.Position{
   760  									Line:   15,
   761  									Column: 5,
   762  								},
   763  								End: &rdf.Position{
   764  									Line:   16,
   765  									Column: 7,
   766  								},
   767  							},
   768  							Text: "replacement",
   769  						},
   770  					},
   771  					Message: "non-line based suggestion comment (no source lines)",
   772  				},
   773  				InDiffContext: true,
   774  			},
   775  		},
   776  		{
   777  			Result: &filter.FilteredDiagnostic{
   778  				SourceLines: map[int]string{15: "haya15busa"},
   779  				Diagnostic: &rdf.Diagnostic{
   780  					Location: &rdf.Location{
   781  						Path: "reviewdog.go",
   782  						Range: &rdf.Range{
   783  							Start: &rdf.Position{Line: 15, Column: 5},
   784  							End:   &rdf.Position{Line: 15, Column: 7},
   785  						},
   786  					},
   787  					Suggestions: []*rdf.Suggestion{
   788  						{
   789  							Range: &rdf.Range{
   790  								Start: &rdf.Position{Line: 15, Column: 5},
   791  								End:   &rdf.Position{Line: 15, Column: 7},
   792  							},
   793  							Text: "14",
   794  						},
   795  					},
   796  					Message: "range suggestion (single line)",
   797  				},
   798  				InDiffContext: true,
   799  			},
   800  		},
   801  		{
   802  			Result: &filter.FilteredDiagnostic{
   803  				SourceLines: map[int]string{
   804  					15: "haya???",
   805  					16: "???busa (multi-line)",
   806  				},
   807  				Diagnostic: &rdf.Diagnostic{
   808  					Location: &rdf.Location{
   809  						Path: "reviewdog.go",
   810  						Range: &rdf.Range{
   811  							Start: &rdf.Position{Line: 15, Column: 5},
   812  							End:   &rdf.Position{Line: 16, Column: 4},
   813  						},
   814  					},
   815  					Suggestions: []*rdf.Suggestion{
   816  						{
   817  							Range: &rdf.Range{
   818  								Start: &rdf.Position{Line: 15, Column: 5},
   819  								End:   &rdf.Position{Line: 16, Column: 4},
   820  							},
   821  							Text: "14",
   822  						},
   823  					},
   824  					Message: "range suggestion (multi-line)",
   825  				},
   826  				InDiffContext: true,
   827  			},
   828  		},
   829  		{
   830  			Result: &filter.FilteredDiagnostic{
   831  				SourceLines: map[int]string{
   832  					15: "line 15 xxx",
   833  					16: "line 16",
   834  					17: "(content at line 15)",
   835  				},
   836  				Diagnostic: &rdf.Diagnostic{
   837  					Location: &rdf.Location{
   838  						Path: "reviewdog.go",
   839  						Range: &rdf.Range{
   840  							Start: &rdf.Position{Line: 15, Column: 9},
   841  							End:   &rdf.Position{Line: 17, Column: 1},
   842  						},
   843  					},
   844  					Suggestions: []*rdf.Suggestion{
   845  						{
   846  							Range: &rdf.Range{
   847  								Start: &rdf.Position{Line: 15, Column: 9},
   848  								End:   &rdf.Position{Line: 17, Column: 1},
   849  							},
   850  							Text: "",
   851  						},
   852  					},
   853  					Message: "range suggestion (line-break, remove)",
   854  				},
   855  				InDiffContext: true,
   856  			},
   857  		},
   858  		{
   859  			Result: &filter.FilteredDiagnostic{
   860  				SourceLines: map[int]string{
   861  					15: "hayabusa",
   862  				},
   863  				Diagnostic: &rdf.Diagnostic{
   864  					Location: &rdf.Location{
   865  						Path: "reviewdog.go",
   866  						Range: &rdf.Range{
   867  							Start: &rdf.Position{Line: 15, Column: 5},
   868  							End:   &rdf.Position{Line: 15, Column: 5},
   869  						},
   870  					},
   871  					Suggestions: []*rdf.Suggestion{
   872  						{
   873  							Range: &rdf.Range{
   874  								Start: &rdf.Position{Line: 15, Column: 5},
   875  								End:   &rdf.Position{Line: 15, Column: 5},
   876  							},
   877  							Text: "14",
   878  						},
   879  					},
   880  					Message: "range suggestion (insert)",
   881  				},
   882  				InDiffContext: true,
   883  			},
   884  		},
   885  		{
   886  			Result: &filter.FilteredDiagnostic{
   887  				SourceLines: map[int]string{15: "haya??busa"},
   888  				Diagnostic: &rdf.Diagnostic{
   889  					Location: &rdf.Location{
   890  						Path: "reviewdog.go",
   891  						Range: &rdf.Range{
   892  							Start: &rdf.Position{Line: 15, Column: 5},
   893  							End:   &rdf.Position{Line: 15, Column: 7},
   894  						},
   895  					},
   896  					Suggestions: []*rdf.Suggestion{
   897  						{
   898  							Range: &rdf.Range{
   899  								Start: &rdf.Position{Line: 15, Column: 5},
   900  								End:   &rdf.Position{Line: 15, Column: 7},
   901  							},
   902  							Text: "1",
   903  						},
   904  						{
   905  							Range: &rdf.Range{
   906  								Start: &rdf.Position{Line: 15, Column: 5},
   907  								End:   &rdf.Position{Line: 15, Column: 7},
   908  							},
   909  							Text: "4",
   910  						},
   911  						{
   912  							Range: &rdf.Range{
   913  								Start: &rdf.Position{Line: 15, Column: 5},
   914  								End:   &rdf.Position{Line: 15, Column: 7},
   915  							},
   916  							Text: "14",
   917  						},
   918  					},
   919  					Message: "multiple suggestions",
   920  				},
   921  				InDiffContext: true,
   922  			},
   923  		},
   924  		{
   925  			Result: &filter.FilteredDiagnostic{
   926  				SourceLines: map[int]string{15: "haya15busa"},
   927  				Diagnostic: &rdf.Diagnostic{
   928  					Location: &rdf.Location{
   929  						Path: "reviewdog.go",
   930  						Range: &rdf.Range{
   931  							Start: &rdf.Position{Line: 15, Column: 5},
   932  						},
   933  					},
   934  					Suggestions: []*rdf.Suggestion{
   935  						{
   936  							Range: &rdf.Range{
   937  								Start: &rdf.Position{Line: 15, Column: 5},
   938  								End:   &rdf.Position{Line: 15, Column: 7},
   939  							},
   940  							Text: "14",
   941  						},
   942  					},
   943  					Message: "range suggestion with start only location",
   944  				},
   945  				InDiffContext: true,
   946  			},
   947  		},
   948  	}
   949  	for _, c := range comments {
   950  		if err := g.Post(context.Background(), c); err != nil {
   951  			t.Error(err)
   952  		}
   953  	}
   954  	if err := g.Flush(context.Background()); err != nil {
   955  		t.Error(err)
   956  	}
   957  	if listCommentsAPICalled != 2 {
   958  		t.Errorf("GitHub List PullRequest comments API called %v times, want 2 times", listCommentsAPICalled)
   959  	}
   960  	if postCommentsAPICalled != 1 {
   961  		t.Errorf("GitHub post PullRequest comments API called %v times, want 1 times", postCommentsAPICalled)
   962  	}
   963  }
   964  
   965  func TestGitHubPullRequest_Post_toomany(t *testing.T) {
   966  	cwd, _ := os.Getwd()
   967  	defer os.Chdir(cwd)
   968  	moveToRootDir()
   969  	defer setupEnvs()()
   970  
   971  	listCommentsAPICalled := 0
   972  	postCommentsAPICalled := 0
   973  
   974  	mux := http.NewServeMux()
   975  	mux.HandleFunc("/repos/o/r/pulls/14/comments", func(w http.ResponseWriter, r *http.Request) {
   976  		listCommentsAPICalled++
   977  		if err := json.NewEncoder(w).Encode([]*github.PullRequestComment{}); err != nil {
   978  			t.Fatal(err)
   979  		}
   980  	})
   981  	mux.HandleFunc("/repos/o/r/pulls/14/reviews", func(w http.ResponseWriter, r *http.Request) {
   982  		postCommentsAPICalled++
   983  		var req github.PullRequestReviewRequest
   984  		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
   985  			t.Error(err)
   986  		}
   987  		if req.GetBody() == "" {
   988  			t.Errorf("PullRequestReviewRequest.Body is empty but want some summary text")
   989  		}
   990  	})
   991  	ts := httptest.NewServer(mux)
   992  	defer ts.Close()
   993  
   994  	cli := github.NewClient(nil)
   995  	cli.BaseURL, _ = url.Parse(ts.URL + "/")
   996  	g, err := NewGitHubPullRequest(cli, "o", "r", 14, "sha")
   997  	if err != nil {
   998  		t.Fatal(err)
   999  	}
  1000  	var comments []*reviewdog.Comment
  1001  	for i := 0; i < 100; i++ {
  1002  		comments = append(comments, &reviewdog.Comment{
  1003  			Result: &filter.FilteredDiagnostic{
  1004  				Diagnostic: &rdf.Diagnostic{
  1005  					Location: &rdf.Location{
  1006  						Path: "reviewdog.go",
  1007  						Range: &rdf.Range{Start: &rdf.Position{
  1008  							Line: int32(i),
  1009  						}},
  1010  					},
  1011  					Message: "comment",
  1012  				},
  1013  				InDiffContext: true,
  1014  			},
  1015  			ToolName: "tool",
  1016  		})
  1017  	}
  1018  	for _, c := range comments {
  1019  		if err := g.Post(context.Background(), c); err != nil {
  1020  			t.Error(err)
  1021  		}
  1022  	}
  1023  	if err := g.Flush(context.Background()); err != nil {
  1024  		t.Error(err)
  1025  	}
  1026  	if want := 1; listCommentsAPICalled != want {
  1027  		t.Errorf("GitHub List PullRequest comments API called %v times, want %d times", listCommentsAPICalled, want)
  1028  	}
  1029  	if want := 1; postCommentsAPICalled != want {
  1030  		t.Errorf("GitHub post PullRequest comments API called %v times, want %d times", postCommentsAPICalled, want)
  1031  	}
  1032  }
  1033  
  1034  func TestGitHubPullRequest_workdir(t *testing.T) {
  1035  	cwd, _ := os.Getwd()
  1036  	defer os.Chdir(cwd)
  1037  	moveToRootDir()
  1038  	defer setupEnvs()()
  1039  
  1040  	g, err := NewGitHubPullRequest(nil, "", "", 0, "")
  1041  	if err != nil {
  1042  		t.Fatal(err)
  1043  	}
  1044  	if g.wd != "" {
  1045  		t.Fatalf("g.wd = %q, want empty", g.wd)
  1046  	}
  1047  	ctx := context.Background()
  1048  	want := "a/b/c"
  1049  	g.Post(ctx, &reviewdog.Comment{Result: &filter.FilteredDiagnostic{
  1050  		Diagnostic: &rdf.Diagnostic{Location: &rdf.Location{Path: want}}}})
  1051  	if got := g.postComments[0].Result.Diagnostic.GetLocation().GetPath(); got != want {
  1052  		t.Errorf("wd=%q path=%q, want %q", g.wd, got, want)
  1053  	}
  1054  
  1055  	subDir := "cmd/"
  1056  	if err := os.Chdir(subDir); err != nil {
  1057  		t.Fatal(err)
  1058  	}
  1059  	g, _ = NewGitHubPullRequest(nil, "", "", 0, "")
  1060  	if g.wd != subDir {
  1061  		t.Fatalf("gitRelWorkdir() = %q, want %q", g.wd, subDir)
  1062  	}
  1063  	path := "a/b/c"
  1064  	wantPath := "cmd/" + path
  1065  	g.Post(ctx, &reviewdog.Comment{Result: &filter.FilteredDiagnostic{
  1066  		Diagnostic: &rdf.Diagnostic{Location: &rdf.Location{Path: want}}}})
  1067  	if got := g.postComments[0].Result.Diagnostic.GetLocation().GetPath(); got != wantPath {
  1068  		t.Errorf("wd=%q path=%q, want %q", g.wd, got, wantPath)
  1069  	}
  1070  }
  1071  
  1072  func TestGitHubPullRequest_Diff_fake(t *testing.T) {
  1073  	apiCalled := 0
  1074  	mux := http.NewServeMux()
  1075  	mux.HandleFunc("/repos/o/r/pulls/14", func(w http.ResponseWriter, r *http.Request) {
  1076  		apiCalled++
  1077  		if r.Method != http.MethodGet {
  1078  			t.Errorf("unexpected access: %v %v", r.Method, r.URL)
  1079  		}
  1080  		if accept := r.Header.Get("Accept"); !strings.Contains(accept, "diff") {
  1081  			t.Errorf("Accept header doesn't contain 'diff': %v", accept)
  1082  		}
  1083  		w.Write([]byte("Pull Request diff"))
  1084  	})
  1085  	ts := httptest.NewServer(mux)
  1086  	defer ts.Close()
  1087  
  1088  	cli := github.NewClient(nil)
  1089  	cli.BaseURL, _ = url.Parse(ts.URL + "/")
  1090  	g, err := NewGitHubPullRequest(cli, "o", "r", 14, "sha")
  1091  	if err != nil {
  1092  		t.Fatal(err)
  1093  	}
  1094  	if _, err := g.Diff(context.Background()); err != nil {
  1095  		t.Fatal(err)
  1096  	}
  1097  	if apiCalled != 1 {
  1098  		t.Errorf("GitHub API should be called once; called %v times", apiCalled)
  1099  	}
  1100  }