github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/internal/upload/uploadstore_test.go (about)

     1  // Copyright 2022 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package upload_test
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"math"
    13  	"math/rand"
    14  	"os"
    15  	"testing"
    16  	"time"
    17  
    18  	storage "github.com/ethersphere/bee/v2/pkg/storage"
    19  	"github.com/ethersphere/bee/v2/pkg/storage/storagetest"
    20  	chunktest "github.com/ethersphere/bee/v2/pkg/storage/testing"
    21  	"github.com/ethersphere/bee/v2/pkg/storer/internal"
    22  	"github.com/ethersphere/bee/v2/pkg/storer/internal/transaction"
    23  	"github.com/ethersphere/bee/v2/pkg/storer/internal/upload"
    24  	"github.com/ethersphere/bee/v2/pkg/swarm"
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/google/go-cmp/cmp/cmpopts"
    27  )
    28  
    29  // now is a function that returns the current time and replaces time.Now.
    30  var now = func() time.Time { return time.Unix(1234567890, 0) }
    31  
    32  // TestMain exists to adjust the time.Now function to a fixed value.
    33  func TestMain(m *testing.M) {
    34  	upload.ReplaceTimeNow(now)
    35  	code := m.Run()
    36  	upload.ReplaceTimeNow(time.Now)
    37  	os.Exit(code)
    38  }
    39  
    40  func TestPushItem(t *testing.T) {
    41  	t.Parallel()
    42  
    43  	tests := []struct {
    44  		name string
    45  		test *storagetest.ItemMarshalAndUnmarshalTest
    46  	}{{
    47  		name: "zero values",
    48  		test: &storagetest.ItemMarshalAndUnmarshalTest{
    49  			Item:       &upload.PushItem{},
    50  			Factory:    func() storage.Item { return new(upload.PushItem) },
    51  			MarshalErr: upload.ErrPushItemMarshalAddressIsZero,
    52  		},
    53  	}, {
    54  		name: "zero address",
    55  		test: &storagetest.ItemMarshalAndUnmarshalTest{
    56  			Item: &upload.PushItem{
    57  				Timestamp: 1,
    58  				Address:   swarm.ZeroAddress,
    59  				TagID:     1,
    60  			},
    61  			Factory:    func() storage.Item { return new(upload.PushItem) },
    62  			MarshalErr: upload.ErrPushItemMarshalAddressIsZero,
    63  		},
    64  	}, {
    65  		name: "nil stamp",
    66  		test: &storagetest.ItemMarshalAndUnmarshalTest{
    67  			Item: &upload.PushItem{
    68  				Timestamp: 1,
    69  				Address:   swarm.NewAddress(storagetest.MinAddressBytes[:]),
    70  				TagID:     1,
    71  			},
    72  			Factory:    func() storage.Item { return new(upload.PushItem) },
    73  			MarshalErr: upload.ErrPushItemMarshalBatchInvalid,
    74  		},
    75  	}, {
    76  		name: "min values",
    77  		test: &storagetest.ItemMarshalAndUnmarshalTest{
    78  			Item: &upload.PushItem{
    79  				Timestamp: 0,
    80  				Address:   swarm.NewAddress(storagetest.MinAddressBytes[:]),
    81  				BatchID:   storagetest.MinAddressBytes[:],
    82  				TagID:     0,
    83  			},
    84  			Factory: func() storage.Item { return new(upload.PushItem) },
    85  		},
    86  	}, {
    87  		name: "max values",
    88  		test: &storagetest.ItemMarshalAndUnmarshalTest{
    89  			Item: &upload.PushItem{
    90  				Timestamp: math.MaxInt64,
    91  				Address:   swarm.NewAddress(storagetest.MaxAddressBytes[:]),
    92  				BatchID:   storagetest.MaxAddressBytes[:],
    93  				TagID:     math.MaxUint64,
    94  			},
    95  			Factory: func() storage.Item { return new(upload.PushItem) },
    96  		},
    97  	}, {
    98  		name: "random values",
    99  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   100  			Item: &upload.PushItem{
   101  				Timestamp: rand.Int63(),
   102  				Address:   swarm.RandAddress(t),
   103  				BatchID:   swarm.RandAddress(t).Bytes(),
   104  				TagID:     rand.Uint64(),
   105  			},
   106  			Factory: func() storage.Item { return new(upload.PushItem) },
   107  		},
   108  	}, {
   109  		name: "invalid size",
   110  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   111  			Item: &storagetest.ItemStub{
   112  				MarshalBuf:   []byte{0xFF},
   113  				UnmarshalBuf: []byte{0xFF},
   114  			},
   115  			Factory:      func() storage.Item { return new(upload.PushItem) },
   116  			UnmarshalErr: upload.ErrPushItemUnmarshalInvalidSize,
   117  		},
   118  	}}
   119  
   120  	for _, tc := range tests {
   121  		tc := tc
   122  
   123  		t.Run(fmt.Sprintf("%s marshal/unmarshal", tc.name), func(t *testing.T) {
   124  			t.Parallel()
   125  
   126  			storagetest.TestItemMarshalAndUnmarshal(t, tc.test)
   127  		})
   128  
   129  		t.Run(fmt.Sprintf("%s clone", tc.name), func(t *testing.T) {
   130  			t.Parallel()
   131  
   132  			storagetest.TestItemClone(t, &storagetest.ItemCloneTest{
   133  				Item:    tc.test.Item,
   134  				CmpOpts: tc.test.CmpOpts,
   135  			})
   136  		})
   137  	}
   138  }
   139  
   140  func TestTagItem(t *testing.T) {
   141  	t.Parallel()
   142  
   143  	tests := []struct {
   144  		name string
   145  		test *storagetest.ItemMarshalAndUnmarshalTest
   146  	}{{
   147  		name: "zero values",
   148  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   149  			Item:    &upload.TagItem{},
   150  			Factory: func() storage.Item { return new(upload.TagItem) },
   151  		},
   152  	}, {
   153  		name: "max values",
   154  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   155  			Item: &upload.TagItem{
   156  				TagID:     math.MaxUint64,
   157  				Split:     math.MaxUint64,
   158  				Seen:      math.MaxUint64,
   159  				Stored:    math.MaxUint64,
   160  				Sent:      math.MaxUint64,
   161  				Synced:    math.MaxUint64,
   162  				Address:   swarm.NewAddress(storagetest.MaxAddressBytes[:]),
   163  				StartedAt: math.MaxInt64,
   164  			},
   165  			Factory: func() storage.Item { return new(upload.TagItem) },
   166  		},
   167  	}, {
   168  		name: "random values",
   169  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   170  			Item: &upload.TagItem{
   171  				TagID:     rand.Uint64(),
   172  				Split:     rand.Uint64(),
   173  				Seen:      rand.Uint64(),
   174  				Stored:    rand.Uint64(),
   175  				Sent:      rand.Uint64(),
   176  				Synced:    rand.Uint64(),
   177  				Address:   swarm.RandAddress(t),
   178  				StartedAt: rand.Int63(),
   179  			},
   180  			Factory: func() storage.Item { return new(upload.TagItem) },
   181  		},
   182  	}, {
   183  		name: "invalid size",
   184  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   185  			Item: &storagetest.ItemStub{
   186  				MarshalBuf:   []byte{0xFF},
   187  				UnmarshalBuf: []byte{0xFF},
   188  			},
   189  			Factory:      func() storage.Item { return new(upload.TagItem) },
   190  			UnmarshalErr: upload.ErrTagItemUnmarshalInvalidSize,
   191  		},
   192  	}}
   193  
   194  	for _, tc := range tests {
   195  		tc := tc
   196  
   197  		t.Run(fmt.Sprintf("%s marshal/unmarshal", tc.name), func(t *testing.T) {
   198  			t.Parallel()
   199  
   200  			storagetest.TestItemMarshalAndUnmarshal(t, tc.test)
   201  		})
   202  
   203  		t.Run(fmt.Sprintf("%s clone", tc.name), func(t *testing.T) {
   204  			t.Parallel()
   205  
   206  			storagetest.TestItemClone(t, &storagetest.ItemCloneTest{
   207  				Item:    tc.test.Item,
   208  				CmpOpts: tc.test.CmpOpts,
   209  			})
   210  		})
   211  	}
   212  }
   213  
   214  func TestUploadItem(t *testing.T) {
   215  	t.Parallel()
   216  
   217  	randomAddress := swarm.RandAddress(t)
   218  	randomBatchId := swarm.RandAddress(t).Bytes()
   219  
   220  	tests := []struct {
   221  		name string
   222  		test *storagetest.ItemMarshalAndUnmarshalTest
   223  	}{{
   224  		name: "zero values",
   225  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   226  			Item:       &upload.UploadItem{},
   227  			Factory:    func() storage.Item { return new(upload.UploadItem) },
   228  			MarshalErr: upload.ErrUploadItemMarshalAddressIsZero,
   229  		},
   230  	}, {
   231  		name: "zero address",
   232  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   233  			Item: &upload.UploadItem{
   234  				Address: swarm.ZeroAddress,
   235  				BatchID: storagetest.MinAddressBytes[:],
   236  			},
   237  			Factory:    func() storage.Item { return new(upload.UploadItem) },
   238  			MarshalErr: upload.ErrUploadItemMarshalAddressIsZero,
   239  		},
   240  	}, {
   241  		name: "nil stamp",
   242  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   243  			Item: &upload.UploadItem{
   244  				Address: swarm.NewAddress(storagetest.MinAddressBytes[:]),
   245  			},
   246  			Factory:    func() storage.Item { return new(upload.UploadItem) },
   247  			MarshalErr: upload.ErrUploadItemMarshalBatchInvalid,
   248  		},
   249  	}, {
   250  		name: "min values",
   251  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   252  			Item: &upload.UploadItem{
   253  				Address: swarm.NewAddress(storagetest.MinAddressBytes[:]),
   254  				BatchID: storagetest.MinAddressBytes[:],
   255  			},
   256  			Factory: func() storage.Item {
   257  				return &upload.UploadItem{
   258  					Address: swarm.NewAddress(storagetest.MinAddressBytes[:]),
   259  					BatchID: storagetest.MinAddressBytes[:],
   260  				}
   261  			},
   262  		},
   263  	}, {
   264  		name: "max values",
   265  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   266  			Item: &upload.UploadItem{
   267  				Address:  swarm.NewAddress(storagetest.MaxAddressBytes[:]),
   268  				BatchID:  storagetest.MaxAddressBytes[:],
   269  				TagID:    math.MaxUint64,
   270  				Uploaded: math.MaxInt64,
   271  				Synced:   math.MaxInt64,
   272  			},
   273  			Factory: func() storage.Item {
   274  				return &upload.UploadItem{
   275  					Address: swarm.NewAddress(storagetest.MaxAddressBytes[:]),
   276  					BatchID: storagetest.MaxAddressBytes[:],
   277  				}
   278  			},
   279  		},
   280  	}, {
   281  		name: "random values",
   282  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   283  			Item: &upload.UploadItem{
   284  				Address:  randomAddress,
   285  				BatchID:  randomBatchId,
   286  				TagID:    rand.Uint64(),
   287  				Uploaded: rand.Int63(),
   288  				Synced:   rand.Int63(),
   289  			},
   290  			Factory: func() storage.Item {
   291  				return &upload.UploadItem{
   292  					Address: randomAddress,
   293  					BatchID: randomBatchId,
   294  				}
   295  			},
   296  		},
   297  	}, {
   298  		name: "invalid size",
   299  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   300  			Item: &storagetest.ItemStub{
   301  				MarshalBuf:   []byte{0xFF},
   302  				UnmarshalBuf: []byte{0xFF},
   303  			},
   304  			Factory:      func() storage.Item { return new(upload.UploadItem) },
   305  			UnmarshalErr: upload.ErrUploadItemUnmarshalInvalidSize,
   306  		},
   307  	}}
   308  
   309  	for _, tc := range tests {
   310  		tc := tc
   311  
   312  		t.Run(fmt.Sprintf("%s marshal/unmarshal", tc.name), func(t *testing.T) {
   313  			t.Parallel()
   314  
   315  			storagetest.TestItemMarshalAndUnmarshal(t, tc.test)
   316  		})
   317  
   318  		t.Run(fmt.Sprintf("%s clone", tc.name), func(t *testing.T) {
   319  			t.Parallel()
   320  
   321  			storagetest.TestItemClone(t, &storagetest.ItemCloneTest{
   322  				Item:    tc.test.Item,
   323  				CmpOpts: tc.test.CmpOpts,
   324  			})
   325  		})
   326  	}
   327  }
   328  
   329  func TestItemNextTagID(t *testing.T) {
   330  	t.Parallel()
   331  
   332  	zeroValue := upload.NextTagID(0)
   333  	maxValue := upload.NextTagID(math.MaxUint64)
   334  
   335  	tests := []struct {
   336  		name string
   337  		test *storagetest.ItemMarshalAndUnmarshalTest
   338  	}{{
   339  		name: "zero values",
   340  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   341  			Item:    &zeroValue,
   342  			Factory: func() storage.Item { return new(upload.NextTagID) },
   343  		},
   344  	}, {
   345  		name: "max value",
   346  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   347  			Item:    &maxValue,
   348  			Factory: func() storage.Item { return new(upload.NextTagID) },
   349  		},
   350  	}, {
   351  		name: "invalid size",
   352  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   353  			Item: &storagetest.ItemStub{
   354  				MarshalBuf:   []byte{0xFF},
   355  				UnmarshalBuf: []byte{0xFF},
   356  			},
   357  			Factory:      func() storage.Item { return new(upload.NextTagID) },
   358  			UnmarshalErr: upload.ErrNextTagIDUnmarshalInvalidSize,
   359  		},
   360  	}}
   361  
   362  	for _, tc := range tests {
   363  		tc := tc
   364  
   365  		t.Run(fmt.Sprintf("%s marshal/unmarshal", tc.name), func(t *testing.T) {
   366  			t.Parallel()
   367  
   368  			storagetest.TestItemMarshalAndUnmarshal(t, tc.test)
   369  		})
   370  
   371  		t.Run(fmt.Sprintf("%s clone", tc.name), func(t *testing.T) {
   372  			t.Parallel()
   373  
   374  			storagetest.TestItemClone(t, &storagetest.ItemCloneTest{
   375  				Item:    tc.test.Item,
   376  				CmpOpts: tc.test.CmpOpts,
   377  			})
   378  		})
   379  	}
   380  }
   381  
   382  func TestItemDirtyTagItem(t *testing.T) {
   383  	t.Parallel()
   384  
   385  	tests := []struct {
   386  		name string
   387  		test *storagetest.ItemMarshalAndUnmarshalTest
   388  	}{{
   389  		name: "zero values",
   390  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   391  			Item:    &upload.DirtyTagItem{},
   392  			Factory: func() storage.Item { return new(upload.DirtyTagItem) },
   393  		},
   394  	}, {
   395  		name: "max value",
   396  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   397  			Item:    &upload.DirtyTagItem{TagID: math.MaxUint64, Started: math.MaxInt64},
   398  			Factory: func() storage.Item { return new(upload.DirtyTagItem) },
   399  		},
   400  	}, {
   401  		name: "invalid size",
   402  		test: &storagetest.ItemMarshalAndUnmarshalTest{
   403  			Item: &storagetest.ItemStub{
   404  				MarshalBuf:   []byte{0xFF},
   405  				UnmarshalBuf: []byte{0xFF},
   406  			},
   407  			Factory:      func() storage.Item { return new(upload.DirtyTagItem) },
   408  			UnmarshalErr: upload.ErrDirtyTagItemUnmarshalInvalidSize,
   409  		},
   410  	}}
   411  
   412  	for _, tc := range tests {
   413  		tc := tc
   414  
   415  		t.Run(fmt.Sprintf("%s marshal/unmarshal", tc.name), func(t *testing.T) {
   416  			t.Parallel()
   417  
   418  			storagetest.TestItemMarshalAndUnmarshal(t, tc.test)
   419  		})
   420  
   421  		t.Run(fmt.Sprintf("%s clone", tc.name), func(t *testing.T) {
   422  			t.Parallel()
   423  
   424  			storagetest.TestItemClone(t, &storagetest.ItemCloneTest{
   425  				Item:    tc.test.Item,
   426  				CmpOpts: tc.test.CmpOpts,
   427  			})
   428  		})
   429  	}
   430  }
   431  
   432  func newTestStorage(t *testing.T) transaction.Storage {
   433  	t.Helper()
   434  
   435  	storg := internal.NewInmemStorage()
   436  	return storg
   437  }
   438  
   439  func TestChunkPutter(t *testing.T) {
   440  	t.Parallel()
   441  
   442  	ts := newTestStorage(t)
   443  
   444  	tx, done := ts.NewTransaction(context.Background())
   445  	defer done()
   446  	tag, err := upload.NextTag(tx.IndexStore())
   447  	if err != nil {
   448  		t.Fatalf("failed creating tag: %v", err)
   449  	}
   450  
   451  	putter, err := upload.NewPutter(tx.IndexStore(), tag.TagID)
   452  	if err != nil {
   453  		t.Fatalf("failed creating putter: %v", err)
   454  	}
   455  	_ = tx.Commit()
   456  
   457  	for _, chunk := range chunktest.GenerateTestRandomChunks(10) {
   458  		t.Run(fmt.Sprintf("chunk %s", chunk.Address()), func(t *testing.T) {
   459  			t.Run("put new chunk", func(t *testing.T) {
   460  				err := ts.Run(context.Background(), func(s transaction.Store) error {
   461  					return putter.Put(context.Background(), s, chunk)
   462  				})
   463  				if err != nil {
   464  					t.Fatalf("Put(...): unexpected error: %v", err)
   465  				}
   466  			})
   467  
   468  			t.Run("put existing chunk", func(t *testing.T) {
   469  				err := ts.Run(context.Background(), func(s transaction.Store) error {
   470  					return putter.Put(context.Background(), s, chunk)
   471  				})
   472  				if err != nil {
   473  					t.Fatalf("Put(...): unexpected error: %v", err)
   474  				}
   475  			})
   476  
   477  			t.Run("verify internal state", func(t *testing.T) {
   478  				ui := &upload.UploadItem{
   479  					Address: chunk.Address(),
   480  					BatchID: chunk.Stamp().BatchID(),
   481  				}
   482  				err := ts.IndexStore().Get(ui)
   483  				if err != nil {
   484  					t.Fatalf("Get(...): unexpected error: %v", err)
   485  				}
   486  				wantUI := &upload.UploadItem{
   487  					Address:  chunk.Address(),
   488  					BatchID:  chunk.Stamp().BatchID(),
   489  					TagID:    tag.TagID,
   490  					Uploaded: now().UnixNano(),
   491  				}
   492  
   493  				if diff := cmp.Diff(wantUI, ui); diff != "" {
   494  					t.Fatalf("Get(...): unexpected UploadItem (-want +have):\n%s", diff)
   495  				}
   496  
   497  				pi := &upload.PushItem{
   498  					Timestamp: now().UnixNano(),
   499  					Address:   chunk.Address(),
   500  					BatchID:   chunk.Stamp().BatchID(),
   501  				}
   502  				err = ts.IndexStore().Get(pi)
   503  				if err != nil {
   504  					t.Fatalf("Get(...): unexpected error: %v", err)
   505  				}
   506  				wantPI := &upload.PushItem{
   507  					Address:   chunk.Address(),
   508  					BatchID:   chunk.Stamp().BatchID(),
   509  					TagID:     tag.TagID,
   510  					Timestamp: now().UnixNano(),
   511  				}
   512  
   513  				if diff := cmp.Diff(wantPI, pi); diff != "" {
   514  					t.Fatalf("Get(...): unexpected UploadItem (-want +have):\n%s", diff)
   515  				}
   516  
   517  				have, err := ts.ChunkStore().Get(context.Background(), chunk.Address())
   518  				if err != nil {
   519  					t.Fatalf("Get(...): unexpected error: %v", err)
   520  				}
   521  				if want := chunk; !want.Equal(have) {
   522  					t.Fatalf("Get(...): chunk mismatch:\nwant: %x\nhave: %x", want, have)
   523  				}
   524  			})
   525  		})
   526  	}
   527  
   528  	t.Run("iterate all", func(t *testing.T) {
   529  		count := 0
   530  		err := ts.IndexStore().Iterate(
   531  			storage.Query{
   532  				Factory: func() storage.Item { return new(upload.UploadItem) },
   533  			},
   534  			func(r storage.Result) (bool, error) {
   535  				address := swarm.NewAddress([]byte(r.ID[:32]))
   536  				synced := r.Entry.(*upload.UploadItem).Synced != 0
   537  				count++
   538  				if synced {
   539  					t.Fatal("expected synced to be false")
   540  				}
   541  				has, err := ts.ChunkStore().Has(context.Background(), address)
   542  				if err != nil {
   543  					t.Fatalf("unexpected error in Has(...): %v", err)
   544  				}
   545  				if !has {
   546  					t.Fatalf("expected chunk to be present %s", address.String())
   547  				}
   548  				return false, nil
   549  			},
   550  		)
   551  		if err != nil {
   552  			t.Fatalf("IterateAll(...): unexpected error %v", err)
   553  		}
   554  		if count != 10 {
   555  			t.Fatalf("unexpected count: want 10 have %d", count)
   556  		}
   557  	})
   558  
   559  	t.Run("close with reference", func(t *testing.T) {
   560  		addr := swarm.RandAddress(t)
   561  
   562  		err := ts.Run(context.Background(), func(s transaction.Store) error {
   563  			return putter.Close(s.IndexStore(), addr)
   564  		})
   565  		if err != nil {
   566  			t.Fatalf("Close(...): unexpected error %v", err)
   567  		}
   568  
   569  		var ti upload.TagItem
   570  
   571  		err = ts.Run(context.Background(), func(s transaction.Store) error {
   572  			ti, err = upload.TagInfo(s.IndexStore(), tag.TagID)
   573  			return err
   574  		})
   575  		if err != nil {
   576  			t.Fatalf("TagInfo(...): unexpected error %v", err)
   577  		}
   578  
   579  		wantTI := upload.TagItem{
   580  			TagID:     tag.TagID,
   581  			Split:     20,
   582  			Seen:      10,
   583  			StartedAt: now().UnixNano(),
   584  			Address:   addr,
   585  		}
   586  		if diff := cmp.Diff(wantTI, ti); diff != "" {
   587  			t.Fatalf("Get(...): unexpected TagItem (-want +have):\n%s", diff)
   588  		}
   589  
   590  		t.Run("iterate all tag items", func(t *testing.T) {
   591  			var tagItemsCount, uploaded, synced uint64
   592  			err := upload.IterateAllTagItems(ts.IndexStore(), func(ti *upload.TagItem) (bool, error) {
   593  				uploaded += ti.Split
   594  				synced += ti.Synced
   595  				tagItemsCount++
   596  				return false, nil
   597  			})
   598  			if err != nil {
   599  				t.Fatalf("IterateAllTagItems(...): unexpected error %v", err)
   600  			}
   601  			if tagItemsCount != 1 {
   602  				t.Fatalf("unexpected tagItemsCount: want 1 have %d", tagItemsCount)
   603  			}
   604  			if uploaded != 20 {
   605  				t.Fatalf("unexpected uploaded: want 20 have %d", uploaded)
   606  			}
   607  			if synced != 0 {
   608  				t.Fatalf("unexpected synced: want 0 have %d", synced)
   609  			}
   610  		})
   611  	})
   612  
   613  	t.Run("error after close", func(t *testing.T) {
   614  		err := ts.Run(context.Background(), func(s transaction.Store) error {
   615  			return putter.Put(context.Background(), s, chunktest.GenerateTestRandomChunk())
   616  		})
   617  		if !errors.Is(err, upload.ErrPutterAlreadyClosed) {
   618  			t.Fatalf("unexpected error, expected: %v, got: %v", upload.ErrPutterAlreadyClosed, err)
   619  		}
   620  	})
   621  
   622  	t.Run("restart putter", func(t *testing.T) {
   623  
   624  		var putter internal.PutterCloserWithReference
   625  
   626  		err = ts.Run(context.Background(), func(s transaction.Store) error {
   627  			putter, err = upload.NewPutter(s.IndexStore(), tag.TagID)
   628  			return err
   629  		})
   630  		if err != nil {
   631  			t.Fatalf("failed creating putter: %v", err)
   632  		}
   633  
   634  		for _, chunk := range chunktest.GenerateTestRandomChunks(5) {
   635  			if err := ts.Run(context.Background(), func(s transaction.Store) error {
   636  				return putter.Put(context.Background(), s, chunk)
   637  			}); err != nil {
   638  				t.Fatalf("Put(...): unexpected error: %v", err)
   639  			}
   640  		}
   641  
   642  		// close with different address
   643  		addr := swarm.RandAddress(t)
   644  		if err := ts.Run(context.Background(), func(s transaction.Store) error {
   645  			return putter.Close(s.IndexStore(), addr)
   646  		}); err != nil {
   647  			t.Fatalf("Close(...): unexpected error %v", err)
   648  		}
   649  
   650  		ti, err := upload.TagInfo(ts.IndexStore(), tag.TagID)
   651  		if err != nil {
   652  			t.Fatalf("TagInfo(...): unexpected error %v", err)
   653  		}
   654  
   655  		wantTI := upload.TagItem{
   656  			TagID:     tag.TagID,
   657  			Split:     25,
   658  			Seen:      10,
   659  			StartedAt: now().UnixNano(),
   660  			Address:   addr,
   661  		}
   662  
   663  		if diff := cmp.Diff(wantTI, ti); diff != "" {
   664  			t.Fatalf("Get(...): unexpected TagItem (-want +have):\n%s", diff)
   665  		}
   666  	})
   667  }
   668  
   669  func TestChunkReporter(t *testing.T) {
   670  	t.Parallel()
   671  
   672  	ts := newTestStorage(t)
   673  
   674  	var (
   675  		tag    upload.TagItem
   676  		putter internal.PutterCloserWithReference
   677  		err    error
   678  	)
   679  	if err := ts.Run(context.Background(), func(s transaction.Store) error {
   680  		tag, err = upload.NextTag(s.IndexStore())
   681  		return err
   682  	}); err != nil {
   683  		t.Fatalf("failed creating tag: %v", err)
   684  	}
   685  
   686  	if err := ts.Run(context.Background(), func(s transaction.Store) error {
   687  		putter, err = upload.NewPutter(s.IndexStore(), tag.TagID)
   688  		return err
   689  	}); err != nil {
   690  		t.Fatalf("failed creating putter: %v", err)
   691  	}
   692  
   693  	for idx, chunk := range chunktest.GenerateTestRandomChunks(10) {
   694  		t.Run(fmt.Sprintf("chunk %s", chunk.Address()), func(t *testing.T) {
   695  
   696  			if err := ts.Run(context.Background(), func(s transaction.Store) error {
   697  				return putter.Put(context.Background(), s, chunk)
   698  			}); err != nil {
   699  				t.Fatalf("Put(...): unexpected error: %v", err)
   700  			}
   701  
   702  			report := func(ch swarm.Chunk, state int) {
   703  				t.Helper()
   704  				if err := ts.Run(context.Background(), func(s transaction.Store) error {
   705  					return upload.Report(context.Background(), s, ch, state)
   706  				}); err != nil {
   707  					t.Fatalf("Report(...): unexpected error: %v", err)
   708  				}
   709  			}
   710  
   711  			t.Run("mark sent", func(t *testing.T) {
   712  				report(chunk, storage.ChunkSent)
   713  			})
   714  
   715  			if idx < 4 {
   716  				t.Run("mark stored", func(t *testing.T) {
   717  					report(chunk, storage.ChunkStored)
   718  				})
   719  			}
   720  
   721  			if idx >= 4 && idx < 8 {
   722  				t.Run("mark synced", func(t *testing.T) {
   723  					report(chunk, storage.ChunkSynced)
   724  				})
   725  			}
   726  
   727  			if idx >= 8 {
   728  				t.Run("mark could not sync", func(t *testing.T) {
   729  					report(chunk, storage.ChunkCouldNotSync)
   730  				})
   731  			}
   732  
   733  			t.Run("verify internal state", func(t *testing.T) {
   734  				ti := &upload.TagItem{
   735  					TagID: tag.TagID,
   736  				}
   737  				err := ts.IndexStore().Get(ti)
   738  				if err != nil {
   739  					t.Fatalf("Get(...): unexpected error: %v", err)
   740  				}
   741  				var synced, sent, stored uint64
   742  				sent = uint64(idx + 1)
   743  				if idx >= 8 {
   744  					synced, stored = 8, 4
   745  				} else {
   746  					synced, stored = uint64(idx+1), uint64(idx+1)
   747  					if idx >= 4 {
   748  						stored = 4
   749  					}
   750  				}
   751  				wantTI := &upload.TagItem{
   752  					TagID:     tag.TagID,
   753  					StartedAt: now().UnixNano(),
   754  					Sent:      sent,
   755  					Synced:    synced,
   756  					Stored:    stored,
   757  				}
   758  
   759  				if diff := cmp.Diff(wantTI, ti); diff != "" {
   760  					t.Fatalf("Get(...): unexpected TagItem (-want +have):\n%s", diff)
   761  				}
   762  
   763  				ui := &upload.UploadItem{
   764  					Address: chunk.Address(),
   765  					BatchID: chunk.Stamp().BatchID(),
   766  				}
   767  				has, err := ts.IndexStore().Has(ui)
   768  				if err != nil {
   769  					t.Fatalf("unexpected error: %v", err)
   770  				}
   771  				if has {
   772  					t.Fatalf("expected to not be found: %s", ui)
   773  				}
   774  
   775  				pi := &upload.PushItem{
   776  					Timestamp: now().UnixNano(),
   777  					Address:   chunk.Address(),
   778  					BatchID:   chunk.Stamp().BatchID(),
   779  				}
   780  				has, err = ts.IndexStore().Has(pi)
   781  				if err != nil {
   782  					t.Fatalf("Has(...): unexpected error: %v", err)
   783  				}
   784  				if has {
   785  					t.Fatalf("Has(...): expected to not be found: %s", pi)
   786  				}
   787  
   788  				have, err := ts.ChunkStore().Has(context.Background(), chunk.Address())
   789  				if err != nil {
   790  					t.Fatalf("Get(...): unexpected error: %v", err)
   791  				}
   792  				if have {
   793  					t.Fatalf("Get(...): chunk expected to not be found: %s", chunk.Address())
   794  				}
   795  			})
   796  		})
   797  	}
   798  
   799  	t.Run("close with reference", func(t *testing.T) {
   800  		addr := swarm.RandAddress(t)
   801  
   802  		err := ts.Run(context.Background(), func(s transaction.Store) error { return putter.Close(s.IndexStore(), addr) })
   803  		if err != nil {
   804  			t.Fatalf("Close(...): unexpected error %v", err)
   805  		}
   806  
   807  		var ti upload.TagItem
   808  		err = ts.Run(context.Background(), func(s transaction.Store) error {
   809  			ti, err = upload.TagInfo(s.IndexStore(), tag.TagID)
   810  			return err
   811  		})
   812  		if err != nil {
   813  			t.Fatalf("TagInfo(...): unexpected error %v", err)
   814  		}
   815  
   816  		wantTI := upload.TagItem{
   817  			TagID:     tag.TagID,
   818  			Split:     10,
   819  			Seen:      0,
   820  			Stored:    4,
   821  			Sent:      10,
   822  			Synced:    8,
   823  			StartedAt: now().UnixNano(),
   824  			Address:   addr,
   825  		}
   826  		if diff := cmp.Diff(wantTI, ti); diff != "" {
   827  			t.Fatalf("Get(...): unexpected TagItem (-want +have):\n%s", diff)
   828  		}
   829  	})
   830  }
   831  
   832  func TestNextTagID(t *testing.T) {
   833  	t.Parallel()
   834  
   835  	ts := newTestStorage(t)
   836  
   837  	for i := 1; i < 4; i++ {
   838  		var tag upload.TagItem
   839  		var err error
   840  		err = ts.Run(context.Background(), func(s transaction.Store) error {
   841  			tag, err = upload.NextTag(s.IndexStore())
   842  			return err
   843  		})
   844  		if err != nil {
   845  			t.Fatalf("failed creating tag: %v", err)
   846  		}
   847  
   848  		if tag.TagID != uint64(i) {
   849  			t.Fatalf("incorrect tag ID returned, exp: %d found %d", i, tag.TagID)
   850  		}
   851  	}
   852  
   853  	var lastTag upload.NextTagID
   854  	err := ts.IndexStore().Get(&lastTag)
   855  	if err != nil {
   856  		t.Fatal(err)
   857  	}
   858  
   859  	if uint64(lastTag) != 3 {
   860  		t.Fatalf("incorrect value for last tag, exp 3 found %d", uint64(lastTag))
   861  	}
   862  }
   863  
   864  func TestListTags(t *testing.T) {
   865  	t.Parallel()
   866  
   867  	ts := newTestStorage(t)
   868  
   869  	want := make([]upload.TagItem, 10)
   870  	for i := range want {
   871  		var tag upload.TagItem
   872  		var err error
   873  		err = ts.Run(context.Background(), func(s transaction.Store) error {
   874  			tag, err = upload.NextTag(s.IndexStore())
   875  			return err
   876  		})
   877  		if err != nil {
   878  			t.Fatalf("failed creating tag: %v", err)
   879  		}
   880  		want[i] = tag
   881  	}
   882  
   883  	have, err := upload.ListAllTags(ts.IndexStore())
   884  	if err != nil {
   885  		t.Fatalf("upload.ListAllTags(): unexpected error: %v", err)
   886  	}
   887  
   888  	opts := cmpopts.SortSlices(func(i, j upload.TagItem) bool { return i.TagID < j.TagID })
   889  	if diff := cmp.Diff(want, have, opts); diff != "" {
   890  		t.Fatalf("upload.ListAllTags(): mismatch (-want +have):\n%s", diff)
   891  	}
   892  }
   893  
   894  func TestIterate(t *testing.T) {
   895  	t.Parallel()
   896  
   897  	ts := newTestStorage(t)
   898  
   899  	t.Run("on empty storage does not call the callback fn", func(t *testing.T) {
   900  		err := upload.IteratePending(context.Background(), ts, func(chunk swarm.Chunk) (bool, error) {
   901  			t.Fatal("unexpected call")
   902  			return false, nil
   903  		})
   904  		if err != nil {
   905  			t.Fatal(err)
   906  		}
   907  	})
   908  
   909  	t.Run("iterates chunks", func(t *testing.T) {
   910  		var tag upload.TagItem
   911  		var err error
   912  		err = ts.Run(context.Background(), func(s transaction.Store) error {
   913  			tag, err = upload.NextTag(s.IndexStore())
   914  			return err
   915  		})
   916  		if err != nil {
   917  			t.Fatalf("failed creating tag: %v", err)
   918  		}
   919  
   920  		var putter internal.PutterCloserWithReference
   921  		err = ts.Run(context.Background(), func(s transaction.Store) error {
   922  			putter, err = upload.NewPutter(s.IndexStore(), tag.TagID)
   923  			return err
   924  		})
   925  		if err != nil {
   926  			t.Fatalf("failed creating putter: %v", err)
   927  		}
   928  
   929  		chunk1, chunk2 := chunktest.GenerateTestRandomChunk(), chunktest.GenerateTestRandomChunk()
   930  		err = put(t, ts, putter, chunk1)
   931  		if err != nil {
   932  			t.Fatalf("session.Put(...): unexpected error: %v", err)
   933  		}
   934  		err = put(t, ts, putter, chunk2)
   935  		if err != nil {
   936  			t.Fatalf("session.Put(...): unexpected error: %v", err)
   937  		}
   938  
   939  		var count int
   940  
   941  		err = upload.IteratePending(context.Background(), ts, func(chunk swarm.Chunk) (bool, error) {
   942  			count++
   943  			if !chunk.Equal(chunk1) && !chunk.Equal(chunk2) {
   944  				return true, fmt.Errorf("unknown chunk %s", chunk.Address())
   945  			}
   946  			return false, nil
   947  		})
   948  		if err != nil {
   949  			t.Fatalf("Iterate(...): unexpected error: %v", err)
   950  		}
   951  
   952  		if count != 0 {
   953  			t.Fatalf("expected to iterate 0 chunks, got: %v", count)
   954  		}
   955  
   956  		err = ts.Run(context.Background(), func(s transaction.Store) error { return putter.Close(s.IndexStore(), swarm.ZeroAddress) })
   957  		if err != nil {
   958  			t.Fatalf("Close(...) error: %v", err)
   959  		}
   960  
   961  		err = upload.IteratePending(context.Background(), ts, func(chunk swarm.Chunk) (bool, error) {
   962  			count++
   963  			if !chunk.Equal(chunk1) && !chunk.Equal(chunk2) {
   964  				return true, fmt.Errorf("unknown chunk %s", chunk.Address())
   965  			}
   966  			return false, nil
   967  		})
   968  		if err != nil {
   969  			t.Fatalf("Iterate(...): unexpected error: %v", err)
   970  		}
   971  
   972  		if count != 2 {
   973  			t.Fatalf("expected to iterate two chunks, got: %v", count)
   974  		}
   975  	})
   976  }
   977  
   978  func TestDeleteTag(t *testing.T) {
   979  	t.Parallel()
   980  
   981  	ts := newTestStorage(t)
   982  
   983  	var tag upload.TagItem
   984  	var err error
   985  	err = ts.Run(context.Background(), func(s transaction.Store) error {
   986  		tag, err = upload.NextTag(s.IndexStore())
   987  		return err
   988  	})
   989  	if err != nil {
   990  		t.Fatalf("failed creating tag: %v", err)
   991  	}
   992  
   993  	err = ts.Run(context.Background(), func(s transaction.Store) error {
   994  		return upload.DeleteTag(s.IndexStore(), tag.TagID)
   995  	})
   996  	if err != nil {
   997  		t.Fatalf("upload.DeleteTag(): unexpected error: %v", err)
   998  	}
   999  
  1000  	_, err = upload.TagInfo(ts.IndexStore(), tag.TagID)
  1001  	if !errors.Is(err, storage.ErrNotFound) {
  1002  		t.Fatalf("want: %v; have: %v", storage.ErrNotFound, err)
  1003  	}
  1004  }
  1005  
  1006  func TestBatchIDForChunk(t *testing.T) {
  1007  	t.Parallel()
  1008  
  1009  	ts := newTestStorage(t)
  1010  
  1011  	var tag upload.TagItem
  1012  	var err error
  1013  	err = ts.Run(context.Background(), func(s transaction.Store) error {
  1014  		tag, err = upload.NextTag(s.IndexStore())
  1015  		return err
  1016  	})
  1017  	if err != nil {
  1018  		t.Fatalf("failed creating tag: %v", err)
  1019  	}
  1020  
  1021  	var putter internal.PutterCloserWithReference
  1022  	err = ts.Run(context.Background(), func(s transaction.Store) error {
  1023  		putter, err = upload.NewPutter(s.IndexStore(), tag.TagID)
  1024  		return err
  1025  	})
  1026  	if err != nil {
  1027  		t.Fatalf("failed creating putter: %v", err)
  1028  	}
  1029  
  1030  	chunk := chunktest.GenerateTestRandomChunk()
  1031  	if err := put(t, ts, putter, chunk); err != nil {
  1032  		t.Fatalf("Put(...): unexpected error: %v", err)
  1033  	}
  1034  
  1035  	batchID, err := upload.BatchIDForChunk(ts.IndexStore(), chunk.Address())
  1036  	if err != nil {
  1037  		t.Fatalf("BatchIDForChunk(...): unexpected error: %v", err)
  1038  	}
  1039  
  1040  	if !bytes.Equal(batchID, chunk.Stamp().BatchID()) {
  1041  		t.Fatalf("BatchIDForChunk(...): want %x; got %x", chunk.Stamp().BatchID(), batchID)
  1042  	}
  1043  }
  1044  
  1045  func TestCleanup(t *testing.T) {
  1046  	t.Parallel()
  1047  
  1048  	t.Run("cleanup putter", func(t *testing.T) {
  1049  		t.Parallel()
  1050  
  1051  		ts := newTestStorage(t)
  1052  
  1053  		var tag upload.TagItem
  1054  		var err error
  1055  		err = ts.Run(context.Background(), func(s transaction.Store) error {
  1056  			tag, err = upload.NextTag(s.IndexStore())
  1057  			return err
  1058  		})
  1059  		if err != nil {
  1060  			t.Fatalf("failed creating tag: %v", err)
  1061  		}
  1062  
  1063  		var putter internal.PutterCloserWithReference
  1064  		err = ts.Run(context.Background(), func(s transaction.Store) error {
  1065  			putter, err = upload.NewPutter(s.IndexStore(), tag.TagID)
  1066  			return err
  1067  		})
  1068  		if err != nil {
  1069  			t.Fatalf("failed creating putter: %v", err)
  1070  		}
  1071  
  1072  		chunk := chunktest.GenerateTestRandomChunk()
  1073  		err = put(t, ts, putter, chunk)
  1074  		if err != nil {
  1075  			t.Fatal("session.Put(...): unexpected error", err)
  1076  		}
  1077  
  1078  		err = putter.Cleanup(ts)
  1079  		if err != nil {
  1080  			t.Fatal("upload.Cleanup(...): unexpected error", err)
  1081  		}
  1082  
  1083  		count := 0
  1084  		_ = upload.IteratePending(context.Background(), ts, func(chunk swarm.Chunk) (bool, error) {
  1085  			count++
  1086  			return false, nil
  1087  		})
  1088  		if count != 0 {
  1089  			t.Fatalf("expected to iterate 0 chunks, got: %v", count)
  1090  		}
  1091  
  1092  		if _, err := ts.ChunkStore().Get(context.Background(), chunk.Address()); !errors.Is(err, storage.ErrNotFound) {
  1093  			t.Fatalf("expected chunk not found error, got: %v", err)
  1094  		}
  1095  	})
  1096  
  1097  	t.Run("cleanup dirty", func(t *testing.T) {
  1098  		t.Parallel()
  1099  
  1100  		ts := newTestStorage(t)
  1101  
  1102  		var tag upload.TagItem
  1103  		var err error
  1104  		err = ts.Run(context.Background(), func(s transaction.Store) error {
  1105  			tag, err = upload.NextTag(s.IndexStore())
  1106  			return err
  1107  		})
  1108  		if err != nil {
  1109  			t.Fatalf("failed creating tag: %v", err)
  1110  		}
  1111  
  1112  		var putter internal.PutterCloserWithReference
  1113  		err = ts.Run(context.Background(), func(s transaction.Store) error {
  1114  			putter, err = upload.NewPutter(s.IndexStore(), tag.TagID)
  1115  			return err
  1116  		})
  1117  		if err != nil {
  1118  			t.Fatalf("failed creating putter: %v", err)
  1119  		}
  1120  
  1121  		chunk := chunktest.GenerateTestRandomChunk()
  1122  		err = put(t, ts, putter, chunk)
  1123  		if err != nil {
  1124  			t.Fatal("session.Put(...): unexpected error", err)
  1125  		}
  1126  
  1127  		err = upload.CleanupDirty(ts)
  1128  		if err != nil {
  1129  			t.Fatal("upload.Cleanup(...): unexpected error", err)
  1130  		}
  1131  
  1132  		count := 0
  1133  		_ = upload.IteratePending(context.Background(), ts, func(chunk swarm.Chunk) (bool, error) {
  1134  			count++
  1135  			return false, nil
  1136  		})
  1137  		if count != 0 {
  1138  			t.Fatalf("expected to iterate 0 chunks, got: %v", count)
  1139  		}
  1140  
  1141  		if _, err := ts.ChunkStore().Get(context.Background(), chunk.Address()); !errors.Is(err, storage.ErrNotFound) {
  1142  			t.Fatalf("expected chunk not found error, got: %v", err)
  1143  		}
  1144  	})
  1145  }
  1146  
  1147  func put(t *testing.T, ts transaction.Storage, putter internal.PutterCloserWithReference, ch swarm.Chunk) error {
  1148  	t.Helper()
  1149  	return ts.Run(context.Background(), func(s transaction.Store) error {
  1150  		return putter.Put(context.Background(), s, ch)
  1151  	})
  1152  }