github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/api/receiver/receive_results_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 receiver 9 10 import ( 11 "bytes" 12 "fmt" 13 "mime/multipart" 14 "net/http" 15 "net/http/httptest" 16 "net/url" 17 "regexp" 18 "strings" 19 "testing" 20 21 "github.com/golang/mock/gomock" 22 "github.com/stretchr/testify/assert" 23 24 "github.com/web-platform-tests/wpt.fyi/api/receiver/mock_receiver" 25 "github.com/web-platform-tests/wpt.fyi/shared" 26 "github.com/web-platform-tests/wpt.fyi/shared/sharedtest" 27 ) 28 29 // regexMatcher is a gomock.Matcher that verifies whether a string argument 30 // matches the predefined regular expression. 31 // 32 // This is used to match arguments containing random strings (e.g. UUID). 33 type regexMatcher struct { 34 regex *regexp.Regexp 35 } 36 37 func (r *regexMatcher) Matches(x interface{}) bool { 38 s, ok := x.(string) 39 if !ok { 40 return false 41 } 42 return r.regex.MatchString(s) 43 } 44 45 func (r *regexMatcher) String() string { 46 return "matches " + r.regex.String() 47 } 48 49 func matchRegex(r string) *regexMatcher { 50 return ®exMatcher{regex: regexp.MustCompile(r)} 51 } 52 53 // An empty (default) extraParams 54 var emptyParams = map[string]string{ 55 "browser_name": "", 56 "labels": "", 57 "revision": "", 58 "browser_version": "", 59 "os_name": "", 60 "os_version": "", 61 "callback_url": "", 62 } 63 64 func TestHandleResultsUpload_not_admin(t *testing.T) { 65 mockCtrl := gomock.NewController(t) 66 defer mockCtrl.Finish() 67 68 req := httptest.NewRequest("POST", "/api/results/upload", nil) 69 resp := httptest.NewRecorder() 70 mockAE := mock_receiver.NewMockAPI(mockCtrl) 71 mockAE.EXPECT().IsAdmin(req).Return(false) 72 73 HandleResultsUpload(mockAE, resp, req) 74 assert.Equal(t, resp.Code, http.StatusUnauthorized) 75 } 76 77 func TestHandleResultsUpload_http_basic_auth_invalid(t *testing.T) { 78 mockCtrl := gomock.NewController(t) 79 defer mockCtrl.Finish() 80 81 req := httptest.NewRequest("POST", "/api/results/upload", nil) 82 req.SetBasicAuth("not_a_user", "123") 83 resp := httptest.NewRecorder() 84 mockAE := mock_receiver.NewMockAPI(mockCtrl) 85 gomock.InOrder( 86 mockAE.EXPECT().IsAdmin(req).Return(false), 87 mockAE.EXPECT().GetUploader("not_a_user").Return(shared.Uploader{}, fmt.Errorf("not found")), 88 ) 89 90 HandleResultsUpload(mockAE, resp, req) 91 assert.Equal(t, resp.Code, http.StatusUnauthorized) 92 } 93 94 func TestHandleResultsUpload_extra_params(t *testing.T) { 95 mockCtrl := gomock.NewController(t) 96 defer mockCtrl.Finish() 97 98 payload := url.Values{ 99 // Uploader cannot specify ID (i.e. this field should be discarded). 100 "id": {"12345"}, 101 "result_url": {"http://wpt.fyi/test.json.gz"}, 102 "browser_name": {"firefox"}, 103 "labels": {"stable"}, 104 "invalid_param": {"should be ignored"}, 105 } 106 req := httptest.NewRequest("POST", "/api/results/upload", strings.NewReader(payload.Encode())) 107 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 108 req.SetBasicAuth("blade-runner", "123") 109 resp := httptest.NewRecorder() 110 111 extraParams := map[string]string{ 112 "browser_name": "firefox", 113 "labels": "stable", 114 "revision": "", 115 "browser_version": "", 116 "os_name": "", 117 "os_version": "", 118 "callback_url": "", 119 } 120 mockAE := mock_receiver.NewMockAPI(mockCtrl) 121 mockAE.EXPECT().Context().Return(sharedtest.NewTestContext()).AnyTimes() 122 gomock.InOrder( 123 mockAE.EXPECT().IsAdmin(req).Return(false), 124 mockAE.EXPECT().GetUploader("blade-runner").Return(shared.Uploader{"blade-runner", "123"}, nil), 125 mockAE.EXPECT().ScheduleResultsTask( 126 "blade-runner", []string{"http://wpt.fyi/test.json.gz"}, nil, extraParams).Return("task", nil), 127 ) 128 129 HandleResultsUpload(mockAE, resp, req) 130 assert.Equal(t, resp.Code, http.StatusOK) 131 } 132 133 func TestHandleResultsUpload_azure(t *testing.T) { 134 mockCtrl := gomock.NewController(t) 135 defer mockCtrl.Finish() 136 137 azureURL := "https://dev.azure.com/web-platform-tests/b14026b4-9423-4454-858f-bf76cf6d1faa/_apis/build/builds/4230/artifacts?artifactName=results&api-version=5.0&%24format=zip" 138 payload := url.Values{"result_url": []string{azureURL}} 139 extraParams := map[string]string{ 140 "azure_url": azureURL, 141 "browser_name": "", 142 "labels": "", 143 "revision": "", 144 "browser_version": "", 145 "os_name": "", 146 "os_version": "", 147 "callback_url": "", 148 } 149 req := httptest.NewRequest("POST", "/api/results/upload", strings.NewReader(payload.Encode())) 150 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 151 req.SetBasicAuth("blade-runner", "123") 152 resp := httptest.NewRecorder() 153 154 mockAE := mock_receiver.NewMockAPI(mockCtrl) 155 mockAE.EXPECT().Context().Return(sharedtest.NewTestContext()).AnyTimes() 156 gomock.InOrder( 157 mockAE.EXPECT().IsAdmin(req).Return(false), 158 mockAE.EXPECT().GetUploader("blade-runner").Return(shared.Uploader{"blade-runner", "123"}, nil), 159 mockAE.EXPECT().ScheduleResultsTask("blade-runner", nil, nil, extraParams).Return("task", nil), 160 ) 161 162 HandleResultsUpload(mockAE, resp, req) 163 assert.Equal(t, resp.Code, http.StatusOK) 164 } 165 166 func TestHandleResultsUpload_url(t *testing.T) { 167 var urls []string 168 screenshot := []string{"http://wpt.fyi/wpt_screenshot.txt.gz"} 169 for i := 1; i <= 2; i++ { 170 urls = append(urls, fmt.Sprintf("http://wpt.fyi/wpt_report_%d.json.gz", i)) 171 t.Run(fmt.Sprintf("%d url(s)", len(urls)), func(t *testing.T) { 172 mockCtrl := gomock.NewController(t) 173 defer mockCtrl.Finish() 174 175 payload := url.Values{ 176 "result_url": urls, 177 "screenshot_url": screenshot, 178 } 179 req := httptest.NewRequest("POST", "/api/results/upload", strings.NewReader(payload.Encode())) 180 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 181 req.SetBasicAuth("blade-runner", "123") 182 resp := httptest.NewRecorder() 183 184 mockAE := mock_receiver.NewMockAPI(mockCtrl) 185 mockAE.EXPECT().Context().Return(sharedtest.NewTestContext()).AnyTimes() 186 gomock.InOrder( 187 mockAE.EXPECT().IsAdmin(req).Return(false), 188 mockAE.EXPECT().GetUploader("blade-runner").Return(shared.Uploader{"blade-runner", "123"}, nil), 189 mockAE.EXPECT().ScheduleResultsTask("blade-runner", urls, screenshot, emptyParams).Return("task", nil), 190 ) 191 192 HandleResultsUpload(mockAE, resp, req) 193 assert.Equal(t, resp.Code, http.StatusOK) 194 }) 195 } 196 } 197 198 func TestHandleResultsUpload_file(t *testing.T) { 199 var filenames []string 200 for i := 1; i <= 2; i++ { 201 filenames = append(filenames, fmt.Sprintf("wpt_report_%d.json.gz", i)) 202 t.Run(fmt.Sprintf("%d file(s)", len(filenames)), func(t *testing.T) { 203 mockCtrl := gomock.NewController(t) 204 defer mockCtrl.Finish() 205 206 buffer := new(bytes.Buffer) 207 writer := multipart.NewWriter(buffer) 208 for _, filename := range filenames { 209 writer.CreateFormFile("result_file", filename) 210 } 211 writer.Close() 212 req := httptest.NewRequest("POST", "/api/results/upload", buffer) 213 req.Header.Set("Content-Type", writer.FormDataContentType()) 214 req.SetBasicAuth("blade-runner", "123") 215 resp := httptest.NewRecorder() 216 217 mockAE := mock_receiver.NewMockAPI(mockCtrl) 218 mockAE.EXPECT().Context().Return(sharedtest.NewTestContext()).AnyTimes() 219 gomock.InOrder( 220 mockAE.EXPECT().IsAdmin(req).Return(false), 221 mockAE.EXPECT().GetUploader("blade-runner").Return(shared.Uploader{"blade-runner", "123"}, nil), 222 mockAE.EXPECT().ScheduleResultsTask("blade-runner", gomock.Any(), gomock.Any(), emptyParams).Return("task", nil), 223 ) 224 for range filenames { 225 mockAE.EXPECT().UploadToGCS(matchRegex(`^gs://wptd-results-buffer/blade-runner/.*\.json$`), gomock.Any(), true).Return(nil) 226 } 227 228 HandleResultsUpload(mockAE, resp, req) 229 assert.Equal(t, resp.Code, http.StatusOK) 230 }) 231 } 232 } 233 234 func TestHandleResultsUpload_fail_uploading(t *testing.T) { 235 mockCtrl := gomock.NewController(t) 236 defer mockCtrl.Finish() 237 238 buffer := new(bytes.Buffer) 239 writer := multipart.NewWriter(buffer) 240 writer.CreateFormFile("result_file", "test.json.gz") 241 writer.Close() 242 req := httptest.NewRequest("POST", "/api/results/upload", buffer) 243 req.Header.Set("Content-Type", writer.FormDataContentType()) 244 req.SetBasicAuth("blade-runner", "123") 245 resp := httptest.NewRecorder() 246 247 errGCS := fmt.Errorf("failed to upload to GCS") 248 mockAE := mock_receiver.NewMockAPI(mockCtrl) 249 mockAE.EXPECT().Context().Return(sharedtest.NewTestContext()).AnyTimes() 250 gomock.InOrder( 251 mockAE.EXPECT().IsAdmin(req).Return(false), 252 mockAE.EXPECT().GetUploader("blade-runner").Return(shared.Uploader{"blade-runner", "123"}, nil), 253 mockAE.EXPECT().UploadToGCS(matchRegex(`^gs://wptd-results-buffer/blade-runner/.*\.json$`), gomock.Any(), true).Return(errGCS), 254 ) 255 256 HandleResultsUpload(mockAE, resp, req) 257 assert.Equal(t, resp.Code, http.StatusInternalServerError) 258 } 259 260 func TestHandleResultsUpload_empty_payload(t *testing.T) { 261 mockCtrl := gomock.NewController(t) 262 defer mockCtrl.Finish() 263 264 req := httptest.NewRequest("POST", "/api/results/upload", nil) 265 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 266 req.SetBasicAuth("blade-runner", "123") 267 resp := httptest.NewRecorder() 268 269 mockAE := mock_receiver.NewMockAPI(mockCtrl) 270 mockAE.EXPECT().Context().Return(sharedtest.NewTestContext()).AnyTimes() 271 gomock.InOrder( 272 mockAE.EXPECT().IsAdmin(req).Return(false), 273 mockAE.EXPECT().GetUploader("blade-runner").Return(shared.Uploader{"blade-runner", "123"}, nil), 274 ) 275 276 HandleResultsUpload(mockAE, resp, req) 277 assert.Equal(t, resp.Code, http.StatusBadRequest) 278 }