github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/tenant/service_test.go (about) 1 package tenant_test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "regexp" 8 "testing" 9 10 "github.com/jmoiron/sqlx" 11 12 "github.com/DATA-DOG/go-sqlmock" 13 "github.com/kyma-incubator/compass/components/director/internal/repo/testdb" 14 "github.com/kyma-incubator/compass/components/director/pkg/pagination" 15 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 16 17 "github.com/kyma-incubator/compass/components/director/pkg/str" 18 19 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 20 tenantEntity "github.com/kyma-incubator/compass/components/director/pkg/tenant" 21 "github.com/stretchr/testify/mock" 22 23 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 24 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant/automock" 25 "github.com/kyma-incubator/compass/components/director/internal/model" 26 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 ) 30 31 func TestService_GetExternalTenant(t *testing.T) { 32 // GIVEN 33 ctx := tenant.SaveToContext(context.TODO(), "test", "external-test") 34 tenantMappingModel := newModelBusinessTenantMapping(testID, testName) 35 36 testCases := []struct { 37 Name string 38 TenantMappingRepoFn func() *automock.TenantMappingRepository 39 ExpectedError error 40 ExpectedOutput string 41 }{ 42 { 43 Name: "Success", 44 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 45 tenantMappingRepo := &automock.TenantMappingRepository{} 46 tenantMappingRepo.On("Get", ctx, testID).Return(tenantMappingModel, nil).Once() 47 return tenantMappingRepo 48 }, 49 ExpectedOutput: testExternal, 50 }, 51 { 52 Name: "Error when getting the internal tenant", 53 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 54 tenantMappingRepo := &automock.TenantMappingRepository{} 55 tenantMappingRepo.On("Get", ctx, testID).Return(nil, testError).Once() 56 return tenantMappingRepo 57 }, 58 ExpectedError: testError, 59 ExpectedOutput: "", 60 }, 61 } 62 63 for _, testCase := range testCases { 64 t.Run(testCase.Name, func(t *testing.T) { 65 tenantMappingRepoFn := testCase.TenantMappingRepoFn() 66 svc := tenant.NewService(tenantMappingRepoFn, nil, nil) 67 68 // WHEN 69 result, err := svc.GetExternalTenant(ctx, testID) 70 71 // THEN 72 if testCase.ExpectedError != nil { 73 require.Error(t, err) 74 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 75 } else { 76 assert.NoError(t, err) 77 } 78 assert.Equal(t, testCase.ExpectedOutput, result) 79 80 tenantMappingRepoFn.AssertExpectations(t) 81 }) 82 } 83 } 84 85 func TestService_GetInternalTenant(t *testing.T) { 86 // GIVEN 87 ctx := tenant.SaveToContext(context.TODO(), "test", "external-test") 88 tenantMappingModel := newModelBusinessTenantMapping(testID, testName) 89 90 testCases := []struct { 91 Name string 92 TenantMappingRepoFn func() *automock.TenantMappingRepository 93 ExpectedError error 94 ExpectedOutput string 95 }{ 96 { 97 Name: "Success", 98 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 99 tenantMappingRepo := &automock.TenantMappingRepository{} 100 tenantMappingRepo.On("GetByExternalTenant", ctx, testExternal).Return(tenantMappingModel, nil).Once() 101 return tenantMappingRepo 102 }, 103 ExpectedOutput: testID, 104 }, 105 { 106 Name: "Error when getting the internal tenant", 107 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 108 tenantMappingRepo := &automock.TenantMappingRepository{} 109 tenantMappingRepo.On("GetByExternalTenant", ctx, testExternal).Return(nil, testError).Once() 110 return tenantMappingRepo 111 }, 112 ExpectedError: testError, 113 ExpectedOutput: "", 114 }, 115 } 116 117 for _, testCase := range testCases { 118 t.Run(testCase.Name, func(t *testing.T) { 119 tenantMappingRepoFn := testCase.TenantMappingRepoFn() 120 svc := tenant.NewService(tenantMappingRepoFn, nil, nil) 121 122 // WHEN 123 result, err := svc.GetInternalTenant(ctx, testExternal) 124 125 // THEN 126 if testCase.ExpectedError != nil { 127 require.Error(t, err) 128 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 129 } else { 130 assert.NoError(t, err) 131 } 132 assert.Equal(t, testCase.ExpectedOutput, result) 133 134 tenantMappingRepoFn.AssertExpectations(t) 135 }) 136 } 137 } 138 139 func TestService_ExtractTenantIDForTenantScopedFormationTemplates(t *testing.T) { 140 // GIVEN 141 ctx := tenant.SaveToContext(context.TODO(), testID, testExternal) 142 ctxWithEmptyTenants := tenant.SaveToContext(context.TODO(), "", "") 143 144 testCases := []struct { 145 Name string 146 Context context.Context 147 TenantMappingRepoFn func() *automock.TenantMappingRepository 148 ExpectedError string 149 ExpectedOutput string 150 }{ 151 { 152 Name: "Success when tenant is GA", 153 Context: ctx, 154 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 155 tenantMappingRepo := &automock.TenantMappingRepository{} 156 tenantMappingRepo.On("Get", ctx, testID).Return(newModelBusinessTenantMappingWithType(testID, testName, "", nil, tenantEntity.Account), nil).Once() 157 return tenantMappingRepo 158 }, 159 ExpectedOutput: testID, 160 }, 161 { 162 Name: "Success when tenant is SA", 163 Context: ctx, 164 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 165 tenantMappingRepo := &automock.TenantMappingRepository{} 166 tenantMappingRepo.On("Get", ctx, testID).Return(newModelBusinessTenantMappingWithType(testID, testName, testParentID, nil, tenantEntity.Subaccount), nil).Once() 167 return tenantMappingRepo 168 }, 169 ExpectedOutput: testParentID, 170 }, 171 { 172 Name: "Success when empty tenant", 173 Context: ctxWithEmptyTenants, 174 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 175 return &automock.TenantMappingRepository{} 176 }, 177 ExpectedOutput: "", 178 }, 179 { 180 Name: "Error when getting the internal tenant", 181 Context: ctx, 182 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 183 tenantMappingRepo := &automock.TenantMappingRepository{} 184 tenantMappingRepo.On("Get", ctx, testID).Return(nil, testError).Once() 185 return tenantMappingRepo 186 }, 187 ExpectedError: testError.Error(), 188 ExpectedOutput: "", 189 }, 190 { 191 Name: "Error when tenant is not in context", 192 Context: context.TODO(), 193 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 194 return &automock.TenantMappingRepository{} 195 }, 196 ExpectedError: "cannot read tenant from context", 197 ExpectedOutput: "", 198 }, 199 { 200 Name: "Error when there is only internalID in context", 201 Context: tenant.SaveToContext(context.TODO(), testID, ""), 202 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 203 return &automock.TenantMappingRepository{} 204 }, 205 ExpectedError: apperrors.NewTenantNotFoundError("").Error(), 206 ExpectedOutput: "", 207 }, 208 { 209 Name: "Error when there is only externalID in context", 210 Context: tenant.SaveToContext(context.TODO(), "", testID), 211 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 212 return &automock.TenantMappingRepository{} 213 }, 214 ExpectedError: apperrors.NewTenantNotFoundError(testID).Error(), 215 ExpectedOutput: "", 216 }, 217 } 218 219 for _, testCase := range testCases { 220 t.Run(testCase.Name, func(t *testing.T) { 221 tenantMappingRepoFn := testCase.TenantMappingRepoFn() 222 svc := tenant.NewService(tenantMappingRepoFn, nil, nil) 223 224 // WHEN 225 result, err := svc.ExtractTenantIDForTenantScopedFormationTemplates(testCase.Context) 226 227 // THEN 228 if len(testCase.ExpectedError) > 0 { 229 require.Error(t, err) 230 assert.Contains(t, err.Error(), testCase.ExpectedError) 231 } else { 232 assert.NoError(t, err) 233 } 234 assert.Equal(t, testCase.ExpectedOutput, result) 235 236 tenantMappingRepoFn.AssertExpectations(t) 237 }) 238 } 239 } 240 241 func TestService_List(t *testing.T) { 242 // GIVEN 243 ctx := tenant.SaveToContext(context.TODO(), "test", "external-test") 244 modelTenantMappings := []*model.BusinessTenantMapping{ 245 newModelBusinessTenantMapping("foo1", "bar1"), 246 newModelBusinessTenantMapping("foo2", "bar2"), 247 } 248 249 testCases := []struct { 250 Name string 251 TenantMappingRepoFn func() *automock.TenantMappingRepository 252 ExpectedError error 253 ExpectedOutput []*model.BusinessTenantMapping 254 }{ 255 { 256 Name: "Success", 257 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 258 tenantMappingRepo := &automock.TenantMappingRepository{} 259 tenantMappingRepo.On("List", ctx).Return(modelTenantMappings, nil).Once() 260 return tenantMappingRepo 261 }, 262 ExpectedOutput: modelTenantMappings, 263 }, 264 { 265 Name: "Error when listing tenants", 266 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 267 tenantMappingRepo := &automock.TenantMappingRepository{} 268 tenantMappingRepo.On("List", ctx).Return([]*model.BusinessTenantMapping{}, testError).Once() 269 return tenantMappingRepo 270 }, 271 ExpectedError: testError, 272 ExpectedOutput: []*model.BusinessTenantMapping{}, 273 }, 274 } 275 276 for _, testCase := range testCases { 277 t.Run(testCase.Name, func(t *testing.T) { 278 tenantMappingRepo := testCase.TenantMappingRepoFn() 279 svc := tenant.NewService(tenantMappingRepo, nil, nil) 280 281 // WHEN 282 result, err := svc.List(ctx) 283 284 // THEN 285 if testCase.ExpectedError != nil { 286 require.Error(t, err) 287 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 288 } else { 289 assert.NoError(t, err) 290 } 291 assert.Equal(t, testCase.ExpectedOutput, result) 292 293 tenantMappingRepo.AssertExpectations(t) 294 }) 295 } 296 } 297 298 func TestService_ListPageBySearchTerm(t *testing.T) { 299 // GIVEN 300 searchTerm := "" 301 first := 100 302 endCursor := "" 303 ctx := tenant.SaveToContext(context.TODO(), "test", "external-test") 304 modelTenantMappingPage := &model.BusinessTenantMappingPage{ 305 Data: []*model.BusinessTenantMapping{ 306 newModelBusinessTenantMapping("foo1", "bar1"), 307 newModelBusinessTenantMapping("foo2", "bar2"), 308 }, 309 PageInfo: &pagination.Page{ 310 StartCursor: "", 311 EndCursor: "", 312 HasNextPage: false, 313 }, 314 TotalCount: 2, 315 } 316 317 testCases := []struct { 318 Name string 319 TenantMappingRepoFn func() *automock.TenantMappingRepository 320 ExpectedError error 321 ExpectedOutput *model.BusinessTenantMappingPage 322 }{ 323 { 324 Name: "Success", 325 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 326 tenantMappingRepo := &automock.TenantMappingRepository{} 327 tenantMappingRepo.On("ListPageBySearchTerm", ctx, searchTerm, first, endCursor).Return(modelTenantMappingPage, nil).Once() 328 return tenantMappingRepo 329 }, 330 ExpectedOutput: modelTenantMappingPage, 331 }, 332 { 333 Name: "Error when listing tenants", 334 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 335 tenantMappingRepo := &automock.TenantMappingRepository{} 336 tenantMappingRepo.On("ListPageBySearchTerm", ctx, searchTerm, first, endCursor).Return(&model.BusinessTenantMappingPage{}, testError).Once() 337 return tenantMappingRepo 338 }, 339 ExpectedError: testError, 340 ExpectedOutput: &model.BusinessTenantMappingPage{}, 341 }, 342 } 343 344 for _, testCase := range testCases { 345 t.Run(testCase.Name, func(t *testing.T) { 346 tenantMappingRepo := testCase.TenantMappingRepoFn() 347 svc := tenant.NewService(tenantMappingRepo, nil, nil) 348 349 // WHEN 350 result, err := svc.ListPageBySearchTerm(ctx, searchTerm, first, endCursor) 351 352 // THEN 353 if testCase.ExpectedError != nil { 354 require.Error(t, err) 355 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 356 } else { 357 assert.NoError(t, err) 358 } 359 assert.Equal(t, testCase.ExpectedOutput, result) 360 361 tenantMappingRepo.AssertExpectations(t) 362 }) 363 } 364 } 365 366 func TestService_DeleteMany(t *testing.T) { 367 // GIVEN 368 ctx := tenant.SaveToContext(context.TODO(), "test", "external-test") 369 tenantInput := newModelBusinessTenantMappingInput(testName, "", "", nil) 370 testErr := errors.New("test") 371 testCases := []struct { 372 Name string 373 TenantMappingRepoFn func() *automock.TenantMappingRepository 374 ExpectedOutput error 375 }{ 376 { 377 Name: "Success", 378 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 379 tenantMappingRepo := &automock.TenantMappingRepository{} 380 tenantMappingRepo.On("DeleteByExternalTenant", ctx, tenantInput.ExternalTenant).Return(nil).Once() 381 return tenantMappingRepo 382 }, 383 ExpectedOutput: nil, 384 }, 385 { 386 Name: "Error while deleting the tenant mapping", 387 TenantMappingRepoFn: func() *automock.TenantMappingRepository { 388 tenantMappingRepo := &automock.TenantMappingRepository{} 389 tenantMappingRepo.On("DeleteByExternalTenant", ctx, tenantInput.ExternalTenant).Return(testErr).Once() 390 return tenantMappingRepo 391 }, 392 ExpectedOutput: testErr, 393 }, 394 } 395 396 for _, testCase := range testCases { 397 t.Run(testCase.Name, func(t *testing.T) { 398 tenantMappingRepo := testCase.TenantMappingRepoFn() 399 svc := tenant.NewService(tenantMappingRepo, nil, nil) 400 401 // WHEN 402 err := svc.DeleteMany(ctx, []string{tenantInput.ExternalTenant}) 403 404 // THEN 405 if testCase.ExpectedOutput != nil { 406 require.Error(t, err) 407 assert.Contains(t, err.Error(), testCase.ExpectedOutput.Error()) 408 } else { 409 assert.NoError(t, err) 410 } 411 412 tenantMappingRepo.AssertExpectations(t) 413 }) 414 } 415 } 416 417 func TestService_CreateManyIfNotExists(t *testing.T) { 418 // GIVEN 419 ctx := tenant.SaveToContext(context.TODO(), "test", "external-test") 420 421 tenantInputs := []model.BusinessTenantMappingInput{newModelBusinessTenantMappingInput("test1", "", "", nil), 422 newModelBusinessTenantMappingInput("test2", "", "", nil).WithExternalTenant("external2")} 423 tenantInputsWithSubdomains := []model.BusinessTenantMappingInput{newModelBusinessTenantMappingInput("test1", testSubdomain, "", nil), 424 newModelBusinessTenantMappingInput("test2", "", "", nil).WithExternalTenant("external2")} 425 tenantInputsWithRegions := []model.BusinessTenantMappingInput{newModelBusinessTenantMappingInput("test1", "", testRegion, nil), 426 newModelBusinessTenantMappingInput("test2", "", testRegion, nil).WithExternalTenant("external2")} 427 tenantInputsWithLicenseType := []model.BusinessTenantMappingInput{newModelBusinessTenantMappingInput("test1", "", "", str.Ptr(testLicenseType)), 428 newModelBusinessTenantMappingInput("test2", "", "", str.Ptr(testLicenseType)).WithExternalTenant("external2")} 429 tenantModelInputsWithParent := []model.BusinessTenantMappingInput{newModelBusinessTenantMappingInputWithType(testID, "test1", testParentID, "", "", nil, tenantEntity.Account), 430 newModelBusinessTenantMappingInputWithType(testParentID, "test2", "", "", "", nil, tenantEntity.Customer)} 431 tenantWithSubdomainAndRegion := newModelBusinessTenantMappingInput("test1", testSubdomain, testRegion, nil) 432 tenantModelInputsWithParentOrganization := []model.BusinessTenantMappingInput{newModelBusinessTenantMappingInputWithType(testID, "test1", testParentID, "", "", nil, tenantEntity.Organization), 433 newModelBusinessTenantMappingInputWithType(testParentID, "test2", "", "", "", nil, tenantEntity.Folder)} 434 435 tenantModels := []model.BusinessTenantMapping{*newModelBusinessTenantMapping(testID, "test1"), 436 newModelBusinessTenantMapping(testID, "test2").WithExternalTenant("external2")} 437 tenantModelsWithLicense := []model.BusinessTenantMapping{*newModelBusinessTenantMappingWithLicense(testID, "test1", str.Ptr(testLicenseType)), 438 newModelBusinessTenantMappingWithLicense(testID, "test2", str.Ptr(testLicenseType)).WithExternalTenant("external2")} 439 440 expectedResult := []string{testID, testID} 441 uidSvcFn := func() *automock.UIDService { 442 uidSvc := &automock.UIDService{} 443 uidSvc.On("Generate").Return(testID) 444 return uidSvc 445 } 446 noopLabelRepo := func() *automock.LabelRepository { 447 return &automock.LabelRepository{} 448 } 449 noopLabelUpsertSvc := func() *automock.LabelUpsertService { 450 return &automock.LabelUpsertService{} 451 } 452 testErr := errors.New("test") 453 type testCase struct { 454 Name string 455 tenantInputs []model.BusinessTenantMappingInput 456 TenantMappingRepoFn func(string) *automock.TenantMappingRepository 457 LabelRepoFn func() *automock.LabelRepository 458 LabelUpsertSvcFn func() *automock.LabelUpsertService 459 UIDSvcFn func() *automock.UIDService 460 ExpectedError error 461 ExpectedResult []string 462 } 463 464 testCases := []testCase{ 465 { 466 Name: "Success", 467 tenantInputs: tenantInputs, 468 TenantMappingRepoFn: func(createRepoFunc string) *automock.TenantMappingRepository { 469 return createRepoSvc(ctx, createRepoFunc, tenantModels[0], tenantModels[1]) 470 }, 471 UIDSvcFn: uidSvcFn, 472 LabelRepoFn: noopLabelRepo, 473 LabelUpsertSvcFn: noopLabelUpsertSvc, 474 ExpectedError: nil, 475 ExpectedResult: expectedResult, 476 }, 477 { 478 Name: "Success when parent tenant exists with another ID", 479 tenantInputs: tenantModelInputsWithParent, 480 TenantMappingRepoFn: func(createFunc string) *automock.TenantMappingRepository { 481 parent := tenantModelInputsWithParent[1] 482 modifiedTenant := tenantModelInputsWithParent[0] 483 modifiedTenant.Parent = testInternalParentID 484 485 tenantMappingRepo := &automock.TenantMappingRepository{} 486 tenantMappingRepo.On(createFunc, ctx, *parent.ToBusinessTenantMapping(testTemporaryInternalParentID)).Return(nil).Once() 487 tenantMappingRepo.On("GetByExternalTenant", ctx, parent.ExternalTenant).Return(parent.ToBusinessTenantMapping(testInternalParentID), nil).Once() 488 tenantMappingRepo.On(createFunc, ctx, *modifiedTenant.ToBusinessTenantMapping(testID)).Return(nil).Once() 489 tenantMappingRepo.On("GetByExternalTenant", ctx, modifiedTenant.ExternalTenant).Return(modifiedTenant.ToBusinessTenantMapping(testID), nil).Once() 490 return tenantMappingRepo 491 }, 492 UIDSvcFn: func() *automock.UIDService { 493 uidSvc := &automock.UIDService{} 494 uidSvc.On("Generate").Return(testID).Once() 495 uidSvc.On("Generate").Return(testTemporaryInternalParentID).Once() 496 return uidSvc 497 }, 498 LabelRepoFn: noopLabelRepo, 499 LabelUpsertSvcFn: noopLabelUpsertSvc, 500 ExpectedError: nil, 501 ExpectedResult: []string{testInternalParentID, testID}, 502 }, 503 { 504 Name: "Success when parent tenant organization exists with another ID", 505 tenantInputs: tenantModelInputsWithParentOrganization, 506 TenantMappingRepoFn: func(createFunc string) *automock.TenantMappingRepository { 507 parent := tenantModelInputsWithParentOrganization[1] 508 modifiedTenant := tenantModelInputsWithParentOrganization[0] 509 modifiedTenant.Parent = testInternalParentID 510 511 tenantMappingRepo := &automock.TenantMappingRepository{} 512 tenantMappingRepo.On(createFunc, ctx, *parent.ToBusinessTenantMapping(testTemporaryInternalParentID)).Return(nil).Once() 513 tenantMappingRepo.On("GetByExternalTenant", ctx, parent.ExternalTenant).Return(parent.ToBusinessTenantMapping(testInternalParentID), nil).Once() 514 tenantMappingRepo.On(createFunc, ctx, *modifiedTenant.ToBusinessTenantMapping(testID)).Return(nil).Once() 515 tenantMappingRepo.On("GetByExternalTenant", ctx, modifiedTenant.ExternalTenant).Return(modifiedTenant.ToBusinessTenantMapping(testID), nil).Once() 516 return tenantMappingRepo 517 }, 518 UIDSvcFn: func() *automock.UIDService { 519 uidSvc := &automock.UIDService{} 520 uidSvc.On("Generate").Return(testID).Once() 521 uidSvc.On("Generate").Return(testTemporaryInternalParentID).Once() 522 return uidSvc 523 }, 524 LabelRepoFn: noopLabelRepo, 525 LabelUpsertSvcFn: noopLabelUpsertSvc, 526 ExpectedError: nil, 527 ExpectedResult: []string{testInternalParentID, testID}, 528 }, 529 { 530 Name: "Success when subdomain should be added", 531 tenantInputs: tenantInputsWithSubdomains, 532 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 533 return createRepoSvc(ctx, createFuncName, *tenantInputsWithSubdomains[0].ToBusinessTenantMapping(testID), *tenantInputsWithSubdomains[1].ToBusinessTenantMapping(testID)) 534 }, 535 UIDSvcFn: uidSvcFn, 536 LabelRepoFn: noopLabelRepo, 537 LabelUpsertSvcFn: func() *automock.LabelUpsertService { 538 svc := &automock.LabelUpsertService{} 539 label := &model.LabelInput{ 540 Key: "subdomain", 541 Value: testSubdomain, 542 ObjectID: testID, 543 ObjectType: model.TenantLabelableObject, 544 } 545 svc.On("UpsertLabel", ctx, testID, label).Return(nil).Once() 546 return svc 547 }, 548 ExpectedError: nil, 549 ExpectedResult: expectedResult, 550 }, 551 { 552 Name: "Success when region should be added", 553 tenantInputs: tenantInputsWithRegions, 554 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 555 return createRepoSvc(ctx, createFuncName, *tenantInputsWithRegions[0].ToBusinessTenantMapping(testID), *tenantInputsWithRegions[1].ToBusinessTenantMapping(testID)) 556 }, 557 UIDSvcFn: uidSvcFn, 558 LabelRepoFn: noopLabelRepo, 559 LabelUpsertSvcFn: func() *automock.LabelUpsertService { 560 svc := &automock.LabelUpsertService{} 561 regionLabel := &model.LabelInput{ 562 Key: "region", 563 Value: testRegion, 564 ObjectID: tenantModels[1].ID, 565 ObjectType: model.TenantLabelableObject, 566 } 567 svc.On("UpsertLabel", ctx, testID, regionLabel).Return(nil).Twice() 568 return svc 569 }, 570 ExpectedError: nil, 571 ExpectedResult: expectedResult, 572 }, 573 { 574 Name: "Success when licenseType should be added", 575 tenantInputs: tenantInputsWithLicenseType, 576 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 577 return createRepoSvc(ctx, createFuncName, *tenantInputsWithLicenseType[0].ToBusinessTenantMapping(testID), *tenantInputsWithLicenseType[1].ToBusinessTenantMapping(testID)) 578 }, 579 UIDSvcFn: uidSvcFn, 580 LabelRepoFn: noopLabelRepo, 581 LabelUpsertSvcFn: func() *automock.LabelUpsertService { 582 svc := &automock.LabelUpsertService{} 583 label := &model.LabelInput{ 584 Key: "licensetype", 585 Value: testLicenseType, 586 ObjectID: tenantModelsWithLicense[1].ID, 587 ObjectType: model.TenantLabelableObject, 588 } 589 svc.On("UpsertLabel", ctx, testID, label).Return(nil).Twice() 590 return svc 591 }, 592 ExpectedError: nil, 593 ExpectedResult: expectedResult, 594 }, 595 { 596 Name: "Error when checking the existence of tenant", 597 tenantInputs: []model.BusinessTenantMappingInput{tenantWithSubdomainAndRegion}, 598 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 599 tenantMappingRepo := &automock.TenantMappingRepository{} 600 tenantMappingRepo.On(createFuncName, ctx, *tenantWithSubdomainAndRegion.ToBusinessTenantMapping(testID)).Return(nil).Once() 601 tenantMappingRepo.On("GetByExternalTenant", ctx, tenantWithSubdomainAndRegion.ExternalTenant).Return(nil, testErr) 602 return tenantMappingRepo 603 }, 604 UIDSvcFn: uidSvcFn, 605 LabelRepoFn: noopLabelRepo, 606 LabelUpsertSvcFn: noopLabelUpsertSvc, 607 ExpectedError: testErr, 608 ExpectedResult: nil, 609 }, 610 { 611 Name: "Error when subdomain label setting fails", 612 tenantInputs: tenantInputsWithSubdomains, 613 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 614 tenantMappingRepo := &automock.TenantMappingRepository{} 615 tenantMappingRepo.On(createFuncName, ctx, tenantModels[0]).Return(nil).Once() 616 tenantMappingRepo.On("GetByExternalTenant", ctx, tenantInputsWithSubdomains[0].ExternalTenant).Return(&tenantModels[0], nil) 617 return tenantMappingRepo 618 }, 619 UIDSvcFn: uidSvcFn, 620 LabelRepoFn: noopLabelRepo, 621 LabelUpsertSvcFn: func() *automock.LabelUpsertService { 622 svc := &automock.LabelUpsertService{} 623 label := &model.LabelInput{ 624 Key: "subdomain", 625 Value: testSubdomain, 626 ObjectID: testID, 627 ObjectType: model.TenantLabelableObject, 628 } 629 svc.On("UpsertLabel", ctx, testID, label).Return(testErr).Once() 630 return svc 631 }, 632 ExpectedError: testErr, 633 ExpectedResult: nil, 634 }, 635 { 636 Name: "Error when region label setting fails", 637 tenantInputs: tenantInputsWithRegions, 638 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 639 tenantMappingRepo := &automock.TenantMappingRepository{} 640 tenantMappingRepo.On(createFuncName, ctx, tenantModels[0]).Return(nil).Once() 641 tenantMappingRepo.On("GetByExternalTenant", ctx, tenantInputsWithRegions[0].ExternalTenant).Return(&tenantModels[0], nil) 642 return tenantMappingRepo 643 }, 644 UIDSvcFn: uidSvcFn, 645 LabelRepoFn: noopLabelRepo, 646 LabelUpsertSvcFn: func() *automock.LabelUpsertService { 647 svc := &automock.LabelUpsertService{} 648 label := &model.LabelInput{ 649 Key: "region", 650 Value: testRegion, 651 ObjectID: testID, 652 ObjectType: model.TenantLabelableObject, 653 } 654 svc.On("UpsertLabel", ctx, testID, label).Return(testErr).Once() 655 return svc 656 }, 657 ExpectedError: testErr, 658 ExpectedResult: nil, 659 }, 660 { 661 Name: "Error when licenseType label setting fails", 662 tenantInputs: tenantInputsWithLicenseType, 663 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 664 tenantMappingRepo := &automock.TenantMappingRepository{} 665 tenantMappingRepo.On(createFuncName, ctx, tenantModelsWithLicense[0]).Return(nil).Once() 666 tenantMappingRepo.On("GetByExternalTenant", ctx, tenantInputsWithRegions[0].ExternalTenant).Return(&tenantModelsWithLicense[0], nil) 667 return tenantMappingRepo 668 }, 669 UIDSvcFn: uidSvcFn, 670 LabelRepoFn: noopLabelRepo, 671 LabelUpsertSvcFn: func() *automock.LabelUpsertService { 672 svc := &automock.LabelUpsertService{} 673 label := &model.LabelInput{ 674 Key: "licensetype", 675 Value: testLicenseType, 676 ObjectID: testID, 677 ObjectType: model.TenantLabelableObject, 678 } 679 svc.On("UpsertLabel", ctx, testID, label).Return(testErr).Once() 680 return svc 681 }, 682 ExpectedError: testErr, 683 ExpectedResult: nil, 684 }, 685 { 686 Name: "Error when creating the tenant", 687 tenantInputs: tenantInputs, 688 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 689 tenantMappingRepo := &automock.TenantMappingRepository{} 690 tenantMappingRepo.On(createFuncName, ctx, tenantModels[0]).Return(testErr).Once() 691 return tenantMappingRepo 692 }, 693 UIDSvcFn: uidSvcFn, 694 LabelRepoFn: noopLabelRepo, 695 LabelUpsertSvcFn: noopLabelUpsertSvc, 696 ExpectedError: testErr, 697 ExpectedResult: nil, 698 }, 699 } 700 701 t.Run("CreateManyIfNotExists", func(t *testing.T) { 702 for _, testCase := range testCases { 703 t.Run(testCase.Name, func(t *testing.T) { 704 uidSvc := testCase.UIDSvcFn() 705 tenantMappingRepo := testCase.TenantMappingRepoFn("UnsafeCreate") 706 labelRepo := testCase.LabelRepoFn() 707 labelUpsertSvc := testCase.LabelUpsertSvcFn() 708 defer mock.AssertExpectationsForObjects(t, tenantMappingRepo, uidSvc, labelRepo, labelUpsertSvc) 709 710 svc := tenant.NewServiceWithLabels(tenantMappingRepo, uidSvc, labelRepo, labelUpsertSvc, nil) 711 712 // WHEN 713 res, err := svc.CreateManyIfNotExists(ctx, testCase.tenantInputs...) 714 715 // THEN 716 if testCase.ExpectedError != nil { 717 require.Error(t, err) 718 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 719 } else { 720 assert.NoError(t, err) 721 assert.Equal(t, testCase.ExpectedResult, res) 722 } 723 }) 724 } 725 }) 726 727 t.Run("UpsertMany", func(t *testing.T) { 728 for _, testCase := range testCases { 729 t.Run(testCase.Name, func(t *testing.T) { 730 uidSvc := testCase.UIDSvcFn() 731 tenantMappingRepo := testCase.TenantMappingRepoFn("Upsert") 732 labelRepo := testCase.LabelRepoFn() 733 labelUpsertSvc := testCase.LabelUpsertSvcFn() 734 defer mock.AssertExpectationsForObjects(t, tenantMappingRepo, uidSvc, labelRepo, labelUpsertSvc) 735 736 svc := tenant.NewServiceWithLabels(tenantMappingRepo, uidSvc, labelRepo, labelUpsertSvc, nil) 737 738 // WHEN 739 res, err := svc.UpsertMany(ctx, testCase.tenantInputs...) 740 741 // THEN 742 if testCase.ExpectedError != nil { 743 require.Error(t, err) 744 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 745 } else { 746 assert.NoError(t, err) 747 assert.Equal(t, testCase.ExpectedResult, res) 748 } 749 }) 750 } 751 }) 752 } 753 754 func Test_UpsertSingle(t *testing.T) { 755 ctx := tenant.SaveToContext(context.TODO(), "test", "external-test") 756 757 tenantInput := newModelBusinessTenantMappingInput("test1", "", "", nil) 758 tenantInputWithSubdomain := newModelBusinessTenantMappingInput("test1", testSubdomain, "", nil) 759 tenantInputWithRegion := newModelBusinessTenantMappingInput("test1", "", testRegion, nil) 760 761 tenantModel := newModelBusinessTenantMapping(testID, "test1") 762 763 uidSvcFn := func() *automock.UIDService { 764 uidSvc := &automock.UIDService{} 765 uidSvc.On("Generate").Return(testID) 766 return uidSvc 767 } 768 769 noopLabelRepo := func() *automock.LabelRepository { 770 return &automock.LabelRepository{} 771 } 772 noopLabelUpsertSvc := func() *automock.LabelUpsertService { 773 return &automock.LabelUpsertService{} 774 } 775 776 testCases := []struct { 777 Name string 778 tenantInput model.BusinessTenantMappingInput 779 TenantMappingRepoFn func(string) *automock.TenantMappingRepository 780 LabelRepoFn func() *automock.LabelRepository 781 LabelUpsertSvcFn func() *automock.LabelUpsertService 782 UIDSvcFn func() *automock.UIDService 783 ExpectedError error 784 ExpectedResult string 785 }{ 786 { 787 Name: "Success", 788 tenantInput: tenantInput, 789 TenantMappingRepoFn: func(createRepoFunc string) *automock.TenantMappingRepository { 790 return createRepoSvc(ctx, createRepoFunc, *tenantModel) 791 }, 792 UIDSvcFn: uidSvcFn, 793 LabelRepoFn: noopLabelRepo, 794 LabelUpsertSvcFn: noopLabelUpsertSvc, 795 ExpectedError: nil, 796 ExpectedResult: testID, 797 }, 798 { 799 Name: "Success when subdomain should be added", 800 tenantInput: tenantInputWithSubdomain, 801 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 802 return createRepoSvc(ctx, createFuncName, *tenantInputWithSubdomain.ToBusinessTenantMapping(testID)) 803 }, 804 UIDSvcFn: uidSvcFn, 805 LabelRepoFn: noopLabelRepo, 806 LabelUpsertSvcFn: func() *automock.LabelUpsertService { 807 svc := &automock.LabelUpsertService{} 808 label := &model.LabelInput{ 809 Key: "subdomain", 810 Value: testSubdomain, 811 ObjectID: testID, 812 ObjectType: model.TenantLabelableObject, 813 } 814 svc.On("UpsertLabel", ctx, testID, label).Return(nil).Once() 815 return svc 816 }, 817 ExpectedError: nil, 818 ExpectedResult: testID, 819 }, 820 { 821 Name: "Success when region should be added", 822 tenantInput: tenantInputWithRegion, 823 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 824 return createRepoSvc(ctx, createFuncName, *tenantInputWithRegion.ToBusinessTenantMapping(testID)) 825 }, 826 UIDSvcFn: uidSvcFn, 827 LabelRepoFn: noopLabelRepo, 828 LabelUpsertSvcFn: func() *automock.LabelUpsertService { 829 svc := &automock.LabelUpsertService{} 830 label := &model.LabelInput{ 831 Key: "region", 832 Value: testRegion, 833 ObjectID: testID, 834 ObjectType: model.TenantLabelableObject, 835 } 836 svc.On("UpsertLabel", ctx, testID, label).Return(nil).Once() 837 return svc 838 }, 839 ExpectedError: nil, 840 ExpectedResult: testID, 841 }, 842 { 843 Name: "Error when checking the existence of tenant", 844 tenantInput: tenantInput, 845 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 846 tenantMappingRepo := &automock.TenantMappingRepository{} 847 tenantMappingRepo.On(createFuncName, ctx, *tenantInput.ToBusinessTenantMapping(testID)).Return(nil).Once() 848 tenantMappingRepo.On("GetByExternalTenant", ctx, tenantInput.ExternalTenant).Return(nil, testError) 849 return tenantMappingRepo 850 }, 851 UIDSvcFn: uidSvcFn, 852 LabelRepoFn: noopLabelRepo, 853 LabelUpsertSvcFn: noopLabelUpsertSvc, 854 ExpectedError: testError, 855 ExpectedResult: "", 856 }, 857 { 858 Name: "Error when subdomain label setting fails", 859 tenantInput: tenantInputWithSubdomain, 860 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 861 tenantMappingRepo := &automock.TenantMappingRepository{} 862 tenantMappingRepo.On(createFuncName, ctx, *tenantInputWithSubdomain.ToBusinessTenantMapping(testID)).Return(nil).Once() 863 tenantMappingRepo.On("GetByExternalTenant", ctx, tenantInputWithSubdomain.ExternalTenant).Return(tenantModel, nil) 864 return tenantMappingRepo 865 }, 866 UIDSvcFn: uidSvcFn, 867 LabelRepoFn: noopLabelRepo, 868 LabelUpsertSvcFn: func() *automock.LabelUpsertService { 869 svc := &automock.LabelUpsertService{} 870 label := &model.LabelInput{ 871 Key: "subdomain", 872 Value: testSubdomain, 873 ObjectID: testID, 874 ObjectType: model.TenantLabelableObject, 875 } 876 svc.On("UpsertLabel", ctx, testID, label).Return(testError).Once() 877 return svc 878 }, 879 ExpectedError: testError, 880 ExpectedResult: "", 881 }, 882 { 883 Name: "Error when region label setting fails", 884 tenantInput: tenantInputWithRegion, 885 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 886 tenantMappingRepo := &automock.TenantMappingRepository{} 887 tenantMappingRepo.On(createFuncName, ctx, *tenantInputWithRegion.ToBusinessTenantMapping(testID)).Return(nil).Once() 888 tenantMappingRepo.On("GetByExternalTenant", ctx, tenantInputWithRegion.ExternalTenant).Return(tenantModel, nil) 889 return tenantMappingRepo 890 }, 891 UIDSvcFn: uidSvcFn, 892 LabelRepoFn: noopLabelRepo, 893 LabelUpsertSvcFn: func() *automock.LabelUpsertService { 894 svc := &automock.LabelUpsertService{} 895 label := &model.LabelInput{ 896 Key: "region", 897 Value: testRegion, 898 ObjectID: testID, 899 ObjectType: model.TenantLabelableObject, 900 } 901 svc.On("UpsertLabel", ctx, testID, label).Return(testError).Once() 902 return svc 903 }, 904 ExpectedError: testError, 905 ExpectedResult: "", 906 }, 907 { 908 Name: "Error when creating the tenant", 909 tenantInput: tenantInput, 910 TenantMappingRepoFn: func(createFuncName string) *automock.TenantMappingRepository { 911 tenantMappingRepo := &automock.TenantMappingRepository{} 912 tenantMappingRepo.On(createFuncName, ctx, *tenantModel).Return(testError).Once() 913 return tenantMappingRepo 914 }, 915 UIDSvcFn: uidSvcFn, 916 LabelRepoFn: noopLabelRepo, 917 LabelUpsertSvcFn: noopLabelUpsertSvc, 918 ExpectedError: testError, 919 ExpectedResult: "", 920 }, 921 } 922 923 for _, testCase := range testCases { 924 t.Run(testCase.Name, func(t *testing.T) { 925 uidSvc := testCase.UIDSvcFn() 926 tenantMappingRepo := testCase.TenantMappingRepoFn("Upsert") 927 labelRepo := testCase.LabelRepoFn() 928 labelUpsertSvc := testCase.LabelUpsertSvcFn() 929 defer mock.AssertExpectationsForObjects(t, tenantMappingRepo, uidSvc, labelRepo, labelUpsertSvc) 930 931 svc := tenant.NewServiceWithLabels(tenantMappingRepo, uidSvc, labelRepo, labelUpsertSvc, nil) 932 933 // WHEN 934 result, err := svc.UpsertSingle(ctx, testCase.tenantInput) 935 936 // THEN 937 if testCase.ExpectedError != nil { 938 require.Error(t, err) 939 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 940 } else { 941 assert.NoError(t, err) 942 require.Equal(t, testCase.ExpectedResult, result) 943 } 944 }) 945 } 946 } 947 948 func Test_MultipleToTenantMapping(t *testing.T) { 949 testCases := []struct { 950 Name string 951 InputSlice []model.BusinessTenantMappingInput 952 ExpectedSlice []model.BusinessTenantMapping 953 }{ 954 { 955 Name: "success with more than one parent chain", 956 InputSlice: []model.BusinessTenantMappingInput{ 957 { 958 Name: "acc1", 959 ExternalTenant: "0", 960 }, 961 { 962 Name: "acc2", 963 ExternalTenant: "1", 964 Parent: "2", 965 }, 966 { 967 Name: "customer1", 968 ExternalTenant: "2", 969 Parent: "4", 970 }, 971 { 972 Name: "acc3", 973 ExternalTenant: "3", 974 }, 975 { 976 Name: "x1", 977 ExternalTenant: "4", 978 }, 979 }, 980 ExpectedSlice: []model.BusinessTenantMapping{ 981 { 982 ID: "0", 983 Name: "acc1", 984 ExternalTenant: "0", 985 Status: tenantEntity.Active, 986 Type: tenantEntity.Unknown, 987 }, 988 { 989 ID: "4", 990 Name: "x1", 991 ExternalTenant: "4", 992 Status: tenantEntity.Active, 993 Type: tenantEntity.Unknown, 994 }, 995 { 996 ID: "2", 997 Name: "customer1", 998 ExternalTenant: "2", 999 Parent: "4", 1000 Status: tenantEntity.Active, 1001 Type: tenantEntity.Unknown, 1002 }, 1003 { 1004 ID: "1", 1005 Name: "acc2", 1006 ExternalTenant: "1", 1007 Parent: "2", 1008 Status: tenantEntity.Active, 1009 Type: tenantEntity.Unknown, 1010 }, 1011 { 1012 ID: "3", 1013 Name: "acc3", 1014 ExternalTenant: "3", 1015 Status: tenantEntity.Active, 1016 Type: tenantEntity.Unknown, 1017 }, 1018 }, 1019 }, 1020 } 1021 1022 for _, testCase := range testCases { 1023 t.Run(testCase.Name, func(t *testing.T) { 1024 svc := tenant.NewService(nil, &serialUUIDService{}, nil) 1025 require.Equal(t, testCase.ExpectedSlice, svc.MultipleToTenantMapping(testCase.InputSlice)) 1026 }) 1027 } 1028 } 1029 1030 func Test_Update(t *testing.T) { 1031 tnt := model.BusinessTenantMappingInput{ 1032 Name: testName, 1033 ExternalTenant: testExternal, 1034 Parent: testParentID, 1035 Subdomain: testSubdomain, 1036 Region: testRegion, 1037 Type: string(tenantEntity.Account), 1038 Provider: testProvider, 1039 } 1040 tntToBusinessTenantMapping := &model.BusinessTenantMapping{ 1041 ID: testID, 1042 Name: testName, 1043 ExternalTenant: testExternal, 1044 Parent: testParentID, 1045 Type: tenantEntity.Account, 1046 Provider: testProvider, 1047 Status: tenantEntity.Active, 1048 Initialized: nil, 1049 } 1050 1051 testCases := []struct { 1052 Name string 1053 InputID string 1054 InputTenant model.BusinessTenantMappingInput 1055 TenantMappingRepositoryFn func() *automock.TenantMappingRepository 1056 ExpectedErr error 1057 }{ 1058 { 1059 Name: "Success", 1060 InputID: testID, 1061 InputTenant: tnt, 1062 TenantMappingRepositoryFn: func() *automock.TenantMappingRepository { 1063 tenantMappingRepo := &automock.TenantMappingRepository{} 1064 tenantMappingRepo.On("Update", mock.Anything, tntToBusinessTenantMapping).Return(nil) 1065 return tenantMappingRepo 1066 }, 1067 ExpectedErr: nil, 1068 }, 1069 { 1070 Name: "Returns error when can't update the tenant", 1071 InputID: testID, 1072 InputTenant: tnt, 1073 TenantMappingRepositoryFn: func() *automock.TenantMappingRepository { 1074 tenantMappingRepo := &automock.TenantMappingRepository{} 1075 tenantMappingRepo.On("Update", mock.Anything, tntToBusinessTenantMapping).Return(testError) 1076 return tenantMappingRepo 1077 }, 1078 ExpectedErr: testError, 1079 }, 1080 } 1081 1082 for _, testCase := range testCases { 1083 t.Run(testCase.Name, func(t *testing.T) { 1084 ctx := context.TODO() 1085 tenantMappingRepo := testCase.TenantMappingRepositoryFn() 1086 serialUUIDService := &serialUUIDService{} 1087 svc := tenant.NewService(tenantMappingRepo, serialUUIDService, nil) 1088 err := svc.Update(ctx, testCase.InputID, testCase.InputTenant) 1089 1090 if testCase.ExpectedErr != nil { 1091 assert.Error(t, err) 1092 assert.Contains(t, err.Error(), testCase.ExpectedErr.Error()) 1093 } else { 1094 assert.NoError(t, err) 1095 } 1096 }) 1097 } 1098 } 1099 1100 func Test_MoveBeforeIndex(t *testing.T) { 1101 testCases := []struct { 1102 Name string 1103 InputSlice []model.BusinessTenantMapping 1104 TargetTenantID string 1105 TargetIndex int 1106 ExpectedSlice []model.BusinessTenantMapping 1107 ShouldMove bool 1108 }{ 1109 { 1110 Name: "success", 1111 InputSlice: []model.BusinessTenantMapping{ 1112 {ID: "1"}, {ID: "2"}, {ID: "3"}, {ID: "4"}, {ID: "5"}, 1113 }, 1114 TargetTenantID: "4", 1115 TargetIndex: 1, 1116 ExpectedSlice: []model.BusinessTenantMapping{ 1117 {ID: "1"}, {ID: "4"}, {ID: "2"}, {ID: "3"}, {ID: "5"}, 1118 }, 1119 ShouldMove: true, 1120 }, 1121 { 1122 Name: "move before first element", 1123 InputSlice: []model.BusinessTenantMapping{ 1124 {ID: "1"}, {ID: "2"}, {ID: "3"}, {ID: "4"}, {ID: "5"}, 1125 }, 1126 TargetTenantID: "3", 1127 TargetIndex: 0, 1128 ExpectedSlice: []model.BusinessTenantMapping{ 1129 {ID: "3"}, {ID: "1"}, {ID: "2"}, {ID: "4"}, {ID: "5"}, 1130 }, 1131 ShouldMove: true, 1132 }, 1133 { 1134 Name: "move before last element", 1135 InputSlice: []model.BusinessTenantMapping{ 1136 {ID: "1"}, {ID: "2"}, {ID: "3"}, {ID: "4"}, {ID: "5"}, 1137 }, 1138 TargetTenantID: "3", 1139 TargetIndex: 4, 1140 ExpectedSlice: []model.BusinessTenantMapping{ 1141 {ID: "1"}, {ID: "2"}, {ID: "3"}, {ID: "4"}, {ID: "5"}, 1142 }, 1143 }, 1144 } 1145 1146 for _, testCase := range testCases { 1147 t.Run(testCase.Name, func(t *testing.T) { 1148 result, moved := tenant.MoveBeforeIfShould(testCase.InputSlice, testCase.TargetTenantID, testCase.TargetIndex) 1149 require.Equal(t, testCase.ShouldMove, moved) 1150 require.Equal(t, testCase.ExpectedSlice, result) 1151 }) 1152 } 1153 } 1154 1155 func Test_ListLabels(t *testing.T) { 1156 const tenantID = "edc6857b-b0c7-49e6-9f0a-e87a9c2a4eb8" 1157 1158 ctx := context.TODO() 1159 testErr := errors.New("failed to list labels") 1160 1161 t.Run("Success", func(t *testing.T) { 1162 labels := map[string]*model.Label{ 1163 "label-key": { 1164 ID: "5ef5ebd0-987d-4cb6-a3c1-7d710de259a2", 1165 Tenant: str.Ptr(tenantID), 1166 Key: "label-key", 1167 Value: "value", 1168 ObjectID: tenantID, 1169 ObjectType: model.TenantLabelableObject, 1170 }, 1171 } 1172 1173 uidSvc := &automock.UIDService{} 1174 labelUpsertSvc := &automock.LabelUpsertService{} 1175 1176 tenantRepo := &automock.TenantMappingRepository{} 1177 tenantRepo.On("Exists", ctx, tenantID).Return(true, nil) 1178 1179 labelRepo := &automock.LabelRepository{} 1180 labelRepo.On("ListForObject", ctx, tenantID, model.TenantLabelableObject, tenantID).Return(labels, nil) 1181 1182 defer mock.AssertExpectationsForObjects(t, tenantRepo, uidSvc, labelRepo, labelUpsertSvc) 1183 1184 svc := tenant.NewServiceWithLabels(tenantRepo, uidSvc, labelRepo, labelUpsertSvc, nil) 1185 1186 actualLabels, err := svc.ListLabels(ctx, tenantID) 1187 assert.NoError(t, err) 1188 assert.Equal(t, labels, actualLabels) 1189 }) 1190 1191 t.Run("Error when tenant existence cannot be ensured", func(t *testing.T) { 1192 uidSvc := &automock.UIDService{} 1193 labelRepo := &automock.LabelRepository{} 1194 labelUpsertSvc := &automock.LabelUpsertService{} 1195 1196 tenantRepo := &automock.TenantMappingRepository{} 1197 tenantRepo.On("Exists", ctx, tenantID).Return(false, testErr) 1198 1199 defer mock.AssertExpectationsForObjects(t, tenantRepo, uidSvc, labelRepo, labelUpsertSvc) 1200 1201 svc := tenant.NewServiceWithLabels(tenantRepo, uidSvc, labelRepo, labelUpsertSvc, nil) 1202 1203 _, err := svc.ListLabels(ctx, tenantID) 1204 assert.Error(t, err) 1205 assert.Contains(t, err.Error(), fmt.Sprintf("while checking if tenant with ID %s exists", tenantID)) 1206 }) 1207 1208 t.Run("Error when tenant does not exist", func(t *testing.T) { 1209 uidSvc := &automock.UIDService{} 1210 labelRepo := &automock.LabelRepository{} 1211 labelUpsertSvc := &automock.LabelUpsertService{} 1212 1213 tenantRepo := &automock.TenantMappingRepository{} 1214 tenantRepo.On("Exists", ctx, tenantID).Return(false, nil) 1215 1216 defer mock.AssertExpectationsForObjects(t, tenantRepo, uidSvc, labelRepo, labelUpsertSvc) 1217 1218 svc := tenant.NewServiceWithLabels(tenantRepo, uidSvc, labelRepo, labelUpsertSvc, nil) 1219 1220 _, err := svc.ListLabels(ctx, tenantID) 1221 assert.Error(t, err) 1222 assert.True(t, apperrors.IsNotFoundError(err)) 1223 }) 1224 1225 t.Run("Error when fails to list labels from repo", func(t *testing.T) { 1226 uidSvc := &automock.UIDService{} 1227 labelUpsertSvc := &automock.LabelUpsertService{} 1228 1229 tenantRepo := &automock.TenantMappingRepository{} 1230 tenantRepo.On("Exists", ctx, tenantID).Return(true, nil) 1231 1232 labelRepo := &automock.LabelRepository{} 1233 labelRepo.On("ListForObject", ctx, tenantID, model.TenantLabelableObject, tenantID).Return(nil, testErr) 1234 1235 defer mock.AssertExpectationsForObjects(t, tenantRepo, uidSvc, labelRepo, labelUpsertSvc) 1236 1237 svc := tenant.NewServiceWithLabels(tenantRepo, uidSvc, labelRepo, labelUpsertSvc, nil) 1238 1239 _, err := svc.ListLabels(ctx, tenantID) 1240 assert.Error(t, err) 1241 assert.Contains(t, err.Error(), fmt.Sprintf("whilie listing labels for tenant with ID %s", tenantID)) 1242 }) 1243 } 1244 1245 func Test_GetTenantByExternalID(t *testing.T) { 1246 t.Run("Success", func(t *testing.T) { 1247 // GIVEN 1248 ctx := context.TODO() 1249 expected := &model.BusinessTenantMapping{ 1250 ID: testID, 1251 Name: testName, 1252 ExternalTenant: testExternal, 1253 Status: tenantEntity.Active, 1254 Type: tenantEntity.Account, 1255 } 1256 1257 uidSvc := &automock.UIDService{} 1258 labelUpsertSvc := &automock.LabelUpsertService{} 1259 labelRepo := &automock.LabelRepository{} 1260 1261 tenantRepo := &automock.TenantMappingRepository{} 1262 tenantRepo.On("GetByExternalTenant", ctx, testID).Return(expected, nil) 1263 1264 defer mock.AssertExpectationsForObjects(t, tenantRepo, uidSvc, labelRepo, labelUpsertSvc) 1265 1266 svc := tenant.NewServiceWithLabels(tenantRepo, uidSvc, labelRepo, labelUpsertSvc, nil) 1267 1268 // WHEN 1269 actual, err := svc.GetTenantByExternalID(ctx, testID) 1270 1271 // THEN 1272 assert.NoError(t, err) 1273 assert.Equal(t, expected, actual) 1274 }) 1275 t.Run("Returns error when retrieval from DB fails", func(t *testing.T) { 1276 // GIVEN 1277 ctx := context.TODO() 1278 1279 uidSvc := &automock.UIDService{} 1280 labelUpsertSvc := &automock.LabelUpsertService{} 1281 labelRepo := &automock.LabelRepository{} 1282 1283 tenantRepo := &automock.TenantMappingRepository{} 1284 tenantRepo.On("GetByExternalTenant", ctx, testID).Return(nil, testError) 1285 1286 defer mock.AssertExpectationsForObjects(t, tenantRepo, uidSvc, labelRepo, labelUpsertSvc) 1287 1288 svc := tenant.NewServiceWithLabels(tenantRepo, uidSvc, labelRepo, labelUpsertSvc, nil) 1289 1290 // WHEN 1291 actual, err := svc.GetTenantByExternalID(ctx, testID) 1292 1293 // THEN 1294 assert.Error(t, err) 1295 assert.Nil(t, actual) 1296 assert.Equal(t, testError, err) 1297 }) 1298 } 1299 1300 func TestService_CreateTenantAccessForResource(t *testing.T) { 1301 testCases := []struct { 1302 Name string 1303 ConverterFn func() *automock.BusinessTenantMappingConverter 1304 PersistenceFn func() (*sqlx.DB, testdb.DBMock) 1305 Input *model.TenantAccess 1306 ExpectedErrorMsg string 1307 }{ 1308 { 1309 Name: "Success", 1310 ConverterFn: func() *automock.BusinessTenantMappingConverter { 1311 conv := &automock.BusinessTenantMappingConverter{} 1312 conv.On("TenantAccessToEntity", tenantAccessModel).Return(tenantAccessEntity).Once() 1313 return conv 1314 }, 1315 PersistenceFn: func() (*sqlx.DB, testdb.DBMock) { 1316 db, dbMock := testdb.MockDatabase(t) 1317 1318 dbMock.ExpectExec(regexp.QuoteMeta(`INSERT INTO tenant_applications ( tenant_id, id, owner ) VALUES ( ?, ?, ? ) ON CONFLICT ON CONSTRAINT tenant_applications_pkey DO NOTHING`)). 1319 WithArgs(testInternal, testID, true). 1320 WillReturnResult(sqlmock.NewResult(1, 1)) 1321 return db, dbMock 1322 }, 1323 Input: tenantAccessModel, 1324 }, 1325 { 1326 Name: "Error when resource does not have access table", 1327 Input: invalidTenantAccessModel, 1328 ExpectedErrorMsg: fmt.Sprintf("entity %q does not have access table", invalidResourceType), 1329 }, 1330 { 1331 Name: "Error while creating tenant access", 1332 ConverterFn: func() *automock.BusinessTenantMappingConverter { 1333 conv := &automock.BusinessTenantMappingConverter{} 1334 conv.On("TenantAccessToEntity", tenantAccessModel).Return(tenantAccessEntity).Once() 1335 return conv 1336 }, 1337 Input: tenantAccessModel, 1338 ExpectedErrorMsg: fmt.Sprintf("while creating tenant acccess for resource type %q with ID %q for tenant %q", tenantAccessModel.ResourceType, tenantAccessModel.ResourceID, tenantAccessModel.InternalTenantID), 1339 }, 1340 } 1341 1342 for _, testCase := range testCases { 1343 t.Run(testCase.Name, func(t *testing.T) { 1344 ctx := context.TODO() 1345 converter := unusedConverter() 1346 if testCase.ConverterFn != nil { 1347 converter = testCase.ConverterFn() 1348 } 1349 db, dbMock := unusedDBMock(t) 1350 if testCase.PersistenceFn != nil { 1351 db, dbMock = testCase.PersistenceFn() 1352 } 1353 ctx = persistence.SaveToContext(ctx, db) 1354 1355 svc := tenant.NewService(nil, nil, converter) 1356 1357 // WHEN 1358 err := svc.CreateTenantAccessForResource(ctx, testCase.Input) 1359 1360 if testCase.ExpectedErrorMsg != "" { 1361 require.Error(t, err) 1362 require.Contains(t, err.Error(), testCase.ExpectedErrorMsg) 1363 } else { 1364 require.NoError(t, err) 1365 } 1366 1367 mock.AssertExpectationsForObjects(t, converter) 1368 dbMock.AssertExpectations(t) 1369 }) 1370 } 1371 } 1372 1373 func TestService_CreateTenantAccessForResourceRecursively(t *testing.T) { 1374 testCases := []struct { 1375 Name string 1376 ConverterFn func() *automock.BusinessTenantMappingConverter 1377 PersistenceFn func() (*sqlx.DB, testdb.DBMock) 1378 Input *model.TenantAccess 1379 ExpectedErrorMsg string 1380 }{ 1381 { 1382 Name: "Success", 1383 ConverterFn: func() *automock.BusinessTenantMappingConverter { 1384 conv := &automock.BusinessTenantMappingConverter{} 1385 conv.On("TenantAccessToEntity", tenantAccessModel).Return(tenantAccessEntity).Once() 1386 return conv 1387 }, 1388 PersistenceFn: func() (*sqlx.DB, testdb.DBMock) { 1389 db, dbMock := testdb.MockDatabase(t) 1390 1391 dbMock.ExpectExec(regexp.QuoteMeta(`WITH RECURSIVE parents AS (SELECT t1.id, t1.parent FROM business_tenant_mappings t1 WHERE id = ? UNION ALL SELECT t2.id, t2.parent FROM business_tenant_mappings t2 INNER JOIN parents t on t2.id = t.parent) INSERT INTO tenant_applications ( tenant_id, id, owner ) (SELECT parents.id AS tenant_id, ? as id, ? AS owner FROM parents)`)). 1392 WithArgs(testInternal, testID, true). 1393 WillReturnResult(sqlmock.NewResult(1, 1)) 1394 return db, dbMock 1395 }, 1396 Input: tenantAccessModel, 1397 }, 1398 { 1399 Name: "Error when resource does not have access table", 1400 Input: invalidTenantAccessModel, 1401 ExpectedErrorMsg: fmt.Sprintf("entity %q does not have access table", invalidResourceType), 1402 }, 1403 { 1404 Name: "Error while creating tenant access", 1405 ConverterFn: func() *automock.BusinessTenantMappingConverter { 1406 conv := &automock.BusinessTenantMappingConverter{} 1407 conv.On("TenantAccessToEntity", tenantAccessModel).Return(tenantAccessEntity).Once() 1408 return conv 1409 }, 1410 Input: tenantAccessModel, 1411 ExpectedErrorMsg: fmt.Sprintf("while creating tenant acccess for resource type %q with ID %q for tenant %q", tenantAccessModel.ResourceType, tenantAccessModel.ResourceID, tenantAccessModel.InternalTenantID), 1412 }, 1413 } 1414 1415 for _, testCase := range testCases { 1416 t.Run(testCase.Name, func(t *testing.T) { 1417 ctx := context.TODO() 1418 converter := unusedConverter() 1419 if testCase.ConverterFn != nil { 1420 converter = testCase.ConverterFn() 1421 } 1422 db, dbMock := unusedDBMock(t) 1423 if testCase.PersistenceFn != nil { 1424 db, dbMock = testCase.PersistenceFn() 1425 } 1426 ctx = persistence.SaveToContext(ctx, db) 1427 1428 svc := tenant.NewService(nil, nil, converter) 1429 1430 // WHEN 1431 err := svc.CreateTenantAccessForResourceRecursively(ctx, testCase.Input) 1432 1433 if testCase.ExpectedErrorMsg != "" { 1434 require.Error(t, err) 1435 require.Contains(t, err.Error(), testCase.ExpectedErrorMsg) 1436 } else { 1437 require.NoError(t, err) 1438 } 1439 1440 mock.AssertExpectationsForObjects(t, converter) 1441 dbMock.AssertExpectations(t) 1442 }) 1443 } 1444 } 1445 1446 func TestService_DeleteTenantAccessForResource(t *testing.T) { 1447 testCases := []struct { 1448 Name string 1449 ConverterFn func() *automock.BusinessTenantMappingConverter 1450 PersistenceFn func() (*sqlx.DB, testdb.DBMock) 1451 Input *model.TenantAccess 1452 ExpectedErrorMsg string 1453 }{ 1454 { 1455 Name: "Success", 1456 ConverterFn: func() *automock.BusinessTenantMappingConverter { 1457 conv := &automock.BusinessTenantMappingConverter{} 1458 conv.On("TenantAccessToEntity", tenantAccessModel).Return(tenantAccessEntity).Once() 1459 return conv 1460 }, 1461 PersistenceFn: func() (*sqlx.DB, testdb.DBMock) { 1462 db, dbMock := testdb.MockDatabase(t) 1463 1464 dbMock.ExpectExec(regexp.QuoteMeta(`WITH RECURSIVE parents AS (SELECT t1.id, t1.parent FROM business_tenant_mappings t1 WHERE id = $1 UNION ALL SELECT t2.id, t2.parent FROM business_tenant_mappings t2 INNER JOIN parents t on t2.id = t.parent) DELETE FROM tenant_applications WHERE id IN ($2) AND tenant_id IN (SELECT id FROM parents)`)). 1465 WithArgs(testInternal, testID). 1466 WillReturnResult(sqlmock.NewResult(1, 1)) 1467 return db, dbMock 1468 }, 1469 Input: tenantAccessModel, 1470 }, 1471 { 1472 Name: "Error when resource does not have access table", 1473 Input: invalidTenantAccessModel, 1474 ExpectedErrorMsg: fmt.Sprintf("entity %q does not have access table", invalidResourceType), 1475 }, 1476 { 1477 Name: "Error while deleting tenant access", 1478 ConverterFn: func() *automock.BusinessTenantMappingConverter { 1479 conv := &automock.BusinessTenantMappingConverter{} 1480 conv.On("TenantAccessToEntity", tenantAccessModel).Return(tenantAccessEntity).Once() 1481 return conv 1482 }, 1483 Input: tenantAccessModel, 1484 ExpectedErrorMsg: fmt.Sprintf("while deleting tenant acccess for resource type %q with ID %q for tenant %q", tenantAccessModel.ResourceType, tenantAccessModel.ResourceID, tenantAccessModel.InternalTenantID), 1485 }, 1486 } 1487 1488 for _, testCase := range testCases { 1489 t.Run(testCase.Name, func(t *testing.T) { 1490 ctx := context.TODO() 1491 converter := unusedConverter() 1492 if testCase.ConverterFn != nil { 1493 converter = testCase.ConverterFn() 1494 } 1495 db, dbMock := unusedDBMock(t) 1496 if testCase.PersistenceFn != nil { 1497 db, dbMock = testCase.PersistenceFn() 1498 } 1499 ctx = persistence.SaveToContext(ctx, db) 1500 1501 svc := tenant.NewService(nil, nil, converter) 1502 1503 // WHEN 1504 err := svc.DeleteTenantAccessForResourceRecursively(ctx, testCase.Input) 1505 1506 if testCase.ExpectedErrorMsg != "" { 1507 require.Error(t, err) 1508 require.Contains(t, err.Error(), testCase.ExpectedErrorMsg) 1509 } else { 1510 require.NoError(t, err) 1511 } 1512 1513 mock.AssertExpectationsForObjects(t, converter) 1514 dbMock.AssertExpectations(t) 1515 }) 1516 } 1517 } 1518 1519 func TestService_GetTenantAccessForResource(t *testing.T) { 1520 testCases := []struct { 1521 Name string 1522 ConverterFn func() *automock.BusinessTenantMappingConverter 1523 PersistenceFn func() (*sqlx.DB, testdb.DBMock) 1524 Input *model.TenantAccess 1525 ExpectedErrorMsg string 1526 ExpectedOutput *model.TenantAccess 1527 }{ 1528 { 1529 Name: "Success", 1530 ConverterFn: func() *automock.BusinessTenantMappingConverter { 1531 conv := &automock.BusinessTenantMappingConverter{} 1532 conv.On("TenantAccessFromEntity", tenantAccessEntity).Return(tenantAccessModelWithoutExternalTenant).Once() 1533 return conv 1534 }, 1535 PersistenceFn: func() (*sqlx.DB, testdb.DBMock) { 1536 db, dbMock := testdb.MockDatabase(t) 1537 rows := sqlmock.NewRows(tenantAccessTestTableColumns) 1538 rows.AddRow(testInternal, testID, true) 1539 dbMock.ExpectQuery(regexp.QuoteMeta(`SELECT tenant_id, id, owner FROM tenant_applications WHERE tenant_id = $1 AND id = $2`)). 1540 WithArgs(testInternal, testID).WillReturnRows(rows) 1541 return db, dbMock 1542 }, 1543 Input: tenantAccessModel, 1544 ExpectedOutput: tenantAccessModelWithoutExternalTenant, 1545 }, 1546 { 1547 Name: "Error when resource does not have access table", 1548 Input: invalidTenantAccessModel, 1549 ExpectedErrorMsg: fmt.Sprintf("entity %q does not have access table", invalidResourceType), 1550 }, 1551 { 1552 Name: "Error while getting tenant access", 1553 PersistenceFn: func() (*sqlx.DB, testdb.DBMock) { 1554 db, dbMock := testdb.MockDatabase(t) 1555 dbMock.ExpectQuery(regexp.QuoteMeta(`SELECT tenant_id, id, owner FROM tenant_applications WHERE tenant_id = $1 AND id = $2`)). 1556 WithArgs(testInternal, testID).WillReturnError(testError) 1557 return db, dbMock 1558 }, 1559 Input: tenantAccessModel, 1560 ExpectedErrorMsg: "Unexpected error while executing SQL query", 1561 }, 1562 } 1563 1564 for _, testCase := range testCases { 1565 t.Run(testCase.Name, func(t *testing.T) { 1566 ctx := context.TODO() 1567 converter := unusedConverter() 1568 if testCase.ConverterFn != nil { 1569 converter = testCase.ConverterFn() 1570 } 1571 db, dbMock := unusedDBMock(t) 1572 if testCase.PersistenceFn != nil { 1573 db, dbMock = testCase.PersistenceFn() 1574 } 1575 ctx = persistence.SaveToContext(ctx, db) 1576 1577 svc := tenant.NewService(nil, nil, converter) 1578 1579 // WHEN 1580 result, err := svc.GetTenantAccessForResource(ctx, testCase.Input.InternalTenantID, testCase.Input.ResourceID, testCase.Input.ResourceType) 1581 1582 if testCase.ExpectedErrorMsg != "" { 1583 require.Error(t, err) 1584 require.Contains(t, err.Error(), testCase.ExpectedErrorMsg) 1585 } else { 1586 require.NoError(t, err) 1587 require.Equal(t, testCase.ExpectedOutput, result) 1588 } 1589 1590 mock.AssertExpectationsForObjects(t, converter) 1591 dbMock.AssertExpectations(t) 1592 }) 1593 } 1594 } 1595 1596 type serialUUIDService struct { 1597 i int 1598 } 1599 1600 func (s *serialUUIDService) Generate() string { 1601 result := s.i 1602 s.i++ 1603 return fmt.Sprintf("%d", result) 1604 } 1605 1606 func createRepoSvc(ctx context.Context, createFuncName string, tenants ...model.BusinessTenantMapping) *automock.TenantMappingRepository { 1607 tenantMappingRepo := &automock.TenantMappingRepository{} 1608 for _, t := range tenants { 1609 tenantMappingRepo.On(createFuncName, ctx, t).Return(nil).Once() 1610 tenantMappingRepo.On("GetByExternalTenant", ctx, t.ExternalTenant).Return(&t, nil).Once() 1611 } 1612 return tenantMappingRepo 1613 }