github.com/grafana/synthetic-monitoring-agent@v0.64.2/internal/prober/interpolation/interpolation_test.go (about) 1 package interpolation 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/grafana/synthetic-monitoring-agent/internal/testhelper" 8 "github.com/stretchr/testify/require" 9 ) 10 11 // mockVariableProvider is a mock implementation of VariableProvider for testing 12 type mockVariableProvider struct { 13 variables map[string]string 14 } 15 16 func (m *mockVariableProvider) GetVariable(name string) (string, error) { 17 if value, exists := m.variables[name]; exists { 18 return value, nil 19 } 20 21 return "", fmt.Errorf("variable '%s' not found", name) 22 } 23 24 func TestResolver_Resolve(t *testing.T) { 25 ctx, logger, tenantID := testhelper.CommonTestSetup() 26 27 // Mock providers 28 variableProvider := &mockVariableProvider{ 29 variables: map[string]string{ 30 "username": "admin", 31 "domain": "example.com", 32 "random": "123", 33 "variable-with-secret": "${secrets.api-token}", 34 }, 35 } 36 37 secretProvider := testhelper.NewMockSecretProvider(map[string]string{ 38 "api-token": "secret-token-123", 39 "db-password": "secret-password", 40 "empty-secret": "", 41 "auth-config": "username=${username}&token=${api-token}", 42 "random-password": "my-password-${random}", 43 }) 44 45 testcases := map[string]struct { 46 input string 47 secretEnabled bool 48 expectedOutput string 49 expectError bool 50 }{ 51 "empty string": { 52 input: "", 53 secretEnabled: true, 54 expectedOutput: "", 55 expectError: false, 56 }, 57 "plaintext only": { 58 input: "hello world", 59 secretEnabled: true, 60 expectedOutput: "hello world", 61 expectError: false, 62 }, 63 "secret interpolation only": { 64 input: "${secrets.api-token}", 65 secretEnabled: true, 66 expectedOutput: "secret-token-123", 67 expectError: false, 68 }, 69 "variable interpolation only": { 70 input: "${username}", 71 secretEnabled: true, 72 expectedOutput: "admin", 73 expectError: false, 74 }, 75 "mixed secret and variable": { 76 input: "Bearer ${secrets.api-token} for ${username}@${domain}", 77 secretEnabled: true, 78 expectedOutput: "Bearer secret-token-123 for admin@example.com", 79 expectError: false, 80 }, 81 "multiple secrets": { 82 input: "${secrets.api-token}:${secrets.db-password}", 83 secretEnabled: true, 84 expectedOutput: "secret-token-123:secret-password", 85 expectError: false, 86 }, 87 "secrets disabled": { 88 input: "${secrets.api-token}", 89 secretEnabled: false, 90 expectedOutput: "${secrets.api-token}", 91 expectError: false, 92 }, 93 "variables still work when secrets disabled": { 94 input: "${username}", 95 secretEnabled: false, 96 expectedOutput: "admin", 97 expectError: false, 98 }, 99 "empty secret name": { 100 input: "${secrets.}", 101 secretEnabled: true, 102 expectedOutput: "", 103 expectError: true, 104 }, 105 "invalid secret name": { 106 input: "${secrets.invalid-name}", 107 secretEnabled: true, 108 expectedOutput: "", 109 expectError: true, 110 }, 111 "missing secret": { 112 input: "${secrets.missing-secret}", 113 secretEnabled: true, 114 expectedOutput: "", 115 expectError: true, 116 }, 117 "missing variable": { 118 input: "${missing-variable}", 119 secretEnabled: true, 120 expectedOutput: "${missing-variable}", 121 expectError: false, 122 }, 123 "missing variable when secrets disabled": { 124 input: "${missing-variable}", 125 secretEnabled: false, 126 expectedOutput: "${missing-variable}", 127 expectError: false, 128 }, 129 "variable with no provider": { 130 input: "${some-variable}", 131 secretEnabled: true, 132 expectedOutput: "${some-variable}", 133 expectError: false, 134 }, 135 "secret containing variables": { 136 input: "https://api.example.com/auth?${secrets.auth-config}", 137 secretEnabled: true, 138 expectedOutput: "https://api.example.com/auth?username=${username}&token=${api-token}", 139 expectError: false, 140 }, 141 "secret with variable-like pattern": { 142 input: "Password: ${secrets.random-password}", 143 secretEnabled: true, 144 expectedOutput: "Password: my-password-${random}", 145 expectError: false, 146 }, 147 "variable with secret": { 148 input: "${variable-with-secret}", 149 secretEnabled: true, 150 expectedOutput: "${secrets.api-token}", 151 expectError: false, 152 }, 153 "variables disabled": { 154 input: "Hello ${username} with token ${secrets.api-token}", 155 secretEnabled: true, 156 expectedOutput: "Hello ${username} with token secret-token-123", 157 expectError: false, 158 }, 159 } 160 161 for name, tc := range testcases { 162 t.Run(name, func(t *testing.T) { 163 var resolver *Resolver 164 if name == "variables disabled" { 165 // Create resolver with no variable provider to test variables disabled 166 resolver = NewResolver(nil, secretProvider, tenantID, logger, tc.secretEnabled) 167 } else { 168 resolver = NewResolver(variableProvider, secretProvider, tenantID, logger, tc.secretEnabled) 169 } 170 171 actual, err := resolver.Resolve(ctx, tc.input) 172 173 if tc.expectError { 174 require.Error(t, err) 175 } else { 176 require.NoError(t, err) 177 require.Equal(t, tc.expectedOutput, actual) 178 } 179 }) 180 } 181 182 // Test with no variable provider 183 t.Run("no variable provider", func(t *testing.T) { 184 resolver := NewResolver(nil, secretProvider, tenantID, logger, true) 185 actual, err := resolver.Resolve(ctx, "${some-variable}") 186 require.NoError(t, err) 187 require.Equal(t, "${some-variable}", actual) 188 }) 189 } 190 191 func TestIsValidSecretName(t *testing.T) { 192 testcases := map[string]struct { 193 name string 194 expected bool 195 }{ 196 "valid simple name": { 197 name: "my-secret", 198 expected: true, 199 }, 200 "valid with dots": { 201 name: "my.secret.name", 202 expected: true, 203 }, 204 "valid with numbers": { 205 name: "secret123", 206 expected: true, 207 }, 208 "valid mixed case": { 209 name: "my-secret-name", 210 expected: true, 211 }, 212 "empty name": { 213 name: "", 214 expected: false, 215 }, 216 "too long": { 217 name: "a" + string(make([]byte, 253)), 218 expected: false, 219 }, 220 "starts with dash": { 221 name: "-invalid", 222 expected: false, 223 }, 224 "ends with dash": { 225 name: "invalid-", 226 expected: false, 227 }, 228 "contains uppercase": { 229 name: "Invalid-Name", 230 expected: false, 231 }, 232 "contains special chars": { 233 name: "invalid@name", 234 expected: false, 235 }, 236 "starts with dot": { 237 name: ".invalid", 238 expected: false, 239 }, 240 "ends with dot": { 241 name: "invalid.", 242 expected: false, 243 }, 244 } 245 246 for name, tc := range testcases { 247 t.Run(name, func(t *testing.T) { 248 actual := isValidSecretName(tc.name) 249 require.Equal(t, tc.expected, actual) 250 }) 251 } 252 } 253 254 func TestToJavaScript(t *testing.T) { 255 testcases := map[string]struct { 256 input string 257 expected string 258 }{ 259 "empty string": { 260 input: "", 261 expected: "''", 262 }, 263 "plaintext only": { 264 input: "hello world", 265 expected: "'hello world'", 266 }, 267 "variable only": { 268 input: "${username}", 269 expected: "vars['username']", 270 }, 271 "mixed text and variable": { 272 input: "Hello ${username}!", 273 expected: "'Hello '+vars['username']+'!'", 274 }, 275 "multiple variables": { 276 input: "${username}@${domain}", 277 expected: "vars['username']+'@'+vars['domain']", 278 }, 279 "complex mixed": { 280 input: "Bearer ${token} for ${username}@${domain}", 281 expected: "'Bearer '+vars['token']+' for '+vars['username']+'@'+vars['domain']", 282 }, 283 "with quotes": { 284 input: "Hello \"${username}\"", 285 expected: "'Hello \\\"'+vars['username']+'\\\"'", 286 }, 287 "with backslashes": { 288 input: "Path: \\${username}\\", 289 expected: "'Path: \\\\'+vars['username']+'\\\\'", 290 }, 291 } 292 293 for name, tc := range testcases { 294 t.Run(name, func(t *testing.T) { 295 actual := ToJavaScript(tc.input) 296 require.Equal(t, tc.expected, actual) 297 }) 298 } 299 }