github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/shared/web_features_manifest_github_download_test.go (about) 1 // Copyright 2024 The WPT Dashboard Project. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 //go:build small 6 7 package shared 8 9 import ( 10 "bytes" 11 "compress/gzip" 12 "context" 13 "encoding/json" 14 "errors" 15 "io" 16 "net/http" 17 "os" 18 "path/filepath" 19 "testing" 20 21 "github.com/google/go-github/v47/github" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 ) 25 26 var compressedWebFeaturesManifestFilePath = filepath.Join("web_features_manifest_testdata", "WEB_FEATURES_MANIFEST.json.gz") 27 28 func createWebFeaturesTestdata() { 29 v1Manifest := struct { 30 Version int `json:"version,omitempty"` 31 Data map[string][]string `json:"data,omitempty"` 32 }{ 33 Version: 1, 34 Data: map[string][]string{ 35 "grid": {"test1.js", "test2.js"}, 36 "subgrid": {"test3.js", "test4.js"}, 37 }, 38 } 39 jsonData, err := json.Marshal(v1Manifest) 40 if err != nil { 41 panic(err) 42 } 43 44 // Create a buffer for compressing the JSON. 45 var buf bytes.Buffer 46 47 // Create a gzip writer and write the JSON to it. 48 gz := gzip.NewWriter(&buf) 49 if _, err := gz.Write(jsonData); err != nil { 50 panic(err) 51 } 52 if err := gz.Close(); err != nil { 53 panic(err) 54 } 55 56 // Write the compressed data to a file. 57 if err := os.WriteFile(compressedWebFeaturesManifestFilePath, buf.Bytes(), 0644); err != nil { 58 panic(err) 59 } 60 } 61 62 func TestResponseBodyTransformer_Success(t *testing.T) { 63 updateGolden := false // Switch this when we want to update the golden file. 64 if updateGolden { 65 createWebFeaturesTestdata() 66 } 67 f, err := os.Open(compressedWebFeaturesManifestFilePath) 68 defer f.Close() 69 require.NoError(t, err) 70 71 transformer := gzipBodyTransformer{} 72 reader, err := transformer.Transform(f) 73 defer reader.Close() 74 require.NoError(t, err) 75 76 rawBytes, err := io.ReadAll(reader) 77 require.NoError(t, err) 78 79 assert.Equal(t, `{"version":1,"data":{"grid":["test1.js","test2.js"],"subgrid":["test3.js","test4.js"]}}`, string(rawBytes)) 80 } 81 82 type RoundTripFunc struct { 83 function func(req *http.Request) *http.Response 84 err error 85 } 86 87 func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { 88 return f.function(req), f.err 89 } 90 91 type mockBodyTransformerInput struct { 92 expectedBody string 93 output io.ReadCloser 94 err error 95 } 96 97 type mockBodyTransformer struct { 98 t *testing.T 99 mockBodyTransformerInput 100 } 101 102 func (tr mockBodyTransformer) Transform(body io.Reader) (io.ReadCloser, error) { 103 bodyBytes, err := io.ReadAll(body) 104 require.NoError(tr.t, err) 105 assert.Equal(tr.t, tr.expectedBody, string(bodyBytes)) 106 return tr.output, tr.err 107 } 108 109 type mockRepositoryReleaseGetter struct { 110 t *testing.T 111 mockRepositoryReleaseGetterInput 112 } 113 114 type mockRepositoryReleaseGetterInput struct { 115 expectedOwner string 116 expectedRepo string 117 repoRelease *github.RepositoryRelease 118 resp *github.Response 119 err error 120 } 121 122 func (g mockRepositoryReleaseGetter) GetLatestRelease( 123 ctx context.Context, 124 owner, repo string) (*github.RepositoryRelease, *github.Response, error) { 125 require.Equal(g.t, g.expectedOwner, owner) 126 require.Equal(g.t, g.expectedRepo, repo) 127 return g.repoRelease, g.resp, g.err 128 } 129 130 /* 131 Truncated example output from GitHub API. 132 Useful for building the returned responses in TestGitHubWebFeaturesManifestDownloader_Download. 133 134 gh release view --repo web-platform-tests/wpt --json assets 135 { 136 "assets": [ 137 { 138 "apiUrl": "https://api.github.com/repos/web-platform-tests/wpt/releases/assets/147533430", 139 "contentType": "application/octet-stream", 140 "createdAt": "2024-01-24T14:40:18Z", 141 "downloadCount": 0, 142 "id": "RA_kwDOADc1Vc4Iyy52", 143 "label": "WEB_FEATURES_MANIFEST.json.gz", 144 "name": "WEB_FEATURES_MANIFEST-f8871bc568c2cf86b38cb70f28a9d5f707e19259.json.gz", 145 "size": 38815, 146 "state": "uploaded", 147 "updatedAt": "2024-01-24T14:40:18Z", 148 "url": "https://github.com/web-platform-tests/wpt/releases/download/merge_pr_41522/WEB_FEATURES_MANIFEST-f8871bc568c2cf86b38cb70f28a9d5f707e19259.json.gz" 149 } 150 ] 151 } 152 */ 153 154 func TestGitHubWebFeaturesManifestDownloader_Download(t *testing.T) { 155 // Test cases for Download 156 tests := []struct { 157 name string 158 releaseGetterInput mockRepositoryReleaseGetterInput 159 roundTrip RoundTripFunc 160 transformer mockBodyTransformerInput 161 expectedBody []byte 162 expectedError error 163 }{ 164 { 165 name: "successful download", 166 releaseGetterInput: mockRepositoryReleaseGetterInput{ 167 expectedOwner: "web-platform-tests", 168 expectedRepo: "wpt", 169 repoRelease: &github.RepositoryRelease{ 170 Assets: []*github.ReleaseAsset{ 171 { 172 Label: github.String("WEB_FEATURES_MANIFEST.json.gz"), 173 BrowserDownloadURL: github.String("https://example.com/WEB_FEATURES_MANIFEST.json.gz"), 174 }, 175 }, 176 }, 177 resp: &github.Response{}, 178 err: nil, 179 }, 180 roundTrip: RoundTripFunc{function: func(req *http.Request) *http.Response { 181 assert.Equal(t, "https://example.com/WEB_FEATURES_MANIFEST.json.gz", req.URL.String()) 182 return &http.Response{ 183 StatusCode: http.StatusOK, 184 ContentLength: int64(len("raw data")), 185 Body: io.NopCloser(bytes.NewBufferString("raw data")), 186 } 187 }, err: nil}, 188 transformer: mockBodyTransformerInput{ 189 expectedBody: "raw data", 190 output: io.NopCloser(bytes.NewBufferString("transformed data")), 191 err: nil, 192 }, 193 expectedBody: []byte("transformed data"), 194 expectedError: nil, 195 }, 196 { 197 name: "error getting latest release", 198 releaseGetterInput: mockRepositoryReleaseGetterInput{ 199 expectedOwner: "web-platform-tests", 200 expectedRepo: "wpt", 201 repoRelease: nil, 202 resp: nil, 203 err: errors.New("fake GitHub client error"), 204 }, 205 expectedBody: nil, 206 expectedError: errUnableToRetrieveGitHubRelease, 207 }, 208 { 209 name: "manifest file not found", 210 releaseGetterInput: mockRepositoryReleaseGetterInput{ 211 expectedOwner: "web-platform-tests", 212 expectedRepo: "wpt", 213 repoRelease: &github.RepositoryRelease{ 214 Assets: []*github.ReleaseAsset{}, 215 }, 216 resp: &github.Response{}, 217 err: nil, 218 }, 219 expectedBody: nil, 220 expectedError: errNoWebFeaturesManifestFileFound, 221 }, 222 { 223 name: "error downloading asset", 224 releaseGetterInput: mockRepositoryReleaseGetterInput{ 225 expectedOwner: "web-platform-tests", 226 expectedRepo: "wpt", 227 repoRelease: &github.RepositoryRelease{ 228 Assets: []*github.ReleaseAsset{ 229 { 230 Label: github.String("WEB_FEATURES_MANIFEST.json.gz"), 231 BrowserDownloadURL: github.String("https://example.com/WEB_FEATURES_MANIFEST.json.gz"), 232 }, 233 }, 234 }, 235 resp: &github.Response{}, 236 err: nil, 237 }, 238 roundTrip: RoundTripFunc{function: func(req *http.Request) *http.Response { 239 assert.Equal(t, "https://example.com/WEB_FEATURES_MANIFEST.json.gz", req.URL.String()) 240 return &http.Response{ 241 StatusCode: http.StatusInternalServerError, 242 } 243 }, err: errors.New("simulated network error")}, 244 expectedBody: nil, 245 expectedError: errGitHubAssetDownloadFailedToComplete, 246 }, 247 { 248 name: "empty response body", 249 releaseGetterInput: mockRepositoryReleaseGetterInput{ 250 expectedOwner: "web-platform-tests", 251 expectedRepo: "wpt", 252 repoRelease: &github.RepositoryRelease{ 253 Assets: []*github.ReleaseAsset{ 254 { 255 Label: github.String("WEB_FEATURES_MANIFEST.json.gz"), 256 BrowserDownloadURL: github.String("https://example.com/WEB_FEATURES_MANIFEST.json.gz"), 257 }, 258 }, 259 }, 260 resp: &github.Response{}, 261 err: nil, 262 }, 263 roundTrip: RoundTripFunc{function: func(req *http.Request) *http.Response { 264 assert.Equal(t, "https://example.com/WEB_FEATURES_MANIFEST.json.gz", req.URL.String()) 265 return &http.Response{ 266 StatusCode: http.StatusNoContent, 267 Body: nil, 268 } 269 }, err: nil}, 270 expectedBody: nil, 271 expectedError: errMissingBodyDuringWebFeaturesManifestDownload, 272 }, 273 } 274 for _, tc := range tests { 275 t.Run(tc.name, func(t *testing.T) { 276 getter := mockRepositoryReleaseGetter{t, tc.releaseGetterInput} 277 httpClient := &http.Client{ 278 Transport: tc.roundTrip, 279 } 280 downloader := newGitHubWebFeaturesManifestDownloader(httpClient, getter) 281 downloader.bodyTransformer = mockBodyTransformer{t, tc.transformer} 282 body, err := downloader.Download(context.Background()) 283 if !errors.Is(err, tc.expectedError) { 284 t.Errorf("Download() returned unexpected error: (%v). expected error: (%v).", err, tc.expectedError) 285 } 286 287 // No need to compare the body if there's an error. 288 if err != nil { 289 return 290 } 291 292 bodyBytes, err := io.ReadAll(body) 293 require.NoError(t, err) 294 assert.Equal(t, tc.expectedBody, bodyBytes) 295 }) 296 } 297 }