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

     1  //go:build medium
     2  // +build medium
     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  	"context"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"net/http"
    17  	"net/http/httptest"
    18  	"net/url"
    19  	"strconv"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/golang/mock/gomock"
    24  	"github.com/stretchr/testify/assert"
    25  
    26  	"github.com/web-platform-tests/wpt.fyi/shared"
    27  	"github.com/web-platform-tests/wpt.fyi/shared/sharedtest"
    28  )
    29  
    30  type mockGcs struct {
    31  	mockWriter mockGcsWriter
    32  	errOnNew   error
    33  }
    34  
    35  // mockGcsWriter implements io.WriteCloser
    36  type mockGcsWriter struct {
    37  	bytes.Buffer
    38  	bucketName   string
    39  	fileName     string
    40  	finalContent []byte
    41  	errOnClose   error
    42  }
    43  
    44  func (m *mockGcsWriter) Close() error {
    45  	m.finalContent = m.Bytes()
    46  	return m.errOnClose
    47  }
    48  
    49  func (m *mockGcs) NewWriter(bucketName, fileName, contentType, contentEncoding string) (io.WriteCloser, error) {
    50  	m.mockWriter.bucketName = bucketName
    51  	m.mockWriter.fileName = fileName
    52  	return &m.mockWriter, m.errOnNew
    53  }
    54  
    55  func TestIsAdmin_failsToConstructACL(t *testing.T) {
    56  	ctx, done, err := sharedtest.NewAEContext(true)
    57  	assert.Nil(t, err)
    58  	defer done()
    59  	a := NewAPI(ctx).(*apiImpl)
    60  	r := httptest.NewRequest("GET", "/api/results/upload", nil)
    61  
    62  	// User not logged in
    63  	a.githubACLFactory = func(_ *http.Request) (shared.GitHubAccessControl, error) {
    64  		return nil, nil
    65  	}
    66  	assert.False(t, a.IsAdmin(r))
    67  
    68  	a.githubACLFactory = func(_ *http.Request) (shared.GitHubAccessControl, error) {
    69  		return nil, errors.New("error constructing ACL")
    70  	}
    71  	assert.False(t, a.IsAdmin(r))
    72  }
    73  
    74  func TestIsAdmin_mockACL(t *testing.T) {
    75  	ctx, done, err := sharedtest.NewAEContext(true)
    76  	assert.Nil(t, err)
    77  	defer done()
    78  	a := NewAPI(ctx).(*apiImpl)
    79  	r := httptest.NewRequest("GET", "/api/results/upload", nil)
    80  
    81  	t.Run("error", func(t *testing.T) {
    82  		mockCtrl := gomock.NewController(t)
    83  		defer mockCtrl.Finish()
    84  		mockACL := sharedtest.NewMockGitHubAccessControl(mockCtrl)
    85  		mockACL.EXPECT().IsValidAdmin().Return(true, errors.New("error checking admin"))
    86  		a.githubACLFactory = func(_ *http.Request) (shared.GitHubAccessControl, error) {
    87  			return mockACL, nil
    88  		}
    89  		assert.False(t, a.IsAdmin(r))
    90  	})
    91  
    92  	t.Run("admin", func(t *testing.T) {
    93  		mockCtrl := gomock.NewController(t)
    94  		defer mockCtrl.Finish()
    95  		mockACL := sharedtest.NewMockGitHubAccessControl(mockCtrl)
    96  		mockACL.EXPECT().IsValidAdmin().Return(true, nil)
    97  		a.githubACLFactory = func(_ *http.Request) (shared.GitHubAccessControl, error) {
    98  			return mockACL, nil
    99  		}
   100  		assert.True(t, a.IsAdmin(r))
   101  	})
   102  
   103  	t.Run("nonadmin", func(t *testing.T) {
   104  		mockCtrl := gomock.NewController(t)
   105  		defer mockCtrl.Finish()
   106  		mockACL := sharedtest.NewMockGitHubAccessControl(mockCtrl)
   107  		mockACL.EXPECT().IsValidAdmin().Return(false, nil)
   108  		a.githubACLFactory = func(_ *http.Request) (shared.GitHubAccessControl, error) {
   109  			return mockACL, nil
   110  		}
   111  		assert.False(t, a.IsAdmin(r))
   112  	})
   113  }
   114  
   115  func TestUploadToGCS(t *testing.T) {
   116  	ctx := context.Background()
   117  	a := NewAPI(ctx).(*apiImpl)
   118  	mGcs := mockGcs{}
   119  	a.gcs = &mGcs
   120  
   121  	err := a.UploadToGCS("gs://test_bucket/path/to/test.json", strings.NewReader("test content"), false)
   122  	assert.Nil(t, err)
   123  	assert.Equal(t, "test_bucket", mGcs.mockWriter.bucketName)
   124  	assert.Equal(t, "path/to/test.json", mGcs.mockWriter.fileName)
   125  	assert.Equal(t, "test content", string(mGcs.mockWriter.finalContent))
   126  }
   127  
   128  func TestUploadToGCS_handlesErrors(t *testing.T) {
   129  	ctx := context.Background()
   130  	a := NewAPI(ctx).(*apiImpl)
   131  
   132  	errNew := fmt.Errorf("error creating writer")
   133  	a.gcs = &mockGcs{errOnNew: errNew}
   134  	err := a.UploadToGCS("gs://bucket/test.json", strings.NewReader(""), false)
   135  	assert.Equal(t, errNew, err)
   136  
   137  	errClose := fmt.Errorf("error closing writer")
   138  	a.gcs = &mockGcs{mockWriter: mockGcsWriter{errOnClose: errClose}}
   139  	err = a.UploadToGCS("gs://bucket/test.json", strings.NewReader(""), false)
   140  	assert.Equal(t, errClose, err)
   141  
   142  	a.gcs = &mockGcs{}
   143  	err = a.UploadToGCS("/bucket/test.json", strings.NewReader(""), false)
   144  	assert.EqualError(t, err, "invalid GCS path: /bucket/test.json")
   145  }
   146  
   147  func TestScheduleResultsTask(t *testing.T) {
   148  	ctx, done, err := sharedtest.NewAEContext(true)
   149  	assert.Nil(t, err)
   150  	defer done()
   151  	mockCtrl := gomock.NewController(t)
   152  	defer mockCtrl.Finish()
   153  	mockAE := sharedtest.NewMockAppEngineAPI(mockCtrl)
   154  	mockAE.EXPECT().Context().Return(ctx).AnyTimes()
   155  
   156  	a := NewAPI(ctx).(*apiImpl)
   157  	a.AppEngineAPI = mockAE
   158  	results := []string{"gs://blade-runner/test.json", "http://wpt.fyi/test.json.gz"}
   159  	screenshots := []string{"gs://blade-runner/test.db"}
   160  	var id string
   161  	mockAE.EXPECT().ScheduleTask(ResultsQueue, gomock.Any(), ResultsTarget, gomock.Any()).DoAndReturn(
   162  		func(queueName, taskName, target string, params url.Values) (string, error) {
   163  			assert.Equal(t, results, params["results"])
   164  			assert.Equal(t, screenshots, params["screenshots"])
   165  			assert.Equal(t, "blade-runner", params.Get("uploader"))
   166  			assert.Equal(t, taskName, params.Get("id"))
   167  			id = taskName
   168  			return id, nil
   169  		})
   170  	task, err := a.ScheduleResultsTask("blade-runner", results, screenshots, nil)
   171  	assert.Equal(t, id, task)
   172  	assert.Nil(t, err)
   173  
   174  	intID, err := strconv.ParseInt(id, 10, 64)
   175  	assert.Nil(t, err)
   176  	var pendingRun shared.PendingTestRun
   177  	store := shared.NewAppEngineDatastore(ctx, false)
   178  	store.Get(store.NewIDKey("PendingTestRun", intID), &pendingRun)
   179  	assert.Equal(t, "blade-runner", pendingRun.Uploader)
   180  	assert.Equal(t, shared.StageWptFyiReceived, pendingRun.Stage)
   181  }
   182  
   183  func TestAddTestRun(t *testing.T) {
   184  	ctx, done, err := sharedtest.NewAEContext(true)
   185  	assert.Nil(t, err)
   186  	defer done()
   187  	a := NewAPI(ctx)
   188  
   189  	testRun := shared.TestRun{
   190  		ID: 123456,
   191  		ProductAtRevision: shared.ProductAtRevision{
   192  			Revision: "0123456789",
   193  		},
   194  	}
   195  
   196  	key, err := a.AddTestRun(&testRun)
   197  	assert.Nil(t, err)
   198  	assert.Equal(t, "TestRun", key.Kind())
   199  	assert.Equal(t, int64(123456), key.IntID())
   200  
   201  	var testRun2 shared.TestRun
   202  	store := shared.NewAppEngineDatastore(ctx, false)
   203  	store.Get(key, &testRun2)
   204  	testRun2.ID = key.IntID()
   205  	assert.Equal(t, testRun, testRun2)
   206  }
   207  
   208  func TestUpdatePendingTestRun(t *testing.T) {
   209  	ctx, done, err := sharedtest.NewAEContext(true)
   210  	assert.Nil(t, err)
   211  	defer done()
   212  	a := NewAPI(ctx)
   213  
   214  	sha := "0123456789012345678901234567890123456789"
   215  	store := shared.NewAppEngineDatastore(ctx, false)
   216  	key := store.NewIDKey("PendingTestRun", 1)
   217  	run := shared.PendingTestRun{
   218  		ID:         1,
   219  		CheckRunID: 100,
   220  		Stage:      shared.StageWptFyiReceived,
   221  		ProductAtRevision: shared.ProductAtRevision{
   222  			FullRevisionHash: sha,
   223  		},
   224  	}
   225  	assert.Nil(t, a.UpdatePendingTestRun(run))
   226  	var run2 shared.PendingTestRun
   227  	store.Get(key, &run2)
   228  	assert.Equal(t, shared.StageWptFyiReceived, run2.Stage)
   229  	assert.Equal(t, sha, run2.FullRevisionHash)
   230  	assert.Equal(t, sha[:10], run2.Revision)
   231  
   232  	// CheckRunID should not be updated; Stage should be transitioned.
   233  	run.CheckRunID = 0
   234  	run.Stage = shared.StageValid
   235  	assert.Nil(t, a.UpdatePendingTestRun(run))
   236  	var run3 shared.PendingTestRun
   237  	store.Get(key, &run3)
   238  	assert.Equal(t, int64(100), run3.CheckRunID)
   239  	assert.Equal(t, shared.StageValid, run3.Stage)
   240  	assert.Equal(t, run2.Created, run3.Created)
   241  
   242  	// Stage cannot be transitioned backwards.
   243  	run.Stage = shared.StageWptFyiProcessing
   244  	assert.EqualError(t, a.UpdatePendingTestRun(run),
   245  		"cannot transition from VALID to WPTFYI_PROCESSING")
   246  }