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 }