github.com/weaviate/weaviate@v1.24.6/usecases/replica/finder_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 replica
    13  
    14  import (
    15  	"context"
    16  	"testing"
    17  
    18  	"github.com/go-openapi/strfmt"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/weaviate/weaviate/entities/additional"
    21  	"github.com/weaviate/weaviate/entities/models"
    22  	"github.com/weaviate/weaviate/entities/search"
    23  	"github.com/weaviate/weaviate/entities/storobj"
    24  	"github.com/weaviate/weaviate/usecases/objects"
    25  )
    26  
    27  func object(id strfmt.UUID, lastTime int64) *storobj.Object {
    28  	return &storobj.Object{
    29  		Object: models.Object{
    30  			ID:                 id,
    31  			LastUpdateTimeUnix: lastTime,
    32  		},
    33  	}
    34  }
    35  
    36  func replica(id strfmt.UUID, lastTime int64, deleted bool) objects.Replica {
    37  	x := objects.Replica{
    38  		Deleted: deleted,
    39  		Object: &storobj.Object{
    40  			Object: models.Object{
    41  				ID:                 id,
    42  				LastUpdateTimeUnix: lastTime,
    43  			},
    44  		},
    45  	}
    46  	if !x.Deleted {
    47  		x.ID = id
    48  	}
    49  	return x
    50  }
    51  
    52  func TestFinderReplicaNotFound(t *testing.T) {
    53  	var (
    54  		f      = newFakeFactory("C1", "S", []string{})
    55  		ctx    = context.Background()
    56  		finder = f.newFinder("A")
    57  	)
    58  	_, err := finder.GetOne(ctx, "ONE", "S", "id", nil, additional.Properties{})
    59  	assert.ErrorIs(t, err, errReplicas)
    60  	f.assertLogErrorContains(t, errNoReplicaFound.Error())
    61  
    62  	_, err = finder.Exists(ctx, "ONE", "S", "id")
    63  	assert.ErrorIs(t, err, errReplicas)
    64  	f.assertLogErrorContains(t, errNoReplicaFound.Error())
    65  
    66  	finder.CheckConsistency(ctx, All, []*storobj.Object{objectEx("1", 1, "S", "N")})
    67  	f.assertLogErrorContains(t, errReplicas.Error())
    68  }
    69  
    70  func TestFinderNodeObject(t *testing.T) {
    71  	var (
    72  		id    = strfmt.UUID("123")
    73  		cls   = "C1"
    74  		shard = "SH1"
    75  		nodes = []string{"A", "B", "C"}
    76  		ctx   = context.Background()
    77  		r     = objects.Replica{ID: id, Object: object(id, 3)}
    78  		adds  = additional.Properties{}
    79  		proj  = search.SelectProperties{}
    80  	)
    81  
    82  	t.Run("Unresolved", func(t *testing.T) {
    83  		f := newFakeFactory("C1", shard, nodes)
    84  		finder := f.newFinder("A")
    85  		_, err := finder.NodeObject(ctx, "N", "S", "id", nil, additional.Properties{})
    86  		assert.Contains(t, err.Error(), "N")
    87  	})
    88  
    89  	t.Run("Success", func(t *testing.T) {
    90  		f := newFakeFactory("C1", shard, nodes)
    91  		finder := f.newFinder("A")
    92  		for _, n := range nodes {
    93  			f.RClient.On("FetchObject", anyVal, n, cls, shard, id, proj, adds).Return(r, nil)
    94  		}
    95  		got, err := finder.NodeObject(ctx, nodes[0], shard, id, proj, adds)
    96  		assert.Nil(t, err)
    97  		assert.Equal(t, r.Object, got)
    98  	})
    99  }
   100  
   101  func TestFinderGetOneWithConsistencyLevelALL(t *testing.T) {
   102  	var (
   103  		id        = strfmt.UUID("123")
   104  		cls       = "C1"
   105  		shard     = "SH1"
   106  		nodes     = []string{"A", "B", "C"}
   107  		ctx       = context.Background()
   108  		adds      = additional.Properties{}
   109  		proj      = search.SelectProperties{}
   110  		nilObject *storobj.Object
   111  		emptyItem = objects.Replica{}
   112  	)
   113  
   114  	t.Run("AllButOne", func(t *testing.T) {
   115  		var (
   116  			f         = newFakeFactory("C1", shard, nodes)
   117  			finder    = f.newFinder("A")
   118  			digestIDs = []strfmt.UUID{id}
   119  			item      = objects.Replica{ID: id, Object: object(id, 3)}
   120  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 3}}
   121  		)
   122  		f.RClient.On("FetchObject", anyVal, nodes[0], cls, shard, id, proj, adds).Return(item, nil)
   123  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(digestR, errAny)
   124  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, nil)
   125  
   126  		got, err := finder.GetOne(ctx, All, shard, id, proj, adds)
   127  
   128  		assert.ErrorIs(t, err, errRead)
   129  		f.assertLogErrorContains(t, errAny.Error())
   130  
   131  		assert.Equal(t, nilObject, got)
   132  	})
   133  
   134  	t.Run("Success", func(t *testing.T) {
   135  		var (
   136  			f         = newFakeFactory("C1", shard, nodes)
   137  			finder    = f.newFinder("A")
   138  			digestIDs = []strfmt.UUID{id}
   139  			item      = objects.Replica{ID: id, Object: object(id, 3)}
   140  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 3}}
   141  		)
   142  		f.RClient.On("FetchObject", anyVal, nodes[0], cls, shard, id, proj, adds).Return(item, nil)
   143  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(digestR, nil)
   144  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, nil)
   145  
   146  		got, err := finder.GetOne(ctx, All, shard, id, proj, adds)
   147  		assert.Nil(t, err)
   148  		assert.Equal(t, item.Object, got)
   149  	})
   150  
   151  	t.Run("NotFound", func(t *testing.T) {
   152  		var (
   153  			f         = newFakeFactory("C1", shard, nodes)
   154  			finder    = f.newFinder("A")
   155  			digestIDs = []strfmt.UUID{id}
   156  			// obj       = object(id, 3)
   157  			digestR = []RepairResponse{{ID: id.String(), UpdateTime: 0}}
   158  		)
   159  		f.RClient.On("FetchObject", anyVal, nodes[0], cls, shard, id, proj, adds).Return(emptyItem, nil)
   160  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(digestR, nil)
   161  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, nil)
   162  
   163  		got, err := finder.GetOne(ctx, All, shard, id, proj, adds)
   164  		assert.Nil(t, err)
   165  		assert.Equal(t, nilObject, got)
   166  	})
   167  }
   168  
   169  func TestFinderGetOneWithConsistencyLevelQuorum(t *testing.T) {
   170  	var (
   171  		id        = strfmt.UUID("123")
   172  		cls       = "C1"
   173  		shard     = "SH1"
   174  		nodes     = []string{"A", "B", "C"}
   175  		ctx       = context.Background()
   176  		adds      = additional.Properties{}
   177  		proj      = search.SelectProperties{}
   178  		nilObject *storobj.Object
   179  		emptyItem = objects.Replica{}
   180  	)
   181  
   182  	t.Run("AllButOne", func(t *testing.T) {
   183  		var (
   184  			f         = newFakeFactory("C1", shard, nodes)
   185  			finder    = f.newFinder("A")
   186  			digestIDs = []strfmt.UUID{id}
   187  			item      = objects.Replica{ID: id, Object: object(id, 3)}
   188  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 3}}
   189  		)
   190  		f.RClient.On("FetchObject", anyVal, nodes[0], cls, shard, id, proj, adds).Return(item, nil)
   191  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(digestR, errAny)
   192  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, errAny)
   193  
   194  		got, err := finder.GetOne(ctx, Quorum, shard, id, proj, adds)
   195  		assert.ErrorIs(t, err, errRead)
   196  		f.assertLogErrorContains(t, errAny.Error())
   197  		assert.Equal(t, nilObject, got)
   198  	})
   199  
   200  	t.Run("Success", func(t *testing.T) {
   201  		var (
   202  			f         = newFakeFactory("C1", shard, nodes)
   203  			finder    = f.newFinder("A")
   204  			digestIDs = []strfmt.UUID{id}
   205  			item      = objects.Replica{ID: id, Object: object(id, 3)}
   206  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 3}}
   207  		)
   208  		f.RClient.On("FetchObject", anyVal, nodes[0], cls, shard, id, proj, adds).Return(item, nil)
   209  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(digestR, errAny)
   210  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, nil)
   211  
   212  		got, err := finder.GetOne(ctx, Quorum, shard, id, proj, adds)
   213  		assert.Nil(t, err)
   214  		assert.Equal(t, item.Object, got)
   215  	})
   216  
   217  	t.Run("NotFound", func(t *testing.T) {
   218  		var (
   219  			f         = newFakeFactory("C1", shard, nodes)
   220  			finder    = f.newFinder("A")
   221  			digestIDs = []strfmt.UUID{id}
   222  			// obj       = object(id, 3)
   223  			digestR = []RepairResponse{{ID: id.String(), UpdateTime: 0}}
   224  		)
   225  		f.RClient.On("FetchObject", anyVal, nodes[0], cls, shard, id, proj, adds).Return(emptyItem, nil)
   226  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(digestR, nil)
   227  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, errAny)
   228  
   229  		got, err := finder.GetOne(ctx, Quorum, shard, id, proj, adds)
   230  		assert.Nil(t, err)
   231  		assert.Equal(t, nilObject, got)
   232  	})
   233  }
   234  
   235  func TestFinderGetOneWithConsistencyLevelOne(t *testing.T) {
   236  	var (
   237  		id        = strfmt.UUID("123")
   238  		cls       = "C1"
   239  		shard     = "SH1"
   240  		nodes     = []string{"A", "B", "C"}
   241  		ctx       = context.Background()
   242  		adds      = additional.Properties{}
   243  		proj      = search.SelectProperties{}
   244  		nilObject *storobj.Object
   245  		emptyItem = objects.Replica{}
   246  	)
   247  
   248  	t.Run("None", func(t *testing.T) {
   249  		var (
   250  			f      = newFakeFactory("C1", shard, nodes)
   251  			finder = f.newFinder("A")
   252  			// obj    = objects.Replica{ID: id, Object: object(id, 3)
   253  		)
   254  		for _, n := range nodes {
   255  			f.RClient.On("FetchObject", anyVal, n, cls, shard, id, proj, adds).Return(emptyItem, errAny)
   256  		}
   257  
   258  		got, err := finder.GetOne(ctx, One, shard, id, proj, adds)
   259  		assert.ErrorIs(t, err, errRead)
   260  		f.assertLogErrorContains(t, errAny.Error())
   261  		assert.Equal(t, nilObject, got)
   262  	})
   263  
   264  	t.Run("Success", func(t *testing.T) {
   265  		var (
   266  			f      = newFakeFactory("C1", shard, nodes)
   267  			finder = f.newFinder(nodes[2])
   268  			item   = objects.Replica{ID: id, Object: object(id, 3)}
   269  		)
   270  		f.RClient.On("FetchObject", anyVal, nodes[2], cls, shard, id, proj, adds).Return(item, nil)
   271  		got, err := finder.GetOne(ctx, One, shard, id, proj, adds)
   272  		assert.Nil(t, err)
   273  		assert.Equal(t, item.Object, got)
   274  	})
   275  
   276  	t.Run("NotFound", func(t *testing.T) {
   277  		var (
   278  			f      = newFakeFactory("C1", shard, nodes)
   279  			finder = f.newFinder("A")
   280  		)
   281  		f.RClient.On("FetchObject", anyVal, nodes[0], cls, shard, id, proj, adds).Return(emptyItem, nil)
   282  
   283  		got, err := finder.GetOne(ctx, One, shard, id, proj, adds)
   284  		assert.Nil(t, err)
   285  		assert.Equal(t, nilObject, got)
   286  	})
   287  }
   288  
   289  func TestFinderExistsWithConsistencyLevelALL(t *testing.T) {
   290  	var (
   291  		id       = strfmt.UUID("123")
   292  		cls      = "C1"
   293  		shard    = "SH1"
   294  		nodes    = []string{"A", "B", "C"}
   295  		ctx      = context.Background()
   296  		nilReply = []RepairResponse(nil)
   297  	)
   298  
   299  	t.Run("None", func(t *testing.T) {
   300  		var (
   301  			f         = newFakeFactory("C1", shard, nodes)
   302  			finder    = f.newFinder("A")
   303  			digestIDs = []strfmt.UUID{id}
   304  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 3}}
   305  		)
   306  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shard, digestIDs).Return(digestR, nil)
   307  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(nilReply, errAny)
   308  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, nil)
   309  
   310  		got, err := finder.Exists(ctx, All, shard, id)
   311  		assert.ErrorIs(t, err, errRead)
   312  		f.assertLogErrorContains(t, errAny.Error())
   313  		assert.Equal(t, false, got)
   314  	})
   315  
   316  	t.Run("Success", func(t *testing.T) {
   317  		var (
   318  			f         = newFakeFactory("C1", shard, nodes)
   319  			finder    = f.newFinder("A")
   320  			digestIDs = []strfmt.UUID{id}
   321  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 3}}
   322  		)
   323  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shard, digestIDs).Return(digestR, nil)
   324  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(digestR, nil)
   325  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, nil)
   326  
   327  		got, err := finder.Exists(ctx, All, shard, id)
   328  		assert.Nil(t, err)
   329  		assert.Equal(t, true, got)
   330  	})
   331  
   332  	t.Run("NotFound", func(t *testing.T) {
   333  		var (
   334  			f         = newFakeFactory("C1", shard, nodes)
   335  			finder    = f.newFinder("A")
   336  			digestIDs = []strfmt.UUID{id}
   337  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 0, Deleted: true}}
   338  		)
   339  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shard, digestIDs).Return(digestR, nil)
   340  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(digestR, nil)
   341  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, nil)
   342  
   343  		got, err := finder.Exists(ctx, All, shard, id)
   344  		assert.Nil(t, err)
   345  		assert.Equal(t, false, got)
   346  	})
   347  }
   348  
   349  func TestFinderExistsWithConsistencyLevelQuorum(t *testing.T) {
   350  	var (
   351  		id       = strfmt.UUID("123")
   352  		cls      = "C1"
   353  		shard    = "SH1"
   354  		nodes    = []string{"A", "B", "C"}
   355  		ctx      = context.Background()
   356  		nilReply = []RepairResponse(nil)
   357  	)
   358  
   359  	t.Run("AllButOne", func(t *testing.T) {
   360  		var (
   361  			f         = newFakeFactory("C1", shard, nodes)
   362  			finder    = f.newFinder("A")
   363  			digestIDs = []strfmt.UUID{id}
   364  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 3}}
   365  		)
   366  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shard, digestIDs).Return(digestR, nil)
   367  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(nilReply, errAny)
   368  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, errAny)
   369  
   370  		got, err := finder.Exists(ctx, Quorum, shard, id)
   371  		assert.ErrorIs(t, err, errRead)
   372  		f.assertLogErrorContains(t, errAny.Error())
   373  		assert.Equal(t, false, got)
   374  	})
   375  
   376  	t.Run("Success", func(t *testing.T) {
   377  		var (
   378  			f         = newFakeFactory("C1", shard, nodes)
   379  			finder    = f.newFinder("A")
   380  			digestIDs = []strfmt.UUID{id}
   381  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 3}}
   382  		)
   383  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shard, digestIDs).Return(digestR, nil)
   384  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(digestR, nil)
   385  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, errAny)
   386  
   387  		got, err := finder.Exists(ctx, Quorum, shard, id)
   388  		assert.Nil(t, err)
   389  		assert.Equal(t, true, got)
   390  	})
   391  
   392  	t.Run("NotFound", func(t *testing.T) {
   393  		var (
   394  			f         = newFakeFactory("C1", shard, nodes)
   395  			finder    = f.newFinder("A")
   396  			digestIDs = []strfmt.UUID{id}
   397  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 0, Deleted: true}}
   398  		)
   399  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shard, digestIDs).Return(digestR, nil)
   400  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(digestR, nil)
   401  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, digestIDs).Return(digestR, errAny)
   402  
   403  		got, err := finder.Exists(ctx, Quorum, shard, id)
   404  		assert.Nil(t, err)
   405  		assert.Equal(t, false, got)
   406  	})
   407  }
   408  
   409  func TestFinderExistsWithConsistencyLevelOne(t *testing.T) {
   410  	var (
   411  		id    = strfmt.UUID("123")
   412  		cls   = "C1"
   413  		shard = "SH1"
   414  		nodes = []string{"A", "B"}
   415  		ctx   = context.Background()
   416  	)
   417  
   418  	t.Run("Success", func(t *testing.T) {
   419  		var (
   420  			f         = newFakeFactory("C1", shard, nodes)
   421  			finder    = f.newFinder("A")
   422  			digestIDs = []strfmt.UUID{id}
   423  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 3}}
   424  		)
   425  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shard, digestIDs).Return(digestR, errAny)
   426  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, digestIDs).Return(digestR, nil)
   427  
   428  		got, err := finder.Exists(ctx, One, shard, id)
   429  		assert.Nil(t, err)
   430  		assert.Equal(t, true, got)
   431  	})
   432  
   433  	t.Run("NotFound", func(t *testing.T) {
   434  		var (
   435  			f         = newFakeFactory("C1", shard, nodes)
   436  			finder    = f.newFinder("A")
   437  			digestIDs = []strfmt.UUID{id}
   438  			digestR   = []RepairResponse{{ID: id.String(), UpdateTime: 0, Deleted: true}}
   439  		)
   440  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shard, digestIDs).Return(digestR, nil)
   441  
   442  		got, err := finder.Exists(ctx, One, shard, id)
   443  		assert.Nil(t, err)
   444  		assert.Equal(t, false, got)
   445  	})
   446  }
   447  
   448  func TestFinderCheckConsistencyALL(t *testing.T) {
   449  	var (
   450  		ids    = []strfmt.UUID{"0", "1", "2", "3", "4", "5"}
   451  		cls    = "C1"
   452  		shards = []string{"S1", "S2", "S3"}
   453  		nodes  = []string{"A", "B", "C"}
   454  		ctx    = context.Background()
   455  	)
   456  
   457  	t.Run("ExceptOne", func(t *testing.T) {
   458  		var (
   459  			shard       = shards[0]
   460  			f           = newFakeFactory("C1", shard, nodes)
   461  			finder      = f.newFinder("A")
   462  			xs, digestR = genInputs("A", shard, 1, ids)
   463  		)
   464  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, ids).Return(digestR, nil)
   465  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, ids).Return(digestR, errAny)
   466  
   467  		err := finder.CheckConsistency(ctx, All, xs)
   468  		want := setObjectsConsistency(xs, false)
   469  		assert.ErrorIs(t, err, errRead)
   470  		assert.ElementsMatch(t, want, xs)
   471  		f.assertLogErrorContains(t, errRead.Error())
   472  	})
   473  
   474  	t.Run("OneShard", func(t *testing.T) {
   475  		var (
   476  			shard       = shards[0]
   477  			f           = newFakeFactory("C1", shard, nodes)
   478  			finder      = f.newFinder("A")
   479  			xs, digestR = genInputs("A", shard, 2, ids)
   480  		)
   481  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, ids).Return(digestR, nil)
   482  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, ids).Return(digestR, nil)
   483  
   484  		want := setObjectsConsistency(xs, true)
   485  		err := finder.CheckConsistency(ctx, All, xs)
   486  		assert.Nil(t, err)
   487  		assert.ElementsMatch(t, want, xs)
   488  	})
   489  
   490  	t.Run("TwoShards", func(t *testing.T) {
   491  		var (
   492  			f             = newFakeFactory("C1", shards[0], nodes)
   493  			finder        = f.newFinder("A")
   494  			idSet1        = ids[:3]
   495  			idSet2        = ids[3:6]
   496  			xs1, digestR1 = genInputs("A", shards[0], 1, idSet1)
   497  			xs2, digestR2 = genInputs("B", shards[1], 2, idSet2)
   498  		)
   499  		xs := make([]*storobj.Object, 0, len(xs1)+len(xs2))
   500  		for i := 0; i < 3; i++ {
   501  			xs = append(xs, xs1[i])
   502  			xs = append(xs, xs2[i])
   503  		}
   504  		// first shard
   505  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shards[0], idSet1).Return(digestR1, nil)
   506  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shards[0], idSet1).Return(digestR1, nil)
   507  
   508  		// second shard
   509  		f.AddShard(shards[1], nodes)
   510  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shards[1], idSet2).Return(digestR2, nil)
   511  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shards[1], idSet2).Return(digestR2, nil)
   512  
   513  		want := setObjectsConsistency(xs, true)
   514  		err := finder.CheckConsistency(ctx, All, xs)
   515  		assert.Nil(t, err)
   516  		assert.ElementsMatch(t, want, xs)
   517  	})
   518  
   519  	t.Run("ThreeShard", func(t *testing.T) {
   520  		var (
   521  			f             = newFakeFactory("C1", shards[0], nodes)
   522  			finder        = f.newFinder("A")
   523  			ids1          = ids[:2]
   524  			ids2          = ids[2:4]
   525  			ids3          = ids[4:]
   526  			xs1, digestR1 = genInputs("A", shards[0], 1, ids1)
   527  			xs2, digestR2 = genInputs("B", shards[1], 2, ids2)
   528  			xs3, digestR3 = genInputs("C", shards[2], 3, ids3)
   529  		)
   530  		xs := make([]*storobj.Object, 0, len(xs1)+len(xs2))
   531  		for i := 0; i < 2; i++ {
   532  			xs = append(xs, xs1[i])
   533  			xs = append(xs, xs2[i])
   534  			xs = append(xs, xs3[i])
   535  		}
   536  		// first shard
   537  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shards[0], ids1).Return(digestR1, nil)
   538  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shards[0], ids1).Return(digestR1, nil)
   539  
   540  		// second shard
   541  		f.AddShard(shards[1], nodes)
   542  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shards[1], ids2).Return(digestR2, nil)
   543  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shards[1], ids2).Return(digestR2, nil)
   544  
   545  		// third shard
   546  		f.AddShard(shards[2], nodes)
   547  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shards[2], ids3).Return(digestR3, nil)
   548  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shards[2], ids3).Return(digestR3, nil)
   549  
   550  		want := setObjectsConsistency(xs, true)
   551  		err := finder.CheckConsistency(ctx, All, xs)
   552  		assert.Nil(t, err)
   553  		assert.ElementsMatch(t, want, xs)
   554  	})
   555  
   556  	t.Run("TwoShardSingleNode", func(t *testing.T) {
   557  		var (
   558  			f             = newFakeFactory("C1", shards[0], nodes)
   559  			finder        = f.newFinder("A")
   560  			ids1          = ids[:2]
   561  			ids2          = ids[2:4]
   562  			ids3          = ids[4:]
   563  			xs1, digestR1 = genInputs("A", shards[0], 1, ids1)
   564  			xs2, digestR2 = genInputs("B", shards[1], 1, ids2)
   565  			xs3, digestR3 = genInputs("A", shards[2], 2, ids3)
   566  		)
   567  		xs := make([]*storobj.Object, 0, len(xs1)+len(xs2))
   568  		for i := 0; i < 2; i++ {
   569  			xs = append(xs, xs1[i])
   570  			xs = append(xs, xs2[i])
   571  			xs = append(xs, xs3[i])
   572  		}
   573  		// first shard
   574  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shards[0], ids1).Return(digestR1, nil)
   575  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shards[0], ids1).Return(digestR1, nil)
   576  
   577  		// second shard
   578  		f.AddShard(shards[1], nodes)
   579  		f.RClient.On("DigestObjects", anyVal, nodes[0], cls, shards[1], ids2).Return(digestR2, nil)
   580  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shards[1], ids2).Return(digestR2, nil)
   581  
   582  		// third shard
   583  		f.AddShard(shards[2], nodes)
   584  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shards[2], ids3).Return(digestR3, nil)
   585  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shards[2], ids3).Return(digestR3, nil)
   586  
   587  		want := setObjectsConsistency(xs, true)
   588  		err := finder.CheckConsistency(ctx, All, xs)
   589  		assert.Nil(t, err)
   590  		assert.ElementsMatch(t, want, xs)
   591  	})
   592  }
   593  
   594  func TestFinderCheckConsistencyQuorum(t *testing.T) {
   595  	var (
   596  		ids   = []strfmt.UUID{"10", "20", "30"}
   597  		cls   = "C1"
   598  		shard = "SH1"
   599  		nodes = []string{"A", "B", "C"}
   600  		ctx   = context.Background()
   601  	)
   602  
   603  	t.Run("MalformedInputs", func(t *testing.T) {
   604  		var (
   605  			ids    = []strfmt.UUID{"10", "20", "30"}
   606  			shard  = "SH1"
   607  			nodes  = []string{"A", "B", "C"}
   608  			ctx    = context.Background()
   609  			f      = newFakeFactory("C1", shard, nodes)
   610  			finder = f.newFinder("A")
   611  			xs1    = []*storobj.Object{
   612  				objectEx(ids[0], 4, shard, "A"),
   613  				nil,
   614  				objectEx(ids[2], 6, shard, "A"),
   615  			}
   616  			// BelongToShard and BelongToNode are empty
   617  			xs2 = []*storobj.Object{
   618  				objectEx(ids[0], 4, shard, "A"),
   619  				{Object: models.Object{ID: ids[1]}},
   620  				objectEx(ids[2], 6, shard, "A"),
   621  			}
   622  		)
   623  
   624  		assert.Nil(t, finder.CheckConsistency(ctx, Quorum, nil))
   625  
   626  		err := finder.CheckConsistency(ctx, Quorum, xs1)
   627  		assert.NotNil(t, err)
   628  
   629  		err = finder.CheckConsistency(ctx, Quorum, xs2)
   630  		assert.NotNil(t, err)
   631  	})
   632  
   633  	t.Run("None", func(t *testing.T) {
   634  		var (
   635  			f      = newFakeFactory("C1", shard, nodes)
   636  			finder = f.newFinder("A")
   637  			xs     = []*storobj.Object{
   638  				objectEx(ids[0], 1, shard, "A"),
   639  				objectEx(ids[1], 2, shard, "A"),
   640  				objectEx(ids[2], 3, shard, "A"),
   641  			}
   642  			digestR = []RepairResponse{
   643  				{ID: ids[0].String(), UpdateTime: 1},
   644  				{ID: ids[1].String(), UpdateTime: 2},
   645  				{ID: ids[2].String(), UpdateTime: 3},
   646  			}
   647  		)
   648  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, ids).Return(digestR, errAny)
   649  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, ids).Return(digestR, errAny)
   650  
   651  		err := finder.CheckConsistency(ctx, All, xs)
   652  		want := setObjectsConsistency(xs, false)
   653  		assert.ErrorIs(t, err, errRead)
   654  		assert.ElementsMatch(t, want, xs)
   655  		f.assertLogErrorContains(t, errRead.Error())
   656  	})
   657  
   658  	t.Run("Success", func(t *testing.T) {
   659  		var (
   660  			f      = newFakeFactory("C1", shard, nodes)
   661  			finder = f.newFinder("A")
   662  			xs     = []*storobj.Object{
   663  				objectEx(ids[0], 1, shard, "A"),
   664  				objectEx(ids[1], 2, shard, "A"),
   665  				objectEx(ids[2], 3, shard, "A"),
   666  			}
   667  			digestR = []RepairResponse{
   668  				{ID: ids[0].String(), UpdateTime: 1},
   669  				{ID: ids[1].String(), UpdateTime: 2},
   670  				{ID: ids[2].String(), UpdateTime: 3},
   671  			}
   672  			want = setObjectsConsistency(xs, true)
   673  		)
   674  		f.RClient.On("DigestObjects", anyVal, nodes[1], cls, shard, ids).Return(digestR, nil)
   675  		f.RClient.On("DigestObjects", anyVal, nodes[2], cls, shard, ids).Return(digestR, errAny)
   676  
   677  		err := finder.CheckConsistency(ctx, Quorum, xs)
   678  		assert.Nil(t, err)
   679  		assert.ElementsMatch(t, want, xs)
   680  	})
   681  }
   682  
   683  func TestFinderCheckConsistencyOne(t *testing.T) {
   684  	var (
   685  		ids    = []strfmt.UUID{"10", "20", "30"}
   686  		shard  = "SH1"
   687  		nodes  = []string{"A", "B", "C"}
   688  		ctx    = context.Background()
   689  		f      = newFakeFactory("C1", shard, nodes)
   690  		finder = f.newFinder("A")
   691  		xs     = []*storobj.Object{
   692  			objectEx(ids[0], 4, shard, "A"),
   693  			objectEx(ids[1], 5, shard, "A"),
   694  			objectEx(ids[2], 6, shard, "A"),
   695  		}
   696  		want = setObjectsConsistency(xs, true)
   697  	)
   698  
   699  	err := finder.CheckConsistency(ctx, One, xs)
   700  	assert.Nil(t, err)
   701  	assert.Equal(t, want, xs)
   702  }