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