github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/systemauth/service_test.go (about) 1 package systemauth_test 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 8 "github.com/kyma-incubator/compass/components/director/internal/model" 9 10 pkgmodel "github.com/kyma-incubator/compass/components/director/pkg/model" 11 12 "github.com/kyma-incubator/compass/components/director/pkg/str" 13 14 "github.com/stretchr/testify/mock" 15 16 "github.com/kyma-incubator/compass/components/director/internal/domain/systemauth" 17 18 "github.com/kyma-incubator/compass/components/director/internal/domain/systemauth/automock" 19 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 20 "github.com/pkg/errors" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 ) 24 25 func TestService_Create(t *testing.T) { 26 // GIVEN 27 ctx := tenant.SaveToContext(context.TODO(), testTenant, testExternalTenant) 28 29 sysAuthID := "foo" 30 objID := "bar" 31 32 modelAuthInput := fixModelAuthInput() 33 modelAuth := fixModelAuth() 34 35 uidSvcFn := func() *automock.UIDService { 36 uidSvc := &automock.UIDService{} 37 uidSvc.On("Generate").Return(sysAuthID) 38 return uidSvc 39 } 40 41 testCases := []struct { 42 Name string 43 sysAuthRepoFn func() *automock.Repository 44 InputObjectType pkgmodel.SystemAuthReferenceObjectType 45 InputAuth *model.AuthInput 46 ExpectedOutput string 47 ExpectedError error 48 }{ 49 { 50 Name: "Success creating auth for Runtime", 51 sysAuthRepoFn: func() *automock.Repository { 52 sysAuthRepo := &automock.Repository{} 53 sysAuthRepo.On("Create", contextThatHasTenant(testTenant), *fixModelSystemAuth(sysAuthID, pkgmodel.RuntimeReference, objID, modelAuth)).Return(nil) 54 return sysAuthRepo 55 }, 56 InputObjectType: pkgmodel.RuntimeReference, 57 InputAuth: &modelAuthInput, 58 ExpectedOutput: sysAuthID, 59 ExpectedError: nil, 60 }, 61 { 62 Name: "Success creating auth for Application", 63 sysAuthRepoFn: func() *automock.Repository { 64 sysAuthRepo := &automock.Repository{} 65 sysAuthRepo.On("Create", contextThatHasTenant(testTenant), *fixModelSystemAuth(sysAuthID, pkgmodel.ApplicationReference, objID, modelAuth)).Return(nil) 66 return sysAuthRepo 67 }, 68 InputObjectType: pkgmodel.ApplicationReference, 69 InputAuth: &modelAuthInput, 70 ExpectedOutput: sysAuthID, 71 ExpectedError: nil, 72 }, 73 { 74 Name: "Success creating auth for Integration System", 75 sysAuthRepoFn: func() *automock.Repository { 76 sysAuthRepo := &automock.Repository{} 77 sysAuthRepo.On("Create", contextThatHasTenant(testTenant), *fixModelSystemAuth(sysAuthID, pkgmodel.IntegrationSystemReference, objID, modelAuth)).Return(nil) 78 return sysAuthRepo 79 }, 80 InputObjectType: pkgmodel.IntegrationSystemReference, 81 InputAuth: &modelAuthInput, 82 ExpectedOutput: sysAuthID, 83 ExpectedError: nil, 84 }, 85 { 86 Name: "Success creating auth with nil value", 87 sysAuthRepoFn: func() *automock.Repository { 88 sysAuthRepo := &automock.Repository{} 89 sysAuthRepo.On("Create", contextThatHasTenant(testTenant), *fixModelSystemAuth(sysAuthID, pkgmodel.RuntimeReference, objID, nil)).Return(nil) 90 return sysAuthRepo 91 }, 92 InputObjectType: pkgmodel.RuntimeReference, 93 InputAuth: nil, 94 ExpectedOutput: sysAuthID, 95 ExpectedError: nil, 96 }, 97 { 98 Name: "Error creating auth for unknown object type", 99 sysAuthRepoFn: func() *automock.Repository { 100 sysAuthRepo := &automock.Repository{} 101 return sysAuthRepo 102 }, 103 InputObjectType: "unknown", 104 InputAuth: &modelAuthInput, 105 ExpectedOutput: "", 106 ExpectedError: errors.New("unknown reference object type"), 107 }, 108 { 109 Name: "Error creating System Auth", 110 sysAuthRepoFn: func() *automock.Repository { 111 sysAuthRepo := &automock.Repository{} 112 sysAuthRepo.On("Create", contextThatHasTenant(testTenant), *fixModelSystemAuth(sysAuthID, pkgmodel.RuntimeReference, objID, modelAuth)).Return(testErr) 113 return sysAuthRepo 114 }, 115 InputObjectType: pkgmodel.RuntimeReference, 116 InputAuth: &modelAuthInput, 117 ExpectedOutput: "", 118 ExpectedError: testErr, 119 }, 120 } 121 122 for _, testCase := range testCases { 123 t.Run(testCase.Name, func(t *testing.T) { 124 sysAuthRepo := testCase.sysAuthRepoFn() 125 uidSvc := uidSvcFn() 126 svc := systemauth.NewService(sysAuthRepo, uidSvc) 127 128 // WHEN 129 result, err := svc.Create(ctx, testCase.InputObjectType, objID, testCase.InputAuth) 130 131 // THEN 132 if testCase.ExpectedError != nil { 133 require.Error(t, err) 134 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 135 } else { 136 assert.NoError(t, err) 137 } 138 assert.Equal(t, testCase.ExpectedOutput, result) 139 140 sysAuthRepo.AssertExpectations(t) 141 uidSvc.AssertExpectations(t) 142 }) 143 } 144 145 t.Run("Error when tenant not in context", func(t *testing.T) { 146 uidSvc := uidSvcFn() 147 defer uidSvc.AssertExpectations(t) 148 svc := systemauth.NewService(nil, uidSvc) 149 150 // WHEN 151 _, err := svc.Create(context.TODO(), "", "", nil) 152 153 // THEN 154 require.Error(t, err) 155 assert.Contains(t, err.Error(), "cannot read tenant from context") 156 }) 157 } 158 159 // Just happy path, as it is the same as Create method 160 func TestService_CreateWithCustomID(t *testing.T) { 161 // GIVEN 162 ctx := tenant.SaveToContext(context.TODO(), testTenant, testExternalTenant) 163 164 sysAuthID := "bla" 165 objID := "bar" 166 167 modelAuthInput := fixModelAuthInput() 168 modelAuth := fixModelAuth() 169 170 sysAuthRepo := &automock.Repository{} 171 sysAuthRepo.On("Create", contextThatHasTenant(testTenant), *fixModelSystemAuth(sysAuthID, pkgmodel.RuntimeReference, objID, modelAuth)).Return(nil) 172 defer sysAuthRepo.AssertExpectations(t) 173 174 svc := systemauth.NewService(sysAuthRepo, nil) 175 176 // WHEN 177 result, err := svc.CreateWithCustomID(ctx, sysAuthID, pkgmodel.RuntimeReference, objID, &modelAuthInput) 178 179 // THEN 180 assert.NoError(t, err) 181 assert.Equal(t, sysAuthID, result) 182 } 183 184 func TestService_ListForObject(t *testing.T) { 185 // GIVEN 186 ctx := tenant.SaveToContext(context.TODO(), testTenant, testExternalTenant) 187 188 objID := "bar" 189 190 modelAuth := fixModelAuth() 191 192 expectedRtmSysAuths := []pkgmodel.SystemAuth{ 193 { 194 ID: "foo", 195 TenantID: &testTenant, 196 RuntimeID: str.Ptr("bar"), 197 Value: modelAuth, 198 }, 199 { 200 ID: "foo2", 201 TenantID: &testTenant, 202 RuntimeID: str.Ptr("bar2"), 203 Value: modelAuth, 204 }, 205 } 206 expectedAppSysAuths := []pkgmodel.SystemAuth{ 207 { 208 ID: "foo", 209 TenantID: &testTenant, 210 AppID: str.Ptr("bar"), 211 Value: modelAuth, 212 }, 213 { 214 ID: "foo2", 215 TenantID: &testTenant, 216 AppID: str.Ptr("bar2"), 217 Value: modelAuth, 218 }, 219 } 220 expectedIntSysAuths := []pkgmodel.SystemAuth{ 221 { 222 ID: "foo", 223 TenantID: nil, 224 IntegrationSystemID: str.Ptr("bar"), 225 Value: modelAuth, 226 }, 227 { 228 ID: "foo2", 229 TenantID: nil, 230 IntegrationSystemID: str.Ptr("bar2"), 231 Value: modelAuth, 232 }, 233 } 234 235 testCases := []struct { 236 Name string 237 sysAuthRepoFn func() *automock.Repository 238 InputObjectType pkgmodel.SystemAuthReferenceObjectType 239 ExpectedOutput []pkgmodel.SystemAuth 240 ExpectedError error 241 }{ 242 { 243 Name: "Success listing Auths for Runtime", 244 sysAuthRepoFn: func() *automock.Repository { 245 sysAuthRepo := &automock.Repository{} 246 sysAuthRepo.On("ListForObject", contextThatHasTenant(testTenant), testTenant, pkgmodel.RuntimeReference, objID).Return(expectedRtmSysAuths, nil) 247 return sysAuthRepo 248 }, 249 InputObjectType: pkgmodel.RuntimeReference, 250 ExpectedOutput: expectedRtmSysAuths, 251 ExpectedError: nil, 252 }, 253 { 254 Name: "Success listing Auths for Application", 255 sysAuthRepoFn: func() *automock.Repository { 256 sysAuthRepo := &automock.Repository{} 257 sysAuthRepo.On("ListForObject", contextThatHasTenant(testTenant), testTenant, pkgmodel.ApplicationReference, objID).Return(expectedAppSysAuths, nil) 258 return sysAuthRepo 259 }, 260 InputObjectType: pkgmodel.ApplicationReference, 261 ExpectedOutput: expectedAppSysAuths, 262 ExpectedError: nil, 263 }, 264 { 265 Name: "Success listing Auths for Integration System", 266 sysAuthRepoFn: func() *automock.Repository { 267 sysAuthRepo := &automock.Repository{} 268 sysAuthRepo.On("ListForObjectGlobal", contextThatHasTenant(testTenant), pkgmodel.IntegrationSystemReference, objID).Return(expectedIntSysAuths, nil) 269 return sysAuthRepo 270 }, 271 InputObjectType: pkgmodel.IntegrationSystemReference, 272 ExpectedOutput: expectedIntSysAuths, 273 ExpectedError: nil, 274 }, 275 { 276 Name: "Error listing System Auths", 277 sysAuthRepoFn: func() *automock.Repository { 278 sysAuthRepo := &automock.Repository{} 279 sysAuthRepo.On("ListForObject", contextThatHasTenant(testTenant), testTenant, pkgmodel.RuntimeReference, objID).Return(nil, testErr) 280 return sysAuthRepo 281 }, 282 InputObjectType: pkgmodel.RuntimeReference, 283 ExpectedOutput: nil, 284 ExpectedError: testErr, 285 }, 286 } 287 288 for _, testCase := range testCases { 289 t.Run(testCase.Name, func(t *testing.T) { 290 sysAuthRepo := testCase.sysAuthRepoFn() 291 svc := systemauth.NewService(sysAuthRepo, nil) 292 293 // WHEN 294 result, err := svc.ListForObject(ctx, testCase.InputObjectType, objID) 295 296 // THEN 297 if testCase.ExpectedError != nil { 298 require.Error(t, err) 299 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 300 } else { 301 assert.NoError(t, err) 302 } 303 assert.Equal(t, testCase.ExpectedOutput, result) 304 305 sysAuthRepo.AssertExpectations(t) 306 }) 307 } 308 309 t.Run("Error when tenant not in context", func(t *testing.T) { 310 svc := systemauth.NewService(nil, nil) 311 312 // WHEN 313 _, err := svc.ListForObject(context.TODO(), "", "") 314 315 // THEN 316 require.Error(t, err) 317 assert.Contains(t, err.Error(), "cannot read tenant from context") 318 }) 319 } 320 321 func TestService_GetByIDForObject(t *testing.T) { 322 // GIVEN 323 ctx := tenant.SaveToContext(context.TODO(), testTenant, testExternalTenant) 324 325 sysAuthID := "foo" 326 modelSysAuth := fixModelSystemAuth(sysAuthID, pkgmodel.RuntimeReference, "bar", nil) 327 328 testCases := []struct { 329 Name string 330 sysAuthRepoFn func() *automock.Repository 331 InputObjectType pkgmodel.SystemAuthReferenceObjectType 332 ExpectedSysAuth *pkgmodel.SystemAuth 333 ExpectedError error 334 }{ 335 { 336 Name: "Success getting auth for Runtime", 337 sysAuthRepoFn: func() *automock.Repository { 338 sysAuthRepo := &automock.Repository{} 339 sysAuthRepo.On("GetByIDForObject", contextThatHasTenant(testTenant), testTenant, sysAuthID, pkgmodel.RuntimeReference).Return(modelSysAuth, nil) 340 return sysAuthRepo 341 }, 342 InputObjectType: pkgmodel.RuntimeReference, 343 ExpectedError: nil, 344 ExpectedSysAuth: modelSysAuth, 345 }, 346 { 347 Name: "Success getting auth for Application", 348 sysAuthRepoFn: func() *automock.Repository { 349 sysAuthRepo := &automock.Repository{} 350 sysAuthRepo.On("GetByIDForObject", contextThatHasTenant(testTenant), testTenant, sysAuthID, pkgmodel.ApplicationReference).Return(modelSysAuth, nil) 351 return sysAuthRepo 352 }, 353 InputObjectType: pkgmodel.ApplicationReference, 354 ExpectedError: nil, 355 ExpectedSysAuth: modelSysAuth, 356 }, 357 { 358 Name: "Success getting auth for Integration System", 359 sysAuthRepoFn: func() *automock.Repository { 360 sysAuthRepo := &automock.Repository{} 361 sysAuthRepo.On("GetByIDForObjectGlobal", contextThatHasTenant(testTenant), sysAuthID, pkgmodel.IntegrationSystemReference).Return(modelSysAuth, nil) 362 return sysAuthRepo 363 }, 364 InputObjectType: pkgmodel.IntegrationSystemReference, 365 ExpectedError: nil, 366 ExpectedSysAuth: modelSysAuth, 367 }, 368 { 369 Name: "Error getting System Auths", 370 sysAuthRepoFn: func() *automock.Repository { 371 sysAuthRepo := &automock.Repository{} 372 sysAuthRepo.On("GetByIDForObject", contextThatHasTenant(testTenant), testTenant, sysAuthID, pkgmodel.RuntimeReference).Return(nil, testErr) 373 return sysAuthRepo 374 }, 375 InputObjectType: pkgmodel.RuntimeReference, 376 ExpectedError: testErr, 377 }, 378 } 379 380 for _, testCase := range testCases { 381 t.Run(testCase.Name, func(t *testing.T) { 382 sysAuthRepo := testCase.sysAuthRepoFn() 383 svc := systemauth.NewService(sysAuthRepo, nil) 384 385 // WHEN 386 sysAuth, err := svc.GetByIDForObject(ctx, testCase.InputObjectType, sysAuthID) 387 388 // THEN 389 if testCase.ExpectedError != nil { 390 require.Error(t, err) 391 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 392 } else { 393 assert.NoError(t, err) 394 assert.Equal(t, testCase.ExpectedSysAuth, sysAuth) 395 } 396 397 sysAuthRepo.AssertExpectations(t) 398 }) 399 } 400 401 t.Run("Error when tenant not in context", func(t *testing.T) { 402 svc := systemauth.NewService(nil, nil) 403 404 // WHEN 405 err := svc.DeleteByIDForObject(context.TODO(), "", "") 406 407 // THEN 408 require.Error(t, err) 409 assert.Contains(t, err.Error(), "cannot read tenant from context") 410 }) 411 } 412 413 func TestService_DeleteByIDForObject(t *testing.T) { 414 // GIVEN 415 ctx := tenant.SaveToContext(context.TODO(), testTenant, testExternalTenant) 416 sysAuthID := "foo" 417 418 testCases := []struct { 419 Name string 420 sysAuthRepoFn func() *automock.Repository 421 InputObjectType pkgmodel.SystemAuthReferenceObjectType 422 ExpectedError error 423 }{ 424 { 425 Name: "Success deleting auth for Runtime", 426 sysAuthRepoFn: func() *automock.Repository { 427 sysAuthRepo := &automock.Repository{} 428 sysAuthRepo.On("DeleteByIDForObject", contextThatHasTenant(testTenant), testTenant, sysAuthID, pkgmodel.RuntimeReference).Return(nil) 429 return sysAuthRepo 430 }, 431 InputObjectType: pkgmodel.RuntimeReference, 432 ExpectedError: nil, 433 }, 434 { 435 Name: "Success deleting auth for Application", 436 sysAuthRepoFn: func() *automock.Repository { 437 sysAuthRepo := &automock.Repository{} 438 sysAuthRepo.On("DeleteByIDForObject", contextThatHasTenant(testTenant), testTenant, sysAuthID, pkgmodel.ApplicationReference).Return(nil) 439 return sysAuthRepo 440 }, 441 InputObjectType: pkgmodel.ApplicationReference, 442 ExpectedError: nil, 443 }, 444 { 445 Name: "Success deleting auth for Integration System", 446 sysAuthRepoFn: func() *automock.Repository { 447 sysAuthRepo := &automock.Repository{} 448 sysAuthRepo.On("DeleteByIDForObjectGlobal", contextThatHasTenant(testTenant), sysAuthID, pkgmodel.IntegrationSystemReference).Return(nil) 449 return sysAuthRepo 450 }, 451 InputObjectType: pkgmodel.IntegrationSystemReference, 452 ExpectedError: nil, 453 }, 454 { 455 Name: "Error deleting System Auths", 456 sysAuthRepoFn: func() *automock.Repository { 457 sysAuthRepo := &automock.Repository{} 458 sysAuthRepo.On("DeleteByIDForObject", contextThatHasTenant(testTenant), testTenant, sysAuthID, pkgmodel.RuntimeReference).Return(testErr) 459 return sysAuthRepo 460 }, 461 InputObjectType: pkgmodel.RuntimeReference, 462 ExpectedError: testErr, 463 }, 464 } 465 466 for _, testCase := range testCases { 467 t.Run(testCase.Name, func(t *testing.T) { 468 sysAuthRepo := testCase.sysAuthRepoFn() 469 svc := systemauth.NewService(sysAuthRepo, nil) 470 471 // WHEN 472 err := svc.DeleteByIDForObject(ctx, testCase.InputObjectType, sysAuthID) 473 474 // THEN 475 if testCase.ExpectedError != nil { 476 require.Error(t, err) 477 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 478 } else { 479 assert.NoError(t, err) 480 } 481 482 sysAuthRepo.AssertExpectations(t) 483 }) 484 } 485 486 t.Run("Error when tenant not in context", func(t *testing.T) { 487 svc := systemauth.NewService(nil, nil) 488 489 // WHEN 490 err := svc.DeleteByIDForObject(context.TODO(), "", "") 491 492 // THEN 493 require.Error(t, err) 494 assert.Contains(t, err.Error(), "cannot read tenant from context") 495 }) 496 } 497 498 func TestService_GetGlobal(t *testing.T) { 499 authID := "authID" 500 501 t.Run("success when systemAuth can be fetched from repo", func(t *testing.T) { 502 // GIVEN 503 repo := &automock.Repository{} 504 defer repo.AssertExpectations(t) 505 repo.On("GetByIDGlobal", context.Background(), authID).Return(&pkgmodel.SystemAuth{}, nil) 506 svc := systemauth.NewService(repo, nil) 507 // WHEN 508 item, err := svc.GetGlobal(context.Background(), authID) 509 // THEN 510 assert.Nil(t, err) 511 assert.Equal(t, &pkgmodel.SystemAuth{}, item) 512 }) 513 514 t.Run("error when systemAuth cannot be fetched from repo", func(t *testing.T) { 515 // GIVEN 516 repo := &automock.Repository{} 517 defer repo.AssertExpectations(t) 518 repo.On("GetByIDGlobal", context.Background(), authID).Return(nil, errors.New("could not fetch")) 519 svc := systemauth.NewService(repo, nil) 520 // WHEN 521 item, err := svc.GetGlobal(context.Background(), authID) 522 // THEN 523 assert.Nil(t, item) 524 assert.Error(t, err, fmt.Sprintf("while getting SystemAuth with ID %s could not fetch", authID)) 525 }) 526 } 527 528 func TestService_GetByToken(t *testing.T) { 529 token := "YWJj" 530 input := map[string]interface{}{ 531 "OneTimeToken": map[string]interface{}{ 532 "Token": token, 533 "Used": false, 534 }} 535 536 t.Run("success when systemAuth can be fetched from repo", func(t *testing.T) { 537 // GIVEN 538 repo := &automock.Repository{} 539 defer repo.AssertExpectations(t) 540 repo.On("GetByJSONValue", context.Background(), input).Return(&pkgmodel.SystemAuth{}, nil) 541 svc := systemauth.NewService(repo, nil) 542 // WHEN 543 item, err := svc.GetByToken(context.Background(), token) 544 // THEN 545 assert.Nil(t, err) 546 assert.Equal(t, &pkgmodel.SystemAuth{}, item) 547 }) 548 549 t.Run("error when systemAuth cannot be fetched from repo", func(t *testing.T) { 550 // GIVEN 551 repo := &automock.Repository{} 552 defer repo.AssertExpectations(t) 553 repo.On("GetByJSONValue", context.Background(), input).Return(nil, errors.New("err")) 554 svc := systemauth.NewService(repo, nil) 555 // WHEN 556 item, err := svc.GetByToken(context.Background(), token) 557 // THEN 558 assert.Error(t, err) 559 assert.Nil(t, item) 560 }) 561 } 562 563 func TestService_UpdateValue(t *testing.T) { 564 authID := "authID" 565 modelAuth := fixModelAuth() 566 567 t.Run("error when systemAuth cannot be fetched from repo", func(t *testing.T) { 568 // GIVEN 569 repo := &automock.Repository{} 570 defer repo.AssertExpectations(t) 571 repo.On("GetByIDGlobal", context.Background(), authID).Return(nil, errors.New("could not fetch")) 572 svc := systemauth.NewService(repo, nil) 573 // WHEN 574 item, err := svc.UpdateValue(context.Background(), authID, modelAuth) 575 // THEN 576 assert.Nil(t, item) 577 assert.Error(t, err, fmt.Sprintf("while getting SystemAuth with ID %s could not fetch", authID)) 578 }) 579 580 t.Run("Error when systemAuth cannot be updated", func(t *testing.T) { 581 // GIVEN 582 repo := &automock.Repository{} 583 defer repo.AssertExpectations(t) 584 repo.On("GetByIDGlobal", context.Background(), authID).Return(&pkgmodel.SystemAuth{}, nil) 585 repo.On("Update", context.Background(), mock.Anything).Return(errors.New("could not update")) 586 svc := systemauth.NewService(repo, nil) 587 // WHEN 588 item, err := svc.UpdateValue(context.Background(), authID, modelAuth) 589 // THEN 590 assert.Nil(t, item) 591 assert.Error(t, err, fmt.Sprintf("while getting SystemAuth with ID %s could not update", authID)) 592 }) 593 594 t.Run("Success", func(t *testing.T) { 595 // GIVEN 596 repo := &automock.Repository{} 597 defer repo.AssertExpectations(t) 598 sysAuth := &pkgmodel.SystemAuth{ 599 Value: modelAuth, 600 } 601 repo.On("GetByIDGlobal", context.Background(), authID).Return(sysAuth, nil) 602 repo.On("Update", context.Background(), sysAuth).Return(nil) 603 svc := systemauth.NewService(repo, nil) 604 // WHEN 605 item, err := svc.UpdateValue(context.Background(), authID, modelAuth) 606 607 // THEN 608 assert.Nil(t, err) 609 assert.Equal(t, sysAuth, item) 610 }) 611 } 612 613 func contextThatHasTenant(expectedTenant string) interface{} { 614 return mock.MatchedBy(func(actual context.Context) bool { 615 actualTenant, err := tenant.LoadFromContext(actual) 616 if err != nil { 617 return false 618 } 619 return actualTenant == expectedTenant 620 }) 621 }