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 &regexMatcher{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  }