github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/renameworker_test.go (about)

     1  package sdk
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"io"
     8  	"io/ioutil"
     9  	"mime"
    10  	"mime/multipart"
    11  	"net/http"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"testing"
    16  
    17  	"github.com/0chain/errors"
    18  	"github.com/0chain/gosdk/core/zcncrypto"
    19  	"github.com/0chain/gosdk/dev"
    20  	devMock "github.com/0chain/gosdk/dev/mock"
    21  	"github.com/0chain/gosdk/sdks/blobber"
    22  	"github.com/0chain/gosdk/zboxcore/blockchain"
    23  	zclient "github.com/0chain/gosdk/zboxcore/client"
    24  	"github.com/0chain/gosdk/zboxcore/fileref"
    25  	"github.com/0chain/gosdk/zboxcore/mocks"
    26  	"github.com/0chain/gosdk/zboxcore/zboxutil"
    27  	"github.com/stretchr/testify/mock"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  func TestRenameRequest_renameBlobberObject(t *testing.T) {
    32  	const (
    33  		mockAllocationTxId = "mock transaction id"
    34  		mockClientId       = "mock client id"
    35  		mockClientKey      = "mock client key"
    36  		mockRemoteFilePath = "mock/remote/file/path"
    37  		mockAllocationId   = "mock allocation id"
    38  		mockBlobberId      = "mock blobber id"
    39  		mockType           = "f"
    40  		mockConnectionId   = "1234567890"
    41  		mockNewName        = "mock new name"
    42  	)
    43  
    44  	var mockClient = mocks.HttpClient{}
    45  	rawClient := zboxutil.Client
    46  	zboxutil.Client = &mockClient
    47  	defer func() {
    48  		zboxutil.Client = rawClient
    49  	}()
    50  
    51  	client := zclient.GetClient()
    52  	client.Wallet = &zcncrypto.Wallet{
    53  		ClientID:  mockClientId,
    54  		ClientKey: mockClientKey,
    55  	}
    56  
    57  	type parameters struct {
    58  		referencePathToRetrieve fileref.ReferencePath
    59  		requestFields           map[string]string
    60  	}
    61  
    62  	tests := []struct {
    63  		name       string
    64  		parameters parameters
    65  		setup      func(*testing.T, string, parameters)
    66  		wantErr    bool
    67  		errMsg     string
    68  		wantFunc   func(*require.Assertions, *RenameRequest)
    69  	}{
    70  		{
    71  			name:    "Test_Error_New_HTTP_Failed_By_Containing_" + string([]byte{0x7f, 0, 0}),
    72  			setup:   func(t *testing.T, testName string, p parameters) {},
    73  			wantErr: true,
    74  			errMsg:  `net/url: invalid control character in URL`,
    75  		},
    76  		{
    77  			name: "Test_Error_Get_Object_Tree_From_Blobber_Failed",
    78  			setup: func(t *testing.T, testName string, p parameters) {
    79  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
    80  					return strings.HasPrefix(req.URL.Path, testName)
    81  				})).Return(&http.Response{
    82  					StatusCode: http.StatusBadRequest,
    83  					Body:       ioutil.NopCloser(bytes.NewReader([]byte(""))),
    84  				}, nil)
    85  			},
    86  			wantErr: true,
    87  			errMsg:  "400: Object tree error response: Body:",
    88  		},
    89  		{
    90  			name: "Test_Rename_Blobber_Object_Failed",
    91  			parameters: parameters{
    92  				referencePathToRetrieve: fileref.ReferencePath{
    93  					Meta: map[string]interface{}{
    94  						"type": mockType,
    95  					},
    96  				},
    97  			},
    98  			setup: func(t *testing.T, testName string, p parameters) {
    99  
   100  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   101  					return strings.Contains(req.URL.Path, "latestwritemarker") &&
   102  						req.Method == "GET"
   103  				})).Return(&http.Response{
   104  					StatusCode: http.StatusOK,
   105  					Body: func() io.ReadCloser {
   106  						jsonFR := `{"latest_write_marker":null,"prev_write_marker":null}`
   107  						return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR)))
   108  					}(),
   109  				}, nil)
   110  
   111  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   112  					return strings.HasPrefix(req.URL.Path, testName) &&
   113  						req.Method == "GET" &&
   114  						req.Header.Get("X-App-Client-ID") == mockClientId &&
   115  						req.Header.Get("X-App-Client-Key") == mockClientKey
   116  				})).Return(&http.Response{
   117  					StatusCode: http.StatusOK,
   118  					Body: func() io.ReadCloser {
   119  						jsonFR, err := json.Marshal(p.referencePathToRetrieve)
   120  						require.NoError(t, err)
   121  						return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR)))
   122  					}(),
   123  				}, nil)
   124  
   125  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   126  					return strings.HasPrefix(req.URL.Path, testName) &&
   127  						req.Method == "POST" &&
   128  						req.Header.Get("X-App-Client-ID") == mockClientId &&
   129  						req.Header.Get("X-App-Client-Key") == mockClientKey
   130  				})).Return(&http.Response{
   131  					StatusCode: http.StatusBadRequest,
   132  					Body:       ioutil.NopCloser(bytes.NewReader([]byte(""))),
   133  				}, nil)
   134  			},
   135  			wantErr: true,
   136  			errMsg:  "response_error: ",
   137  			wantFunc: func(require *require.Assertions, req *RenameRequest) {
   138  				require.NotNil(req)
   139  				require.Equal(0, req.renameMask.CountOnes())
   140  				require.Equal(0, req.consensus.consensus)
   141  			},
   142  		},
   143  		{
   144  			name: "Test_Rename_Blobber_Object_Success",
   145  			parameters: parameters{
   146  				referencePathToRetrieve: fileref.ReferencePath{
   147  					Meta: map[string]interface{}{
   148  						"type": mockType,
   149  					},
   150  				},
   151  				requestFields: map[string]string{
   152  					"connection_id": mockConnectionId,
   153  					"path":          mockRemoteFilePath,
   154  					"new_name":      mockNewName,
   155  				},
   156  			},
   157  			setup: func(t *testing.T, testName string, p parameters) {
   158  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   159  					return strings.Contains(req.URL.Path, "latestwritemarker") &&
   160  						req.Method == "GET"
   161  				})).Return(&http.Response{
   162  					StatusCode: http.StatusOK,
   163  					Body: func() io.ReadCloser {
   164  						jsonFR := `{"latest_write_marker":null,"prev_write_marker":null}`
   165  						return io.NopCloser(bytes.NewReader([]byte(jsonFR)))
   166  					}(),
   167  				}, nil)
   168  
   169  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   170  					return strings.HasPrefix(req.URL.Path, testName) &&
   171  						req.Method == "GET" &&
   172  						req.Header.Get("X-App-Client-ID") == mockClientId &&
   173  						req.Header.Get("X-App-Client-Key") == mockClientKey
   174  				})).Return(&http.Response{
   175  					StatusCode: http.StatusOK,
   176  					Body: func() io.ReadCloser {
   177  						jsonFR, err := json.Marshal(p.referencePathToRetrieve)
   178  						require.NoError(t, err)
   179  						return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR)))
   180  					}(),
   181  				}, nil)
   182  
   183  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   184  					mediaType, params, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
   185  					require.NoError(t, err)
   186  					require.True(t, strings.HasPrefix(mediaType, "multipart/"))
   187  					reader := multipart.NewReader(req.Body, params["boundary"])
   188  
   189  					err = nil
   190  					for {
   191  						var part *multipart.Part
   192  						part, err = reader.NextPart()
   193  						if err != nil {
   194  							break
   195  						}
   196  						expected, ok := p.requestFields[part.FormName()]
   197  						require.True(t, ok)
   198  						actual, err := ioutil.ReadAll(part)
   199  						require.NoError(t, err)
   200  						require.EqualValues(t, expected, string(actual))
   201  					}
   202  					require.Error(t, err)
   203  					require.EqualValues(t, "EOF", errors.Top(err))
   204  
   205  					return strings.HasPrefix(req.URL.Path, testName) &&
   206  						req.Method == "POST" &&
   207  						req.Header.Get("X-App-Client-ID") == mockClientId &&
   208  						req.Header.Get("X-App-Client-Key") == mockClientKey
   209  				})).Return(&http.Response{
   210  					StatusCode: http.StatusOK,
   211  					Body: func() io.ReadCloser {
   212  						jsonFR, err := json.Marshal(p.referencePathToRetrieve)
   213  						require.NoError(t, err)
   214  						return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR)))
   215  					}(),
   216  				}, nil)
   217  			},
   218  			wantFunc: func(require *require.Assertions, req *RenameRequest) {
   219  				require.NotNil(req)
   220  				require.Equal(4, req.renameMask.CountOnes())
   221  				require.Equal(1, req.consensus.consensus)
   222  			},
   223  		},
   224  	}
   225  	for _, tt := range tests {
   226  		t.Run(tt.name, func(t *testing.T) {
   227  			require := require.New(t)
   228  			tt.setup(t, tt.name, tt.parameters)
   229  			req := &RenameRequest{
   230  				allocationID:   mockAllocationId,
   231  				allocationTx:   mockAllocationTxId,
   232  				remotefilepath: mockRemoteFilePath,
   233  				consensus: Consensus{
   234  					RWMutex:         &sync.RWMutex{},
   235  					consensusThresh: 2,
   236  					fullconsensus:   4,
   237  				},
   238  				ctx:          context.TODO(),
   239  				renameMask:   zboxutil.NewUint128(1).Lsh(uint64(4)).Sub64(1),
   240  				maskMU:       &sync.Mutex{},
   241  				connectionID: mockConnectionId,
   242  				newName:      mockNewName,
   243  			}
   244  			req.blobbers = append(req.blobbers, &blockchain.StorageNode{
   245  				Baseurl: tt.name,
   246  			})
   247  			_, err := req.renameBlobberObject(req.blobbers[0], 0)
   248  			require.EqualValues(tt.wantErr, err != nil, "Error: ", err)
   249  			if err != nil {
   250  				require.Contains(errors.Top(err), tt.errMsg)
   251  				return
   252  			}
   253  			require.NoErrorf(err, "expected no error but got %v", err)
   254  			if tt.wantFunc != nil {
   255  				tt.wantFunc(require, req)
   256  			}
   257  		})
   258  	}
   259  }
   260  
   261  func TestRenameRequest_ProcessRename(t *testing.T) {
   262  	const (
   263  		mockAllocationTxId = "mock transaction id"
   264  		mockClientId       = "mock client id"
   265  		mockClientKey      = "mock client key"
   266  		mockRemoteFilePath = "mock/remote/file/path"
   267  		mockAllocationId   = "mock allocation id"
   268  		mockBlobberId      = "mock blobber id"
   269  		mockBlobberUrl     = "mockblobberurl"
   270  		mockType           = "f"
   271  		mockConnectionId   = "1234567890"
   272  		mockNewName        = "mock new name"
   273  	)
   274  
   275  	var mockClient = mocks.HttpClient{}
   276  	rawClient := zboxutil.Client
   277  	zboxutil.Client = &mockClient
   278  	defer func() {
   279  		zboxutil.Client = rawClient
   280  	}()
   281  
   282  	client := zclient.GetClient()
   283  	client.Wallet = &zcncrypto.Wallet{
   284  		ClientID:  mockClientId,
   285  		ClientKey: mockClientKey,
   286  	}
   287  
   288  	setupHttpResponses := func(t *testing.T, testName string, numBlobbers int, numCorrect int, req *RenameRequest) {
   289  
   290  		for i := 0; i < numBlobbers; i++ {
   291  			url := mockBlobberUrl + strconv.Itoa(i)
   292  			mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   293  				return req.Method == "GET" &&
   294  					strings.Contains(req.URL.String(), testName+url) && !strings.Contains(req.URL.String(), "latestwritemarker")
   295  			})).Return(&http.Response{
   296  				StatusCode: http.StatusOK,
   297  				Body: func() io.ReadCloser {
   298  					jsonFR, err := json.Marshal(&fileref.ReferencePath{
   299  						Meta: map[string]interface{}{
   300  							"type": mockType,
   301  						},
   302  					})
   303  					require.NoError(t, err)
   304  					return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR)))
   305  				}(),
   306  			}, nil)
   307  
   308  			mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   309  				return req.Method == "POST" &&
   310  					strings.Contains(req.URL.String(), zboxutil.RENAME_ENDPOINT) &&
   311  					strings.Contains(req.URL.String(), testName+url)
   312  			})).Return(&http.Response{
   313  				StatusCode: func() int {
   314  					if i < numCorrect {
   315  						return http.StatusOK
   316  					}
   317  					return http.StatusBadRequest
   318  				}(),
   319  				Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
   320  			}, nil)
   321  
   322  			if i < numCorrect {
   323  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   324  					return req.Method == "POST" &&
   325  						strings.Contains(req.URL.String(), zboxutil.WM_LOCK_ENDPOINT) &&
   326  						strings.Contains(req.URL.String(), testName+url)
   327  				})).Return(&http.Response{
   328  					StatusCode: func() int {
   329  						if i < numCorrect {
   330  							return http.StatusOK
   331  						}
   332  						return http.StatusBadRequest
   333  					}(),
   334  					Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"status":2}`))),
   335  				}, nil)
   336  
   337  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   338  					return req.Method == "POST" &&
   339  						strings.Contains(req.URL.String(), zboxutil.COMMIT_ENDPOINT) &&
   340  						strings.Contains(req.URL.String(), testName+url)
   341  				})).Return(&http.Response{
   342  					StatusCode: func() int {
   343  						if i < numCorrect {
   344  							return http.StatusOK
   345  						}
   346  						return http.StatusBadRequest
   347  					}(),
   348  					Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
   349  				}, nil)
   350  			}
   351  
   352  			mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   353  				return req.Method == "DELETE" &&
   354  					strings.Contains(req.URL.String(), testName+url)
   355  			})).Return(&http.Response{
   356  				StatusCode: http.StatusOK,
   357  			}, nil)
   358  		}
   359  
   360  		commitChan = make(map[string]chan *CommitRequest)
   361  		for _, blobber := range req.blobbers {
   362  			if _, ok := commitChan[blobber.ID]; !ok {
   363  				commitChan[blobber.ID] = make(chan *CommitRequest, 1)
   364  			}
   365  		}
   366  		blobberChan := commitChan
   367  		go func() {
   368  			cm0 := <-blobberChan[req.blobbers[0].ID]
   369  			require.EqualValues(t, cm0.blobber.ID, testName+mockBlobberId+strconv.Itoa(0))
   370  			cm0.result = &CommitResult{
   371  				Success: true,
   372  			}
   373  			if cm0.wg != nil {
   374  				cm0.wg.Done()
   375  			}
   376  		}()
   377  		go func() {
   378  			cm1 := <-blobberChan[req.blobbers[1].ID]
   379  			require.EqualValues(t, cm1.blobber.ID, testName+mockBlobberId+strconv.Itoa(1))
   380  			cm1.result = &CommitResult{
   381  				Success: true,
   382  			}
   383  			if cm1.wg != nil {
   384  				cm1.wg.Done()
   385  			}
   386  		}()
   387  		go func() {
   388  			cm2 := <-blobberChan[req.blobbers[2].ID]
   389  			require.EqualValues(t, cm2.blobber.ID, testName+mockBlobberId+strconv.Itoa(2))
   390  			cm2.result = &CommitResult{
   391  				Success: true,
   392  			}
   393  			if cm2.wg != nil {
   394  				cm2.wg.Done()
   395  			}
   396  		}()
   397  		go func() {
   398  			cm3 := <-blobberChan[req.blobbers[3].ID]
   399  			require.EqualValues(t, cm3.blobber.ID, testName+mockBlobberId+strconv.Itoa(3))
   400  			cm3.result = &CommitResult{
   401  				Success: true,
   402  			}
   403  			if cm3.wg != nil {
   404  				cm3.wg.Done()
   405  			}
   406  		}()
   407  	}
   408  
   409  	tests := []struct {
   410  		name        string
   411  		numBlobbers int
   412  		numCorrect  int
   413  		setup       func(*testing.T, string, int, int, *RenameRequest)
   414  		wantErr     bool
   415  		errMsg      string
   416  		wantFunc    func(require *require.Assertions, req *RenameRequest)
   417  	}{
   418  		{
   419  			name:        "Test_All_Blobber_Rename_Success",
   420  			numBlobbers: 4,
   421  			numCorrect:  4,
   422  			setup:       setupHttpResponses,
   423  			wantErr:     false,
   424  			wantFunc: func(require *require.Assertions, req *RenameRequest) {
   425  				require.NotNil(req)
   426  				require.Equal(4, req.renameMask.CountOnes())
   427  				require.Equal(4, req.consensus.consensus)
   428  			},
   429  		},
   430  		{
   431  			name:        "Test_Blobber_Index_3_Error_On_Rename_Success",
   432  			numBlobbers: 4,
   433  			numCorrect:  3,
   434  			setup:       setupHttpResponses,
   435  			wantErr:     false,
   436  			wantFunc: func(require *require.Assertions, req *RenameRequest) {
   437  				require.NotNil(req)
   438  				require.Equal(3, req.renameMask.CountOnes())
   439  				require.Equal(3, req.consensus.consensus)
   440  			},
   441  		},
   442  		{
   443  			name:        "Test_Blobber_Index_2_3_Error_On_Rename_Failure",
   444  			numBlobbers: 4,
   445  			numCorrect:  2,
   446  			setup:       setupHttpResponses,
   447  			wantErr:     true,
   448  			errMsg:      "rename_failed",
   449  		},
   450  		{
   451  			name:        "Test_All_Blobber_Error_On_Rename_Failure",
   452  			numBlobbers: 4,
   453  			numCorrect:  0,
   454  			setup:       setupHttpResponses,
   455  			wantErr:     true,
   456  			errMsg:      "rename_failed",
   457  		},
   458  	}
   459  	for _, tt := range tests {
   460  		t.Run(tt.name, func(t *testing.T) {
   461  			require := require.New(t)
   462  
   463  			a := &Allocation{
   464  				Tx:         "TestRenameRequest_ProcessRename",
   465  				DataShards: numBlobbers,
   466  			}
   467  
   468  			setupMockAllocation(t, a)
   469  
   470  			resp := &WMLockResult{
   471  				Status: WMLockStatusOK,
   472  			}
   473  
   474  			respBuf, _ := json.Marshal(resp)
   475  			m := make(devMock.ResponseMap)
   476  
   477  			server := dev.NewBlobberServer(m)
   478  			defer server.Close()
   479  
   480  			for i := 0; i < numBlobbers; i++ {
   481  				path := "/TestRenameRequest_ProcessRename" + tt.name + mockBlobberUrl + strconv.Itoa(i)
   482  
   483  				m[http.MethodPost+":"+path+blobber.EndpointWriteMarkerLock+a.ID] = devMock.Response{
   484  					StatusCode: http.StatusOK,
   485  					Body:       respBuf,
   486  				}
   487  
   488  				a.Blobbers = append(a.Blobbers, &blockchain.StorageNode{
   489  					ID:      tt.name + mockBlobberId + strconv.Itoa(i),
   490  					Baseurl: server.URL + path,
   491  				})
   492  			}
   493  
   494  			setupMockRollback(a, &mockClient)
   495  
   496  			req := &RenameRequest{
   497  				allocationObj:  a,
   498  				blobbers:       a.Blobbers,
   499  				allocationID:   mockAllocationId,
   500  				allocationTx:   mockAllocationTxId,
   501  				remotefilepath: mockRemoteFilePath,
   502  				consensus: Consensus{
   503  					RWMutex:         &sync.RWMutex{},
   504  					consensusThresh: 3,
   505  					fullconsensus:   4,
   506  				},
   507  				renameMask:   zboxutil.NewUint128(1).Lsh(uint64(len(a.Blobbers))).Sub64(1),
   508  				maskMU:       &sync.Mutex{},
   509  				connectionID: mockConnectionId,
   510  				newName:      mockNewName,
   511  			}
   512  			req.ctx, req.ctxCncl = context.WithCancel(context.TODO())
   513  			tt.setup(t, tt.name, tt.numBlobbers, tt.numCorrect, req)
   514  			err := req.ProcessRename()
   515  			require.EqualValues(tt.wantErr, err != nil, err)
   516  			if err != nil {
   517  				require.Contains(errors.Top(err), tt.errMsg)
   518  				return
   519  			}
   520  			if tt.wantFunc != nil {
   521  				tt.wantFunc(require, req)
   522  			}
   523  		})
   524  	}
   525  }