github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/uploader/multi_parts_uploader_v1_test.go (about) 1 //go:build unit 2 // +build unit 3 4 package uploader_test 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/base64" 10 "encoding/json" 11 "fmt" 12 "hash/crc32" 13 "io" 14 "math/rand" 15 "net/http" 16 "net/http/httptest" 17 "os" 18 "strconv" 19 "strings" 20 "testing" 21 "time" 22 23 "github.com/gorilla/mux" 24 internal_io "github.com/qiniu/go-sdk/v7/internal/io" 25 "github.com/qiniu/go-sdk/v7/storagev2/apis" 26 "github.com/qiniu/go-sdk/v7/storagev2/credentials" 27 "github.com/qiniu/go-sdk/v7/storagev2/http_client" 28 "github.com/qiniu/go-sdk/v7/storagev2/region" 29 "github.com/qiniu/go-sdk/v7/storagev2/uploader" 30 resumablerecorder "github.com/qiniu/go-sdk/v7/storagev2/uploader/resumable_recorder" 31 "github.com/qiniu/go-sdk/v7/storagev2/uploader/source" 32 ) 33 34 func TestMultiPartsUploaderV1(t *testing.T) { 35 tmpFile, err := os.CreateTemp("", "multi-parts-uploader-test-*") 36 if err != nil { 37 t.Fatal(err) 38 } 39 defer os.Remove(tmpFile.Name()) 40 defer tmpFile.Close() 41 42 r := rand.New(rand.NewSource(time.Now().UnixNano())) 43 if _, err = io.CopyN(tmpFile, r, 5*1024*1024); err != nil { 44 t.Fatal(err) 45 } 46 if _, err = tmpFile.Seek(0, io.SeekStart); err != nil { 47 t.Fatal(err) 48 } 49 50 var server *httptest.Server 51 serveMux := mux.NewRouter() 52 serveMux.HandleFunc("/mkblk/4194304", func(w http.ResponseWriter, r *http.Request) { 53 if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") { 54 t.Fatalf("unexpected authorization") 55 } 56 actualBody, err := internal_io.ReadAll(r.Body) 57 if err != nil { 58 t.Fatal(err) 59 } 60 expectedBody, err := internal_io.ReadAll(io.NewSectionReader(tmpFile, 0, 4*1024*1024)) 61 if err != nil { 62 t.Fatal(err) 63 } 64 if !bytes.Equal(actualBody, expectedBody) { 65 t.Fatalf("unexpected body") 66 } 67 jsonBody, err := json.Marshal(&apis.ResumableUploadV1MakeBlockResponse{ 68 Ctx: "testctx1", 69 Checksum: "testchecksum1", 70 Crc32: int64(crc32.ChecksumIEEE(actualBody)), 71 Host: server.URL, 72 ExpiredAt: time.Now().Add(1 * time.Hour).Unix(), 73 }) 74 if err != nil { 75 t.Fatal(err) 76 } 77 w.Header().Add("X-ReqId", "fakereqid") 78 w.Write(jsonBody) 79 }).Methods(http.MethodPost) 80 serveMux.HandleFunc("/mkblk/1048576", func(w http.ResponseWriter, r *http.Request) { 81 if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") { 82 t.Fatalf("unexpected authorization") 83 } 84 actualBody, err := internal_io.ReadAll(r.Body) 85 if err != nil { 86 t.Fatal(err) 87 } 88 expectedBody, err := internal_io.ReadAll(io.NewSectionReader(tmpFile, 4*1024*1024, 1*1024*1024)) 89 if err != nil { 90 t.Fatal(err) 91 } 92 if !bytes.Equal(actualBody, expectedBody) { 93 t.Fatalf("unexpected body") 94 } 95 jsonBody, err := json.Marshal(&apis.ResumableUploadV1MakeBlockResponse{ 96 Ctx: "testctx2", 97 Checksum: "testchecksum2", 98 Crc32: int64(crc32.ChecksumIEEE(actualBody)), 99 Host: server.URL, 100 ExpiredAt: time.Now().Add(1 * time.Hour).Unix(), 101 }) 102 if err != nil { 103 t.Fatal(err) 104 } 105 w.Header().Add("X-ReqId", "fakereqid") 106 w.Write(jsonBody) 107 }).Methods(http.MethodPost) 108 serveMux.PathPrefix("/mkfile/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 109 if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") { 110 t.Fatalf("unexpected authorization") 111 } 112 components := strings.Split(strings.TrimPrefix(r.URL.Path, "/mkfile/"), "/") 113 if components[0] != strconv.FormatInt(5*1024*1024, 10) { 114 t.Fatalf("unexpected fileSize") 115 } 116 components = components[1:] 117 for len(components) > 0 { 118 switch components[0] { 119 case "key": 120 value := components[1] 121 valueBytes, err := base64.URLEncoding.DecodeString(value) 122 if err != nil { 123 t.Fatal(err) 124 } 125 if string(valueBytes) != "testkey" { 126 t.Fatalf("unexpected key") 127 } 128 case "fname": 129 value := components[1] 130 valueBytes, err := base64.URLEncoding.DecodeString(value) 131 if err != nil { 132 t.Fatal(err) 133 } 134 if string(valueBytes) != "testfilename" { 135 t.Fatalf("unexpected fname") 136 } 137 case "mimeType": 138 value := components[1] 139 valueBytes, err := base64.URLEncoding.DecodeString(value) 140 if err != nil { 141 t.Fatal(err) 142 } 143 if string(valueBytes) != "application/json" { 144 t.Fatalf("unexpected mimeType") 145 } 146 case "x-qn-meta-a": 147 value := components[1] 148 valueBytes, err := base64.URLEncoding.DecodeString(value) 149 if err != nil { 150 t.Fatal(err) 151 } 152 if string(valueBytes) != "b" { 153 t.Fatalf("unexpected x-qn-meta-a") 154 } 155 case "x-qn-meta-c": 156 value := components[1] 157 valueBytes, err := base64.URLEncoding.DecodeString(value) 158 if err != nil { 159 t.Fatal(err) 160 } 161 if string(valueBytes) != "d" { 162 t.Fatalf("unexpected x-qn-meta-c") 163 } 164 case "x:a": 165 value := components[1] 166 valueBytes, err := base64.URLEncoding.DecodeString(value) 167 if err != nil { 168 t.Fatal(err) 169 } 170 if string(valueBytes) != "b" { 171 t.Fatalf("unexpected x:a") 172 } 173 case "x:c": 174 value := components[1] 175 valueBytes, err := base64.URLEncoding.DecodeString(value) 176 if err != nil { 177 t.Fatal(err) 178 } 179 if string(valueBytes) != "d" { 180 t.Fatalf("unexpected x:c") 181 } 182 default: 183 t.Fatalf("unexpected component key: %s", components[0]) 184 } 185 components = components[2:] 186 } 187 actualBody, err := internal_io.ReadAll(r.Body) 188 if err != nil { 189 t.Fatal(err) 190 } 191 if string(actualBody) != "testctx1,testctx2" { 192 t.Fatalf("unexpected body") 193 } 194 w.Header().Add("X-ReqId", "fakereqid") 195 w.Write([]byte(`{"ok":true}`)) 196 }).Methods(http.MethodPost) 197 server = httptest.NewServer(serveMux) 198 defer server.Close() 199 200 multiPartsUploaderV1 := uploader.NewMultiPartsUploaderV1(&uploader.MultiPartsUploaderOptions{ 201 Options: http_client.Options{ 202 Regions: ®ion.Region{Up: region.Endpoints{Preferred: []string{server.URL}}}, 203 Credentials: credentials.NewCredentials("testak", "testsk"), 204 }, 205 }) 206 207 src, err := source.NewFileSource(tmpFile.Name()) 208 if err != nil { 209 t.Fatal(err) 210 } 211 key := "testkey" 212 initializedPart, err := multiPartsUploaderV1.InitializeParts(context.Background(), src, &uploader.MultiPartsObjectOptions{ 213 uploader.ObjectOptions{ 214 BucketName: "testbucket", 215 ObjectName: &key, 216 FileName: "testfilename", 217 ContentType: "application/json", 218 Metadata: map[string]string{"a": "b", "c": "d"}, 219 CustomVars: map[string]string{"a": "b", "c": "d"}, 220 }, 221 4 * 1024 * 1024, 222 }) 223 if err != nil { 224 t.Fatal(err) 225 } 226 defer initializedPart.Close() 227 228 part, err := src.Slice(4 * 1024 * 1024) 229 if err != nil { 230 t.Fatal(err) 231 } 232 lastUploaded := uint64(0) 233 uploadedPart_1, err := multiPartsUploaderV1.UploadPart(context.Background(), initializedPart, part, &uploader.UploadPartOptions{ 234 OnUploadingProgress: func(progress *uploader.UploadingPartProgress) { 235 if progress.PartSize != 4*1024*1024 { 236 t.Fatalf("unexpected partSize") 237 } 238 if progress.Uploaded < lastUploaded || progress.Uploaded > progress.PartSize { 239 t.Fatalf("unexpected uploaded") 240 } 241 lastUploaded = progress.Uploaded 242 }, 243 }) 244 if err != nil { 245 t.Fatal(err) 246 } 247 248 part, err = src.Slice(4 * 1024 * 1024) 249 if err != nil { 250 t.Fatal(err) 251 } 252 lastUploaded = 0 253 uploadedPart_2, err := multiPartsUploaderV1.UploadPart(context.Background(), initializedPart, part, &uploader.UploadPartOptions{ 254 OnUploadingProgress: func(progress *uploader.UploadingPartProgress) { 255 if progress.PartSize != 1*1024*1024 { 256 t.Fatalf("unexpected partSize") 257 } 258 if progress.Uploaded < lastUploaded || progress.Uploaded > progress.PartSize { 259 t.Fatalf("unexpected uploaded") 260 } 261 lastUploaded = progress.Uploaded 262 }, 263 }) 264 if err != nil { 265 t.Fatal(err) 266 } 267 268 var returnValue struct { 269 Ok bool `json:"ok"` 270 } 271 err = multiPartsUploaderV1.CompleteParts(context.Background(), initializedPart, []uploader.UploadedPart{uploadedPart_1, uploadedPart_2}, &returnValue) 272 if err != nil { 273 t.Fatal(err) 274 } else if !returnValue.Ok { 275 t.Fatalf("unexpected response body") 276 } 277 } 278 279 func TestMultiPartsUploaderV1Resuming(t *testing.T) { 280 tmpDir, err := os.MkdirTemp("", "multi-parts-uploader-test-*") 281 if err != nil { 282 t.Fatal(err) 283 } 284 defer os.RemoveAll(tmpDir) 285 286 tmpFile, err := os.CreateTemp("", "multi-parts-uploader-test-*") 287 if err != nil { 288 t.Fatal(err) 289 } 290 defer os.Remove(tmpFile.Name()) 291 defer tmpFile.Close() 292 293 r := rand.New(rand.NewSource(time.Now().UnixNano())) 294 if _, err = io.CopyN(tmpFile, r, 5*1024*1024); err != nil { 295 t.Fatal(err) 296 } 297 if _, err = tmpFile.Seek(0, io.SeekStart); err != nil { 298 t.Fatal(err) 299 } 300 301 var server *httptest.Server 302 serveMux := mux.NewRouter() 303 serveMux.PathPrefix("/mkfile/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 304 if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") { 305 t.Fatalf("unexpected authorization") 306 } 307 components := strings.Split(strings.TrimPrefix(r.URL.Path, "/mkfile/"), "/") 308 if components[0] != strconv.FormatInt(5*1024*1024, 10) { 309 t.Fatalf("unexpected fileSize") 310 } 311 components = components[1:] 312 for len(components) > 0 { 313 switch components[0] { 314 case "key": 315 value := components[1] 316 valueBytes, err := base64.URLEncoding.DecodeString(value) 317 if err != nil { 318 t.Fatal(err) 319 } 320 if string(valueBytes) != "testkey" { 321 t.Fatalf("unexpected key") 322 } 323 case "fname": 324 value := components[1] 325 valueBytes, err := base64.URLEncoding.DecodeString(value) 326 if err != nil { 327 t.Fatal(err) 328 } 329 if string(valueBytes) != "testfilename" { 330 t.Fatalf("unexpected fname") 331 } 332 case "mimeType": 333 value := components[1] 334 valueBytes, err := base64.URLEncoding.DecodeString(value) 335 if err != nil { 336 t.Fatal(err) 337 } 338 if string(valueBytes) != "application/json" { 339 t.Fatalf("unexpected mimeType") 340 } 341 case "x-qn-meta-a": 342 value := components[1] 343 valueBytes, err := base64.URLEncoding.DecodeString(value) 344 if err != nil { 345 t.Fatal(err) 346 } 347 if string(valueBytes) != "b" { 348 t.Fatalf("unexpected x-qn-meta-a") 349 } 350 case "x-qn-meta-c": 351 value := components[1] 352 valueBytes, err := base64.URLEncoding.DecodeString(value) 353 if err != nil { 354 t.Fatal(err) 355 } 356 if string(valueBytes) != "d" { 357 t.Fatalf("unexpected x-qn-meta-c") 358 } 359 case "x:a": 360 value := components[1] 361 valueBytes, err := base64.URLEncoding.DecodeString(value) 362 if err != nil { 363 t.Fatal(err) 364 } 365 if string(valueBytes) != "b" { 366 t.Fatalf("unexpected x:a") 367 } 368 case "x:c": 369 value := components[1] 370 valueBytes, err := base64.URLEncoding.DecodeString(value) 371 if err != nil { 372 t.Fatal(err) 373 } 374 if string(valueBytes) != "d" { 375 t.Fatalf("unexpected x:c") 376 } 377 default: 378 t.Fatalf("unexpected component key: %s", components[0]) 379 } 380 components = components[2:] 381 } 382 actualBody, err := internal_io.ReadAll(r.Body) 383 if err != nil { 384 t.Fatal(err) 385 } 386 if string(actualBody) != "testctx1,testctx2" { 387 t.Fatalf("unexpected body") 388 } 389 w.Header().Add("X-ReqId", "fakereqid") 390 w.Write([]byte(`{"ok":true}`)) 391 }).Methods(http.MethodPost) 392 server = httptest.NewServer(serveMux) 393 defer server.Close() 394 395 tmpFileStat, err := tmpFile.Stat() 396 if err != nil { 397 t.Fatal(err) 398 } 399 tmpFileSourceID := fmt.Sprintf("%d:%d:%s", tmpFileStat.Size(), tmpFileStat.ModTime().UnixNano(), tmpFile.Name()) 400 401 resumableRecorder := resumablerecorder.NewJsonFileSystemResumableRecorder(tmpDir) 402 medium := resumableRecorder.OpenForCreatingNew(&resumablerecorder.ResumableRecorderOpenArgs{ 403 AccessKey: "testak", 404 BucketName: "testbucket", 405 ObjectName: "testkey", 406 SourceID: tmpFileSourceID, 407 PartSize: 4 * 1024 * 1024, 408 TotalSize: 5 * 1024 * 1024, 409 UpEndpoints: region.Endpoints{Preferred: []string{server.URL}}, 410 }) 411 if err = medium.Write(&resumablerecorder.ResumableRecord{ 412 PartID: "testctx1", 413 Offset: 0, 414 PartSize: 4 * 1024 * 1024, 415 PartNumber: 1, 416 ExpiredAt: time.Now().Add(1 * time.Hour), 417 }); err != nil { 418 t.Fatal(err) 419 } 420 if err = medium.Write(&resumablerecorder.ResumableRecord{ 421 PartID: "testctx2", 422 Offset: 4 * 1024 * 1024, 423 PartSize: 1 * 1024 * 1024, 424 PartNumber: 2, 425 ExpiredAt: time.Now().Add(1 * time.Hour), 426 }); err != nil { 427 t.Fatal(err) 428 } 429 if err = medium.Close(); err != nil { 430 t.Fatal(err) 431 } 432 433 multiPartsUploaderV1 := uploader.NewMultiPartsUploaderV1(&uploader.MultiPartsUploaderOptions{ 434 ResumableRecorder: resumableRecorder, 435 Options: http_client.Options{ 436 Regions: ®ion.Region{Up: region.Endpoints{Preferred: []string{server.URL}}}, 437 Credentials: credentials.NewCredentials("testak", "testsk"), 438 }, 439 }) 440 441 src, err := source.NewFileSource(tmpFile.Name()) 442 if err != nil { 443 t.Fatal(err) 444 } 445 key := "testkey" 446 initializedPart := multiPartsUploaderV1.TryToResume(context.Background(), src, &uploader.MultiPartsObjectOptions{ 447 uploader.ObjectOptions{ 448 BucketName: "testbucket", 449 ObjectName: &key, 450 FileName: "testfilename", 451 ContentType: "application/json", 452 Metadata: map[string]string{"a": "b", "c": "d"}, 453 CustomVars: map[string]string{"a": "b", "c": "d"}, 454 }, 455 4 * 1024 * 1024, 456 }) 457 if initializedPart == nil { 458 t.Fatalf("initializedPart is nil") 459 } 460 defer initializedPart.Close() 461 462 part, err := src.Slice(4 * 1024 * 1024) 463 if err != nil { 464 t.Fatal(err) 465 } 466 uploadedPart_1, err := multiPartsUploaderV1.UploadPart(context.Background(), initializedPart, part, &uploader.UploadPartOptions{ 467 OnUploadingProgress: func(progress *uploader.UploadingPartProgress) { 468 if progress.PartSize != 4*1024*1024 { 469 t.Fatalf("unexpected partSize") 470 } 471 if progress.Uploaded != 4*1024*1024 { 472 t.Fatalf("unexpected uploaded") 473 } 474 }, 475 }) 476 if err != nil { 477 t.Fatal(err) 478 } 479 480 part, err = src.Slice(4 * 1024 * 1024) 481 if err != nil { 482 t.Fatal(err) 483 } 484 uploadedPart_2, err := multiPartsUploaderV1.UploadPart(context.Background(), initializedPart, part, &uploader.UploadPartOptions{ 485 OnUploadingProgress: func(progress *uploader.UploadingPartProgress) { 486 if progress.PartSize != 1024*1024 { 487 t.Fatalf("unexpected partSize") 488 } 489 if progress.Uploaded != 1024*1024 { 490 t.Fatalf("unexpected uploaded") 491 } 492 }, 493 }) 494 if err != nil { 495 t.Fatal(err) 496 } 497 498 var returnValue struct { 499 Ok bool `json:"ok"` 500 } 501 err = multiPartsUploaderV1.CompleteParts(context.Background(), initializedPart, []uploader.UploadedPart{uploadedPart_1, uploadedPart_2}, &returnValue) 502 if err != nil { 503 t.Fatal(err) 504 } else if !returnValue.Ok { 505 t.Fatalf("unexpected response body") 506 } 507 }