github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/uploader/multi_parts_uploader_v2_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  	"fmt"
    14  	"io"
    15  	"math/rand"
    16  	"net/http"
    17  	"net/http/httptest"
    18  	"os"
    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 TestMultiPartsUploaderV2(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  	serveMux := mux.NewRouter()
    51  	serveMux.HandleFunc("/buckets/{bucketName}/objects/{encodedObjectName}/uploads", func(w http.ResponseWriter, r *http.Request) {
    52  		if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") {
    53  			t.Fatalf("unexpected authorization")
    54  		}
    55  		vars := mux.Vars(r)
    56  		if vars["bucketName"] != "testbucket" {
    57  			t.Fatalf("unexpected bucket name")
    58  		}
    59  		objectBytes, err := base64.URLEncoding.DecodeString(vars["encodedObjectName"])
    60  		if err != nil {
    61  			t.Fatal(err)
    62  		} else if string(objectBytes) != "testkey" {
    63  			t.Fatalf("unexpected object name")
    64  		}
    65  		jsonBytes, err := json.Marshal(&apis.ResumableUploadV2InitiateMultipartUploadResponse{
    66  			UploadId:  "testuploadID",
    67  			ExpiredAt: time.Now().Add(1 * time.Hour).Unix(),
    68  		})
    69  		if err != nil {
    70  			t.Fatal(err)
    71  		}
    72  		w.Header().Add("X-ReqId", "fakereqid")
    73  		w.Write(jsonBytes)
    74  	}).Methods(http.MethodPost)
    75  	serveMux.HandleFunc("/buckets/{bucketName}/objects/{encodedObjectName}/uploads/{uploadID}/{partNumber}", func(w http.ResponseWriter, r *http.Request) {
    76  		if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") {
    77  			t.Fatalf("unexpected authorization")
    78  		}
    79  		vars := mux.Vars(r)
    80  		if vars["bucketName"] != "testbucket" {
    81  			t.Fatalf("unexpected bucket name")
    82  		}
    83  		objectBytes, err := base64.URLEncoding.DecodeString(vars["encodedObjectName"])
    84  		if err != nil {
    85  			t.Fatal(err)
    86  		} else if string(objectBytes) != "testkey" {
    87  			t.Fatalf("unexpected object name")
    88  		}
    89  		if vars["uploadID"] != "testuploadID" {
    90  			t.Fatalf("unexpected upload id")
    91  		}
    92  		actualBody, err := internal_io.ReadAll(r.Body)
    93  		if err != nil {
    94  			t.Fatal(err)
    95  		}
    96  		var expectedBody, jsonBody []byte
    97  		switch vars["partNumber"] {
    98  		case "1":
    99  			expectedBody, err = internal_io.ReadAll(io.NewSectionReader(tmpFile, 0, 4*1024*1024))
   100  			if err != nil {
   101  				t.Fatal(err)
   102  			}
   103  		case "2":
   104  			expectedBody, err = internal_io.ReadAll(io.NewSectionReader(tmpFile, 4*1024*1024, 1024*1024))
   105  			if err != nil {
   106  				t.Fatal(err)
   107  			}
   108  		default:
   109  			t.Fatalf("unexpected part number")
   110  		}
   111  		if !bytes.Equal(actualBody, expectedBody) {
   112  			t.Fatalf("unexpected body")
   113  		}
   114  		md5Sum := md5.Sum(actualBody)
   115  		if r.Header.Get("Content-MD5") != hex.EncodeToString(md5Sum[:]) {
   116  			t.Fatalf("unexpected content-md5")
   117  		}
   118  		switch vars["partNumber"] {
   119  		case "1":
   120  			jsonBody, err = json.Marshal(&apis.ResumableUploadV2UploadPartResponse{
   121  				Etag: "testetag1",
   122  				Md5:  r.Header.Get("Content-MD5"),
   123  			})
   124  		case "2":
   125  			jsonBody, err = json.Marshal(&apis.ResumableUploadV2UploadPartResponse{
   126  				Etag: "testetag2",
   127  				Md5:  r.Header.Get("Content-MD5"),
   128  			})
   129  		}
   130  		if err != nil {
   131  			t.Fatal(err)
   132  		}
   133  		w.Header().Add("X-ReqId", "fakereqid")
   134  		w.Write(jsonBody)
   135  	}).Methods(http.MethodPut)
   136  	serveMux.HandleFunc("/buckets/{bucketName}/objects/{encodedObjectName}/uploads/{uploadID}", func(w http.ResponseWriter, r *http.Request) {
   137  		if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") {
   138  			t.Fatalf("unexpected authorization")
   139  		}
   140  		vars := mux.Vars(r)
   141  		if vars["bucketName"] != "testbucket" {
   142  			t.Fatalf("unexpected bucket name")
   143  		}
   144  		objectBytes, err := base64.URLEncoding.DecodeString(vars["encodedObjectName"])
   145  		if err != nil {
   146  			t.Fatal(err)
   147  		} else if string(objectBytes) != "testkey" {
   148  			t.Fatalf("unexpected object name")
   149  		}
   150  		if vars["uploadID"] != "testuploadID" {
   151  			t.Fatalf("unexpected upload id")
   152  		}
   153  		requestBodyBytes, err := internal_io.ReadAll(r.Body)
   154  		if err != nil {
   155  			t.Fatal(err)
   156  		}
   157  		var body apis.ResumableUploadV2CompleteMultipartUploadRequest
   158  		if err = body.UnmarshalJSON(requestBodyBytes); err != nil {
   159  			t.Fatalf("unexpected request body")
   160  		}
   161  		if len(body.Parts) != 2 {
   162  			t.Fatalf("unexpected parts")
   163  		} else if body.Parts[0].PartNumber != 1 {
   164  			t.Fatalf("unexpected part number")
   165  		} else if body.Parts[0].Etag != "testetag1" {
   166  			t.Fatalf("unexpected part number")
   167  		} else if body.Parts[1].PartNumber != 2 {
   168  			t.Fatalf("unexpected part number")
   169  		} else if body.Parts[1].Etag != "testetag2" {
   170  			t.Fatalf("unexpected part number")
   171  		}
   172  		if body.FileName != "testfilename" {
   173  			t.Fatalf("unexpected fileName")
   174  		}
   175  		if body.MimeType != "application/json" {
   176  			t.Fatalf("unexpected mimeType")
   177  		}
   178  		if len(body.Metadata) != 2 {
   179  			t.Fatalf("unexpected metadata")
   180  		} else if body.Metadata["x-qn-meta-a"] != "b" {
   181  			t.Fatalf("unexpected x-qn-meta-a")
   182  		} else if body.Metadata["x-qn-meta-c"] != "d" {
   183  			t.Fatalf("unexpected x-qn-meta-c")
   184  		} else if body.CustomVars["x:a"] != "b" {
   185  			t.Fatalf("unexpected x:a")
   186  		} else if body.CustomVars["x:c"] != "d" {
   187  			t.Fatalf("unexpected x:c")
   188  		}
   189  		w.Header().Add("X-ReqId", "fakereqid")
   190  		w.Write([]byte(`{"ok":true}`))
   191  	}).Methods(http.MethodPost)
   192  	server := httptest.NewServer(serveMux)
   193  	defer server.Close()
   194  
   195  	multiPartsUploaderV2 := uploader.NewMultiPartsUploaderV2(&uploader.MultiPartsUploaderOptions{
   196  		Options: http_client.Options{
   197  			Regions:     &region.Region{Up: region.Endpoints{Preferred: []string{server.URL}}},
   198  			Credentials: credentials.NewCredentials("testak", "testsk"),
   199  		},
   200  	})
   201  
   202  	src, err := source.NewFileSource(tmpFile.Name())
   203  	if err != nil {
   204  		t.Fatal(err)
   205  	}
   206  	key := "testkey"
   207  	initializedPart, err := multiPartsUploaderV2.InitializeParts(context.Background(), src, &uploader.MultiPartsObjectOptions{
   208  		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  		},
   216  		4 * 1024 * 1024,
   217  	})
   218  	if err != nil {
   219  		t.Fatal(err)
   220  	}
   221  	defer initializedPart.Close()
   222  
   223  	part, err := src.Slice(4 * 1024 * 1024)
   224  	if err != nil {
   225  		t.Fatal(err)
   226  	}
   227  	lastUploaded := uint64(0)
   228  	uploadedPart_1, err := multiPartsUploaderV2.UploadPart(context.Background(), initializedPart, part, &uploader.UploadPartOptions{
   229  		OnUploadingProgress: func(progress *uploader.UploadingPartProgress) {
   230  			if progress.PartSize != 4*1024*1024 {
   231  				t.Fatalf("unexpected partSize")
   232  			}
   233  			if progress.Uploaded < lastUploaded || progress.Uploaded > progress.PartSize {
   234  				t.Fatalf("unexpected uploaded")
   235  			}
   236  			lastUploaded = progress.Uploaded
   237  		},
   238  	})
   239  	if err != nil {
   240  		t.Fatal(err)
   241  	}
   242  
   243  	part, err = src.Slice(4 * 1024 * 1024)
   244  	if err != nil {
   245  		t.Fatal(err)
   246  	}
   247  	lastUploaded = 0
   248  	uploadedPart_2, err := multiPartsUploaderV2.UploadPart(context.Background(), initializedPart, part, &uploader.UploadPartOptions{
   249  		OnUploadingProgress: func(progress *uploader.UploadingPartProgress) {
   250  			if progress.PartSize != 1*1024*1024 {
   251  				t.Fatalf("unexpected partSize")
   252  			}
   253  			if progress.Uploaded < lastUploaded || progress.Uploaded > progress.PartSize {
   254  				t.Fatalf("unexpected uploaded")
   255  			}
   256  			lastUploaded = progress.Uploaded
   257  		},
   258  	})
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  
   263  	var returnValue struct {
   264  		Ok bool `json:"ok"`
   265  	}
   266  	err = multiPartsUploaderV2.CompleteParts(context.Background(), initializedPart, []uploader.UploadedPart{uploadedPart_1, uploadedPart_2}, &returnValue)
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	} else if !returnValue.Ok {
   270  		t.Fatalf("unexpected response body")
   271  	}
   272  }
   273  
   274  func TestMultiPartsUploaderV2Resuming(t *testing.T) {
   275  	tmpDir, err := os.MkdirTemp("", "multi-parts-uploader-test-*")
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  	defer os.RemoveAll(tmpDir)
   280  
   281  	tmpFile, err := os.CreateTemp("", "multi-parts-uploader-test-*")
   282  	if err != nil {
   283  		t.Fatal(err)
   284  	}
   285  	defer os.Remove(tmpFile.Name())
   286  	defer tmpFile.Close()
   287  
   288  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   289  	if _, err = io.CopyN(tmpFile, r, 5*1024*1024); err != nil {
   290  		t.Fatal(err)
   291  	}
   292  	if _, err = tmpFile.Seek(0, io.SeekStart); err != nil {
   293  		t.Fatal(err)
   294  	}
   295  
   296  	serveMux := mux.NewRouter()
   297  	serveMux.HandleFunc("/buckets/{bucketName}/objects/{encodedObjectName}/uploads/{uploadID}", func(w http.ResponseWriter, r *http.Request) {
   298  		if !strings.HasPrefix(r.Header.Get("Authorization"), "UpToken testak:") {
   299  			t.Fatalf("unexpected authorization")
   300  		}
   301  		vars := mux.Vars(r)
   302  		if vars["bucketName"] != "testbucket" {
   303  			t.Fatalf("unexpected bucket name")
   304  		}
   305  		objectBytes, err := base64.URLEncoding.DecodeString(vars["encodedObjectName"])
   306  		if err != nil {
   307  			t.Fatal(err)
   308  		} else if string(objectBytes) != "testkey" {
   309  			t.Fatalf("unexpected object name")
   310  		}
   311  		if vars["uploadID"] != "testuploadID" {
   312  			t.Fatalf("unexpected upload id")
   313  		}
   314  		requestBodyBytes, err := internal_io.ReadAll(r.Body)
   315  		if err != nil {
   316  			t.Fatal(err)
   317  		}
   318  		var body apis.ResumableUploadV2CompleteMultipartUploadRequest
   319  		if err = body.UnmarshalJSON(requestBodyBytes); err != nil {
   320  			t.Fatalf("unexpected request body")
   321  		}
   322  		if len(body.Parts) != 2 {
   323  			t.Fatalf("unexpected parts")
   324  		} else if body.Parts[0].PartNumber != 1 {
   325  			t.Fatalf("unexpected part number")
   326  		} else if body.Parts[0].Etag != "testetag1" {
   327  			t.Fatalf("unexpected part number")
   328  		} else if body.Parts[1].PartNumber != 2 {
   329  			t.Fatalf("unexpected part number")
   330  		} else if body.Parts[1].Etag != "testetag2" {
   331  			t.Fatalf("unexpected part number")
   332  		}
   333  		if body.FileName != "testfilename" {
   334  			t.Fatalf("unexpected fileName")
   335  		}
   336  		if body.MimeType != "application/json" {
   337  			t.Fatalf("unexpected mimeType")
   338  		}
   339  		if len(body.Metadata) != 2 {
   340  			t.Fatalf("unexpected metadata")
   341  		} else if body.Metadata["x-qn-meta-a"] != "b" {
   342  			t.Fatalf("unexpected x-qn-meta-a")
   343  		} else if body.Metadata["x-qn-meta-c"] != "d" {
   344  			t.Fatalf("unexpected x-qn-meta-c")
   345  		} else if body.CustomVars["x:a"] != "b" {
   346  			t.Fatalf("unexpected x:a")
   347  		} else if body.CustomVars["x:c"] != "d" {
   348  			t.Fatalf("unexpected x:c")
   349  		}
   350  		w.Header().Add("X-ReqId", "fakereqid")
   351  		w.Write([]byte(`{"ok":true}`))
   352  	}).Methods(http.MethodPost)
   353  	server := httptest.NewServer(serveMux)
   354  	defer server.Close()
   355  
   356  	tmpFileStat, err := tmpFile.Stat()
   357  	if err != nil {
   358  		t.Fatal(err)
   359  	}
   360  	tmpFileSourceID := fmt.Sprintf("%d:%d:%s", tmpFileStat.Size(), tmpFileStat.ModTime().UnixNano(), tmpFile.Name())
   361  
   362  	resumableRecorder := resumablerecorder.NewJsonFileSystemResumableRecorder(tmpDir)
   363  	medium := resumableRecorder.OpenForCreatingNew(&resumablerecorder.ResumableRecorderOpenArgs{
   364  		AccessKey:   "testak",
   365  		BucketName:  "testbucket",
   366  		ObjectName:  "testkey",
   367  		SourceID:    tmpFileSourceID,
   368  		PartSize:    4 * 1024 * 1024,
   369  		TotalSize:   5 * 1024 * 1024,
   370  		UpEndpoints: region.Endpoints{Preferred: []string{server.URL}},
   371  	})
   372  	if err = medium.Write(&resumablerecorder.ResumableRecord{
   373  		UploadID:   "testuploadID",
   374  		PartID:     "testetag1",
   375  		Offset:     0,
   376  		PartSize:   4 * 1024 * 1024,
   377  		PartNumber: 1,
   378  		ExpiredAt:  time.Now().Add(1 * time.Hour),
   379  	}); err != nil {
   380  		t.Fatal(err)
   381  	}
   382  	if err = medium.Write(&resumablerecorder.ResumableRecord{
   383  		UploadID:   "testuploadID",
   384  		PartID:     "testetag2",
   385  		Offset:     4 * 1024 * 1024,
   386  		PartSize:   1 * 1024 * 1024,
   387  		PartNumber: 2,
   388  		ExpiredAt:  time.Now().Add(1 * time.Hour),
   389  	}); err != nil {
   390  		t.Fatal(err)
   391  	}
   392  	if err = medium.Close(); err != nil {
   393  		t.Fatal(err)
   394  	}
   395  
   396  	multiPartsUploaderV2 := uploader.NewMultiPartsUploaderV2(&uploader.MultiPartsUploaderOptions{
   397  		ResumableRecorder: resumableRecorder,
   398  		Options: http_client.Options{
   399  			Regions:     &region.Region{Up: region.Endpoints{Preferred: []string{server.URL}}},
   400  			Credentials: credentials.NewCredentials("testak", "testsk"),
   401  		},
   402  	})
   403  
   404  	src, err := source.NewFileSource(tmpFile.Name())
   405  	if err != nil {
   406  		t.Fatal(err)
   407  	}
   408  	key := "testkey"
   409  	initializedPart := multiPartsUploaderV2.TryToResume(context.Background(), src, &uploader.MultiPartsObjectOptions{
   410  		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  		},
   418  		4 * 1024 * 1024,
   419  	})
   420  	if initializedPart == nil {
   421  		t.Fatalf("initializedPart is nil")
   422  	}
   423  	defer initializedPart.Close()
   424  
   425  	part, err := src.Slice(4 * 1024 * 1024)
   426  	if err != nil {
   427  		t.Fatal(err)
   428  	}
   429  	uploadedPart_1, err := multiPartsUploaderV2.UploadPart(context.Background(), initializedPart, part, &uploader.UploadPartOptions{
   430  		OnUploadingProgress: func(progress *uploader.UploadingPartProgress) {
   431  			if progress.PartSize != 4*1024*1024 {
   432  				t.Fatalf("unexpected partSize")
   433  			}
   434  			if progress.Uploaded != 4*1024*1024 {
   435  				t.Fatalf("unexpected uploaded")
   436  			}
   437  		},
   438  	})
   439  	if err != nil {
   440  		t.Fatal(err)
   441  	}
   442  
   443  	part, err = src.Slice(4 * 1024 * 1024)
   444  	if err != nil {
   445  		t.Fatal(err)
   446  	}
   447  	uploadedPart_2, err := multiPartsUploaderV2.UploadPart(context.Background(), initializedPart, part, &uploader.UploadPartOptions{
   448  		OnUploadingProgress: func(progress *uploader.UploadingPartProgress) {
   449  			if progress.PartSize != 1024*1024 {
   450  				t.Fatalf("unexpected partSize")
   451  			}
   452  			if progress.Uploaded != 1024*1024 {
   453  				t.Fatalf("unexpected uploaded")
   454  			}
   455  		},
   456  	})
   457  	if err != nil {
   458  		t.Fatal(err)
   459  	}
   460  
   461  	var returnValue struct {
   462  		Ok bool `json:"ok"`
   463  	}
   464  	err = multiPartsUploaderV2.CompleteParts(context.Background(), initializedPart, []uploader.UploadedPart{uploadedPart_1, uploadedPart_2}, &returnValue)
   465  	if err != nil {
   466  		t.Fatal(err)
   467  	} else if !returnValue.Ok {
   468  		t.Fatalf("unexpected response body")
   469  	}
   470  }