git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/object/patcher/patcher_test.go (about)

     1  package patcher
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"io"
     7  	"testing"
     8  
     9  	objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
    10  	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
    11  	oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
    12  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/transformer"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  type mockPatchedObjectWriter struct {
    17  	obj *objectSDK.Object
    18  }
    19  
    20  func (m *mockPatchedObjectWriter) Write(_ context.Context, chunk []byte) (int, error) {
    21  	res := append(m.obj.Payload(), chunk...)
    22  
    23  	m.obj.SetPayload(res)
    24  	m.obj.SetPayloadSize(uint64(len(res)))
    25  
    26  	return len(chunk), nil
    27  }
    28  
    29  func (m *mockPatchedObjectWriter) WriteHeader(_ context.Context, hdr *objectSDK.Object) error {
    30  	m.obj.ToV2().SetHeader(hdr.ToV2().GetHeader())
    31  	return nil
    32  }
    33  
    34  func (m *mockPatchedObjectWriter) Close(context.Context) (*transformer.AccessIdentifiers, error) {
    35  	return &transformer.AccessIdentifiers{}, nil
    36  }
    37  
    38  type mockRangeProvider struct {
    39  	originalObjectPayload []byte
    40  }
    41  
    42  var _ RangeProvider = (*mockRangeProvider)(nil)
    43  
    44  func (m *mockRangeProvider) GetRange(_ context.Context, rng *objectSDK.Range) io.Reader {
    45  	offset := rng.GetOffset()
    46  	length := rng.GetLength()
    47  
    48  	if length == 0 {
    49  		return bytes.NewReader(m.originalObjectPayload[offset:])
    50  	}
    51  	return bytes.NewReader(m.originalObjectPayload[offset : offset+length])
    52  }
    53  
    54  func newTestObject() (*objectSDK.Object, oid.Address) {
    55  	obj := objectSDK.New()
    56  
    57  	addr := oidtest.Address()
    58  	obj.SetContainerID(addr.Container())
    59  	obj.SetID(addr.Object())
    60  
    61  	return obj, addr
    62  }
    63  
    64  func rangeWithOffestWithLength(offset, length uint64) *objectSDK.Range {
    65  	rng := new(objectSDK.Range)
    66  	rng.SetOffset(offset)
    67  	rng.SetLength(length)
    68  	return rng
    69  }
    70  
    71  func TestPatchRevert(t *testing.T) {
    72  	obj, _ := newTestObject()
    73  
    74  	modifPatch := &objectSDK.Patch{
    75  		PayloadPatch: &objectSDK.PayloadPatch{
    76  			Range: rangeWithOffestWithLength(0, 0),
    77  
    78  			Chunk: []byte("inserted"),
    79  		},
    80  	}
    81  
    82  	originalObjectPayload := []byte("*******************")
    83  
    84  	obj.SetPayload(originalObjectPayload)
    85  	obj.SetPayloadSize(uint64(len(originalObjectPayload)))
    86  
    87  	exp := []byte("inserted*******************")
    88  
    89  	rangeProvider := &mockRangeProvider{
    90  		originalObjectPayload: originalObjectPayload,
    91  	}
    92  
    93  	patchedObj, _ := newTestObject()
    94  
    95  	wr := &mockPatchedObjectWriter{
    96  		obj: patchedObj,
    97  	}
    98  
    99  	prm := Params{
   100  		Header: obj.CutPayload(),
   101  
   102  		RangeProvider: rangeProvider,
   103  
   104  		ObjectWriter: wr,
   105  	}
   106  
   107  	patcher := New(prm)
   108  
   109  	err := patcher.ApplyAttributesPatch(context.Background(), modifPatch.NewAttributes, modifPatch.ReplaceAttributes)
   110  	require.NoError(t, err)
   111  
   112  	err = patcher.ApplyPayloadPatch(context.Background(), modifPatch.PayloadPatch)
   113  	require.NoError(t, err)
   114  
   115  	_, err = patcher.Close(context.Background())
   116  	require.NoError(t, err)
   117  
   118  	require.Equal(t, exp, patchedObj.Payload())
   119  
   120  	revertPatch := &objectSDK.Patch{
   121  		PayloadPatch: &objectSDK.PayloadPatch{
   122  			Range: rangeWithOffestWithLength(0, uint64(len("inserted"))),
   123  
   124  			Chunk: []byte{},
   125  		},
   126  	}
   127  
   128  	rangeProvider = &mockRangeProvider{
   129  		originalObjectPayload: exp,
   130  	}
   131  
   132  	patchedPatchedObj, _ := newTestObject()
   133  
   134  	wr = &mockPatchedObjectWriter{
   135  		obj: patchedPatchedObj,
   136  	}
   137  
   138  	prm = Params{
   139  		Header: patchedObj.CutPayload(),
   140  
   141  		RangeProvider: rangeProvider,
   142  
   143  		ObjectWriter: wr,
   144  	}
   145  
   146  	patcher = New(prm)
   147  
   148  	err = patcher.ApplyAttributesPatch(context.Background(), revertPatch.NewAttributes, revertPatch.ReplaceAttributes)
   149  	require.NoError(t, err)
   150  
   151  	err = patcher.ApplyPayloadPatch(context.Background(), revertPatch.PayloadPatch)
   152  	require.NoError(t, err)
   153  
   154  	_, err = patcher.Close(context.Background())
   155  	require.NoError(t, err)
   156  
   157  	require.Equal(t, originalObjectPayload, patchedPatchedObj.Payload())
   158  }
   159  
   160  func TestPatchRepeatAttributePatch(t *testing.T) {
   161  	obj, _ := newTestObject()
   162  
   163  	modifPatch := &objectSDK.Patch{}
   164  
   165  	originalObjectPayload := []byte("*******************")
   166  
   167  	obj.SetPayload(originalObjectPayload)
   168  	obj.SetPayloadSize(uint64(len(originalObjectPayload)))
   169  
   170  	rangeProvider := &mockRangeProvider{
   171  		originalObjectPayload: originalObjectPayload,
   172  	}
   173  
   174  	patchedObj, _ := newTestObject()
   175  
   176  	wr := &mockPatchedObjectWriter{
   177  		obj: patchedObj,
   178  	}
   179  
   180  	prm := Params{
   181  		Header: obj.CutPayload(),
   182  
   183  		RangeProvider: rangeProvider,
   184  
   185  		ObjectWriter: wr,
   186  	}
   187  
   188  	patcher := New(prm)
   189  
   190  	err := patcher.ApplyAttributesPatch(context.Background(), modifPatch.NewAttributes, modifPatch.ReplaceAttributes)
   191  	require.NoError(t, err)
   192  
   193  	err = patcher.ApplyAttributesPatch(context.Background(), modifPatch.NewAttributes, modifPatch.ReplaceAttributes)
   194  	require.ErrorIs(t, err, ErrAttrPatchAlreadyApplied)
   195  }
   196  
   197  func TestPatchEmptyPayloadPatch(t *testing.T) {
   198  	obj, _ := newTestObject()
   199  
   200  	modifPatch := &objectSDK.Patch{}
   201  
   202  	originalObjectPayload := []byte("*******************")
   203  
   204  	obj.SetPayload(originalObjectPayload)
   205  	obj.SetPayloadSize(uint64(len(originalObjectPayload)))
   206  
   207  	rangeProvider := &mockRangeProvider{
   208  		originalObjectPayload: originalObjectPayload,
   209  	}
   210  
   211  	patchedObj, _ := newTestObject()
   212  
   213  	wr := &mockPatchedObjectWriter{
   214  		obj: patchedObj,
   215  	}
   216  
   217  	prm := Params{
   218  		Header: obj.CutPayload(),
   219  
   220  		RangeProvider: rangeProvider,
   221  
   222  		ObjectWriter: wr,
   223  	}
   224  
   225  	patcher := New(prm)
   226  
   227  	err := patcher.ApplyAttributesPatch(context.Background(), modifPatch.NewAttributes, modifPatch.ReplaceAttributes)
   228  	require.NoError(t, err)
   229  
   230  	err = patcher.ApplyPayloadPatch(context.Background(), nil)
   231  	require.ErrorIs(t, err, ErrPayloadPatchIsNil)
   232  }
   233  
   234  func newTestAttribute(key, val string) objectSDK.Attribute {
   235  	var attr objectSDK.Attribute
   236  	attr.SetKey(key)
   237  	attr.SetValue(val)
   238  	return attr
   239  }
   240  
   241  func TestPatch(t *testing.T) {
   242  	for _, test := range []struct {
   243  		name                     string
   244  		patches                  []objectSDK.Patch
   245  		originalObjectPayload    []byte
   246  		patchedPayload           []byte
   247  		originalHeaderAttributes []objectSDK.Attribute
   248  		patchedHeaderAttributes  []objectSDK.Attribute
   249  		expectedPayloadPatchErr  error
   250  	}{
   251  		{
   252  			name: "invalid offset",
   253  			patches: []objectSDK.Patch{
   254  				{
   255  					PayloadPatch: &objectSDK.PayloadPatch{
   256  						Range: rangeWithOffestWithLength(100, 0),
   257  						Chunk: []byte(""),
   258  					},
   259  				},
   260  			},
   261  			originalObjectPayload:   []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   262  			expectedPayloadPatchErr: ErrOffsetExceedsSize,
   263  		},
   264  		{
   265  			name: "invalid following patch offset",
   266  			patches: []objectSDK.Patch{
   267  				{
   268  					PayloadPatch: &objectSDK.PayloadPatch{
   269  						Range: rangeWithOffestWithLength(10, 0),
   270  						Chunk: []byte(""),
   271  					},
   272  				},
   273  				{
   274  					PayloadPatch: &objectSDK.PayloadPatch{
   275  						Range: rangeWithOffestWithLength(7, 0),
   276  						Chunk: []byte(""),
   277  					},
   278  				},
   279  			},
   280  			originalObjectPayload:   []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   281  			expectedPayloadPatchErr: ErrInvalidPatchOffsetOrder,
   282  		},
   283  		{
   284  			name: "only header patch",
   285  			patches: []objectSDK.Patch{
   286  				{
   287  					NewAttributes: []objectSDK.Attribute{
   288  						newTestAttribute("key1", "val2"),
   289  						newTestAttribute("key2", "val2"),
   290  					},
   291  				},
   292  			},
   293  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   294  			patchedPayload:        []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   295  			patchedHeaderAttributes: []objectSDK.Attribute{
   296  				newTestAttribute("key1", "val2"),
   297  				newTestAttribute("key2", "val2"),
   298  			},
   299  		},
   300  		{
   301  			name: "header and payload",
   302  			patches: []objectSDK.Patch{
   303  				{
   304  					NewAttributes: []objectSDK.Attribute{
   305  						newTestAttribute("key1", "val2"),
   306  						newTestAttribute("key2", "val2"),
   307  					},
   308  					PayloadPatch: &objectSDK.PayloadPatch{
   309  						Range: rangeWithOffestWithLength(0, 0),
   310  						Chunk: []byte("inserted at the beginning"),
   311  					},
   312  				},
   313  			},
   314  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   315  			patchedPayload:        []byte("inserted at the beginning0123456789qwertyuiopasdfghjklzxcvbnm"),
   316  			patchedHeaderAttributes: []objectSDK.Attribute{
   317  				newTestAttribute("key1", "val2"),
   318  				newTestAttribute("key2", "val2"),
   319  			},
   320  		},
   321  		{
   322  			name: "header only merge attributes",
   323  			patches: []objectSDK.Patch{
   324  				{
   325  					NewAttributes: []objectSDK.Attribute{
   326  						newTestAttribute("key1", "val2"),
   327  						newTestAttribute("key2", "val2-incoming"),
   328  					},
   329  					PayloadPatch: &objectSDK.PayloadPatch{
   330  						Range: rangeWithOffestWithLength(0, 0),
   331  						Chunk: []byte("inserted at the beginning"),
   332  					},
   333  				},
   334  			},
   335  			originalHeaderAttributes: []objectSDK.Attribute{
   336  				newTestAttribute("key2", "to be popped out"),
   337  				newTestAttribute("key3", "val3"),
   338  			},
   339  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   340  			patchedPayload:        []byte("inserted at the beginning0123456789qwertyuiopasdfghjklzxcvbnm"),
   341  			patchedHeaderAttributes: []objectSDK.Attribute{
   342  				newTestAttribute("key2", "val2-incoming"),
   343  				newTestAttribute("key3", "val3"),
   344  				newTestAttribute("key1", "val2"),
   345  			},
   346  		},
   347  		{
   348  			name: "header only then payload",
   349  			patches: []objectSDK.Patch{
   350  				{
   351  					NewAttributes: []objectSDK.Attribute{
   352  						newTestAttribute("key1", "val2"),
   353  						newTestAttribute("key2", "val2"),
   354  					},
   355  				},
   356  				{
   357  					PayloadPatch: &objectSDK.PayloadPatch{
   358  						Range: rangeWithOffestWithLength(0, 0),
   359  						Chunk: []byte("inserted at the beginning"),
   360  					},
   361  				},
   362  			},
   363  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   364  			patchedPayload:        []byte("inserted at the beginning0123456789qwertyuiopasdfghjklzxcvbnm"),
   365  			patchedHeaderAttributes: []objectSDK.Attribute{
   366  				newTestAttribute("key1", "val2"),
   367  				newTestAttribute("key2", "val2"),
   368  			},
   369  		},
   370  		{
   371  			name: "no effect",
   372  			patches: []objectSDK.Patch{
   373  				{
   374  					PayloadPatch: &objectSDK.PayloadPatch{
   375  						Range: rangeWithOffestWithLength(0, 0),
   376  						Chunk: []byte(""),
   377  					},
   378  				},
   379  				{
   380  					PayloadPatch: &objectSDK.PayloadPatch{
   381  						Range: rangeWithOffestWithLength(12, 0),
   382  						Chunk: []byte(""),
   383  					},
   384  				},
   385  				{
   386  					PayloadPatch: &objectSDK.PayloadPatch{
   387  						Range: rangeWithOffestWithLength(20, 0),
   388  						Chunk: []byte(""),
   389  					},
   390  				},
   391  			},
   392  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   393  			patchedPayload:        []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   394  		},
   395  		{
   396  			name: "insert prefix",
   397  			patches: []objectSDK.Patch{
   398  				{
   399  					PayloadPatch: &objectSDK.PayloadPatch{
   400  						Range: rangeWithOffestWithLength(0, 0),
   401  						Chunk: []byte("inserted at the beginning"),
   402  					},
   403  				},
   404  			},
   405  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   406  			patchedPayload:        []byte("inserted at the beginning0123456789qwertyuiopasdfghjklzxcvbnm"),
   407  		},
   408  		{
   409  			name: "insert in the middle",
   410  			patches: []objectSDK.Patch{
   411  				{
   412  					PayloadPatch: &objectSDK.PayloadPatch{
   413  						Range: rangeWithOffestWithLength(5, 0),
   414  						Chunk: []byte("inserted somewhere in the middle"),
   415  					},
   416  				},
   417  			},
   418  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   419  			patchedPayload:        []byte("01234inserted somewhere in the middle56789qwertyuiopasdfghjklzxcvbnm"),
   420  		},
   421  		{
   422  			name: "insert at the end",
   423  			patches: []objectSDK.Patch{
   424  				{
   425  					PayloadPatch: &objectSDK.PayloadPatch{
   426  						Range: rangeWithOffestWithLength(36, 0),
   427  						Chunk: []byte("inserted somewhere at the end"),
   428  					},
   429  				},
   430  			},
   431  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   432  			patchedPayload:        []byte("0123456789qwertyuiopasdfghjklzxcvbnminserted somewhere at the end"),
   433  		},
   434  		{
   435  			name: "replace by range",
   436  			patches: []objectSDK.Patch{
   437  				{
   438  					PayloadPatch: &objectSDK.PayloadPatch{
   439  						Range: rangeWithOffestWithLength(0, 12),
   440  						Chunk: []byte("just replace"),
   441  					},
   442  				},
   443  			},
   444  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   445  			patchedPayload:        []byte("just replaceertyuiopasdfghjklzxcvbnm"),
   446  		},
   447  		{
   448  			name: "replace and insert some bytes",
   449  			patches: []objectSDK.Patch{
   450  				{
   451  					PayloadPatch: &objectSDK.PayloadPatch{
   452  						Range: rangeWithOffestWithLength(0, 11),
   453  						Chunk: []byte("replace and append in the middle"),
   454  					},
   455  				},
   456  			},
   457  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   458  			patchedPayload:        []byte("replace and append in the middlewertyuiopasdfghjklzxcvbnm"),
   459  		},
   460  		{
   461  			name: "replace and insert some bytes in the middle",
   462  			patches: []objectSDK.Patch{
   463  				{
   464  					PayloadPatch: &objectSDK.PayloadPatch{
   465  						Range: rangeWithOffestWithLength(5, 3),
   466  						Chunk: []byte("@@@@@"),
   467  					},
   468  				},
   469  			},
   470  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   471  			patchedPayload:        []byte("01234@@@@@89qwertyuiopasdfghjklzxcvbnm"),
   472  		},
   473  		{
   474  			name: "a few patches: prefix, suffix",
   475  			patches: []objectSDK.Patch{
   476  				{
   477  					PayloadPatch: &objectSDK.PayloadPatch{
   478  						Range: rangeWithOffestWithLength(0, 0),
   479  						Chunk: []byte("this_will_be_prefix"),
   480  					},
   481  				},
   482  				{
   483  					PayloadPatch: &objectSDK.PayloadPatch{
   484  						Range: rangeWithOffestWithLength(36, 0),
   485  						Chunk: []byte("this_will_be_suffix"),
   486  					},
   487  				},
   488  			},
   489  			originalObjectPayload: []byte("0123456789qwertyuiopasdfghjklzxcvbnm"),
   490  			patchedPayload:        []byte("this_will_be_prefix0123456789qwertyuiopasdfghjklzxcvbnmthis_will_be_suffix"),
   491  		},
   492  		{
   493  			name: "a few patches: replace and insert some bytes",
   494  			patches: []objectSDK.Patch{
   495  				{
   496  					PayloadPatch: &objectSDK.PayloadPatch{
   497  						Range: rangeWithOffestWithLength(10, 3),
   498  						Chunk: []byte("aaaaa"),
   499  					},
   500  				},
   501  				{
   502  					PayloadPatch: &objectSDK.PayloadPatch{
   503  						Range: rangeWithOffestWithLength(16, 0),
   504  						Chunk: []byte("bbbbb"),
   505  					},
   506  				},
   507  			},
   508  			originalObjectPayload: []byte("0123456789ABCDEF"),
   509  			patchedPayload:        []byte("0123456789aaaaaDEFbbbbb"),
   510  		},
   511  		{
   512  			name: "starting from the same offset",
   513  			patches: []objectSDK.Patch{
   514  				{
   515  					PayloadPatch: &objectSDK.PayloadPatch{
   516  						Range: rangeWithOffestWithLength(8, 3),
   517  						Chunk: []byte("1"),
   518  					},
   519  				},
   520  				{
   521  					PayloadPatch: &objectSDK.PayloadPatch{
   522  						Range: rangeWithOffestWithLength(11, 0),
   523  						Chunk: []byte("2"),
   524  					},
   525  				},
   526  				{
   527  					PayloadPatch: &objectSDK.PayloadPatch{
   528  						Range: rangeWithOffestWithLength(11, 0),
   529  						Chunk: []byte("3"),
   530  					},
   531  				},
   532  			},
   533  			originalObjectPayload: []byte("abcdefghijklmnop"),
   534  			patchedPayload:        []byte("abcdefgh123lmnop"),
   535  		},
   536  		{
   537  			name: "a few patches: various modifiactions",
   538  			patches: []objectSDK.Patch{
   539  				{
   540  					PayloadPatch: &objectSDK.PayloadPatch{
   541  						Range: rangeWithOffestWithLength(4, 8),
   542  						Chunk: []byte("earliest"),
   543  					},
   544  				},
   545  				{
   546  					PayloadPatch: &objectSDK.PayloadPatch{
   547  						Range: rangeWithOffestWithLength(13, 0),
   548  						Chunk: []byte("known "),
   549  					},
   550  				},
   551  				{
   552  					PayloadPatch: &objectSDK.PayloadPatch{
   553  						Range: rangeWithOffestWithLength(35, 8),
   554  						Chunk: []byte("a small town"),
   555  					},
   556  				},
   557  				{
   558  					PayloadPatch: &objectSDK.PayloadPatch{
   559  						Range: rangeWithOffestWithLength(62, 6),
   560  						Chunk: []byte("tablet"),
   561  					},
   562  				},
   563  				{
   564  					PayloadPatch: &objectSDK.PayloadPatch{
   565  						Range: rangeWithOffestWithLength(87, 0),
   566  						Chunk: []byte("Shar-Kali-Sharri"),
   567  					},
   568  				},
   569  			},
   570  			originalObjectPayload: []byte("The ******** mention of Babylon as [insert] appears on a clay ****** from the reign of "),
   571  			patchedPayload:        []byte("The earliest known mention of Babylon as a small town appears on a clay tablet from the reign of Shar-Kali-Sharri"),
   572  		},
   573  	} {
   574  		t.Run(test.name, func(t *testing.T) {
   575  			rangeProvider := &mockRangeProvider{
   576  				originalObjectPayload: test.originalObjectPayload,
   577  			}
   578  
   579  			originalObject, _ := newTestObject()
   580  			originalObject.SetPayload(test.originalObjectPayload)
   581  			originalObject.SetPayloadSize(uint64(len(test.originalObjectPayload)))
   582  			originalObject.SetAttributes(test.originalHeaderAttributes...)
   583  
   584  			patchedObject, _ := newTestObject()
   585  
   586  			wr := &mockPatchedObjectWriter{
   587  				obj: patchedObject,
   588  			}
   589  
   590  			prm := Params{
   591  				Header: originalObject.CutPayload(),
   592  
   593  				RangeProvider: rangeProvider,
   594  
   595  				ObjectWriter: wr,
   596  			}
   597  
   598  			patcher := New(prm)
   599  
   600  			for i, patch := range test.patches {
   601  				if i == 0 {
   602  					_ = patcher.ApplyAttributesPatch(context.Background(), patch.NewAttributes, patch.ReplaceAttributes)
   603  				}
   604  
   605  				if patch.PayloadPatch == nil {
   606  					continue
   607  				}
   608  
   609  				err := patcher.ApplyPayloadPatch(context.Background(), patch.PayloadPatch)
   610  				if err != nil && test.expectedPayloadPatchErr != nil {
   611  					require.ErrorIs(t, err, test.expectedPayloadPatchErr)
   612  					return
   613  				}
   614  				require.NoError(t, err)
   615  			}
   616  
   617  			_, err := patcher.Close(context.Background())
   618  			require.NoError(t, err)
   619  			require.Equal(t, test.patchedPayload, patchedObject.Payload())
   620  
   621  			patchedAttrs := append([]objectSDK.Attribute{}, test.patchedHeaderAttributes...)
   622  			require.Equal(t, patchedAttrs, patchedObject.Attributes())
   623  		})
   624  	}
   625  
   626  }