github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/tenantfetchersvc/resync/synchronizer_test.go (about) 1 package resync_test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strconv" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/kyma-incubator/compass/components/director/internal/model" 13 "github.com/kyma-incubator/compass/components/director/internal/tenantfetchersvc/resync" 14 "github.com/kyma-incubator/compass/components/director/internal/tenantfetchersvc/resync/automock" 15 persistenceautomock "github.com/kyma-incubator/compass/components/director/pkg/persistence/automock" 16 "github.com/kyma-incubator/compass/components/director/pkg/persistence/txtest" 17 "github.com/kyma-incubator/compass/components/director/pkg/tenant" 18 "github.com/stretchr/testify/mock" 19 "github.com/stretchr/testify/require" 20 ) 21 22 func TestTenantsSynchronizer_Synchronize(t *testing.T) { 23 ctx := context.TODO() 24 ctxWithRegion := context.WithValue(ctx, resync.TenantRegionCtxKey, "central") 25 testErr := errors.New("test error") 26 txGen := txtest.NewTransactionContextGenerator(testErr) 27 28 const ( 29 region = "central" 30 provider = "test-provider" 31 newTenantID = "da363eb6-9444-4452-9bf6-40ee7e8da4d8" 32 movedTenantID = "5ca5aa4c-6498-45d0-87c6-d150b2cef1d2" 33 deletedTenantID = "f2f99619-ab1c-4875-b4a6-15e5871fec39" 34 35 parentTenantID = "20e67c37-d1a1-418d-a61a-37b485a2f163" 36 internalParentTenantID = "52f74825-83b5-46f4-884e-3ce2d589061f" 37 38 failedToFetchNewTenantsErrMsg = "failed to fetch new tenants" 39 failedToFetchMovedTenantsErrMsg = "failed to fetch moved tenants" 40 failedToFetchDeletedTenantsErrMsg = "failed to fetch deleted tenants" 41 failedToCreateTenantsErrMsg = "failed to create tenants" 42 failedToMoveTenantsErrMsg = "failed to move tenants" 43 failedToDeleteTenantsErrMsg = "failed to delete tenants" 44 failedToGetExistingTenantsErrMsg = "failed to get existing tenants" 45 ) 46 47 lastConsumedTenantTimestamp := strconv.FormatInt(time.Now().Add(time.Duration(-10)*time.Minute).UnixNano()/int64(time.Millisecond), 10) // 10 minutes ago 48 lastResyncTimestamp := strconv.FormatInt(time.Now().Add(time.Duration(-5)*time.Minute).UnixNano()/int64(time.Millisecond), 10) // 5 minutes ago 49 50 jobCfg := resync.JobConfig{ 51 TenantProvider: provider, 52 EventsConfig: resync.EventsConfig{ 53 RegionalAPIConfigs: map[string]*resync.EventsAPIConfig{region: {RegionName: region}}, 54 }, 55 ResyncConfig: resync.ResyncConfig{FullResyncInterval: time.Hour}, 56 } 57 58 newAccountTenant := model.BusinessTenantMappingInput{ExternalTenant: newTenantID, Region: region} 59 movedAccountTenant := model.MovedSubaccountMappingInput{SubaccountID: movedTenantID} 60 deletedAccountTenant := model.BusinessTenantMappingInput{ExternalTenant: deletedTenantID} 61 emptyTenantsResult := make([]model.BusinessTenantMappingInput, 0) 62 63 kubeClientFn := func() *automock.KubeClient { 64 client := &automock.KubeClient{} 65 client.On("GetTenantFetcherConfigMapData", ctx).Return(lastConsumedTenantTimestamp, lastResyncTimestamp, nil) 66 client.On("UpdateTenantFetcherConfigMapData", ctxWithRegion, mock.Anything, mock.Anything).Return(nil) 67 return client 68 } 69 70 kubeClientWithResyncTimestampFn := func() *automock.KubeClient { 71 client := &automock.KubeClient{} 72 client.On("GetTenantFetcherConfigMapData", ctx).Return(lastConsumedTenantTimestamp, lastResyncTimestamp, nil) 73 return client 74 } 75 76 noOpMoverFn := func() *automock.TenantMover { 77 svc := &automock.TenantMover{} 78 svc.On("TenantsToMove", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.MovedSubaccountMappingInput{}, nil) 79 return svc 80 } 81 82 testCases := []struct { 83 Name string 84 JobCfg resync.JobConfig 85 TransactionerFn func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner) 86 TenantStorageSvcFn func() *automock.TenantStorageService 87 TenantCreatorFn func() *automock.TenantCreator 88 TenantMoverFn func() *automock.TenantMover 89 TenantDeleterFn func() *automock.TenantDeleter 90 KubeClientFn func() *automock.KubeClient 91 ExpectedErrMsg string 92 }{ 93 { 94 Name: "Success when create, move and delete events are present for different tenants", 95 JobCfg: jobCfg, 96 TransactionerFn: txGen.ThatSucceeds, 97 TenantStorageSvcFn: func() *automock.TenantStorageService { 98 svc := &automock.TenantStorageService{} 99 deleted := deletedAccountTenant.ToBusinessTenantMapping("123") 100 svc.On("ListsByExternalIDs", txtest.CtxWithDBMatcher(), []string{newTenantID, deletedTenantID}).Return([]*model.BusinessTenantMapping{deleted}, nil) 101 return svc 102 }, 103 TenantCreatorFn: func() *automock.TenantCreator { 104 svc := &automock.TenantCreator{} 105 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.BusinessTenantMappingInput{newAccountTenant}, nil) 106 svc.On("CreateTenants", ctxWithRegion, []model.BusinessTenantMappingInput{newAccountTenant}).Return(nil) 107 return svc 108 }, 109 TenantMoverFn: func() *automock.TenantMover { 110 svc := &automock.TenantMover{} 111 svc.On("TenantsToMove", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.MovedSubaccountMappingInput{movedAccountTenant}, nil) 112 svc.On("MoveTenants", ctxWithRegion, []model.MovedSubaccountMappingInput{movedAccountTenant}).Return(nil) 113 return svc 114 }, 115 TenantDeleterFn: func() *automock.TenantDeleter { 116 svc := &automock.TenantDeleter{} 117 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.BusinessTenantMappingInput{deletedAccountTenant}, nil) 118 svc.On("DeleteTenants", ctxWithRegion, []model.BusinessTenantMappingInput{deletedAccountTenant}).Return(nil) 119 return svc 120 }, 121 KubeClientFn: kubeClientFn, 122 }, 123 { 124 Name: "Success when no new events are present", 125 JobCfg: jobCfg, 126 TransactionerFn: txGen.ThatDoesntStartTransaction, 127 TenantStorageSvcFn: func() *automock.TenantStorageService { return &automock.TenantStorageService{} }, 128 TenantCreatorFn: func() *automock.TenantCreator { 129 svc := &automock.TenantCreator{} 130 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 131 return svc 132 }, 133 TenantMoverFn: func() *automock.TenantMover { 134 svc := &automock.TenantMover{} 135 svc.On("TenantsToMove", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.MovedSubaccountMappingInput{}, nil) 136 return svc 137 }, 138 TenantDeleterFn: func() *automock.TenantDeleter { 139 svc := &automock.TenantDeleter{} 140 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 141 return svc 142 }, 143 KubeClientFn: kubeClientFn, 144 }, 145 { 146 Name: "Tenant is not created when both create and delete events are present for the same unknown tenant", 147 JobCfg: jobCfg, 148 TransactionerFn: txGen.ThatSucceeds, 149 TenantStorageSvcFn: func() *automock.TenantStorageService { 150 svc := &automock.TenantStorageService{} 151 svc.On("ListsByExternalIDs", txtest.CtxWithDBMatcher(), []string{deletedTenantID}).Return([]*model.BusinessTenantMapping{}, nil) 152 return svc 153 }, 154 TenantCreatorFn: func() *automock.TenantCreator { 155 svc := &automock.TenantCreator{} 156 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.BusinessTenantMappingInput{deletedAccountTenant}, nil) 157 return svc 158 }, 159 TenantMoverFn: noOpMoverFn, 160 TenantDeleterFn: func() *automock.TenantDeleter { 161 svc := &automock.TenantDeleter{} 162 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.BusinessTenantMappingInput{deletedAccountTenant}, nil) 163 return svc 164 }, 165 KubeClientFn: kubeClientFn, 166 }, 167 { 168 Name: "Parent tenant is also created when tenant from create event has unknown parent", 169 JobCfg: jobCfg, 170 TransactionerFn: txGen.ThatSucceeds, 171 TenantStorageSvcFn: func() *automock.TenantStorageService { 172 svc := &automock.TenantStorageService{} 173 svc.On("ListsByExternalIDs", txtest.CtxWithDBMatcher(), []string{parentTenantID, newTenantID}).Return([]*model.BusinessTenantMapping{}, nil) 174 return svc 175 }, 176 TenantCreatorFn: func() *automock.TenantCreator { 177 svc := &automock.TenantCreator{} 178 179 tenantWithParent := model.BusinessTenantMappingInput{ 180 ExternalTenant: newTenantID, 181 Parent: parentTenantID, 182 Type: string(tenant.Subaccount), 183 Region: region, 184 } 185 expectedParentTenant := model.BusinessTenantMappingInput{ 186 Name: parentTenantID, 187 ExternalTenant: parentTenantID, 188 Region: region, 189 Type: string(tenant.Account), 190 Provider: provider, 191 } 192 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.BusinessTenantMappingInput{tenantWithParent}, nil) 193 svc.On("CreateTenants", ctxWithRegion, []model.BusinessTenantMappingInput{expectedParentTenant, tenantWithParent}).Return(nil) 194 return svc 195 }, 196 TenantMoverFn: noOpMoverFn, 197 TenantDeleterFn: func() *automock.TenantDeleter { 198 svc := &automock.TenantDeleter{} 199 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 200 return svc 201 }, 202 KubeClientFn: kubeClientFn, 203 }, 204 { 205 Name: "Child tenant is correctly associated with internal ID of parent when parent is pre-existing", 206 JobCfg: jobCfg, 207 TransactionerFn: txGen.ThatSucceeds, 208 TenantStorageSvcFn: func() *automock.TenantStorageService { 209 svc := &automock.TenantStorageService{} 210 parentTenant := &model.BusinessTenantMapping{ 211 ID: internalParentTenantID, 212 ExternalTenant: parentTenantID, 213 } 214 svc.On("ListsByExternalIDs", txtest.CtxWithDBMatcher(), []string{parentTenantID, newTenantID}).Return([]*model.BusinessTenantMapping{parentTenant}, nil) 215 return svc 216 }, 217 TenantCreatorFn: func() *automock.TenantCreator { 218 svc := &automock.TenantCreator{} 219 220 tenantWithParent := model.BusinessTenantMappingInput{ 221 ExternalTenant: newTenantID, 222 Parent: parentTenantID, 223 Type: string(tenant.Subaccount), 224 Region: region, 225 } 226 227 expectedTenantWithParent := tenantWithParent 228 expectedTenantWithParent.Parent = internalParentTenantID 229 230 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.BusinessTenantMappingInput{tenantWithParent}, nil) 231 svc.On("CreateTenants", ctxWithRegion, []model.BusinessTenantMappingInput{expectedTenantWithParent}).Return(nil) 232 return svc 233 }, 234 TenantMoverFn: noOpMoverFn, 235 TenantDeleterFn: func() *automock.TenantDeleter { 236 svc := &automock.TenantDeleter{} 237 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 238 return svc 239 }, 240 KubeClientFn: kubeClientFn, 241 }, 242 { 243 Name: "Fails when fetching created tenants returns an error", 244 JobCfg: jobCfg, 245 TransactionerFn: txGen.ThatDoesntStartTransaction, 246 TenantStorageSvcFn: func() *automock.TenantStorageService { return &automock.TenantStorageService{} }, 247 TenantCreatorFn: func() *automock.TenantCreator { 248 svc := &automock.TenantCreator{} 249 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(nil, errors.New(failedToFetchNewTenantsErrMsg)) 250 return svc 251 }, 252 TenantMoverFn: func() *automock.TenantMover { return &automock.TenantMover{} }, 253 TenantDeleterFn: func() *automock.TenantDeleter { return &automock.TenantDeleter{} }, 254 KubeClientFn: kubeClientWithResyncTimestampFn, 255 ExpectedErrMsg: failedToFetchNewTenantsErrMsg, 256 }, 257 { 258 Name: "Fails when fetching moved tenants returns an error", 259 JobCfg: jobCfg, 260 TransactionerFn: txGen.ThatDoesntStartTransaction, 261 TenantStorageSvcFn: func() *automock.TenantStorageService { return &automock.TenantStorageService{} }, 262 TenantCreatorFn: func() *automock.TenantCreator { 263 svc := &automock.TenantCreator{} 264 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 265 return svc 266 }, 267 TenantMoverFn: func() *automock.TenantMover { 268 svc := &automock.TenantMover{} 269 svc.On("TenantsToMove", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(nil, errors.New(failedToFetchMovedTenantsErrMsg)) 270 return svc 271 }, 272 TenantDeleterFn: func() *automock.TenantDeleter { return &automock.TenantDeleter{} }, 273 KubeClientFn: kubeClientWithResyncTimestampFn, 274 ExpectedErrMsg: failedToFetchMovedTenantsErrMsg, 275 }, 276 { 277 Name: "Fails when fetching deleted tenants returns an error", 278 JobCfg: jobCfg, 279 TransactionerFn: txGen.ThatDoesntStartTransaction, 280 TenantStorageSvcFn: func() *automock.TenantStorageService { return &automock.TenantStorageService{} }, 281 TenantCreatorFn: func() *automock.TenantCreator { 282 svc := &automock.TenantCreator{} 283 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 284 return svc 285 }, 286 TenantMoverFn: noOpMoverFn, 287 TenantDeleterFn: func() *automock.TenantDeleter { 288 svc := &automock.TenantDeleter{} 289 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(nil, errors.New(failedToFetchDeletedTenantsErrMsg)) 290 return svc 291 }, 292 KubeClientFn: kubeClientWithResyncTimestampFn, 293 ExpectedErrMsg: failedToFetchDeletedTenantsErrMsg, 294 }, 295 { 296 Name: "Fails when creating new tenants returns an error", 297 JobCfg: jobCfg, 298 TransactionerFn: txGen.ThatSucceeds, 299 TenantStorageSvcFn: func() *automock.TenantStorageService { 300 svc := &automock.TenantStorageService{} 301 svc.On("ListsByExternalIDs", txtest.CtxWithDBMatcher(), []string{newTenantID}).Return(nil, nil) 302 return svc 303 }, 304 TenantCreatorFn: func() *automock.TenantCreator { 305 svc := &automock.TenantCreator{} 306 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.BusinessTenantMappingInput{newAccountTenant}, nil) 307 svc.On("CreateTenants", ctxWithRegion, []model.BusinessTenantMappingInput{newAccountTenant}).Return(errors.New(failedToCreateTenantsErrMsg)) 308 return svc 309 }, 310 TenantMoverFn: noOpMoverFn, 311 TenantDeleterFn: func() *automock.TenantDeleter { 312 svc := &automock.TenantDeleter{} 313 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 314 return svc 315 }, 316 KubeClientFn: kubeClientWithResyncTimestampFn, 317 ExpectedErrMsg: failedToCreateTenantsErrMsg, 318 }, 319 { 320 Name: "Fails when moving tenants returns an error", 321 JobCfg: jobCfg, 322 TransactionerFn: txGen.ThatDoesntStartTransaction, 323 TenantStorageSvcFn: func() *automock.TenantStorageService { return &automock.TenantStorageService{} }, 324 TenantCreatorFn: func() *automock.TenantCreator { 325 svc := &automock.TenantCreator{} 326 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 327 return svc 328 }, 329 TenantMoverFn: func() *automock.TenantMover { 330 svc := &automock.TenantMover{} 331 svc.On("TenantsToMove", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.MovedSubaccountMappingInput{movedAccountTenant}, nil) 332 svc.On("MoveTenants", ctxWithRegion, []model.MovedSubaccountMappingInput{movedAccountTenant}).Return(errors.New(failedToMoveTenantsErrMsg)) 333 return svc 334 }, 335 TenantDeleterFn: func() *automock.TenantDeleter { 336 svc := &automock.TenantDeleter{} 337 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 338 return svc 339 }, 340 KubeClientFn: kubeClientWithResyncTimestampFn, 341 ExpectedErrMsg: failedToMoveTenantsErrMsg, 342 }, 343 { 344 Name: "Fails when deleting tenants returns an error", 345 JobCfg: jobCfg, 346 TransactionerFn: txGen.ThatSucceeds, 347 TenantStorageSvcFn: func() *automock.TenantStorageService { 348 svc := &automock.TenantStorageService{} 349 svc.On("ListsByExternalIDs", txtest.CtxWithDBMatcher(), []string{deletedTenantID}).Return([]*model.BusinessTenantMapping{deletedAccountTenant.ToBusinessTenantMapping(deletedTenantID)}, nil) 350 return svc 351 }, 352 TenantCreatorFn: func() *automock.TenantCreator { 353 svc := &automock.TenantCreator{} 354 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 355 return svc 356 }, 357 TenantMoverFn: func() *automock.TenantMover { 358 svc := &automock.TenantMover{} 359 svc.On("TenantsToMove", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.MovedSubaccountMappingInput{}, nil) 360 return svc 361 }, 362 TenantDeleterFn: func() *automock.TenantDeleter { 363 svc := &automock.TenantDeleter{} 364 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.BusinessTenantMappingInput{deletedAccountTenant}, nil) 365 svc.On("DeleteTenants", ctxWithRegion, []model.BusinessTenantMappingInput{deletedAccountTenant}).Return(errors.New(failedToDeleteTenantsErrMsg)) 366 return svc 367 }, 368 KubeClientFn: kubeClientWithResyncTimestampFn, 369 ExpectedErrMsg: failedToDeleteTenantsErrMsg, 370 }, 371 { 372 Name: "Fails when fetching existing tenants returns an error", 373 JobCfg: jobCfg, 374 TransactionerFn: txGen.ThatDoesntExpectCommit, 375 TenantStorageSvcFn: func() *automock.TenantStorageService { 376 svc := &automock.TenantStorageService{} 377 svc.On("ListsByExternalIDs", txtest.CtxWithDBMatcher(), []string{newTenantID}).Return(nil, errors.New(failedToGetExistingTenantsErrMsg)) 378 return svc 379 }, 380 TenantCreatorFn: func() *automock.TenantCreator { 381 svc := &automock.TenantCreator{} 382 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.BusinessTenantMappingInput{newAccountTenant}, nil) 383 return svc 384 }, 385 TenantMoverFn: func() *automock.TenantMover { 386 svc := &automock.TenantMover{} 387 svc.On("TenantsToMove", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.MovedSubaccountMappingInput{}, nil) 388 return svc 389 }, 390 TenantDeleterFn: func() *automock.TenantDeleter { 391 svc := &automock.TenantDeleter{} 392 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 393 return svc 394 }, 395 KubeClientFn: kubeClientWithResyncTimestampFn, 396 ExpectedErrMsg: failedToGetExistingTenantsErrMsg, 397 }, 398 { 399 Name: "Fails when fetching existing tenants returns an error caused by failed transaction start", 400 JobCfg: jobCfg, 401 TransactionerFn: txGen.ThatFailsOnBegin, 402 TenantStorageSvcFn: func() *automock.TenantStorageService { return &automock.TenantStorageService{} }, 403 TenantCreatorFn: func() *automock.TenantCreator { 404 svc := &automock.TenantCreator{} 405 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.BusinessTenantMappingInput{newAccountTenant}, nil) 406 return svc 407 }, 408 TenantMoverFn: func() *automock.TenantMover { 409 svc := &automock.TenantMover{} 410 svc.On("TenantsToMove", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.MovedSubaccountMappingInput{}, nil) 411 return svc 412 }, 413 TenantDeleterFn: func() *automock.TenantDeleter { 414 svc := &automock.TenantDeleter{} 415 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 416 return svc 417 }, 418 KubeClientFn: kubeClientWithResyncTimestampFn, 419 ExpectedErrMsg: testErr.Error(), 420 }, 421 { 422 Name: "Fails when fetching existing tenants returns an error caused by failed transaction commit", 423 JobCfg: jobCfg, 424 TransactionerFn: txGen.ThatFailsOnCommit, 425 TenantStorageSvcFn: func() *automock.TenantStorageService { 426 svc := &automock.TenantStorageService{} 427 svc.On("ListsByExternalIDs", txtest.CtxWithDBMatcher(), []string{newTenantID}).Return(nil, nil) 428 return svc 429 }, 430 TenantCreatorFn: func() *automock.TenantCreator { 431 svc := &automock.TenantCreator{} 432 svc.On("TenantsToCreate", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.BusinessTenantMappingInput{newAccountTenant}, nil) 433 return svc 434 }, 435 TenantMoverFn: func() *automock.TenantMover { 436 svc := &automock.TenantMover{} 437 svc.On("TenantsToMove", ctxWithRegion, region, lastConsumedTenantTimestamp).Return([]model.MovedSubaccountMappingInput{}, nil) 438 return svc 439 }, 440 TenantDeleterFn: func() *automock.TenantDeleter { 441 svc := &automock.TenantDeleter{} 442 svc.On("TenantsToDelete", ctxWithRegion, region, lastConsumedTenantTimestamp).Return(emptyTenantsResult, nil) 443 return svc 444 }, 445 KubeClientFn: kubeClientWithResyncTimestampFn, 446 ExpectedErrMsg: testErr.Error(), 447 }, 448 { 449 Name: "Fails when getting resync info returns an error", 450 JobCfg: jobCfg, 451 TransactionerFn: txGen.ThatDoesntStartTransaction, 452 TenantStorageSvcFn: func() *automock.TenantStorageService { return &automock.TenantStorageService{} }, 453 TenantCreatorFn: func() *automock.TenantCreator { return &automock.TenantCreator{} }, 454 TenantMoverFn: func() *automock.TenantMover { return &automock.TenantMover{} }, 455 TenantDeleterFn: func() *automock.TenantDeleter { return &automock.TenantDeleter{} }, 456 KubeClientFn: func() *automock.KubeClient { 457 client := &automock.KubeClient{} 458 client.On("GetTenantFetcherConfigMapData", ctx).Return("", "", testErr) 459 return client 460 }, 461 ExpectedErrMsg: testErr.Error(), 462 }, 463 } 464 for _, testCase := range testCases { 465 t.Run(testCase.Name, func(t *testing.T) { 466 persist, transact := testCase.TransactionerFn() 467 tenantStorageSvc := testCase.TenantStorageSvcFn() 468 tenantCreator := testCase.TenantCreatorFn() 469 tenantMover := testCase.TenantMoverFn() 470 tenantDeleter := testCase.TenantDeleterFn() 471 kubeClient := testCase.KubeClientFn() 472 473 metricsPusher := &automock.AggregationFailurePusher{} 474 if len(testCase.ExpectedErrMsg) > 0 { 475 metricsPusher.On("ReportAggregationFailure", ctx, mock.MatchedBy(func(actual error) bool { 476 return strings.Contains(actual.Error(), testCase.ExpectedErrMsg) 477 })) 478 } 479 480 defer mock.AssertExpectationsForObjects(t, persist, transact, tenantStorageSvc, tenantCreator, tenantMover, 481 tenantDeleter, kubeClient, metricsPusher) 482 483 synchronizer := resync.NewTenantSynchronizer(testCase.JobCfg, transact, tenantStorageSvc, tenantCreator, tenantMover, tenantDeleter, kubeClient, metricsPusher) 484 err := synchronizer.Synchronize(context.TODO()) 485 if len(testCase.ExpectedErrMsg) > 0 { 486 require.Error(t, err) 487 require.Contains(t, err.Error(), testCase.ExpectedErrMsg) 488 } else { 489 require.NoError(t, err, "unexpected error while running tenant resync") 490 } 491 }) 492 } 493 } 494 495 func TestTenantsSynchronizer_SynchronizeTenant(t *testing.T) { 496 ctx := context.TODO() 497 testErr := errors.New("test error") 498 txGen := txtest.NewTransactionContextGenerator(testErr) 499 500 const ( 501 region = "central" 502 newTenantID = "da363eb6-9444-4452-9bf6-40ee7e8da4d8" 503 504 parentTenantID = "20e67c37-d1a1-418d-a61a-37b485a2f163" 505 internalParentTenantID = "52f74825-83b5-46f4-884e-3ce2d589061f" 506 507 failedToFetchNewTenantsErrMsg = "failed to fetch new tenants" 508 failedToGetExistingTenantsErrMsg = "failed to get existing tenants" 509 ) 510 511 jobCfg := resync.JobConfig{ 512 TenantProvider: resync.TenantOnDemandProvider, 513 } 514 515 newSubaccountTenant := model.BusinessTenantMappingInput{ExternalTenant: newTenantID, Parent: parentTenantID, Region: region, Type: string(tenant.Subaccount)} 516 517 testCases := []struct { 518 Name string 519 JobCfg resync.JobConfig 520 TransactionerFn func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner) 521 TenantStorageSvcFn func() *automock.TenantStorageService 522 TenantCreatorFn func() *automock.TenantCreator 523 ExpectedErrMsg string 524 }{ 525 { 526 Name: "Success when create event is present for tenant", 527 JobCfg: jobCfg, 528 TransactionerFn: func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner) { 529 return txGen.ThatSucceedsMultipleTimes(2) 530 }, 531 TenantStorageSvcFn: func() *automock.TenantStorageService { 532 svc := &automock.TenantStorageService{} 533 parentTnt := &model.BusinessTenantMapping{ID: internalParentTenantID, ExternalTenant: parentTenantID} 534 svc.On("GetTenantByExternalID", txtest.CtxWithDBMatcher(), newTenantID).Return(nil, nil) 535 svc.On("GetTenantByExternalID", txtest.CtxWithDBMatcher(), newSubaccountTenant.Parent).Return(parentTnt, nil) 536 537 return svc 538 }, 539 TenantCreatorFn: func() *automock.TenantCreator { 540 svc := &automock.TenantCreator{} 541 tenantWithExistingParent := newSubaccountTenant 542 tenantWithExistingParent.Parent = internalParentTenantID 543 svc.On("FetchTenant", ctx, newTenantID).Return(&newSubaccountTenant, nil) 544 svc.On("CreateTenants", ctx, []model.BusinessTenantMappingInput{tenantWithExistingParent}).Return(nil) 545 return svc 546 }, 547 }, 548 { 549 Name: "[temporary] Success when create event is missing for tenant", 550 JobCfg: jobCfg, 551 TransactionerFn: txGen.ThatSucceeds, 552 TenantStorageSvcFn: func() *automock.TenantStorageService { 553 svc := &automock.TenantStorageService{} 554 svc.On("GetTenantByExternalID", txtest.CtxWithDBMatcher(), newTenantID).Return(nil, nil) 555 return svc 556 }, 557 TenantCreatorFn: func() *automock.TenantCreator { 558 svc := &automock.TenantCreator{} 559 lazilyStoredTnt := model.BusinessTenantMappingInput{ 560 Name: newTenantID, 561 ExternalTenant: newTenantID, 562 Parent: parentTenantID, // we expect the parent tenant ID to be internal tenant 563 Type: string(tenant.Subaccount), 564 Provider: "lazily-tenant-fetcher", 565 } 566 svc.On("FetchTenant", ctx, newTenantID).Return(nil, nil) 567 svc.On("CreateTenants", ctx, []model.BusinessTenantMappingInput{lazilyStoredTnt}).Return(nil) 568 return svc 569 }, 570 }, 571 { 572 Name: "Success when tenant already exists", 573 JobCfg: jobCfg, 574 TransactionerFn: txGen.ThatSucceeds, 575 TenantStorageSvcFn: func() *automock.TenantStorageService { 576 svc := &automock.TenantStorageService{} 577 svc.On("GetTenantByExternalID", txtest.CtxWithDBMatcher(), newTenantID).Return(newSubaccountTenant.ToBusinessTenantMapping(newTenantID), nil) 578 return svc 579 }, 580 TenantCreatorFn: func() *automock.TenantCreator { return &automock.TenantCreator{} }, 581 }, 582 { 583 Name: "Fails when tenant from create event has no parent tenant", 584 JobCfg: jobCfg, 585 TransactionerFn: txGen.ThatSucceeds, 586 TenantStorageSvcFn: func() *automock.TenantStorageService { 587 svc := &automock.TenantStorageService{} 588 svc.On("GetTenantByExternalID", txtest.CtxWithDBMatcher(), newTenantID).Return(nil, nil) 589 return svc 590 }, 591 TenantCreatorFn: func() *automock.TenantCreator { 592 svc := &automock.TenantCreator{} 593 tenantWithoutParent := newSubaccountTenant 594 tenantWithoutParent.Parent = "" 595 svc.On("FetchTenant", ctx, newTenantID).Return(&tenantWithoutParent, nil) 596 return svc 597 }, 598 ExpectedErrMsg: fmt.Sprintf("parent tenant not found of tenant with ID %s", newTenantID), 599 }, 600 { 601 Name: "Fails when checking for already existing tenant returns an error", 602 JobCfg: jobCfg, 603 TransactionerFn: txGen.ThatDoesntExpectCommit, 604 TenantStorageSvcFn: func() *automock.TenantStorageService { 605 svc := &automock.TenantStorageService{} 606 svc.On("GetTenantByExternalID", txtest.CtxWithDBMatcher(), newTenantID).Return(nil, errors.New(failedToGetExistingTenantsErrMsg)) 607 return svc 608 }, 609 TenantCreatorFn: func() *automock.TenantCreator { return &automock.TenantCreator{} }, 610 ExpectedErrMsg: failedToGetExistingTenantsErrMsg, 611 }, 612 { 613 Name: "Fails when fetching tenant returns an error", 614 JobCfg: jobCfg, 615 TransactionerFn: txGen.ThatSucceeds, 616 TenantStorageSvcFn: func() *automock.TenantStorageService { 617 svc := &automock.TenantStorageService{} 618 svc.On("GetTenantByExternalID", txtest.CtxWithDBMatcher(), newTenantID).Return(nil, nil) 619 620 return svc 621 }, 622 TenantCreatorFn: func() *automock.TenantCreator { 623 svc := &automock.TenantCreator{} 624 tenantWithExistingParent := newSubaccountTenant 625 tenantWithExistingParent.Parent = internalParentTenantID 626 svc.On("FetchTenant", ctx, newTenantID).Return(nil, errors.New(failedToFetchNewTenantsErrMsg)) 627 return svc 628 }, 629 ExpectedErrMsg: failedToFetchNewTenantsErrMsg, 630 }, 631 { 632 Name: "Fails when parent retrieval returns an error", 633 JobCfg: jobCfg, 634 TransactionerFn: func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner) { 635 persistTx := &persistenceautomock.PersistenceTx{} 636 persistTx.On("Commit").Return(nil).Times(1) 637 638 transact := &persistenceautomock.Transactioner{} 639 transact.On("Begin").Return(persistTx, nil).Times(2) 640 transact.On("RollbackUnlessCommitted", mock.Anything, persistTx).Return(false).Times(2) 641 642 return persistTx, transact 643 }, 644 TenantStorageSvcFn: func() *automock.TenantStorageService { 645 svc := &automock.TenantStorageService{} 646 svc.On("GetTenantByExternalID", txtest.CtxWithDBMatcher(), newTenantID).Return(nil, nil) 647 svc.On("GetTenantByExternalID", txtest.CtxWithDBMatcher(), newSubaccountTenant.Parent).Return(nil, testErr) 648 649 return svc 650 }, 651 TenantCreatorFn: func() *automock.TenantCreator { 652 svc := &automock.TenantCreator{} 653 tenantWithExistingParent := newSubaccountTenant 654 tenantWithExistingParent.Parent = internalParentTenantID 655 svc.On("FetchTenant", ctx, newTenantID).Return(&newSubaccountTenant, nil) 656 return svc 657 }, 658 ExpectedErrMsg: testErr.Error(), 659 }, 660 { 661 Name: "Fails when transaction start returns an error", 662 JobCfg: jobCfg, 663 TransactionerFn: txGen.ThatFailsOnBegin, 664 TenantStorageSvcFn: func() *automock.TenantStorageService { return &automock.TenantStorageService{} }, 665 TenantCreatorFn: func() *automock.TenantCreator { return &automock.TenantCreator{} }, 666 ExpectedErrMsg: testErr.Error(), 667 }, 668 { 669 Name: "Fails when first transaction commit returns an error", 670 JobCfg: jobCfg, 671 TransactionerFn: txGen.ThatFailsOnCommit, 672 TenantStorageSvcFn: func() *automock.TenantStorageService { 673 svc := &automock.TenantStorageService{} 674 svc.On("GetTenantByExternalID", txtest.CtxWithDBMatcher(), newTenantID).Return(nil, nil) 675 676 return svc 677 }, 678 TenantCreatorFn: func() *automock.TenantCreator { return &automock.TenantCreator{} }, 679 ExpectedErrMsg: testErr.Error(), 680 }, 681 } 682 683 for _, testCase := range testCases { 684 t.Run(testCase.Name, func(t *testing.T) { 685 persist, transact := testCase.TransactionerFn() 686 tenantStorageSvc := testCase.TenantStorageSvcFn() 687 tenantCreator := testCase.TenantCreatorFn() 688 689 defer mock.AssertExpectationsForObjects(t, persist, transact, tenantStorageSvc, tenantCreator) 690 691 synchronizer := resync.NewTenantSynchronizer(testCase.JobCfg, transact, tenantStorageSvc, tenantCreator, nil, nil, nil, nil) 692 err := synchronizer.SynchronizeTenant(ctx, parentTenantID, newTenantID) 693 if len(testCase.ExpectedErrMsg) > 0 { 694 require.Error(t, err) 695 require.Contains(t, err.Error(), testCase.ExpectedErrMsg) 696 } else { 697 require.NoError(t, err) 698 } 699 }) 700 } 701 }