github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/tenantfetchersvc/resync/tenant_manager_test.go (about) 1 package resync_test 2 3 import ( 4 "context" 5 "testing" 6 7 domaintenant "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 8 "github.com/kyma-incubator/compass/components/director/internal/model" 9 "github.com/kyma-incubator/compass/components/director/internal/tenantfetchersvc/resync" 10 "github.com/kyma-incubator/compass/components/director/internal/tenantfetchersvc/resync/automock" 11 "github.com/kyma-incubator/compass/components/director/pkg/graphql" 12 "github.com/kyma-incubator/compass/components/director/pkg/tenant" 13 "github.com/pkg/errors" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/mock" 16 "github.com/stretchr/testify/require" 17 ) 18 19 const ( 20 centralRegion = "central" 21 region = "europe-east" 22 provider = "external-service" 23 ) 24 25 var ( 26 testLicenseType = "LICENSETYPE" 27 ) 28 29 func TestTenantManager_TenantsToCreate(t *testing.T) { 30 const ( 31 centralRegion = "central" 32 unknownRegion = "europe-east" 33 provider = "external-service" 34 timestamp = "1234567899987" 35 ) 36 37 ctx := context.TODO() 38 39 // GIVEN 40 tenantConverter := domaintenant.NewConverter() 41 42 jobConfig := configForTenantType(tenant.Account) 43 44 busTenant1 := fixBusinessTenantMappingInput("1", provider, "subdomain-1", "", "", tenant.Account, &testLicenseType) 45 busTenant2 := fixBusinessTenantMappingInput("2", provider, "subdomain-2", "", "", tenant.Account, nil) 46 47 event1 := fixEvent(t, "GlobalAccount", busTenant1.ExternalTenant, eventFieldsFromTenant(tenant.Account, jobConfig.APIConfig.TenantFieldMapping, busTenant1)) 48 event2 := fixEvent(t, "GlobalAccount", busTenant2.ExternalTenant, eventFieldsFromTenant(tenant.Account, jobConfig.APIConfig.TenantFieldMapping, busTenant2)) 49 50 pageOneQueryParams := resync.QueryParams{ 51 "pageSize": "1", 52 "pageNum": "1", 53 "timestamp": timestamp, 54 } 55 56 testCases := []struct { 57 name string 58 jobConfigFn func() resync.JobConfig 59 region string 60 directorClientFn func() *automock.DirectorGraphQLClient 61 universalClientFn func(resync.JobConfig) *automock.EventAPIClient 62 regionalClientsFn func(resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) 63 expectedTenants []model.BusinessTenantMappingInput 64 expectedErrMsg string 65 }{ 66 { 67 name: "Success when only one page is returned from created tenants events", 68 jobConfigFn: func() resync.JobConfig { 69 return configForTenantType(tenant.Account) 70 }, 71 region: centralRegion, 72 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 73 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 74 client := &automock.EventAPIClient{} 75 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 76 client.On("FetchTenantEventsPage", ctx, resync.UpdatedAccountType, pageOneQueryParams).Return(nil, nil).Once() 77 78 return client 79 }, 80 regionalClientsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 81 return nil, nil 82 }, 83 expectedTenants: []model.BusinessTenantMappingInput{busTenant1}, 84 }, 85 { 86 name: "Success when two pages are returned from created tenants events", 87 jobConfigFn: func() resync.JobConfig { 88 return configForTenantType(tenant.Account) 89 }, 90 region: centralRegion, 91 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 92 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 93 pageTwoQueryParams := resync.QueryParams{ 94 "pageSize": "1", 95 "pageNum": "2", 96 "timestamp": timestamp, 97 } 98 99 client := &automock.EventAPIClient{} 100 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 2, 2, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 101 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, pageTwoQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event2), 2, 2, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 102 client.On("FetchTenantEventsPage", ctx, resync.UpdatedAccountType, pageOneQueryParams).Return(nil, nil).Once() 103 return client 104 }, 105 regionalClientsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 106 return nil, nil 107 }, 108 expectedTenants: []model.BusinessTenantMappingInput{busTenant1, busTenant2}, 109 }, 110 { 111 name: "Success when only one page is returned from updated tenants events", 112 jobConfigFn: func() resync.JobConfig { 113 return configForTenantType(tenant.Account) 114 }, 115 region: centralRegion, 116 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 117 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 118 client := &automock.EventAPIClient{} 119 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, pageOneQueryParams).Return(nil, nil).Once() 120 client.On("FetchTenantEventsPage", ctx, resync.UpdatedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 121 return client 122 }, 123 regionalClientsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 124 return nil, nil 125 }, 126 expectedTenants: []model.BusinessTenantMappingInput{busTenant1}, 127 }, 128 { 129 name: "Success when events for both create and update are returned for the same tenant", 130 jobConfigFn: func() resync.JobConfig { 131 return configForTenantType(tenant.Account) 132 }, 133 region: centralRegion, 134 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 135 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 136 client := &automock.EventAPIClient{} 137 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 138 client.On("FetchTenantEventsPage", ctx, resync.UpdatedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 139 return client 140 }, 141 regionalClientsFn: func(resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 142 return nil, nil 143 }, 144 expectedTenants: []model.BusinessTenantMappingInput{busTenant1}, 145 }, 146 { 147 name: "Success when events for both create and update are returned for different tenants", 148 jobConfigFn: func() resync.JobConfig { 149 return configForTenantType(tenant.Account) 150 }, 151 region: centralRegion, 152 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 153 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 154 client := &automock.EventAPIClient{} 155 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 156 client.On("FetchTenantEventsPage", ctx, resync.UpdatedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event2), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 157 return client 158 }, 159 regionalClientsFn: func(resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 160 return nil, nil 161 }, 162 expectedTenants: []model.BusinessTenantMappingInput{busTenant1, busTenant2}, 163 }, 164 { 165 name: "Success when regional client is available", 166 jobConfigFn: func() resync.JobConfig { 167 cfg := configForTenantType(tenant.Account) 168 cfg.QueryConfig.RegionField = "region" // enables region query parameter 169 reg := cfg.APIConfig 170 cfg.RegionalAPIConfigs[centralRegion] = ® 171 return cfg 172 }, 173 region: centralRegion, 174 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 175 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 176 client := &automock.EventAPIClient{} 177 queryParams := resync.QueryParams{ 178 "pageSize": "1", 179 "pageNum": "1", 180 "timestamp": timestamp, 181 "region": centralRegion, 182 } 183 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, queryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 184 client.On("FetchTenantEventsPage", ctx, resync.UpdatedAccountType, queryParams).Return(nil, nil).Once() 185 return client 186 }, 187 regionalClientsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 188 client := &automock.EventAPIClient{} 189 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, pageOneQueryParams).Return(nil, nil).Once() 190 client.On("FetchTenantEventsPage", ctx, resync.UpdatedAccountType, pageOneQueryParams).Return(nil, nil).Once() 191 192 details := map[string]resync.EventAPIClient{ 193 centralRegion: client, 194 } 195 196 return details, []*automock.EventAPIClient{client} 197 }, 198 expectedTenants: []model.BusinessTenantMappingInput{busTenant1}, 199 }, 200 { 201 name: "Success when regional and universal clients return the same tenant", 202 jobConfigFn: func() resync.JobConfig { 203 cfg := configForTenantType(tenant.Account) 204 cfg.QueryConfig.RegionField = "region" // enables region query parameter 205 reg := cfg.APIConfig 206 cfg.RegionalAPIConfigs[centralRegion] = ® 207 return cfg 208 }, 209 region: centralRegion, 210 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 211 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 212 client := &automock.EventAPIClient{} 213 queryParams := resync.QueryParams{ 214 "pageSize": "1", 215 "pageNum": "1", 216 "timestamp": timestamp, 217 "region": centralRegion, 218 } 219 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, queryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 220 client.On("FetchTenantEventsPage", ctx, resync.UpdatedAccountType, queryParams).Return(nil, nil).Once() 221 return client 222 }, 223 regionalClientsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 224 client := &automock.EventAPIClient{} 225 busTenantWithoutSubdomain := busTenant1 226 busTenantWithoutSubdomain.Subdomain = "" 227 event := fixEvent(t, "GlobalAccount", busTenant1.ExternalTenant, eventFieldsFromTenant(tenant.Account, cfg.RegionalAPIConfigs[centralRegion].TenantFieldMapping, busTenant1)) 228 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 229 client.On("FetchTenantEventsPage", ctx, resync.UpdatedAccountType, pageOneQueryParams).Return(nil, nil).Once() 230 231 details := map[string]resync.EventAPIClient{ 232 centralRegion: client, 233 } 234 235 return details, []*automock.EventAPIClient{client} 236 }, 237 expectedTenants: []model.BusinessTenantMappingInput{busTenant1}, 238 }, 239 { 240 name: "Fail when fetching created tenants events returns an error", 241 jobConfigFn: func() resync.JobConfig { 242 return configForTenantType(tenant.Account) 243 }, 244 region: centralRegion, 245 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 246 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 247 client := &automock.EventAPIClient{} 248 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, pageOneQueryParams).Return(nil, errors.New("failed to get created")).Once() 249 return client 250 }, 251 regionalClientsFn: func(resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 252 return nil, nil 253 }, 254 expectedErrMsg: "while fetching created tenants", 255 }, 256 { 257 name: "Fail when fetching updated tenants events returns an error", 258 jobConfigFn: func() resync.JobConfig { 259 return configForTenantType(tenant.Account) 260 }, 261 region: centralRegion, 262 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 263 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 264 client := &automock.EventAPIClient{} 265 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 266 client.On("FetchTenantEventsPage", ctx, resync.UpdatedAccountType, pageOneQueryParams).Return(nil, errors.New("failed to get updated")).Once() 267 return client 268 }, 269 regionalClientsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 270 return nil, nil 271 }, 272 expectedErrMsg: "while fetching updated tenants", 273 }, 274 { 275 name: "Fail when API client returns an error while fetching tenants", 276 jobConfigFn: func() resync.JobConfig { 277 cfg := configForTenantType(tenant.Account) 278 cfg.QueryConfig.RegionField = "region" // enables region query parameter 279 reg := cfg.APIConfig 280 cfg.RegionalAPIConfigs[centralRegion] = ® 281 return cfg 282 }, 283 region: centralRegion, 284 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 285 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 286 client := &automock.EventAPIClient{} 287 queryParams := resync.QueryParams{ 288 "pageSize": "1", 289 "pageNum": "1", 290 "timestamp": timestamp, 291 "region": centralRegion, 292 } 293 client.On("FetchTenantEventsPage", ctx, resync.CreatedAccountType, queryParams).Return(nil, errors.New("error")).Once() 294 return client 295 }, 296 regionalClientsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 297 return nil, nil 298 }, 299 expectedErrMsg: "while fetching created tenants", 300 }, 301 } 302 303 for _, tc := range testCases { 304 t.Run(tc.name, func(t *testing.T) { 305 cfg := tc.jobConfigFn() 306 307 directorClient := tc.directorClientFn() 308 universalClient := tc.universalClientFn(cfg) 309 regionalDetails, clientMocks := tc.regionalClientsFn(cfg) 310 311 defer func(t *testing.T) { 312 mock.AssertExpectationsForObjects(t, directorClient, universalClient) 313 for _, clientMock := range clientMocks { 314 clientMock.AssertExpectations(t) 315 } 316 }(t) 317 318 manager, err := resync.NewTenantsManager(cfg, directorClient, universalClient, regionalDetails, tenantConverter) 319 require.NoError(t, err) 320 321 res, err := manager.TenantsToCreate(ctx, tc.region, timestamp) 322 if len(tc.expectedErrMsg) > 0 { 323 require.Error(t, err) 324 require.Contains(t, err.Error(), tc.expectedErrMsg) 325 } else { 326 require.NoError(t, err) 327 require.EqualValues(t, tc.expectedTenants, res) 328 } 329 }) 330 } 331 } 332 333 func TestTenantManager_CreateTenants(t *testing.T) { 334 const ( 335 region = "europe-east" 336 provider = "external-service" 337 338 failedToStoreTenantsErrMsg = "failed to store tenants in Director" 339 ) 340 341 ctx := context.TODO() 342 343 // GIVEN 344 tenantConverter := domaintenant.NewConverter() 345 346 busTenant1 := fixBusinessTenantMappingInput("1", provider, "subdomain-1", region, "", tenant.Account, &testLicenseType) 347 busTenant2 := fixBusinessTenantMappingInput("2", provider, "subdomain-2", region, "", tenant.Account, nil) 348 busTenants := []model.BusinessTenantMappingInput{busTenant1, busTenant2} 349 350 testCases := []struct { 351 name string 352 jobConfigFn func() resync.JobConfig 353 directorClientFn func() *automock.DirectorGraphQLClient 354 expectedErrMsg string 355 }{ 356 { 357 name: "Success when tenants are stored in one chunk", 358 jobConfigFn: func() resync.JobConfig { 359 return configForTenantType(tenant.Account) 360 }, 361 directorClientFn: func() *automock.DirectorGraphQLClient { 362 gqlClient := &automock.DirectorGraphQLClient{} 363 gqlClient.On("WriteTenants", mock.Anything, matchArrayWithoutOrderArgument(tenantConverter.MultipleInputToGraphQLInput(busTenants))).Return(nil) 364 return gqlClient 365 }, 366 }, 367 { 368 name: "Success when tenants are stored in more than one chunk", 369 jobConfigFn: func() resync.JobConfig { 370 cfg := configForTenantType(tenant.Account) 371 cfg.TenantOperationChunkSize = 1 372 return cfg 373 }, 374 directorClientFn: func() *automock.DirectorGraphQLClient { 375 gqlClient := &automock.DirectorGraphQLClient{} 376 gqlClient.On("WriteTenants", ctx, tenantConverter.MultipleInputToGraphQLInput([]model.BusinessTenantMappingInput{busTenant1})).Return(nil).Once() 377 gqlClient.On("WriteTenants", ctx, tenantConverter.MultipleInputToGraphQLInput([]model.BusinessTenantMappingInput{busTenant2})).Return(nil).Once() 378 return gqlClient 379 }, 380 }, 381 { 382 name: "Fail when tenant insertion in Director returns an error", 383 jobConfigFn: func() resync.JobConfig { 384 return configForTenantType(tenant.Account) 385 }, 386 directorClientFn: func() *automock.DirectorGraphQLClient { 387 gqlClient := &automock.DirectorGraphQLClient{} 388 gqlClient.On("WriteTenants", mock.Anything, matchArrayWithoutOrderArgument(tenantConverter.MultipleInputToGraphQLInput(busTenants))).Return(errors.New(failedToStoreTenantsErrMsg)) 389 return gqlClient 390 }, 391 expectedErrMsg: failedToStoreTenantsErrMsg, 392 }, 393 } 394 395 for _, tc := range testCases { 396 t.Run(tc.name, func(t *testing.T) { 397 jobCfg := tc.jobConfigFn() 398 directorClient := tc.directorClientFn() 399 universalClient := &automock.EventAPIClient{} 400 401 manager, err := resync.NewTenantsManager(jobCfg, directorClient, universalClient, map[string]resync.EventAPIClient{}, tenantConverter) 402 require.NoError(t, err) 403 404 err = manager.CreateTenants(ctx, busTenants) 405 if len(tc.expectedErrMsg) > 0 { 406 require.Error(t, err) 407 require.Contains(t, err.Error(), tc.expectedErrMsg) 408 } else { 409 require.NoError(t, err) 410 } 411 }) 412 } 413 } 414 415 func TestTenantManager_TenantsToDelete(t *testing.T) { 416 const ( 417 centralRegion = "central" 418 provider = "external-service" 419 timestamp = "1234567899987" 420 ) 421 422 ctx := context.TODO() 423 424 // GIVEN 425 tenantConverter := domaintenant.NewConverter() 426 427 jobConfig := configForTenantType(tenant.Account) 428 429 busTenant1 := fixBusinessTenantMappingInput("1", provider, "subdomain-1", "", "", tenant.Account, &testLicenseType) 430 busTenant2 := fixBusinessTenantMappingInput("2", provider, "subdomain-2", "", "", tenant.Account, nil) 431 432 event1 := fixEvent(t, "GlobalAccount", busTenant1.ExternalTenant, eventFieldsFromTenant(tenant.Account, jobConfig.APIConfig.TenantFieldMapping, busTenant1)) 433 event2 := fixEvent(t, "GlobalAccount", busTenant2.ExternalTenant, eventFieldsFromTenant(tenant.Account, jobConfig.APIConfig.TenantFieldMapping, busTenant2)) 434 435 pageOneQueryParams := resync.QueryParams{ 436 "pageSize": "1", 437 "pageNum": "1", 438 "timestamp": timestamp, 439 } 440 441 testCases := []struct { 442 name string 443 jobConfigFn func() resync.JobConfig 444 region string 445 directorClientFn func() *automock.DirectorGraphQLClient 446 universalClientFn func(resync.JobConfig) *automock.EventAPIClient 447 regionalDetailsFn func(resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) 448 expectedTenants []model.BusinessTenantMappingInput 449 expectedErrMsg string 450 }{ 451 { 452 name: "Success when only one page is returned from deleted tenants events", 453 jobConfigFn: func() resync.JobConfig { 454 return configForTenantType(tenant.Account) 455 }, 456 region: centralRegion, 457 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 458 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 459 client := &automock.EventAPIClient{} 460 client.On("FetchTenantEventsPage", ctx, resync.DeletedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 461 return client 462 }, 463 regionalDetailsFn: func(resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 464 return nil, nil 465 }, 466 expectedTenants: []model.BusinessTenantMappingInput{busTenant1}, 467 }, 468 { 469 name: "Success when two pages are returned from deleted tenants events", 470 jobConfigFn: func() resync.JobConfig { 471 return configForTenantType(tenant.Account) 472 }, 473 region: centralRegion, 474 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 475 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 476 pageTwoQueryParams := resync.QueryParams{ 477 "pageSize": "1", 478 "pageNum": "2", 479 "timestamp": timestamp, 480 } 481 482 client := &automock.EventAPIClient{} 483 client.On("FetchTenantEventsPage", ctx, resync.DeletedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 2, 2, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 484 client.On("FetchTenantEventsPage", ctx, resync.DeletedAccountType, pageTwoQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event2), 2, 2, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 485 486 return client 487 }, 488 regionalDetailsFn: func(resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 489 return nil, nil 490 }, 491 expectedTenants: []model.BusinessTenantMappingInput{busTenant1, busTenant2}, 492 }, 493 { 494 name: "Success when regional client is enabled", 495 jobConfigFn: func() resync.JobConfig { 496 cfg := configForTenantType(tenant.Account) 497 cfg.QueryConfig.RegionField = "region" // enables region query parameter 498 reg := cfg.APIConfig 499 cfg.RegionalAPIConfigs[centralRegion] = ® 500 return cfg 501 }, 502 region: centralRegion, 503 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 504 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 505 client := &automock.EventAPIClient{} 506 queryParams := resync.QueryParams{ 507 "pageSize": "1", 508 "pageNum": "1", 509 "timestamp": timestamp, 510 "region": centralRegion, 511 } 512 client.On("FetchTenantEventsPage", ctx, resync.DeletedAccountType, queryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 513 return client 514 }, 515 regionalDetailsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 516 client := &automock.EventAPIClient{} 517 client.On("FetchTenantEventsPage", ctx, resync.DeletedAccountType, pageOneQueryParams).Return(nil, nil).Once() 518 519 details := map[string]resync.EventAPIClient{ 520 centralRegion: client, 521 } 522 523 return details, []*automock.EventAPIClient{client} 524 }, 525 expectedTenants: []model.BusinessTenantMappingInput{busTenant1}, 526 }, 527 { 528 name: "Success when regional and universal clients return the same tenant", 529 jobConfigFn: func() resync.JobConfig { 530 cfg := configForTenantType(tenant.Account) 531 cfg.QueryConfig.RegionField = "region" // enables region query parameter 532 reg := cfg.APIConfig 533 cfg.RegionalAPIConfigs[centralRegion] = ® 534 return cfg 535 }, 536 region: centralRegion, 537 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 538 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 539 client := &automock.EventAPIClient{} 540 queryParams := resync.QueryParams{ 541 "pageSize": "1", 542 "pageNum": "1", 543 "timestamp": timestamp, 544 "region": centralRegion, 545 } 546 client.On("FetchTenantEventsPage", ctx, resync.DeletedAccountType, queryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event1), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 547 return client 548 }, 549 regionalDetailsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 550 client := &automock.EventAPIClient{} 551 busTenantWithoutSubdomain := busTenant1 552 busTenantWithoutSubdomain.Subdomain = "" 553 event := fixEvent(t, "GlobalAccount", busTenant1.ExternalTenant, eventFieldsFromTenant(tenant.Account, cfg.RegionalAPIConfigs[centralRegion].TenantFieldMapping, busTenant1)) 554 client.On("FetchTenantEventsPage", ctx, resync.DeletedAccountType, pageOneQueryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 555 556 details := map[string]resync.EventAPIClient{ 557 centralRegion: client, 558 } 559 560 return details, []*automock.EventAPIClient{client} 561 }, 562 expectedTenants: []model.BusinessTenantMappingInput{busTenant1}, 563 }, 564 { 565 name: "Fail when fetching deleted tenants events returns an error", 566 jobConfigFn: func() resync.JobConfig { 567 return configForTenantType(tenant.Account) 568 }, 569 region: centralRegion, 570 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 571 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 572 client := &automock.EventAPIClient{} 573 client.On("FetchTenantEventsPage", ctx, resync.DeletedAccountType, pageOneQueryParams).Return(nil, errors.New("failed to get deleted")).Once() 574 return client 575 }, 576 regionalDetailsFn: func(resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 577 return nil, nil 578 }, 579 expectedErrMsg: "while fetching deleted tenants", 580 }, 581 { 582 name: "Fail when regional client returns an error while fetching deleted tenants", 583 jobConfigFn: func() resync.JobConfig { 584 cfg := configForTenantType(tenant.Account) 585 cfg.QueryConfig.RegionField = "region" // enables region query parameter 586 reg := cfg.APIConfig 587 cfg.RegionalAPIConfigs[centralRegion] = ® 588 return cfg 589 }, 590 region: centralRegion, 591 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 592 universalClientFn: func(resync.JobConfig) *automock.EventAPIClient { 593 client := &automock.EventAPIClient{} 594 queryParams := resync.QueryParams{ 595 "pageSize": "1", 596 "pageNum": "1", 597 "timestamp": timestamp, 598 "region": centralRegion, 599 } 600 client.On("FetchTenantEventsPage", ctx, resync.DeletedAccountType, queryParams).Return(nil, nil).Once() 601 return client 602 }, 603 regionalDetailsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 604 client := &automock.EventAPIClient{} 605 client.On("FetchTenantEventsPage", ctx, resync.DeletedAccountType, pageOneQueryParams).Return(nil, errors.New("error")).Once() 606 607 details := map[string]resync.EventAPIClient{ 608 centralRegion: client, 609 } 610 611 return details, []*automock.EventAPIClient{client} 612 }, 613 expectedErrMsg: "while fetching deleted tenants", 614 }, 615 } 616 617 for _, tc := range testCases { 618 t.Run(tc.name, func(t *testing.T) { 619 cfg := tc.jobConfigFn() 620 621 directorClient := tc.directorClientFn() 622 universalClient := tc.universalClientFn(cfg) 623 regionalDetails, clientMocks := tc.regionalDetailsFn(cfg) 624 625 defer func(t *testing.T) { 626 mock.AssertExpectationsForObjects(t, directorClient, universalClient) 627 for _, clientMock := range clientMocks { 628 clientMock.AssertExpectations(t) 629 } 630 }(t) 631 632 manager, err := resync.NewTenantsManager(cfg, directorClient, universalClient, regionalDetails, tenantConverter) 633 require.NoError(t, err) 634 635 res, err := manager.TenantsToDelete(ctx, tc.region, timestamp) 636 if len(tc.expectedErrMsg) > 0 { 637 require.Error(t, err) 638 require.Contains(t, err.Error(), tc.expectedErrMsg) 639 } else { 640 require.NoError(t, err) 641 require.EqualValues(t, tc.expectedTenants, res) 642 } 643 }) 644 } 645 } 646 647 func TestTenantManager_DeleteTenants(t *testing.T) { 648 const failedToDeleteTenantsErrMsg = "failed to delete tenants in Director" 649 650 ctx := context.TODO() 651 652 // GIVEN 653 tenantConverter := domaintenant.NewConverter() 654 655 busTenant1 := fixBusinessTenantMappingInput("1", provider, "subdomain-1", region, "", tenant.Account, &testLicenseType) 656 busTenant2 := fixBusinessTenantMappingInput("2", provider, "subdomain-2", region, "", tenant.Account, nil) 657 busTenants := []model.BusinessTenantMappingInput{busTenant1, busTenant2} 658 659 testCases := []struct { 660 name string 661 jobConfigFn func() resync.JobConfig 662 directorClientFn func() *automock.DirectorGraphQLClient 663 expectedErrMsg string 664 }{ 665 { 666 name: "Success when tenants are deleted in one chunk", 667 jobConfigFn: func() resync.JobConfig { 668 return configForTenantType(tenant.Account) 669 }, 670 directorClientFn: func() *automock.DirectorGraphQLClient { 671 gqlClient := &automock.DirectorGraphQLClient{} 672 gqlClient.On("DeleteTenants", ctx, matchArrayWithoutOrderArgument(tenantConverter.MultipleInputToGraphQLInput(busTenants))).Return(nil) 673 return gqlClient 674 }, 675 }, 676 { 677 name: "Success when tenants are deleted in more than one chunk", 678 jobConfigFn: func() resync.JobConfig { 679 cfg := configForTenantType(tenant.Account) 680 cfg.TenantOperationChunkSize = 1 681 return cfg 682 }, 683 directorClientFn: func() *automock.DirectorGraphQLClient { 684 gqlClient := &automock.DirectorGraphQLClient{} 685 gqlClient.On("DeleteTenants", ctx, tenantConverter.MultipleInputToGraphQLInput([]model.BusinessTenantMappingInput{busTenant1})).Return(nil).Once() 686 gqlClient.On("DeleteTenants", ctx, tenantConverter.MultipleInputToGraphQLInput([]model.BusinessTenantMappingInput{busTenant2})).Return(nil).Once() 687 return gqlClient 688 }, 689 }, 690 { 691 name: "Fail when tenant deletion in Director returns an error", 692 jobConfigFn: func() resync.JobConfig { 693 return configForTenantType(tenant.Account) 694 }, 695 directorClientFn: func() *automock.DirectorGraphQLClient { 696 gqlClient := &automock.DirectorGraphQLClient{} 697 gqlClient.On("DeleteTenants", ctx, matchArrayWithoutOrderArgument(tenantConverter.MultipleInputToGraphQLInput(busTenants))).Return(errors.New(failedToDeleteTenantsErrMsg)) 698 return gqlClient 699 }, 700 expectedErrMsg: failedToDeleteTenantsErrMsg, 701 }, 702 } 703 704 for _, tc := range testCases { 705 t.Run(tc.name, func(t *testing.T) { 706 jobCfg := tc.jobConfigFn() 707 directorClient := tc.directorClientFn() 708 universalClient := &automock.EventAPIClient{} 709 710 manager, err := resync.NewTenantsManager(jobCfg, directorClient, universalClient, map[string]resync.EventAPIClient{}, tenantConverter) 711 require.NoError(t, err) 712 713 err = manager.DeleteTenants(ctx, busTenants) 714 if len(tc.expectedErrMsg) > 0 { 715 require.Error(t, err) 716 require.Contains(t, err.Error(), tc.expectedErrMsg) 717 } else { 718 require.NoError(t, err) 719 } 720 }) 721 } 722 } 723 724 func TestTenantManager_FetchTenant(t *testing.T) { 725 ctx := context.TODO() 726 727 // GIVEN 728 tenantConverter := domaintenant.NewConverter() 729 730 jobConfig := configForTenantType(tenant.Account) 731 732 busTenant := fixBusinessTenantMappingInput("1", provider, "subdomain-1", region, "", tenant.Subaccount, &testLicenseType) 733 event := fixEvent(t, "Subaccount", busTenant.Parent, eventFieldsFromTenant(tenant.Subaccount, jobConfig.APIConfig.TenantFieldMapping, busTenant)) 734 735 queryParams := resync.QueryParams{ 736 "pageSize": "1", 737 "pageNum": "1", 738 "entityId": busTenant.ExternalTenant, 739 } 740 741 testCases := []struct { 742 name string 743 jobConfigFn func() resync.JobConfig 744 region string 745 directorClientFn func() *automock.DirectorGraphQLClient 746 universalClientFn func(resync.JobConfig) *automock.EventAPIClient 747 regionalDetailsFn func(resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) 748 expectedTenant *model.BusinessTenantMappingInput 749 expectedErrMsg string 750 }{ 751 { 752 name: "Success when tenant is found in the central region", 753 jobConfigFn: func() resync.JobConfig { 754 return configWithRegionsForSubaccounts(centralRegion, region) 755 }, 756 region: centralRegion, 757 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 758 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 759 client := &automock.EventAPIClient{} 760 queryParams := resync.QueryParams{ 761 "pageSize": "1", 762 "pageNum": "1", 763 "entityId": busTenant.ExternalTenant, 764 } 765 client.On("FetchTenantEventsPage", ctx, resync.CreatedSubaccountType, queryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 766 client.On("FetchTenantEventsPage", ctx, resync.UpdatedSubaccountType, queryParams).Return(nil, nil).Once() 767 return client 768 }, 769 regionalDetailsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 770 return nil, nil 771 }, 772 expectedTenant: &busTenant, 773 }, 774 { 775 name: "Success when tenant is found from regional client", 776 jobConfigFn: func() resync.JobConfig { 777 return configWithRegionsForSubaccounts(centralRegion, region) 778 }, 779 region: centralRegion, 780 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 781 universalClientFn: func(cfg resync.JobConfig) *automock.EventAPIClient { 782 client := &automock.EventAPIClient{} 783 client.On("FetchTenantEventsPage", mock.Anything, resync.CreatedSubaccountType, queryParams).Return(nil, nil).Once() 784 client.On("FetchTenantEventsPage", mock.Anything, resync.UpdatedSubaccountType, queryParams).Return(nil, nil).Once() 785 return client 786 }, 787 regionalDetailsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 788 client := &automock.EventAPIClient{} 789 client.On("FetchTenantEventsPage", mock.Anything, resync.CreatedSubaccountType, queryParams).Return(fixTenantEventsResponse(eventsToJSONArray(event), 1, 1, cfg.APIConfig.TenantFieldMapping, cfg.APIConfig.MovedSubaccountsFieldMapping, cfg.TenantProvider), nil).Once() 790 client.On("FetchTenantEventsPage", mock.Anything, resync.UpdatedSubaccountType, queryParams).Return(nil, nil).Once() 791 792 details := map[string]resync.EventAPIClient{ 793 centralRegion: client, 794 } 795 796 return details, []*automock.EventAPIClient{client} 797 }, 798 expectedTenant: &busTenant, 799 }, 800 { 801 name: "[Temporary] Success when tenant is not found", 802 jobConfigFn: func() resync.JobConfig { 803 return configWithRegionsForSubaccounts(centralRegion, region) 804 }, 805 region: centralRegion, 806 directorClientFn: func() *automock.DirectorGraphQLClient { return &automock.DirectorGraphQLClient{} }, 807 universalClientFn: func(resync.JobConfig) *automock.EventAPIClient { 808 client := &automock.EventAPIClient{} 809 client.On("FetchTenantEventsPage", mock.Anything, resync.CreatedSubaccountType, queryParams).Return(nil, nil).Once() 810 client.On("FetchTenantEventsPage", mock.Anything, resync.UpdatedSubaccountType, queryParams).Return(nil, nil).Once() 811 return client 812 }, 813 regionalDetailsFn: func(cfg resync.JobConfig) (map[string]resync.EventAPIClient, []*automock.EventAPIClient) { 814 client := &automock.EventAPIClient{} 815 client.On("FetchTenantEventsPage", mock.Anything, resync.CreatedSubaccountType, queryParams).Return(nil, nil).Once() 816 client.On("FetchTenantEventsPage", mock.Anything, resync.UpdatedSubaccountType, queryParams).Return(nil, nil).Once() 817 818 details := map[string]resync.EventAPIClient{ 819 centralRegion: client, 820 } 821 822 return details, []*automock.EventAPIClient{client} 823 }, 824 }, 825 } 826 827 for _, tc := range testCases { 828 t.Run(tc.name, func(t *testing.T) { 829 cfg := tc.jobConfigFn() 830 831 directorClient := tc.directorClientFn() 832 universalClient := tc.universalClientFn(cfg) 833 regionalDetails, clientMocks := tc.regionalDetailsFn(cfg) 834 835 defer func(t *testing.T) { 836 mock.AssertExpectationsForObjects(t, directorClient, universalClient) 837 for _, clientMock := range clientMocks { 838 clientMock.AssertExpectations(t) 839 } 840 }(t) 841 842 manager, err := resync.NewTenantsManager(cfg, directorClient, universalClient, regionalDetails, tenantConverter) 843 require.NoError(t, err) 844 845 actualTenant, err := manager.FetchTenant(ctx, busTenant.ExternalTenant) 846 if len(tc.expectedErrMsg) > 0 { 847 require.Error(t, err) 848 require.Contains(t, err.Error(), tc.expectedErrMsg) 849 } else { 850 require.NoError(t, err) 851 require.Equal(t, tc.expectedTenant, actualTenant) 852 } 853 }) 854 } 855 } 856 857 func configWithRegionsForSubaccounts(regions ...string) resync.JobConfig { 858 cfg := configForTenantType(tenant.Subaccount) 859 regionalCfgs := make(map[string]*resync.EventsAPIConfig) 860 for _, r := range regions { 861 regionalCfgs[r] = &resync.EventsAPIConfig{ 862 RegionName: r, 863 } 864 } 865 cfg.RegionalAPIConfigs = regionalCfgs 866 return cfg 867 } 868 869 func configForTenantType(tenantType tenant.Type) resync.JobConfig { 870 centralRegionCfg := &resync.EventsAPIConfig{ 871 APIEndpointsConfig: resync.APIEndpointsConfig{}, 872 TenantFieldMapping: resync.TenantFieldMapping{ 873 EventsField: "events", 874 NameField: "name", 875 IDField: "id", 876 GlobalAccountGUIDField: "globalAccountGUID", 877 SubaccountIDField: "subaccountId", 878 CustomerIDField: "customerId", 879 SubdomainField: "subdomain", 880 DetailsField: "eventData", 881 EntityIDField: "entityId", 882 EntityTypeField: "type", 883 RegionField: "region", 884 LicenseTypeField: "licenseType", 885 }, 886 MovedSubaccountsFieldMapping: resync.MovedSubaccountsFieldMapping{ 887 SubaccountID: "subaccountId", 888 SourceTenant: "source", 889 TargetTenant: "target", 890 }, 891 OAuthConfig: resync.OAuth2Config{}, 892 ClientTimeout: 0, 893 RegionName: centralRegion, 894 } 895 return resync.JobConfig{ 896 EventsConfig: resync.EventsConfig{ 897 QueryConfig: resync.QueryConfig{ 898 PageNumField: "pageNum", 899 PageSizeField: "pageSize", 900 TimestampField: "timestamp", 901 PageSizeValue: "1", 902 PageStartValue: "1", 903 EntityField: "entityId", 904 }, 905 PagingConfig: resync.PagingConfig{ 906 TotalPagesField: "pages", 907 TotalResultsField: "total", 908 }, 909 APIConfig: *centralRegionCfg, 910 RegionalAPIConfigs: map[string]*resync.EventsAPIConfig{ 911 centralRegion: centralRegionCfg, 912 }, 913 TenantOperationChunkSize: 500, 914 RetryAttempts: 1, 915 PageWorkers: 2, 916 }, 917 ResyncConfig: resync.ResyncConfig{}, 918 KubeConfig: resync.KubeConfig{}, 919 JobName: "tenant-fetcher", 920 TenantProvider: provider, 921 TenantType: tenantType, 922 } 923 } 924 925 func eventFieldsFromTenant(tenantType tenant.Type, tenantFieldMapping resync.TenantFieldMapping, tenantInput model.BusinessTenantMappingInput) map[string]string { 926 fields := map[string]string{ 927 tenantFieldMapping.IDField: tenantInput.ExternalTenant, 928 tenantFieldMapping.NameField: tenantInput.Name, 929 tenantFieldMapping.SubdomainField: tenantInput.Subdomain, 930 tenantFieldMapping.RegionField: tenantInput.Region, 931 } 932 if tenantInput.LicenseType != nil { 933 fields[tenantFieldMapping.LicenseTypeField] = *tenantInput.LicenseType 934 } 935 936 switch tenantType { 937 case tenant.Account: 938 fields[tenantFieldMapping.EntityTypeField] = "GlobalAccount" 939 fields[tenantFieldMapping.GlobalAccountGUIDField] = tenantInput.ExternalTenant 940 fields[tenantFieldMapping.CustomerIDField] = tenantInput.Parent 941 case tenant.Subaccount: 942 fields[tenantFieldMapping.EntityTypeField] = "Subaccount" 943 fields[tenantFieldMapping.SubaccountIDField] = tenantInput.ExternalTenant 944 fields[tenantFieldMapping.GlobalAccountGUIDField] = tenantInput.Parent 945 } 946 return fields 947 } 948 949 func matchArrayWithoutOrderArgument(expected []graphql.BusinessTenantMappingInput) interface{} { 950 return mock.MatchedBy(func(actual []graphql.BusinessTenantMappingInput) bool { 951 if len(expected) != len(actual) { 952 return false 953 } 954 matched := make([]bool, len(actual)) 955 for i := 0; i < len(matched); i++ { 956 matched[i] = false 957 } 958 for i := 0; i < len(expected); i++ { 959 for j := 0; j < len(actual); j++ { 960 if assert.ObjectsAreEqual(expected[i], actual[j]) { 961 matched[j] = true 962 } 963 } 964 } 965 for i := 0; i < len(matched); i++ { 966 if matched[i] { 967 continue 968 } 969 return false 970 } 971 return true 972 }) 973 }