golang.org/x/build@v0.0.0-20240506185731-218518f32b70/maintner/github_test.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package maintner
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"os"
    15  	"path/filepath"
    16  	"reflect"
    17  	"sort"
    18  	"strings"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/golang/protobuf/ptypes"
    23  	"github.com/golang/protobuf/ptypes/timestamp"
    24  	"github.com/google/go-github/github"
    25  
    26  	"golang.org/x/build/maintner/maintpb"
    27  )
    28  
    29  func TestParseGithubEvents(t *testing.T) {
    30  	tests := []struct {
    31  		name string                    // test
    32  		j    string                    // JSON from Github API
    33  		e    *GitHubIssueEvent         // in-memory
    34  		p    *maintpb.GithubIssueEvent // on disk
    35  	}{
    36  		{
    37  			name: "labeled",
    38  			j: `{
    39      "id": 998144526,
    40      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/998144526",
    41      "actor": {
    42        "login": "bradfitz",
    43        "id": 2621
    44      },
    45      "event": "labeled",
    46      "commit_id": null,
    47      "commit_url": null,
    48      "created_at": "2017-03-13T22:39:28Z",
    49      "label": {
    50        "name": "enhancement",
    51        "color": "84b6eb"
    52      }
    53    }
    54  `,
    55  			e: &GitHubIssueEvent{
    56  				ID:      998144526,
    57  				Type:    "labeled",
    58  				Created: t3339("2017-03-13T22:39:28Z"),
    59  				Actor: &GitHubUser{
    60  					ID:    2621,
    61  					Login: "bradfitz",
    62  				},
    63  				Label: "enhancement",
    64  			},
    65  			p: &maintpb.GithubIssueEvent{
    66  				Id:        998144526,
    67  				EventType: "labeled",
    68  				ActorId:   2621,
    69  				Created:   p3339("2017-03-13T22:39:28Z"),
    70  				Label:     &maintpb.GithubLabel{Name: "enhancement"},
    71  			},
    72  		},
    73  
    74  		{
    75  			name: "unlabeled",
    76  			j: `{
    77      "id": 998144526,
    78      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/998144526",
    79      "actor": {
    80        "login": "bradfitz",
    81        "id": 2621
    82      },
    83      "event": "unlabeled",
    84      "commit_id": null,
    85      "commit_url": null,
    86      "created_at": "2017-03-13T22:39:28Z",
    87      "label": {
    88        "name": "enhancement",
    89        "color": "84b6eb"
    90      }
    91    }
    92  `,
    93  			e: &GitHubIssueEvent{
    94  				ID:      998144526,
    95  				Type:    "unlabeled",
    96  				Created: t3339("2017-03-13T22:39:28Z"),
    97  				Actor: &GitHubUser{
    98  					ID:    2621,
    99  					Login: "bradfitz",
   100  				},
   101  				Label: "enhancement",
   102  			},
   103  			p: &maintpb.GithubIssueEvent{
   104  				Id:        998144526,
   105  				EventType: "unlabeled",
   106  				ActorId:   2621,
   107  				Created:   p3339("2017-03-13T22:39:28Z"),
   108  				Label:     &maintpb.GithubLabel{Name: "enhancement"},
   109  			},
   110  		},
   111  
   112  		{
   113  			name: "milestoned",
   114  			j: `{
   115      "id": 998144529,
   116      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/998144529",
   117      "actor": {
   118        "login": "bradfitz",
   119        "id": 2621
   120      },
   121      "event": "milestoned",
   122      "commit_id": null,
   123      "commit_url": null,
   124      "created_at": "2017-03-13T22:39:28Z",
   125      "milestone": {
   126        "title": "World Domination"
   127      }}`,
   128  			e: &GitHubIssueEvent{
   129  				ID:      998144529,
   130  				Type:    "milestoned",
   131  				Created: t3339("2017-03-13T22:39:28Z"),
   132  				Actor: &GitHubUser{
   133  					ID:    2621,
   134  					Login: "bradfitz",
   135  				},
   136  				Milestone: "World Domination",
   137  			},
   138  			p: &maintpb.GithubIssueEvent{
   139  				Id:        998144529,
   140  				EventType: "milestoned",
   141  				ActorId:   2621,
   142  				Created:   p3339("2017-03-13T22:39:28Z"),
   143  				Milestone: &maintpb.GithubMilestone{Title: "World Domination"},
   144  			},
   145  		},
   146  
   147  		{
   148  			name: "demilestoned",
   149  			j: `{
   150      "id": 998144529,
   151      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/998144529",
   152      "actor": {
   153        "login": "bradfitz",
   154        "id": 2621
   155      },
   156      "event": "demilestoned",
   157      "commit_id": null,
   158      "commit_url": null,
   159      "created_at": "2017-03-13T22:39:28Z",
   160      "milestone": {
   161        "title": "World Domination"
   162      }}`,
   163  			e: &GitHubIssueEvent{
   164  				ID:      998144529,
   165  				Type:    "demilestoned",
   166  				Created: t3339("2017-03-13T22:39:28Z"),
   167  				Actor: &GitHubUser{
   168  					ID:    2621,
   169  					Login: "bradfitz",
   170  				},
   171  				Milestone: "World Domination",
   172  			},
   173  			p: &maintpb.GithubIssueEvent{
   174  				Id:        998144529,
   175  				EventType: "demilestoned",
   176  				ActorId:   2621,
   177  				Created:   p3339("2017-03-13T22:39:28Z"),
   178  				Milestone: &maintpb.GithubMilestone{Title: "World Domination"},
   179  			},
   180  		},
   181  
   182  		{
   183  			name: "assigned",
   184  			j: `{
   185      "id": 998144530,
   186      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/998144530",
   187      "actor": {
   188        "login": "bradfitz",
   189        "id": 2621
   190      },
   191      "event": "assigned",
   192      "commit_id": null,
   193      "commit_url": null,
   194      "created_at": "2017-03-13T22:39:28Z",
   195      "assignee": {
   196        "login": "bradfitz",
   197        "id": 2621
   198      },
   199      "assigner": {
   200        "login": "bradfitz",
   201        "id": 2621
   202      }}`,
   203  			e: &GitHubIssueEvent{
   204  				ID:      998144530,
   205  				Type:    "assigned",
   206  				Created: t3339("2017-03-13T22:39:28Z"),
   207  				Actor: &GitHubUser{
   208  					ID:    2621,
   209  					Login: "bradfitz",
   210  				},
   211  				Assignee: &GitHubUser{
   212  					ID:    2621,
   213  					Login: "bradfitz",
   214  				},
   215  				Assigner: &GitHubUser{
   216  					ID:    2621,
   217  					Login: "bradfitz",
   218  				},
   219  			},
   220  			p: &maintpb.GithubIssueEvent{
   221  				Id:         998144530,
   222  				EventType:  "assigned",
   223  				ActorId:    2621,
   224  				Created:    p3339("2017-03-13T22:39:28Z"),
   225  				AssigneeId: 2621,
   226  				AssignerId: 2621,
   227  			},
   228  		},
   229  
   230  		{
   231  			name: "unassigned",
   232  			j: `{
   233      "id": 1000077586,
   234      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/1000077586",
   235      "actor": {
   236        "login": "dmitshur",
   237        "id": 1924134
   238      },
   239      "event": "unassigned",
   240      "commit_id": null,
   241      "commit_url": null,
   242      "created_at": "2017-03-15T00:31:42Z",
   243      "assignee": {
   244        "login": "dmitshur",
   245        "id": 1924134
   246      },
   247      "assigner": {
   248        "login": "bradfitz",
   249        "id": 2621
   250      }
   251    }`,
   252  			e: &GitHubIssueEvent{
   253  				ID:      1000077586,
   254  				Type:    "unassigned",
   255  				Created: t3339("2017-03-15T00:31:42Z"),
   256  				Actor: &GitHubUser{
   257  					ID:    1924134,
   258  					Login: "dmitshur",
   259  				},
   260  				Assignee: &GitHubUser{
   261  					ID:    1924134,
   262  					Login: "dmitshur",
   263  				},
   264  				Assigner: &GitHubUser{
   265  					ID:    2621,
   266  					Login: "bradfitz",
   267  				},
   268  			},
   269  			p: &maintpb.GithubIssueEvent{
   270  				Id:         1000077586,
   271  				EventType:  "unassigned",
   272  				ActorId:    1924134,
   273  				Created:    p3339("2017-03-15T00:31:42Z"),
   274  				AssigneeId: 1924134,
   275  				AssignerId: 2621,
   276  			},
   277  		},
   278  
   279  		{
   280  			name: "locked",
   281  			j: `{
   282      "id": 998144646,
   283      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/998144646",
   284      "actor": {
   285        "login": "bradfitz",
   286        "id": 2621
   287      },
   288      "event": "locked",
   289      "commit_id": null,
   290      "commit_url": null,
   291      "created_at": "2017-03-13T22:39:36Z",
   292      "lock_reason": "off-topic"
   293    }`,
   294  			e: &GitHubIssueEvent{
   295  				ID:      998144646,
   296  				Type:    "locked",
   297  				Created: t3339("2017-03-13T22:39:36Z"),
   298  				Actor: &GitHubUser{
   299  					ID:    2621,
   300  					Login: "bradfitz",
   301  				},
   302  			},
   303  			p: &maintpb.GithubIssueEvent{
   304  				Id:        998144646,
   305  				EventType: "locked",
   306  				ActorId:   2621,
   307  				Created:   p3339("2017-03-13T22:39:36Z"),
   308  			},
   309  		},
   310  
   311  		{
   312  			name: "unlocked",
   313  			j: `{
   314      "id": 1000014895,
   315      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/1000014895",
   316      "actor": {
   317        "login": "bradfitz",
   318        "id": 2621
   319      },
   320      "event": "unlocked",
   321      "commit_id": null,
   322      "commit_url": null,
   323      "created_at": "2017-03-14T23:26:21Z"
   324   }`,
   325  			e: &GitHubIssueEvent{
   326  				ID:      1000014895,
   327  				Type:    "unlocked",
   328  				Created: t3339("2017-03-14T23:26:21Z"),
   329  				Actor: &GitHubUser{
   330  					ID:    2621,
   331  					Login: "bradfitz",
   332  				},
   333  			},
   334  			p: &maintpb.GithubIssueEvent{
   335  				Id:        1000014895,
   336  				EventType: "unlocked",
   337  				ActorId:   2621,
   338  				Created:   p3339("2017-03-14T23:26:21Z"),
   339  			},
   340  		},
   341  
   342  		{
   343  			name: "closed",
   344  			j: `  {
   345      "id": 1006040931,
   346      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/1006040931",
   347      "actor": {
   348        "login": "bradfitz",
   349        "id": 2621
   350      },
   351      "event": "closed",
   352      "commit_id": "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   353      "commit_url": "https://api.github.com/repos/bradfitz/go-issue-mirror/commits/e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   354      "created_at": "2017-03-19T23:40:33Z"
   355    }`,
   356  			e: &GitHubIssueEvent{
   357  				ID:      1006040931,
   358  				Type:    "closed",
   359  				Created: t3339("2017-03-19T23:40:33Z"),
   360  				Actor: &GitHubUser{
   361  					ID:    2621,
   362  					Login: "bradfitz",
   363  				},
   364  				CommitID:  "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   365  				CommitURL: "https://api.github.com/repos/bradfitz/go-issue-mirror/commits/e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   366  			},
   367  			p: &maintpb.GithubIssueEvent{
   368  				Id:        1006040931,
   369  				EventType: "closed",
   370  				ActorId:   2621,
   371  				Created:   p3339("2017-03-19T23:40:33Z"),
   372  				Commit: &maintpb.GithubCommit{
   373  					Owner:    "bradfitz",
   374  					Repo:     "go-issue-mirror",
   375  					CommitId: "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   376  				},
   377  			},
   378  		},
   379  
   380  		{
   381  			name: "reopened",
   382  			j: `{
   383      "id": 1000014895,
   384      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/1000014895",
   385      "actor": {
   386        "login": "bradfitz",
   387        "id": 2621
   388      },
   389      "event": "reopened",
   390      "commit_id": null,
   391      "commit_url": null,
   392      "created_at": "2017-03-14T23:26:21Z"
   393   }`,
   394  			e: &GitHubIssueEvent{
   395  				ID:      1000014895,
   396  				Type:    "reopened",
   397  				Created: t3339("2017-03-14T23:26:21Z"),
   398  				Actor: &GitHubUser{
   399  					ID:    2621,
   400  					Login: "bradfitz",
   401  				},
   402  			},
   403  			p: &maintpb.GithubIssueEvent{
   404  				Id:        1000014895,
   405  				EventType: "reopened",
   406  				ActorId:   2621,
   407  				Created:   p3339("2017-03-14T23:26:21Z"),
   408  			},
   409  		},
   410  
   411  		{
   412  			name: "referenced",
   413  			j: `{
   414      "id": 1006040930,
   415      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/1006040930",
   416      "actor": {
   417        "login": "bradfitz",
   418        "id": 2621
   419      },
   420      "event": "referenced",
   421      "commit_id": "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   422      "commit_url": "https://api.github.com/repos/bradfitz/go-issue-mirror/commits/e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   423      "created_at": "2017-03-19T23:40:32Z"
   424    }`,
   425  			e: &GitHubIssueEvent{
   426  				ID:      1006040930,
   427  				Type:    "referenced",
   428  				Created: t3339("2017-03-19T23:40:32Z"),
   429  				Actor: &GitHubUser{
   430  					ID:    2621,
   431  					Login: "bradfitz",
   432  				},
   433  				CommitID:  "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   434  				CommitURL: "https://api.github.com/repos/bradfitz/go-issue-mirror/commits/e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   435  			},
   436  			p: &maintpb.GithubIssueEvent{
   437  				Id:        1006040930,
   438  				EventType: "referenced",
   439  				ActorId:   2621,
   440  				Created:   p3339("2017-03-19T23:40:32Z"),
   441  				Commit: &maintpb.GithubCommit{
   442  					Owner:    "bradfitz",
   443  					Repo:     "go-issue-mirror",
   444  					CommitId: "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   445  				},
   446  			},
   447  		},
   448  
   449  		{
   450  			name: "renamed",
   451  			j: `{
   452      "id": 1006107803,
   453      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/1006107803",
   454      "actor": {
   455        "login": "bradfitz",
   456        "id": 2621
   457      },
   458      "event": "renamed",
   459      "commit_id": null,
   460      "commit_url": null,
   461      "created_at": "2017-03-20T02:53:43Z",
   462      "rename": {
   463        "from": "test-2",
   464        "to": "test-2 new name"
   465      }
   466    }`,
   467  			e: &GitHubIssueEvent{
   468  				ID:      1006107803,
   469  				Type:    "renamed",
   470  				Created: t3339("2017-03-20T02:53:43Z"),
   471  				Actor: &GitHubUser{
   472  					ID:    2621,
   473  					Login: "bradfitz",
   474  				},
   475  				From: "test-2",
   476  				To:   "test-2 new name",
   477  			},
   478  			p: &maintpb.GithubIssueEvent{
   479  				Id:         1006107803,
   480  				EventType:  "renamed",
   481  				ActorId:    2621,
   482  				Created:    p3339("2017-03-20T02:53:43Z"),
   483  				RenameFrom: "test-2",
   484  				RenameTo:   "test-2 new name",
   485  			},
   486  		},
   487  
   488  		{
   489  			name: "Extra Unknown JSON",
   490  			j: `{
   491      "id": 998144526,
   492      "url": "https://api.github.com/repos/bradfitz/go-issue-mirror/issues/events/998144526",
   493      "actor": {
   494        "login": "bradfitz",
   495        "id": 2621
   496      },
   497      "event": "labeled",
   498      "commit_id": null,
   499      "commit_url": null,
   500      "created_at": "2017-03-13T22:39:28Z",
   501      "label": {
   502        "name": "enhancement",
   503        "color": "84b6eb"
   504      },
   505      "random_new_field": "some new thing that GitHub API may add"
   506    }
   507  `,
   508  			e: &GitHubIssueEvent{
   509  				ID:      998144526,
   510  				Type:    "labeled",
   511  				Created: t3339("2017-03-13T22:39:28Z"),
   512  				Actor: &GitHubUser{
   513  					ID:    2621,
   514  					Login: "bradfitz",
   515  				},
   516  				Label:     "enhancement",
   517  				OtherJSON: `{"random_new_field":"some new thing that GitHub API may add"}`,
   518  			},
   519  			p: &maintpb.GithubIssueEvent{
   520  				Id:        998144526,
   521  				EventType: "labeled",
   522  				ActorId:   2621,
   523  				Created:   p3339("2017-03-13T22:39:28Z"),
   524  				Label:     &maintpb.GithubLabel{Name: "enhancement"},
   525  				OtherJson: []byte(`{"random_new_field":"some new thing that GitHub API may add"}`),
   526  			},
   527  		},
   528  	}
   529  
   530  	var eventTypes []string
   531  
   532  	for _, tt := range tests {
   533  		evts, err := parseGithubEvents(strings.NewReader("[" + tt.j + "]"))
   534  		if err != nil {
   535  			t.Errorf("%s: parse JSON: %v", tt.name, err)
   536  			continue
   537  		}
   538  		if len(evts) != 1 {
   539  			t.Errorf("%s: parse JSON = %v entries; want 1", tt.name, len(evts))
   540  			continue
   541  		}
   542  		gote := evts[0]
   543  		if !reflect.DeepEqual(gote, tt.e) {
   544  			t.Errorf("%s: JSON -> githubEvent differs: %v", tt.name, DeepDiff(gote, tt.e))
   545  			continue
   546  		}
   547  		eventTypes = append(eventTypes, gote.Type)
   548  
   549  		gotp := gote.Proto()
   550  		if !reflect.DeepEqual(gotp, tt.p) {
   551  			t.Errorf("%s: githubEvent -> proto differs: %v", tt.name, DeepDiff(gotp, tt.p))
   552  			continue
   553  		}
   554  
   555  		var c Corpus
   556  		c.initGithub()
   557  		c.github.getOrCreateUserID(2621).Login = "bradfitz"
   558  		c.github.getOrCreateUserID(1924134).Login = "dmitshur"
   559  		gr := c.github.getOrCreateRepo("foowner", "bar")
   560  		e2 := gr.newGithubEvent(gotp)
   561  
   562  		if !reflect.DeepEqual(e2, tt.e) {
   563  			t.Errorf("%s: proto -> githubEvent differs: %v", tt.name, DeepDiff(e2, tt.e))
   564  			continue
   565  		}
   566  	}
   567  
   568  	t.Logf("Tested event types: %q", eventTypes)
   569  }
   570  
   571  func TestParseMultipleGithubEvents(t *testing.T) {
   572  	content, err := os.ReadFile(filepath.Join("testdata", "TestParseMultipleGithubEvents.json"))
   573  	if err != nil {
   574  		t.Errorf("error while loading testdata: %s\n", err.Error())
   575  	}
   576  	evts, err := parseGithubEvents(strings.NewReader(string(content)))
   577  	if err != nil {
   578  		t.Errorf("error was not expected: %s\n", err.Error())
   579  	}
   580  	if len(evts) != 7 {
   581  		t.Errorf("there should have been three events. was: %d\n", len(evts))
   582  	}
   583  	lastEvent := evts[len(evts)-1]
   584  	if lastEvent.Type != "closed" {
   585  		t.Errorf("the last event's type should have been closed. was: %s\n", lastEvent.Type)
   586  	}
   587  }
   588  
   589  func TestParseMultipleGithubEventsWithForeach(t *testing.T) {
   590  	issue := &GitHubIssue{
   591  		PullRequest: true,
   592  		events: map[int64]*GitHubIssueEvent{
   593  			0: &GitHubIssueEvent{
   594  				Type: "labelled",
   595  			},
   596  			1: &GitHubIssueEvent{
   597  				Type: "milestone",
   598  			},
   599  			2: &GitHubIssueEvent{
   600  				Type: "closed",
   601  			},
   602  		},
   603  	}
   604  	eventTypes := []string{"closed", "labelled", "milestone"}
   605  	gatheredTypes := make([]string, 0)
   606  	issue.ForeachEvent(func(e *GitHubIssueEvent) error {
   607  		gatheredTypes = append(gatheredTypes, e.Type)
   608  		return nil
   609  	})
   610  	sort.Strings(gatheredTypes)
   611  	if !reflect.DeepEqual(eventTypes, gatheredTypes) {
   612  		t.Fatalf("want event types: %v; got: %v\n", eventTypes, gatheredTypes)
   613  	}
   614  }
   615  
   616  type ClientMock struct {
   617  	err        error
   618  	status     string
   619  	statusCode int
   620  	testdata   string
   621  }
   622  
   623  var timesDoWasCalled = 0
   624  
   625  func (c *ClientMock) Do(req *http.Request) (*http.Response, error) {
   626  	if len(c.testdata) < 1 {
   627  		c.testdata = "TestParseMultipleGithubEvents.json"
   628  	}
   629  	timesDoWasCalled++
   630  	content, _ := os.ReadFile(filepath.Join("testdata", c.testdata))
   631  	headers := make(http.Header, 0)
   632  	t := time.Now()
   633  	var b []byte
   634  	headers["Date"] = []string{string(t.AppendFormat(b, "Mon Jan _2 15:04:05 2006"))}
   635  	return &http.Response{
   636  		Body:       io.NopCloser(bytes.NewReader(content)),
   637  		Status:     c.status,
   638  		StatusCode: c.statusCode,
   639  		Header:     headers,
   640  	}, c.err
   641  }
   642  
   643  type MockLogger struct {
   644  }
   645  
   646  var eventLog = make([]string, 0)
   647  
   648  func (m *MockLogger) Log(mut *maintpb.Mutation) error {
   649  	for _, e := range mut.GithubIssue.Event {
   650  		eventLog = append(eventLog, e.EventType)
   651  	}
   652  	return nil
   653  }
   654  
   655  func TestSyncEvents(t *testing.T) {
   656  	var c Corpus
   657  	c.initGithub()
   658  	c.mutationLogger = &MockLogger{}
   659  	gr := c.github.getOrCreateRepo("foowner", "bar")
   660  	issue := &GitHubIssue{
   661  		ID:          1001,
   662  		PullRequest: true,
   663  		events:      map[int64]*GitHubIssueEvent{},
   664  	}
   665  	gr.issues = map[int32]*GitHubIssue{
   666  		1001: issue,
   667  	}
   668  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   669  		rw.Write([]byte("OK"))
   670  	}))
   671  	defer server.Close()
   672  	p := &githubRepoPoller{
   673  		c:             &c,
   674  		token:         "foobar",
   675  		gr:            gr,
   676  		githubDirect:  github.NewClient(server.Client()),
   677  		githubCaching: github.NewClient(server.Client()),
   678  	}
   679  	t.Run("successful sync", func(t2 *testing.T) {
   680  		defer func() { eventLog = make([]string, 0) }()
   681  		timesDoWasCalled = 0
   682  		ctx := context.Background()
   683  		p.client = &ClientMock{
   684  			status:     "OK",
   685  			statusCode: http.StatusOK,
   686  			err:        nil,
   687  			testdata:   "TestParseMultipleGithubEvents.json",
   688  		}
   689  		err := p.syncEventsOnIssue(ctx, int32(issue.ID))
   690  		if err != nil {
   691  			t2.Error(err)
   692  		}
   693  		want := []string{"labeled", "labeled", "labeled", "labeled", "labeled", "milestoned", "closed"}
   694  		if !reflect.DeepEqual(want, eventLog) {
   695  			t2.Errorf("want: %v; got: %v\n", want, eventLog)
   696  		}
   697  
   698  		wantTimesCalled := 1
   699  		if timesDoWasCalled != wantTimesCalled {
   700  			t.Errorf("client.Do should have been called %d times. got: %d\n", wantTimesCalled, timesDoWasCalled)
   701  		}
   702  	})
   703  	t.Run("successful sync missing milestones", func(t2 *testing.T) {
   704  		defer func() { eventLog = make([]string, 0) }()
   705  		timesDoWasCalled = 0
   706  		ctx := context.Background()
   707  		p.client = &ClientMock{
   708  			status:     "OK",
   709  			statusCode: http.StatusOK,
   710  			err:        nil,
   711  			testdata:   "TestMissingMilestoneEvents.json",
   712  		}
   713  		err := p.syncEventsOnIssue(ctx, int32(issue.ID))
   714  		if err != nil {
   715  			t2.Error(err)
   716  		}
   717  		want := []string{"mentioned", "subscribed", "mentioned", "subscribed", "assigned", "labeled", "labeled", "milestoned", "renamed", "demilestoned", "milestoned"}
   718  		sort.Strings(want)
   719  		sort.Strings(eventLog)
   720  		if !reflect.DeepEqual(want, eventLog) {
   721  			t2.Errorf("want: %v; got: %v\n", want, eventLog)
   722  		}
   723  
   724  		wantTimesCalled := 1
   725  		if timesDoWasCalled != wantTimesCalled {
   726  			t.Errorf("client.Do should have been called %d times. got: %d\n", wantTimesCalled, timesDoWasCalled)
   727  		}
   728  	})
   729  }
   730  
   731  func TestSyncMultipleConsecutiveEvents(t *testing.T) {
   732  	var c Corpus
   733  	c.initGithub()
   734  	c.mutationLogger = &MockLogger{}
   735  	gr := c.github.getOrCreateRepo("foowner", "bar")
   736  	issue := &GitHubIssue{
   737  		ID:          1001,
   738  		PullRequest: true,
   739  		events:      map[int64]*GitHubIssueEvent{},
   740  	}
   741  	gr.issues = map[int32]*GitHubIssue{
   742  		1001: issue,
   743  	}
   744  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   745  		rw.Write([]byte("OK"))
   746  	}))
   747  	defer server.Close()
   748  	p := &githubRepoPoller{
   749  		c:             &c,
   750  		token:         "foobar",
   751  		gr:            gr,
   752  		githubDirect:  github.NewClient(server.Client()),
   753  		githubCaching: github.NewClient(server.Client()),
   754  	}
   755  	t.Run("successful sync", func(t2 *testing.T) {
   756  		defer func() { eventLog = make([]string, 0) }()
   757  		timesDoWasCalled = 0
   758  		ctx := context.Background()
   759  		for i := 1; i < 5; i++ {
   760  			testdata := fmt.Sprintf("TestParseMultipleGithubEvents_p%d.json", i)
   761  			p.client = &ClientMock{
   762  				status:     "OK",
   763  				statusCode: http.StatusOK,
   764  				err:        nil,
   765  				testdata:   testdata,
   766  			}
   767  			err := p.syncEventsOnIssue(ctx, int32(issue.ID))
   768  			if err != nil {
   769  				t2.Fatal(err)
   770  			}
   771  		}
   772  
   773  		want := []string{"labeled", "labeled", "labeled", "labeled", "labeled", "milestoned", "closed"}
   774  		if !reflect.DeepEqual(want, eventLog) {
   775  			t2.Errorf("want: %v; got: %v\n", want, eventLog)
   776  		}
   777  
   778  		wantTimesCalled := 4
   779  		if timesDoWasCalled != wantTimesCalled {
   780  			t.Errorf("client.Do should have been called %d times. got: %d\n", wantTimesCalled, timesDoWasCalled)
   781  		}
   782  	})
   783  }
   784  
   785  func TestParseGitHubReviews(t *testing.T) {
   786  	tests := []struct {
   787  		name string                // test
   788  		j    string                // JSON from Github API
   789  		e    *GitHubReview         // in-memory
   790  		p    *maintpb.GithubReview // on disk
   791  	}{
   792  		{
   793  			name: "Approved",
   794  			j: `{
   795  				"id": 123456,
   796  				"node_id": "548913adsafas84asdf48a",
   797  				"user": {
   798  					"login": "bradfitz",
   799  					"id": 2621
   800  				},
   801  				"body": "I approve this commit",
   802  				"state": "APPROVED",
   803  				"html_url": "https://github.com/bradfitz/go-issue-mirror/pull/21",
   804  				"pull_request_url": "https://github.com/bradfitz/go-issue-mirror/pull/21",
   805  				"author_association": "CONTRIBUTOR",
   806  				"_links":{
   807  					"html":{
   808  						"href": "https://github.com/bradfitz/go-issue-mirror/pull/21"
   809  					},
   810  					"pull_request":{
   811  						"href": "https://github.com/bradfitz/go-issue-mirror/pull/21"
   812  					}
   813  				},
   814  				"submitted_at": "2018-03-22T00:26:48Z",
   815  				"commit_id" : "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126"
   816  				}`,
   817  			e: &GitHubReview{
   818  				ID: 123456,
   819  				Actor: &GitHubUser{
   820  					ID:    2621,
   821  					Login: "bradfitz",
   822  				},
   823  				Body:             "I approve this commit",
   824  				State:            "APPROVED",
   825  				CommitID:         "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   826  				ActorAssociation: "CONTRIBUTOR",
   827  				Created:          t3339("2018-03-22T00:26:48Z"),
   828  			},
   829  			p: &maintpb.GithubReview{
   830  				Id:               123456,
   831  				ActorId:          2621,
   832  				Body:             "I approve this commit",
   833  				State:            "APPROVED",
   834  				CommitId:         "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   835  				ActorAssociation: "CONTRIBUTOR",
   836  				Created:          p3339("2018-03-22T00:26:48Z"),
   837  			},
   838  		},
   839  		{
   840  			name: "Extra Unknown JSON",
   841  			j: `{
   842  				"id": 123456,
   843  				"node_id": "548913adsafas84asdf48a",
   844  				"user": {
   845  					"login": "bradfitz",
   846  					"id": 2621
   847  				},
   848  				"body": "I approve this commit",
   849  				"state": "APPROVED",
   850  				"html_url": "https://github.com/bradfitz/go-issue-mirror/pull/21",
   851  				"pull_request_url": "https://github.com/bradfitz/go-issue-mirror/pull/21",
   852  				"author_association": "CONTRIBUTOR",
   853  				"_links":{
   854  					"html":{
   855  						"href": "https://github.com/bradfitz/go-issue-mirror/pull/21"
   856  					},
   857  					"pull_request":{
   858  						"href": "https://github.com/bradfitz/go-issue-mirror/pull/21"
   859  					}
   860  				},
   861  				"submitted_at": "2018-03-22T00:26:48Z",
   862  				"commit_id" : "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   863  				"random_new_field": "some new thing that GitHub API may add"
   864  				}`,
   865  			e: &GitHubReview{
   866  				ID: 123456,
   867  				Actor: &GitHubUser{
   868  					ID:    2621,
   869  					Login: "bradfitz",
   870  				},
   871  				Body:             "I approve this commit",
   872  				State:            "APPROVED",
   873  				CommitID:         "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   874  				ActorAssociation: "CONTRIBUTOR",
   875  				Created:          t3339("2018-03-22T00:26:48Z"),
   876  				OtherJSON:        `{"random_new_field":"some new thing that GitHub API may add"}`,
   877  			},
   878  			p: &maintpb.GithubReview{
   879  				Id:               123456,
   880  				ActorId:          2621,
   881  				Body:             "I approve this commit",
   882  				State:            "APPROVED",
   883  				CommitId:         "e4d70f7e8892f024e4ed3e8b99ee6c5a9f16e126",
   884  				ActorAssociation: "CONTRIBUTOR",
   885  				Created:          p3339("2018-03-22T00:26:48Z"),
   886  				OtherJson:        []byte(`{"random_new_field":"some new thing that GitHub API may add"}`),
   887  			},
   888  		},
   889  	}
   890  
   891  	for _, tt := range tests {
   892  		evts, err := parseGithubReviews(strings.NewReader("[" + tt.j + "]"))
   893  		if err != nil {
   894  			t.Errorf("%s: parse JSON: %v", tt.name, err)
   895  			continue
   896  		}
   897  		if len(evts) != 1 {
   898  			t.Errorf("%s: parse JSON = %v entries; want 1", tt.name, len(evts))
   899  			continue
   900  		}
   901  		gote := evts[0]
   902  		if !reflect.DeepEqual(gote, tt.e) {
   903  			t.Errorf("%s: JSON -> githubReviewEvent differs: %v", tt.name, DeepDiff(gote, tt.e))
   904  			continue
   905  		}
   906  
   907  		gotp := gote.Proto()
   908  		if !reflect.DeepEqual(gotp, tt.p) {
   909  			t.Errorf("%s: githubReviewEvent -> proto differs: %v", tt.name, DeepDiff(gotp, tt.p))
   910  			continue
   911  		}
   912  
   913  		var c Corpus
   914  		c.initGithub()
   915  		c.github.getOrCreateUserID(2621).Login = "bradfitz"
   916  		c.github.getOrCreateUserID(1924134).Login = "dmitshur"
   917  		gr := c.github.getOrCreateRepo("foowner", "bar")
   918  		e2 := gr.newGithubReview(gotp)
   919  
   920  		if !reflect.DeepEqual(e2, tt.e) {
   921  			t.Errorf("%s: proto -> githubReviewEvent differs: %v", tt.name, DeepDiff(e2, tt.e))
   922  			continue
   923  		}
   924  	}
   925  }
   926  
   927  func TestForeachRepo(t *testing.T) {
   928  	tests := []struct {
   929  		name    string
   930  		issue   *GitHubIssue
   931  		want    []string
   932  		wantErr error
   933  	}{
   934  		{
   935  			name: "Skips non-PullRequests",
   936  			issue: &GitHubIssue{
   937  				PullRequest: false,
   938  			},
   939  			want:    []string{},
   940  			wantErr: nil,
   941  		},
   942  		{
   943  			name: "Processes Multiple in Order",
   944  			issue: &GitHubIssue{
   945  				PullRequest: true,
   946  				reviews: map[int64]*GitHubReview{
   947  					0: &GitHubReview{
   948  						Body:    "Second",
   949  						Created: t3339("2018-04-22T00:26:48Z"),
   950  					},
   951  					1: &GitHubReview{
   952  						Body:    "First",
   953  						Created: t3339("2018-03-22T00:26:48Z"),
   954  					},
   955  				},
   956  			},
   957  			want:    []string{"First", "Second"},
   958  			wantErr: nil,
   959  		},
   960  		{
   961  			name: "Will Error",
   962  			issue: &GitHubIssue{
   963  				PullRequest: true,
   964  				reviews: map[int64]*GitHubReview{
   965  					0: &GitHubReview{
   966  						Body: "Fail",
   967  					},
   968  				},
   969  			},
   970  			want:    []string{},
   971  			wantErr: fmt.Errorf("Planned Failure"),
   972  		},
   973  		{
   974  			name: "Will Error Late",
   975  			issue: &GitHubIssue{
   976  				PullRequest: true,
   977  				reviews: map[int64]*GitHubReview{
   978  					0: &GitHubReview{
   979  						Body:    "First Event",
   980  						Created: t3339("2018-03-22T00:26:48Z"),
   981  					},
   982  					1: &GitHubReview{
   983  						Body:    "Fail",
   984  						Created: t3339("2018-04-22T00:26:48Z"),
   985  					},
   986  					2: &GitHubReview{
   987  						Body:    "Third Event",
   988  						Created: t3339("2018-05-22T00:26:48Z"),
   989  					},
   990  				},
   991  			},
   992  			want:    []string{"First Event"},
   993  			wantErr: fmt.Errorf("Planned Failure"),
   994  		}}
   995  
   996  	for _, tt := range tests {
   997  		got := make([]string, 0)
   998  
   999  		err := tt.issue.ForeachReview(func(r *GitHubReview) error {
  1000  			if r.Body == "Fail" {
  1001  				return fmt.Errorf("Planned Failure")
  1002  			}
  1003  			got = append(got, r.Body)
  1004  			return nil
  1005  		})
  1006  
  1007  		if !equalError(tt.wantErr, err) {
  1008  			t.Errorf("%s: ForeachReview errs differ. got: %s, want: %s", tt.name, err, tt.wantErr)
  1009  		}
  1010  
  1011  		if !reflect.DeepEqual(got, tt.want) {
  1012  			t.Errorf("%s: ForeachReview calls differ. got: %s want: %s", tt.name, got, tt.want)
  1013  		}
  1014  	}
  1015  
  1016  	t.Log("Tested Reviews")
  1017  }
  1018  
  1019  // equalError reports whether errors a and b are considered equal.
  1020  // They're equal if both are nil, or both are not nil and a.Error() == b.Error().
  1021  func equalError(a, b error) bool {
  1022  	return a == nil && b == nil || a != nil && b != nil && a.Error() == b.Error()
  1023  }
  1024  
  1025  func TestCacheableURL(t *testing.T) {
  1026  	tests := []struct {
  1027  		v    string
  1028  		want bool
  1029  	}{
  1030  		{"https://api.github.com/repos/OWNER/RePO/milestones?page=1", true},
  1031  		{"https://api.github.com/repos/OWNER/RePO/milestones?page=2", false},
  1032  		{"https://api.github.com/repos/OWNER/RePO/milestones?", false},
  1033  		{"https://api.github.com/repos/OWNER/RePO/milestones", false},
  1034  
  1035  		{"https://api.github.com/repos/OWNER/RePO/labels?page=1", true},
  1036  		{"https://api.github.com/repos/OWNER/RePO/labels?page=2", false},
  1037  		{"https://api.github.com/repos/OWNER/RePO/labels?", false},
  1038  		{"https://api.github.com/repos/OWNER/RePO/labels", false},
  1039  
  1040  		{"https://api.github.com/repos/OWNER/RePO/foos?page=1", false},
  1041  
  1042  		{"https://api.github.com/repos/OWNER/RePO/issues?page=1", false},
  1043  		{"https://api.github.com/repos/OWNER/RePO/issues?page=1&sort=updated&direction=desc", true},
  1044  	}
  1045  
  1046  	for _, tt := range tests {
  1047  		got := cacheableURL(tt.v)
  1048  		if got != tt.want {
  1049  			t.Errorf("cacheableURL(%q) = %v; want %v", tt.v, got, tt.want)
  1050  		}
  1051  	}
  1052  }
  1053  
  1054  func t3339(s string) time.Time {
  1055  	t, err := time.Parse(time.RFC3339, s)
  1056  	if err != nil {
  1057  		panic(err)
  1058  	}
  1059  	return t.UTC()
  1060  }
  1061  
  1062  func p3339(s string) *timestamp.Timestamp {
  1063  	tp, err := ptypes.TimestampProto(t3339(s))
  1064  	if err != nil {
  1065  		panic(err)
  1066  	}
  1067  	return tp
  1068  }
  1069  
  1070  func TestParseGithubRefs(t *testing.T) {
  1071  	tests := []struct {
  1072  		gerritProj string // "go.googlesource.com/go", etc
  1073  		msg        string
  1074  		want       []string
  1075  	}{
  1076  		{"go.googlesource.com/go", "\nFixes #1234\n", []string{"golang/go#1234"}},
  1077  		{"go.googlesource.com/go", "Fixes #1234\n", []string{"golang/go#1234"}},
  1078  		{"go.googlesource.com/go", "Fixes #1234", []string{"golang/go#1234"}},
  1079  		{"go.googlesource.com/go", "Fixes golang/go#1234", []string{"golang/go#1234"}},
  1080  		{"go.googlesource.com/go", "Fixes golang/go#1234\n", []string{"golang/go#1234"}},
  1081  		{"go.googlesource.com/go", "Fixes golang/go#1234.", []string{"golang/go#1234"}},
  1082  		{"go.googlesource.com/go", "Mention issue #1234 a second time.\n\nFixes #1234.", []string{"golang/go#1234"}},
  1083  		{"go.googlesource.com/go", "Mention issue #1234 a second time.\n\nFixes #1234.\nUpdates #1235.", []string{"golang/go#1234", "golang/go#1235"}},
  1084  		{"go.googlesource.com/net", "Fixes golang/go#1234.", []string{"golang/go#1234"}},
  1085  		{"go.googlesource.com/net", "Fixes #1234", nil},
  1086  	}
  1087  	for _, tt := range tests {
  1088  		c := new(Corpus)
  1089  		var got []string
  1090  		for _, ref := range c.parseGithubRefs(tt.gerritProj, tt.msg) {
  1091  			got = append(got, ref.String())
  1092  		}
  1093  		if !reflect.DeepEqual(got, tt.want) {
  1094  			t.Errorf("parseGithubRefs(%q, %q) = %q; want %q", tt.gerritProj, tt.msg, got, tt.want)
  1095  		}
  1096  	}
  1097  }