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 }