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

     1  package settings
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"connectrpc.com/connect"
    10  	"github.com/go-kit/log"
    11  	"github.com/stretchr/testify/mock"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	settingsv1 "github.com/grafana/pyroscope/api/gen/proto/go/settings/v1"
    15  	"github.com/grafana/pyroscope/pkg/tenant"
    16  )
    17  
    18  func TestTenantSettings_Get(t *testing.T) {
    19  	t.Run("get a setting", func(t *testing.T) {
    20  		const tenantID = "1234"
    21  		wantSetting := &settingsv1.Setting{
    22  			Name:       "key1",
    23  			Value:      "val1",
    24  			ModifiedAt: 100,
    25  		}
    26  
    27  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{
    28  			tenantID: {
    29  				wantSetting,
    30  			},
    31  		})
    32  		defer cleanup()
    33  
    34  		ctx := tenant.InjectTenantID(context.Background(), tenantID)
    35  		req := &connect.Request[settingsv1.GetSettingsRequest]{}
    36  
    37  		got, err := ts.Get(ctx, req)
    38  		require.NoError(t, err)
    39  
    40  		want := &settingsv1.GetSettingsResponse{
    41  			Settings: []*settingsv1.Setting{wantSetting},
    42  		}
    43  		require.Equal(t, want, got.Msg)
    44  	})
    45  
    46  	t.Run("missing tenant id", func(t *testing.T) {
    47  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{})
    48  		defer cleanup()
    49  
    50  		ctx := context.Background()
    51  		req := &connect.Request[settingsv1.GetSettingsRequest]{}
    52  
    53  		_, err := ts.Get(ctx, req)
    54  		require.EqualError(t, err, "invalid_argument: no org id")
    55  	})
    56  
    57  	t.Run("settings store returns error", func(t *testing.T) {
    58  		store := &fakeStore{}
    59  		wantErr := fmt.Errorf("settings store failed")
    60  
    61  		// Get method fails once.
    62  		store.On("Get", mock.Anything, mock.Anything).
    63  			Return(nil, wantErr).
    64  			Once()
    65  
    66  		ts := &TenantSettings{
    67  			store:  store,
    68  			logger: log.NewNopLogger(),
    69  		}
    70  
    71  		ctx := tenant.InjectTenantID(context.Background(), "1234")
    72  		req := &connect.Request[settingsv1.GetSettingsRequest]{}
    73  
    74  		_, err := ts.Get(ctx, req)
    75  		require.EqualError(t, err, fmt.Sprintf("internal: %s", wantErr))
    76  	})
    77  }
    78  
    79  func TestTenantSettings_Set(t *testing.T) {
    80  	t.Run("set a new setting", func(t *testing.T) {
    81  		const tenantID = "1234"
    82  		wantSetting := &settingsv1.Setting{
    83  			Name:       "key1",
    84  			Value:      "val1",
    85  			ModifiedAt: 100,
    86  		}
    87  
    88  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{})
    89  		defer cleanup()
    90  
    91  		ctx := tenant.InjectTenantID(context.Background(), tenantID)
    92  		req := &connect.Request[settingsv1.SetSettingsRequest]{
    93  			Msg: &settingsv1.SetSettingsRequest{
    94  				Setting: wantSetting,
    95  			},
    96  		}
    97  
    98  		got, err := ts.Set(ctx, req)
    99  		require.NoError(t, err)
   100  
   101  		want := &settingsv1.SetSettingsResponse{
   102  			Setting: wantSetting,
   103  		}
   104  		require.Equal(t, want, got.Msg)
   105  	})
   106  
   107  	t.Run("set a new setting without a timestamp", func(t *testing.T) {
   108  		const tenantID = "1234"
   109  		wantSetting := &settingsv1.Setting{
   110  			Name:  "key1",
   111  			Value: "val1",
   112  		}
   113  
   114  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{})
   115  		defer cleanup()
   116  
   117  		ctx := tenant.InjectTenantID(context.Background(), tenantID)
   118  		req := &connect.Request[settingsv1.SetSettingsRequest]{
   119  			Msg: &settingsv1.SetSettingsRequest{
   120  				Setting: wantSetting,
   121  			},
   122  		}
   123  
   124  		got, err := ts.Set(ctx, req)
   125  		require.NoError(t, err)
   126  
   127  		want := &settingsv1.SetSettingsResponse{
   128  			Setting: wantSetting,
   129  		}
   130  		require.Equal(t, want.Setting.Name, got.Msg.Setting.Name)
   131  		require.Equal(t, want.Setting.Value, got.Msg.Setting.Value)
   132  		require.NotZero(t, got.Msg.Setting.ModifiedAt, "ModifiedAt value did not get set")
   133  	})
   134  
   135  	t.Run("update a setting", func(t *testing.T) {
   136  		const tenantID = "1234"
   137  		initialSetting := &settingsv1.Setting{
   138  			Name:       "key1",
   139  			Value:      "val1",
   140  			ModifiedAt: 100,
   141  		}
   142  		wantSetting := &settingsv1.Setting{
   143  			Name:       "key1",
   144  			Value:      "val1 (new)",
   145  			ModifiedAt: 101,
   146  		}
   147  
   148  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{
   149  			tenantID: {
   150  				initialSetting,
   151  			},
   152  		})
   153  		defer cleanup()
   154  
   155  		ctx := tenant.InjectTenantID(context.Background(), tenantID)
   156  		req := &connect.Request[settingsv1.SetSettingsRequest]{
   157  			Msg: &settingsv1.SetSettingsRequest{
   158  				Setting: wantSetting,
   159  			},
   160  		}
   161  
   162  		got, err := ts.Set(ctx, req)
   163  		require.NoError(t, err)
   164  
   165  		want := &settingsv1.SetSettingsResponse{
   166  			Setting: wantSetting,
   167  		}
   168  		require.Equal(t, want, got.Msg)
   169  	})
   170  
   171  	t.Run("missing tenant id", func(t *testing.T) {
   172  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{})
   173  		defer cleanup()
   174  
   175  		ctx := context.Background()
   176  		req := &connect.Request[settingsv1.SetSettingsRequest]{
   177  			Msg: &settingsv1.SetSettingsRequest{
   178  				Setting: &settingsv1.Setting{},
   179  			},
   180  		}
   181  
   182  		_, err := ts.Set(ctx, req)
   183  		require.EqualError(t, err, "invalid_argument: no org id")
   184  	})
   185  
   186  	t.Run("missing setting values", func(t *testing.T) {
   187  		const tenantID = "1234"
   188  
   189  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{})
   190  		defer cleanup()
   191  
   192  		ctx := tenant.InjectTenantID(context.Background(), tenantID)
   193  		req := &connect.Request[settingsv1.SetSettingsRequest]{
   194  			Msg: &settingsv1.SetSettingsRequest{
   195  				Setting: nil, // Purposely empty
   196  			},
   197  		}
   198  
   199  		_, err := ts.Set(ctx, req)
   200  		require.EqualError(t, err, "invalid_argument: no setting values provided")
   201  	})
   202  
   203  	t.Run("already exists", func(t *testing.T) {
   204  		const tenantID = "1234"
   205  		initialSetting := &settingsv1.Setting{
   206  			Name:       "key1",
   207  			Value:      "val1",
   208  			ModifiedAt: 100,
   209  		}
   210  		wantSetting := &settingsv1.Setting{
   211  			Name:       "key1",
   212  			Value:      "val1 (new)",
   213  			ModifiedAt: 99, // Timestamp older than most current.
   214  		}
   215  
   216  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{
   217  			tenantID: {
   218  				initialSetting,
   219  			},
   220  		})
   221  		defer cleanup()
   222  
   223  		ctx := tenant.InjectTenantID(context.Background(), tenantID)
   224  		req := &connect.Request[settingsv1.SetSettingsRequest]{
   225  			Msg: &settingsv1.SetSettingsRequest{
   226  				Setting: wantSetting,
   227  			},
   228  		}
   229  
   230  		_, err := ts.Set(ctx, req)
   231  		require.EqualError(t, err, "already_exists: failed to update key1: newer update already written")
   232  	})
   233  
   234  	t.Run("settings store returns error", func(t *testing.T) {
   235  		store := &fakeStore{}
   236  		wantErr := fmt.Errorf("settings store failed")
   237  
   238  		// Get method fails once.
   239  		store.On("Set", mock.Anything, mock.Anything, mock.Anything).
   240  			Return(nil, wantErr).
   241  			Once()
   242  
   243  		ts := &TenantSettings{
   244  			store:  store,
   245  			logger: log.NewNopLogger(),
   246  		}
   247  
   248  		ctx := tenant.InjectTenantID(context.Background(), "1234")
   249  		req := &connect.Request[settingsv1.SetSettingsRequest]{
   250  			Msg: &settingsv1.SetSettingsRequest{
   251  				Setting: &settingsv1.Setting{
   252  					Name:       "key1",
   253  					Value:      "val1",
   254  					ModifiedAt: 100,
   255  				},
   256  			},
   257  		}
   258  
   259  		_, err := ts.Set(ctx, req)
   260  		require.EqualError(t, err, fmt.Sprintf("internal: %s", wantErr))
   261  	})
   262  }
   263  
   264  func TestTenantSettings_Delete(t *testing.T) {
   265  	t.Run("delete a setting", func(t *testing.T) {
   266  		const tenantID = "1234"
   267  		initialSetting := &settingsv1.Setting{
   268  			Name:       "key1",
   269  			Value:      "val1",
   270  			ModifiedAt: 100,
   271  		}
   272  
   273  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{
   274  			tenantID: {
   275  				initialSetting,
   276  			},
   277  		})
   278  		defer cleanup()
   279  
   280  		ctx := tenant.InjectTenantID(context.Background(), tenantID)
   281  		req := &connect.Request[settingsv1.DeleteSettingsRequest]{
   282  			Msg: &settingsv1.DeleteSettingsRequest{
   283  				Name: initialSetting.Name,
   284  			},
   285  		}
   286  
   287  		got, err := ts.Delete(ctx, req)
   288  		require.NoError(t, err)
   289  
   290  		want := &settingsv1.DeleteSettingsResponse{}
   291  		require.Equal(t, want, got.Msg)
   292  	})
   293  
   294  	t.Run("missing tenant id", func(t *testing.T) {
   295  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{})
   296  		defer cleanup()
   297  
   298  		ctx := context.Background()
   299  		req := &connect.Request[settingsv1.DeleteSettingsRequest]{
   300  			Msg: &settingsv1.DeleteSettingsRequest{
   301  				Name: "key1",
   302  			},
   303  		}
   304  
   305  		_, err := ts.Delete(ctx, req)
   306  		require.EqualError(t, err, "invalid_argument: no org id")
   307  	})
   308  
   309  	t.Run("missing setting name", func(t *testing.T) {
   310  		const tenantID = "1234"
   311  
   312  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{})
   313  		defer cleanup()
   314  
   315  		ctx := tenant.InjectTenantID(context.Background(), tenantID)
   316  		req := &connect.Request[settingsv1.DeleteSettingsRequest]{
   317  			Msg: &settingsv1.DeleteSettingsRequest{
   318  				Name: "", // Purposely empty
   319  			},
   320  		}
   321  
   322  		_, err := ts.Delete(ctx, req)
   323  		require.EqualError(t, err, "invalid_argument: no setting name provided")
   324  	})
   325  
   326  	t.Run("out of order", func(t *testing.T) {
   327  		const tenantID = "1234"
   328  		initialSetting := &settingsv1.Setting{
   329  			Name:  "key1",
   330  			Value: "val1",
   331  
   332  			// Set to some point in the future to make subsequent deletes come
   333  			// in out of order.
   334  			ModifiedAt: time.Now().Add(12 * time.Hour).UnixMilli(),
   335  		}
   336  
   337  		ts, cleanup := newTestTenantSettings(t, map[string][]*settingsv1.Setting{
   338  			tenantID: {
   339  				initialSetting,
   340  			},
   341  		})
   342  		defer cleanup()
   343  
   344  		ctx := tenant.InjectTenantID(context.Background(), tenantID)
   345  		req := &connect.Request[settingsv1.DeleteSettingsRequest]{
   346  			Msg: &settingsv1.DeleteSettingsRequest{
   347  				Name: initialSetting.Name,
   348  			},
   349  		}
   350  
   351  		_, err := ts.Delete(ctx, req)
   352  		require.EqualError(t, err, "already_exists: failed to delete key1: newer update already written")
   353  	})
   354  
   355  	t.Run("settings store returns error", func(t *testing.T) {
   356  		store := &fakeStore{}
   357  		wantErr := fmt.Errorf("settings store failed")
   358  
   359  		// Get method fails once.
   360  		store.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
   361  			Return(wantErr).
   362  			Once()
   363  
   364  		ts := &TenantSettings{
   365  			store:  store,
   366  			logger: log.NewNopLogger(),
   367  		}
   368  
   369  		ctx := tenant.InjectTenantID(context.Background(), "1234")
   370  		req := &connect.Request[settingsv1.DeleteSettingsRequest]{
   371  			Msg: &settingsv1.DeleteSettingsRequest{
   372  				Name: "key1",
   373  			},
   374  		}
   375  
   376  		_, err := ts.Delete(ctx, req)
   377  		require.EqualError(t, err, fmt.Sprintf("internal: %s", wantErr))
   378  	})
   379  }
   380  
   381  func newTestTenantSettings(t *testing.T, initial map[string][]*settingsv1.Setting) (*TenantSettings, func()) {
   382  	t.Helper()
   383  
   384  	store := newMemoryStore()
   385  	var err error
   386  
   387  	for tenant, settings := range initial {
   388  		for _, setting := range settings {
   389  			_, err = store.Set(context.Background(), tenant, setting)
   390  			require.NoError(t, err)
   391  		}
   392  	}
   393  
   394  	ts := &TenantSettings{
   395  		store:  store,
   396  		logger: log.NewNopLogger(),
   397  	}
   398  
   399  	cleanupFn := func() {
   400  		ts.store.Close()
   401  	}
   402  
   403  	return ts, cleanupFn
   404  }
   405  
   406  type fakeStore struct {
   407  	mock.Mock
   408  }
   409  
   410  func (s *fakeStore) Get(ctx context.Context, tenantID string) ([]*settingsv1.Setting, error) {
   411  	args := s.Called(ctx, tenantID)
   412  	if args.Get(0) == nil {
   413  		args[0] = []*settingsv1.Setting{}
   414  	}
   415  
   416  	return args.Get(0).([]*settingsv1.Setting), args.Error(1)
   417  }
   418  
   419  func (s *fakeStore) Set(ctx context.Context, tenantID string, setting *settingsv1.Setting) (*settingsv1.Setting, error) {
   420  	args := s.Called(ctx, tenantID, setting)
   421  	if args.Get(0) == nil {
   422  		args[0] = &settingsv1.Setting{}
   423  	}
   424  
   425  	return args.Get(0).(*settingsv1.Setting), args.Error(1)
   426  }
   427  
   428  func (s *fakeStore) Delete(ctx context.Context, tenantID string, name string, modifiedAtMs int64) error {
   429  	args := s.Called(ctx, tenantID, name, modifiedAtMs)
   430  	return args.Error(0)
   431  }
   432  
   433  func (s *fakeStore) Close() error {
   434  	args := s.Called()
   435  	return args.Error(0)
   436  }