code.gitea.io/gitea@v1.19.3/modules/lfs/http_client_test.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package lfs 5 6 import ( 7 "bytes" 8 "context" 9 "io" 10 "net/http" 11 "strings" 12 "testing" 13 14 "code.gitea.io/gitea/modules/json" 15 16 "github.com/stretchr/testify/assert" 17 ) 18 19 type RoundTripFunc func(req *http.Request) *http.Response 20 21 func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { 22 return f(req), nil 23 } 24 25 type DummyTransferAdapter struct{} 26 27 func (a *DummyTransferAdapter) Name() string { 28 return "dummy" 29 } 30 31 func (a *DummyTransferAdapter) Download(ctx context.Context, l *Link) (io.ReadCloser, error) { 32 return io.NopCloser(bytes.NewBufferString("dummy")), nil 33 } 34 35 func (a *DummyTransferAdapter) Upload(ctx context.Context, l *Link, p Pointer, r io.Reader) error { 36 return nil 37 } 38 39 func (a *DummyTransferAdapter) Verify(ctx context.Context, l *Link, p Pointer) error { 40 return nil 41 } 42 43 func lfsTestRoundtripHandler(req *http.Request) *http.Response { 44 var batchResponse *BatchResponse 45 url := req.URL.String() 46 47 if strings.Contains(url, "status-not-ok") { 48 return &http.Response{StatusCode: http.StatusBadRequest} 49 } else if strings.Contains(url, "invalid-json-response") { 50 return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString("invalid json"))} 51 } else if strings.Contains(url, "valid-batch-request-download") { 52 batchResponse = &BatchResponse{ 53 Transfer: "dummy", 54 Objects: []*ObjectResponse{ 55 { 56 Actions: map[string]*Link{ 57 "download": {}, 58 }, 59 }, 60 }, 61 } 62 } else if strings.Contains(url, "valid-batch-request-upload") { 63 batchResponse = &BatchResponse{ 64 Transfer: "dummy", 65 Objects: []*ObjectResponse{ 66 { 67 Actions: map[string]*Link{ 68 "upload": {}, 69 }, 70 }, 71 }, 72 } 73 } else if strings.Contains(url, "response-no-objects") { 74 batchResponse = &BatchResponse{Transfer: "dummy"} 75 } else if strings.Contains(url, "unknown-transfer-adapter") { 76 batchResponse = &BatchResponse{Transfer: "unknown_adapter"} 77 } else if strings.Contains(url, "error-in-response-objects") { 78 batchResponse = &BatchResponse{ 79 Transfer: "dummy", 80 Objects: []*ObjectResponse{ 81 { 82 Error: &ObjectError{ 83 Code: http.StatusNotFound, 84 Message: "Object not found", 85 }, 86 }, 87 }, 88 } 89 } else if strings.Contains(url, "empty-actions-map") { 90 batchResponse = &BatchResponse{ 91 Transfer: "dummy", 92 Objects: []*ObjectResponse{ 93 { 94 Actions: map[string]*Link{}, 95 }, 96 }, 97 } 98 } else if strings.Contains(url, "download-actions-map") { 99 batchResponse = &BatchResponse{ 100 Transfer: "dummy", 101 Objects: []*ObjectResponse{ 102 { 103 Actions: map[string]*Link{ 104 "download": {}, 105 }, 106 }, 107 }, 108 } 109 } else if strings.Contains(url, "upload-actions-map") { 110 batchResponse = &BatchResponse{ 111 Transfer: "dummy", 112 Objects: []*ObjectResponse{ 113 { 114 Actions: map[string]*Link{ 115 "upload": {}, 116 }, 117 }, 118 }, 119 } 120 } else if strings.Contains(url, "verify-actions-map") { 121 batchResponse = &BatchResponse{ 122 Transfer: "dummy", 123 Objects: []*ObjectResponse{ 124 { 125 Actions: map[string]*Link{ 126 "verify": {}, 127 }, 128 }, 129 }, 130 } 131 } else if strings.Contains(url, "unknown-actions-map") { 132 batchResponse = &BatchResponse{ 133 Transfer: "dummy", 134 Objects: []*ObjectResponse{ 135 { 136 Actions: map[string]*Link{ 137 "unknown": {}, 138 }, 139 }, 140 }, 141 } 142 } else { 143 return nil 144 } 145 146 payload := new(bytes.Buffer) 147 json.NewEncoder(payload).Encode(batchResponse) 148 149 return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(payload)} 150 } 151 152 func TestHTTPClientDownload(t *testing.T) { 153 p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6} 154 155 hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response { 156 assert.Equal(t, "POST", req.Method) 157 assert.Equal(t, MediaType, req.Header.Get("Content-type")) 158 assert.Equal(t, MediaType, req.Header.Get("Accept")) 159 160 var batchRequest BatchRequest 161 err := json.NewDecoder(req.Body).Decode(&batchRequest) 162 assert.NoError(t, err) 163 164 assert.Equal(t, "download", batchRequest.Operation) 165 assert.Equal(t, 1, len(batchRequest.Objects)) 166 assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid) 167 assert.Equal(t, p.Size, batchRequest.Objects[0].Size) 168 169 return lfsTestRoundtripHandler(req) 170 })} 171 dummy := &DummyTransferAdapter{} 172 173 cases := []struct { 174 endpoint string 175 expectederror string 176 }{ 177 // case 0 178 { 179 endpoint: "https://status-not-ok.io", 180 expectederror: "Unexpected server response: ", 181 }, 182 // case 1 183 { 184 endpoint: "https://invalid-json-response.io", 185 expectederror: "invalid json", 186 }, 187 // case 2 188 { 189 endpoint: "https://valid-batch-request-download.io", 190 expectederror: "", 191 }, 192 // case 3 193 { 194 endpoint: "https://response-no-objects.io", 195 expectederror: "", 196 }, 197 // case 4 198 { 199 endpoint: "https://unknown-transfer-adapter.io", 200 expectederror: "TransferAdapter not found: ", 201 }, 202 // case 5 203 { 204 endpoint: "https://error-in-response-objects.io", 205 expectederror: "Object not found", 206 }, 207 // case 6 208 { 209 endpoint: "https://empty-actions-map.io", 210 expectederror: "Missing action 'download'", 211 }, 212 // case 7 213 { 214 endpoint: "https://download-actions-map.io", 215 expectederror: "", 216 }, 217 // case 8 218 { 219 endpoint: "https://upload-actions-map.io", 220 expectederror: "Missing action 'download'", 221 }, 222 // case 9 223 { 224 endpoint: "https://verify-actions-map.io", 225 expectederror: "Missing action 'download'", 226 }, 227 // case 10 228 { 229 endpoint: "https://unknown-actions-map.io", 230 expectederror: "Missing action 'download'", 231 }, 232 } 233 234 for n, c := range cases { 235 client := &HTTPClient{ 236 client: hc, 237 endpoint: c.endpoint, 238 transfers: make(map[string]TransferAdapter), 239 } 240 client.transfers["dummy"] = dummy 241 242 err := client.Download(context.Background(), []Pointer{p}, func(p Pointer, content io.ReadCloser, objectError error) error { 243 if objectError != nil { 244 return objectError 245 } 246 b, err := io.ReadAll(content) 247 assert.NoError(t, err) 248 assert.Equal(t, []byte("dummy"), b) 249 return nil 250 }) 251 if len(c.expectederror) > 0 { 252 assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) 253 } else { 254 assert.NoError(t, err, "case %d", n) 255 } 256 } 257 } 258 259 func TestHTTPClientUpload(t *testing.T) { 260 p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6} 261 262 hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response { 263 assert.Equal(t, "POST", req.Method) 264 assert.Equal(t, MediaType, req.Header.Get("Content-type")) 265 assert.Equal(t, MediaType, req.Header.Get("Accept")) 266 267 var batchRequest BatchRequest 268 err := json.NewDecoder(req.Body).Decode(&batchRequest) 269 assert.NoError(t, err) 270 271 assert.Equal(t, "upload", batchRequest.Operation) 272 assert.Equal(t, 1, len(batchRequest.Objects)) 273 assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid) 274 assert.Equal(t, p.Size, batchRequest.Objects[0].Size) 275 276 return lfsTestRoundtripHandler(req) 277 })} 278 dummy := &DummyTransferAdapter{} 279 280 cases := []struct { 281 endpoint string 282 expectederror string 283 }{ 284 // case 0 285 { 286 endpoint: "https://status-not-ok.io", 287 expectederror: "Unexpected server response: ", 288 }, 289 // case 1 290 { 291 endpoint: "https://invalid-json-response.io", 292 expectederror: "invalid json", 293 }, 294 // case 2 295 { 296 endpoint: "https://valid-batch-request-upload.io", 297 expectederror: "", 298 }, 299 // case 3 300 { 301 endpoint: "https://response-no-objects.io", 302 expectederror: "", 303 }, 304 // case 4 305 { 306 endpoint: "https://unknown-transfer-adapter.io", 307 expectederror: "TransferAdapter not found: ", 308 }, 309 // case 5 310 { 311 endpoint: "https://error-in-response-objects.io", 312 expectederror: "Object not found", 313 }, 314 // case 6 315 { 316 endpoint: "https://empty-actions-map.io", 317 expectederror: "", 318 }, 319 // case 7 320 { 321 endpoint: "https://download-actions-map.io", 322 expectederror: "Missing action 'upload'", 323 }, 324 // case 8 325 { 326 endpoint: "https://upload-actions-map.io", 327 expectederror: "", 328 }, 329 // case 9 330 { 331 endpoint: "https://verify-actions-map.io", 332 expectederror: "Missing action 'upload'", 333 }, 334 // case 10 335 { 336 endpoint: "https://unknown-actions-map.io", 337 expectederror: "Missing action 'upload'", 338 }, 339 } 340 341 for n, c := range cases { 342 client := &HTTPClient{ 343 client: hc, 344 endpoint: c.endpoint, 345 transfers: make(map[string]TransferAdapter), 346 } 347 client.transfers["dummy"] = dummy 348 349 err := client.Upload(context.Background(), []Pointer{p}, func(p Pointer, objectError error) (io.ReadCloser, error) { 350 return io.NopCloser(new(bytes.Buffer)), objectError 351 }) 352 if len(c.expectederror) > 0 { 353 assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) 354 } else { 355 assert.NoError(t, err, "case %d", n) 356 } 357 } 358 }