github.com/openshift-online/ocm-sdk-go@v0.1.473/testing/jq.go (about)

     1  /*
     2  Copyright (c) 2021 Red Hat, Inc.
     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 testing
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"net/http"
    25  	"reflect"
    26  	"strings"
    27  
    28  	"github.com/itchyny/gojq"
    29  	"github.com/onsi/gomega/types"
    30  
    31  	. "github.com/onsi/gomega" //nolint
    32  )
    33  
    34  // JQ runs the given `jq` filter on the given object and returns the list of results. The returned
    35  // slice will never be nil; if there are no results it will be empty.
    36  func JQ(filter string, input interface{}) (results []interface{}, err error) {
    37  	query, err := gojq.Parse(filter)
    38  	if err != nil {
    39  		return
    40  	}
    41  	iterator := query.Run(input)
    42  	for {
    43  		result, ok := iterator.Next()
    44  		if !ok {
    45  			break
    46  		}
    47  		results = append(results, result)
    48  	}
    49  	return
    50  }
    51  
    52  // MatchJQ creates a matcher that checks that the all the results of applying a `jq` filter to the
    53  // actual value is the given expected value.
    54  func MatchJQ(filter string, expected interface{}) types.GomegaMatcher {
    55  	return &jqMatcher{
    56  		filter:   filter,
    57  		expected: expected,
    58  	}
    59  }
    60  
    61  type jqMatcher struct {
    62  	filter   string
    63  	expected interface{}
    64  	results  []interface{}
    65  }
    66  
    67  func (m *jqMatcher) Match(actual interface{}) (success bool, err error) {
    68  	// Run the query:
    69  	m.results, err = JQ(m.filter, actual)
    70  	if err != nil {
    71  		return
    72  	}
    73  
    74  	// Check that there is at least one result:
    75  	if len(m.results) == 0 {
    76  		return
    77  	}
    78  
    79  	// We consider the match sucessful if all the results returned by the JQ filter are exactly
    80  	// equal to the expected value.
    81  	success = true
    82  	for _, result := range m.results {
    83  		if !reflect.DeepEqual(result, m.expected) {
    84  			success = false
    85  			break
    86  		}
    87  	}
    88  	return
    89  }
    90  
    91  func (m *jqMatcher) FailureMessage(actual interface{}) string {
    92  	return fmt.Sprintf(
    93  		"Expected all results of running JQ filter\n\t%s\n"+
    94  			"on input\n\t%s\n"+
    95  			"to be\n\t%s\n"+
    96  			"but at list one of the following results isn't\n\t%s\n",
    97  		m.filter, m.pretty(actual), m.pretty(m.expected), m.pretty(m.results),
    98  	)
    99  }
   100  
   101  func (m *jqMatcher) NegatedFailureMessage(actual interface{}) (message string) {
   102  	return fmt.Sprintf(
   103  		"Expected results of running JQ filter\n\t%s\n"+
   104  			"on\n\t%s\n"+
   105  			"to not be\n\t%s\n",
   106  		m.filter, m.pretty(actual), m.pretty(m.expected),
   107  	)
   108  }
   109  
   110  func (m *jqMatcher) pretty(object interface{}) string {
   111  	var buffer bytes.Buffer
   112  	encoder := json.NewEncoder(&buffer)
   113  	encoder.SetIndent("\t", "  ")
   114  	err := encoder.Encode(object)
   115  	if err != nil {
   116  		return fmt.Sprintf("\t%v", object)
   117  	}
   118  	return strings.TrimRight(buffer.String(), "\n")
   119  }
   120  
   121  // VerifyJQ verifies that the result of applying the given `jq` filter to the request body matches
   122  // the given expected value.
   123  func VerifyJQ(filter string, expected interface{}) http.HandlerFunc {
   124  	return func(w http.ResponseWriter, r *http.Request) {
   125  		// Read the body completely:
   126  		body, err := io.ReadAll(r.Body)
   127  		Expect(err).ToNot(HaveOccurred())
   128  		err = r.Body.Close()
   129  		Expect(err).ToNot(HaveOccurred())
   130  
   131  		// Replace the body with a buffer so that other calls to this same method, or to
   132  		// other verification methods will also be able to work with it.
   133  		r.Body = io.NopCloser(bytes.NewBuffer(body))
   134  
   135  		// Parse the body as JSON and verify that it matches the filter:
   136  		var data interface{}
   137  		err = json.Unmarshal(body, &data)
   138  		Expect(err).ToNot(HaveOccurred())
   139  		Expect(data).To(MatchJQ(filter, expected))
   140  	}
   141  }