github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/uploader/upload_manager_test.go (about) 1 //go:build unit 2 // +build unit 3 4 package uploader_test 5 6 import ( 7 "bytes" 8 "context" 9 "crypto/md5" 10 "encoding/base64" 11 "encoding/hex" 12 "encoding/json" 13 "io" 14 "math/rand" 15 "net/http" 16 "net/http/httptest" 17 "os" 18 "path/filepath" 19 "strings" 20 "sync" 21 "testing" 22 "time" 23 24 "github.com/gorilla/mux" 25 internal_io "github.com/qiniu/go-sdk/v7/internal/io" 26 "github.com/qiniu/go-sdk/v7/storagev2/apis" 27 "github.com/qiniu/go-sdk/v7/storagev2/credentials" 28 "github.com/qiniu/go-sdk/v7/storagev2/http_client" 29 "github.com/qiniu/go-sdk/v7/storagev2/region" 30 "github.com/qiniu/go-sdk/v7/storagev2/uploader" 31 ) 32 33 func TestUploadManagerUploadFile(t *testing.T) { 34 tmpFile, err := os.CreateTemp("", "multi-parts-uploader-test-*") 35 if err != nil { 36 t.Fatal(err) 37 } 38 defer os.Remove(tmpFile.Name()) 39 defer tmpFile.Close() 40 41 r := rand.New(rand.NewSource(time.Now().UnixNano())) 42 if _, err = io.CopyN(tmpFile, r, 5*1024*1024); err != nil { 43 t.Fatal(err) 44 } 45 if _, err = tmpFile.Seek(0, io.SeekStart); err != nil { 46 t.Fatal(err) 47 } 48 49 serveMux := mux.NewRouter() 50 serveMux.HandleFunc("/buckets/{bucketName}/objects/{encodedObjectName}/uploads", func(w http.ResponseWriter, r *http.Request) { 51 if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") { 52 t.Fatalf("unexpected authorization") 53 } 54 vars := mux.Vars(r) 55 if vars["bucketName"] != "testbucket" { 56 t.Fatalf("unexpected bucket name") 57 } 58 objectBytes, err := base64.URLEncoding.DecodeString(vars["encodedObjectName"]) 59 if err != nil { 60 t.Fatal(err) 61 } else if string(objectBytes) != "testkey" { 62 t.Fatalf("unexpected object name") 63 } 64 jsonBytes, err := json.Marshal(&apis.ResumableUploadV2InitiateMultipartUploadResponse{ 65 UploadId: "testuploadID", 66 ExpiredAt: time.Now().Add(1 * time.Hour).Unix(), 67 }) 68 if err != nil { 69 t.Fatal(err) 70 } 71 w.Header().Add("X-ReqId", "fakereqid") 72 w.Write(jsonBytes) 73 }).Methods(http.MethodPost) 74 serveMux.HandleFunc("/buckets/{bucketName}/objects/{encodedObjectName}/uploads/{uploadID}/{partNumber}", func(w http.ResponseWriter, r *http.Request) { 75 if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") { 76 t.Fatalf("unexpected authorization") 77 } 78 vars := mux.Vars(r) 79 if vars["bucketName"] != "testbucket" { 80 t.Fatalf("unexpected bucket name") 81 } 82 objectBytes, err := base64.URLEncoding.DecodeString(vars["encodedObjectName"]) 83 if err != nil { 84 t.Fatal(err) 85 } else if string(objectBytes) != "testkey" { 86 t.Fatalf("unexpected object name") 87 } 88 if vars["uploadID"] != "testuploadID" { 89 t.Fatalf("unexpected upload id") 90 } 91 actualBody, err := internal_io.ReadAll(r.Body) 92 if err != nil { 93 t.Fatal(err) 94 } 95 var expectedBody, jsonBody []byte 96 switch vars["partNumber"] { 97 case "1": 98 expectedBody, err = internal_io.ReadAll(io.NewSectionReader(tmpFile, 0, 4*1024*1024)) 99 if err != nil { 100 t.Fatal(err) 101 } 102 case "2": 103 expectedBody, err = internal_io.ReadAll(io.NewSectionReader(tmpFile, 4*1024*1024, 1024*1024)) 104 if err != nil { 105 t.Fatal(err) 106 } 107 default: 108 t.Fatalf("unexpected part number") 109 } 110 if !bytes.Equal(actualBody, expectedBody) { 111 t.Fatalf("unexpected body") 112 } 113 md5Sum := md5.Sum(actualBody) 114 if r.Header.Get("Content-MD5") != hex.EncodeToString(md5Sum[:]) { 115 t.Fatalf("unexpected content-md5") 116 } 117 switch vars["partNumber"] { 118 case "1": 119 jsonBody, err = json.Marshal(&apis.ResumableUploadV2UploadPartResponse{ 120 Etag: "testetag1", 121 Md5: r.Header.Get("Content-MD5"), 122 }) 123 case "2": 124 jsonBody, err = json.Marshal(&apis.ResumableUploadV2UploadPartResponse{ 125 Etag: "testetag2", 126 Md5: r.Header.Get("Content-MD5"), 127 }) 128 } 129 if err != nil { 130 t.Fatal(err) 131 } 132 w.Header().Add("X-ReqId", "fakereqid") 133 w.Write(jsonBody) 134 }).Methods(http.MethodPut) 135 serveMux.HandleFunc("/buckets/{bucketName}/objects/{encodedObjectName}/uploads/{uploadID}", func(w http.ResponseWriter, r *http.Request) { 136 if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") { 137 t.Fatalf("unexpected authorization") 138 } 139 vars := mux.Vars(r) 140 if vars["bucketName"] != "testbucket" { 141 t.Fatalf("unexpected bucket name") 142 } 143 objectBytes, err := base64.URLEncoding.DecodeString(vars["encodedObjectName"]) 144 if err != nil { 145 t.Fatal(err) 146 } else if string(objectBytes) != "testkey" { 147 t.Fatalf("unexpected object name") 148 } 149 if vars["uploadID"] != "testuploadID" { 150 t.Fatalf("unexpected upload id") 151 } 152 requestBodyBytes, err := internal_io.ReadAll(r.Body) 153 if err != nil { 154 t.Fatal(err) 155 } 156 var body apis.ResumableUploadV2CompleteMultipartUploadRequest 157 if err = body.UnmarshalJSON(requestBodyBytes); err != nil { 158 t.Fatalf("unexpected request body") 159 } 160 if len(body.Parts) != 2 { 161 t.Fatalf("unexpected parts") 162 } else if body.Parts[0].PartNumber != 1 { 163 t.Fatalf("unexpected part number") 164 } else if body.Parts[0].Etag != "testetag1" { 165 t.Fatalf("unexpected part number") 166 } else if body.Parts[1].PartNumber != 2 { 167 t.Fatalf("unexpected part number") 168 } else if body.Parts[1].Etag != "testetag2" { 169 t.Fatalf("unexpected part number") 170 } 171 if body.FileName != "testfilename" { 172 t.Fatalf("unexpected fileName") 173 } 174 if body.MimeType != "application/json" { 175 t.Fatalf("unexpected mimeType") 176 } 177 if len(body.Metadata) != 2 { 178 t.Fatalf("unexpected metadata") 179 } else if body.Metadata["x-qn-meta-a"] != "b" { 180 t.Fatalf("unexpected x-qn-meta-a") 181 } else if body.Metadata["x-qn-meta-c"] != "d" { 182 t.Fatalf("unexpected x-qn-meta-c") 183 } else if body.CustomVars["x:a"] != "b" { 184 t.Fatalf("unexpected x:a") 185 } else if body.CustomVars["x:c"] != "d" { 186 t.Fatalf("unexpected x:c") 187 } 188 w.Header().Add("X-ReqId", "fakereqid") 189 w.Write([]byte(`{"ok":true}`)) 190 }).Methods(http.MethodPost) 191 server := httptest.NewServer(serveMux) 192 defer server.Close() 193 194 var ( 195 uploadManager = uploader.NewUploadManager(&uploader.UploadManagerOptions{ 196 Options: http_client.Options{ 197 Regions: ®ion.Region{Up: region.Endpoints{Preferred: []string{server.URL}}}, 198 Credentials: credentials.NewCredentials("testak", "testsk"), 199 }, 200 Concurrency: 2, 201 }) 202 returnValue struct { 203 Ok bool `json:"ok"` 204 } 205 key = "testkey" 206 ) 207 208 err = uploadManager.UploadFile(context.Background(), tmpFile.Name(), &uploader.ObjectOptions{ 209 BucketName: "testbucket", 210 ObjectName: &key, 211 FileName: "testfilename", 212 ContentType: "application/json", 213 Metadata: map[string]string{"a": "b", "c": "d"}, 214 CustomVars: map[string]string{"a": "b", "c": "d"}, 215 }, &returnValue) 216 if err != nil { 217 t.Fatal(err) 218 } else if !returnValue.Ok { 219 t.Fatalf("unexpected response body") 220 } 221 } 222 223 func TestUploadManagerUploadReader(t *testing.T) { 224 tmpFile, err := os.CreateTemp("", "multi-parts-uploader-test-*") 225 if err != nil { 226 t.Fatal(err) 227 } 228 defer os.Remove(tmpFile.Name()) 229 defer tmpFile.Close() 230 231 r := rand.New(rand.NewSource(time.Now().UnixNano())) 232 if _, err = io.CopyN(tmpFile, r, 5*1024*1024); err != nil { 233 t.Fatal(err) 234 } 235 if _, err = tmpFile.Seek(0, io.SeekStart); err != nil { 236 t.Fatal(err) 237 } 238 239 serveMux := mux.NewRouter() 240 serveMux.HandleFunc("/buckets/{bucketName}/objects/{encodedObjectName}/uploads", func(w http.ResponseWriter, r *http.Request) { 241 if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") { 242 t.Fatalf("unexpected authorization") 243 } 244 vars := mux.Vars(r) 245 if vars["bucketName"] != "testbucket" { 246 t.Fatalf("unexpected bucket name") 247 } 248 objectBytes, err := base64.URLEncoding.DecodeString(vars["encodedObjectName"]) 249 if err != nil { 250 t.Fatal(err) 251 } else if string(objectBytes) != "testkey" { 252 t.Fatalf("unexpected object name") 253 } 254 jsonBytes, err := json.Marshal(&apis.ResumableUploadV2InitiateMultipartUploadResponse{ 255 UploadId: "testuploadID", 256 ExpiredAt: time.Now().Add(1 * time.Hour).Unix(), 257 }) 258 if err != nil { 259 t.Fatal(err) 260 } 261 w.Header().Add("X-ReqId", "fakereqid") 262 w.Write(jsonBytes) 263 }).Methods(http.MethodPost) 264 serveMux.HandleFunc("/buckets/{bucketName}/objects/{encodedObjectName}/uploads/{uploadID}/{partNumber}", func(w http.ResponseWriter, r *http.Request) { 265 if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") { 266 t.Fatalf("unexpected authorization") 267 } 268 vars := mux.Vars(r) 269 if vars["bucketName"] != "testbucket" { 270 t.Fatalf("unexpected bucket name") 271 } 272 objectBytes, err := base64.URLEncoding.DecodeString(vars["encodedObjectName"]) 273 if err != nil { 274 t.Fatal(err) 275 } else if string(objectBytes) != "testkey" { 276 t.Fatalf("unexpected object name") 277 } 278 if vars["uploadID"] != "testuploadID" { 279 t.Fatalf("unexpected upload id") 280 } 281 actualBody, err := internal_io.ReadAll(r.Body) 282 if err != nil { 283 t.Fatal(err) 284 } 285 var expectedBody, jsonBody []byte 286 switch vars["partNumber"] { 287 case "1": 288 expectedBody, err = internal_io.ReadAll(io.NewSectionReader(tmpFile, 0, 4*1024*1024)) 289 if err != nil { 290 t.Fatal(err) 291 } 292 case "2": 293 expectedBody, err = internal_io.ReadAll(io.NewSectionReader(tmpFile, 4*1024*1024, 1024*1024)) 294 if err != nil { 295 t.Fatal(err) 296 } 297 default: 298 t.Fatalf("unexpected part number") 299 } 300 if !bytes.Equal(actualBody, expectedBody) { 301 t.Fatalf("unexpected body") 302 } 303 md5Sum := md5.Sum(actualBody) 304 if r.Header.Get("Content-MD5") != hex.EncodeToString(md5Sum[:]) { 305 t.Fatalf("unexpected content-md5") 306 } 307 switch vars["partNumber"] { 308 case "1": 309 jsonBody, err = json.Marshal(&apis.ResumableUploadV2UploadPartResponse{ 310 Etag: "testetag1", 311 Md5: r.Header.Get("Content-MD5"), 312 }) 313 case "2": 314 jsonBody, err = json.Marshal(&apis.ResumableUploadV2UploadPartResponse{ 315 Etag: "testetag2", 316 Md5: r.Header.Get("Content-MD5"), 317 }) 318 } 319 if err != nil { 320 t.Fatal(err) 321 } 322 w.Header().Add("X-ReqId", "fakereqid") 323 w.Write(jsonBody) 324 }).Methods(http.MethodPut) 325 serveMux.HandleFunc("/buckets/{bucketName}/objects/{encodedObjectName}/uploads/{uploadID}", func(w http.ResponseWriter, r *http.Request) { 326 if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") { 327 t.Fatalf("unexpected authorization") 328 } 329 vars := mux.Vars(r) 330 if vars["bucketName"] != "testbucket" { 331 t.Fatalf("unexpected bucket name") 332 } 333 objectBytes, err := base64.URLEncoding.DecodeString(vars["encodedObjectName"]) 334 if err != nil { 335 t.Fatal(err) 336 } else if string(objectBytes) != "testkey" { 337 t.Fatalf("unexpected object name") 338 } 339 if vars["uploadID"] != "testuploadID" { 340 t.Fatalf("unexpected upload id") 341 } 342 requestBodyBytes, err := internal_io.ReadAll(r.Body) 343 if err != nil { 344 t.Fatal(err) 345 } 346 var body apis.ResumableUploadV2CompleteMultipartUploadRequest 347 if err = body.UnmarshalJSON(requestBodyBytes); err != nil { 348 t.Fatalf("unexpected request body") 349 } 350 if len(body.Parts) != 2 { 351 t.Fatalf("unexpected parts") 352 } else if body.Parts[0].PartNumber != 1 { 353 t.Fatalf("unexpected part number") 354 } else if body.Parts[0].Etag != "testetag1" { 355 t.Fatalf("unexpected part number") 356 } else if body.Parts[1].PartNumber != 2 { 357 t.Fatalf("unexpected part number") 358 } else if body.Parts[1].Etag != "testetag2" { 359 t.Fatalf("unexpected part number") 360 } 361 if body.FileName != "testfilename" { 362 t.Fatalf("unexpected fileName") 363 } 364 if body.MimeType != "application/json" { 365 t.Fatalf("unexpected mimeType") 366 } 367 if len(body.Metadata) != 2 { 368 t.Fatalf("unexpected metadata") 369 } else if body.Metadata["x-qn-meta-a"] != "b" { 370 t.Fatalf("unexpected x-qn-meta-a") 371 } else if body.Metadata["x-qn-meta-c"] != "d" { 372 t.Fatalf("unexpected x-qn-meta-c") 373 } else if body.CustomVars["x:a"] != "b" { 374 t.Fatalf("unexpected x:a") 375 } else if body.CustomVars["x:c"] != "d" { 376 t.Fatalf("unexpected x:c") 377 } 378 w.Header().Add("X-ReqId", "fakereqid") 379 w.Write([]byte(`{"ok":true}`)) 380 }).Methods(http.MethodPost) 381 server := httptest.NewServer(serveMux) 382 defer server.Close() 383 384 var ( 385 uploadManager = uploader.NewUploadManager(&uploader.UploadManagerOptions{ 386 Options: http_client.Options{ 387 Regions: ®ion.Region{Up: region.Endpoints{Preferred: []string{server.URL}}}, 388 Credentials: credentials.NewCredentials("testak", "testsk"), 389 }, 390 Concurrency: 2, 391 }) 392 returnValue struct { 393 Ok bool `json:"ok"` 394 } 395 key = "testkey" 396 pipeR, pipeW = io.Pipe() 397 wg sync.WaitGroup 398 ) 399 wg.Add(1) 400 defer pipeR.Close() 401 402 go func(t *testing.T, w io.WriteCloser) { 403 defer wg.Done() 404 defer w.Close() 405 if _, err = io.Copy(w, tmpFile); err != nil { 406 t.Error(err) 407 } 408 }(t, pipeW) 409 410 err = uploadManager.UploadReader(context.Background(), pipeR, &uploader.ObjectOptions{ 411 BucketName: "testbucket", 412 ObjectName: &key, 413 FileName: "testfilename", 414 ContentType: "application/json", 415 Metadata: map[string]string{"a": "b", "c": "d"}, 416 CustomVars: map[string]string{"a": "b", "c": "d"}, 417 }, &returnValue) 418 if err != nil { 419 t.Fatal(err) 420 } else if !returnValue.Ok { 421 t.Fatalf("unexpected response body") 422 } 423 424 wg.Wait() 425 } 426 427 func TestUploadManagerUploadDirectory(t *testing.T) { 428 testUploadManagerUploadDirectory(t, true) 429 testUploadManagerUploadDirectory(t, false) 430 } 431 432 func testUploadManagerUploadDirectory(t *testing.T, createDirectory bool) { 433 var localFiles, remoteObjects sync.Map 434 435 tmpDir_1, err := os.MkdirTemp("", "multi-parts-uploader-test-*") 436 if err != nil { 437 t.Fatal(err) 438 } 439 defer os.RemoveAll(tmpDir_1) 440 441 const objectPrefix = "remoteDirectory" 442 remoteObjects.Store(objectPrefix+"/", (*os.File)(nil)) 443 444 r := rand.New(rand.NewSource(time.Now().UnixNano())) 445 446 tmpFile_1, err := os.CreateTemp(tmpDir_1, "multi-parts-uploader-test-*") 447 if err != nil { 448 t.Fatal(err) 449 } 450 defer os.Remove(tmpFile_1.Name()) 451 defer tmpFile_1.Close() 452 453 if _, err = io.CopyN(tmpFile_1, r, 1024*1024); err != nil { 454 t.Fatal(err) 455 } 456 if relativePath, err := filepath.Rel(tmpDir_1, tmpFile_1.Name()); err != nil { 457 t.Fatal(err) 458 } else { 459 remoteObjects.Store(strings.Replace(filepath.Join(objectPrefix, relativePath), string(filepath.Separator), "/", -1), tmpFile_1) 460 } 461 localFiles.Store(tmpFile_1.Name(), uint64(0)) 462 463 tmpDir_2, err := os.MkdirTemp(tmpDir_1, "multi-parts-uploader-test-*") 464 if err != nil { 465 t.Fatal(err) 466 } 467 if relativeDir, err := filepath.Rel(tmpDir_1, tmpDir_2); err != nil { 468 t.Fatal(err) 469 } else { 470 remoteObjects.Store(strings.Replace(filepath.Join(objectPrefix, relativeDir)+string(filepath.Separator), string(filepath.Separator), "/", -1), (*os.File)(nil)) 471 } 472 473 tmpFile_2, err := os.CreateTemp(tmpDir_2, "multi-parts-uploader-test-*") 474 if err != nil { 475 t.Fatal(err) 476 } 477 defer os.Remove(tmpFile_2.Name()) 478 defer tmpFile_2.Close() 479 480 if _, err = io.CopyN(tmpFile_2, r, 1024*1024); err != nil { 481 t.Fatal(err) 482 } 483 if relativePath, err := filepath.Rel(tmpDir_1, tmpFile_2.Name()); err != nil { 484 t.Fatal(err) 485 } else { 486 remoteObjects.Store(strings.Replace(filepath.Join(objectPrefix, relativePath), string(filepath.Separator), "/", -1), tmpFile_2) 487 } 488 localFiles.Store(tmpFile_2.Name(), uint64(0)) 489 490 tmpDir_3, err := os.MkdirTemp(tmpDir_2, "multi-parts-uploader-test-*") 491 if err != nil { 492 t.Fatal(err) 493 } 494 if relativeDir, err := filepath.Rel(tmpDir_1, tmpDir_3); err != nil { 495 t.Fatal(err) 496 } else { 497 remoteObjects.Store(strings.Replace(filepath.Join(objectPrefix, relativeDir)+string(filepath.Separator), string(filepath.Separator), "/", -1), (*os.File)(nil)) 498 } 499 500 tmpFile_3, err := os.CreateTemp(tmpDir_3, "multi-parts-uploader-test-*") 501 if err != nil { 502 t.Fatal(err) 503 } 504 defer os.Remove(tmpFile_3.Name()) 505 defer tmpFile_3.Close() 506 507 if _, err = io.CopyN(tmpFile_3, r, 1024*1024); err != nil { 508 t.Fatal(err) 509 } 510 if relativePath, err := filepath.Rel(tmpDir_1, tmpFile_3.Name()); err != nil { 511 t.Fatal(err) 512 } else { 513 remoteObjects.Store(strings.Replace(filepath.Join(objectPrefix, relativePath), string(filepath.Separator), "/", -1), tmpFile_3) 514 } 515 localFiles.Store(tmpFile_3.Name(), uint64(0)) 516 517 serveMux := http.NewServeMux() 518 serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 519 if r.Method != http.MethodPost { 520 t.Fatalf("unexpected method: %s", r.Method) 521 } 522 if err := r.ParseMultipartForm(2 * 1024 * 1024); err != nil { 523 t.Fatal(err) 524 } 525 if values := r.MultipartForm.Value["token"]; len(values) != 1 || !strings.HasPrefix(values[0], "testak:") { 526 t.Fatalf("unexpected token") 527 } 528 529 key := r.MultipartForm.Value["key"][0] 530 if expectedValue, ok := remoteObjects.Load(key); !ok { 531 t.Fatalf("unexpected key") 532 } else if expectedObject, ok := expectedValue.(*os.File); !ok { 533 t.Fatalf("unexpected key") 534 } else { 535 remoteObjects.Delete(key) 536 multiPartFile := r.MultipartForm.File["file"][0] 537 receivedFile, err := multiPartFile.Open() 538 if err != nil { 539 t.Fatal(err) 540 } 541 defer receivedFile.Close() 542 543 receivedFileBytes, err := internal_io.ReadAll(receivedFile) 544 if err != nil { 545 t.Fatal(err) 546 } 547 548 if expectedObject == nil { 549 if !createDirectory { 550 t.Fatalf("unexpected directory creation") 551 } 552 if len(receivedFileBytes) != 0 { 553 t.Fatalf("content of directory should be empty") 554 } 555 } else { 556 if _, err = expectedObject.Seek(0, io.SeekStart); err != nil { 557 t.Fatal(err) 558 } 559 expectedObjectBytes, err := internal_io.ReadAll(expectedObject) 560 if err != nil { 561 t.Fatal(err) 562 } 563 if !bytes.Equal(expectedObjectBytes, receivedFileBytes) { 564 t.Fatalf("unexpected content") 565 } 566 } 567 } 568 w.Header().Add("X-ReqId", "fakereqid") 569 w.Write([]byte(`{"ok":true}`)) 570 }) 571 server := httptest.NewServer(serveMux) 572 defer server.Close() 573 574 uploadManager := uploader.NewUploadManager(&uploader.UploadManagerOptions{ 575 Options: http_client.Options{ 576 Regions: ®ion.Region{Up: region.Endpoints{Preferred: []string{server.URL}}}, 577 Credentials: credentials.NewCredentials("testak", "testsk"), 578 }, 579 }) 580 581 err = uploadManager.UploadDirectory(context.Background(), tmpDir_1, &uploader.DirectoryOptions{ 582 BucketName: "testbucket", 583 UpdateObjectName: func(path string) string { 584 return objectPrefix + "/" + path 585 }, 586 BeforeObjectUpload: func(filePath string, _ *uploader.ObjectOptions) { 587 if _, ok := localFiles.Load(filePath); !ok { 588 t.Fatalf("unexpected filePath") 589 } 590 }, 591 OnUploadingProgress: func(filePath string, progress *uploader.UploadingProgress) { 592 if progress.TotalSize != 1024*1024 { 593 t.Fatalf("unexpected totalSize") 594 } 595 if lastUploadedValue, ok := localFiles.Load(filePath); !ok { 596 t.Fatalf("unexpected filePath") 597 } else if lastUploaded, ok := lastUploadedValue.(uint64); !ok { 598 t.Fatalf("unexpected filePath") 599 } else if progress.Uploaded < lastUploaded { 600 t.Fatalf("unexpected uploaded") 601 } else { 602 localFiles.Store(filePath, progress.Uploaded) 603 } 604 }, 605 OnObjectUploaded: func(filePath string, info *uploader.UploadedObjectInfo) { 606 if info.Size != 1024*1024 { 607 t.Fatalf("unexpected size") 608 } 609 if _, ok := localFiles.Load(filePath); !ok { 610 t.Fatalf("unexpected filePath") 611 } 612 }, 613 ShouldCreateDirectory: createDirectory, 614 }) 615 if err != nil { 616 t.Fatal(err) 617 } 618 } 619 620 func TestUploadManagerUploadDirectoryWithFilter(t *testing.T) { 621 tmpDir_1, err := os.MkdirTemp("", "multi-parts-uploader-test-*") 622 if err != nil { 623 t.Fatal(err) 624 } 625 defer os.RemoveAll(tmpDir_1) 626 627 tmpFile_1, err := os.CreateTemp(tmpDir_1, "multi-parts-uploader-test-*") 628 if err != nil { 629 t.Fatal(err) 630 } 631 defer os.Remove(tmpFile_1.Name()) 632 defer tmpFile_1.Close() 633 634 tmpFile_2, err := os.CreateTemp(tmpDir_1, "multi-parts-uploader-test-*") 635 if err != nil { 636 t.Fatal(err) 637 } 638 defer os.Remove(tmpFile_2.Name()) 639 defer tmpFile_2.Close() 640 641 keys := make(map[string]struct{}) 642 var keysMutex sync.Mutex 643 644 serveMux := http.NewServeMux() 645 serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 646 if r.Method != http.MethodPost { 647 t.Fatalf("unexpected method: %s", r.Method) 648 } 649 if err := r.ParseMultipartForm(2 * 1024 * 1024); err != nil { 650 t.Fatal(err) 651 } 652 if values := r.MultipartForm.Value["token"]; len(values) != 1 || !strings.HasPrefix(values[0], "testak:") { 653 t.Fatalf("unexpected token") 654 } 655 656 key := r.MultipartForm.Value["key"][0] 657 keysMutex.Lock() 658 keys[key] = struct{}{} 659 keysMutex.Unlock() 660 w.Header().Add("X-ReqId", "fakereqid") 661 w.Write([]byte(`{"ok":true}`)) 662 }) 663 server := httptest.NewServer(serveMux) 664 defer server.Close() 665 666 uploadManager := uploader.NewUploadManager(&uploader.UploadManagerOptions{ 667 Options: http_client.Options{ 668 Regions: ®ion.Region{Up: region.Endpoints{Preferred: []string{server.URL}}}, 669 Credentials: credentials.NewCredentials("testak", "testsk"), 670 }, 671 }) 672 673 err = uploadManager.UploadDirectory(context.Background(), tmpDir_1, &uploader.DirectoryOptions{ 674 BucketName: "testbucket", 675 ShouldCreateDirectory: true, 676 ShouldUploadObject: func(_ string, objectOptions *uploader.ObjectOptions) bool { 677 return objectOptions != nil 678 }, 679 }) 680 if err != nil { 681 t.Fatal(err) 682 } 683 684 if len(keys) != 2 { 685 t.Fatalf("unexpected uploaded oject names count") 686 } 687 if _, ok := keys[filepath.Base(tmpFile_1.Name())]; !ok { 688 t.Fatalf("unexpected uploaded oject name") 689 } 690 if _, ok := keys[filepath.Base(tmpFile_2.Name())]; !ok { 691 t.Fatalf("unexpected uploaded oject name") 692 } 693 }