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 }