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