code.gitea.io/gitea@v1.22.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, AcceptHeader, 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.Len(t, batchRequest.Objects, 1) 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: io.ErrUnexpectedEOF.Error(), 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: map[string]TransferAdapter{ 239 "dummy": dummy, 240 }, 241 } 242 243 err := client.Download(context.Background(), []Pointer{p}, func(p Pointer, content io.ReadCloser, objectError error) error { 244 if objectError != nil { 245 return objectError 246 } 247 b, err := io.ReadAll(content) 248 assert.NoError(t, err) 249 assert.Equal(t, []byte("dummy"), b) 250 return nil 251 }) 252 if len(c.expectederror) > 0 { 253 assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) 254 } else { 255 assert.NoError(t, err, "case %d", n) 256 } 257 } 258 } 259 260 func TestHTTPClientUpload(t *testing.T) { 261 p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6} 262 263 hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response { 264 assert.Equal(t, "POST", req.Method) 265 assert.Equal(t, MediaType, req.Header.Get("Content-type")) 266 assert.Equal(t, AcceptHeader, req.Header.Get("Accept")) 267 268 var batchRequest BatchRequest 269 err := json.NewDecoder(req.Body).Decode(&batchRequest) 270 assert.NoError(t, err) 271 272 assert.Equal(t, "upload", batchRequest.Operation) 273 assert.Len(t, batchRequest.Objects, 1) 274 assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid) 275 assert.Equal(t, p.Size, batchRequest.Objects[0].Size) 276 277 return lfsTestRoundtripHandler(req) 278 })} 279 dummy := &DummyTransferAdapter{} 280 281 cases := []struct { 282 endpoint string 283 expectederror string 284 }{ 285 // case 0 286 { 287 endpoint: "https://status-not-ok.io", 288 expectederror: io.ErrUnexpectedEOF.Error(), 289 }, 290 // case 1 291 { 292 endpoint: "https://invalid-json-response.io", 293 expectederror: "invalid json", 294 }, 295 // case 2 296 { 297 endpoint: "https://valid-batch-request-upload.io", 298 expectederror: "", 299 }, 300 // case 3 301 { 302 endpoint: "https://response-no-objects.io", 303 expectederror: "", 304 }, 305 // case 4 306 { 307 endpoint: "https://unknown-transfer-adapter.io", 308 expectederror: "TransferAdapter not found: ", 309 }, 310 // case 5 311 { 312 endpoint: "https://error-in-response-objects.io", 313 expectederror: "Object not found", 314 }, 315 // case 6 316 { 317 endpoint: "https://empty-actions-map.io", 318 expectederror: "", 319 }, 320 // case 7 321 { 322 endpoint: "https://download-actions-map.io", 323 expectederror: "missing action 'upload'", 324 }, 325 // case 8 326 { 327 endpoint: "https://upload-actions-map.io", 328 expectederror: "", 329 }, 330 // case 9 331 { 332 endpoint: "https://verify-actions-map.io", 333 expectederror: "missing action 'upload'", 334 }, 335 // case 10 336 { 337 endpoint: "https://unknown-actions-map.io", 338 expectederror: "missing action 'upload'", 339 }, 340 } 341 342 for n, c := range cases { 343 client := &HTTPClient{ 344 client: hc, 345 endpoint: c.endpoint, 346 transfers: map[string]TransferAdapter{ 347 "dummy": dummy, 348 }, 349 } 350 351 err := client.Upload(context.Background(), []Pointer{p}, func(p Pointer, objectError error) (io.ReadCloser, error) { 352 return io.NopCloser(new(bytes.Buffer)), objectError 353 }) 354 if len(c.expectederror) > 0 { 355 assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) 356 } else { 357 assert.NoError(t, err, "case %d", n) 358 } 359 } 360 }