github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/formation/notifications_test.go (about) 1 package formation_test 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/kyma-incubator/compass/components/director/pkg/formationconstraint" 8 9 "github.com/stretchr/testify/assert" 10 11 "github.com/kyma-incubator/compass/components/director/internal/domain/formation" 12 "github.com/kyma-incubator/compass/components/director/internal/domain/formation/automock" 13 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 14 "github.com/kyma-incubator/compass/components/director/internal/model" 15 "github.com/kyma-incubator/compass/components/director/pkg/graphql" 16 "github.com/kyma-incubator/compass/components/director/pkg/webhook" 17 webhookclient "github.com/kyma-incubator/compass/components/director/pkg/webhook_client" 18 "github.com/stretchr/testify/mock" 19 "github.com/stretchr/testify/require" 20 ) 21 22 func Test_NotificationsService_GenerateNotifications(t *testing.T) { 23 ctx := context.TODO() 24 ctx = tenant.SaveToContext(ctx, TntInternalID, TntExternalID) 25 26 inputFormation := &model.Formation{ 27 ID: fixUUID(), 28 Name: testFormationName, 29 FormationTemplateID: FormationTemplateID, 30 TenantID: TntInternalID, 31 } 32 33 testCases := []struct { 34 Name string 35 TenantRepoFn func() *automock.TenantRepository 36 NotificationsGenerator func() *automock.NotificationsGenerator 37 ObjectID string 38 ObjectType graphql.FormationObjectType 39 OperationType model.FormationOperation 40 InputFormation *model.Formation 41 ExpectedRequests []*webhookclient.FormationAssignmentNotificationRequest 42 ExpectedErrMessage string 43 }{ 44 // start testing 'generateNotificationsAboutApplicationsForTheRuntimeThatIsAssigned' and 'generateNotificationsForApplicationsAboutTheRuntimeThatIsAssigned' funcs 45 { 46 Name: "success for runtime", 47 TenantRepoFn: func() *automock.TenantRepository { 48 repo := &automock.TenantRepository{} 49 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 50 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 51 return repo 52 }, 53 NotificationsGenerator: func() *automock.NotificationsGenerator { 54 generator := &automock.NotificationsGenerator{} 55 56 generator.On("GenerateNotificationsForApplicationsAboutTheRuntimeThatIsAssigned", ctx, TntInternalID, RuntimeID, inputFormation, model.AssignFormation, customerTenantContext).Return( 57 []*webhookclient.FormationAssignmentNotificationRequest{ 58 applicationNotificationWithAppTemplate, 59 applicationNotificationWithoutAppTemplate, 60 }, nil).Once() 61 62 generator.On("GenerateNotificationsAboutApplicationsForTheRuntimeThatIsAssigned", ctx, TntInternalID, RuntimeID, inputFormation, model.AssignFormation, customerTenantContext).Return( 63 []*webhookclient.FormationAssignmentNotificationRequest{ 64 runtimeNotificationWithAppTemplate, 65 runtimeNotificationWithoutAppTemplate, 66 }, nil).Once() 67 68 return generator 69 }, 70 ObjectType: graphql.FormationObjectTypeRuntime, 71 OperationType: model.AssignFormation, 72 ObjectID: RuntimeID, 73 InputFormation: inputFormation, 74 ExpectedRequests: []*webhookclient.FormationAssignmentNotificationRequest{ 75 runtimeNotificationWithAppTemplate, 76 runtimeNotificationWithoutAppTemplate, 77 applicationNotificationWithAppTemplate, 78 applicationNotificationWithoutAppTemplate, 79 }, 80 ExpectedErrMessage: "", 81 }, 82 { 83 Name: "success for runtime when customer tenant context is resource group", 84 TenantRepoFn: func() *automock.TenantRepository { 85 repo := &automock.TenantRepository{} 86 repo.On("Get", ctx, inputFormation.TenantID).Return(rgTenantObject, nil) 87 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 88 return repo 89 }, 90 NotificationsGenerator: func() *automock.NotificationsGenerator { 91 generator := &automock.NotificationsGenerator{} 92 93 generator.On("GenerateNotificationsForApplicationsAboutTheRuntimeThatIsAssigned", ctx, TntInternalID, RuntimeID, inputFormation, model.AssignFormation, rgCustomerTenantContext).Return( 94 []*webhookclient.FormationAssignmentNotificationRequest{ 95 applicationNotificationWithAppTemplate, 96 applicationNotificationWithoutAppTemplate, 97 }, nil).Once() 98 99 generator.On("GenerateNotificationsAboutApplicationsForTheRuntimeThatIsAssigned", ctx, TntInternalID, RuntimeID, inputFormation, model.AssignFormation, rgCustomerTenantContext).Return( 100 []*webhookclient.FormationAssignmentNotificationRequest{ 101 runtimeNotificationWithAppTemplate, 102 runtimeNotificationWithoutAppTemplate, 103 }, nil).Once() 104 105 return generator 106 }, 107 ObjectType: graphql.FormationObjectTypeRuntime, 108 OperationType: model.AssignFormation, 109 ObjectID: RuntimeID, 110 InputFormation: inputFormation, 111 ExpectedRequests: []*webhookclient.FormationAssignmentNotificationRequest{ 112 runtimeNotificationWithAppTemplate, 113 runtimeNotificationWithoutAppTemplate, 114 applicationNotificationWithAppTemplate, 115 applicationNotificationWithoutAppTemplate, 116 }, 117 ExpectedErrMessage: "", 118 }, 119 { 120 Name: "error for runtime - when generating notifications for runtime", 121 TenantRepoFn: func() *automock.TenantRepository { 122 repo := &automock.TenantRepository{} 123 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 124 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 125 return repo 126 }, 127 NotificationsGenerator: func() *automock.NotificationsGenerator { 128 generator := &automock.NotificationsGenerator{} 129 130 generator.On("GenerateNotificationsForApplicationsAboutTheRuntimeThatIsAssigned", ctx, TntInternalID, RuntimeID, inputFormation, model.AssignFormation, customerTenantContext).Return( 131 []*webhookclient.FormationAssignmentNotificationRequest{ 132 applicationNotificationWithAppTemplate, 133 applicationNotificationWithoutAppTemplate, 134 }, nil).Once() 135 136 generator.On("GenerateNotificationsAboutApplicationsForTheRuntimeThatIsAssigned", ctx, TntInternalID, RuntimeID, inputFormation, model.AssignFormation, customerTenantContext).Return(nil, testErr).Once() 137 138 return generator 139 }, 140 ObjectType: graphql.FormationObjectTypeRuntime, 141 OperationType: model.AssignFormation, 142 ObjectID: RuntimeID, 143 InputFormation: inputFormation, 144 ExpectedErrMessage: testErr.Error(), 145 }, 146 { 147 Name: "error for runtime - when generating notifications for application", 148 TenantRepoFn: func() *automock.TenantRepository { 149 repo := &automock.TenantRepository{} 150 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 151 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 152 return repo 153 }, 154 NotificationsGenerator: func() *automock.NotificationsGenerator { 155 generator := &automock.NotificationsGenerator{} 156 157 generator.On("GenerateNotificationsForApplicationsAboutTheRuntimeThatIsAssigned", ctx, TntInternalID, RuntimeID, inputFormation, model.AssignFormation, customerTenantContext).Return(nil, testErr).Once() 158 159 return generator 160 }, 161 ObjectType: graphql.FormationObjectTypeRuntime, 162 OperationType: model.AssignFormation, 163 ObjectID: RuntimeID, 164 InputFormation: inputFormation, 165 ExpectedErrMessage: testErr.Error(), 166 }, 167 // start testing 'generateNotificationsForApplicationsAboutTheRuntimeContextThatIsAssigned' and 'generateNotificationsAboutApplicationsForTheRuntimeContextThatIsAssigned' funcs 168 { 169 Name: "success for runtime context", 170 TenantRepoFn: func() *automock.TenantRepository { 171 repo := &automock.TenantRepository{} 172 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 173 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 174 return repo 175 }, 176 NotificationsGenerator: func() *automock.NotificationsGenerator { 177 generator := &automock.NotificationsGenerator{} 178 179 generator.On("GenerateNotificationsForApplicationsAboutTheRuntimeContextThatIsAssigned", ctx, TntInternalID, RuntimeContextID, inputFormation, model.AssignFormation, customerTenantContext).Return( 180 []*webhookclient.FormationAssignmentNotificationRequest{ 181 appNotificationWithRtmCtxAndTemplate, 182 appNotificationWithRtmCtxWithoutTemplate, 183 }, nil).Once() 184 185 generator.On("GenerateNotificationsAboutApplicationsForTheRuntimeContextThatIsAssigned", ctx, TntInternalID, RuntimeContextID, inputFormation, model.AssignFormation, customerTenantContext).Return( 186 []*webhookclient.FormationAssignmentNotificationRequest{ 187 runtimeCtxNotificationWithAppTemplate, 188 runtimeCtxNotificationWithoutAppTemplate, 189 }, nil).Once() 190 191 return generator 192 }, 193 ObjectType: graphql.FormationObjectTypeRuntimeContext, 194 OperationType: model.AssignFormation, 195 ObjectID: RuntimeContextID, 196 ExpectedRequests: []*webhookclient.FormationAssignmentNotificationRequest{ 197 runtimeCtxNotificationWithAppTemplate, 198 runtimeCtxNotificationWithoutAppTemplate, 199 appNotificationWithRtmCtxAndTemplate, 200 appNotificationWithRtmCtxWithoutTemplate, 201 }, 202 InputFormation: inputFormation, 203 ExpectedErrMessage: "", 204 }, 205 { 206 Name: "error for runtime context - when generating notifications for runtime context", 207 TenantRepoFn: func() *automock.TenantRepository { 208 repo := &automock.TenantRepository{} 209 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 210 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 211 return repo 212 }, 213 NotificationsGenerator: func() *automock.NotificationsGenerator { 214 generator := &automock.NotificationsGenerator{} 215 216 generator.On("GenerateNotificationsForApplicationsAboutTheRuntimeContextThatIsAssigned", ctx, TntInternalID, RuntimeContextID, inputFormation, model.AssignFormation, customerTenantContext).Return( 217 []*webhookclient.FormationAssignmentNotificationRequest{ 218 appNotificationWithRtmCtxAndTemplate, 219 appNotificationWithRtmCtxWithoutTemplate, 220 }, nil).Once() 221 222 generator.On("GenerateNotificationsAboutApplicationsForTheRuntimeContextThatIsAssigned", ctx, TntInternalID, RuntimeContextID, inputFormation, model.AssignFormation, customerTenantContext).Return(nil, testErr).Once() 223 224 return generator 225 }, 226 ObjectType: graphql.FormationObjectTypeRuntimeContext, 227 OperationType: model.AssignFormation, 228 ObjectID: RuntimeContextID, 229 InputFormation: inputFormation, 230 ExpectedErrMessage: testErr.Error(), 231 }, 232 { 233 Name: "error for runtime context - when generating notifications for applications", 234 TenantRepoFn: func() *automock.TenantRepository { 235 repo := &automock.TenantRepository{} 236 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 237 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 238 return repo 239 }, 240 NotificationsGenerator: func() *automock.NotificationsGenerator { 241 generator := &automock.NotificationsGenerator{} 242 243 generator.On("GenerateNotificationsForApplicationsAboutTheRuntimeContextThatIsAssigned", ctx, TntInternalID, RuntimeContextID, inputFormation, model.AssignFormation, customerTenantContext).Return(nil, testErr).Once() 244 245 return generator 246 }, 247 ObjectType: graphql.FormationObjectTypeRuntimeContext, 248 OperationType: model.AssignFormation, 249 ObjectID: RuntimeContextID, 250 InputFormation: inputFormation, 251 ExpectedErrMessage: testErr.Error(), 252 }, 253 // start testing 'generateRuntimeNotificationsForApplicationAssignment' and 'generateApplicationNotificationsForApplicationAssignment' funcs 254 { 255 Name: "success for application", 256 TenantRepoFn: func() *automock.TenantRepository { 257 repo := &automock.TenantRepository{} 258 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 259 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 260 return repo 261 }, 262 NotificationsGenerator: func() *automock.NotificationsGenerator { 263 generator := &automock.NotificationsGenerator{} 264 265 generator.On("GenerateNotificationsAboutRuntimeAndRuntimeContextForTheApplicationThatIsAssigned", ctx, TntInternalID, ApplicationID, inputFormation, model.AssignFormation, customerTenantContext).Return( 266 []*webhookclient.FormationAssignmentNotificationRequest{ 267 appNotificationWithRtmCtxRtmIDAndTemplate, 268 appNotificationWithRtmCtxAndTemplate, 269 }, nil).Once() 270 271 generator.On("GenerateNotificationsForRuntimeAboutTheApplicationThatIsAssigned", ctx, TntInternalID, ApplicationID, inputFormation, model.AssignFormation, customerTenantContext).Return( 272 []*webhookclient.FormationAssignmentNotificationRequest{ 273 runtimeNotificationWithAppTemplate, 274 runtimeNotificationWithRtmCtxAndAppTemplate, 275 }, nil).Once() 276 277 generator.On("GenerateNotificationsForApplicationsAboutTheApplicationThatIsAssigned", ctx, TntInternalID, ApplicationID, inputFormation, model.AssignFormation, customerTenantContext).Return( 278 []*webhookclient.FormationAssignmentNotificationRequest{ 279 appToAppNotificationWithoutSourceTemplateWithTargetTemplate, 280 appToAppNotificationWithSourceTemplateWithoutTargetTemplate, 281 }, nil).Once() 282 283 return generator 284 }, 285 ExpectedRequests: []*webhookclient.FormationAssignmentNotificationRequest{ 286 runtimeNotificationWithAppTemplate, 287 runtimeNotificationWithRtmCtxAndAppTemplate, 288 appNotificationWithRtmCtxRtmIDAndTemplate, 289 appNotificationWithRtmCtxAndTemplate, 290 appToAppNotificationWithoutSourceTemplateWithTargetTemplate, 291 appToAppNotificationWithSourceTemplateWithoutTargetTemplate, 292 }, 293 ObjectType: graphql.FormationObjectTypeApplication, 294 OperationType: model.AssignFormation, 295 ObjectID: ApplicationID, 296 InputFormation: inputFormation, 297 ExpectedErrMessage: "", 298 }, 299 { 300 Name: "error for application - when generating app to app notifications", 301 TenantRepoFn: func() *automock.TenantRepository { 302 repo := &automock.TenantRepository{} 303 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 304 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 305 return repo 306 }, 307 NotificationsGenerator: func() *automock.NotificationsGenerator { 308 generator := &automock.NotificationsGenerator{} 309 310 generator.On("GenerateNotificationsAboutRuntimeAndRuntimeContextForTheApplicationThatIsAssigned", ctx, TntInternalID, ApplicationID, inputFormation, model.AssignFormation, customerTenantContext).Return( 311 []*webhookclient.FormationAssignmentNotificationRequest{ 312 appNotificationWithRtmCtxRtmIDAndTemplate, 313 appNotificationWithRtmCtxAndTemplate, 314 }, nil).Once() 315 316 generator.On("GenerateNotificationsForRuntimeAboutTheApplicationThatIsAssigned", ctx, TntInternalID, ApplicationID, inputFormation, model.AssignFormation, customerTenantContext).Return( 317 []*webhookclient.FormationAssignmentNotificationRequest{ 318 runtimeNotificationWithAppTemplate, 319 runtimeNotificationWithRtmCtxAndAppTemplate, 320 }, nil).Once() 321 322 generator.On("GenerateNotificationsForApplicationsAboutTheApplicationThatIsAssigned", ctx, TntInternalID, ApplicationID, inputFormation, model.AssignFormation, customerTenantContext).Return(nil, testErr).Once() 323 324 return generator 325 }, 326 ObjectType: graphql.FormationObjectTypeApplication, 327 OperationType: model.AssignFormation, 328 ObjectID: ApplicationID, 329 InputFormation: inputFormation, 330 ExpectedErrMessage: testErr.Error(), 331 }, 332 { 333 Name: "error for application - when generating notifications for runtimes", 334 TenantRepoFn: func() *automock.TenantRepository { 335 repo := &automock.TenantRepository{} 336 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 337 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 338 return repo 339 }, 340 NotificationsGenerator: func() *automock.NotificationsGenerator { 341 generator := &automock.NotificationsGenerator{} 342 343 generator.On("GenerateNotificationsAboutRuntimeAndRuntimeContextForTheApplicationThatIsAssigned", ctx, TntInternalID, ApplicationID, inputFormation, model.AssignFormation, customerTenantContext).Return( 344 []*webhookclient.FormationAssignmentNotificationRequest{ 345 appNotificationWithRtmCtxRtmIDAndTemplate, 346 appNotificationWithRtmCtxAndTemplate, 347 }, nil).Once() 348 349 generator.On("GenerateNotificationsForRuntimeAboutTheApplicationThatIsAssigned", ctx, TntInternalID, ApplicationID, inputFormation, model.AssignFormation, customerTenantContext).Return(nil, testErr).Once() 350 351 return generator 352 }, 353 ObjectType: graphql.FormationObjectTypeApplication, 354 OperationType: model.AssignFormation, 355 ObjectID: ApplicationID, 356 InputFormation: inputFormation, 357 ExpectedErrMessage: testErr.Error(), 358 }, 359 { 360 Name: "error for application - when generating notifications for applications about runtimes and runtime contexts", 361 TenantRepoFn: func() *automock.TenantRepository { 362 repo := &automock.TenantRepository{} 363 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 364 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 365 return repo 366 }, 367 NotificationsGenerator: func() *automock.NotificationsGenerator { 368 generator := &automock.NotificationsGenerator{} 369 370 generator.On("GenerateNotificationsAboutRuntimeAndRuntimeContextForTheApplicationThatIsAssigned", ctx, TntInternalID, ApplicationID, inputFormation, model.AssignFormation, customerTenantContext).Return(nil, testErr).Once() 371 372 return generator 373 }, 374 ObjectType: graphql.FormationObjectTypeApplication, 375 OperationType: model.AssignFormation, 376 ObjectID: ApplicationID, 377 InputFormation: inputFormation, 378 ExpectedErrMessage: testErr.Error(), 379 }, 380 { 381 Name: "error when getting customer parent", 382 TenantRepoFn: func() *automock.TenantRepository { 383 repo := &automock.TenantRepository{} 384 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 385 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return("", testErr) 386 return repo 387 }, 388 ObjectType: graphql.FormationObjectTypeApplication, 389 OperationType: model.AssignFormation, 390 ObjectID: ApplicationID, 391 InputFormation: inputFormation, 392 ExpectedErrMessage: testErr.Error(), 393 }, 394 { 395 Name: "error when getting tenant", 396 TenantRepoFn: func() *automock.TenantRepository { 397 repo := &automock.TenantRepository{} 398 repo.On("Get", ctx, inputFormation.TenantID).Return(nil, testErr) 399 return repo 400 }, 401 ObjectType: graphql.FormationObjectTypeApplication, 402 OperationType: model.AssignFormation, 403 ObjectID: ApplicationID, 404 InputFormation: inputFormation, 405 ExpectedErrMessage: testErr.Error(), 406 }, 407 { 408 Name: "error when unknown formation object type", 409 TenantRepoFn: func() *automock.TenantRepository { 410 repo := &automock.TenantRepository{} 411 repo.On("Get", ctx, inputFormation.TenantID).Return(gaTenantObject, nil) 412 repo.On("GetCustomerIDParentRecursively", ctx, inputFormation.TenantID).Return(TntParentID, nil) 413 return repo 414 }, 415 ObjectType: graphql.FormationObjectTypeTenant, 416 OperationType: model.AssignFormation, 417 ObjectID: ApplicationID, 418 InputFormation: inputFormation, 419 ExpectedErrMessage: "unknown formation type", 420 }, 421 } 422 423 for _, testCase := range testCases { 424 t.Run(testCase.Name, func(t *testing.T) { 425 // GIVEN 426 tenantRepo := unusedTenantRepo() 427 if testCase.TenantRepoFn() != nil { 428 tenantRepo = testCase.TenantRepoFn() 429 } 430 431 notificationsGenerator := unusedNotificationsGenerator() 432 if testCase.NotificationsGenerator != nil { 433 notificationsGenerator = testCase.NotificationsGenerator() 434 } 435 436 notificationSvc := formation.NewNotificationService(tenantRepo, nil, notificationsGenerator, nil, nil, nil) 437 438 // WHEN 439 actual, err := notificationSvc.GenerateFormationAssignmentNotifications(ctx, TntInternalID, testCase.ObjectID, testCase.InputFormation, testCase.OperationType, testCase.ObjectType) 440 441 // THEN 442 if testCase.ExpectedErrMessage == "" { 443 require.NoError(t, err) 444 assert.ElementsMatch(t, testCase.ExpectedRequests, actual) 445 } else { 446 require.Error(t, err) 447 require.Contains(t, err.Error(), testCase.ExpectedErrMessage) 448 require.Nil(t, actual) 449 } 450 451 mock.AssertExpectationsForObjects(t, tenantRepo, notificationsGenerator) 452 }) 453 } 454 } 455 456 func Test_NotificationService_GenerateFormationNotifications(t *testing.T) { 457 ctx := context.Background() 458 formationInput := fixFormationModelWithoutError() 459 460 testCases := []struct { 461 name string 462 tenantRepoFn func() *automock.TenantRepository 463 notificationsGeneratorFn func() *automock.NotificationsGenerator 464 expectedErrMsg string 465 expectedFormationNotificationReqs []*webhookclient.FormationNotificationRequest 466 }{ 467 { 468 name: "Successfully generate formation notifications", 469 tenantRepoFn: func() *automock.TenantRepository { 470 tenantRepo := &automock.TenantRepository{} 471 tenantRepo.On("Get", ctx, TntInternalID).Return(gaTenantObject, nil).Once() 472 tenantRepo.On("GetCustomerIDParentRecursively", ctx, TntInternalID).Return(TntCustomerID, nil).Once() 473 return tenantRepo 474 }, 475 notificationsGeneratorFn: func() *automock.NotificationsGenerator { 476 notificationGenerator := &automock.NotificationsGenerator{} 477 notificationGenerator.On("GenerateFormationLifecycleNotifications", ctx, formationLifecycleSyncWebhooks, TntInternalID, formationInput, testFormationTemplateName, FormationTemplateID, model.CreateFormation, CustomerTenantContextAccount).Return(formationNotificationSyncCreateRequests, nil).Once() 478 return notificationGenerator 479 }, 480 expectedFormationNotificationReqs: formationNotificationSyncCreateRequests, 481 }, 482 { 483 name: "Error when extracting customer tenant context fails", 484 tenantRepoFn: func() *automock.TenantRepository { 485 repo := &automock.TenantRepository{} 486 repo.On("Get", ctx, TntInternalID).Return(nil, testErr) 487 return repo 488 }, 489 expectedErrMsg: testErr.Error(), 490 }, 491 { 492 name: "Error when generating formation lifecycle notifications fail", 493 tenantRepoFn: func() *automock.TenantRepository { 494 tenantRepo := &automock.TenantRepository{} 495 tenantRepo.On("Get", ctx, TntInternalID).Return(gaTenantObject, nil).Once() 496 tenantRepo.On("GetCustomerIDParentRecursively", ctx, TntInternalID).Return(TntCustomerID, nil).Once() 497 return tenantRepo 498 }, 499 notificationsGeneratorFn: func() *automock.NotificationsGenerator { 500 notificationGenerator := &automock.NotificationsGenerator{} 501 notificationGenerator.On("GenerateFormationLifecycleNotifications", ctx, formationLifecycleSyncWebhooks, TntInternalID, formationInput, testFormationTemplateName, FormationTemplateID, model.CreateFormation, CustomerTenantContextAccount).Return(nil, testErr).Once() 502 return notificationGenerator 503 }, 504 expectedErrMsg: testErr.Error(), 505 }, 506 } 507 508 for _, testCase := range testCases { 509 t.Run(testCase.name, func(t *testing.T) { 510 tenantRepo := unusedTenantRepo() 511 if testCase.tenantRepoFn != nil { 512 tenantRepo = testCase.tenantRepoFn() 513 } 514 515 notificationGenerator := unusedNotificationsGenerator() 516 if testCase.notificationsGeneratorFn != nil { 517 notificationGenerator = testCase.notificationsGeneratorFn() 518 } 519 520 defer mock.AssertExpectationsForObjects(t, tenantRepo, notificationGenerator) 521 522 notificationSvc := formation.NewNotificationService(tenantRepo, nil, notificationGenerator, nil, nil, nil) 523 524 formationNotificationReqs, err := notificationSvc.GenerateFormationNotifications(ctx, formationLifecycleSyncWebhooks, TntInternalID, formationInput, testFormationTemplateName, FormationTemplateID, model.CreateFormation) 525 526 if testCase.expectedErrMsg != "" { 527 require.Error(t, err) 528 require.Contains(t, err.Error(), testCase.expectedErrMsg) 529 require.Empty(t, formationNotificationReqs) 530 } else { 531 require.NoError(t, err) 532 require.ElementsMatch(t, formationNotificationReqs, testCase.expectedFormationNotificationReqs) 533 } 534 }) 535 } 536 } 537 538 func Test_NotificationsService_SendNotification(t *testing.T) { 539 ctx := context.TODO() 540 ctx = tenant.SaveToContext(ctx, TntInternalID, TntExternalID) 541 542 subtype := "subtype" 543 544 fa := fixFormationAssignmentModelWithParameters("id1", FormationID, RuntimeID, ApplicationID, model.FormationAssignmentTypeRuntime, model.FormationAssignmentTypeApplication, model.InitialFormationState) 545 reverseFa := fixFormationAssignmentModelWithParameters("id2", FormationID, ApplicationID, RuntimeID, model.FormationAssignmentTypeApplication, model.FormationAssignmentTypeRuntime, model.InitialFormationState) 546 547 templateInput := &webhook.FormationConfigurationChangeInput{ 548 Operation: model.AssignFormation, 549 FormationID: FormationID, 550 ApplicationTemplate: &webhook.ApplicationTemplateWithLabels{ 551 ApplicationTemplate: fixApplicationTemplateModel(), 552 Labels: fixApplicationTemplateLabelsMap(), 553 }, 554 Application: &webhook.ApplicationWithLabels{ 555 Application: fixApplicationModel(ApplicationID), 556 Labels: fixApplicationLabelsMap(), 557 }, 558 Runtime: fixRuntimeWithLabels(RuntimeID), 559 RuntimeContext: nil, 560 CustomerTenantContext: fixCustomerTenantContext(TntParentID, TntExternalID), 561 Assignment: emptyFormationAssignment, 562 ReverseAssignment: emptyFormationAssignment, 563 } 564 565 preJoinPointDetails := &formationconstraint.SendNotificationOperationDetails{ 566 ResourceType: model.ApplicationResourceType, 567 ResourceSubtype: subtype, 568 Location: formationconstraint.PreSendNotification, 569 Operation: model.AssignFormation, 570 Webhook: fixRuntimeWebhookModel(WebhookID, RuntimeID), 571 CorrelationID: "", 572 TemplateInput: templateInput, 573 FormationAssignment: fa, 574 ReverseFormationAssignment: reverseFa, 575 Formation: formationModel, 576 } 577 postJoinPointDetails := &formationconstraint.SendNotificationOperationDetails{ 578 ResourceType: model.ApplicationResourceType, 579 ResourceSubtype: subtype, 580 Location: formationconstraint.PostSendNotification, 581 Operation: model.AssignFormation, 582 Webhook: fixRuntimeWebhookModel(WebhookID, RuntimeID), 583 CorrelationID: "", 584 TemplateInput: templateInput, 585 FormationAssignment: fa, 586 ReverseFormationAssignment: reverseFa, 587 Formation: formationModel, 588 } 589 590 faRequestExt := &webhookclient.FormationAssignmentNotificationRequestExt{ 591 FormationAssignmentNotificationRequest: &webhookclient.FormationAssignmentNotificationRequest{ 592 Webhook: *fixRuntimeWebhookGQLModel(WebhookID, RuntimeID), 593 Object: templateInput, 594 CorrelationID: "", 595 }, 596 Operation: model.AssignFormation, 597 FormationAssignment: fa, 598 ReverseFormationAssignment: reverseFa, 599 Formation: formationModel, 600 TargetSubtype: subtype, 601 } 602 603 testCases := []struct { 604 Name string 605 WebhookClientFN func() *automock.WebhookClient 606 ConstraintEngine func() *automock.ConstraintEngine 607 WebhookConverter func() *automock.WebhookConverter 608 InputRequest *webhookclient.FormationAssignmentNotificationRequestExt 609 ExpectedErrMessage string 610 }{ 611 { 612 Name: "success when webhook client call doesn't return error", 613 WebhookClientFN: func() *automock.WebhookClient { 614 client := &automock.WebhookClient{} 615 client.On("Do", ctx, faRequestExt).Return(nil, nil) 616 return client 617 }, 618 ConstraintEngine: func() *automock.ConstraintEngine { 619 engine := &automock.ConstraintEngine{} 620 engine.On("EnforceConstraints", ctx, formationconstraint.PreSendNotification, preJoinPointDetails, FormationTemplateID).Return(nil).Once() 621 engine.On("EnforceConstraints", ctx, formationconstraint.PostSendNotification, postJoinPointDetails, FormationTemplateID).Return(nil).Once() 622 return engine 623 }, 624 WebhookConverter: func() *automock.WebhookConverter { 625 conv := &automock.WebhookConverter{} 626 conv.On("ToModel", fixRuntimeWebhookGQLModel(WebhookID, RuntimeID)).Return(fixRuntimeWebhookModel(WebhookID, RuntimeID), nil).Once() 627 return conv 628 }, 629 InputRequest: faRequestExt, 630 }, 631 { 632 Name: "fail when webhook client call fails", 633 WebhookClientFN: func() *automock.WebhookClient { 634 client := &automock.WebhookClient{} 635 client.On("Do", ctx, faRequestExt).Return(nil, testErr) 636 return client 637 }, 638 ConstraintEngine: func() *automock.ConstraintEngine { 639 engine := &automock.ConstraintEngine{} 640 engine.On("EnforceConstraints", ctx, formationconstraint.PreSendNotification, preJoinPointDetails, FormationTemplateID).Return(nil).Once() 641 return engine 642 }, 643 WebhookConverter: func() *automock.WebhookConverter { 644 conv := &automock.WebhookConverter{} 645 conv.On("ToModel", fixRuntimeWebhookGQLModel(WebhookID, RuntimeID)).Return(fixRuntimeWebhookModel(WebhookID, RuntimeID), nil).Once() 646 return conv 647 }, 648 InputRequest: faRequestExt, 649 ExpectedErrMessage: testErr.Error(), 650 }, 651 { 652 Name: "fail when enforcing POST constraints returns error", 653 WebhookClientFN: func() *automock.WebhookClient { 654 client := &automock.WebhookClient{} 655 client.On("Do", ctx, faRequestExt).Return(nil, nil) 656 return client 657 }, 658 ConstraintEngine: func() *automock.ConstraintEngine { 659 engine := &automock.ConstraintEngine{} 660 engine.On("EnforceConstraints", ctx, formationconstraint.PreSendNotification, preJoinPointDetails, FormationTemplateID).Return(nil).Once() 661 engine.On("EnforceConstraints", ctx, formationconstraint.PostSendNotification, postJoinPointDetails, FormationTemplateID).Return(testErr).Once() 662 return engine 663 }, 664 WebhookConverter: func() *automock.WebhookConverter { 665 conv := &automock.WebhookConverter{} 666 conv.On("ToModel", fixRuntimeWebhookGQLModel(WebhookID, RuntimeID)).Return(fixRuntimeWebhookModel(WebhookID, RuntimeID), nil).Once() 667 return conv 668 }, 669 InputRequest: faRequestExt, 670 ExpectedErrMessage: testErr.Error(), 671 }, 672 { 673 Name: "fail when enforcing PRE constraints returns error", 674 ConstraintEngine: func() *automock.ConstraintEngine { 675 engine := &automock.ConstraintEngine{} 676 engine.On("EnforceConstraints", ctx, formationconstraint.PreSendNotification, preJoinPointDetails, FormationTemplateID).Return(testErr).Once() 677 return engine 678 }, 679 WebhookConverter: func() *automock.WebhookConverter { 680 conv := &automock.WebhookConverter{} 681 conv.On("ToModel", fixRuntimeWebhookGQLModel(WebhookID, RuntimeID)).Return(fixRuntimeWebhookModel(WebhookID, RuntimeID), nil).Once() 682 return conv 683 }, 684 InputRequest: faRequestExt, 685 ExpectedErrMessage: testErr.Error(), 686 }, 687 { 688 Name: "fail when converting gql webhook to model returns error", 689 WebhookConverter: func() *automock.WebhookConverter { 690 conv := &automock.WebhookConverter{} 691 conv.On("ToModel", fixRuntimeWebhookGQLModel(WebhookID, RuntimeID)).Return(nil, testErr).Once() 692 return conv 693 }, 694 InputRequest: faRequestExt, 695 ExpectedErrMessage: testErr.Error(), 696 }, 697 } 698 699 for _, testCase := range testCases { 700 t.Run(testCase.Name, func(t *testing.T) { 701 webhookClient := unusedWebhookClient() 702 if testCase.WebhookClientFN != nil { 703 webhookClient = testCase.WebhookClientFN() 704 } 705 constraintEngine := &automock.ConstraintEngine{} 706 if testCase.ConstraintEngine != nil { 707 constraintEngine = testCase.ConstraintEngine() 708 } 709 webhookConverter := &automock.WebhookConverter{} 710 if testCase.WebhookConverter != nil { 711 webhookConverter = testCase.WebhookConverter() 712 } 713 714 notificationSvc := formation.NewNotificationService(nil, webhookClient, nil, constraintEngine, webhookConverter, nil) 715 716 // WHEN 717 _, err := notificationSvc.SendNotification(ctx, testCase.InputRequest) 718 719 // THEN 720 if testCase.ExpectedErrMessage == "" { 721 require.NoError(t, err) 722 } else { 723 require.Error(t, err) 724 require.Contains(t, err.Error(), testCase.ExpectedErrMessage) 725 } 726 mock.AssertExpectationsForObjects(t, webhookClient, constraintEngine, webhookConverter) 727 }) 728 } 729 }