github.com/grafana/synthetic-monitoring-agent@v0.64.2/internal/secrets/tenant_test.go (about)

     1  package secrets
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/rs/zerolog"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/grafana/synthetic-monitoring-agent/internal/model"
    14  	sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring"
    15  )
    16  
    17  type mockTenantProvider struct {
    18  	tenant *sm.Tenant
    19  	err    error
    20  }
    21  
    22  func (m *mockTenantProvider) GetTenant(ctx context.Context, info *sm.TenantInfo) (*sm.Tenant, error) {
    23  	return m.tenant, m.err
    24  }
    25  
    26  func TestTenantSecrets_GetSecretCredentials(t *testing.T) {
    27  	logger := zerolog.New(io.Discard)
    28  
    29  	testcases := map[string]struct {
    30  		tenantProvider *mockTenantProvider
    31  		expectedStore  *sm.SecretStore
    32  		expectError    bool
    33  	}{
    34  		"successful retrieval": {
    35  			tenantProvider: &mockTenantProvider{
    36  				tenant: &sm.Tenant{
    37  					Id: 123,
    38  					SecretStore: &sm.SecretStore{
    39  						Url:   "https://secrets.example.com",
    40  						Token: "test-token",
    41  					},
    42  				},
    43  			},
    44  			expectedStore: &sm.SecretStore{
    45  				Url:   "https://secrets.example.com",
    46  				Token: "test-token",
    47  			},
    48  			expectError: false,
    49  		},
    50  		"tenant not found": {
    51  			tenantProvider: &mockTenantProvider{
    52  				err: assert.AnError,
    53  			},
    54  			expectedStore: nil,
    55  			expectError:   true,
    56  		},
    57  		"tenant without secret store": {
    58  			tenantProvider: &mockTenantProvider{
    59  				tenant: &sm.Tenant{
    60  					Id:          123,
    61  					SecretStore: nil,
    62  				},
    63  			},
    64  			expectedStore: nil,
    65  			expectError:   false,
    66  		},
    67  	}
    68  
    69  	for name, tc := range testcases {
    70  		t.Run(name, func(t *testing.T) {
    71  			ts := NewTenantSecrets(tc.tenantProvider, logger)
    72  
    73  			store, err := ts.GetSecretCredentials(context.Background(), model.GlobalID(123))
    74  
    75  			if tc.expectError {
    76  				require.Error(t, err)
    77  			} else {
    78  				require.NoError(t, err)
    79  				require.Equal(t, tc.expectedStore, store)
    80  			}
    81  		})
    82  	}
    83  }
    84  
    85  func TestTenantSecrets_GetSecretValue(t *testing.T) {
    86  	logger := zerolog.New(io.Discard)
    87  	tenantProvider := &mockTenantProvider{
    88  		tenant: &sm.Tenant{
    89  			Id: 123,
    90  			SecretStore: &sm.SecretStore{
    91  				Url:   "https://secrets.example.com",
    92  				Token: "test-token",
    93  			},
    94  		},
    95  	}
    96  
    97  	ts := NewTenantSecrets(tenantProvider, logger)
    98  
    99  	// Test that GetSecretValue returns empty string for backward compatibility
   100  	value, err := ts.GetSecretValue(context.Background(), model.GlobalID(123), "test-secret")
   101  	require.NoError(t, err)
   102  	require.Equal(t, "", value)
   103  }
   104  
   105  func TestSecretProvider_GetSecretCredentials(t *testing.T) {
   106  	logger := zerolog.New(io.Discard)
   107  
   108  	testcases := map[string]struct {
   109  		tenantProvider *mockTenantProvider
   110  		expectedStore  *sm.SecretStore
   111  		expectError    bool
   112  	}{
   113  		"successful retrieval": {
   114  			tenantProvider: &mockTenantProvider{
   115  				tenant: &sm.Tenant{
   116  					Id: 123,
   117  					SecretStore: &sm.SecretStore{
   118  						Url:   "https://secrets.example.com",
   119  						Token: "test-token",
   120  					},
   121  				},
   122  			},
   123  			expectedStore: &sm.SecretStore{
   124  				Url:   "https://secrets.example.com",
   125  				Token: "test-token",
   126  			},
   127  			expectError: false,
   128  		},
   129  		"no secret store configured": {
   130  			tenantProvider: &mockTenantProvider{
   131  				tenant: &sm.Tenant{
   132  					Id:          123,
   133  					SecretStore: nil,
   134  				},
   135  			},
   136  			expectedStore: nil,
   137  			expectError:   false,
   138  		},
   139  	}
   140  
   141  	for name, tc := range testcases {
   142  		t.Run(name, func(t *testing.T) {
   143  			sp := NewSecretProvider(tc.tenantProvider, time.Minute, logger)
   144  
   145  			store, err := sp.GetSecretCredentials(context.Background(), model.GlobalID(123))
   146  
   147  			if tc.expectError {
   148  				require.Error(t, err)
   149  			} else {
   150  				require.NoError(t, err)
   151  				require.Equal(t, tc.expectedStore, store)
   152  			}
   153  		})
   154  	}
   155  }
   156  
   157  func TestSecretProvider_GetSecretValue_NoSecretStore(t *testing.T) {
   158  	logger := zerolog.New(io.Discard)
   159  
   160  	// Mock tenant provider that returns a tenant without secret store
   161  	tenantProvider := &mockTenantProvider{
   162  		tenant: &sm.Tenant{
   163  			Id:          123,
   164  			SecretStore: nil,
   165  		},
   166  	}
   167  
   168  	sp := NewSecretProvider(tenantProvider, time.Minute, logger)
   169  
   170  	_, err := sp.GetSecretValue(context.Background(), model.GlobalID(123), "test-secret")
   171  	require.Error(t, err)
   172  	require.Contains(t, err.Error(), "no secret store configured")
   173  }
   174  
   175  func TestSecretProvider_GetSecretValue_EmptyURLAndToken(t *testing.T) {
   176  	logger := zerolog.New(io.Discard)
   177  
   178  	testCases := map[string]struct {
   179  		url   string
   180  		token string
   181  		error string
   182  	}{
   183  		"empty URL": {
   184  			url:   "",
   185  			token: "test-token",
   186  			error: "GSM URL cannot be empty",
   187  		},
   188  		"empty token": {
   189  			url:   "https://test-gsm.com",
   190  			token: "",
   191  			error: "GSM token cannot be empty",
   192  		},
   193  		"both empty": {
   194  			url:   "",
   195  			token: "",
   196  			error: "GSM URL cannot be empty",
   197  		},
   198  	}
   199  
   200  	for name, tc := range testCases {
   201  		t.Run(name, func(t *testing.T) {
   202  			tenantProvider := &mockTenantProvider{
   203  				tenant: &sm.Tenant{
   204  					Id: 123,
   205  					SecretStore: &sm.SecretStore{
   206  						Url:   tc.url,
   207  						Token: tc.token,
   208  					},
   209  				},
   210  			}
   211  
   212  			sp := NewSecretProvider(tenantProvider, time.Minute, logger)
   213  
   214  			_, err := sp.GetSecretValue(context.Background(), model.GlobalID(123), "test-secret")
   215  			require.Error(t, err)
   216  			require.Contains(t, err.Error(), tc.error)
   217  		})
   218  	}
   219  }
   220  
   221  func TestSecretProvider(t *testing.T) {
   222  	logger := zerolog.New(nil).Level(zerolog.Disabled)
   223  
   224  	t.Run("cache behavior can be observed through API", func(t *testing.T) {
   225  		tenantProvider := &mockTenantProvider{
   226  			tenant: &sm.Tenant{
   227  				Id: 123,
   228  				SecretStore: &sm.SecretStore{
   229  					Url:   "http://test-gsm.com",
   230  					Token: "test-token",
   231  				},
   232  			},
   233  		}
   234  
   235  		sp := NewSecretProvider(tenantProvider, time.Minute, logger)
   236  
   237  		// Test that we can call the basic interface methods
   238  		store, err := sp.GetSecretCredentials(context.Background(), model.GlobalID(123))
   239  		require.NoError(t, err)
   240  		assert.NotNil(t, store)
   241  	})
   242  
   243  	t.Run("cache TTL is respected", func(t *testing.T) {
   244  		ttl := 50 * time.Millisecond
   245  		tenantProvider := &mockTenantProvider{
   246  			tenant: &sm.Tenant{
   247  				Id: 123,
   248  				SecretStore: &sm.SecretStore{
   249  					Url:   "http://test-gsm.com",
   250  					Token: "test-token",
   251  				},
   252  			},
   253  		}
   254  		sp := NewSecretProvider(tenantProvider, ttl, logger)
   255  
   256  		// Test that the provider was created successfully
   257  		assert.NotNil(t, sp)
   258  
   259  		// Test that the provider works correctly
   260  		store, err := sp.GetSecretCredentials(context.Background(), model.GlobalID(123))
   261  		require.NoError(t, err)
   262  		assert.NotNil(t, store)
   263  	})
   264  
   265  	t.Run("GetSecretCredentials works correctly", func(t *testing.T) {
   266  		expectedStore := &sm.SecretStore{
   267  			Url:   "http://test-gsm.com",
   268  			Token: "test-token",
   269  		}
   270  
   271  		tenantProvider := &mockTenantProvider{
   272  			tenant: &sm.Tenant{
   273  				Id:          123,
   274  				SecretStore: expectedStore,
   275  			},
   276  		}
   277  
   278  		sp := NewSecretProvider(tenantProvider, time.Minute, logger)
   279  		tenantID := model.GlobalID(123)
   280  
   281  		// Should delegate to tenant provider
   282  		store, err := sp.GetSecretCredentials(context.Background(), tenantID)
   283  		require.NoError(t, err)
   284  		assert.Equal(t, expectedStore, store)
   285  	})
   286  }