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  }