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 }