github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/api/taskcluster/webhook_test.go (about)

     1  //go:build small
     2  // +build small
     3  
     4  // Copyright 2018 The WPT Dashboard Project. All rights reserved.
     5  // Use of this source code is governed by a BSD-style license that can be
     6  // found in the LICENSE file.
     7  
     8  package taskcluster_test
     9  
    10  import (
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"net/http"
    15  	"net/http/httptest"
    16  	"net/url"
    17  	"strings"
    18  	"sync/atomic"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/golang/mock/gomock"
    23  	"github.com/google/go-github/v47/github"
    24  	"github.com/stretchr/testify/assert"
    25  	uc "github.com/web-platform-tests/wpt.fyi/api/receiver/client"
    26  	tc "github.com/web-platform-tests/wpt.fyi/api/taskcluster"
    27  	mock_tc "github.com/web-platform-tests/wpt.fyi/api/taskcluster/mock_taskcluster"
    28  	"github.com/web-platform-tests/wpt.fyi/shared"
    29  	"github.com/web-platform-tests/wpt.fyi/shared/sharedtest"
    30  )
    31  
    32  type branchInfos []*github.Branch
    33  
    34  func strPtr(s string) *string {
    35  	return &s
    36  }
    37  
    38  func TestShouldProcessStatus_states(t *testing.T) {
    39  	status := tc.StatusEventPayload{}
    40  	status.State = strPtr("success")
    41  	status.Context = strPtr("Taskcluster")
    42  	status.Branches = branchInfos{&github.Branch{Name: strPtr(shared.MasterLabel)}}
    43  	assert.True(t, tc.ShouldProcessStatus(shared.NewNilLogger(), &status))
    44  
    45  	status.Context = strPtr("Community-TC")
    46  	assert.True(t, tc.ShouldProcessStatus(shared.NewNilLogger(), &status))
    47  
    48  	status.State = strPtr("failure")
    49  	assert.True(t, tc.ShouldProcessStatus(shared.NewNilLogger(), &status))
    50  
    51  	status.State = strPtr("error")
    52  	assert.False(t, tc.ShouldProcessStatus(shared.NewNilLogger(), &status))
    53  
    54  	status.State = strPtr("pending")
    55  	assert.False(t, tc.ShouldProcessStatus(shared.NewNilLogger(), &status))
    56  }
    57  
    58  func TestShouldProcessStatus_notTaskcluster(t *testing.T) {
    59  	status := tc.StatusEventPayload{}
    60  	status.State = strPtr("success")
    61  	status.Context = strPtr("Travis")
    62  	status.Branches = branchInfos{&github.Branch{Name: strPtr(shared.MasterLabel)}}
    63  	assert.False(t, tc.ShouldProcessStatus(shared.NewNilLogger(), &status))
    64  }
    65  
    66  func TestShouldProcessStatus_notOnMaster(t *testing.T) {
    67  	status := tc.StatusEventPayload{}
    68  	status.State = strPtr("success")
    69  	status.Context = strPtr("Taskcluster")
    70  	status.Branches = branchInfos{&github.Branch{Name: strPtr("gh-pages")}}
    71  	assert.True(t, tc.ShouldProcessStatus(shared.NewNilLogger(), &status))
    72  }
    73  
    74  func TestIsOnMaster(t *testing.T) {
    75  	status := tc.StatusEventPayload{}
    76  	status.SHA = strPtr("a10867b14bb761a232cd80139fbd4c0d33264240")
    77  	status.State = strPtr("success")
    78  	status.Context = strPtr("Taskcluster")
    79  	status.Branches = branchInfos{
    80  		&github.Branch{
    81  			Name:   strPtr(shared.MasterLabel),
    82  			Commit: &github.RepositoryCommit{SHA: strPtr("a10867b14bb761a232cd80139fbd4c0d33264240")},
    83  		},
    84  		&github.Branch{
    85  			Name:   strPtr("changes"),
    86  			Commit: &github.RepositoryCommit{SHA: strPtr("34c5c7793cb3b279e22454cb6750c80560547b3a")},
    87  		},
    88  		&github.Branch{
    89  			Name:   strPtr("gh-pages"),
    90  			Commit: &github.RepositoryCommit{SHA: strPtr("fd353d4ae7c19d2268397459524f849c129944a7")},
    91  		},
    92  	}
    93  	assert.True(t, status.IsOnMaster())
    94  
    95  	status.Branches = status.Branches[1:]
    96  	assert.False(t, status.IsOnMaster())
    97  }
    98  
    99  func TestParseTaskclusterURL(t *testing.T) {
   100  	t.Run("Status", func(t *testing.T) {
   101  		root, group, task := tc.ParseTaskclusterURL("https://tools.taskcluster.net/task-group-inspector/#/Y4rnZeqDRXGiRNiqxT5Qeg")
   102  		assert.Equal(t, "https://taskcluster.net", root)
   103  		assert.Equal(t, "Y4rnZeqDRXGiRNiqxT5Qeg", group)
   104  		assert.Equal(t, "", task)
   105  	})
   106  	t.Run("CheckRun with task", func(t *testing.T) {
   107  		root, group, task := tc.ParseTaskclusterURL("https://tc.example.com/groups/IWlO7NuxRnO0_8PKMuHFkw/tasks/NOToWHr0T-u62B9yGQnD5w/details")
   108  		assert.Equal(t, "https://tc.example.com", root)
   109  		assert.Equal(t, "IWlO7NuxRnO0_8PKMuHFkw", group)
   110  		assert.Equal(t, "NOToWHr0T-u62B9yGQnD5w", task)
   111  	})
   112  	t.Run("CheckRun without task", func(t *testing.T) {
   113  		root, group, task := tc.ParseTaskclusterURL("https://tc.other-example.com/groups/IWlO7NuxRnO0_8PKMuHFkw")
   114  		assert.Equal(t, "https://tc.other-example.com", root)
   115  		assert.Equal(t, "IWlO7NuxRnO0_8PKMuHFkw", group)
   116  		assert.Equal(t, "", task)
   117  	})
   118  	t.Run("CheckRun without task", func(t *testing.T) {
   119  		root, group, task := tc.ParseTaskclusterURL("https://tc.community.com/tasks/groups/IWlO7NuxRnO0_8PKMuHFkw")
   120  		assert.Equal(t, "https://tc.community.com", root)
   121  		assert.Equal(t, "IWlO7NuxRnO0_8PKMuHFkw", group)
   122  		assert.Equal(t, "", task)
   123  	})
   124  }
   125  
   126  func TestExtractArtifactURLs_all_success_master(t *testing.T) {
   127  	group := &tc.TaskGroupInfo{Tasks: make([]tc.TaskInfo, 5)}
   128  	group.Tasks[0].Name = "wpt-firefox-nightly-testharness-1"
   129  	group.Tasks[1].Name = "wpt-firefox-nightly-testharness-2"
   130  	group.Tasks[2].Name = "wpt-chrome-dev-testharness-1"
   131  	group.Tasks[3].Name = "wpt-chrome-dev-reftest-1"
   132  	group.Tasks[4].Name = "wpt-chrome-dev-crashtest-1"
   133  	for i := 0; i < len(group.Tasks); i++ {
   134  		group.Tasks[i].State = "completed"
   135  		group.Tasks[i].TaskID = fmt.Sprint(i)
   136  	}
   137  
   138  	t.Run("All", func(t *testing.T) {
   139  		urls, err := tc.ExtractArtifactURLs("https://tc.example.com", shared.NewNilLogger(), group, "")
   140  		assert.Nil(t, err)
   141  		assert.Equal(t, map[string]tc.ArtifactURLs{
   142  			"firefox-nightly": {
   143  				Results: []string{
   144  					"https://tc.example.com/api/queue/v1/task/0/artifacts/public/results/wpt_report.json.gz",
   145  					"https://tc.example.com/api/queue/v1/task/1/artifacts/public/results/wpt_report.json.gz",
   146  				},
   147  				Screenshots: []string{
   148  					"https://tc.example.com/api/queue/v1/task/0/artifacts/public/results/wpt_screenshot.txt.gz",
   149  					"https://tc.example.com/api/queue/v1/task/1/artifacts/public/results/wpt_screenshot.txt.gz",
   150  				},
   151  			},
   152  			"chrome-dev": {
   153  				Results: []string{
   154  					"https://tc.example.com/api/queue/v1/task/2/artifacts/public/results/wpt_report.json.gz",
   155  					"https://tc.example.com/api/queue/v1/task/3/artifacts/public/results/wpt_report.json.gz",
   156  					"https://tc.example.com/api/queue/v1/task/4/artifacts/public/results/wpt_report.json.gz",
   157  				},
   158  				Screenshots: []string{
   159  					"https://tc.example.com/api/queue/v1/task/2/artifacts/public/results/wpt_screenshot.txt.gz",
   160  					"https://tc.example.com/api/queue/v1/task/3/artifacts/public/results/wpt_screenshot.txt.gz",
   161  					"https://tc.example.com/api/queue/v1/task/4/artifacts/public/results/wpt_screenshot.txt.gz",
   162  				},
   163  			},
   164  		}, urls)
   165  	})
   166  
   167  	t.Run("Filtered", func(t *testing.T) {
   168  		urls, err := tc.ExtractArtifactURLs("https://tc.example.com", shared.NewNilLogger(), group, "0")
   169  		assert.Nil(t, err)
   170  		assert.Equal(t, map[string]tc.ArtifactURLs{
   171  			"firefox-nightly": {
   172  				Results: []string{
   173  					"https://tc.example.com/api/queue/v1/task/0/artifacts/public/results/wpt_report.json.gz",
   174  				},
   175  				Screenshots: []string{
   176  					"https://tc.example.com/api/queue/v1/task/0/artifacts/public/results/wpt_screenshot.txt.gz",
   177  				},
   178  			},
   179  		}, urls)
   180  	})
   181  }
   182  
   183  func TestExtractArtifactURLs_all_success_pr(t *testing.T) {
   184  	group := &tc.TaskGroupInfo{Tasks: make([]tc.TaskInfo, 3)}
   185  	group.Tasks[0].Name = "wpt-chrome-dev-results"
   186  	group.Tasks[1].Name = "wpt-chrome-dev-stability" // must be skipped
   187  	group.Tasks[2].Name = "wpt-chrome-dev-results-without-changes"
   188  	for i := 0; i < len(group.Tasks); i++ {
   189  		group.Tasks[i].State = "completed"
   190  		group.Tasks[i].TaskID = fmt.Sprint(i)
   191  	}
   192  
   193  	t.Run("All", func(t *testing.T) {
   194  		urls, err := tc.ExtractArtifactURLs("https://tc.example.com", shared.NewNilLogger(), group, "")
   195  		assert.Nil(t, err)
   196  		assert.Equal(t, map[string]tc.ArtifactURLs{
   197  			"chrome-dev-pr_head": {
   198  				Results: []string{
   199  					"https://tc.example.com/api/queue/v1/task/0/artifacts/public/results/wpt_report.json.gz",
   200  				},
   201  				Screenshots: []string{
   202  					"https://tc.example.com/api/queue/v1/task/0/artifacts/public/results/wpt_screenshot.txt.gz",
   203  				},
   204  			},
   205  			"chrome-dev-pr_base": {
   206  				Results: []string{
   207  					"https://tc.example.com/api/queue/v1/task/2/artifacts/public/results/wpt_report.json.gz",
   208  				},
   209  				Screenshots: []string{
   210  					"https://tc.example.com/api/queue/v1/task/2/artifacts/public/results/wpt_screenshot.txt.gz",
   211  				},
   212  			},
   213  		}, urls)
   214  	})
   215  
   216  	t.Run("Filtered", func(t *testing.T) {
   217  		urls, err := tc.ExtractArtifactURLs("https://tc.example.com", shared.NewNilLogger(), group, "2")
   218  		assert.Nil(t, err)
   219  		assert.Equal(t, map[string]tc.ArtifactURLs{
   220  			"chrome-dev-pr_base": {
   221  				Results: []string{
   222  					"https://tc.example.com/api/queue/v1/task/2/artifacts/public/results/wpt_report.json.gz",
   223  				},
   224  				Screenshots: []string{
   225  					"https://tc.example.com/api/queue/v1/task/2/artifacts/public/results/wpt_screenshot.txt.gz",
   226  				},
   227  			},
   228  		}, urls)
   229  	})
   230  }
   231  
   232  func TestExtractArtifactURLs_with_failures(t *testing.T) {
   233  	group := &tc.TaskGroupInfo{Tasks: make([]tc.TaskInfo, 3)}
   234  	group.Tasks[0].State = "failed"
   235  	group.Tasks[0].TaskID = "foo"
   236  	group.Tasks[0].Name = "wpt-firefox-nightly-testharness-1"
   237  	group.Tasks[1].State = "completed"
   238  	group.Tasks[1].TaskID = "bar"
   239  	group.Tasks[1].Name = "wpt-firefox-nightly-testharness-2"
   240  	group.Tasks[2].State = "completed"
   241  	group.Tasks[2].TaskID = "baz"
   242  	group.Tasks[2].Name = "wpt-chrome-dev-testharness-1"
   243  
   244  	urls, err := tc.ExtractArtifactURLs("https://tc.example.com", shared.NewNilLogger(), group, "")
   245  	assert.Nil(t, err)
   246  	assert.Equal(t, 1, len(urls))
   247  	assert.Contains(t, urls, "chrome-dev")
   248  }
   249  
   250  func TestCreateAllRuns_success(t *testing.T) {
   251  	var requested uint32
   252  	requested = 0
   253  	handler := func(w http.ResponseWriter, r *http.Request) {
   254  		atomic.AddUint32(&requested, 1)
   255  		w.Write([]byte("OK"))
   256  	}
   257  	server := httptest.NewServer(http.HandlerFunc(handler))
   258  	defer server.Close()
   259  	serverURL, _ := url.Parse(server.URL)
   260  	sha := "abcdef1234abcdef1234abcdef1234abcdef1234"
   261  
   262  	mockC := gomock.NewController(t)
   263  	defer mockC.Finish()
   264  	aeAPI := sharedtest.NewMockAppEngineAPI(mockC)
   265  	aeAPI.EXPECT().GetVersionedHostname().AnyTimes().Return("localhost:8080")
   266  	aeAPI.EXPECT().GetHTTPClientWithTimeout(uc.UploadTimeout).AnyTimes().Return(server.Client())
   267  	aeAPI.EXPECT().GetResultsUploadURL().AnyTimes().Return(serverURL)
   268  
   269  	t.Run("master", func(t *testing.T) {
   270  		err := tc.CreateAllRuns(
   271  			shared.NewNilLogger(),
   272  			aeAPI,
   273  			sha,
   274  			"username",
   275  			"password",
   276  			map[string]tc.ArtifactURLs{
   277  				"safari-preview": {Results: []string{"1"}},
   278  				"chrome-dev":     {Results: []string{"1"}},
   279  				"firefox-stable": {Results: []string{"1", "2"}},
   280  			},
   281  			[]string{shared.MasterLabel, "user:person"},
   282  		)
   283  		assert.Nil(t, err)
   284  		assert.Equal(t, uint32(3), requested)
   285  	})
   286  
   287  	requested = 0
   288  	t.Run("PR", func(t *testing.T) {
   289  		err := tc.CreateAllRuns(
   290  			shared.NewNilLogger(),
   291  			aeAPI,
   292  			sha,
   293  			"username",
   294  			"password",
   295  			map[string]tc.ArtifactURLs{
   296  				"chrome-dev-pr_head":     {Results: []string{"1"}},
   297  				"chrome-dev-pr_base":     {Results: []string{"1"}},
   298  				"firefox-stable-pr_head": {Results: []string{"1"}},
   299  				"firefox-stable-pr_base": {Results: []string{"1"}},
   300  			},
   301  			nil,
   302  		)
   303  		assert.Nil(t, err)
   304  		assert.Equal(t, uint32(4), requested)
   305  	})
   306  }
   307  
   308  func TestCreateAllRuns_one_error(t *testing.T) {
   309  	var requested uint32
   310  	requested = 0
   311  	handler := func(w http.ResponseWriter, r *http.Request) {
   312  		if atomic.CompareAndSwapUint32(&requested, 0, 1) {
   313  			w.Write([]byte("OK"))
   314  		} else if atomic.CompareAndSwapUint32(&requested, 1, 2) {
   315  			http.Error(w, "Not found", http.StatusNotFound)
   316  		} else {
   317  			assert.FailNow(t, "requested != 0 && requested != 1")
   318  		}
   319  	}
   320  	server := httptest.NewServer(http.HandlerFunc(handler))
   321  	defer server.Close()
   322  	mockC := gomock.NewController(t)
   323  	defer mockC.Finish()
   324  
   325  	sha := "abcdef1234abcdef1234abcdef1234abcdef1234"
   326  
   327  	aeAPI := sharedtest.NewMockAppEngineAPI(mockC)
   328  	aeAPI.EXPECT().GetVersionedHostname().MinTimes(1).Return("localhost:8080")
   329  	aeAPI.EXPECT().GetHTTPClientWithTimeout(uc.UploadTimeout).Times(2).Return(server.Client())
   330  	serverURL, _ := url.Parse(server.URL)
   331  	aeAPI.EXPECT().GetResultsUploadURL().AnyTimes().Return(serverURL)
   332  
   333  	err := tc.CreateAllRuns(
   334  		shared.NewNilLogger(),
   335  		aeAPI,
   336  		sha,
   337  		"username",
   338  		"password",
   339  		map[string]tc.ArtifactURLs{
   340  			"chrome":  {Results: []string{"1"}},
   341  			"firefox": {Results: []string{"1", "2"}},
   342  		},
   343  		[]string{shared.MasterLabel},
   344  	)
   345  	assert.NotNil(t, err)
   346  	assert.Equal(t, uint32(2), requested)
   347  	assert.Contains(t, err.Error(), "API error:")
   348  	assert.Contains(t, err.Error(), "404")
   349  }
   350  
   351  func TestCreateAllRuns_all_errors(t *testing.T) {
   352  	handler := func(w http.ResponseWriter, r *http.Request) {
   353  		time.Sleep(time.Second * 2)
   354  	}
   355  	server := httptest.NewServer(http.HandlerFunc(handler))
   356  	defer server.Close()
   357  	mockC := gomock.NewController(t)
   358  	defer mockC.Finish()
   359  
   360  	sha := "abcdef1234abcdef1234abcdef1234abcdef1234"
   361  
   362  	aeAPI := sharedtest.NewMockAppEngineAPI(mockC)
   363  	aeAPI.EXPECT().GetVersionedHostname().MinTimes(1).Return("localhost:8080")
   364  	// Give a very short timeout (instead of the asked 1min) to make tests faster.
   365  	aeAPI.EXPECT().GetHTTPClientWithTimeout(uc.UploadTimeout).MinTimes(1).Return(&http.Client{Timeout: time.Microsecond})
   366  	serverURL, _ := url.Parse(server.URL)
   367  	aeAPI.EXPECT().GetResultsUploadURL().AnyTimes().Return(serverURL)
   368  
   369  	err := tc.CreateAllRuns(
   370  		shared.NewNilLogger(),
   371  		aeAPI,
   372  		sha,
   373  		"username",
   374  		"password",
   375  		map[string]tc.ArtifactURLs{
   376  			"chrome":  {Results: []string{"1"}},
   377  			"firefox": {Results: []string{"1", "2"}},
   378  		},
   379  		[]string{shared.MasterLabel},
   380  	)
   381  	assert.NotNil(t, err)
   382  	assert.Equal(t, 2, strings.Count(err.Error(), "Client.Timeout"))
   383  }
   384  
   385  func TestCreateAllRuns_pr_labels_exclude_master(t *testing.T) {
   386  	handler := func(w http.ResponseWriter, r *http.Request) {
   387  		// We should not see a master label here, even though we
   388  		// specify one in the call to tc.CreateAllRuns.
   389  		defer r.Body.Close()
   390  		body, _ := io.ReadAll(r.Body)
   391  		assert.NotContains(t, string(body), "master")
   392  		w.Write([]byte("OK"))
   393  	}
   394  	server := httptest.NewServer(http.HandlerFunc(handler))
   395  	defer server.Close()
   396  	serverURL, _ := url.Parse(server.URL)
   397  	sha := "abcdef1234abcdef1234abcdef1234abcdef1234"
   398  
   399  	mockC := gomock.NewController(t)
   400  	defer mockC.Finish()
   401  	aeAPI := sharedtest.NewMockAppEngineAPI(mockC)
   402  	aeAPI.EXPECT().GetVersionedHostname().AnyTimes().Return("localhost:8080")
   403  	aeAPI.EXPECT().GetHTTPClientWithTimeout(uc.UploadTimeout).AnyTimes().Return(server.Client())
   404  	aeAPI.EXPECT().GetResultsUploadURL().AnyTimes().Return(serverURL)
   405  
   406  	// This test reproduces the case where Community-TC executes a pull
   407  	// request run on a master commit (which we have historically seen).
   408  	// When we get a master-tagged run which contains pull-request runs, we
   409  	// should ignore the tag. This is asserted by the HTTP handler above.
   410  	err := tc.CreateAllRuns(
   411  		shared.NewNilLogger(),
   412  		aeAPI,
   413  		sha,
   414  		"username",
   415  		"password",
   416  		map[string]tc.ArtifactURLs{
   417  			"chrome-dev-pr_head":     {Results: []string{"1"}},
   418  			"chrome-dev-pr_base":     {Results: []string{"1"}},
   419  			"firefox-stable-pr_head": {Results: []string{"1"}},
   420  			"firefox-stable-pr_base": {Results: []string{"1"}},
   421  		},
   422  		[]string{shared.MasterLabel, "user:person"},
   423  	)
   424  	assert.Nil(t, err)
   425  }
   426  
   427  func TestTaskNameRegex(t *testing.T) {
   428  	assert.Equal(t, []string{"chrome-dev", "results"}, tc.TaskNameRegex.FindStringSubmatch("wpt-chrome-dev-results")[1:])
   429  	assert.Equal(t, []string{"chrome-dev", "results-without-changes"}, tc.TaskNameRegex.FindStringSubmatch("wpt-chrome-dev-results-without-changes")[1:])
   430  	assert.Equal(t, []string{"chrome-dev", "stability"}, tc.TaskNameRegex.FindStringSubmatch("wpt-chrome-dev-stability")[1:])
   431  	assert.Equal(t, []string{"chrome-stable", "reftest"}, tc.TaskNameRegex.FindStringSubmatch("wpt-chrome-stable-reftest-1")[1:])
   432  	assert.Equal(t, []string{"firefox-beta", "crashtest"}, tc.TaskNameRegex.FindStringSubmatch("wpt-firefox-beta-crashtest-2")[1:])
   433  	assert.Equal(t, []string{"firefox-nightly", "testharness"}, tc.TaskNameRegex.FindStringSubmatch("wpt-firefox-nightly-testharness-5")[1:])
   434  	assert.Equal(t, []string{"firefox-stable", "wdspec"}, tc.TaskNameRegex.FindStringSubmatch("wpt-firefox-stable-wdspec-1")[1:])
   435  	assert.Equal(t, []string{"webkitgtk_minibrowser-nightly", "testharness"}, tc.TaskNameRegex.FindStringSubmatch("wpt-webkitgtk_minibrowser-nightly-testharness-2")[1:])
   436  	assert.Nil(t, tc.TaskNameRegex.FindStringSubmatch("wpt-foo-bar--1"))
   437  	assert.Nil(t, tc.TaskNameRegex.FindStringSubmatch("wpt-foo-bar-"))
   438  }
   439  
   440  func TestGetStatusEventInfo_target_url(t *testing.T) {
   441  	mockC := gomock.NewController(t)
   442  	defer mockC.Finish()
   443  	api := mock_tc.NewMockAPI(mockC)
   444  	api.EXPECT().GetTaskGroupInfo("https://tc.community.com", "IWlO7NuxRnO0_8PKMuHFkw").Return(nil, nil)
   445  
   446  	status := tc.StatusEventPayload{}
   447  	status.State = strPtr("success")
   448  	status.TargetURL = strPtr("https://tc.community.com/tasks/groups/IWlO7NuxRnO0_8PKMuHFkw/tasks/123")
   449  	status.Context = strPtr("Community-TC")
   450  	status.Branches = branchInfos{&github.Branch{Name: strPtr(shared.MasterLabel)}}
   451  	status.SHA = strPtr("abcdef123")
   452  
   453  	// The target URL must be present, and must at least be a recognized
   454  	// URL containing a taskGroupID. ParseTaskclusterURL is tested
   455  	// separately, so just do a basic check here.
   456  	event, err := tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   457  	assert.Equal(t, event.RootURL, "https://tc.community.com")
   458  	assert.Equal(t, event.TaskID, "123")
   459  	assert.Nil(t, err)
   460  
   461  	status.TargetURL = strPtr("https://example.com/nope/not/right")
   462  	event, err = tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   463  	assert.NotNil(t, err)
   464  
   465  	status.TargetURL = nil
   466  	event, err = tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   467  	assert.NotNil(t, err)
   468  }
   469  
   470  func TestGetStatusEventInfo_sha(t *testing.T) {
   471  	mockC := gomock.NewController(t)
   472  	defer mockC.Finish()
   473  	api := mock_tc.NewMockAPI(mockC)
   474  	api.EXPECT().GetTaskGroupInfo(gomock.Any(), gomock.Any()).Return(nil, nil)
   475  
   476  	status := tc.StatusEventPayload{}
   477  	status.State = strPtr("success")
   478  	status.TargetURL = strPtr("https://tc.community.com/tasks/groups/IWlO7NuxRnO0_8PKMuHFkw/tasks/123")
   479  	status.Context = strPtr("Community-TC")
   480  	status.Branches = branchInfos{&github.Branch{Name: strPtr(shared.MasterLabel)}}
   481  	status.SHA = strPtr("abcdef123")
   482  
   483  	// We don't place requirements on the SHA other than it exists.
   484  	event, err := tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   485  	assert.Equal(t, event.Sha, "abcdef123")
   486  	assert.Nil(t, err)
   487  
   488  	status.SHA = nil
   489  	event, err = tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   490  	assert.NotNil(t, err)
   491  }
   492  
   493  func TestGetStatusEventInfo_master(t *testing.T) {
   494  	mockC := gomock.NewController(t)
   495  	defer mockC.Finish()
   496  	api := mock_tc.NewMockAPI(mockC)
   497  	api.EXPECT().GetTaskGroupInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
   498  
   499  	status := tc.StatusEventPayload{}
   500  	status.State = strPtr("success")
   501  	status.TargetURL = strPtr("https://tc.community.com/tasks/groups/IWlO7NuxRnO0_8PKMuHFkw/tasks/123")
   502  	status.Context = strPtr("Community-TC")
   503  	status.Branches = branchInfos{&github.Branch{Name: strPtr("mybranch")}, &github.Branch{Name: strPtr(shared.MasterLabel)}}
   504  	status.SHA = strPtr("abcdef123")
   505  
   506  	// We check whether an event is for master by looking at the branches
   507  	// it is associated with.
   508  	event, err := tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   509  	assert.Equal(t, event.Master, true)
   510  	assert.Nil(t, err)
   511  
   512  	status.Branches = branchInfos{&github.Branch{Name: strPtr("mybranch")}}
   513  	event, err = tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   514  	assert.Equal(t, event.Master, false)
   515  	assert.Nil(t, err)
   516  
   517  	// Missing the 'branches' entry is not an error; the event just isn't
   518  	// for master.
   519  	status.Branches = nil
   520  	event, err = tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   521  	assert.Equal(t, event.Master, false)
   522  	assert.Nil(t, err)
   523  }
   524  
   525  func TestGetStatusEventInfo_sender(t *testing.T) {
   526  	mockC := gomock.NewController(t)
   527  	defer mockC.Finish()
   528  	api := mock_tc.NewMockAPI(mockC)
   529  	api.EXPECT().GetTaskGroupInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
   530  
   531  	status := tc.StatusEventPayload{}
   532  	status.State = strPtr("success")
   533  	status.TargetURL = strPtr("https://tc.community.com/tasks/groups/IWlO7NuxRnO0_8PKMuHFkw/tasks/123")
   534  	status.Context = strPtr("Community-TC")
   535  	status.Branches = branchInfos{&github.Branch{Name: strPtr(shared.MasterLabel)}}
   536  	status.SHA = strPtr("abcdef123")
   537  
   538  	// The sender is entirely optional.
   539  	status.Commit = &github.RepositoryCommit{Author: &github.User{Login: strPtr("someuser")}}
   540  	event, err := tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   541  	assert.Equal(t, event.Sender, "someuser")
   542  	assert.Nil(t, err)
   543  
   544  	status.Commit = nil
   545  	event, err = tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   546  	assert.Equal(t, event.Sender, "")
   547  	assert.Nil(t, err)
   548  }
   549  
   550  func TestGetStatusEventInfo_group(t *testing.T) {
   551  	mockC := gomock.NewController(t)
   552  	defer mockC.Finish()
   553  	api := mock_tc.NewMockAPI(mockC)
   554  	group := &tc.TaskGroupInfo{Tasks: make([]tc.TaskInfo, 0)}
   555  
   556  	status := tc.StatusEventPayload{}
   557  	status.State = strPtr("success")
   558  	status.TargetURL = strPtr("https://tc.community.com/tasks/groups/IWlO7NuxRnO0_8PKMuHFkw/tasks/123")
   559  	status.Context = strPtr("Community-TC")
   560  	status.Branches = branchInfos{&github.Branch{Name: strPtr(shared.MasterLabel)}}
   561  	status.SHA = strPtr("abcdef123")
   562  
   563  	api.EXPECT().GetTaskGroupInfo(gomock.Any(), gomock.Any()).Return(group, nil).Times(1)
   564  	event, err := tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   565  	assert.Equal(t, event.Group, group)
   566  	assert.Nil(t, err)
   567  
   568  	api.EXPECT().GetTaskGroupInfo(gomock.Any(), gomock.Any()).Return(nil, errors.New("failed")).Times(1)
   569  	event, err = tc.GetStatusEventInfo(status, shared.NewNilLogger(), api)
   570  	assert.NotNil(t, err)
   571  }
   572  
   573  func TestGetCheckSuiteEventInfo_sourceRepo(t *testing.T) {
   574  	mockC := gomock.NewController(t)
   575  	defer mockC.Finish()
   576  	api := mock_tc.NewMockAPI(mockC)
   577  
   578  	runs := []*github.CheckRun{
   579  		{
   580  			Name:       strPtr("wpt-decision-task"),
   581  			Status:     strPtr("completed"),
   582  			DetailsURL: strPtr("https://community-tc.services.mozilla.com/tasks/Jq4HzLz0R2eKkJFdmf47Bg"),
   583  		},
   584  	}
   585  	api.EXPECT().ListCheckRuns(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(runs, nil)
   586  
   587  	event := github.CheckSuiteEvent{
   588  		CheckSuite: &github.CheckSuite{
   589  			HeadSHA: strPtr("abcdef123"),
   590  		},
   591  		Repo: &github.Repository{
   592  			Owner: &github.User{
   593  				Login: strPtr("web-platform-tests"),
   594  			},
   595  			Name: strPtr("wpt"),
   596  		},
   597  	}
   598  
   599  	// Valid owner and name.
   600  	_, err := tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   601  	assert.Nil(t, err)
   602  
   603  	// Invalid name.
   604  	event.Repo.Name = strPtr("not-wpt")
   605  	_, err = tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   606  	assert.NotNil(t, err)
   607  
   608  	// Invalid owner.
   609  	event.Repo.Name = strPtr("wpt")
   610  	event.Repo.Owner.Login = strPtr("stephenmcgruer")
   611  	_, err = tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   612  	assert.NotNil(t, err)
   613  }
   614  
   615  func TestGetCheckSuiteEventInfo_sha(t *testing.T) {
   616  	mockC := gomock.NewController(t)
   617  	defer mockC.Finish()
   618  	api := mock_tc.NewMockAPI(mockC)
   619  
   620  	runs := []*github.CheckRun{
   621  		{
   622  			Name:       strPtr("wpt-decision-task"),
   623  			Status:     strPtr("completed"),
   624  			DetailsURL: strPtr("https://community-tc.services.mozilla.com/tasks/Jq4HzLz0R2eKkJFdmf47Bg"),
   625  		},
   626  	}
   627  	api.EXPECT().ListCheckRuns(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(runs, nil)
   628  
   629  	event := github.CheckSuiteEvent{
   630  		CheckSuite: &github.CheckSuite{
   631  			HeadSHA: strPtr("abcdef123"),
   632  		},
   633  		Repo: &github.Repository{
   634  			Owner: &github.User{
   635  				Login: strPtr("web-platform-tests"),
   636  			},
   637  			Name: strPtr("wpt"),
   638  		},
   639  	}
   640  
   641  	// We don't place requirements on the SHA other than it exists.
   642  	eventInfo, err := tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   643  	assert.Equal(t, "abcdef123", eventInfo.Sha)
   644  	assert.Nil(t, err)
   645  
   646  	event.CheckSuite.HeadSHA = nil
   647  	eventInfo, err = tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   648  	assert.NotNil(t, err)
   649  }
   650  
   651  func TestGetCheckSuiteEventInfo_master(t *testing.T) {
   652  	mockC := gomock.NewController(t)
   653  	defer mockC.Finish()
   654  	api := mock_tc.NewMockAPI(mockC)
   655  
   656  	runs := []*github.CheckRun{
   657  		{
   658  			Name:       strPtr("wpt-decision-task"),
   659  			Status:     strPtr("completed"),
   660  			DetailsURL: strPtr("https://community-tc.services.mozilla.com/tasks/Jq4HzLz0R2eKkJFdmf47Bg"),
   661  		},
   662  	}
   663  	api.EXPECT().ListCheckRuns(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(runs, nil)
   664  
   665  	event := github.CheckSuiteEvent{
   666  		CheckSuite: &github.CheckSuite{
   667  			HeadBranch: strPtr("master"),
   668  			HeadSHA:    strPtr("abcdef123"),
   669  		},
   670  		Repo: &github.Repository{
   671  			Owner: &github.User{
   672  				Login: strPtr("web-platform-tests"),
   673  			},
   674  			Name: strPtr("wpt"),
   675  		},
   676  	}
   677  
   678  	eventInfo, err := tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   679  	assert.Equal(t, true, eventInfo.Master)
   680  	assert.Nil(t, err)
   681  
   682  	event.CheckSuite.HeadBranch = strPtr("my-branch")
   683  	eventInfo, err = tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   684  	assert.Equal(t, false, eventInfo.Master)
   685  	assert.Nil(t, err)
   686  }
   687  
   688  func TestGetCheckSuiteEventInfo_sender(t *testing.T) {
   689  	mockC := gomock.NewController(t)
   690  	defer mockC.Finish()
   691  	api := mock_tc.NewMockAPI(mockC)
   692  
   693  	runs := []*github.CheckRun{
   694  		{
   695  			Name:       strPtr("wpt-decision-task"),
   696  			Status:     strPtr("completed"),
   697  			DetailsURL: strPtr("https://community-tc.services.mozilla.com/tasks/Jq4HzLz0R2eKkJFdmf47Bg"),
   698  		},
   699  	}
   700  	api.EXPECT().ListCheckRuns(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(runs, nil)
   701  
   702  	event := github.CheckSuiteEvent{
   703  		Sender: &github.User{
   704  			Login: strPtr("myuser"),
   705  		},
   706  		CheckSuite: &github.CheckSuite{
   707  			HeadSHA: strPtr("abcdef123"),
   708  		},
   709  		Repo: &github.Repository{
   710  			Owner: &github.User{
   711  				Login: strPtr("web-platform-tests"),
   712  			},
   713  			Name: strPtr("wpt"),
   714  		},
   715  	}
   716  
   717  	eventInfo, err := tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   718  	assert.Equal(t, "myuser", eventInfo.Sender)
   719  	assert.Nil(t, err)
   720  
   721  	event.Sender.Login = nil
   722  	eventInfo, err = tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   723  	assert.Equal(t, "", eventInfo.Sender)
   724  	assert.Nil(t, err)
   725  }
   726  
   727  func TestGetCheckSuiteEventInfo_checkRuns(t *testing.T) {
   728  	mockC := gomock.NewController(t)
   729  	defer mockC.Finish()
   730  	api := mock_tc.NewMockAPI(mockC)
   731  
   732  	// The list of check_run events give us two main pieces of information:
   733  	//
   734  	//	the RootURL, which must match across runs, and
   735  	//	the TaskGroupInfo:
   736  	//		TaskGroupID is the wpt-decision-tasks's taskID
   737  	//		Tasks is filled with each check_run's name, taskID, and status.
   738  	runs := []*github.CheckRun{
   739  		{
   740  			Name:       strPtr("wpt-decision-task"),
   741  			Status:     strPtr("completed"),
   742  			Conclusion: strPtr("success"),
   743  			DetailsURL: strPtr("https://community-tc.services.mozilla.com/tasks/Jq4HzLz0R2eKkJFdmf47Bg"),
   744  		},
   745  		{
   746  			Name:       strPtr("wpt-chrome-dev-testharness-1"),
   747  			Status:     strPtr("completed"),
   748  			Conclusion: strPtr("failed"),
   749  			DetailsURL: strPtr("https://community-tc.services.mozilla.com/tasks/IWlO7NuxRnO0_8PKMuHFkw"),
   750  		},
   751  	}
   752  	api.EXPECT().ListCheckRuns(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(runs, nil)
   753  
   754  	event := github.CheckSuiteEvent{
   755  		CheckSuite: &github.CheckSuite{
   756  			HeadSHA: strPtr("abcdef123"),
   757  		},
   758  		Repo: &github.Repository{
   759  			Owner: &github.User{
   760  				Login: strPtr("web-platform-tests"),
   761  			},
   762  			Name: strPtr("wpt"),
   763  		},
   764  	}
   765  
   766  	eventInfo, err := tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   767  	assert.Equal(t, "https://community-tc.services.mozilla.com", eventInfo.RootURL)
   768  	assert.Equal(t, "Jq4HzLz0R2eKkJFdmf47Bg", eventInfo.Group.TaskGroupID)
   769  	assert.Equal(t, "wpt-decision-task", eventInfo.Group.Tasks[0].Name)
   770  	assert.Equal(t, "Jq4HzLz0R2eKkJFdmf47Bg", eventInfo.Group.Tasks[0].TaskID)
   771  	assert.Equal(t, "completed", eventInfo.Group.Tasks[0].State)
   772  	assert.Equal(t, "wpt-chrome-dev-testharness-1", eventInfo.Group.Tasks[1].Name)
   773  	assert.Equal(t, "IWlO7NuxRnO0_8PKMuHFkw", eventInfo.Group.Tasks[1].TaskID)
   774  	assert.Equal(t, "failed", eventInfo.Group.Tasks[1].State)
   775  	assert.Nil(t, err)
   776  
   777  	// Check the case where a details URL will fail to parse.
   778  	runs[0].DetailsURL = strPtr("https://example.com/nope/not/right")
   779  	eventInfo, err = tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   780  	assert.NotNil(t, err)
   781  
   782  	// Check the case where a details URL is missing.
   783  	runs[0].DetailsURL = nil
   784  	eventInfo, err = tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   785  	assert.NotNil(t, err)
   786  
   787  	// Check the case where a details URL has a mismatching root URL.
   788  	runs[0].DetailsURL = strPtr("https://tc.community.com/tasks/Jq4HzLz0R2eKkJFdmf47Bg")
   789  	eventInfo, err = tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   790  	assert.NotNil(t, err)
   791  }
   792  
   793  func TestGetCheckSuiteEventInfo_checkRunsEmpty(t *testing.T) {
   794  	mockC := gomock.NewController(t)
   795  	defer mockC.Finish()
   796  	api := mock_tc.NewMockAPI(mockC)
   797  	api.EXPECT().ListCheckRuns(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]*github.CheckRun{}, nil)
   798  
   799  	event := github.CheckSuiteEvent{
   800  		CheckSuite: &github.CheckSuite{
   801  			HeadSHA: strPtr("abcdef123"),
   802  		},
   803  		Repo: &github.Repository{
   804  			Owner: &github.User{
   805  				Login: strPtr("web-platform-tests"),
   806  			},
   807  			Name: strPtr("wpt"),
   808  		},
   809  	}
   810  
   811  	_, err := tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   812  	assert.NotNil(t, err)
   813  }
   814  
   815  func TestGetCheckSuiteEventInfo_checkRunsFailed(t *testing.T) {
   816  	mockC := gomock.NewController(t)
   817  	defer mockC.Finish()
   818  	api := mock_tc.NewMockAPI(mockC)
   819  	api.EXPECT().ListCheckRuns(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil, errors.New("failed"))
   820  
   821  	event := github.CheckSuiteEvent{
   822  		CheckSuite: &github.CheckSuite{
   823  			HeadSHA: strPtr("abcdef123"),
   824  		},
   825  		Repo: &github.Repository{
   826  			Owner: &github.User{
   827  				Login: strPtr("web-platform-tests"),
   828  			},
   829  			Name: strPtr("wpt"),
   830  		},
   831  	}
   832  
   833  	_, err := tc.GetCheckSuiteEventInfo(event, shared.NewNilLogger(), api)
   834  	assert.NotNil(t, err)
   835  }