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  }