github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/tenantfetchersvc/resync/config_test.go (about) 1 package resync_test 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "strings" 8 "testing" 9 10 "github.com/kyma-incubator/compass/components/director/internal/tenantfetchersvc/resync" 11 "github.com/kyma-incubator/compass/components/director/pkg/oauth" 12 "github.com/kyma-incubator/compass/components/director/pkg/tenant" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 ) 16 17 const ( 18 JobName = "testJob" 19 TenantCreatedEndpoint = "https://tenantsregistry/v1/events/created" 20 TenantUpdatedEndpoint = "https://tenantsregistry/v1/events/updated" 21 TenantMovedEndpoint = "https://tenantsregistry/v1/events/moved" 22 TenantDeletedEndpoint = "https://tenantsregistry/v1/events/deleted" 23 ) 24 25 func TestFetcherJobConfig_ReadEnvVars(t *testing.T) { 26 // GIVEN 27 envValues := map[string]string{} 28 envValues["k1"] = "v1" 29 envValues["k2"] = "v2" 30 envValues["k3"] = "v3" 31 32 t.Run("Read environment variables", func(t *testing.T) { 33 var environ []string 34 for k, v := range envValues { 35 environ = append(environ, k+"="+v) 36 } 37 38 // WHEN 39 envVars := resync.ReadFromEnvironment(environ) 40 41 // THEN 42 for k, v := range envValues { 43 assert.NotNil(t, envVars[k], "Environment variables should contain: "+k) 44 assert.Equal(t, v, envVars[k], fmt.Sprintf("Value of environment variable %s should be %s", k, v)) 45 } 46 }) 47 } 48 49 func TestFetcherJobConfig_GetJobsNames(t *testing.T) { 50 // GIVEN 51 jobNames := []string{"job1", "job2", "job3"} 52 53 testCases := []struct { 54 Name string 55 JobNames []string 56 JobNamePattern string 57 ReadSuccess bool 58 }{ 59 { 60 Name: "Success getting tenant fetcher jobs names", 61 JobNames: jobNames, 62 JobNamePattern: "APP_%s_JOB_NAME", 63 ReadSuccess: true, 64 }, { 65 Name: "Failure getting tenant fetcher jobs names with wrong environment variable format", 66 JobNames: jobNames, 67 JobNamePattern: "APP_WRONG_%s_JOB_NAME", 68 ReadSuccess: false, 69 }, 70 } 71 72 for _, testCase := range testCases { 73 t.Run(testCase.Name, func(t *testing.T) { 74 var environ []string 75 for _, name := range testCase.JobNames { 76 varName := fmt.Sprintf(testCase.JobNamePattern, name) 77 environ = append(environ, varName+"="+name) 78 } 79 80 // WHEN 81 jobNamesFromEnv := resync.GetJobNames(resync.ReadFromEnvironment(environ)) 82 83 // THEN 84 if testCase.ReadSuccess == true { 85 for _, name := range testCase.JobNames { 86 assert.Contains(t, jobNamesFromEnv, name, "Job names should contain: "+name) 87 } 88 } else { 89 for _, name := range testCase.JobNames { 90 assert.NotContains(t, jobNamesFromEnv, name, "Job names not expected to contain: "+name) 91 } 92 } 93 }) 94 } 95 } 96 97 func TestJobConfig_ReadJobConfig(t *testing.T) { 98 t.Run("Successfully reads events configuration from environment", func(t *testing.T) { 99 environ := initEnvWithMandatory(t, nil, JobName, tenant.Subaccount, oauth.Standard) 100 101 // WHEN 102 jobConfig, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 103 104 // THEN 105 require.NoError(t, err) 106 eventsCfg := jobConfig.EventsConfig 107 assert.Equal(t, JobName, jobConfig.JobName, fmt.Sprintf("Job name should be %s", JobName)) 108 assert.Equal(t, TenantCreatedEndpoint, eventsCfg.APIConfig.APIEndpointsConfig.EndpointSubaccountCreated, fmt.Sprintf("Tenant created endpint should be %s", TenantCreatedEndpoint)) 109 }) 110 t.Run("Successfully reads regional events configuration from environment", func(t *testing.T) { 111 envs := mandatoryEnvVars(JobName, tenant.Subaccount, oauth.Standard) 112 region := "EU-1" 113 envs["APP_%s_REGIONAL_CONFIG_EU-1_REGION_NAME"] = strings.ToLower(region) 114 envs["APP_%s_REGIONAL_CONFIG_EU-1_AUTH_CONFIG_SECRET_KEY"] = strings.ToLower(region) 115 envs["APP_%s_REGIONAL_CONFIG_EU-1_AUTH_MODE"] = "standard" 116 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_CREATED"] = TenantCreatedEndpoint 117 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_UPDATED"] = TenantUpdatedEndpoint 118 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_MOVED"] = TenantMovedEndpoint 119 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_DELETED"] = TenantDeletedEndpoint 120 121 environ := initEnv(t, envs, JobName) 122 123 // WHEN 124 jobConfig, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 125 126 // THEN 127 require.NoError(t, err) 128 eventsCfg := jobConfig.EventsConfig 129 assert.Equal(t, JobName, jobConfig.JobName, fmt.Sprintf("Job name should be %s", JobName)) 130 assert.Equal(t, TenantCreatedEndpoint, eventsCfg.APIConfig.APIEndpointsConfig.EndpointSubaccountCreated, fmt.Sprintf("Tenant created endpint should be %s", TenantCreatedEndpoint)) 131 assert.Equal(t, TenantCreatedEndpoint, eventsCfg.RegionalAPIConfigs[strings.ToLower(region)].APIEndpointsConfig.EndpointSubaccountCreated, fmt.Sprintf("Regional tenant created endpint should be %s", TenantCreatedEndpoint)) 132 }) 133 t.Run("Returns an error when mandatory env var is missing", func(t *testing.T) { 134 envs := mandatoryEnvVars(JobName, tenant.Subaccount, oauth.Standard) 135 jobNameKey := "APP_%s_JOB_NAME" 136 delete(envs, jobNameKey) 137 environ := initEnv(t, envs, JobName) 138 139 // WHEN 140 _, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 141 142 // THEN 143 require.Error(t, err) 144 require.Contains(t, err.Error(), "missing value") 145 require.Contains(t, err.Error(), fmt.Sprintf(jobNameKey, strings.ToUpper(JobName))) 146 }) 147 t.Run("Returns an error when mandatory env var is missing from regional config", func(t *testing.T) { 148 envs := mandatoryEnvVars(JobName, tenant.Subaccount, oauth.Standard) 149 region := "EU-1" 150 envs["APP_%s_REGIONAL_CONFIG_EU-1_REGION_NAME"] = strings.ToLower(region) 151 envs["APP_%s_REGIONAL_CONFIG_EU-1_AUTH_CONFIG_SECRET_KEY"] = strings.ToLower(region) 152 153 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_CREATED"] = TenantCreatedEndpoint 154 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_UPDATED"] = TenantUpdatedEndpoint 155 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_MOVED"] = TenantMovedEndpoint 156 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_DELETED"] = TenantDeletedEndpoint 157 158 environ := initEnv(t, envs, JobName) 159 160 // WHEN 161 _, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 162 163 // THEN 164 require.Contains(t, err.Error(), "AUTH_MODE") 165 }) 166 t.Run("Returns an error when mandatory API Config is from regional config", func(t *testing.T) { 167 envs := mandatoryEnvVars(JobName, tenant.Subaccount, oauth.Standard) 168 region := "EU-1" 169 envs["APP_%s_REGIONAL_CONFIG_EU-1_REGION_NAME"] = strings.ToLower(region) 170 envs["APP_%s_REGIONAL_CONFIG_EU-1_AUTH_CONFIG_SECRET_KEY"] = strings.ToLower(region) 171 envs["APP_%s_REGIONAL_CONFIG_EU-1_AUTH_MODE"] = "standard" 172 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_CREATED"] = TenantCreatedEndpoint 173 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_UPDATED"] = TenantUpdatedEndpoint 174 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_MOVED"] = TenantMovedEndpoint 175 176 environ := initEnv(t, envs, JobName) 177 178 // WHEN 179 _, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 180 181 // THEN 182 require.Contains(t, err.Error(), "missing API Client config properties: EndpointSubaccountDeleted") 183 }) 184 t.Run("Returns an error when Auth Config cannot be found for region", func(t *testing.T) { 185 envs := mandatoryEnvVars(JobName, tenant.Subaccount, oauth.Standard) 186 region := "EU-1" 187 envs["APP_%s_REGIONAL_CONFIG_EU-1_REGION_NAME"] = strings.ToLower(region) 188 envs["APP_%s_REGIONAL_CONFIG_EU-1_AUTH_MODE"] = "standard" 189 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_CREATED"] = TenantCreatedEndpoint 190 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_UPDATED"] = TenantUpdatedEndpoint 191 envs["APP_%s_REGIONAL_CONFIG_EU-1_ENDPOINT_SUBACCOUNT_MOVED"] = TenantMovedEndpoint 192 193 secretKey := "missing_key" 194 envs["APP_%s_REGIONAL_CONFIG_EU-1_AUTH_CONFIG_SECRET_KEY"] = secretKey 195 environ := initEnv(t, envs, JobName) 196 197 // WHEN 198 _, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 199 200 // THEN 201 require.Contains(t, err.Error(), fmt.Sprintf("secret file does not contain key %s", secretKey)) 202 }) 203 t.Run("Returns an error when Auth Config cannot be found for central API", func(t *testing.T) { 204 secretKey := "missing_key" 205 envs := mandatoryEnvVars(JobName, tenant.Subaccount, oauth.Standard) 206 envs["APP_%s_API_AUTH_CONFIG_SECRET_KEY"] = secretKey 207 environ := initEnv(t, envs, JobName) 208 209 // WHEN 210 _, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 211 212 // THEN 213 require.Contains(t, err.Error(), fmt.Sprintf("auth config not found for Events API: secret file does not contain key %s", secretKey)) 214 }) 215 t.Run("Returns an error when secrets file cannot be read", func(t *testing.T) { 216 envs := mandatoryEnvVars(JobName, tenant.Subaccount, oauth.Standard) 217 envs["APP_%s_SECRET_FILE_PATH"] = "testdata/notfound.json" 218 environ := initEnv(t, envs, JobName) 219 220 // WHEN 221 _, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 222 223 // THEN 224 require.Error(t, err) 225 require.Contains(t, err.Error(), "unable to read job secret file") 226 }) 227 t.Run("Returns an error when secrets file is not provided", func(t *testing.T) { 228 envs := mandatoryEnvVars(JobName, tenant.Subaccount, oauth.Standard) 229 envs["APP_%s_SECRET_FILE_PATH"] = "" 230 environ := initEnv(t, envs, JobName) 231 232 // WHEN 233 _, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 234 235 // THEN 236 require.Error(t, err) 237 require.Contains(t, err.Error(), "job secret path cannot be empty") 238 }) 239 t.Run("Returns an error when secrets file is not a valid JSON", func(t *testing.T) { 240 envs := mandatoryEnvVars(JobName, tenant.Subaccount, oauth.Standard) 241 envs["APP_%s_SECRET_FILE_PATH"] = "testdata/invalid.json" 242 environ := initEnv(t, envs, JobName) 243 244 // WHEN 245 _, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 246 247 // THEN 248 require.Error(t, err) 249 require.Contains(t, err.Error(), "failed to validate job auth configs") 250 }) 251 252 var apiEndpointsCfgTestCase = []struct { 253 name string 254 tenantType tenant.Type 255 missingProperty string 256 propInErrorMsg string 257 }{ 258 { 259 name: "Fails when created endpoint is not provided for subaccount tenant type", 260 tenantType: tenant.Subaccount, 261 missingProperty: "APP_%s_API_ENDPOINT_SUBACCOUNT_CREATED", 262 propInErrorMsg: "EndpointSubaccountCreated", 263 }, 264 { 265 name: "Fails when updated endpoint is not provided for subaccount tenant type", 266 tenantType: tenant.Subaccount, 267 missingProperty: "APP_%s_API_ENDPOINT_SUBACCOUNT_UPDATED", 268 propInErrorMsg: "EndpointSubaccountUpdated", 269 }, 270 { 271 name: "Fails when deleted endpoint is not provided for subaccount tenant type", 272 tenantType: tenant.Subaccount, 273 missingProperty: "APP_%s_API_ENDPOINT_SUBACCOUNT_DELETED", 274 propInErrorMsg: "EndpointSubaccountDeleted", 275 }, 276 { 277 name: "Fails when created endpoint is not provided for account tenant type", 278 tenantType: tenant.Account, 279 missingProperty: "APP_%s_API_ENDPOINT_TENANT_CREATED", 280 propInErrorMsg: "EndpointTenantCreated", 281 }, 282 { 283 name: "Fails when updated endpoint is not provided for account tenant type", 284 tenantType: tenant.Account, 285 missingProperty: "APP_%s_API_ENDPOINT_TENANT_UPDATED", 286 propInErrorMsg: "EndpointTenantUpdated", 287 }, 288 { 289 name: "Fails when deleted endpoint is not provided for account tenant type", 290 tenantType: tenant.Account, 291 missingProperty: "APP_%s_API_ENDPOINT_TENANT_DELETED", 292 propInErrorMsg: "EndpointTenantDeleted", 293 }, 294 } 295 for _, tc := range apiEndpointsCfgTestCase { 296 t.Run(tc.name, func(t *testing.T) { 297 envs := mandatoryEnvVars(JobName, tc.tenantType, oauth.Standard) 298 envs["APP_%s_TENANT_TYPE"] = string(tc.tenantType) 299 delete(envs, tc.missingProperty) 300 environ := initEnv(t, envs, JobName) 301 302 // WHEN 303 _, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 304 require.Error(t, err) 305 require.Contains(t, err.Error(), fmt.Sprintf("missing API Client config properties: %s", tc.propInErrorMsg)) 306 }) 307 } 308 309 var authCfgTestCase = []struct { 310 name string 311 authMode oauth.AuthMode 312 missingProperty string 313 propInErrorMsg string 314 }{ 315 { 316 name: "Fails when client ID is not provided", 317 authMode: oauth.Standard, 318 missingProperty: "APP_%s_SECRET_CLIENT_ID_PATH", 319 propInErrorMsg: "ClientID", 320 }, 321 { 322 name: "Fails when client secret is not provided", 323 authMode: oauth.Standard, 324 missingProperty: "APP_%s_SECRET_CLIENT_SECRET_PATH", 325 propInErrorMsg: "ClientSecret", 326 }, 327 { 328 name: "Fails when token endpoint is not provided", 329 authMode: oauth.Standard, 330 missingProperty: "APP_%s_SECRET_TOKEN_ENDPOINT_PATH", 331 propInErrorMsg: "OAuthTokenEndpoint", 332 }, 333 { 334 name: "Fails when client certificate is not provided", 335 authMode: oauth.Mtls, 336 missingProperty: "APP_%s_SECRET_CERT_PATH", 337 propInErrorMsg: "Certificate", 338 }, 339 { 340 name: "Fails when client certificate key is not provided", 341 authMode: oauth.Mtls, 342 missingProperty: "APP_%s_SECRET_CERT_KEY_PATH", 343 propInErrorMsg: "CertificateKey", 344 }, 345 } 346 347 for _, tc := range authCfgTestCase { 348 t.Run(tc.name, func(t *testing.T) { 349 envs := mandatoryEnvVars(JobName, tenant.Subaccount, tc.authMode) 350 envs[tc.missingProperty] = "unknown" 351 environ := initEnv(t, envs, JobName) 352 353 // WHEN 354 _, err := resync.NewTenantFetcherJobEnvironment(context.TODO(), JobName, resync.ReadFromEnvironment(environ)).ReadJobConfig() 355 356 // THEN 357 require.Error(t, err) 358 require.Contains(t, err.Error(), fmt.Sprintf(" missing API Client Auth config properties: %s", tc.propInErrorMsg)) 359 }) 360 } 361 } 362 363 func initEnv(t *testing.T, envVars map[string]string, jobName string) []string { 364 os.Clearenv() 365 return setEnvVars(t, envVars, strings.ToUpper(jobName)) 366 } 367 368 func initEnvWithMandatory(t *testing.T, envVars map[string]string, jobName string, tenantType tenant.Type, authMode oauth.AuthMode) []string { 369 os.Clearenv() 370 environ := setEnvVars(t, mandatoryEnvVars(jobName, tenantType, authMode), strings.ToUpper(jobName)) 371 return append(environ, setEnvVars(t, envVars, strings.ToUpper(jobName))...) 372 } 373 374 func mandatoryEnvVars(jobName string, tenantType tenant.Type, authMode oauth.AuthMode) map[string]string { 375 env := map[string]string{ 376 "APP_%s_JOB_NAME": jobName, 377 "APP_%s_TENANT_PROVIDER": "external-provider", 378 "APP_%s_TENANT_TYPE": string(tenantType), 379 "APP_%s_API_REGION_NAME": "eu-1", 380 "APP_%s_API_AUTH_CONFIG_SECRET_KEY": "eu-1", 381 "APP_%s_API_AUTH_MODE": string(authMode), 382 "APP_%s_SECRET_FILE_PATH": "testdata/valid.json", 383 "APP_%s_SECRET_CLIENT_ID_PATH": "clientId", 384 "APP_%s_SECRET_TOKEN_ENDPOINT_PATH": "tokenUrl", 385 "APP_%s_SECRET_TOKEN_PATH": "/oauth/token", 386 } 387 switch tenantType { 388 case tenant.Account: 389 env["APP_%s_API_ENDPOINT_TENANT_CREATED"] = TenantCreatedEndpoint 390 env["APP_%s_API_ENDPOINT_TENANT_UPDATED"] = TenantUpdatedEndpoint 391 env["APP_%s_API_ENDPOINT_TENANT_DELETED"] = TenantDeletedEndpoint 392 case tenant.Subaccount: 393 env["APP_%s_API_ENDPOINT_SUBACCOUNT_CREATED"] = TenantCreatedEndpoint 394 env["APP_%s_API_ENDPOINT_SUBACCOUNT_UPDATED"] = TenantUpdatedEndpoint 395 env["APP_%s_API_ENDPOINT_SUBACCOUNT_MOVED"] = TenantMovedEndpoint 396 env["APP_%s_API_ENDPOINT_SUBACCOUNT_DELETED"] = TenantDeletedEndpoint 397 } 398 switch authMode { 399 case oauth.Standard: 400 env["APP_%s_SECRET_CLIENT_SECRET_PATH"] = "clientSecret" 401 case oauth.Mtls: 402 env["APP_%s_SECRET_CERT_PATH"] = "cert" 403 env["APP_%s_SECRET_CERT_KEY_PATH"] = "key" 404 } 405 return env 406 } 407 408 func setEnvVars(t *testing.T, envVars map[string]string, jobName string) []string { 409 environ := make([]string, 0, len(envVars)) 410 for nameFormat, value := range envVars { 411 varName := fmt.Sprintf(nameFormat, jobName) 412 environ = append(environ, varName+"="+value) 413 err := os.Setenv(varName, value) 414 require.NoError(t, err) 415 } 416 return environ 417 }