github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/pkg/operation/middleware_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  	"fmt"
    22  	"testing"
    23  
    24  	gqlgen "github.com/99designs/gqlgen/graphql"
    25  	panichandler "github.com/kyma-incubator/compass/components/director/internal/panic_handler"
    26  	"github.com/kyma-incubator/compass/components/director/pkg/graphql"
    27  	"github.com/kyma-incubator/compass/components/director/pkg/operation"
    28  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    29  	"github.com/stretchr/testify/require"
    30  	"github.com/vektah/gqlparser/v2/ast"
    31  	"github.com/vektah/gqlparser/v2/gqlerror"
    32  )
    33  
    34  const (
    35  	directorURL = "http://test-director/"
    36  	operationID = "6188b606-5a60-451a-8065-d2d13b2245ff"
    37  )
    38  
    39  func TestInterceptResponse(t *testing.T) {
    40  	t.Run("when no operations are found in the context, no location extensions would be attached", func(t *testing.T) {
    41  		gqlResults := []gqlResult{
    42  			{
    43  				resultName:    "result",
    44  				operationType: graphql.OperationModeSync,
    45  			},
    46  		}
    47  		dummyResolver := dummyMiddlewareResolver{
    48  			gqlResults: gqlResults,
    49  		}
    50  
    51  		middleware := operation.NewMiddleware(directorURL)
    52  		resp := middleware.InterceptResponse(context.Background(), dummyResolver.SuccessResolve)
    53  
    54  		require.Equal(t, gqlResultResponse(gqlResults[0].resultName), []byte(resp.Data))
    55  	})
    56  
    57  	t.Run("when an async operation is found in the context, location extension would be attached and data would be dropped", func(t *testing.T) {
    58  		gqlResults := []gqlResult{
    59  			{
    60  				resultName:    "result",
    61  				operationType: graphql.OperationModeAsync,
    62  			},
    63  		}
    64  		operations := &[]*operation.Operation{
    65  			{
    66  				OperationType:     operation.OperationTypeCreate,
    67  				OperationCategory: "registerApplication",
    68  				ResourceID:        operationID,
    69  				ResourceType:      resource.Application,
    70  			},
    71  		}
    72  
    73  		ctx := gqlContext(gqlResults, operations)
    74  		dummyResolver := dummyMiddlewareResolver{
    75  			gqlResults: gqlResults,
    76  		}
    77  
    78  		middleware := operation.NewMiddleware(directorURL)
    79  		resp := middleware.InterceptResponse(ctx, dummyResolver.SuccessResolve)
    80  
    81  		require.Equal(t, "{}", string(resp.Data))
    82  		ext, ok := resp.Extensions[operation.LocationsParam]
    83  		require.True(t, ok)
    84  		require.Len(t, ext, len(*operations))
    85  
    86  		for _, op := range *operations {
    87  			assertOperationInResponseExtension(t, ext, op)
    88  		}
    89  	})
    90  
    91  	t.Run("when multiple async operations are found in the context, multiple location extensions would be attached and data would dropped", func(t *testing.T) {
    92  		gqlResults := []gqlResult{
    93  			{
    94  				resultName:    "result1",
    95  				operationType: graphql.OperationModeAsync,
    96  			}, {
    97  				resultName:    "result2",
    98  				operationType: graphql.OperationModeAsync,
    99  			},
   100  		}
   101  
   102  		operations := &[]*operation.Operation{
   103  			{
   104  				OperationType:     operation.OperationTypeCreate,
   105  				OperationCategory: "registerApplication",
   106  				ResourceID:        operationID + "-1",
   107  				ResourceType:      resource.Application,
   108  			},
   109  			{
   110  				OperationType:     operation.OperationTypeCreate,
   111  				OperationCategory: "registerApplication",
   112  				ResourceID:        operationID + "-2",
   113  				ResourceType:      resource.Application,
   114  			},
   115  		}
   116  
   117  		ctx := gqlContext(gqlResults, operations)
   118  		dummyResolver := dummyMiddlewareResolver{
   119  			gqlResults: gqlResults,
   120  		}
   121  
   122  		middleware := operation.NewMiddleware(directorURL)
   123  		resp := middleware.InterceptResponse(ctx, dummyResolver.SuccessResolve)
   124  
   125  		require.Equal(t, "{}", string(resp.Data))
   126  		ext, ok := resp.Extensions[operation.LocationsParam]
   127  		require.True(t, ok)
   128  		require.Len(t, ext, len(*operations))
   129  
   130  		for _, op := range *operations {
   131  			assertOperationInResponseExtension(t, ext, op)
   132  		}
   133  	})
   134  
   135  	t.Run("when an async operation and sync operation are found in the context, single location extension would be attached and only the async data would dropped", func(t *testing.T) {
   136  		gqlResults := []gqlResult{
   137  			{
   138  				resultName:    "result1",
   139  				operationType: graphql.OperationModeAsync,
   140  			}, {
   141  				resultName:    "result2",
   142  				operationType: graphql.OperationModeSync,
   143  			},
   144  		}
   145  
   146  		operations := &[]*operation.Operation{
   147  			{
   148  				OperationType:     operation.OperationTypeCreate,
   149  				OperationCategory: "registerApplication",
   150  				ResourceID:        operationID,
   151  				ResourceType:      resource.Application,
   152  			},
   153  		}
   154  
   155  		ctx := gqlContext(gqlResults, operations)
   156  		dummyResolver := dummyMiddlewareResolver{
   157  			gqlResults: gqlResults,
   158  		}
   159  
   160  		middleware := operation.NewMiddleware(directorURL)
   161  		resp := middleware.InterceptResponse(ctx, dummyResolver.SuccessResolve)
   162  
   163  		require.Equal(t, fmt.Sprintf("{%s}", gqlResultItem(gqlResults[1].resultName)), string(resp.Data))
   164  
   165  		ext, ok := resp.Extensions[operation.LocationsParam]
   166  		require.True(t, ok)
   167  		require.Len(t, ext, 1)
   168  		assertOperationInResponseExtension(t, ext, (*operations)[0])
   169  	})
   170  }
   171  
   172  func TestInterceptOperation(t *testing.T) {
   173  	t.Run("adds empty operations slice to context", func(t *testing.T) {
   174  		middleware := operation.NewMiddleware(directorURL)
   175  		middleware.InterceptOperation(context.Background(), func(ctx context.Context) gqlgen.ResponseHandler {
   176  			operations, ok := operation.FromCtx(ctx)
   177  			require.True(t, ok)
   178  			require.NotNil(t, operations)
   179  			require.Len(t, *operations, 0)
   180  			return nil
   181  		})
   182  	})
   183  }
   184  
   185  func assertOperationInResponseExtension(t *testing.T, ext interface{}, op *operation.Operation) {
   186  	extArray, ok := ext.([]string)
   187  	require.True(t, ok)
   188  	require.Contains(t, extArray, operationURL(op, directorURL))
   189  }
   190  
   191  func gqlContext(results []gqlResult, operations *[]*operation.Operation) context.Context {
   192  	ctx := operation.SaveToContext(context.Background(), operations)
   193  	rCtx := gqlRequestContextWithSelections(results...)
   194  	ctx = gqlgen.WithOperationContext(ctx, rCtx)
   195  	ctx = gqlgen.WithResponseContext(ctx, func(ctx context.Context, err error) *gqlerror.Error { return nil }, panichandler.RecoverFn)
   196  	return ctx
   197  }
   198  
   199  type dummyMiddlewareResolver struct {
   200  	gqlResults []gqlResult
   201  }
   202  
   203  func (d *dummyMiddlewareResolver) SuccessResolve(_ context.Context) *gqlgen.Response {
   204  	body := ""
   205  	for i, gqlResult := range d.gqlResults {
   206  		body += gqlResultItem(gqlResult.resultName)
   207  
   208  		if i != len(d.gqlResults)-1 {
   209  			body += ","
   210  		}
   211  	}
   212  	return &gqlgen.Response{Data: []byte(fmt.Sprintf("{%s}", body))}
   213  }
   214  
   215  func operationURL(op *operation.Operation, directorURL string) string {
   216  	return fmt.Sprintf("%s/%s/%s", directorURL, op.ResourceType, op.ResourceID)
   217  }
   218  
   219  func gqlResultItem(resultName string) string {
   220  	return fmt.Sprintf(`"%s": { "id": "8b3340ff-b1e0-4e9d-8f3b-c36196279552", "name": "%s-app-name"}`, resultName, resultName)
   221  }
   222  
   223  func gqlResultResponse(resultName string) []byte {
   224  	return []byte(fmt.Sprintf(`{"%s": { "id": "8b3340ff-b1e0-4e9d-8f3b-c36196279552", "name": "%s-app-name"}}`, resultName, resultName))
   225  }
   226  
   227  type gqlResult struct {
   228  	resultName    string
   229  	operationType graphql.OperationMode
   230  }
   231  
   232  func gqlRequestContextWithSelections(results ...gqlResult) *gqlgen.OperationContext {
   233  	reqCtx := &gqlgen.OperationContext{
   234  		RawQuery:      "",
   235  		Variables:     nil,
   236  		OperationName: "",
   237  		Doc: &ast.QueryDocument{
   238  			Operations: ast.OperationList{
   239  				&ast.OperationDefinition{
   240  					SelectionSet: ast.SelectionSet{},
   241  				},
   242  			},
   243  		},
   244  	}
   245  
   246  	gqlOperation := reqCtx.Doc.Operations[0]
   247  
   248  	for _, gqlResult := range results {
   249  		gqlField := &ast.Field{
   250  			Alias: gqlResult.resultName,
   251  			Name:  "registerApplication",
   252  			Arguments: ast.ArgumentList{
   253  				&ast.Argument{
   254  					Name: operation.ModeParam,
   255  					Value: &ast.Value{
   256  						Raw: string(gqlResult.operationType),
   257  					},
   258  				},
   259  			},
   260  		}
   261  
   262  		gqlOperation.SelectionSet = append(gqlOperation.SelectionSet, gqlField)
   263  	}
   264  
   265  	return reqCtx
   266  }