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

     1  package sdk
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/0chain/errors"
    17  	"github.com/0chain/gosdk/core/resty"
    18  	"github.com/0chain/gosdk/core/zcncrypto"
    19  	"github.com/0chain/gosdk/zboxcore/blockchain"
    20  	zclient "github.com/0chain/gosdk/zboxcore/client"
    21  	"github.com/0chain/gosdk/zboxcore/fileref"
    22  	"github.com/0chain/gosdk/zboxcore/mocks"
    23  	"github.com/0chain/gosdk/zboxcore/zboxutil"
    24  	"github.com/stretchr/testify/mock"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  func TestDeleteRequest_deleteBlobberFile(t *testing.T) {
    29  	const (
    30  		mockAllocationTxId = "mock transaction id"
    31  		mockClientId       = "mock client id"
    32  		mockClientKey      = "mock client key"
    33  		mockRemoteFilePath = "mock/remote/file/path"
    34  		mockAllocationId   = "mock allocation id"
    35  		mockBlobberId      = "mock blobber id"
    36  		mockType           = "f"
    37  		mockConnectionId   = "1234567890"
    38  	)
    39  
    40  	var mockClient = mocks.HttpClient{}
    41  	zboxutil.Client = &mockClient
    42  
    43  	client := zclient.GetClient()
    44  	client.Wallet = &zcncrypto.Wallet{
    45  		ClientID:  mockClientId,
    46  		ClientKey: mockClientKey,
    47  	}
    48  
    49  	var wg sync.WaitGroup
    50  
    51  	type parameters struct {
    52  		referencePathToRetrieve fileref.ReferencePath
    53  		requestFields           map[string]string
    54  	}
    55  
    56  	tests := []struct {
    57  		name       string
    58  		parameters parameters
    59  		setup      func(*testing.T, string, parameters)
    60  		wantFunc   func(*require.Assertions, *DeleteRequest)
    61  	}{
    62  		{
    63  			name: "Test_Delete_Blobber_File_Failed",
    64  			parameters: parameters{
    65  				referencePathToRetrieve: fileref.ReferencePath{
    66  					Meta: map[string]interface{}{
    67  						"type": mockType,
    68  					},
    69  				},
    70  			},
    71  			setup: func(t *testing.T, testName string, p parameters) {
    72  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
    73  					return strings.HasPrefix(req.URL.Path, testName) &&
    74  						req.Method == "GET" &&
    75  						req.Header.Get("X-App-Client-ID") == mockClientId &&
    76  						req.Header.Get("X-App-Client-Key") == mockClientKey
    77  				})).Run(func(args mock.Arguments) {
    78  					for _, c := range mockClient.ExpectedCalls {
    79  						c.ReturnArguments = mock.Arguments{&http.Response{
    80  							StatusCode: http.StatusOK,
    81  							Body: func() io.ReadCloser {
    82  								jsonFR, err := json.Marshal(p.referencePathToRetrieve)
    83  								require.NoError(t, err)
    84  								return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR)))
    85  							}(),
    86  						}, nil}
    87  					}
    88  				})
    89  
    90  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
    91  					return strings.HasPrefix(req.URL.Path, testName) &&
    92  						req.Method == "DELETE" &&
    93  						req.Header.Get("X-App-Client-ID") == mockClientId &&
    94  						req.Header.Get("X-App-Client-Key") == mockClientKey
    95  				})).Run(func(args mock.Arguments) {
    96  					for _, c := range mockClient.ExpectedCalls {
    97  						c.ReturnArguments = mock.Arguments{&http.Response{
    98  							StatusCode: http.StatusBadRequest,
    99  							Body:       ioutil.NopCloser(strings.NewReader("")),
   100  						}, nil}
   101  					}
   102  				})
   103  			},
   104  			wantFunc: func(require *require.Assertions, req *DeleteRequest) {
   105  				require.NotNil(req)
   106  				require.Equal(0, req.deleteMask.CountOnes())
   107  				require.Equal(0, req.consensus.consensus)
   108  			},
   109  		},
   110  		{
   111  			name: "Test_Delete_Blobber_File_Success",
   112  			parameters: parameters{
   113  				referencePathToRetrieve: fileref.ReferencePath{
   114  					Meta: map[string]interface{}{
   115  						"type": mockType,
   116  					},
   117  				},
   118  				requestFields: map[string]string{
   119  					"connection_id": mockConnectionId,
   120  					"path":          mockRemoteFilePath,
   121  				},
   122  			},
   123  			setup: func(t *testing.T, testName string, p parameters) {
   124  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   125  					return strings.HasPrefix(req.URL.Path, testName) &&
   126  						req.Method == "GET" &&
   127  						req.Header.Get("X-App-Client-ID") == mockClientId &&
   128  						req.Header.Get("X-App-Client-Key") == mockClientKey
   129  				})).Return(&http.Response{
   130  					StatusCode: http.StatusOK,
   131  					Body: func() io.ReadCloser {
   132  						jsonFR, err := json.Marshal(p.referencePathToRetrieve)
   133  						require.NoError(t, err)
   134  						return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR)))
   135  					}(),
   136  				}, nil)
   137  
   138  				mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   139  
   140  					for k, v := range p.requestFields {
   141  						q := req.URL.Query().Get(k)
   142  						require.Equal(t, v, q)
   143  					}
   144  
   145  					return strings.HasPrefix(req.URL.Path, testName) &&
   146  						req.Method == "DELETE" &&
   147  						req.Header.Get("X-App-Client-ID") == mockClientId &&
   148  						req.Header.Get("X-App-Client-Key") == mockClientKey
   149  				})).Return(&http.Response{
   150  					StatusCode: http.StatusOK,
   151  					Body: func() io.ReadCloser {
   152  						jsonFR, err := json.Marshal(p.referencePathToRetrieve)
   153  						require.NoError(t, err)
   154  						return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR)))
   155  					}(),
   156  				}, nil)
   157  			},
   158  			wantFunc: func(require *require.Assertions, req *DeleteRequest) {
   159  				require.NotNil(req)
   160  				require.Equal(1, req.deleteMask.CountOnes())
   161  				require.Equal(1, req.consensus.consensus)
   162  			},
   163  		},
   164  	}
   165  	for _, tt := range tests {
   166  		t.Run(tt.name, func(t *testing.T) {
   167  			require := require.New(t)
   168  			tt.setup(t, tt.name, tt.parameters)
   169  			req := &DeleteRequest{
   170  				allocationID:   mockAllocationId,
   171  				allocationTx:   mockAllocationTxId,
   172  				remotefilepath: mockRemoteFilePath,
   173  				consensus: Consensus{
   174  					RWMutex:         &sync.RWMutex{},
   175  					consensusThresh: 2,
   176  					fullconsensus:   4,
   177  				},
   178  				maskMu:       &sync.Mutex{},
   179  				ctx:          context.TODO(),
   180  				connectionID: mockConnectionId,
   181  				wg:           func() *sync.WaitGroup { wg.Add(1); return &wg }(),
   182  			}
   183  			req.blobbers = append(req.blobbers, &blockchain.StorageNode{
   184  				Baseurl: tt.name,
   185  			})
   186  			req.deleteMask = zboxutil.NewUint128(1).Lsh(uint64(len(req.blobbers))).Sub64(1)
   187  			objectTreeRefs := make([]fileref.RefEntity, 1)
   188  			refEntity, _ := req.getObjectTreeFromBlobber(0)
   189  			objectTreeRefs[0] = refEntity
   190  			req.deleteBlobberFile(req.blobbers[0], 0) //nolint
   191  			if tt.wantFunc != nil {
   192  				tt.wantFunc(require, req)
   193  			}
   194  		})
   195  	}
   196  }
   197  
   198  func TestDeleteRequest_ProcessDelete(t *testing.T) {
   199  	const (
   200  		mockAllocationTxId = "mock transaction id"
   201  		mockClientId       = "mock client id"
   202  		mockClientKey      = "mock client key"
   203  		mockRemoteFilePath = "mock/remote/file/path"
   204  		mockAllocationId   = "mock allocation id"
   205  		mockBlobberId      = "mock blobber id"
   206  		mockBlobberUrl     = "mockblobberurl"
   207  		mockType           = "f"
   208  		mockConnectionId   = "1234567890"
   209  	)
   210  
   211  	rawClient := zboxutil.Client
   212  	createClient := resty.CreateClient
   213  
   214  	var mockClient = mocks.HttpClient{}
   215  	zboxutil.Client = &mockClient
   216  
   217  	client := zclient.GetClient()
   218  	client.Wallet = &zcncrypto.Wallet{
   219  		ClientID:  mockClientId,
   220  		ClientKey: mockClientKey,
   221  	}
   222  
   223  	zboxutil.Client = &mockClient
   224  	resty.CreateClient = func(t *http.Transport, timeout time.Duration) resty.Client {
   225  		return &mockClient
   226  	}
   227  
   228  	defer func() {
   229  		zboxutil.Client = rawClient
   230  		resty.CreateClient = createClient
   231  	}()
   232  
   233  	setupHttpResponses := func(t *testing.T, testName string, numBlobbers int, numCorrect int, req DeleteRequest) { //nolint
   234  		for i := 0; i < numBlobbers; i++ {
   235  			url := mockBlobberUrl + strconv.Itoa(i)
   236  			mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   237  				return req.Method == "POST" &&
   238  					strings.Contains(req.URL.String(), testName+url)
   239  			})).Return(&http.Response{
   240  				StatusCode: http.StatusOK,
   241  				Body: func() io.ReadCloser {
   242  					jsonFR, err := json.Marshal(&fileref.ReferencePath{
   243  						Meta: map[string]interface{}{
   244  							"type": mockType,
   245  						},
   246  					})
   247  					require.NoError(t, err)
   248  					return ioutil.NopCloser(bytes.NewReader([]byte(jsonFR)))
   249  				}(),
   250  			}, nil)
   251  			mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   252  				return req.Method == "DELETE" &&
   253  					strings.Contains(req.URL.String(), testName+url)
   254  			})).Return(&http.Response{
   255  				StatusCode: func() int {
   256  					if i < numCorrect {
   257  						return http.StatusOK
   258  					}
   259  					return http.StatusBadRequest
   260  				}(),
   261  				Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
   262  			}, nil)
   263  		}
   264  
   265  		commitChan = make(map[string]chan *CommitRequest)
   266  		for _, blobber := range req.blobbers {
   267  			if _, ok := commitChan[blobber.ID]; !ok {
   268  				commitChan[blobber.ID] = make(chan *CommitRequest, 1)
   269  			}
   270  		}
   271  		blobberChan := commitChan
   272  		go func() {
   273  			cm0 := <-blobberChan[req.blobbers[0].ID]
   274  			require.EqualValues(t, cm0.blobber.ID, testName+mockBlobberId+strconv.Itoa(0))
   275  			cm0.result = &CommitResult{
   276  				Success: true,
   277  			}
   278  			if cm0.wg != nil {
   279  				cm0.wg.Done()
   280  			}
   281  		}()
   282  		go func() {
   283  			cm1 := <-blobberChan[req.blobbers[1].ID]
   284  			require.EqualValues(t, cm1.blobber.ID, testName+mockBlobberId+strconv.Itoa(1))
   285  			cm1.result = &CommitResult{
   286  				Success: true,
   287  			}
   288  			if cm1.wg != nil {
   289  				cm1.wg.Done()
   290  			}
   291  		}()
   292  		go func() {
   293  			cm2 := <-blobberChan[req.blobbers[2].ID]
   294  			require.EqualValues(t, cm2.blobber.ID, testName+mockBlobberId+strconv.Itoa(2))
   295  			cm2.result = &CommitResult{
   296  				Success: true,
   297  			}
   298  			if cm2.wg != nil {
   299  				cm2.wg.Done()
   300  			}
   301  		}()
   302  		go func() {
   303  			cm3 := <-blobberChan[req.blobbers[3].ID]
   304  			require.EqualValues(t, cm3.blobber.ID, testName+mockBlobberId+strconv.Itoa(3))
   305  			cm3.result = &CommitResult{
   306  				Success: true,
   307  			}
   308  			if cm3.wg != nil {
   309  				cm3.wg.Done()
   310  			}
   311  		}()
   312  	}
   313  
   314  	tests := []struct {
   315  		name        string
   316  		numBlobbers int
   317  		numCorrect  int
   318  		setup       func(*testing.T, string, int, int, DeleteRequest)
   319  		wantErr     bool
   320  		errMsg      string
   321  		wantFunc    func(*require.Assertions, *DeleteRequest)
   322  	}{
   323  		{
   324  			name:        "Test_All_Blobber_Delete_Attribute_Success",
   325  			numBlobbers: 4,
   326  			numCorrect:  4,
   327  			setup:       setupHttpResponses,
   328  			wantErr:     false,
   329  			wantFunc: func(require *require.Assertions, req *DeleteRequest) {
   330  				require.NotNil(req)
   331  				require.Equal(4, req.deleteMask.CountOnes())
   332  				require.Equal(4, req.consensus.consensus)
   333  			},
   334  		},
   335  		{
   336  			name:        "Test_Blobber_Index_3_Error_On_Delete_Attribute_Success",
   337  			numBlobbers: 4,
   338  			numCorrect:  3,
   339  			setup:       setupHttpResponses,
   340  			wantErr:     false,
   341  			wantFunc: func(require *require.Assertions, req *DeleteRequest) {
   342  				require.NotNil(req)
   343  				require.Equal(3, req.deleteMask.CountOnes())
   344  				require.Equal(3, req.consensus.consensus)
   345  			},
   346  		},
   347  		{
   348  			name:        "Test_Blobber_Index_2_3_Error_On_Delete_Attribute_Failure",
   349  			numBlobbers: 4,
   350  			numCorrect:  2,
   351  			setup:       setupHttpResponses,
   352  			wantErr:     true,
   353  			errMsg:      "delete_failed",
   354  		},
   355  		{
   356  			name:        "Test_All_Blobber_Error_On_Delete_Attribute_Failure",
   357  			numBlobbers: 4,
   358  			numCorrect:  0,
   359  			setup:       setupHttpResponses,
   360  			wantErr:     true,
   361  			errMsg:      "delete_failed",
   362  		},
   363  	}
   364  	for _, tt := range tests {
   365  		t.Run(tt.name, func(t *testing.T) {
   366  			require := require.New(t)
   367  			req := &DeleteRequest{
   368  				allocationID:   mockAllocationId,
   369  				allocationTx:   mockAllocationTxId,
   370  				remotefilepath: mockRemoteFilePath,
   371  				consensus: Consensus{
   372  					RWMutex:         &sync.RWMutex{},
   373  					consensusThresh: 3,
   374  					fullconsensus:   4,
   375  				},
   376  				maskMu:       &sync.Mutex{},
   377  				connectionID: mockConnectionId,
   378  			}
   379  			req.ctx, req.ctxCncl = context.WithCancel(context.TODO())
   380  
   381  			a := &Allocation{
   382  				DataShards: numBlobbers,
   383  			}
   384  
   385  			for i := 0; i < tt.numBlobbers; i++ {
   386  				a.Blobbers = append(a.Blobbers, &blockchain.StorageNode{
   387  					ID:      tt.name + mockBlobberId + strconv.Itoa(i),
   388  					Baseurl: "http://" + tt.name + mockBlobberUrl + strconv.Itoa(i),
   389  				})
   390  			}
   391  			req.deleteMask = zboxutil.NewUint128(1).Lsh(uint64(len(a.Blobbers))).Sub64(1)
   392  
   393  			setupMockAllocation(t, a)
   394  			setupMockRollback(a, &mockClient)
   395  			setupMockWriteLockRequest(a, &mockClient)
   396  
   397  			req.allocationObj = a
   398  			req.blobbers = a.Blobbers
   399  
   400  			tt.setup(t, tt.name, tt.numBlobbers, tt.numCorrect, *req) //nolint
   401  			err := req.ProcessDelete()
   402  			require.EqualValues(tt.wantErr, err != nil)
   403  			if err != nil {
   404  				require.Contains(errors.Top(err), tt.errMsg, "expected error contains '%s'", tt.errMsg)
   405  				return
   406  			}
   407  			if tt.wantFunc != nil {
   408  				tt.wantFunc(require, req)
   409  			}
   410  		})
   411  	}
   412  }