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  }