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 }