func TestIndex(t *testing.T) {
	db := newTestDB(t)
	index, err := db.NewIndex("retrieval", retrievalIndexFuncs)
	if err != nil {
		t.Fatal(err)
	}
	t.Run("put", func(t *testing.T) {
		want := Item{
			Address:        []byte("put-hash"),
			Data:           []byte("DATA"),
			StoreTimestamp: time.Now().UTC().UnixNano(),
		}
		err := index.Put(want)
		if err != nil {
			t.Fatal(err)
		}
		got, err := index.Get(Item{
			Address: want.Address,
		})
		if err != nil {
			t.Fatal(err)
		}
		checkItem(t, got, want)
		t.Run("overwrite", func(t *testing.T) {
			want := Item{
				Address:        []byte("put-hash"),
				Data:           []byte("New DATA"),
				StoreTimestamp: time.Now().UTC().UnixNano(),
			}
			err = index.Put(want)
			if err != nil {
				t.Fatal(err)
			}
			got, err := index.Get(Item{
				Address: want.Address,
			})
			if err != nil {
				t.Fatal(err)
			}
			checkItem(t, got, want)
		})
	})
	t.Run("put in batch", func(t *testing.T) {
		want := Item{
			Address:        []byte("put-in-batch-hash"),
			Data:           []byte("DATA"),
			StoreTimestamp: time.Now().UTC().UnixNano(),
		}
		batch := new(leveldb.Batch)
		err = index.PutInBatch(batch, want)
		if err != nil {
			t.Fatal(err)
		}
		err := db.WriteBatch(batch)
		if err != nil {
			t.Fatal(err)
		}
		got, err := index.Get(Item{
			Address: want.Address,
		})
		if err != nil {
			t.Fatal(err)
		}
		checkItem(t, got, want)
		t.Run("overwrite", func(t *testing.T) {
			want := Item{
				Address:        []byte("put-in-batch-hash"),
				Data:           []byte("New DATA"),
				StoreTimestamp: time.Now().UTC().UnixNano(),
			}
			batch := new(leveldb.Batch)
			err = index.PutInBatch(batch, want)
			if err != nil {
				t.Fatal(err)
			}
			err = db.WriteBatch(batch)
			if err != nil {
				t.Fatal(err)
			}
			got, err := index.Get(Item{
				Address: want.Address,
			})
			if err != nil {
				t.Fatal(err)
			}
			checkItem(t, got, want)
		})
	})
	t.Run("put in batch twice", func(t *testing.T) {
		batch := new(leveldb.Batch)
		address := []byte("put-in-batch-twice-hash")
		err = index.PutInBatch(batch, Item{
			Address:        address,
			Data:           []byte("DATA"),
			StoreTimestamp: time.Now().UTC().UnixNano(),
		})
		if err != nil {
			t.Fatal(err)
		}
		want := Item{
			Address:        address,
			Data:           []byte("New DATA"),
			StoreTimestamp: time.Now().UTC().UnixNano(),
		}
		err = index.PutInBatch(batch, want)
		if err != nil {
			t.Fatal(err)
		}
		err = db.WriteBatch(batch)
		if err != nil {
			t.Fatal(err)
		}
		got, err := index.Get(Item{
			Address: address,
		})
		if err != nil {
			t.Fatal(err)
		}
		checkItem(t, got, want)
	})
	t.Run("has", func(t *testing.T) {
		want := Item{
			Address:        []byte("has-hash"),
			Data:           []byte("DATA"),
			StoreTimestamp: time.Now().UTC().UnixNano(),
		}
		dontWant := Item{
			Address:        []byte("do-not-has-hash"),
			Data:           []byte("DATA"),
			StoreTimestamp: time.Now().UTC().UnixNano(),
		}
		err := index.Put(want)
		if err != nil {
			t.Fatal(err)
		}
		has, err := index.Has(want)
		if err != nil {
			t.Fatal(err)
		}
		if !has {
			t.Error("item is not found")
		}
		has, err = index.Has(dontWant)
		if err != nil {
			t.Fatal(err)
		}
		if has {
			t.Error("unwanted item is found")
		}
	})
	t.Run("delete", func(t *testing.T) {
		want := Item{
			Address:        []byte("delete-hash"),
			Data:           []byte("DATA"),
			StoreTimestamp: time.Now().UTC().UnixNano(),
		}
		err := index.Put(want)
		if err != nil {
			t.Fatal(err)
		}
		got, err := index.Get(Item{
			Address: want.Address,
		})
		if err != nil {
			t.Fatal(err)
		}
		checkItem(t, got, want)
		err = index.Delete(Item{
			Address: want.Address,
		})
		if err != nil {
			t.Fatal(err)
		}
		wantErr := leveldb.ErrNotFound
		_, err = index.Get(Item{
			Address: want.Address,
		})
		if !errors.Is(err, wantErr) {
			t.Fatalf("got error %v, want %v", err, wantErr)
		}
	})
	t.Run("delete in batch", func(t *testing.T) {
		want := Item{
			Address:        []byte("delete-in-batch-hash"),
			Data:           []byte("DATA"),
			StoreTimestamp: time.Now().UTC().UnixNano(),
		}
		err := index.Put(want)
		if err != nil {
			t.Fatal(err)
		}
		got, err := index.Get(Item{
			Address: want.Address,
		})
		if err != nil {
			t.Fatal(err)
		}
		checkItem(t, got, want)
		batch := new(leveldb.Batch)
		err = index.DeleteInBatch(batch, Item{
			Address: want.Address,
		})
		if err != nil {
			t.Fatal(err)
		}
		err = db.WriteBatch(batch)
		if err != nil {
			t.Fatal(err)
		}
		wantErr := leveldb.ErrNotFound
		_, err = index.Get(Item{
			Address: want.Address,
		})
		if !errors.Is(err, wantErr) {
			t.Fatalf("got error %v, want %v", err, wantErr)
		}
	})
	t.Run("fill", func(t *testing.T) {
		want := []Item{
			{
				Address:        []byte("put-hash-1"),
				Data:           []byte("DATA123"),
				StoreTimestamp: time.Now().UTC().UnixNano(),
			},
			{
				Address:        []byte("put-hash-32"),
				Data:           []byte("DATA124"),
				StoreTimestamp: time.Now().UTC().UnixNano(),
			},
			{
				Address:        []byte("put-hash-42"),
				Data:           []byte("DATA125"),
				StoreTimestamp: time.Now().UTC().UnixNano(),
			},
			{
				Address:        []byte("put-hash-71"),
				Data:           []byte("DATA126"),
				StoreTimestamp: time.Now().UTC().UnixNano(),
			},
		}
		for _, item := range want {
			err := index.Put(item)
			if err != nil {
				t.Fatal(err)
			}
		}
		items := make([]Item, len(want))
		for i, w := range want {
			items[i] = Item{
				Address: w.Address,
			}
		}
		err = index.Fill(items)
		if err != nil {
			t.Fatal(err)
		}
		for i := range items {
			checkItem(t, items[i], want[i])
		}
		t.Run("not found", func(t *testing.T) {
			items := make([]Item, len(want))
			for i, w := range want {
				items[i] = Item{
					Address: w.Address,
				}
			}
			items = append(items, Item{
				Address: []byte("put-hash-missing"),
			})
			want := leveldb.ErrNotFound
			err := index.Fill(items)
			if !errors.Is(err, want) {
				t.Errorf("got error %v, want %v", err, want)
			}
		})
	})
}
TestIndex validates put, get, fill, has and delete functions of the Index implementation.
package shed
import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"sort"
	"testing"
	"time"
	"github.com/syndtr/goleveldb/leveldb"
)
var retrievalIndexFuncs = IndexFuncs{
	EncodeKey: func(fields Item) (key []byte, err error) {
		return fields.Address, nil
	},
	DecodeKey: func(key []byte) (e Item, err error) {
		e.Address = key
		return e, nil
	},
	EncodeValue: func(fields Item) (value []byte, err error) {
		b := make([]byte, 8)
		binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp))
		value = append(b, fields.Data...)
		return value, nil
	},
	DecodeValue: func(keyItem Item, value []byte) (e Item, err error) {
		e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8]))
		e.Data = value[8:]
		return e, nil
	},
}