github.com/weaviate/weaviate@v1.24.6/adapters/clients/replication_test.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package clients
    13  
    14  import (
    15  	"context"
    16  	"encoding/json"
    17  	"net/http"
    18  	"net/http/httptest"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/go-openapi/strfmt"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  	"github.com/weaviate/weaviate/entities/additional"
    26  	"github.com/weaviate/weaviate/entities/models"
    27  	"github.com/weaviate/weaviate/entities/storobj"
    28  	"github.com/weaviate/weaviate/usecases/objects"
    29  	"github.com/weaviate/weaviate/usecases/replica"
    30  )
    31  
    32  const (
    33  	RequestError             = "RIDNotFound"
    34  	RequestSuccess           = "RIDSuccess"
    35  	RequestInternalError     = "RIDInternal"
    36  	RequestMalFormedResponse = "RIDMalFormed"
    37  )
    38  
    39  const (
    40  	UUID1 = strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168241")
    41  	UUID2 = strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168242")
    42  )
    43  
    44  type fakeServer struct {
    45  	method         string
    46  	path           string
    47  	RequestError   replica.SimpleResponse
    48  	RequestSuccess replica.SimpleResponse
    49  	host           string
    50  }
    51  
    52  func newFakeReplicationServer(t *testing.T, method, path string) *fakeServer {
    53  	return &fakeServer{
    54  		method:         method,
    55  		path:           path,
    56  		RequestError:   replica.SimpleResponse{Errors: []replica.Error{{Msg: "error"}}},
    57  		RequestSuccess: replica.SimpleResponse{},
    58  	}
    59  }
    60  
    61  func (f *fakeServer) server(t *testing.T) *httptest.Server {
    62  	handler := func(w http.ResponseWriter, r *http.Request) {
    63  		if r.Method != f.method {
    64  			t.Errorf("method want %s got %s", f.method, r.Method)
    65  			w.WriteHeader(http.StatusBadRequest)
    66  			return
    67  		}
    68  		if f.path != r.URL.Path {
    69  			t.Errorf("path want %s got %s", f.path, r.URL.Path)
    70  			w.WriteHeader(http.StatusBadRequest)
    71  			return
    72  		}
    73  		requestID := r.URL.Query().Get(replica.RequestKey)
    74  		switch requestID {
    75  		case RequestInternalError:
    76  			w.WriteHeader(http.StatusInternalServerError)
    77  		case RequestError:
    78  			bytes, _ := json.Marshal(&f.RequestError)
    79  			w.Write(bytes)
    80  		case RequestSuccess:
    81  			bytes, _ := json.Marshal(&replica.SimpleResponse{})
    82  			w.Write(bytes)
    83  		case RequestMalFormedResponse:
    84  			w.Write([]byte(`mal formed`))
    85  		}
    86  	}
    87  	serv := httptest.NewServer(http.HandlerFunc(handler))
    88  	f.host = serv.URL[7:]
    89  	return serv
    90  }
    91  
    92  func anyObject(uuid strfmt.UUID) models.Object {
    93  	return models.Object{
    94  		Class:              "C1",
    95  		CreationTimeUnix:   900000000001,
    96  		LastUpdateTimeUnix: 900000000002,
    97  		ID:                 uuid,
    98  		Properties: map[string]interface{}{
    99  			"stringProp":    "string",
   100  			"textProp":      "text",
   101  			"datePropArray": []string{"1980-01-01T00:00:00+02:00"},
   102  		},
   103  	}
   104  }
   105  
   106  func TestReplicationPutObject(t *testing.T) {
   107  	t.Parallel()
   108  
   109  	ctx := context.Background()
   110  	f := newFakeReplicationServer(t, http.MethodPost, "/replicas/indices/C1/shards/S1/objects")
   111  	ts := f.server(t)
   112  	defer ts.Close()
   113  
   114  	client := newReplicationClient(ts.Client())
   115  	t.Run("EncodeRequest", func(t *testing.T) {
   116  		obj := &storobj.Object{}
   117  		_, err := client.PutObject(ctx, "Node1", "C1", "S1", "RID", obj)
   118  		assert.NotNil(t, err)
   119  		assert.Contains(t, err.Error(), "encode")
   120  	})
   121  
   122  	obj := &storobj.Object{MarshallerVersion: 1, Object: anyObject(UUID1)}
   123  	t.Run("ConnectionError", func(t *testing.T) {
   124  		_, err := client.PutObject(ctx, "", "C1", "S1", "", obj)
   125  		assert.NotNil(t, err)
   126  		assert.Contains(t, err.Error(), "connect")
   127  	})
   128  
   129  	t.Run("Error", func(t *testing.T) {
   130  		resp, err := client.PutObject(ctx, f.host, "C1", "S1", RequestError, obj)
   131  		assert.Nil(t, err)
   132  		assert.Equal(t, replica.SimpleResponse{Errors: f.RequestError.Errors}, resp)
   133  	})
   134  
   135  	t.Run("DecodeResponse", func(t *testing.T) {
   136  		_, err := client.PutObject(ctx, f.host, "C1", "S1", RequestMalFormedResponse, obj)
   137  		assert.NotNil(t, err)
   138  		assert.Contains(t, err.Error(), "decode response")
   139  	})
   140  
   141  	t.Run("ServerInternalError", func(t *testing.T) {
   142  		_, err := client.PutObject(ctx, f.host, "C1", "S1", RequestInternalError, obj)
   143  		assert.NotNil(t, err)
   144  		assert.Contains(t, err.Error(), "status code")
   145  	})
   146  }
   147  
   148  func TestReplicationDeleteObject(t *testing.T) {
   149  	t.Parallel()
   150  
   151  	ctx := context.Background()
   152  	uuid := UUID1
   153  	path := "/replicas/indices/C1/shards/S1/objects/" + uuid.String()
   154  	fs := newFakeReplicationServer(t, http.MethodDelete, path)
   155  	ts := fs.server(t)
   156  	defer ts.Close()
   157  
   158  	client := newReplicationClient(ts.Client())
   159  	t.Run("ConnectionError", func(t *testing.T) {
   160  		_, err := client.DeleteObject(ctx, "", "C1", "S1", "", uuid)
   161  		assert.NotNil(t, err)
   162  		assert.Contains(t, err.Error(), "connect")
   163  	})
   164  
   165  	t.Run("Error", func(t *testing.T) {
   166  		resp, err := client.DeleteObject(ctx, fs.host, "C1", "S1", RequestError, uuid)
   167  		assert.Nil(t, err)
   168  		assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
   169  	})
   170  
   171  	t.Run("DecodeResponse", func(t *testing.T) {
   172  		_, err := client.DeleteObject(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, uuid)
   173  		assert.NotNil(t, err)
   174  		assert.Contains(t, err.Error(), "decode response")
   175  	})
   176  
   177  	t.Run("ServerInternalError", func(t *testing.T) {
   178  		_, err := client.DeleteObject(ctx, fs.host, "C1", "S1", RequestInternalError, uuid)
   179  		assert.NotNil(t, err)
   180  		assert.Contains(t, err.Error(), "status code")
   181  	})
   182  }
   183  
   184  func TestReplicationPutObjects(t *testing.T) {
   185  	t.Parallel()
   186  	ctx := context.Background()
   187  	fs := newFakeReplicationServer(t, http.MethodPost, "/replicas/indices/C1/shards/S1/objects")
   188  	fs.RequestError.Errors = append(fs.RequestError.Errors, replica.Error{Msg: "error2"})
   189  	ts := fs.server(t)
   190  	defer ts.Close()
   191  
   192  	client := newReplicationClient(ts.Client())
   193  	t.Run("EncodeRequest", func(t *testing.T) {
   194  		objs := []*storobj.Object{{}}
   195  		_, err := client.PutObjects(ctx, "Node1", "C1", "S1", "RID", objs)
   196  		assert.NotNil(t, err)
   197  		assert.Contains(t, err.Error(), "encode")
   198  	})
   199  
   200  	objects := []*storobj.Object{
   201  		{MarshallerVersion: 1, Object: anyObject(UUID1)},
   202  		{MarshallerVersion: 1, Object: anyObject(UUID2)},
   203  	}
   204  
   205  	t.Run("ConnectionError", func(t *testing.T) {
   206  		_, err := client.PutObjects(ctx, "", "C1", "S1", "", objects)
   207  		assert.NotNil(t, err)
   208  		assert.Contains(t, err.Error(), "connect")
   209  	})
   210  
   211  	t.Run("Error", func(t *testing.T) {
   212  		resp, err := client.PutObjects(ctx, fs.host, "C1", "S1", RequestError, objects)
   213  		assert.Nil(t, err)
   214  		assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
   215  	})
   216  
   217  	t.Run("DecodeResponse", func(t *testing.T) {
   218  		_, err := client.PutObjects(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, objects)
   219  		assert.NotNil(t, err)
   220  		assert.Contains(t, err.Error(), "decode response")
   221  	})
   222  
   223  	t.Run("ServerInternalError", func(t *testing.T) {
   224  		_, err := client.PutObjects(ctx, fs.host, "C1", "S1", RequestInternalError, objects)
   225  		assert.NotNil(t, err)
   226  		assert.Contains(t, err.Error(), "status code")
   227  	})
   228  }
   229  
   230  func TestReplicationMergeObject(t *testing.T) {
   231  	t.Parallel()
   232  	ctx := context.Background()
   233  	uuid := UUID1
   234  	f := newFakeReplicationServer(t, http.MethodPatch, "/replicas/indices/C1/shards/S1/objects/"+uuid.String())
   235  	ts := f.server(t)
   236  	defer ts.Close()
   237  
   238  	client := newReplicationClient(ts.Client())
   239  	doc := &objects.MergeDocument{ID: uuid}
   240  	t.Run("ConnectionError", func(t *testing.T) {
   241  		_, err := client.MergeObject(ctx, "", "C1", "S1", "", doc)
   242  		assert.NotNil(t, err)
   243  		assert.Contains(t, err.Error(), "connect")
   244  	})
   245  
   246  	t.Run("Error", func(t *testing.T) {
   247  		resp, err := client.MergeObject(ctx, f.host, "C1", "S1", RequestError, doc)
   248  		assert.Nil(t, err)
   249  		assert.Equal(t, replica.SimpleResponse{Errors: f.RequestError.Errors}, resp)
   250  	})
   251  
   252  	t.Run("DecodeResponse", func(t *testing.T) {
   253  		_, err := client.MergeObject(ctx, f.host, "C1", "S1", RequestMalFormedResponse, doc)
   254  		assert.NotNil(t, err)
   255  		assert.Contains(t, err.Error(), "decode response")
   256  	})
   257  
   258  	t.Run("ServerInternalError", func(t *testing.T) {
   259  		_, err := client.MergeObject(ctx, f.host, "C1", "S1", RequestInternalError, doc)
   260  		assert.NotNil(t, err)
   261  		assert.Contains(t, err.Error(), "status code")
   262  	})
   263  }
   264  
   265  func TestReplicationAddReferences(t *testing.T) {
   266  	t.Parallel()
   267  
   268  	ctx := context.Background()
   269  	fs := newFakeReplicationServer(t, http.MethodPost, "/replicas/indices/C1/shards/S1/objects/references")
   270  	fs.RequestError.Errors = append(fs.RequestError.Errors, replica.Error{Msg: "error2"})
   271  	ts := fs.server(t)
   272  	defer ts.Close()
   273  
   274  	client := newReplicationClient(ts.Client())
   275  	refs := []objects.BatchReference{{OriginalIndex: 1}, {OriginalIndex: 2}}
   276  	t.Run("ConnectionError", func(t *testing.T) {
   277  		_, err := client.AddReferences(ctx, "", "C1", "S1", "", refs)
   278  		assert.NotNil(t, err)
   279  		assert.Contains(t, err.Error(), "connect")
   280  	})
   281  
   282  	t.Run("Error", func(t *testing.T) {
   283  		resp, err := client.AddReferences(ctx, fs.host, "C1", "S1", RequestError, refs)
   284  		assert.Nil(t, err)
   285  		assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
   286  	})
   287  
   288  	t.Run("DecodeResponse", func(t *testing.T) {
   289  		_, err := client.AddReferences(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, refs)
   290  		assert.NotNil(t, err)
   291  		assert.Contains(t, err.Error(), "decode response")
   292  	})
   293  
   294  	t.Run("ServerInternalError", func(t *testing.T) {
   295  		_, err := client.AddReferences(ctx, fs.host, "C1", "S1", RequestInternalError, refs)
   296  		assert.NotNil(t, err)
   297  		assert.Contains(t, err.Error(), "status code")
   298  	})
   299  }
   300  
   301  func TestReplicationDeleteObjects(t *testing.T) {
   302  	t.Parallel()
   303  
   304  	ctx := context.Background()
   305  	fs := newFakeReplicationServer(t, http.MethodDelete, "/replicas/indices/C1/shards/S1/objects")
   306  	fs.RequestError.Errors = append(fs.RequestError.Errors, replica.Error{Msg: "error2"})
   307  	ts := fs.server(t)
   308  	defer ts.Close()
   309  	client := newReplicationClient(ts.Client())
   310  
   311  	uuids := []strfmt.UUID{strfmt.UUID("1"), strfmt.UUID("2")}
   312  	t.Run("ConnectionError", func(t *testing.T) {
   313  		_, err := client.DeleteObjects(ctx, "", "C1", "S1", "", uuids, false)
   314  		assert.NotNil(t, err)
   315  		assert.Contains(t, err.Error(), "connect")
   316  	})
   317  
   318  	t.Run("Error", func(t *testing.T) {
   319  		resp, err := client.DeleteObjects(ctx, fs.host, "C1", "S1", RequestError, uuids, false)
   320  		assert.Nil(t, err)
   321  		assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
   322  	})
   323  
   324  	t.Run("DecodeResponse", func(t *testing.T) {
   325  		_, err := client.DeleteObjects(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, uuids, false)
   326  		assert.NotNil(t, err)
   327  		assert.Contains(t, err.Error(), "decode response")
   328  	})
   329  
   330  	t.Run("ServerInternalError", func(t *testing.T) {
   331  		_, err := client.DeleteObjects(ctx, fs.host, "C1", "S1", RequestInternalError, uuids, false)
   332  		assert.NotNil(t, err)
   333  		assert.Contains(t, err.Error(), "status code")
   334  	})
   335  }
   336  
   337  func TestReplicationAbort(t *testing.T) {
   338  	t.Parallel()
   339  
   340  	ctx := context.Background()
   341  	path := "/replicas/indices/C1/shards/S1:abort"
   342  	fs := newFakeReplicationServer(t, http.MethodPost, path)
   343  	ts := fs.server(t)
   344  	defer ts.Close()
   345  	client := newReplicationClient(ts.Client())
   346  
   347  	t.Run("ConnectionError", func(t *testing.T) {
   348  		client := newReplicationClient(ts.Client())
   349  		client.maxBackOff = client.timeoutUnit * 20
   350  		_, err := client.Abort(ctx, "", "C1", "S1", "")
   351  		assert.NotNil(t, err)
   352  		assert.Contains(t, err.Error(), "connect")
   353  	})
   354  
   355  	t.Run("Error", func(t *testing.T) {
   356  		resp, err := client.Abort(ctx, fs.host, "C1", "S1", RequestError)
   357  		assert.Nil(t, err)
   358  		assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
   359  	})
   360  
   361  	t.Run("DecodeResponse", func(t *testing.T) {
   362  		_, err := client.Abort(ctx, fs.host, "C1", "S1", RequestMalFormedResponse)
   363  		assert.NotNil(t, err)
   364  		assert.Contains(t, err.Error(), "decode response")
   365  	})
   366  	client.timeoutUnit = client.maxBackOff * 3
   367  	t.Run("ServerInternalError", func(t *testing.T) {
   368  		_, err := client.Abort(ctx, fs.host, "C1", "S1", RequestInternalError)
   369  		assert.NotNil(t, err)
   370  		assert.Contains(t, err.Error(), "status code")
   371  	})
   372  }
   373  
   374  func TestReplicationCommit(t *testing.T) {
   375  	t.Parallel()
   376  
   377  	ctx := context.Background()
   378  	path := "/replicas/indices/C1/shards/S1:commit"
   379  	fs := newFakeReplicationServer(t, http.MethodPost, path)
   380  	ts := fs.server(t)
   381  	defer ts.Close()
   382  	resp := replica.SimpleResponse{}
   383  	client := newReplicationClient(ts.Client())
   384  
   385  	t.Run("ConnectionError", func(t *testing.T) {
   386  		err := client.Commit(ctx, "", "C1", "S1", "", &resp)
   387  		assert.NotNil(t, err)
   388  		assert.Contains(t, err.Error(), "connect")
   389  	})
   390  
   391  	t.Run("Error", func(t *testing.T) {
   392  		err := client.Commit(ctx, fs.host, "C1", "S1", RequestError, &resp)
   393  		assert.Nil(t, err)
   394  		assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
   395  	})
   396  
   397  	t.Run("DecodeResponse", func(t *testing.T) {
   398  		err := client.Commit(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, &resp)
   399  		assert.NotNil(t, err)
   400  		assert.Contains(t, err.Error(), "decode response")
   401  	})
   402  
   403  	t.Run("ServerInternalError", func(t *testing.T) {
   404  		err := client.Commit(ctx, fs.host, "C1", "S1", RequestInternalError, &resp)
   405  		assert.NotNil(t, err)
   406  		assert.Contains(t, err.Error(), "status code")
   407  	})
   408  }
   409  
   410  func TestReplicationFetchObject(t *testing.T) {
   411  	t.Parallel()
   412  
   413  	expected := objects.Replica{
   414  		ID: UUID1,
   415  		Object: &storobj.Object{
   416  			MarshallerVersion: 1,
   417  			Object: models.Object{
   418  				ID: UUID1,
   419  				Properties: map[string]interface{}{
   420  					"stringProp": "abc",
   421  				},
   422  			},
   423  			Vector:    []float32{1, 2, 3, 4, 5},
   424  			VectorLen: 5,
   425  		},
   426  	}
   427  
   428  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   429  		b, _ := expected.MarshalBinary()
   430  		w.Write(b)
   431  	}))
   432  
   433  	c := newReplicationClient(server.Client())
   434  	resp, err := c.FetchObject(context.Background(), server.URL[7:],
   435  		"C1", "S1", expected.ID, nil, additional.Properties{})
   436  	require.Nil(t, err)
   437  	assert.Equal(t, expected.ID, resp.ID)
   438  	assert.Equal(t, expected.Deleted, resp.Deleted)
   439  	assert.EqualValues(t, expected.Object, resp.Object)
   440  }
   441  
   442  func TestReplicationFetchObjects(t *testing.T) {
   443  	t.Parallel()
   444  	expected := objects.Replicas{
   445  		{
   446  			ID: UUID1,
   447  			Object: &storobj.Object{
   448  				MarshallerVersion: 1,
   449  				Object: models.Object{
   450  					ID: UUID1,
   451  					Properties: map[string]interface{}{
   452  						"stringProp": "abc",
   453  					},
   454  				},
   455  				Vector:    []float32{1, 2, 3, 4, 5},
   456  				VectorLen: 5,
   457  			},
   458  		},
   459  		{
   460  			ID: UUID2,
   461  			Object: &storobj.Object{
   462  				MarshallerVersion: 1,
   463  				Object: models.Object{
   464  					ID: UUID2,
   465  					Properties: map[string]interface{}{
   466  						"floatProp": float64(123),
   467  					},
   468  				},
   469  				Vector:    []float32{10, 20, 30, 40, 50},
   470  				VectorLen: 5,
   471  			},
   472  		},
   473  	}
   474  
   475  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   476  		b, _ := expected.MarshalBinary()
   477  		w.Write(b)
   478  	}))
   479  
   480  	c := newReplicationClient(server.Client())
   481  	resp, err := c.FetchObjects(context.Background(), server.URL[7:], "C1", "S1", []strfmt.UUID{expected[0].ID})
   482  	require.Nil(t, err)
   483  	require.Len(t, resp, 2)
   484  	assert.Equal(t, expected[0].ID, resp[0].ID)
   485  	assert.Equal(t, expected[0].Deleted, resp[0].Deleted)
   486  	assert.EqualValues(t, expected[0].Object, resp[0].Object)
   487  	assert.Equal(t, expected[1].ID, resp[1].ID)
   488  	assert.Equal(t, expected[1].Deleted, resp[1].Deleted)
   489  	assert.EqualValues(t, expected[1].Object, resp[1].Object)
   490  }
   491  
   492  func TestReplicationDigestObjects(t *testing.T) {
   493  	t.Parallel()
   494  
   495  	now := time.Now()
   496  	expected := []replica.RepairResponse{
   497  		{
   498  			ID:         UUID1.String(),
   499  			UpdateTime: now.UnixMilli(),
   500  			Version:    1,
   501  		},
   502  		{
   503  			ID:         UUID2.String(),
   504  			UpdateTime: now.UnixMilli(),
   505  			Version:    1,
   506  		},
   507  	}
   508  
   509  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   510  		b, _ := json.Marshal(expected)
   511  		w.Write(b)
   512  	}))
   513  
   514  	c := newReplicationClient(server.Client())
   515  	resp, err := c.DigestObjects(context.Background(), server.URL[7:], "C1", "S1", []strfmt.UUID{
   516  		strfmt.UUID(expected[0].ID),
   517  		strfmt.UUID(expected[1].ID),
   518  	})
   519  	require.Nil(t, err)
   520  	require.Len(t, resp, 2)
   521  	assert.Equal(t, expected[0].ID, resp[0].ID)
   522  	assert.Equal(t, expected[0].Deleted, resp[0].Deleted)
   523  	assert.Equal(t, expected[0].UpdateTime, resp[0].UpdateTime)
   524  	assert.Equal(t, expected[0].Version, resp[0].Version)
   525  	assert.Equal(t, expected[1].ID, resp[1].ID)
   526  	assert.Equal(t, expected[1].Deleted, resp[1].Deleted)
   527  	assert.Equal(t, expected[1].UpdateTime, resp[1].UpdateTime)
   528  	assert.Equal(t, expected[1].Version, resp[1].Version)
   529  }
   530  
   531  func TestReplicationOverwriteObjects(t *testing.T) {
   532  	t.Parallel()
   533  
   534  	now := time.Now()
   535  	input := []*objects.VObject{
   536  		{
   537  			LatestObject: &models.Object{
   538  				ID:                 UUID1,
   539  				Class:              "C1",
   540  				CreationTimeUnix:   now.UnixMilli(),
   541  				LastUpdateTimeUnix: now.Add(time.Hour).UnixMilli(),
   542  				Properties: map[string]interface{}{
   543  					"stringProp": "abc",
   544  				},
   545  				Vector: []float32{1, 2, 3, 4, 5},
   546  			},
   547  			StaleUpdateTime: now.UnixMilli(),
   548  			Version:         0,
   549  		},
   550  	}
   551  	expected := []replica.RepairResponse{
   552  		{
   553  			ID:         UUID1.String(),
   554  			Version:    1,
   555  			UpdateTime: now.Add(time.Hour).UnixMilli(),
   556  		},
   557  	}
   558  
   559  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   560  		b, _ := json.Marshal(expected)
   561  		w.Write(b)
   562  	}))
   563  
   564  	c := newReplicationClient(server.Client())
   565  	resp, err := c.OverwriteObjects(context.Background(), server.URL[7:], "C1", "S1", input)
   566  	require.Nil(t, err)
   567  	require.Len(t, resp, 1)
   568  	assert.Equal(t, expected[0].ID, resp[0].ID)
   569  	assert.Equal(t, expected[0].Version, resp[0].Version)
   570  	assert.Equal(t, expected[0].UpdateTime, resp[0].UpdateTime)
   571  }
   572  
   573  func TestExpBackOff(t *testing.T) {
   574  	N := 200
   575  	av := time.Duration(0)
   576  	delay := time.Nanosecond * 20
   577  	for i := 0; i < N; i++ {
   578  		av += backOff(delay)
   579  	}
   580  	av /= time.Duration(N)
   581  	if av < time.Nanosecond*30 || av > time.Nanosecond*50 {
   582  		t.Errorf("average time got %v", av)
   583  	}
   584  }
   585  
   586  func newReplicationClient(httpClient *http.Client) *replicationClient {
   587  	c := NewReplicationClient(httpClient).(*replicationClient)
   588  	c.minBackOff = time.Millisecond * 1
   589  	c.maxBackOff = time.Millisecond * 8
   590  	c.timeoutUnit = time.Millisecond * 20
   591  	return c
   592  }