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:     &region.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:     &region.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  }