github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/formation/resolver_test.go (about)

     1  package formation_test
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"testing"
     8  
     9  	dataloader "github.com/kyma-incubator/compass/components/director/internal/dataloaders"
    10  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    11  
    12  	"github.com/kyma-incubator/compass/components/director/pkg/pagination"
    13  	persistenceautomock "github.com/kyma-incubator/compass/components/director/pkg/persistence/automock"
    14  
    15  	"github.com/kyma-incubator/compass/components/director/internal/domain/formation"
    16  	"github.com/kyma-incubator/compass/components/director/internal/domain/formation/automock"
    17  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    18  	"github.com/kyma-incubator/compass/components/director/internal/model"
    19  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    20  	"github.com/kyma-incubator/compass/components/director/pkg/graphql"
    21  	"github.com/kyma-incubator/compass/components/director/pkg/persistence/txtest"
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/mock"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  func TestCreateFormation(t *testing.T) {
    28  	formationInput := graphql.FormationInput{
    29  		Name: testFormationName,
    30  	}
    31  
    32  	testTemplateName := "test-template-name"
    33  	formationInputWithTemplateName := graphql.FormationInput{
    34  		Name:         testFormationName,
    35  		TemplateName: &testTemplateName,
    36  	}
    37  
    38  	tnt := "tenant"
    39  	externalTnt := "external-tenant"
    40  	testErr := errors.New("test error")
    41  	txGen := txtest.NewTransactionContextGenerator(testErr)
    42  
    43  	t.Run("successfully created formation with provided template name", func(t *testing.T) {
    44  		// GIVEN
    45  		persist, transact := txGen.ThatSucceeds()
    46  
    47  		mockService := &automock.Service{}
    48  		mockConverter := &automock.Converter{}
    49  		mockService.On("CreateFormation", contextThatHasTenant(tnt), tnt, modelFormation, testTemplateName).Return(&modelFormation, nil)
    50  
    51  		mockConverter.On("FromGraphQL", formationInputWithTemplateName).Return(modelFormation)
    52  		mockConverter.On("ToGraphQL", &modelFormation).Return(&graphqlFormation, nil)
    53  
    54  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
    55  		sut := formation.NewResolver(transact, mockService, mockConverter, nil, nil, nil)
    56  
    57  		// WHEN
    58  		actual, err := sut.CreateFormation(ctx, formationInputWithTemplateName)
    59  
    60  		// THEN
    61  		require.NoError(t, err)
    62  		assert.Equal(t, testFormationName, actual.Name)
    63  		mock.AssertExpectationsForObjects(t, persist, transact, mockService, mockConverter)
    64  	})
    65  
    66  	t.Run("successfully created formation when no template is provided", func(t *testing.T) {
    67  		// GIVEN
    68  		persist, transact := txGen.ThatSucceeds()
    69  
    70  		mockService := &automock.Service{}
    71  		mockConverter := &automock.Converter{}
    72  		mockService.On("CreateFormation", contextThatHasTenant(tnt), tnt, modelFormation, model.DefaultTemplateName).Return(&modelFormation, nil)
    73  
    74  		mockConverter.On("FromGraphQL", formationInput).Return(modelFormation)
    75  		mockConverter.On("ToGraphQL", &modelFormation).Return(&graphqlFormation, nil)
    76  
    77  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
    78  		sut := formation.NewResolver(transact, mockService, mockConverter, nil, nil, nil)
    79  
    80  		// WHEN
    81  		actual, err := sut.CreateFormation(ctx, formationInput)
    82  
    83  		// THEN
    84  		require.NoError(t, err)
    85  		assert.Equal(t, testFormationName, actual.Name)
    86  		mock.AssertExpectationsForObjects(t, persist, transact, mockService, mockConverter)
    87  	})
    88  
    89  	t.Run("returns error when can not load tenant from context", func(t *testing.T) {
    90  		// GIVEN
    91  		ctx := context.Background()
    92  
    93  		sut := formation.NewResolver(nil, nil, nil, nil, nil, nil)
    94  
    95  		// WHEN
    96  		_, err := sut.CreateFormation(ctx, formationInput)
    97  
    98  		// THEN
    99  		require.Error(t, err)
   100  		assert.Contains(t, err.Error(), apperrors.NewCannotReadTenantError().Error())
   101  	})
   102  
   103  	t.Run("returns error when can not start db transaction", func(t *testing.T) {
   104  		// GIVEN
   105  		persist, transact := txGen.ThatFailsOnBegin()
   106  
   107  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   108  		sut := formation.NewResolver(transact, nil, nil, nil, nil, nil)
   109  
   110  		// WHEN
   111  		_, err := sut.CreateFormation(ctx, formationInput)
   112  
   113  		// THEN
   114  		require.Error(t, err)
   115  		assert.Contains(t, err.Error(), testErr.Error())
   116  		mock.AssertExpectationsForObjects(t, persist, transact)
   117  	})
   118  
   119  	t.Run("returns error when commit fails", func(t *testing.T) {
   120  		// GIVEN
   121  		persist, transact := txGen.ThatFailsOnCommit()
   122  
   123  		mockService := &automock.Service{}
   124  		mockService.On("CreateFormation", contextThatHasTenant(tnt), tnt, modelFormation, model.DefaultTemplateName).Return(&modelFormation, nil)
   125  
   126  		mockConverter := &automock.Converter{}
   127  		mockConverter.On("FromGraphQL", formationInput).Return(modelFormation)
   128  
   129  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   130  		sut := formation.NewResolver(transact, mockService, mockConverter, nil, nil, nil)
   131  
   132  		// WHEN
   133  		_, err := sut.CreateFormation(ctx, formationInput)
   134  
   135  		// THEN
   136  		require.Error(t, err)
   137  		assert.Contains(t, err.Error(), testErr.Error())
   138  		mock.AssertExpectationsForObjects(t, persist, transact, mockService, mockConverter)
   139  	})
   140  
   141  	t.Run("returns error when create formation fails", func(t *testing.T) {
   142  		// GIVEN
   143  		persist, transact := txGen.ThatDoesntExpectCommit()
   144  
   145  		mockService := &automock.Service{}
   146  		mockService.On("CreateFormation", contextThatHasTenant(tnt), tnt, modelFormation, model.DefaultTemplateName).Return(nil, testErr)
   147  
   148  		mockConverter := &automock.Converter{}
   149  		mockConverter.On("FromGraphQL", formationInput).Return(modelFormation)
   150  
   151  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   152  		sut := formation.NewResolver(transact, mockService, mockConverter, nil, nil, nil)
   153  
   154  		// WHEN
   155  		actual, err := sut.CreateFormation(ctx, formationInput)
   156  
   157  		// THEN
   158  		require.Error(t, err)
   159  		assert.Contains(t, err.Error(), testErr.Error())
   160  		require.Nil(t, actual)
   161  		mock.AssertExpectationsForObjects(t, persist, transact, mockService, mockConverter)
   162  	})
   163  }
   164  
   165  func TestDeleteFormation(t *testing.T) {
   166  	testFormation := testFormationName
   167  	formationInput := graphql.FormationInput{
   168  		Name: testFormation,
   169  	}
   170  	tnt := "tenant"
   171  	externalTnt := "external-tenant"
   172  	testErr := errors.New("test error")
   173  	txGen := txtest.NewTransactionContextGenerator(testErr)
   174  
   175  	t.Run("successfully delete formation", func(t *testing.T) {
   176  		// GIVEN
   177  		persist, transact := txGen.ThatSucceeds()
   178  
   179  		mockService := &automock.Service{}
   180  		mockService.On("DeleteFormation", contextThatHasTenant(tnt), tnt, model.Formation{Name: testFormation}).Return(&model.Formation{Name: testFormation}, nil)
   181  
   182  		mockConverter := &automock.Converter{}
   183  		mockConverter.On("FromGraphQL", formationInput).Return(model.Formation{Name: testFormation})
   184  		mockConverter.On("ToGraphQL", &model.Formation{Name: testFormation}).Return(&graphql.Formation{Name: testFormation}, nil)
   185  
   186  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   187  		sut := formation.NewResolver(transact, mockService, mockConverter, nil, nil, nil)
   188  
   189  		// WHEN
   190  		actual, err := sut.DeleteFormation(ctx, formationInput)
   191  
   192  		// THEN
   193  		require.NoError(t, err)
   194  		assert.Equal(t, testFormation, actual.Name)
   195  		mock.AssertExpectationsForObjects(t, persist, transact, mockService, mockConverter)
   196  	})
   197  	t.Run("returns error when can not load tenant from context", func(t *testing.T) {
   198  		// GIVEN
   199  		ctx := context.Background()
   200  
   201  		sut := formation.NewResolver(nil, nil, nil, nil, nil, nil)
   202  
   203  		// WHEN
   204  		_, err := sut.DeleteFormation(ctx, formationInput)
   205  
   206  		// THEN
   207  		require.Error(t, err)
   208  	})
   209  	t.Run("returns error when can not start db transaction", func(t *testing.T) {
   210  		// GIVEN
   211  		persist, transact := txGen.ThatFailsOnBegin()
   212  
   213  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   214  		sut := formation.NewResolver(transact, nil, nil, nil, nil, nil)
   215  
   216  		// WHEN
   217  		_, err := sut.DeleteFormation(ctx, formationInput)
   218  
   219  		// THEN
   220  		require.Error(t, err)
   221  		assert.Contains(t, err.Error(), testErr.Error())
   222  		mock.AssertExpectationsForObjects(t, persist, transact)
   223  	})
   224  	t.Run("returns error when commit fails", func(t *testing.T) {
   225  		// GIVEN
   226  		persist, transact := txGen.ThatFailsOnCommit()
   227  
   228  		mockService := &automock.Service{}
   229  		mockService.On("DeleteFormation", contextThatHasTenant(tnt), tnt, model.Formation{Name: testFormation}).Return(&model.Formation{Name: testFormation}, nil)
   230  
   231  		mockConverter := &automock.Converter{}
   232  		mockConverter.On("FromGraphQL", formationInput).Return(model.Formation{Name: testFormation})
   233  
   234  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   235  		sut := formation.NewResolver(transact, mockService, mockConverter, nil, nil, nil)
   236  
   237  		// WHEN
   238  		_, err := sut.DeleteFormation(ctx, formationInput)
   239  
   240  		// THEN
   241  		require.Error(t, err)
   242  		assert.Contains(t, err.Error(), testErr.Error())
   243  		mock.AssertExpectationsForObjects(t, persist, transact, mockService, mockConverter)
   244  	})
   245  	t.Run("returns error when create formation fails", func(t *testing.T) {
   246  		// GIVEN
   247  		persist, transact := txGen.ThatDoesntExpectCommit()
   248  
   249  		mockService := &automock.Service{}
   250  		mockService.On("DeleteFormation", contextThatHasTenant(tnt), tnt, model.Formation{Name: testFormation}).Return(nil, testErr)
   251  
   252  		mockConverter := &automock.Converter{}
   253  		mockConverter.On("FromGraphQL", formationInput).Return(model.Formation{Name: testFormation})
   254  
   255  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   256  		sut := formation.NewResolver(transact, mockService, mockConverter, nil, nil, nil)
   257  
   258  		// WHEN
   259  		actual, err := sut.DeleteFormation(ctx, formationInput)
   260  
   261  		// THEN
   262  		require.Error(t, err)
   263  		assert.Contains(t, err.Error(), testErr.Error())
   264  		require.Nil(t, actual)
   265  		mock.AssertExpectationsForObjects(t, persist, transact, mockService, mockConverter)
   266  	})
   267  }
   268  
   269  func TestAssignFormation(t *testing.T) {
   270  	formationInput := graphql.FormationInput{
   271  		Name: testFormationName,
   272  	}
   273  	tnt := "tenant"
   274  	externalTnt := "external-tenant"
   275  	testObjectType := graphql.FormationObjectTypeTenant
   276  	testErr := errors.New("test error")
   277  	txGen := txtest.NewTransactionContextGenerator(testErr)
   278  
   279  	t.Run("successfully assigned formation", func(t *testing.T) {
   280  		// GIVEN
   281  		persist, transact := txGen.ThatSucceeds()
   282  
   283  		mockService := &automock.Service{}
   284  		mockConverter := &automock.Converter{}
   285  		fetcherSvc := &automock.TenantFetcher{}
   286  		mockService.On("AssignFormation", contextThatHasTenant(tnt), tnt, "", testObjectType, modelFormation).Return(&modelFormation, nil)
   287  
   288  		mockConverter.On("FromGraphQL", formationInput).Return(modelFormation)
   289  		mockConverter.On("ToGraphQL", &modelFormation).Return(&graphqlFormation, nil)
   290  
   291  		fetcherSvc.On("FetchOnDemand", "", tnt).Return(nil)
   292  
   293  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   294  		sut := formation.NewResolver(transact, mockService, mockConverter, nil, nil, fetcherSvc)
   295  
   296  		// WHEN
   297  		actual, err := sut.AssignFormation(ctx, "", testObjectType, formationInput)
   298  
   299  		// THEN
   300  		require.NoError(t, err)
   301  		assert.Equal(t, testFormationName, actual.Name)
   302  		mock.AssertExpectationsForObjects(t, persist, transact, mockService, mockConverter, fetcherSvc)
   303  	})
   304  	t.Run("returns error when objectType is tenant and cannot fetch its details", func(t *testing.T) {
   305  		// GIVEN
   306  		persist, transact := txGen.ThatDoesntStartTransaction()
   307  
   308  		mockService := &automock.Service{}
   309  		mockConverter := &automock.Converter{}
   310  		fetcherSvc := &automock.TenantFetcher{}
   311  
   312  		fetcherSvc.On("FetchOnDemand", "", tnt).Return(testErr)
   313  
   314  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   315  		sut := formation.NewResolver(transact, mockService, mockConverter, nil, nil, fetcherSvc)
   316  
   317  		// WHEN
   318  		_, err := sut.AssignFormation(ctx, "", testObjectType, formationInput)
   319  
   320  		// THEN
   321  		require.Error(t, err)
   322  		assert.Contains(t, err.Error(), testErr.Error())
   323  		mock.AssertExpectationsForObjects(t, persist, transact, mockService, mockConverter, fetcherSvc)
   324  	})
   325  	t.Run("returns error when can not load tenant from context", func(t *testing.T) {
   326  		// GIVEN
   327  		ctx := context.Background()
   328  
   329  		sut := formation.NewResolver(nil, nil, nil, nil, nil, nil)
   330  
   331  		// WHEN
   332  		_, err := sut.AssignFormation(ctx, "", testObjectType, formationInput)
   333  
   334  		// THEN
   335  		require.Error(t, err)
   336  		assert.Contains(t, err.Error(), apperrors.NewCannotReadTenantError().Error())
   337  	})
   338  	t.Run("returns error when can not start db transaction", func(t *testing.T) {
   339  		// GIVEN
   340  		persist, transact := txGen.ThatFailsOnBegin()
   341  
   342  		fetcherSvc := &automock.TenantFetcher{}
   343  		fetcherSvc.On("FetchOnDemand", "", tnt).Return(nil)
   344  
   345  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   346  		sut := formation.NewResolver(transact, nil, nil, nil, nil, fetcherSvc)
   347  
   348  		// WHEN
   349  		_, err := sut.AssignFormation(ctx, "", testObjectType, formationInput)
   350  
   351  		// THEN
   352  		require.Error(t, err)
   353  		assert.Contains(t, err.Error(), testErr.Error())
   354  		mock.AssertExpectationsForObjects(t, persist, transact, fetcherSvc)
   355  	})
   356  	t.Run("returns error when commit fails", func(t *testing.T) {
   357  		// GIVEN
   358  		persist, transact := txGen.ThatFailsOnCommit()
   359  
   360  		mockService := &automock.Service{}
   361  		mockService.On("AssignFormation", contextThatHasTenant(tnt), tnt, "", testObjectType, modelFormation).Return(&modelFormation, nil)
   362  
   363  		mockConverter := &automock.Converter{}
   364  		mockConverter.On("FromGraphQL", formationInput).Return(modelFormation)
   365  
   366  		fetcherSvc := &automock.TenantFetcher{}
   367  		fetcherSvc.On("FetchOnDemand", "", tnt).Return(nil)
   368  
   369  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   370  		sut := formation.NewResolver(transact, mockService, mockConverter, nil, nil, fetcherSvc)
   371  
   372  		// WHEN
   373  		_, err := sut.AssignFormation(ctx, "", testObjectType, formationInput)
   374  
   375  		// THEN
   376  		require.Error(t, err)
   377  		assert.Contains(t, err.Error(), testErr.Error())
   378  		mock.AssertExpectationsForObjects(t, persist, transact, mockService, mockConverter, fetcherSvc)
   379  	})
   380  	t.Run("returns error when assign formation fails", func(t *testing.T) {
   381  		// GIVEN
   382  		persist, transact := txGen.ThatDoesntExpectCommit()
   383  
   384  		mockService := &automock.Service{}
   385  		mockService.On("AssignFormation", contextThatHasTenant(tnt), tnt, "", testObjectType, modelFormation).Return(nil, testErr)
   386  
   387  		mockConverter := &automock.Converter{}
   388  		mockConverter.On("FromGraphQL", formationInput).Return(modelFormation)
   389  
   390  		fetcherSvc := &automock.TenantFetcher{}
   391  		fetcherSvc.On("FetchOnDemand", "", tnt).Return(nil)
   392  
   393  		ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   394  		sut := formation.NewResolver(transact, mockService, mockConverter, nil, nil, fetcherSvc)
   395  
   396  		// WHEN
   397  		actual, err := sut.AssignFormation(ctx, "", testObjectType, formationInput)
   398  
   399  		// THEN
   400  		require.Error(t, err)
   401  		assert.Contains(t, err.Error(), testErr.Error())
   402  		require.Nil(t, actual)
   403  		mock.AssertExpectationsForObjects(t, persist, transact, mockService, mockConverter, fetcherSvc)
   404  	})
   405  }
   406  
   407  func TestUnassignFormation(t *testing.T) {
   408  	formationInput := graphql.FormationInput{
   409  		Name: testFormationName,
   410  	}
   411  	formationAssignment := model.FormationAssignment{ID: FormationAssignmentID}
   412  	tnt := "tenant"
   413  	externalTnt := "external-tenant"
   414  	testErr := errors.New("test error")
   415  	txGen := txtest.NewTransactionContextGenerator(testErr)
   416  	testCases := []struct {
   417  		Name                     string
   418  		TxFn                     func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner)
   419  		ServiceFn                func() *automock.Service
   420  		ConverterFn              func() *automock.Converter
   421  		FormationAssignmentSvcFn func() *automock.FormationAssignmentService
   422  		InputID                  string
   423  		ObjectType               graphql.FormationObjectType
   424  		Context                  context.Context
   425  		ExpectedFormation        *graphql.Formation
   426  		ExpectedError            error
   427  	}{
   428  		{
   429  			Name: "successfully unassigned formation",
   430  			TxFn: txGen.ThatSucceeds,
   431  			ConverterFn: func() *automock.Converter {
   432  				conv := &automock.Converter{}
   433  				conv.On("FromGraphQL", formationInput).Return(modelFormation)
   434  				conv.On("ToGraphQL", &modelFormation).Return(&graphqlFormation, nil)
   435  				return conv
   436  			},
   437  			Context:    tenant.SaveToContext(context.TODO(), tnt, externalTnt),
   438  			ObjectType: graphql.FormationObjectTypeTenant,
   439  			ServiceFn: func() *automock.Service {
   440  				svc := &automock.Service{}
   441  				svc.On("UnassignFormation", contextThatHasTenant(tnt), tnt, "", graphql.FormationObjectTypeTenant, modelFormation).Return(&modelFormation, nil)
   442  				return svc
   443  			},
   444  			ExpectedFormation: &graphqlFormation,
   445  		},
   446  		{
   447  			Name: "successfully unassigned formation deletes self formation assignment",
   448  			TxFn: txGen.ThatSucceedsTwice,
   449  			ConverterFn: func() *automock.Converter {
   450  				conv := &automock.Converter{}
   451  				conv.On("FromGraphQL", formationInput).Return(modelFormation)
   452  				conv.On("ToGraphQL", &modelFormation).Return(&graphqlFormation, nil)
   453  				return conv
   454  			},
   455  			Context:    tenant.SaveToContext(context.TODO(), tnt, externalTnt),
   456  			ObjectType: graphql.FormationObjectTypeApplication,
   457  			ServiceFn: func() *automock.Service {
   458  				svc := &automock.Service{}
   459  				svc.On("GetFormationByName", contextThatHasTenant(tnt), formationInput.Name, tnt).Return(&modelFormation, nil)
   460  				svc.On("UnassignFormation", contextThatHasTenant(tnt), tnt, ApplicationID, graphql.FormationObjectTypeApplication, modelFormation).Return(&modelFormation, nil)
   461  				return svc
   462  			},
   463  			FormationAssignmentSvcFn: func() *automock.FormationAssignmentService {
   464  				svc := &automock.FormationAssignmentService{}
   465  				svc.On("GetReverseBySourceAndTarget", contextThatHasTenant(tnt), modelFormation.ID, ApplicationID, ApplicationID).Return(&formationAssignment, nil)
   466  				svc.On("Delete", contextThatHasTenant(tnt), formationAssignment.ID).Return(nil)
   467  				return svc
   468  			},
   469  			InputID:           ApplicationID,
   470  			ExpectedFormation: &graphqlFormation,
   471  		},
   472  		{
   473  			Name: "successfully unassigned formation when self formation assignment is missing",
   474  			TxFn: txGen.ThatSucceedsTwice,
   475  			ConverterFn: func() *automock.Converter {
   476  				conv := &automock.Converter{}
   477  				conv.On("FromGraphQL", formationInput).Return(modelFormation)
   478  				conv.On("ToGraphQL", &modelFormation).Return(&graphqlFormation, nil)
   479  				return conv
   480  			},
   481  			Context:    tenant.SaveToContext(context.TODO(), tnt, externalTnt),
   482  			ObjectType: graphql.FormationObjectTypeApplication,
   483  			ServiceFn: func() *automock.Service {
   484  				svc := &automock.Service{}
   485  				svc.On("GetFormationByName", contextThatHasTenant(tnt), formationInput.Name, tnt).Return(&modelFormation, nil)
   486  				svc.On("UnassignFormation", contextThatHasTenant(tnt), tnt, ApplicationID, graphql.FormationObjectTypeApplication, modelFormation).Return(&modelFormation, nil)
   487  				return svc
   488  			},
   489  			FormationAssignmentSvcFn: func() *automock.FormationAssignmentService {
   490  				svc := &automock.FormationAssignmentService{}
   491  				svc.On("GetReverseBySourceAndTarget", contextThatHasTenant(tnt), modelFormation.ID, ApplicationID, ApplicationID).Return(nil, testErr)
   492  				return svc
   493  			},
   494  			InputID:           ApplicationID,
   495  			ExpectedFormation: &graphqlFormation,
   496  		},
   497  		{
   498  			Name:        "successfully unassigned formation when self formation assignment is missing",
   499  			TxFn:        txGen.ThatDoesntExpectCommit,
   500  			ConverterFn: unusedConverter,
   501  			Context:     tenant.SaveToContext(context.TODO(), tnt, externalTnt),
   502  			ObjectType:  graphql.FormationObjectTypeApplication,
   503  			ServiceFn: func() *automock.Service {
   504  				svc := &automock.Service{}
   505  				svc.On("GetFormationByName", contextThatHasTenant(tnt), formationInput.Name, tnt).Return(nil, testErr)
   506  				return svc
   507  			},
   508  			InputID:       ApplicationID,
   509  			ExpectedError: testErr,
   510  		},
   511  		{
   512  			Name:        "successfully unassigned formation when self formation assignment is missing",
   513  			TxFn:        txGen.ThatDoesntExpectCommit,
   514  			ConverterFn: unusedConverter,
   515  			Context:     tenant.SaveToContext(context.TODO(), tnt, externalTnt),
   516  			ObjectType:  graphql.FormationObjectTypeApplication,
   517  			ServiceFn: func() *automock.Service {
   518  				svc := &automock.Service{}
   519  				svc.On("GetFormationByName", contextThatHasTenant(tnt), formationInput.Name, tnt).Return(nil, testErr)
   520  				return svc
   521  			},
   522  			InputID:       ApplicationID,
   523  			ExpectedError: testErr,
   524  		},
   525  		{
   526  			Name:        "successfully unassigned formation when self formation assignment is missing",
   527  			TxFn:        txGen.ThatFailsOnCommit,
   528  			ConverterFn: unusedConverter,
   529  			Context:     tenant.SaveToContext(context.TODO(), tnt, externalTnt),
   530  			ObjectType:  graphql.FormationObjectTypeApplication,
   531  			ServiceFn: func() *automock.Service {
   532  				svc := &automock.Service{}
   533  				svc.On("GetFormationByName", contextThatHasTenant(tnt), formationInput.Name, tnt).Return(&modelFormation, nil)
   534  				return svc
   535  			},
   536  			FormationAssignmentSvcFn: func() *automock.FormationAssignmentService {
   537  				svc := &automock.FormationAssignmentService{}
   538  				svc.On("GetReverseBySourceAndTarget", contextThatHasTenant(tnt), modelFormation.ID, ApplicationID, ApplicationID).Return(&formationAssignment, nil)
   539  				svc.On("Delete", contextThatHasTenant(tnt), formationAssignment.ID).Return(nil)
   540  				return svc
   541  			},
   542  			InputID:       ApplicationID,
   543  			ExpectedError: testErr,
   544  		},
   545  		{
   546  			Name:          "fails when transaction fails to open",
   547  			TxFn:          txGen.ThatFailsOnBegin,
   548  			ConverterFn:   unusedConverter,
   549  			Context:       tenant.SaveToContext(context.TODO(), tnt, externalTnt),
   550  			ObjectType:    graphql.FormationObjectTypeApplication,
   551  			ServiceFn:     unusedService,
   552  			InputID:       ApplicationID,
   553  			ExpectedError: testErr,
   554  		},
   555  		{
   556  			Name:          "returns error when can not load tenant from context",
   557  			TxFn:          txGen.ThatDoesntExpectCommit,
   558  			Context:       context.TODO(),
   559  			ObjectType:    graphql.FormationObjectTypeTenant,
   560  			ConverterFn:   unusedConverter,
   561  			ServiceFn:     unusedService,
   562  			ExpectedError: apperrors.NewCannotReadTenantError(),
   563  		}, {
   564  			Name:          "returns error when can not start db transaction",
   565  			TxFn:          txGen.ThatFailsOnBegin,
   566  			Context:       tenant.SaveToContext(context.TODO(), tnt, externalTnt),
   567  			ObjectType:    graphql.FormationObjectTypeTenant,
   568  			ConverterFn:   unusedConverter,
   569  			ServiceFn:     unusedService,
   570  			ExpectedError: testErr,
   571  		}, {
   572  			Name:       "returns error when commit fails",
   573  			TxFn:       txGen.ThatFailsOnCommit,
   574  			Context:    tenant.SaveToContext(context.TODO(), tnt, externalTnt),
   575  			ObjectType: graphql.FormationObjectTypeTenant,
   576  			ConverterFn: func() *automock.Converter {
   577  				conv := &automock.Converter{}
   578  				conv.On("FromGraphQL", formationInput).Return(modelFormation)
   579  				return conv
   580  			},
   581  			ServiceFn: func() *automock.Service {
   582  				svc := &automock.Service{}
   583  				svc.On("UnassignFormation", contextThatHasTenant(tnt), tnt, "", graphql.FormationObjectTypeTenant, modelFormation).Return(&modelFormation, nil)
   584  				return svc
   585  			},
   586  			ExpectedError: testErr,
   587  		}, {
   588  			Name:       "returns error when assign formation fails",
   589  			TxFn:       txGen.ThatDoesntExpectCommit,
   590  			Context:    tenant.SaveToContext(context.TODO(), tnt, externalTnt),
   591  			ObjectType: graphql.FormationObjectTypeTenant,
   592  			ConverterFn: func() *automock.Converter {
   593  				conv := &automock.Converter{}
   594  				conv.On("FromGraphQL", formationInput).Return(modelFormation)
   595  				return conv
   596  			},
   597  			ServiceFn: func() *automock.Service {
   598  				svc := &automock.Service{}
   599  				svc.On("UnassignFormation", contextThatHasTenant(tnt), tnt, "", graphql.FormationObjectTypeTenant, modelFormation).Return(nil, testErr)
   600  				return svc
   601  			},
   602  			ExpectedError: testErr,
   603  		},
   604  	}
   605  	for _, testCase := range testCases {
   606  		t.Run(testCase.Name, func(t *testing.T) {
   607  			// GIVEN
   608  			persist, transact := testCase.TxFn()
   609  			service := testCase.ServiceFn()
   610  			converter := testCase.ConverterFn()
   611  			formationAssignmentSvc := &automock.FormationAssignmentService{}
   612  			if testCase.FormationAssignmentSvcFn != nil {
   613  				formationAssignmentSvc = testCase.FormationAssignmentSvcFn()
   614  			}
   615  
   616  			resolver := formation.NewResolver(transact, service, converter, formationAssignmentSvc, nil, nil)
   617  
   618  			// WHEN
   619  			f, err := resolver.UnassignFormation(testCase.Context, testCase.InputID, testCase.ObjectType, formationInput)
   620  
   621  			// THEN
   622  			if testCase.ExpectedError != nil {
   623  				require.Error(t, err)
   624  				assert.Contains(t, err.Error(), testCase.ExpectedError.Error())
   625  			} else {
   626  				assert.NoError(t, err)
   627  			}
   628  			assert.Equal(t, testCase.ExpectedFormation, f)
   629  
   630  			mock.AssertExpectationsForObjects(t, persist, service, converter, formationAssignmentSvc)
   631  		})
   632  	}
   633  }
   634  
   635  func TestFormation(t *testing.T) {
   636  	testErr := errors.New("test error")
   637  
   638  	txGen := txtest.NewTransactionContextGenerator(testErr)
   639  
   640  	ctx := context.TODO()
   641  
   642  	testCases := []struct {
   643  		Name              string
   644  		TxFn              func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner)
   645  		ServiceFn         func() *automock.Service
   646  		ConverterFn       func() *automock.Converter
   647  		FetcherFn         func() *automock.TenantFetcher
   648  		InputID           string
   649  		ExpectedFormation *graphql.Formation
   650  		ExpectedError     error
   651  	}{
   652  		{
   653  			Name: "Success",
   654  			TxFn: txGen.ThatSucceeds,
   655  			ServiceFn: func() *automock.Service {
   656  				service := &automock.Service{}
   657  				service.On("Get", txtest.CtxWithDBMatcher(), FormationID).Return(&modelFormation, nil).Once()
   658  				return service
   659  			},
   660  			ConverterFn: func() *automock.Converter {
   661  				conv := &automock.Converter{}
   662  				conv.On("ToGraphQL", &modelFormation).Return(&graphqlFormation, nil).Once()
   663  				return conv
   664  			},
   665  			InputID:           FormationID,
   666  			ExpectedFormation: &graphqlFormation,
   667  			ExpectedError:     nil,
   668  		},
   669  		{
   670  			Name: "Returns error when getting formation fails",
   671  			TxFn: txGen.ThatDoesntExpectCommit,
   672  			ServiceFn: func() *automock.Service {
   673  				service := &automock.Service{}
   674  				service.On("Get", txtest.CtxWithDBMatcher(), FormationID).Return(nil, testErr).Once()
   675  				return service
   676  			},
   677  			ConverterFn:       unusedConverter,
   678  			InputID:           FormationID,
   679  			ExpectedFormation: nil,
   680  			ExpectedError:     testErr,
   681  		},
   682  		{
   683  			Name:              "Returns error when can't start transaction",
   684  			TxFn:              txGen.ThatFailsOnBegin,
   685  			ServiceFn:         unusedService,
   686  			ConverterFn:       unusedConverter,
   687  			InputID:           FormationID,
   688  			ExpectedFormation: nil,
   689  			ExpectedError:     testErr,
   690  		},
   691  		{
   692  			Name: "Returns error when can't commit transaction",
   693  			TxFn: txGen.ThatFailsOnCommit,
   694  			ServiceFn: func() *automock.Service {
   695  				service := &automock.Service{}
   696  				service.On("Get", txtest.CtxWithDBMatcher(), FormationID).Return(formationModel, nil).Once()
   697  				return service
   698  			}, ConverterFn: unusedConverter,
   699  			InputID:           FormationID,
   700  			ExpectedFormation: nil,
   701  			ExpectedError:     testErr,
   702  		},
   703  	}
   704  
   705  	for _, testCase := range testCases {
   706  		t.Run(testCase.Name, func(t *testing.T) {
   707  			// GIVEN
   708  			persist, transact := testCase.TxFn()
   709  			service := testCase.ServiceFn()
   710  			converter := testCase.ConverterFn()
   711  
   712  			resolver := formation.NewResolver(transact, service, converter, nil, nil, nil)
   713  
   714  			// WHEN
   715  			f, err := resolver.Formation(ctx, testCase.InputID)
   716  
   717  			// THEN
   718  			if testCase.ExpectedError != nil {
   719  				require.Error(t, err)
   720  				assert.Contains(t, err.Error(), testCase.ExpectedError.Error())
   721  			} else {
   722  				assert.NoError(t, err)
   723  			}
   724  			assert.Equal(t, testCase.ExpectedFormation, f)
   725  
   726  			mock.AssertExpectationsForObjects(t, persist, service, converter)
   727  		})
   728  	}
   729  }
   730  
   731  func TestFormationByName(t *testing.T) {
   732  	testErr := errors.New("test error")
   733  
   734  	txGen := txtest.NewTransactionContextGenerator(testErr)
   735  
   736  	tnt := "tenant"
   737  	externalTnt := "external-tenant"
   738  	ctx := tenant.SaveToContext(context.TODO(), tnt, externalTnt)
   739  	emptyCtx := context.Background()
   740  
   741  	testCases := []struct {
   742  		Name              string
   743  		Ctx               context.Context
   744  		TxFn              func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner)
   745  		ServiceFn         func() *automock.Service
   746  		ConverterFn       func() *automock.Converter
   747  		FetcherFn         func() *automock.TenantFetcher
   748  		InputName         string
   749  		ExpectedFormation *graphql.Formation
   750  		ExpectedError     error
   751  	}{
   752  		{
   753  			Name: "Success",
   754  			Ctx:  ctx,
   755  			TxFn: txGen.ThatSucceeds,
   756  			ServiceFn: func() *automock.Service {
   757  				service := &automock.Service{}
   758  				service.On("GetFormationByName", contextThatHasTenant(tnt), testFormationName, tnt).Return(&modelFormation, nil).Once()
   759  				return service
   760  			},
   761  			ConverterFn: func() *automock.Converter {
   762  				conv := &automock.Converter{}
   763  				conv.On("ToGraphQL", &modelFormation).Return(&graphqlFormation, nil).Once()
   764  				return conv
   765  			},
   766  			InputName:         testFormationName,
   767  			ExpectedFormation: &graphqlFormation,
   768  			ExpectedError:     nil,
   769  		},
   770  		{
   771  			Name:              "Returns error when getting tenant fails",
   772  			Ctx:               emptyCtx,
   773  			TxFn:              txGen.ThatDoesntExpectCommit,
   774  			ServiceFn:         unusedService,
   775  			ConverterFn:       unusedConverter,
   776  			InputName:         testFormationName,
   777  			ExpectedFormation: nil,
   778  			ExpectedError:     errors.New("cannot read tenant from context"),
   779  		},
   780  		{
   781  			Name: "Returns error when getting formation fails",
   782  			Ctx:  ctx,
   783  			TxFn: txGen.ThatDoesntExpectCommit,
   784  			ServiceFn: func() *automock.Service {
   785  				service := &automock.Service{}
   786  				service.On("GetFormationByName", txtest.CtxWithDBMatcher(), testFormationName, tnt).Return(nil, testErr).Once()
   787  				return service
   788  			},
   789  			ConverterFn:       unusedConverter,
   790  			InputName:         testFormationName,
   791  			ExpectedFormation: nil,
   792  			ExpectedError:     testErr,
   793  		},
   794  		{
   795  			Name:              "Returns error when can't start transaction",
   796  			Ctx:               ctx,
   797  			TxFn:              txGen.ThatFailsOnBegin,
   798  			ServiceFn:         unusedService,
   799  			ConverterFn:       unusedConverter,
   800  			InputName:         testFormationName,
   801  			ExpectedFormation: nil,
   802  			ExpectedError:     testErr,
   803  		},
   804  		{
   805  			Name: "Returns error when can't commit transaction",
   806  			Ctx:  ctx,
   807  			TxFn: txGen.ThatFailsOnCommit,
   808  			ServiceFn: func() *automock.Service {
   809  				service := &automock.Service{}
   810  				service.On("GetFormationByName", txtest.CtxWithDBMatcher(), testFormationName, tnt).Return(formationModel, nil).Once()
   811  				return service
   812  			}, ConverterFn: unusedConverter,
   813  			InputName:         testFormationName,
   814  			ExpectedFormation: nil,
   815  			ExpectedError:     testErr,
   816  		},
   817  	}
   818  
   819  	for _, testCase := range testCases {
   820  		t.Run(testCase.Name, func(t *testing.T) {
   821  			// GIVEN
   822  			testCtx := testCase.Ctx
   823  			persist, transact := testCase.TxFn()
   824  			service := testCase.ServiceFn()
   825  			converter := testCase.ConverterFn()
   826  
   827  			resolver := formation.NewResolver(transact, service, converter, nil, nil, nil)
   828  
   829  			// WHEN
   830  			f, err := resolver.FormationByName(testCtx, testCase.InputName)
   831  
   832  			// THEN
   833  			if testCase.ExpectedError != nil {
   834  				require.Error(t, err)
   835  				assert.Contains(t, err.Error(), testCase.ExpectedError.Error())
   836  			} else {
   837  				assert.NoError(t, err)
   838  			}
   839  			assert.Equal(t, testCase.ExpectedFormation, f)
   840  
   841  			mock.AssertExpectationsForObjects(t, persist, service, converter)
   842  		})
   843  	}
   844  }
   845  func TestFormations(t *testing.T) {
   846  	testErr := errors.New("test error")
   847  
   848  	txGen := txtest.NewTransactionContextGenerator(testErr)
   849  
   850  	ctx := context.TODO()
   851  
   852  	first := 100
   853  	afterStr := "after"
   854  	after := graphql.PageCursor(afterStr)
   855  
   856  	modelFormations := []*model.Formation{&modelFormation}
   857  
   858  	graphqlFormations := []*graphql.Formation{&graphqlFormation}
   859  
   860  	modelPage := &model.FormationPage{
   861  		Data: modelFormations,
   862  		PageInfo: &pagination.Page{
   863  			StartCursor: "start",
   864  			EndCursor:   "end",
   865  			HasNextPage: false,
   866  		},
   867  		TotalCount: 1,
   868  	}
   869  
   870  	expectedOutput := &graphql.FormationPage{
   871  		Data: graphqlFormations,
   872  		PageInfo: &graphql.PageInfo{
   873  			StartCursor: "start",
   874  			EndCursor:   "end",
   875  			HasNextPage: false,
   876  		},
   877  		TotalCount: 1,
   878  	}
   879  
   880  	testCases := []struct {
   881  		Name               string
   882  		TxFn               func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner)
   883  		ServiceFn          func() *automock.Service
   884  		ConverterFn        func() *automock.Converter
   885  		FetcherFn          func() *automock.TenantFetcher
   886  		InputID            string
   887  		ExpectedFormations *graphql.FormationPage
   888  		ExpectedError      error
   889  	}{
   890  		{
   891  			Name: "Success",
   892  			TxFn: txGen.ThatSucceeds,
   893  			ServiceFn: func() *automock.Service {
   894  				service := &automock.Service{}
   895  				service.On("List", txtest.CtxWithDBMatcher(), first, afterStr).Return(modelPage, nil).Once()
   896  				return service
   897  			},
   898  			ConverterFn: func() *automock.Converter {
   899  				conv := &automock.Converter{}
   900  				conv.On("MultipleToGraphQL", modelFormations).Return(graphqlFormations, nil).Once()
   901  				return conv
   902  			},
   903  			InputID:            FormationID,
   904  			ExpectedFormations: expectedOutput,
   905  			ExpectedError:      nil,
   906  		},
   907  		{
   908  			Name: "Returns error when listing formations fails",
   909  			TxFn: txGen.ThatDoesntExpectCommit,
   910  			ServiceFn: func() *automock.Service {
   911  				service := &automock.Service{}
   912  				service.On("List", txtest.CtxWithDBMatcher(), first, afterStr).Return(nil, testErr).Once()
   913  				return service
   914  			},
   915  			ConverterFn:        unusedConverter,
   916  			InputID:            FormationID,
   917  			ExpectedFormations: nil,
   918  			ExpectedError:      testErr,
   919  		},
   920  		{
   921  			Name: "Returns error when converting formations to graphql fails",
   922  			TxFn: txGen.ThatSucceeds,
   923  			ServiceFn: func() *automock.Service {
   924  				service := &automock.Service{}
   925  				service.On("List", txtest.CtxWithDBMatcher(), first, afterStr).Return(modelPage, nil).Once()
   926  				return service
   927  			},
   928  			ConverterFn: func() *automock.Converter {
   929  				conv := &automock.Converter{}
   930  				conv.On("MultipleToGraphQL", modelFormations).Return(nil, testErr).Once()
   931  				return conv
   932  			},
   933  			InputID:            FormationID,
   934  			ExpectedFormations: nil,
   935  			ExpectedError:      testErr,
   936  		},
   937  		{
   938  			Name:               "Returns error when can't start transaction",
   939  			TxFn:               txGen.ThatFailsOnBegin,
   940  			ServiceFn:          unusedService,
   941  			ConverterFn:        unusedConverter,
   942  			InputID:            FormationID,
   943  			ExpectedFormations: nil,
   944  			ExpectedError:      testErr,
   945  		},
   946  		{
   947  			Name: "Returns error when can't commit transaction",
   948  			TxFn: txGen.ThatFailsOnCommit,
   949  			ServiceFn: func() *automock.Service {
   950  				service := &automock.Service{}
   951  				service.On("List", txtest.CtxWithDBMatcher(), first, afterStr).Return(modelPage, nil).Once()
   952  				return service
   953  			},
   954  			ConverterFn:        unusedConverter,
   955  			InputID:            FormationID,
   956  			ExpectedFormations: nil,
   957  			ExpectedError:      testErr,
   958  		},
   959  	}
   960  	for _, testCase := range testCases {
   961  		t.Run(testCase.Name, func(t *testing.T) {
   962  			// GIVEN
   963  			persist, transact := testCase.TxFn()
   964  			service := testCase.ServiceFn()
   965  			converter := testCase.ConverterFn()
   966  
   967  			resolver := formation.NewResolver(transact, service, converter, nil, nil, nil)
   968  
   969  			// WHEN
   970  			f, err := resolver.Formations(ctx, &first, &after)
   971  
   972  			// THEN
   973  			if testCase.ExpectedError != nil {
   974  				require.Error(t, err)
   975  				assert.Contains(t, err.Error(), testCase.ExpectedError.Error())
   976  			} else {
   977  				assert.NoError(t, err)
   978  			}
   979  			assert.Equal(t, testCase.ExpectedFormations, f)
   980  
   981  			mock.AssertExpectationsForObjects(t, persist, service, converter)
   982  		})
   983  	}
   984  
   985  	t.Run("Returns error when 'first' is nil", func(t *testing.T) {
   986  		resolver := formation.NewResolver(nil, nil, nil, nil, nil, nil)
   987  
   988  		// WHEN
   989  		f, err := resolver.Formations(ctx, nil, &after)
   990  
   991  		// THEN
   992  		require.Error(t, err)
   993  		require.Nil(t, f)
   994  	})
   995  }
   996  
   997  func TestResolver_FormationAssignment(t *testing.T) {
   998  	// GIVEN
   999  	ctx := context.TODO()
  1000  
  1001  	testErr := errors.New("test error")
  1002  	notFoundErr := apperrors.NewNotFoundError(resource.FormationAssignment, FormationAssignmentID)
  1003  
  1004  	txGen := txtest.NewTransactionContextGenerator(testErr)
  1005  
  1006  	gqlFormation := fixGqlFormation()
  1007  	gqlFormationAssignment := fixGqlFormationAssignment(FormationAssignmentState, &TestConfigValueStr)
  1008  	formationAssignmentModel := fixFormationAssignmentModel(FormationAssignmentState, TestConfigValueRawJSON)
  1009  
  1010  	testCases := []struct {
  1011  		Name                        string
  1012  		TransactionerFn             func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner)
  1013  		ServiceFn                   func() *automock.FormationAssignmentService
  1014  		ConverterFn                 func() *automock.FormationAssignmentConverter
  1015  		Formation                   *graphql.Formation
  1016  		FormationAssignmentID       string
  1017  		ExpectedFormationAssignment *graphql.FormationAssignment
  1018  		ExpectedErrMsg              string
  1019  	}{
  1020  		{
  1021  			Name:            "Success",
  1022  			TransactionerFn: txGen.ThatSucceeds,
  1023  			ServiceFn: func() *automock.FormationAssignmentService {
  1024  				faSvc := &automock.FormationAssignmentService{}
  1025  				faSvc.On("GetForFormation", txtest.CtxWithDBMatcher(), FormationAssignmentID, FormationID).Return(formationAssignmentModel, nil).Once()
  1026  				return faSvc
  1027  			},
  1028  			ConverterFn: func() *automock.FormationAssignmentConverter {
  1029  				faConv := &automock.FormationAssignmentConverter{}
  1030  				faConv.On("ToGraphQL", formationAssignmentModel).Return(gqlFormationAssignment, nil).Once()
  1031  				return faConv
  1032  			},
  1033  			Formation:                   gqlFormation,
  1034  			FormationAssignmentID:       FormationAssignmentID,
  1035  			ExpectedFormationAssignment: gqlFormationAssignment,
  1036  		},
  1037  		{
  1038  			Name:                        "Return error when formation object is nil",
  1039  			TransactionerFn:             txGen.ThatDoesntStartTransaction,
  1040  			ExpectedFormationAssignment: nil,
  1041  			ExpectedErrMsg:              "Formation cannot be empty",
  1042  		},
  1043  		{
  1044  			Name:                        "Returns error when commit begin fails",
  1045  			TransactionerFn:             txGen.ThatFailsOnBegin,
  1046  			Formation:                   gqlFormation,
  1047  			ExpectedFormationAssignment: nil,
  1048  			ExpectedErrMsg:              testErr.Error(),
  1049  		},
  1050  		{
  1051  			Name:            "Returns error when formation assignment retrieval failed",
  1052  			TransactionerFn: txGen.ThatDoesntExpectCommit,
  1053  			ServiceFn: func() *automock.FormationAssignmentService {
  1054  				faSvc := &automock.FormationAssignmentService{}
  1055  				faSvc.On("GetForFormation", txtest.CtxWithDBMatcher(), FormationAssignmentID, FormationID).Return(nil, testErr).Once()
  1056  				return faSvc
  1057  			},
  1058  			Formation:                   gqlFormation,
  1059  			FormationAssignmentID:       FormationAssignmentID,
  1060  			ExpectedFormationAssignment: nil,
  1061  			ExpectedErrMsg:              testErr.Error(),
  1062  		},
  1063  		{
  1064  			Name:            "Returns error when formation assignment for formation is not found",
  1065  			TransactionerFn: txGen.ThatSucceeds,
  1066  			ServiceFn: func() *automock.FormationAssignmentService {
  1067  				faSvc := &automock.FormationAssignmentService{}
  1068  				faSvc.On("GetForFormation", txtest.CtxWithDBMatcher(), FormationAssignmentID, FormationID).Return(nil, notFoundErr).Once()
  1069  				return faSvc
  1070  			},
  1071  			Formation:                   gqlFormation,
  1072  			FormationAssignmentID:       FormationAssignmentID,
  1073  			ExpectedFormationAssignment: nil,
  1074  		},
  1075  		{
  1076  			Name:            "Returns error when commit failed",
  1077  			TransactionerFn: txGen.ThatFailsOnCommit,
  1078  			ServiceFn: func() *automock.FormationAssignmentService {
  1079  				faSvc := &automock.FormationAssignmentService{}
  1080  				faSvc.On("GetForFormation", txtest.CtxWithDBMatcher(), FormationAssignmentID, FormationID).Return(formationAssignmentModel, nil).Once()
  1081  				return faSvc
  1082  			},
  1083  			Formation:                   gqlFormation,
  1084  			FormationAssignmentID:       FormationAssignmentID,
  1085  			ExpectedFormationAssignment: nil,
  1086  			ExpectedErrMsg:              testErr.Error(),
  1087  		},
  1088  		{
  1089  			Name:            "Returns error when converting to GraphQL failed",
  1090  			TransactionerFn: txGen.ThatSucceeds,
  1091  			ServiceFn: func() *automock.FormationAssignmentService {
  1092  				faSvc := &automock.FormationAssignmentService{}
  1093  				faSvc.On("GetForFormation", txtest.CtxWithDBMatcher(), FormationAssignmentID, FormationID).Return(formationAssignmentModel, nil).Once()
  1094  				return faSvc
  1095  			},
  1096  			ConverterFn: func() *automock.FormationAssignmentConverter {
  1097  				faConv := &automock.FormationAssignmentConverter{}
  1098  				faConv.On("ToGraphQL", formationAssignmentModel).Return(nil, testErr).Once()
  1099  				return faConv
  1100  			},
  1101  			Formation:                   gqlFormation,
  1102  			FormationAssignmentID:       FormationAssignmentID,
  1103  			ExpectedFormationAssignment: nil,
  1104  			ExpectedErrMsg:              testErr.Error(),
  1105  		},
  1106  	}
  1107  
  1108  	for _, testCase := range testCases {
  1109  		t.Run(testCase.Name, func(t *testing.T) {
  1110  			// GIVEN
  1111  			persist, transact := testCase.TransactionerFn()
  1112  
  1113  			faSvc := &automock.FormationAssignmentService{}
  1114  			if testCase.ServiceFn != nil {
  1115  				faSvc = testCase.ServiceFn()
  1116  			}
  1117  
  1118  			faConv := &automock.FormationAssignmentConverter{}
  1119  			if testCase.ConverterFn != nil {
  1120  				faConv = testCase.ConverterFn()
  1121  			}
  1122  
  1123  			resolver := formation.NewResolver(transact, nil, nil, faSvc, faConv, nil)
  1124  
  1125  			// WHEN
  1126  			fa, err := resolver.FormationAssignment(ctx, testCase.Formation, testCase.FormationAssignmentID)
  1127  
  1128  			// THEN
  1129  			require.Equal(t, testCase.ExpectedFormationAssignment, fa)
  1130  			if testCase.ExpectedErrMsg != "" {
  1131  				require.Error(t, err)
  1132  				require.Contains(t, err.Error(), testCase.ExpectedErrMsg)
  1133  			} else {
  1134  				require.NoError(t, err)
  1135  			}
  1136  
  1137  			mock.AssertExpectationsForObjects(t, faSvc, faConv, transact, persist)
  1138  		})
  1139  	}
  1140  }
  1141  
  1142  func TestResolver_FormationAssignments(t *testing.T) {
  1143  	// GIVEN
  1144  	ctx := context.TODO()
  1145  
  1146  	testErr := errors.New("test error")
  1147  
  1148  	txGen := txtest.NewTransactionContextGenerator(testErr)
  1149  
  1150  	first := 2
  1151  	gqlAfter := graphql.PageCursor("test")
  1152  	after := "test"
  1153  
  1154  	formationIDs := []string{FormationID, FormationID + "2"}
  1155  
  1156  	// Formation Assignments model fixtures
  1157  	faModelFirst := fixFormationAssignmentModel(FormationAssignmentState, TestConfigValueRawJSON)
  1158  	faModelSecond := fixFormationAssignmentModelWithSuffix(FormationAssignmentState, TestConfigValueRawJSON, "-2")
  1159  
  1160  	fasFirst := []*model.FormationAssignment{faModelFirst}
  1161  	fasSecond := []*model.FormationAssignment{faModelSecond}
  1162  
  1163  	faPageFirst := fixFormationAssignmentPage(fasFirst)
  1164  	faPageSecond := fixFormationAssignmentPage(fasSecond)
  1165  	faPages := []*model.FormationAssignmentPage{faPageFirst, faPageSecond}
  1166  
  1167  	// Formation Assignments GraphQL fixtures
  1168  	gqlFormationAssignmentFirst := fixGqlFormationAssignment(FormationAssignmentState, &TestConfigValueStr)
  1169  	gqlFormationAssignmentSecond := fixGqlFormationAssignmentWithSuffix(FormationAssignmentState, &TestConfigValueStr, "-2")
  1170  
  1171  	gqlFAFist := []*graphql.FormationAssignment{gqlFormationAssignmentFirst}
  1172  	gqlFASecond := []*graphql.FormationAssignment{gqlFormationAssignmentSecond}
  1173  
  1174  	gqlFAPageFirst := fixGQLFormationAssignmentPage(gqlFAFist)
  1175  	gqlFAPageSecond := fixGQLFormationAssignmentPage(gqlFASecond)
  1176  	gqlFAPages := []*graphql.FormationAssignmentPage{gqlFAPageFirst, gqlFAPageSecond}
  1177  
  1178  	testCases := []struct {
  1179  		Name            string
  1180  		TransactionerFn func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner)
  1181  		ServiceFn       func() *automock.FormationAssignmentService
  1182  		ConverterFn     func() *automock.FormationAssignmentConverter
  1183  		ExpectedResult  []*graphql.FormationAssignmentPage
  1184  		ExpectedErr     []error
  1185  	}{
  1186  		{
  1187  			Name:            "Success",
  1188  			TransactionerFn: txGen.ThatSucceeds,
  1189  			ServiceFn: func() *automock.FormationAssignmentService {
  1190  				faSvc := &automock.FormationAssignmentService{}
  1191  				faSvc.On("ListByFormationIDs", txtest.CtxWithDBMatcher(), formationIDs, first, after).Return(faPages, nil).Once()
  1192  				return faSvc
  1193  			},
  1194  			ConverterFn: func() *automock.FormationAssignmentConverter {
  1195  				faConv := &automock.FormationAssignmentConverter{}
  1196  				faConv.On("MultipleToGraphQL", fasFirst).Return(gqlFAFist, nil).Once()
  1197  				faConv.On("MultipleToGraphQL", fasSecond).Return(gqlFASecond, nil).Once()
  1198  				return faConv
  1199  			},
  1200  			ExpectedResult: gqlFAPages,
  1201  			ExpectedErr:    nil,
  1202  		},
  1203  		{
  1204  			Name:            "Returns error when transaction begin failed",
  1205  			TransactionerFn: txGen.ThatFailsOnBegin,
  1206  			ExpectedResult:  nil,
  1207  			ExpectedErr:     []error{testErr},
  1208  		},
  1209  		{
  1210  			Name:            "Returns error when listing formations",
  1211  			TransactionerFn: txGen.ThatDoesntExpectCommit,
  1212  			ServiceFn: func() *automock.FormationAssignmentService {
  1213  				faSvc := &automock.FormationAssignmentService{}
  1214  				faSvc.On("ListByFormationIDs", txtest.CtxWithDBMatcher(), formationIDs, first, after).Return(nil, testErr).Once()
  1215  				return faSvc
  1216  			},
  1217  			ExpectedResult: nil,
  1218  			ExpectedErr:    []error{testErr},
  1219  		},
  1220  		{
  1221  			Name:            "Returns error when transaction commit failed",
  1222  			TransactionerFn: txGen.ThatFailsOnCommit,
  1223  			ServiceFn: func() *automock.FormationAssignmentService {
  1224  				faSvc := &automock.FormationAssignmentService{}
  1225  				faSvc.On("ListByFormationIDs", txtest.CtxWithDBMatcher(), formationIDs, first, after).Return(faPages, nil).Once()
  1226  				return faSvc
  1227  			},
  1228  			ConverterFn: func() *automock.FormationAssignmentConverter {
  1229  				faConv := &automock.FormationAssignmentConverter{}
  1230  				faConv.On("MultipleToGraphQL", fasFirst).Return(gqlFAFist, nil).Once()
  1231  				faConv.On("MultipleToGraphQL", fasSecond).Return(gqlFASecond, nil).Once()
  1232  				return faConv
  1233  			},
  1234  			ExpectedResult: nil,
  1235  			ExpectedErr:    []error{testErr},
  1236  		},
  1237  		{
  1238  			Name:            "Returns error when converting to GraphQL failed",
  1239  			TransactionerFn: txGen.ThatDoesntExpectCommit,
  1240  			ServiceFn: func() *automock.FormationAssignmentService {
  1241  				faSvc := &automock.FormationAssignmentService{}
  1242  				faSvc.On("ListByFormationIDs", txtest.CtxWithDBMatcher(), formationIDs, first, after).Return(faPages, nil).Once()
  1243  				return faSvc
  1244  			},
  1245  			ConverterFn: func() *automock.FormationAssignmentConverter {
  1246  				faConv := &automock.FormationAssignmentConverter{}
  1247  				faConv.On("MultipleToGraphQL", fasFirst).Return(nil, testErr).Once()
  1248  				return faConv
  1249  			},
  1250  			ExpectedResult: nil,
  1251  			ExpectedErr:    []error{testErr},
  1252  		},
  1253  	}
  1254  
  1255  	for _, testCase := range testCases {
  1256  		t.Run(testCase.Name, func(t *testing.T) {
  1257  			// GIVEN
  1258  			persist, transact := testCase.TransactionerFn()
  1259  
  1260  			faSvc := &automock.FormationAssignmentService{}
  1261  			if testCase.ServiceFn != nil {
  1262  				faSvc = testCase.ServiceFn()
  1263  			}
  1264  
  1265  			faConv := &automock.FormationAssignmentConverter{}
  1266  			if testCase.ConverterFn != nil {
  1267  				faConv = testCase.ConverterFn()
  1268  			}
  1269  
  1270  			resolver := formation.NewResolver(transact, nil, nil, faSvc, faConv, nil)
  1271  			firstFormationAssignmentParams := dataloader.ParamFormationAssignment{ID: FormationID, Ctx: ctx, First: &first, After: &gqlAfter}
  1272  			secondFormationAssignmentParams := dataloader.ParamFormationAssignment{ID: FormationID + "2", Ctx: ctx, First: &first, After: &gqlAfter}
  1273  			keys := []dataloader.ParamFormationAssignment{firstFormationAssignmentParams, secondFormationAssignmentParams}
  1274  
  1275  			// WHEN
  1276  			result, err := resolver.FormationAssignmentsDataLoader(keys)
  1277  
  1278  			// THEN
  1279  			require.Equal(t, testCase.ExpectedResult, result)
  1280  			require.Equal(t, testCase.ExpectedErr, err)
  1281  
  1282  			mock.AssertExpectationsForObjects(t, faSvc, faConv, transact, persist)
  1283  		})
  1284  	}
  1285  
  1286  	t.Run("Returns error when there are no formations IDs", func(t *testing.T) {
  1287  		resolver := formation.NewResolver(nil, nil, nil, nil, nil, nil)
  1288  
  1289  		// WHEN
  1290  		_, errs := resolver.FormationAssignmentsDataLoader([]dataloader.ParamFormationAssignment{})
  1291  
  1292  		// THEN
  1293  		require.Error(t, errs[0])
  1294  		require.EqualError(t, errs[0], apperrors.NewInternalError("No Formations found").Error())
  1295  	})
  1296  
  1297  	t.Run("Returns error when start cursor is nil", func(t *testing.T) {
  1298  		firstFormationAssignmentParams := dataloader.ParamFormationAssignment{ID: FormationID, Ctx: ctx, First: nil, After: &gqlAfter}
  1299  		keys := []dataloader.ParamFormationAssignment{firstFormationAssignmentParams}
  1300  
  1301  		resolver := formation.NewResolver(nil, nil, nil, nil, nil, nil)
  1302  
  1303  		// WHEN
  1304  		_, errs := resolver.FormationAssignmentsDataLoader(keys)
  1305  
  1306  		// THEN
  1307  		require.Error(t, errs[0])
  1308  		require.EqualError(t, errs[0], apperrors.NewInvalidDataError("missing required parameter 'first'").Error())
  1309  	})
  1310  }
  1311  
  1312  func TestResolver_Status(t *testing.T) {
  1313  	// GIVEN
  1314  	ctx := context.TODO()
  1315  
  1316  	testErr := errors.New("test error")
  1317  
  1318  	txGen := txtest.NewTransactionContextGenerator(testErr)
  1319  
  1320  	formationIDs := []string{FormationID, FormationID + "2", FormationID + "3", FormationID + "4"}
  1321  
  1322  	// Formation Assignments model fixtures
  1323  	faModelReady := fixFormationAssignmentModel(string(model.ReadyAssignmentState), TestConfigValueRawJSON)
  1324  	faModelInitial := fixFormationAssignmentModelWithSuffix(string(model.InitialAssignmentState), TestConfigValueRawJSON, "-2")
  1325  	faModelError := fixFormationAssignmentModelWithSuffix(string(model.CreateErrorAssignmentState), json.RawMessage(`{"error": {"message": "failure", "errorCode": 1}}`), "-3")
  1326  	faModelEmptyError := fixFormationAssignmentModelWithSuffix(string(model.CreateErrorAssignmentState), nil, "-4")
  1327  
  1328  	fasReady := []*model.FormationAssignment{faModelReady}                                         // all are READY -> READY condition
  1329  	fasInProgress := []*model.FormationAssignment{faModelInitial, faModelReady}                    // no errors, but one is INITIAL -> IN_PROGRESS condition
  1330  	fasError := []*model.FormationAssignment{faModelReady, faModelError, faModelInitial}           // have error -> ERROR condition
  1331  	fasEmptyError := []*model.FormationAssignment{faModelReady, faModelEmptyError, faModelInitial} // should handle empty Value and have ERROR condition
  1332  
  1333  	fasPerFormation := [][]*model.FormationAssignment{fasReady, fasInProgress, fasError, fasEmptyError}
  1334  
  1335  	fasUnmarshallable := []*model.FormationAssignment{fixFormationAssignmentModelWithSuffix(string(model.DeleteErrorAssignmentState), json.RawMessage(`unmarshallable structure`), "-4")}
  1336  
  1337  	faPagesWithUnmarshallableError := [][]*model.FormationAssignment{fasUnmarshallable}
  1338  
  1339  	// Formation Assignments GraphQL fixtures
  1340  
  1341  	gqlStatusFirst := graphql.FormationStatus{Condition: graphql.FormationStatusConditionReady, Errors: nil}
  1342  	gqlStatusSecond := graphql.FormationStatus{Condition: graphql.FormationStatusConditionInProgress, Errors: nil}
  1343  	gqlStatusThird := graphql.FormationStatus{
  1344  		Condition: graphql.FormationStatusConditionError,
  1345  		Errors: []*graphql.FormationStatusError{{
  1346  			AssignmentID: addSuffix(FormationAssignmentID, "-3"),
  1347  			Message:      "failure",
  1348  			ErrorCode:    1,
  1349  		}},
  1350  	}
  1351  	gqlStatusFourth := graphql.FormationStatus{
  1352  		Condition: graphql.FormationStatusConditionError,
  1353  		Errors: []*graphql.FormationStatusError{{
  1354  			AssignmentID: addSuffix(FormationAssignmentID, "-4"),
  1355  		}},
  1356  	}
  1357  
  1358  	gqlStatuses := []*graphql.FormationStatus{&gqlStatusFirst, &gqlStatusSecond, &gqlStatusThird, &gqlStatusFourth}
  1359  
  1360  	emptyFaPage := [][]*model.FormationAssignment{nil, nil, nil, nil}
  1361  
  1362  	testCases := []struct {
  1363  		Name            string
  1364  		TransactionerFn func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner)
  1365  		ServiceFn       func() *automock.FormationAssignmentService
  1366  		Params          []dataloader.ParamFormationStatus
  1367  		ExpectedResult  []*graphql.FormationStatus
  1368  		ExpectedErr     error
  1369  	}{
  1370  		{
  1371  			Name:            "Success",
  1372  			TransactionerFn: txGen.ThatSucceeds,
  1373  			ServiceFn: func() *automock.FormationAssignmentService {
  1374  				faSvc := &automock.FormationAssignmentService{}
  1375  				faSvc.On("ListByFormationIDsNoPaging", txtest.CtxWithDBMatcher(), formationIDs).Return(fasPerFormation, nil).Once()
  1376  				return faSvc
  1377  			},
  1378  			Params:         []dataloader.ParamFormationStatus{firstFormationStatusParams, secondFormationStatusParams, thirdFormationStatusParams, fourthPageFormations},
  1379  			ExpectedResult: gqlStatuses,
  1380  			ExpectedErr:    nil,
  1381  		},
  1382  		{
  1383  			Name:            "Success when there are no formation assignments",
  1384  			TransactionerFn: txGen.ThatSucceeds,
  1385  			ServiceFn: func() *automock.FormationAssignmentService {
  1386  				faSvc := &automock.FormationAssignmentService{}
  1387  				faSvc.On("ListByFormationIDsNoPaging", txtest.CtxWithDBMatcher(), []string{FormationID}).Return(emptyFaPage, nil).Once()
  1388  				return faSvc
  1389  			},
  1390  			Params:         []dataloader.ParamFormationStatus{firstFormationStatusParams},
  1391  			ExpectedResult: []*graphql.FormationStatus{&gqlStatusFirst},
  1392  			ExpectedErr:    nil,
  1393  		},
  1394  		{
  1395  			Name:            "Success when there are no FAs and the formation is in error state",
  1396  			TransactionerFn: txGen.ThatSucceeds,
  1397  			ServiceFn: func() *automock.FormationAssignmentService {
  1398  				faSvc := &automock.FormationAssignmentService{}
  1399  				faSvc.On("ListByFormationIDsNoPaging", txtest.CtxWithDBMatcher(), []string{FormationID}).Return(emptyFaPage, nil).Once()
  1400  				return faSvc
  1401  			},
  1402  			Params: []dataloader.ParamFormationStatus{{ID: FormationID, State: string(model.CreateErrorFormationState), Message: "failure", ErrorCode: 1}},
  1403  			ExpectedResult: []*graphql.FormationStatus{{
  1404  				Condition: graphql.FormationStatusConditionError,
  1405  				Errors: []*graphql.FormationStatusError{{
  1406  					Message:   "failure",
  1407  					ErrorCode: 1,
  1408  				}},
  1409  			}},
  1410  			ExpectedErr: nil,
  1411  		},
  1412  		{
  1413  			Name:            "Returns error when transaction begin failed",
  1414  			TransactionerFn: txGen.ThatFailsOnBegin,
  1415  			Params:          []dataloader.ParamFormationStatus{firstFormationStatusParams, secondFormationStatusParams, thirdFormationStatusParams, fourthPageFormations},
  1416  			ExpectedResult:  nil,
  1417  			ExpectedErr:     testErr,
  1418  		},
  1419  		{
  1420  			Name:            "Returns error when listing formations",
  1421  			TransactionerFn: txGen.ThatDoesntExpectCommit,
  1422  			ServiceFn: func() *automock.FormationAssignmentService {
  1423  				faSvc := &automock.FormationAssignmentService{}
  1424  				faSvc.On("ListByFormationIDsNoPaging", txtest.CtxWithDBMatcher(), formationIDs).Return(nil, testErr).Once()
  1425  				return faSvc
  1426  			},
  1427  			Params:         []dataloader.ParamFormationStatus{firstFormationStatusParams, secondFormationStatusParams, thirdFormationStatusParams, fourthPageFormations},
  1428  			ExpectedResult: nil,
  1429  			ExpectedErr:    testErr,
  1430  		},
  1431  		{
  1432  			Name:            "Returns error when can't unmarshal assignment value",
  1433  			TransactionerFn: txGen.ThatDoesntExpectCommit,
  1434  			ServiceFn: func() *automock.FormationAssignmentService {
  1435  				faSvc := &automock.FormationAssignmentService{}
  1436  				faSvc.On("ListByFormationIDsNoPaging", txtest.CtxWithDBMatcher(), formationIDs).Return(faPagesWithUnmarshallableError, nil).Once()
  1437  				return faSvc
  1438  			},
  1439  			Params:         []dataloader.ParamFormationStatus{firstFormationStatusParams, secondFormationStatusParams, thirdFormationStatusParams, fourthPageFormations},
  1440  			ExpectedResult: nil,
  1441  			ExpectedErr:    errors.New("while unmarshalling formation assignment error with assignment ID \"FormationAssignmentID-4\""),
  1442  		},
  1443  		{
  1444  			Name:            "Returns error when transaction commit failed",
  1445  			TransactionerFn: txGen.ThatFailsOnCommit,
  1446  			ServiceFn: func() *automock.FormationAssignmentService {
  1447  				faSvc := &automock.FormationAssignmentService{}
  1448  				faSvc.On("ListByFormationIDsNoPaging", txtest.CtxWithDBMatcher(), formationIDs).Return(fasPerFormation, nil).Once()
  1449  				return faSvc
  1450  			},
  1451  			Params:         []dataloader.ParamFormationStatus{firstFormationStatusParams, secondFormationStatusParams, thirdFormationStatusParams, fourthPageFormations},
  1452  			ExpectedResult: nil,
  1453  			ExpectedErr:    testErr,
  1454  		},
  1455  	}
  1456  
  1457  	for _, testCase := range testCases {
  1458  		t.Run(testCase.Name, func(t *testing.T) {
  1459  			// GIVEN
  1460  			persist, transact := testCase.TransactionerFn()
  1461  
  1462  			faSvc := &automock.FormationAssignmentService{}
  1463  			if testCase.ServiceFn != nil {
  1464  				faSvc = testCase.ServiceFn()
  1465  			}
  1466  
  1467  			resolver := formation.NewResolver(transact, nil, nil, faSvc, nil, nil)
  1468  
  1469  			params := make([]dataloader.ParamFormationStatus, 0, len(testCase.Params))
  1470  			for _, param := range testCase.Params {
  1471  				param.Ctx = ctx
  1472  				params = append(params, param)
  1473  			}
  1474  
  1475  			// WHEN
  1476  			result, errs := resolver.StatusDataLoader(params)
  1477  
  1478  			// THEN
  1479  			require.EqualValues(t, testCase.ExpectedResult, result)
  1480  			if errs != nil {
  1481  				require.Contains(t, errs[0].Error(), testCase.ExpectedErr.Error())
  1482  			}
  1483  
  1484  			mock.AssertExpectationsForObjects(t, faSvc, transact, persist)
  1485  		})
  1486  	}
  1487  
  1488  	t.Run("Returns error when there are no formations IDs", func(t *testing.T) {
  1489  		resolver := formation.NewResolver(nil, nil, nil, nil, nil, nil)
  1490  
  1491  		// WHEN
  1492  		_, errs := resolver.StatusDataLoader([]dataloader.ParamFormationStatus{})
  1493  
  1494  		// THEN
  1495  		require.Error(t, errs[0])
  1496  		require.EqualError(t, errs[0], apperrors.NewInternalError("No Formations found").Error())
  1497  	})
  1498  }
  1499  
  1500  func TestResynchronizeFormationNotifications(t *testing.T) {
  1501  	txGen := txtest.NewTransactionContextGenerator(testErr)
  1502  
  1503  	ctx := tenant.SaveToContext(context.TODO(), TntInternalID, TntExternalID)
  1504  
  1505  	testCases := []struct {
  1506  		Name              string
  1507  		Context           context.Context
  1508  		TxFn              func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner)
  1509  		FormationService  func() *automock.Service
  1510  		Converter         func() *automock.Converter
  1511  		ExpectedFormation *graphql.Formation
  1512  		ExpectedErrorMsg  string
  1513  	}{
  1514  		{
  1515  			Name: "successfully resynchronized formation notifications",
  1516  			TxFn: txGen.ThatSucceeds,
  1517  			FormationService: func() *automock.Service {
  1518  				svc := &automock.Service{}
  1519  
  1520  				svc.On("ResynchronizeFormationNotifications", contextThatHasTenant(TntInternalID), FormationID).Return(&modelFormation, nil).Once()
  1521  
  1522  				return svc
  1523  			},
  1524  			Converter: func() *automock.Converter {
  1525  				conv := &automock.Converter{}
  1526  				conv.On("ToGraphQL", &modelFormation).Return(&graphqlFormation, nil).Once()
  1527  				return conv
  1528  			},
  1529  			ExpectedFormation: &graphqlFormation,
  1530  		},
  1531  		{
  1532  			Name: "failed during resynchronizing",
  1533  			TxFn: txGen.ThatDoesntExpectCommit,
  1534  			FormationService: func() *automock.Service {
  1535  				svc := &automock.Service{}
  1536  
  1537  				svc.On("ResynchronizeFormationNotifications", contextThatHasTenant(TntInternalID), FormationID).Return(nil, testErr)
  1538  
  1539  				return svc
  1540  			},
  1541  			ExpectedErrorMsg: testErr.Error(),
  1542  		},
  1543  		{
  1544  			Name: "failed to commit after resynchronizing",
  1545  			TxFn: txGen.ThatFailsOnCommit,
  1546  			FormationService: func() *automock.Service {
  1547  				svc := &automock.Service{}
  1548  
  1549  				svc.On("ResynchronizeFormationNotifications", contextThatHasTenant(TntInternalID), FormationID).Return(&modelFormation, nil).Once()
  1550  
  1551  				return svc
  1552  			},
  1553  			ExpectedErrorMsg: testErr.Error(),
  1554  		},
  1555  		{
  1556  			Name:             "returns error when can not start db transaction",
  1557  			TxFn:             txGen.ThatFailsOnBegin,
  1558  			ExpectedErrorMsg: testErr.Error(),
  1559  		},
  1560  	}
  1561  	for _, testCase := range testCases {
  1562  		t.Run(testCase.Name, func(t *testing.T) {
  1563  			conv := unusedConverter()
  1564  			if testCase.Converter != nil {
  1565  				conv = testCase.Converter()
  1566  			}
  1567  			formationService := unusedService()
  1568  			if testCase.FormationService != nil {
  1569  				formationService = testCase.FormationService()
  1570  			}
  1571  			persist, transact := testCase.TxFn()
  1572  
  1573  			resolver := formation.NewResolver(transact, formationService, conv, nil, nil, nil)
  1574  
  1575  			// WHEN
  1576  			resultFormationModel, err := resolver.ResynchronizeFormationNotifications(ctx, FormationID)
  1577  
  1578  			if testCase.ExpectedErrorMsg != "" {
  1579  				require.Error(t, err)
  1580  				require.Contains(t, err.Error(), testCase.ExpectedErrorMsg)
  1581  				require.Nil(t, resultFormationModel)
  1582  			} else {
  1583  				require.NoError(t, err)
  1584  				require.Equal(t, testCase.ExpectedFormation, resultFormationModel)
  1585  			}
  1586  			mock.AssertExpectationsForObjects(t, conv, formationService, persist, transact)
  1587  		})
  1588  	}
  1589  }
  1590  
  1591  func contextThatHasTenant(expectedTenant string) interface{} {
  1592  	return mock.MatchedBy(func(actual context.Context) bool {
  1593  		actualTenant, err := tenant.LoadFromContext(actual)
  1594  		if err != nil {
  1595  			return false
  1596  		}
  1597  		return actualTenant == expectedTenant
  1598  	})
  1599  }
  1600  
  1601  func addSuffix(str, suffix string) *string {
  1602  	res := str + suffix
  1603  	return &res
  1604  }