github.com/grafana/pyroscope@v1.18.0/pkg/settings/store/store_test.go (about)

     1  package store
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/grafana/pyroscope/pkg/objstore/providers/filesystem"
    11  
    12  	"github.com/go-kit/log"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  type testObj struct {
    17  	Name       string
    18  	Data       string
    19  	Generation int64
    20  }
    21  
    22  const storeJSON = `{
    23    "generation":"4",
    24    "elements":[
    25      {"Name":"a","Data":"data-a-v3","Generation":3},
    26      {"Name":"b","Data":"data-b","Generation":1}
    27    ]
    28  }`
    29  
    30  type testObjHelper struct{}
    31  
    32  func (*testObjHelper) ID(o *testObj) string {
    33  	return o.Name
    34  }
    35  
    36  func (*testObjHelper) GetGeneration(o *testObj) int64 {
    37  	return o.Generation
    38  }
    39  
    40  func (*testObjHelper) SetGeneration(o *testObj, gen int64) {
    41  	o.Generation = gen
    42  }
    43  
    44  func (*testObjHelper) FromStore(data json.RawMessage) (*testObj, error) {
    45  	var obj testObj
    46  	err := json.Unmarshal(data, &obj)
    47  	return &obj, err
    48  }
    49  
    50  func (*testObjHelper) ToStore(obj *testObj) (json.RawMessage, error) {
    51  	return json.Marshal(obj)
    52  }
    53  
    54  func (*testObjHelper) TypePath() string {
    55  	return "testobj.v1"
    56  }
    57  
    58  type testStore struct {
    59  	*GenericStore[*testObj, *testObjHelper]
    60  	bucketPath string
    61  }
    62  
    63  func newTestStore(t testing.TB, tenantID string) *testStore {
    64  	logger := log.NewNopLogger()
    65  	if testing.Verbose() {
    66  		logger = log.NewLogfmtLogger(os.Stderr)
    67  	}
    68  	bucketPath := t.TempDir()
    69  	bucket, err := filesystem.NewBucket(bucketPath)
    70  	require.NoError(t, err)
    71  	return &testStore{
    72  		GenericStore: New(
    73  			logger,
    74  			bucket,
    75  			Key{TenantID: tenantID},
    76  			&testObjHelper{},
    77  		),
    78  		bucketPath: bucketPath,
    79  	}
    80  }
    81  
    82  func Test_GenericStore(t *testing.T) {
    83  	s := newTestStore(t, "user-a")
    84  	ctx := context.Background()
    85  
    86  	t.Run("empty", func(t *testing.T) {
    87  		result, err := s.Get(ctx)
    88  		require.NoError(t, err)
    89  		require.Equal(t, []*testObj{}, result.Elements)
    90  	})
    91  
    92  	t.Run("one element", func(t *testing.T) {
    93  		require.NoError(t, s.Upsert(ctx, &testObj{Name: "a", Data: "data-a"}, nil))
    94  		result, err := s.Get(ctx)
    95  		require.NoError(t, err)
    96  		require.Equal(t, []*testObj{
    97  			{Name: "a", Data: "data-a", Generation: 1},
    98  		}, result.Elements)
    99  	})
   100  
   101  	t.Run("second element", func(t *testing.T) {
   102  		require.NoError(t, s.Upsert(ctx, &testObj{Name: "b", Data: "data-b"}, nil))
   103  		result, err := s.Get(ctx)
   104  		require.NoError(t, err)
   105  		require.Equal(t, []*testObj{
   106  			{Name: "a", Data: "data-a", Generation: 1},
   107  			{Name: "b", Data: "data-b", Generation: 1},
   108  		}, result.Elements)
   109  	})
   110  
   111  	t.Run("update without generation", func(t *testing.T) {
   112  		require.NoError(t, s.Upsert(ctx, &testObj{Name: "a", Data: "data-a-v2"}, nil))
   113  		result, err := s.Get(ctx)
   114  		require.NoError(t, err)
   115  		require.Equal(t, []*testObj{
   116  			{Name: "a", Data: "data-a-v2", Generation: 2},
   117  			{Name: "b", Data: "data-b", Generation: 1},
   118  		}, result.Elements)
   119  	})
   120  
   121  	t.Run("update with generation", func(t *testing.T) {
   122  		observedGeneration := int64(2)
   123  		require.NoError(t, s.Upsert(ctx, &testObj{Name: "a", Data: "data-a-v3"}, &observedGeneration))
   124  		result, err := s.Get(ctx)
   125  		require.NoError(t, err)
   126  		require.Equal(t, []*testObj{
   127  			{Name: "a", Data: "data-a-v3", Generation: 3},
   128  			{Name: "b", Data: "data-b", Generation: 1},
   129  		}, result.Elements)
   130  	})
   131  
   132  	t.Run("validate stored data is as expected", func(t *testing.T) {
   133  		storePath := filepath.Join(s.bucketPath, "user-a/testobj.v1.json")
   134  		actual, err := os.ReadFile(storePath)
   135  		require.NoError(t, err)
   136  		require.JSONEq(t, storeJSON, string(actual))
   137  	})
   138  
   139  	t.Run("restore from stored data", func(t *testing.T) {
   140  		newS := newTestStore(t, "user-b")
   141  		storePath := filepath.Join(newS.bucketPath, "user-b/testobj.v1.json")
   142  		require.NoError(t, os.MkdirAll(filepath.Dir(storePath), 0o755))
   143  		require.NoError(t, os.WriteFile(
   144  			storePath,
   145  			[]byte(storeJSON),
   146  			0o644,
   147  		))
   148  		result, err := newS.Get(ctx)
   149  		require.NoError(t, err)
   150  		require.Equal(t, []*testObj{
   151  			{Name: "a", Data: "data-a-v3", Generation: 3},
   152  			{Name: "b", Data: "data-b", Generation: 1},
   153  		}, result.Elements)
   154  	})
   155  
   156  	t.Run("update with wrong generation", func(t *testing.T) {
   157  		observedGeneration := int64(2)
   158  		require.ErrorContains(t, s.Upsert(ctx, &testObj{Name: "a", Data: "data-a-v4"}, &observedGeneration), "conflicting update, please try again: observed_generation=2, store_generation=3")
   159  		result, err := s.Get(ctx)
   160  		require.NoError(t, err)
   161  		require.Equal(t, []*testObj{
   162  			{Name: "a", Data: "data-a-v3", Generation: 3},
   163  			{Name: "b", Data: "data-b", Generation: 1},
   164  		}, result.Elements)
   165  	})
   166  
   167  	t.Run("delete element that exists", func(t *testing.T) {
   168  		require.NoError(t, s.Delete(ctx, "a"))
   169  		result, err := s.Get(ctx)
   170  		require.NoError(t, err)
   171  		require.Equal(t, []*testObj{
   172  			{Name: "b", Data: "data-b", Generation: 1},
   173  		}, result.Elements)
   174  	})
   175  	t.Run("delete element that doesnt exist", func(t *testing.T) {
   176  		require.ErrorContains(t, s.Delete(ctx, "c"), "element not found")
   177  		result, err := s.Get(ctx)
   178  		require.NoError(t, err)
   179  		require.Equal(t, []*testObj{
   180  			{Name: "b", Data: "data-b", Generation: 1},
   181  		}, result.Elements)
   182  	})
   183  }