github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/pkg/operation/directive_test.go (about)

     1  /*
     2   * Copyright 2020 The Compass Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package operation_test
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"net/http"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    29  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    30  
    31  	gqlgen "github.com/99designs/gqlgen/graphql"
    32  	"github.com/kyma-incubator/compass/components/director/pkg/header"
    33  	tx_automock "github.com/kyma-incubator/compass/components/director/pkg/persistence/automock"
    34  	"github.com/kyma-incubator/compass/components/director/pkg/persistence/txtest"
    35  	"github.com/kyma-incubator/compass/components/director/pkg/webhook"
    36  	"github.com/vektah/gqlparser/v2/ast"
    37  
    38  	"github.com/kyma-incubator/compass/components/director/internal/model"
    39  	"github.com/kyma-incubator/compass/components/director/pkg/graphql"
    40  	"github.com/kyma-incubator/compass/components/director/pkg/operation"
    41  	"github.com/kyma-incubator/compass/components/director/pkg/operation/automock"
    42  	"github.com/stretchr/testify/mock"
    43  	"github.com/stretchr/testify/require"
    44  )
    45  
    46  const (
    47  	webhookID1 = "fe8ce7c6-919f-40f0-b78b-b1662dfbac64"
    48  	webhookID2 = "4f40d0cf-5a33-4895-aa03-528ab0982fb2"
    49  	webhookID3 = "dbd54239-5188-4bea-8826-bc04587a118e"
    50  )
    51  
    52  var (
    53  	resourceIDField             = "id"
    54  	whTypeApplicationRegister   = graphql.WebhookTypeRegisterApplication
    55  	whTypeApplicationUnregister = graphql.WebhookTypeUnregisterApplication
    56  
    57  	mockedHeaders = http.Header{
    58  		"key": []string{"value"},
    59  	}
    60  )
    61  
    62  func TestHandleOperation(t *testing.T) {
    63  	t.Run("missing operation mode param causes internal server error", func(t *testing.T) {
    64  		// GIVEN
    65  		ctx := context.Background()
    66  		rCtx := &gqlgen.FieldContext{
    67  			Object: "RegisterApplication",
    68  			Field:  gqlgen.CollectedField{},
    69  			Args: map[string]interface{}{
    70  				operation.ModeParam: "notModeParam",
    71  			},
    72  			IsMethod: false,
    73  		}
    74  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
    75  
    76  		directive := operation.NewDirective(nil, nil, nil, nil, nil, nil)
    77  
    78  		// WHEN
    79  		_, err := directive.HandleOperation(ctx, nil, nil, graphql.OperationTypeCreate, &whTypeApplicationRegister, &resourceIDField)
    80  		// THEN
    81  		require.Error(t, err, fmt.Sprintf("could not get %s parameter", operation.ModeParam))
    82  	})
    83  
    84  	t.Run("when mutation is in SYNC mode there is no operation in context but transaction fails to begin", func(t *testing.T) {
    85  		// GIVEN
    86  		ctx := context.Background()
    87  		operationMode := graphql.OperationModeSync
    88  		rCtx := &gqlgen.FieldContext{
    89  			Object:   "RegisterApplication",
    90  			Field:    gqlgen.CollectedField{},
    91  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
    92  			IsMethod: false,
    93  		}
    94  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
    95  
    96  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(mockedError()).ThatFailsOnBegin()
    97  		defer mockedTx.AssertExpectations(t)
    98  		defer mockedTransactioner.AssertExpectations(t)
    99  
   100  		directive := operation.NewDirective(mockedTransactioner, nil, nil, nil, nil, nil)
   101  
   102  		// WHEN
   103  		res, err := directive.HandleOperation(ctx, nil, nil, graphql.OperationTypeCreate, &whTypeApplicationRegister, &resourceIDField)
   104  		// THEN
   105  		require.Error(t, err, mockedError().Error(), "Unable to initialize database operation")
   106  		require.Empty(t, res)
   107  	})
   108  
   109  	t.Run("when mutation is in SYNC mode there is no operation in context but request fails should roll-back", func(t *testing.T) {
   110  		// GIVEN
   111  		ctx := context.Background()
   112  		operationMode := graphql.OperationModeSync
   113  		rCtx := &gqlgen.FieldContext{
   114  			Object:   "RegisterApplication",
   115  			Field:    gqlgen.CollectedField{},
   116  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   117  			IsMethod: false,
   118  		}
   119  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   120  
   121  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(mockedError()).ThatDoesntExpectCommit()
   122  		defer mockedTx.AssertExpectations(t)
   123  		defer mockedTransactioner.AssertExpectations(t)
   124  
   125  		directive := operation.NewDirective(mockedTransactioner, nil, nil, nil, nil, nil)
   126  
   127  		dummyResolver := &dummyResolver{}
   128  
   129  		// WHEN
   130  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.ErrorResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   131  		// THEN
   132  		require.Error(t, err, mockedError().Error(), "Unable to process operation")
   133  		require.Empty(t, res)
   134  		require.Equal(t, graphql.OperationModeSync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   135  	})
   136  
   137  	t.Run("when mutation is in SYNC mode there is no operation in context but transaction fails to commit should roll-back", func(t *testing.T) {
   138  		// GIVEN
   139  		ctx := context.Background()
   140  		operationMode := graphql.OperationModeSync
   141  		rCtx := &gqlgen.FieldContext{
   142  			Object:   "RegisterApplication",
   143  			Field:    gqlgen.CollectedField{},
   144  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   145  			IsMethod: false,
   146  		}
   147  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   148  
   149  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(mockedError()).ThatFailsOnCommit()
   150  		defer mockedTx.AssertExpectations(t)
   151  		defer mockedTransactioner.AssertExpectations(t)
   152  
   153  		directive := operation.NewDirective(mockedTransactioner, nil, nil, nil, nil, nil)
   154  
   155  		dummyResolver := &dummyResolver{}
   156  
   157  		// WHEN
   158  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   159  		// THEN
   160  		require.Error(t, err, mockedError().Error(), "Unable to finalize database operation")
   161  		require.Empty(t, res)
   162  		require.Equal(t, graphql.OperationModeSync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   163  	})
   164  
   165  	t.Run("when mutation is in SYNC mode there is no operation in context and finishes successfully", func(t *testing.T) {
   166  		// GIVEN
   167  		ctx := context.Background()
   168  		operationMode := graphql.OperationModeSync
   169  		rCtx := &gqlgen.FieldContext{
   170  			Object:   "RegisterApplication",
   171  			Field:    gqlgen.CollectedField{},
   172  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   173  			IsMethod: false,
   174  		}
   175  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   176  
   177  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatSucceeds()
   178  		defer mockedTx.AssertExpectations(t)
   179  		defer mockedTransactioner.AssertExpectations(t)
   180  
   181  		directive := operation.NewDirective(mockedTransactioner, nil, nil, nil, nil, nil)
   182  
   183  		dummyResolver := &dummyResolver{}
   184  
   185  		// WHEN
   186  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   187  		// THEN
   188  		require.NoError(t, err)
   189  		require.Equal(t, mockedNextResponse(), res)
   190  		require.Equal(t, graphql.OperationModeSync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   191  	})
   192  
   193  	t.Run("when mutation is in ASYNC mode, there is operation in context but request fails should roll-back", func(t *testing.T) {
   194  		// GIVEN
   195  		ctx := context.Background()
   196  		operationMode := graphql.OperationModeAsync
   197  		rCtx := &gqlgen.FieldContext{
   198  			Object: "RegisterApplication",
   199  			Field: gqlgen.CollectedField{
   200  				Field: &ast.Field{
   201  					Name: "registerApplication",
   202  				},
   203  			},
   204  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   205  			IsMethod: false,
   206  		}
   207  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   208  
   209  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   210  		defer mockedTx.AssertExpectations(t)
   211  		defer mockedTransactioner.AssertExpectations(t)
   212  
   213  		directive := operation.NewDirective(mockedTransactioner, nil, nil, nil, nil, nil)
   214  
   215  		dummyResolver := &dummyResolver{}
   216  
   217  		// WHEN
   218  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.ErrorResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   219  		// THEN
   220  		require.Error(t, err, mockedError().Error(), "Unable to process operation")
   221  		require.Empty(t, res)
   222  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   223  
   224  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   225  		assertNoOperationsInCtx(t, opsFromCtx)
   226  	})
   227  
   228  	t.Run("when mutation is in ASYNC mode, there is operation in context but response is not an Entity type should roll-back", func(t *testing.T) {
   229  		// GIVEN
   230  		ctx := context.Background()
   231  		operationMode := graphql.OperationModeAsync
   232  		rCtx := &gqlgen.FieldContext{
   233  			Object: "RegisterApplication",
   234  			Field: gqlgen.CollectedField{
   235  				Field: &ast.Field{
   236  					Name: "registerApplication",
   237  				},
   238  			},
   239  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   240  			IsMethod: false,
   241  		}
   242  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   243  
   244  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   245  		defer mockedTx.AssertExpectations(t)
   246  		defer mockedTransactioner.AssertExpectations(t)
   247  
   248  		directive := operation.NewDirective(mockedTransactioner, nil, nil, nil, nil, nil)
   249  
   250  		dummyResolver := &dummyResolver{}
   251  
   252  		// WHEN
   253  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.NonEntityResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   254  		// THEN
   255  		require.Error(t, err, mockedError().Error(), "Failed to process operation")
   256  		require.Empty(t, res)
   257  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   258  
   259  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   260  		assertNoOperationsInCtx(t, opsFromCtx)
   261  	})
   262  
   263  	t.Run("when mutation is in ASYNC mode, there is operation in context but server Director fails to fetch webhooks should roll-back", func(t *testing.T) {
   264  		// GIVEN
   265  		ctx := context.Background()
   266  		operationMode := graphql.OperationModeAsync
   267  		rCtx := &gqlgen.FieldContext{
   268  			Object: "RegisterApplication",
   269  			Field: gqlgen.CollectedField{
   270  				Field: &ast.Field{
   271  					Name: "registerApplication",
   272  				},
   273  			},
   274  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   275  			IsMethod: false,
   276  		}
   277  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   278  
   279  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   280  		defer mockedTx.AssertExpectations(t)
   281  		defer mockedTransactioner.AssertExpectations(t)
   282  
   283  		directive := operation.NewDirective(mockedTransactioner, func(_ context.Context, _ string) ([]*model.Webhook, error) {
   284  			return nil, mockedError()
   285  		}, nil, nil, nil, nil)
   286  
   287  		dummyResolver := &dummyResolver{}
   288  
   289  		// WHEN
   290  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.NonEntityResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   291  		// THEN
   292  		require.Error(t, err, mockedError().Error(), "Unable to retrieve webhooks")
   293  		require.Empty(t, res)
   294  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   295  
   296  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   297  		assertNoOperationsInCtx(t, opsFromCtx)
   298  	})
   299  
   300  	t.Run("when mutation is in ASYNC mode, there is operation in context but Director fails to prepare operation request due to missing tenant data should roll-back", func(t *testing.T) {
   301  		// GIVEN
   302  		ctx := context.Background()
   303  		operationMode := graphql.OperationModeAsync
   304  		rCtx := &gqlgen.FieldContext{
   305  			Object: "RegisterApplication",
   306  			Field: gqlgen.CollectedField{
   307  				Field: &ast.Field{
   308  					Name: "registerApplication",
   309  				},
   310  			},
   311  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   312  			IsMethod: false,
   313  		}
   314  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   315  
   316  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   317  		defer mockedTx.AssertExpectations(t)
   318  		defer mockedTransactioner.AssertExpectations(t)
   319  
   320  		directive := operation.NewDirective(mockedTransactioner, mockedWebhooksResponse, nil, mockedEmptyResourceUpdaterFunc, func(_ context.Context) (string, error) {
   321  			return "", mockedError()
   322  		}, nil)
   323  
   324  		dummyResolver := &dummyResolver{}
   325  
   326  		// WHEN
   327  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   328  		// THEN
   329  		require.Error(t, err, mockedError().Error(), "Unable to prepare webhook request data")
   330  		require.Empty(t, res)
   331  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   332  
   333  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   334  		assertNoOperationsInCtx(t, opsFromCtx)
   335  	})
   336  
   337  	t.Run("when mutation is in ASYNC mode, there is operation in context but Director fails to prepare operation request due unsupported webhook provider type should roll-back", func(t *testing.T) {
   338  		// GIVEN
   339  		ctx := context.Background()
   340  		operationMode := graphql.OperationModeAsync
   341  		rCtx := &gqlgen.FieldContext{
   342  			Object: "RegisterApplication",
   343  			Field: gqlgen.CollectedField{
   344  				Field: &ast.Field{
   345  					Name: "registerApplication",
   346  				},
   347  			},
   348  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   349  			IsMethod: false,
   350  		}
   351  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   352  
   353  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   354  		defer mockedTx.AssertExpectations(t)
   355  		defer mockedTransactioner.AssertExpectations(t)
   356  
   357  		directive := operation.NewDirective(mockedTransactioner, mockedWebhooksResponse, nil, mockedEmptyResourceUpdaterFunc, mockedTenantLoaderFunc, nil)
   358  
   359  		dummyResolver := &dummyResolver{}
   360  
   361  		// WHEN
   362  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.NonWebhookProviderResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   363  		// THEN
   364  		require.Error(t, err, mockedError().Error(), "Unable to prepare webhook request data")
   365  		require.Empty(t, res)
   366  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   367  
   368  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   369  		assertNoOperationsInCtx(t, opsFromCtx)
   370  	})
   371  
   372  	t.Run("when mutation is in ASYNC mode, there is operation in context but Director fails to prepare operation request due failure to missing request headers should roll-back", func(t *testing.T) {
   373  		// GIVEN
   374  		ctx := context.Background()
   375  		operationMode := graphql.OperationModeAsync
   376  		rCtx := &gqlgen.FieldContext{
   377  			Object: "RegisterApplication",
   378  			Field: gqlgen.CollectedField{
   379  				Field: &ast.Field{
   380  					Name: "registerApplication",
   381  				},
   382  			},
   383  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   384  			IsMethod: false,
   385  		}
   386  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   387  
   388  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   389  		defer mockedTx.AssertExpectations(t)
   390  		defer mockedTransactioner.AssertExpectations(t)
   391  
   392  		directive := operation.NewDirective(mockedTransactioner, mockedWebhooksResponse, nil, mockedEmptyResourceUpdaterFunc, mockedTenantLoaderFunc, nil)
   393  
   394  		dummyResolver := &dummyResolver{}
   395  
   396  		// WHEN
   397  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   398  		// THEN
   399  		require.Error(t, err, mockedError().Error(), "Unable to prepare webhook request data")
   400  		require.Empty(t, res)
   401  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   402  
   403  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   404  		assertNoOperationsInCtx(t, opsFromCtx)
   405  	})
   406  
   407  	t.Run("when mutation is in ASYNC mode, there is operation in context but Director fails to prepare operation request due missing webhooks", func(t *testing.T) {
   408  		// GIVEN
   409  		ctx := context.Background()
   410  		operationMode := graphql.OperationModeAsync
   411  		rCtx := &gqlgen.FieldContext{
   412  			Object: "RegisterApplication",
   413  			Field: gqlgen.CollectedField{
   414  				Field: &ast.Field{
   415  					Name: "registerApplication",
   416  				},
   417  			},
   418  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   419  			IsMethod: false,
   420  		}
   421  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   422  
   423  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   424  		defer mockedTx.AssertExpectations(t)
   425  		defer mockedTransactioner.AssertExpectations(t)
   426  
   427  		directive := operation.NewDirective(mockedTransactioner, func(_ context.Context, _ string) ([]*model.Webhook, error) {
   428  			return nil, mockedError()
   429  		}, nil, mockedEmptyResourceUpdaterFunc, mockedTenantLoaderFunc, nil)
   430  
   431  		dummyResolver := &dummyResolver{}
   432  
   433  		// WHEN
   434  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, &resourceIDField)
   435  		// THEN
   436  		require.Error(t, err, "Unable to prepare webhooks")
   437  		require.Empty(t, res)
   438  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   439  	})
   440  
   441  	t.Run("when mutation is in ASYNC mode, there is operation in context but Director fails to prepare operation request due multiple webhooks found", func(t *testing.T) {
   442  		// GIVEN
   443  		ctx := context.Background()
   444  		operationMode := graphql.OperationModeAsync
   445  		rCtx := &gqlgen.FieldContext{
   446  			Object: "RegisterApplication",
   447  			Field: gqlgen.CollectedField{
   448  				Field: &ast.Field{
   449  					Name: "registerApplication",
   450  				},
   451  			},
   452  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   453  			IsMethod: false,
   454  		}
   455  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   456  
   457  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   458  		defer mockedTx.AssertExpectations(t)
   459  		defer mockedTransactioner.AssertExpectations(t)
   460  
   461  		directive := operation.NewDirective(mockedTransactioner, func(_ context.Context, _ string) ([]*model.Webhook, error) {
   462  			return []*model.Webhook{
   463  				{ID: webhookID1, Type: model.WebhookTypeRegisterApplication},
   464  				{ID: webhookID2, Type: model.WebhookTypeRegisterApplication},
   465  				{ID: webhookID3, Type: model.WebhookTypeRegisterApplication},
   466  			}, nil
   467  		}, nil, mockedEmptyResourceUpdaterFunc, mockedTenantLoaderFunc, nil)
   468  
   469  		dummyResolver := &dummyResolver{}
   470  
   471  		// WHEN
   472  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, &resourceIDField)
   473  		// THEN
   474  		require.Error(t, err, "Unable to prepare webhooks")
   475  		require.Empty(t, res)
   476  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   477  	})
   478  
   479  	t.Run("when mutation is in ASYNC mode, there is operation in context but Scheduler fails to schedule should roll-back", func(t *testing.T) {
   480  		// GIVEN
   481  		ctx := context.Background()
   482  		operationMode := graphql.OperationModeAsync
   483  		rCtx := &gqlgen.FieldContext{
   484  			Object: "RegisterApplication",
   485  			Field: gqlgen.CollectedField{
   486  				Field: &ast.Field{
   487  					Name: "registerApplication",
   488  				},
   489  			},
   490  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   491  			IsMethod: false,
   492  		}
   493  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   494  		ctx = context.WithValue(ctx, header.ContextKey, mockedHeaders)
   495  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   496  		defer mockedTx.AssertExpectations(t)
   497  		defer mockedTransactioner.AssertExpectations(t)
   498  
   499  		mockedScheduler := &automock.Scheduler{}
   500  		mockedScheduler.On("Schedule", mock.Anything, mock.Anything).Return("", mockedError())
   501  		defer mockedScheduler.AssertExpectations(t)
   502  
   503  		directive := operation.NewDirective(mockedTransactioner, mockedWebhooksResponse, nil, mockedEmptyResourceUpdaterFunc, mockedTenantLoaderFunc, mockedScheduler)
   504  
   505  		dummyResolver := &dummyResolver{}
   506  
   507  		// WHEN
   508  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   509  		// THEN
   510  		require.Error(t, err, mockedError().Error(), "Unable to schedule operation")
   511  		require.Empty(t, res)
   512  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   513  
   514  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   515  		assertNoOperationsInCtx(t, opsFromCtx)
   516  	})
   517  
   518  	t.Run("when mutation is in ASYNC mode, there is operation in context but transaction commit fails should roll-back", func(t *testing.T) {
   519  		// GIVEN
   520  		ctx := context.Background()
   521  		operationMode := graphql.OperationModeAsync
   522  		rCtx := &gqlgen.FieldContext{
   523  			Object: "RegisterApplication",
   524  			Field: gqlgen.CollectedField{
   525  				Field: &ast.Field{
   526  					Name: "registerApplication",
   527  				},
   528  			},
   529  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   530  			IsMethod: false,
   531  		}
   532  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   533  		ctx = context.WithValue(ctx, header.ContextKey, mockedHeaders)
   534  
   535  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(mockedError()).ThatFailsOnCommit()
   536  		defer mockedTx.AssertExpectations(t)
   537  		defer mockedTransactioner.AssertExpectations(t)
   538  
   539  		testID := "test-id"
   540  		mockedScheduler := &automock.Scheduler{}
   541  		mockedScheduler.On("Schedule", mock.Anything, mock.Anything).Return(testID, nil)
   542  		defer mockedScheduler.AssertExpectations(t)
   543  
   544  		directive := operation.NewDirective(mockedTransactioner, mockedWebhooksResponse, nil, mockedEmptyResourceUpdaterFunc, mockedTenantLoaderFunc, mockedScheduler)
   545  
   546  		dummyResolver := &dummyResolver{}
   547  
   548  		// WHEN
   549  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   550  		// THEN
   551  		require.Error(t, err, mockedError().Error(), "Unable to finalize database operation")
   552  		require.Empty(t, res)
   553  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   554  
   555  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   556  		assertNoOperationsInCtx(t, opsFromCtx)
   557  	})
   558  
   559  	t.Run("when async mode is disabled it should roll-back", func(t *testing.T) {
   560  		operationType := graphql.OperationTypeCreate
   561  		operationCategory := "registerApplication"
   562  
   563  		// GIVEN
   564  		ctx := context.Background()
   565  		operationMode := graphql.OperationModeAsync
   566  		rCtx := &gqlgen.FieldContext{
   567  			Object: "RegisterApplication",
   568  			Field: gqlgen.CollectedField{
   569  				Field: &ast.Field{
   570  					Name: operationCategory,
   571  				},
   572  			},
   573  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   574  			IsMethod: false,
   575  		}
   576  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   577  		ctx = context.WithValue(ctx, header.ContextKey, mockedHeaders)
   578  
   579  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   580  		defer mockedTx.AssertExpectations(t)
   581  		defer mockedTransactioner.AssertExpectations(t)
   582  		dummyResolver := &dummyResolver{}
   583  		scheduler := &operation.DisabledScheduler{}
   584  		directive := operation.NewDirective(mockedTransactioner, mockedWebhooksResponse, nil, mockedEmptyResourceUpdaterFunc, mockedTenantLoaderFunc, scheduler)
   585  
   586  		// WHEN
   587  		_, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, operationType, nil, nil)
   588  
   589  		// THEN
   590  		require.Error(t, err)
   591  		require.Contains(t, err.Error(), "Unable to schedule operation")
   592  	})
   593  
   594  	t.Run("when mutation is in ASYNC mode, there is operation in context but webhook fetcher fails should roll-back", func(t *testing.T) {
   595  		operationType := graphql.OperationTypeCreate
   596  		operationCategory := "registerApplication"
   597  
   598  		// GIVEN
   599  		ctx := context.Background()
   600  		operationMode := graphql.OperationModeAsync
   601  		rCtx := &gqlgen.FieldContext{
   602  			Object: "RegisterApplication",
   603  			Field: gqlgen.CollectedField{
   604  				Field: &ast.Field{
   605  					Name: operationCategory,
   606  				},
   607  			},
   608  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   609  			IsMethod: false,
   610  		}
   611  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   612  		ctx = context.WithValue(ctx, header.ContextKey, mockedHeaders)
   613  
   614  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   615  		defer mockedTx.AssertExpectations(t)
   616  		defer mockedTransactioner.AssertExpectations(t)
   617  
   618  		dummyResolver := &dummyResolver{}
   619  
   620  		errorWebhooksResponse := func(_ context.Context, _ string) ([]*model.Webhook, error) {
   621  			return nil, errors.New("fail")
   622  		}
   623  
   624  		mockedScheduler := &automock.Scheduler{}
   625  		defer mockedScheduler.AssertExpectations(t)
   626  
   627  		directive := operation.NewDirective(mockedTransactioner, errorWebhooksResponse, nil, mockedEmptyResourceUpdaterFunc, mockedTenantLoaderFunc, mockedScheduler)
   628  
   629  		// WHEN
   630  		_, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, operationType, &whTypeApplicationRegister, nil)
   631  		// THEN
   632  		require.Error(t, err)
   633  		require.Contains(t, err.Error(), "Unable to retrieve webhooks")
   634  	})
   635  
   636  	t.Run("when mutation is in ASYNC mode, there is operation in context and finishes successfully", func(t *testing.T) {
   637  		operationType := operation.OperationTypeCreate
   638  		operationCategory := "registerApplication"
   639  
   640  		// GIVEN
   641  		ctx := context.Background()
   642  		operationMode := graphql.OperationModeAsync
   643  		rCtx := &gqlgen.FieldContext{
   644  			Object: "RegisterApplication",
   645  			Field: gqlgen.CollectedField{
   646  				Field: &ast.Field{
   647  					Name: operationCategory,
   648  				},
   649  			},
   650  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   651  			IsMethod: false,
   652  		}
   653  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   654  		ctx = context.WithValue(ctx, header.ContextKey, mockedHeaders)
   655  
   656  		mockedScheduler := &automock.Scheduler{}
   657  		mockedScheduler.On("Schedule", mock.Anything, mock.Anything).Return(operationID, nil)
   658  		defer mockedScheduler.AssertExpectations(t)
   659  
   660  		webhookType := whTypeApplicationRegister
   661  
   662  		testCases := []struct {
   663  			Name               string
   664  			Webhooks           []*model.Webhook
   665  			ExpectedWebhookIDs []string
   666  		}{
   667  			{
   668  				Name: "when all webhooks match their IDs should be present in the operation",
   669  				Webhooks: []*model.Webhook{
   670  					{ID: webhookID1, Type: model.WebhookType(webhookType)},
   671  				},
   672  				ExpectedWebhookIDs: []string{webhookID1},
   673  			},
   674  			{
   675  				Name: "when a single webhook matches its ID should be present in the operation",
   676  				Webhooks: []*model.Webhook{
   677  					{ID: webhookID1, Type: model.WebhookType(webhookType)},
   678  					{ID: webhookID2, Type: model.WebhookType(graphql.WebhookTypeUnregisterApplication)},
   679  					{ID: webhookID3, Type: model.WebhookType(graphql.WebhookTypeUnregisterApplication)},
   680  				},
   681  				ExpectedWebhookIDs: []string{webhookID1},
   682  			},
   683  		}
   684  
   685  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatSucceedsMultipleTimes(len(testCases))
   686  		defer mockedTx.AssertExpectations(t)
   687  		defer mockedTransactioner.AssertExpectations(t)
   688  
   689  		for _, testCase := range testCases {
   690  			t.Run(testCase.Name, func(t *testing.T) {
   691  				directive := operation.NewDirective(mockedTransactioner, func(_ context.Context, _ string) ([]*model.Webhook, error) {
   692  					return testCase.Webhooks, nil
   693  				}, nil, mockedEmptyResourceUpdaterFunc, mockedTenantLoaderFunc, mockedScheduler)
   694  
   695  				dummyResolver := &dummyResolver{}
   696  
   697  				// WHEN
   698  				res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &webhookType, nil)
   699  
   700  				// THEN
   701  				require.NoError(t, err)
   702  				require.Equal(t, mockedNextResponse(), res)
   703  				require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   704  
   705  				opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   706  				operations, ok := opsFromCtx.(*[]*operation.Operation)
   707  				require.True(t, ok)
   708  				require.Len(t, *operations, 1)
   709  
   710  				op := (*operations)[0]
   711  				require.Equal(t, operationID, op.OperationID)
   712  				require.Equal(t, operationType, op.OperationType)
   713  				require.Equal(t, operationCategory, op.OperationCategory)
   714  
   715  				headers := make(map[string]string)
   716  				for key, value := range mockedHeaders {
   717  					headers[key] = value[0]
   718  				}
   719  
   720  				expectedRequestObject := &webhook.ApplicationLifecycleWebhookRequestObject{
   721  					Application: mockedNextResponse().(webhook.Resource),
   722  					TenantID:    tenantID,
   723  					Headers:     headers,
   724  				}
   725  
   726  				expectedObj, err := json.Marshal(expectedRequestObject)
   727  				require.NoError(t, err)
   728  
   729  				require.Equal(t, string(expectedObj), op.RequestObject)
   730  
   731  				require.Len(t, op.WebhookIDs, len(testCase.ExpectedWebhookIDs))
   732  				require.Equal(t, testCase.ExpectedWebhookIDs, op.WebhookIDs)
   733  			})
   734  		}
   735  	})
   736  
   737  	t.Run("when mutation is in ASYNC mode, there is operation in context and resource updater func fails should return error", func(t *testing.T) {
   738  		// GIVEN
   739  		ctx := context.Background()
   740  		operationMode := graphql.OperationModeAsync
   741  		rCtx := &gqlgen.FieldContext{
   742  			Object: "RegisterApplication",
   743  			Field: gqlgen.CollectedField{
   744  				Field: &ast.Field{
   745  					Name: "registerApplication",
   746  				},
   747  			},
   748  			Args:     map[string]interface{}{operation.ModeParam: &operationMode},
   749  			IsMethod: false,
   750  		}
   751  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   752  
   753  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
   754  		defer mockedTx.AssertExpectations(t)
   755  		defer mockedTransactioner.AssertExpectations(t)
   756  
   757  		directive := operation.NewDirective(mockedTransactioner, mockedWebhooksResponse, nil, mockedResourceUpdaterFuncWithError, mockedTenantLoaderFunc, nil)
   758  
   759  		dummyResolver := &dummyResolver{}
   760  
   761  		// WHEN
   762  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, nil)
   763  		// THEN
   764  		require.Error(t, err)
   765  		require.Contains(t, err.Error(), "Unable to update resource application with id")
   766  		require.Empty(t, res)
   767  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   768  
   769  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   770  		assertNoOperationsInCtx(t, opsFromCtx)
   771  	})
   772  
   773  	t.Run("when mutation is in ASYNC mode, there is operation in context and resource updater func is executed with CREATE operation type should finish successfully and update application status to CREATING", func(t *testing.T) {
   774  		// GIVEN
   775  		ctx := context.Background()
   776  		operationMode := graphql.OperationModeAsync
   777  		operationCategory := "registerApplication"
   778  		rCtx := &gqlgen.FieldContext{
   779  			Object: "RegisterApplication",
   780  			Field: gqlgen.CollectedField{
   781  				Field: &ast.Field{
   782  					Name: operationCategory,
   783  				},
   784  			},
   785  			Args:     map[string]interface{}{operation.ModeParam: &operationMode, resourceIDField: resourceID},
   786  			IsMethod: false,
   787  		}
   788  
   789  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   790  		ctx = context.WithValue(ctx, header.ContextKey, mockedHeaders)
   791  
   792  		mockedScheduler := &automock.Scheduler{}
   793  		mockedScheduler.On("Schedule", mock.Anything, mock.Anything).Return(operationID, nil)
   794  		defer mockedScheduler.AssertExpectations(t)
   795  
   796  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatSucceeds()
   797  		defer mockedTx.AssertExpectations(t)
   798  		defer mockedTransactioner.AssertExpectations(t)
   799  
   800  		directive := operation.NewDirective(mockedTransactioner, mockedWebhooksResponse, mockedResourceFetcherFunc, func(ctx context.Context, id string, ready bool, errorMsg *string, appStatusCondition model.ApplicationStatusCondition) error {
   801  			require.NotNil(t, ctx)
   802  			require.Equal(t, resourceID, id)
   803  			require.Equal(t, false, ready)
   804  			require.Nil(t, errorMsg)
   805  			require.Equal(t, model.ApplicationStatusConditionCreating, appStatusCondition)
   806  			return nil
   807  		}, mockedTenantLoaderFunc, mockedScheduler)
   808  
   809  		dummyResolver := &dummyResolver{}
   810  
   811  		// WHEN
   812  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, &resourceIDField)
   813  
   814  		// THEN
   815  		require.NoError(t, err)
   816  		require.Equal(t, mockedNextResponse(), res)
   817  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   818  
   819  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   820  		operations, ok := opsFromCtx.(*[]*operation.Operation)
   821  		require.True(t, ok)
   822  		require.Len(t, *operations, 1)
   823  
   824  		op := (*operations)[0]
   825  		require.Equal(t, operationID, op.OperationID)
   826  		require.Equal(t, operation.OperationTypeCreate, op.OperationType)
   827  		require.Equal(t, operationCategory, op.OperationCategory)
   828  
   829  		headers := make(map[string]string)
   830  		for key, value := range mockedHeaders {
   831  			headers[key] = value[0]
   832  		}
   833  
   834  		expectedRequestObject := &webhook.ApplicationLifecycleWebhookRequestObject{
   835  			Application: mockedNextResponse().(webhook.Resource),
   836  			TenantID:    tenantID,
   837  			Headers:     headers,
   838  		}
   839  
   840  		expectedObj, err := json.Marshal(expectedRequestObject)
   841  		require.NoError(t, err)
   842  
   843  		require.Equal(t, string(expectedObj), op.RequestObject)
   844  
   845  		require.Len(t, op.WebhookIDs, 1)
   846  		require.Equal(t, webhookID1, op.WebhookIDs[0])
   847  	})
   848  
   849  	t.Run("when mutation is in ASYNC mode, and no webhooks are provided operation should finish successfully and update application status to CREATING", func(t *testing.T) {
   850  		// GIVEN
   851  		ctx := context.Background()
   852  		operationMode := graphql.OperationModeAsync
   853  		operationCategory := "registerApplication"
   854  		rCtx := &gqlgen.FieldContext{
   855  			Object: "RegisterApplication",
   856  			Field: gqlgen.CollectedField{
   857  				Field: &ast.Field{
   858  					Name: operationCategory,
   859  				},
   860  			},
   861  			Args:     map[string]interface{}{operation.ModeParam: &operationMode, resourceIDField: resourceID},
   862  			IsMethod: false,
   863  		}
   864  
   865  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   866  		ctx = context.WithValue(ctx, header.ContextKey, mockedHeaders)
   867  
   868  		mockedScheduler := &automock.Scheduler{}
   869  		mockedScheduler.On("Schedule", mock.Anything, mock.Anything).Return(operationID, nil)
   870  		defer mockedScheduler.AssertExpectations(t)
   871  
   872  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatSucceeds()
   873  		defer mockedTx.AssertExpectations(t)
   874  		defer mockedTransactioner.AssertExpectations(t)
   875  
   876  		directive := operation.NewDirective(mockedTransactioner, mockedEmptyWebhooksResponse, mockedResourceFetcherFunc, func(ctx context.Context, id string, ready bool, errorMsg *string, appStatusCondition model.ApplicationStatusCondition) error {
   877  			require.NotNil(t, ctx)
   878  			require.Equal(t, resourceID, id)
   879  			require.Equal(t, false, ready)
   880  			require.Nil(t, errorMsg)
   881  			require.Equal(t, model.ApplicationStatusConditionCreating, appStatusCondition)
   882  			return nil
   883  		}, mockedTenantLoaderFunc, mockedScheduler)
   884  
   885  		dummyResolver := &dummyResolver{}
   886  
   887  		// WHEN
   888  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeCreate, &whTypeApplicationRegister, &resourceIDField)
   889  
   890  		// THEN
   891  		require.NoError(t, err)
   892  		require.Equal(t, mockedNextResponse(), res)
   893  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   894  
   895  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   896  		operations, ok := opsFromCtx.(*[]*operation.Operation)
   897  		require.True(t, ok)
   898  		require.Len(t, *operations, 1)
   899  
   900  		op := (*operations)[0]
   901  		require.Equal(t, operationID, op.OperationID)
   902  		require.Equal(t, operation.OperationTypeCreate, op.OperationType)
   903  		require.Equal(t, operationCategory, op.OperationCategory)
   904  
   905  		headers := make(map[string]string)
   906  		for key, value := range mockedHeaders {
   907  			headers[key] = value[0]
   908  		}
   909  
   910  		expectedRequestObject := &webhook.ApplicationLifecycleWebhookRequestObject{
   911  			Application: mockedNextResponse().(webhook.Resource),
   912  			TenantID:    tenantID,
   913  			Headers:     headers,
   914  		}
   915  
   916  		expectedObj, err := json.Marshal(expectedRequestObject)
   917  		require.NoError(t, err)
   918  		require.Equal(t, string(expectedObj), op.RequestObject)
   919  		require.Len(t, op.WebhookIDs, 0)
   920  	})
   921  
   922  	t.Run("when mutation is in ASYNC mode, there is operation in context and resource updater func is executed with UPDATE operation type should finish successfully and update application status to UPDATING", func(t *testing.T) {
   923  		// GIVEN
   924  		ctx := context.Background()
   925  		operationMode := graphql.OperationModeAsync
   926  		operationCategory := "registerApplication"
   927  		rCtx := &gqlgen.FieldContext{
   928  			Object: "RegisterApplication",
   929  			Field: gqlgen.CollectedField{
   930  				Field: &ast.Field{
   931  					Name: operationCategory,
   932  				},
   933  			},
   934  			Args:     map[string]interface{}{operation.ModeParam: &operationMode, resourceIDField: resourceID},
   935  			IsMethod: false,
   936  		}
   937  
   938  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
   939  		ctx = context.WithValue(ctx, header.ContextKey, mockedHeaders)
   940  
   941  		mockedScheduler := &automock.Scheduler{}
   942  		mockedScheduler.On("Schedule", mock.Anything, mock.Anything).Return(operationID, nil)
   943  		defer mockedScheduler.AssertExpectations(t)
   944  
   945  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatSucceeds()
   946  		defer mockedTx.AssertExpectations(t)
   947  		defer mockedTransactioner.AssertExpectations(t)
   948  
   949  		directive := operation.NewDirective(mockedTransactioner, mockedWebhooksResponse, mockedResourceFetcherFunc, func(ctx context.Context, id string, ready bool, errorMsg *string, appStatusCondition model.ApplicationStatusCondition) error {
   950  			require.NotNil(t, ctx)
   951  			require.Equal(t, resourceID, id)
   952  			require.Equal(t, false, ready)
   953  			require.Nil(t, errorMsg)
   954  			require.Equal(t, model.ApplicationStatusConditionUpdating, appStatusCondition)
   955  			return nil
   956  		}, mockedTenantLoaderFunc, mockedScheduler)
   957  
   958  		dummyResolver := &dummyResolver{}
   959  
   960  		// WHEN
   961  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeUpdate, &whTypeApplicationRegister, &resourceIDField)
   962  
   963  		// THEN
   964  		require.NoError(t, err)
   965  		require.Equal(t, mockedNextResponse(), res)
   966  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
   967  
   968  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
   969  		operations, ok := opsFromCtx.(*[]*operation.Operation)
   970  		require.True(t, ok)
   971  		require.Len(t, *operations, 1)
   972  
   973  		op := (*operations)[0]
   974  		require.Equal(t, operationID, op.OperationID)
   975  		require.Equal(t, operation.OperationTypeUpdate, op.OperationType)
   976  		require.Equal(t, operationCategory, op.OperationCategory)
   977  
   978  		headers := make(map[string]string)
   979  		for key, value := range mockedHeaders {
   980  			headers[key] = value[0]
   981  		}
   982  
   983  		expectedRequestObject := &webhook.ApplicationLifecycleWebhookRequestObject{
   984  			Application: mockedNextResponse().(webhook.Resource),
   985  			TenantID:    tenantID,
   986  			Headers:     headers,
   987  		}
   988  
   989  		expectedObj, err := json.Marshal(expectedRequestObject)
   990  		require.NoError(t, err)
   991  
   992  		require.Equal(t, string(expectedObj), op.RequestObject)
   993  
   994  		require.Len(t, op.WebhookIDs, 1)
   995  		require.Equal(t, webhookID1, op.WebhookIDs[0])
   996  	})
   997  
   998  	t.Run("when mutation is in ASYNC mode, there is operation in context and resource updater func is executed with DELETE operation type should finish successfully and update application status to DELETING", func(t *testing.T) {
   999  		// GIVEN
  1000  		ctx := context.Background()
  1001  		operationMode := graphql.OperationModeAsync
  1002  		operationCategory := "registerApplication"
  1003  		rCtx := &gqlgen.FieldContext{
  1004  			Object: "RegisterApplication",
  1005  			Field: gqlgen.CollectedField{
  1006  				Field: &ast.Field{
  1007  					Name: operationCategory,
  1008  				},
  1009  			},
  1010  			Args:     map[string]interface{}{operation.ModeParam: &operationMode, resourceIDField: resourceID},
  1011  			IsMethod: false,
  1012  		}
  1013  
  1014  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
  1015  		ctx = context.WithValue(ctx, header.ContextKey, mockedHeaders)
  1016  
  1017  		mockedScheduler := &automock.Scheduler{}
  1018  		mockedScheduler.On("Schedule", mock.Anything, mock.Anything).Return(operationID, nil)
  1019  		defer mockedScheduler.AssertExpectations(t)
  1020  
  1021  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatSucceeds()
  1022  		defer mockedTx.AssertExpectations(t)
  1023  		defer mockedTransactioner.AssertExpectations(t)
  1024  
  1025  		directive := operation.NewDirective(mockedTransactioner, mockedWebhooksResponse, mockedResourceFetcherFunc, func(ctx context.Context, id string, ready bool, errorMsg *string, appStatusCondition model.ApplicationStatusCondition) error {
  1026  			require.NotNil(t, ctx)
  1027  			require.Equal(t, resourceID, id)
  1028  			require.Equal(t, false, ready)
  1029  			require.Nil(t, errorMsg)
  1030  			require.Equal(t, model.ApplicationStatusConditionDeleting, appStatusCondition)
  1031  			return nil
  1032  		}, mockedTenantLoaderFunc, mockedScheduler)
  1033  
  1034  		dummyResolver := &dummyResolver{}
  1035  
  1036  		// WHEN
  1037  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeDelete, &whTypeApplicationRegister, &resourceIDField)
  1038  
  1039  		// THEN
  1040  		require.NoError(t, err)
  1041  		require.Equal(t, mockedNextResponse(), res)
  1042  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
  1043  
  1044  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
  1045  		operations, ok := opsFromCtx.(*[]*operation.Operation)
  1046  		require.True(t, ok)
  1047  		require.Len(t, *operations, 1)
  1048  
  1049  		op := (*operations)[0]
  1050  		require.Equal(t, operationID, op.OperationID)
  1051  		require.Equal(t, operation.OperationTypeDelete, op.OperationType)
  1052  		require.Equal(t, operationCategory, op.OperationCategory)
  1053  
  1054  		headers := make(map[string]string)
  1055  		for key, value := range mockedHeaders {
  1056  			headers[key] = value[0]
  1057  		}
  1058  
  1059  		expectedRequestObject := &webhook.ApplicationLifecycleWebhookRequestObject{
  1060  			Application: mockedNextResponse().(webhook.Resource),
  1061  			TenantID:    tenantID,
  1062  			Headers:     headers,
  1063  		}
  1064  
  1065  		expectedObj, err := json.Marshal(expectedRequestObject)
  1066  		require.NoError(t, err)
  1067  
  1068  		require.Equal(t, string(expectedObj), op.RequestObject)
  1069  
  1070  		require.Len(t, op.WebhookIDs, 1)
  1071  		require.Equal(t, webhookID1, op.WebhookIDs[0])
  1072  	})
  1073  
  1074  	t.Run("when mutation is in ASYNC mode, there is operation without webhooks in context and resource updater func is executed with DELETE operation type should finish successfully and update application status to DELETING", func(t *testing.T) {
  1075  		// GIVEN
  1076  		ctx := context.Background()
  1077  		operationMode := graphql.OperationModeAsync
  1078  		operationCategory := "registerApplication"
  1079  		rCtx := &gqlgen.FieldContext{
  1080  			Object: "RegisterApplication",
  1081  			Field: gqlgen.CollectedField{
  1082  				Field: &ast.Field{
  1083  					Name: operationCategory,
  1084  				},
  1085  			},
  1086  			Args:     map[string]interface{}{operation.ModeParam: &operationMode, resourceIDField: resourceID},
  1087  			IsMethod: false,
  1088  		}
  1089  
  1090  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
  1091  		ctx = context.WithValue(ctx, header.ContextKey, mockedHeaders)
  1092  
  1093  		mockedScheduler := &automock.Scheduler{}
  1094  		mockedScheduler.On("Schedule", mock.Anything, mock.Anything).Return(operationID, nil)
  1095  		defer mockedScheduler.AssertExpectations(t)
  1096  
  1097  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatSucceeds()
  1098  		defer mockedTx.AssertExpectations(t)
  1099  		defer mockedTransactioner.AssertExpectations(t)
  1100  
  1101  		directive := operation.NewDirective(mockedTransactioner, mockedEmptyWebhooksResponse, mockedResourceFetcherFunc, func(ctx context.Context, id string, ready bool, errorMsg *string, appStatusCondition model.ApplicationStatusCondition) error {
  1102  			require.NotNil(t, ctx)
  1103  			require.Equal(t, resourceID, id)
  1104  			require.Equal(t, false, ready)
  1105  			require.Nil(t, errorMsg)
  1106  			require.Equal(t, model.ApplicationStatusConditionDeleting, appStatusCondition)
  1107  			return nil
  1108  		}, mockedTenantLoaderFunc, mockedScheduler)
  1109  
  1110  		dummyResolver := &dummyResolver{}
  1111  
  1112  		// WHEN
  1113  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, graphql.OperationTypeDelete, &whTypeApplicationRegister, &resourceIDField)
  1114  
  1115  		// THEN
  1116  		require.NoError(t, err)
  1117  		require.Equal(t, mockedNextResponse(), res)
  1118  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
  1119  
  1120  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
  1121  		operations, ok := opsFromCtx.(*[]*operation.Operation)
  1122  		require.True(t, ok)
  1123  		require.Len(t, *operations, 1)
  1124  
  1125  		op := (*operations)[0]
  1126  		require.Equal(t, operationID, op.OperationID)
  1127  		require.Equal(t, operation.OperationTypeDelete, op.OperationType)
  1128  		require.Equal(t, operationCategory, op.OperationCategory)
  1129  
  1130  		headers := make(map[string]string)
  1131  		for key, value := range mockedHeaders {
  1132  			headers[key] = value[0]
  1133  		}
  1134  
  1135  		expectedRequestObject := &webhook.ApplicationLifecycleWebhookRequestObject{
  1136  			Application: mockedNextResponse().(webhook.Resource),
  1137  			TenantID:    tenantID,
  1138  			Headers:     headers,
  1139  		}
  1140  
  1141  		expectedObj, err := json.Marshal(expectedRequestObject)
  1142  		require.NoError(t, err)
  1143  		require.Equal(t, string(expectedObj), op.RequestObject)
  1144  		require.Len(t, op.WebhookIDs, 0)
  1145  	})
  1146  
  1147  	t.Run("when mutation is in ASYNC mode, there is operation in context and resource updater func is executed with invalid operation type should return error", func(t *testing.T) {
  1148  		// GIVEN
  1149  		ctx := context.Background()
  1150  		operationMode := graphql.OperationModeAsync
  1151  		operationCategory := "registerApplication"
  1152  		rCtx := &gqlgen.FieldContext{
  1153  			Object: "RegisterApplication",
  1154  			Field: gqlgen.CollectedField{
  1155  				Field: &ast.Field{
  1156  					Name: operationCategory,
  1157  				},
  1158  			},
  1159  			Args:     map[string]interface{}{operation.ModeParam: &operationMode, resourceIDField: resourceID},
  1160  			IsMethod: false,
  1161  		}
  1162  
  1163  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
  1164  
  1165  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
  1166  		defer mockedTx.AssertExpectations(t)
  1167  		defer mockedTransactioner.AssertExpectations(t)
  1168  
  1169  		directive := operation.NewDirective(mockedTransactioner, mockedWebhooksResponse, mockedResourceFetcherFunc, mockedEmptyResourceUpdaterFunc, mockedTenantLoaderFunc, nil)
  1170  
  1171  		dummyResolver := &dummyResolver{}
  1172  
  1173  		// WHEN
  1174  		res, err := directive.HandleOperation(ctx, nil, dummyResolver.SuccessResolve, "invalid", &whTypeApplicationRegister, &resourceIDField)
  1175  
  1176  		// THEN
  1177  		require.Error(t, err)
  1178  		require.Contains(t, err.Error(), "Invalid status condition")
  1179  		require.Nil(t, res)
  1180  		require.Equal(t, graphql.OperationModeAsync, dummyResolver.finalCtx.Value(operation.OpModeKey))
  1181  
  1182  		opsFromCtx := dummyResolver.finalCtx.Value(operation.OpCtxKey)
  1183  		assertNoOperationsInCtx(t, opsFromCtx)
  1184  	})
  1185  }
  1186  
  1187  func TestHandleOperation_ConcurrencyCheck(t *testing.T) {
  1188  	type testCase struct {
  1189  		description         string
  1190  		mutation            string
  1191  		scheduler           *automock.Scheduler
  1192  		tenantLoaderFunc    func(ctx context.Context) (string, error)
  1193  		resourceFetcherFunc func(ctx context.Context, tenant, id string) (model.Entity, error)
  1194  		validationFunc      func(t *testing.T, res interface{}, err error)
  1195  		resolverFunc        func(ctx context.Context) (res interface{}, err error)
  1196  		resolverCtxArgs     map[string]interface{}
  1197  		transactionFunc     func() (*tx_automock.PersistenceTx, *tx_automock.Transactioner)
  1198  	}
  1199  
  1200  	testCases := []testCase{
  1201  		{
  1202  			description:     "when resource ID is not present in the resolver context it should roll-back",
  1203  			mutation:        "UnregisterApplication",
  1204  			resolverCtxArgs: resolverContextArgs(graphql.OperationModeAsync, ""),
  1205  			transactionFunc: func() (*tx_automock.PersistenceTx, *tx_automock.Transactioner) {
  1206  				return txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
  1207  			},
  1208  			validationFunc: func(t *testing.T, res interface{}, err error) {
  1209  				require.Error(t, err)
  1210  				require.Contains(t, err.Error(), fmt.Sprintf("could not get idField: %q from request context", resourceIDField))
  1211  				require.Empty(t, res)
  1212  			},
  1213  		},
  1214  		{
  1215  			description:      "when tenant fetching fails it should roll-back",
  1216  			mutation:         "UnregisterApplication",
  1217  			resolverCtxArgs:  resolverContextArgs(graphql.OperationModeAsync, resourceID),
  1218  			tenantLoaderFunc: tenantLoaderWithOptionalErr(mockedError()),
  1219  			transactionFunc: func() (*tx_automock.PersistenceTx, *tx_automock.Transactioner) {
  1220  				return txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
  1221  			},
  1222  			validationFunc: func(t *testing.T, res interface{}, err error) {
  1223  				require.Error(t, err)
  1224  				require.True(t, apperrors.IsTenantRequired(err))
  1225  				require.Empty(t, res)
  1226  			},
  1227  		},
  1228  		{
  1229  			description:     "when resource is not found it should roll-back",
  1230  			mutation:        "UnregisterApplication",
  1231  			resolverCtxArgs: resolverContextArgs(graphql.OperationModeAsync, resourceID),
  1232  			transactionFunc: func() (*tx_automock.PersistenceTx, *tx_automock.Transactioner) {
  1233  				return txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
  1234  			},
  1235  			tenantLoaderFunc: tenantLoaderWithOptionalErr(nil),
  1236  			resourceFetcherFunc: func(ctx context.Context, tenant, id string) (model.Entity, error) {
  1237  				return nil, apperrors.NewNotFoundError(resource.Application, resourceID)
  1238  			},
  1239  			validationFunc: func(t *testing.T, res interface{}, err error) {
  1240  				require.Error(t, err)
  1241  				require.True(t, apperrors.IsNotFoundError(err))
  1242  				require.Empty(t, res)
  1243  			},
  1244  		},
  1245  		{
  1246  			description:     "when resource fetching fails it should roll-back",
  1247  			mutation:        "UnregisterApplication",
  1248  			resolverCtxArgs: resolverContextArgs(graphql.OperationModeAsync, resourceID),
  1249  			transactionFunc: func() (*tx_automock.PersistenceTx, *tx_automock.Transactioner) {
  1250  				return txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
  1251  			},
  1252  			tenantLoaderFunc: tenantLoaderWithOptionalErr(nil),
  1253  			resourceFetcherFunc: func(ctx context.Context, tenant, id string) (model.Entity, error) {
  1254  				return nil, mockedError()
  1255  			},
  1256  			validationFunc: func(t *testing.T, res interface{}, err error) {
  1257  				require.Error(t, err)
  1258  				require.Contains(t, err.Error(), fmt.Sprintf("failed to fetch resource with id %s", resourceID))
  1259  				require.Empty(t, res)
  1260  			},
  1261  		},
  1262  		{
  1263  			description:     "when concurrent create operation is running it should roll-back",
  1264  			mutation:        "UnregisterApplication",
  1265  			resolverCtxArgs: resolverContextArgs(graphql.OperationModeAsync, resourceID),
  1266  			transactionFunc: func() (*tx_automock.PersistenceTx, *tx_automock.Transactioner) {
  1267  				return txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
  1268  			},
  1269  			tenantLoaderFunc: tenantLoaderWithOptionalErr(nil),
  1270  			resourceFetcherFunc: func(ctx context.Context, tenant, id string) (model.Entity, error) {
  1271  				return &model.Application{
  1272  					BaseEntity: &model.BaseEntity{
  1273  						ID:        resourceID,
  1274  						Ready:     false,
  1275  						CreatedAt: timeToTimePtr(time.Now()),
  1276  					},
  1277  				}, nil
  1278  			},
  1279  			validationFunc: func(t *testing.T, res interface{}, err error) {
  1280  				require.Error(t, err)
  1281  				require.Contains(t, err.Error(), "create operation is in progress")
  1282  				require.Empty(t, res)
  1283  			},
  1284  		},
  1285  		{
  1286  			description:     "when concurrent delete operation is running it should roll-back",
  1287  			mutation:        "UnregisterApplication",
  1288  			resolverCtxArgs: resolverContextArgs(graphql.OperationModeAsync, resourceID),
  1289  			transactionFunc: func() (*tx_automock.PersistenceTx, *tx_automock.Transactioner) {
  1290  				return txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
  1291  			},
  1292  			tenantLoaderFunc: tenantLoaderWithOptionalErr(nil),
  1293  			resourceFetcherFunc: func(ctx context.Context, tenant, id string) (model.Entity, error) {
  1294  				return &model.Application{
  1295  					BaseEntity: &model.BaseEntity{
  1296  						ID:        resourceID,
  1297  						Ready:     false,
  1298  						CreatedAt: timeToTimePtr(time.Now()),
  1299  						DeletedAt: timeToTimePtr(time.Now()),
  1300  					},
  1301  				}, nil
  1302  			},
  1303  			validationFunc: func(t *testing.T, res interface{}, err error) {
  1304  				require.Error(t, err)
  1305  				require.Contains(t, err.Error(), "delete operation is in progress")
  1306  				require.Empty(t, res)
  1307  			},
  1308  		},
  1309  		{
  1310  			description:     "when there are no concurrent operations it should finish successfully",
  1311  			mutation:        "UnregisterApplication",
  1312  			scheduler:       scheduler(operationID, nil),
  1313  			resolverCtxArgs: resolverContextArgs(graphql.OperationModeAsync, resourceID),
  1314  			transactionFunc: func() (*tx_automock.PersistenceTx, *tx_automock.Transactioner) {
  1315  				return txtest.NewTransactionContextGenerator(nil).ThatSucceeds()
  1316  			},
  1317  			tenantLoaderFunc:    tenantLoaderWithOptionalErr(nil),
  1318  			resourceFetcherFunc: mockedResourceFetcherFunc,
  1319  			resolverFunc:        (&dummyResolver{}).SuccessResolve,
  1320  			validationFunc: func(t *testing.T, res interface{}, err error) {
  1321  				require.NoError(t, err)
  1322  				require.Equal(t, mockedNextResponse(), res)
  1323  			},
  1324  		},
  1325  	}
  1326  
  1327  	for _, test := range testCases {
  1328  		t.Run(test.description, func(t *testing.T) {
  1329  			// GIVEN
  1330  			ctx := context.Background()
  1331  			rCtx := &gqlgen.FieldContext{
  1332  				Object: test.mutation,
  1333  				Field: gqlgen.CollectedField{
  1334  					Field: &ast.Field{
  1335  						Name: test.mutation,
  1336  					},
  1337  				},
  1338  				Args:     test.resolverCtxArgs,
  1339  				IsMethod: false,
  1340  			}
  1341  
  1342  			ctx = gqlgen.WithFieldContext(ctx, rCtx)
  1343  			ctx = context.WithValue(ctx, header.ContextKey, mockedHeaders)
  1344  			mockedTx, mockedTransactioner := test.transactionFunc()
  1345  			defer mockedTx.AssertExpectations(t)
  1346  			defer mockedTransactioner.AssertExpectations(t)
  1347  
  1348  			if test.scheduler != nil {
  1349  				defer test.scheduler.AssertExpectations(t)
  1350  			}
  1351  
  1352  			directive := operation.NewDirective(mockedTransactioner, func(ctx context.Context, resourceID string) ([]*model.Webhook, error) {
  1353  				return nil, nil
  1354  			}, test.resourceFetcherFunc, mockedEmptyResourceUpdaterFunc, test.tenantLoaderFunc, test.scheduler)
  1355  
  1356  			// WHEN
  1357  			res, err := directive.HandleOperation(ctx, nil, test.resolverFunc, graphql.OperationTypeDelete, nil, &resourceIDField)
  1358  			// THEN
  1359  			test.validationFunc(t, res, err)
  1360  		})
  1361  	}
  1362  
  1363  	t.Run("when idField is not present in the directive it should roll-back", func(t *testing.T) {
  1364  		// GIVEN
  1365  		operationCategory := "registerApplication"
  1366  		ctx := context.Background()
  1367  		rCtx := &gqlgen.FieldContext{
  1368  			Object: "UnregisterApplication",
  1369  			Field: gqlgen.CollectedField{
  1370  				Field: &ast.Field{
  1371  					Name: operationCategory,
  1372  				},
  1373  			},
  1374  			Args:     resolverContextArgs(graphql.OperationModeAsync, resourceID),
  1375  			IsMethod: false,
  1376  		}
  1377  
  1378  		ctx = gqlgen.WithFieldContext(ctx, rCtx)
  1379  		mockedTx, mockedTransactioner := txtest.NewTransactionContextGenerator(nil).ThatDoesntExpectCommit()
  1380  		defer mockedTx.AssertExpectations(t)
  1381  		defer mockedTransactioner.AssertExpectations(t)
  1382  
  1383  		directive := operation.NewDirective(mockedTransactioner, nil, nil, nil, nil, nil)
  1384  
  1385  		// WHEN
  1386  		_, err := directive.HandleOperation(ctx, nil, nil, graphql.OperationTypeDelete, &whTypeApplicationUnregister, nil)
  1387  		// THEN
  1388  		require.Error(t, err)
  1389  		require.Contains(t, err.Error(), "idField from context should not be empty")
  1390  	})
  1391  }
  1392  
  1393  type dummyResolver struct {
  1394  	finalCtx context.Context
  1395  }
  1396  
  1397  func (d *dummyResolver) SuccessResolve(ctx context.Context) (res interface{}, err error) {
  1398  	d.finalCtx = ctx
  1399  	return mockedNextResponse(), nil
  1400  }
  1401  
  1402  func (d *dummyResolver) ErrorResolve(ctx context.Context) (res interface{}, err error) {
  1403  	d.finalCtx = ctx
  1404  	return nil, mockedError()
  1405  }
  1406  
  1407  func (d *dummyResolver) NonEntityResolve(ctx context.Context) (res interface{}, err error) {
  1408  	d.finalCtx = ctx
  1409  	return &graphql.Runtime{}, nil
  1410  }
  1411  
  1412  func (d *dummyResolver) NonWebhookProviderResolve(ctx context.Context) (res interface{}, err error) {
  1413  	d.finalCtx = ctx
  1414  	return &graphql.Bundle{BaseEntity: &graphql.BaseEntity{ID: resourceID}}, nil
  1415  }
  1416  
  1417  func mockedNextResponse() interface{} {
  1418  	return &graphql.Application{BaseEntity: &graphql.BaseEntity{ID: resourceID}}
  1419  }
  1420  
  1421  func mockedWebhooksResponse(_ context.Context, _ string) ([]*model.Webhook, error) {
  1422  	return []*model.Webhook{
  1423  		{ID: webhookID1, Type: model.WebhookTypeRegisterApplication},
  1424  	}, nil
  1425  }
  1426  
  1427  func mockedEmptyWebhooksResponse(_ context.Context, _ string) ([]*model.Webhook, error) {
  1428  	return nil, nil
  1429  }
  1430  
  1431  func mockedResourceFetcherFunc(context.Context, string, string) (model.Entity, error) {
  1432  	return &model.Application{
  1433  		BaseEntity: &model.BaseEntity{
  1434  			ID:        resourceID,
  1435  			Ready:     true,
  1436  			CreatedAt: timeToTimePtr(time.Now()),
  1437  		},
  1438  	}, nil
  1439  }
  1440  
  1441  func mockedTenantLoaderFunc(_ context.Context) (string, error) {
  1442  	return tenantID, nil
  1443  }
  1444  
  1445  func mockedResourceUpdaterFuncWithError(context.Context, string, bool, *string, model.ApplicationStatusCondition) error {
  1446  	return mockedError()
  1447  }
  1448  
  1449  func mockedEmptyResourceUpdaterFunc(context.Context, string, bool, *string, model.ApplicationStatusCondition) error {
  1450  	return nil
  1451  }
  1452  
  1453  func mockedError() error {
  1454  	return errors.New("mocked error")
  1455  }
  1456  
  1457  func resolverContextArgs(mode graphql.OperationMode, optionalResourceID string) map[string]interface{} {
  1458  	ctxArgs := map[string]interface{}{operation.ModeParam: &mode}
  1459  	if optionalResourceID != "" {
  1460  		ctxArgs[resourceIDField] = resourceID
  1461  	}
  1462  
  1463  	return ctxArgs
  1464  }
  1465  
  1466  func tenantLoaderWithOptionalErr(optionalErr error) func(ctx context.Context) (string, error) {
  1467  	if optionalErr != nil {
  1468  		return func(ctx context.Context) (string, error) { return "", optionalErr }
  1469  	}
  1470  
  1471  	return func(ctx context.Context) (string, error) { return tenantID, nil }
  1472  }
  1473  
  1474  func scheduler(operationID string, err error) *automock.Scheduler {
  1475  	mockedScheduler := &automock.Scheduler{}
  1476  	mockedScheduler.On("Schedule", mock.Anything, mock.Anything).Return(operationID, err)
  1477  	return mockedScheduler
  1478  }
  1479  
  1480  func assertNoOperationsInCtx(t *testing.T, ops interface{}) {
  1481  	operations, ok := ops.(*[]*operation.Operation)
  1482  	require.True(t, ok)
  1483  	require.Len(t, *operations, 0)
  1484  }