1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
//
// Copyright 2022 Cristian Maglie. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
package requirejson
import (
"encoding/json"
"fmt"
"os"
"testing"
"github.com/itchyny/gojq"
"github.com/stretchr/testify/require"
)
// JQObject is an object that abstract operations on JSON data.
type JQObject struct {
t *testing.T
data interface{}
}
func (obj *JQObject) String() string {
r, err := json.Marshal(obj.data)
require.NoError(obj.t, err)
return string(r)
}
// Parse creates a new JQObect from the given jsonData.
// If jsonData is not a valid json the test fails.
func Parse(t *testing.T, jsonData []byte, msgAndArgs ...interface{}) *JQObject {
var data interface{}
require.NoError(t, json.Unmarshal(jsonData, &data), msgAndArgs...)
return &JQObject{t: t, data: data}
}
// ParseFromFile creates a new JQObect from the given file path.
// If the file is not a valid json the test fails.
func ParseFromFile(t *testing.T, filePath string, msgAndArgs ...interface{}) *JQObject {
var data interface{}
jsonData, err := os.ReadFile(filePath)
require.NoError(t, err, msgAndArgs...)
require.NoError(t, json.Unmarshal(jsonData, &data), msgAndArgs...)
return &JQObject{t: t, data: data}
}
// Query performs a query on the given JQObject and returns the first result. If the query
// produces no result the test will fail.
func (obj *JQObject) Query(jqQuery string) *JQObject {
q, err := gojq.Parse(jqQuery)
require.NoError(obj.t, err)
iter := q.Run(obj.data)
data, ok := iter.Next()
require.True(obj.t, ok, "The json query produced no results")
return &JQObject{t: obj.t, data: data}
}
// MustEqual tests if the JQObject equals jsonExpected. If the check
// is not successful the test will fail. If msgAndArgs are provided they
// will be used to explain the error.
func (obj *JQObject) MustEqual(jsonExpected string, msgAndArgs ...interface{}) {
jsonActual, err := json.Marshal(obj.data)
require.NoError(obj.t, err)
require.JSONEq(obj.t, jsonExpected, string(jsonActual), msgAndArgs...)
}
// MustContain tests if jsonExpected is a subset of the JQObject. If the check
// is not successful the test will fail. If msgAndArgs are provided they
// will be used to explain the error.
func (obj *JQObject) MustContain(jsonExpected string, msgAndArgs ...interface{}) {
v := obj.Query("contains(" + jsonExpected + ")").data
require.IsType(obj.t, true, v)
if !v.(bool) {
msg := fmt.Sprintf("json data does not contain: %s", jsonExpected)
require.FailNow(obj.t, msg, msgAndArgs...)
}
}
// MustNotContain tests if the JQObject does not contain jsonExpected. If the check
// is not successful the test will fail. If msgAndArgs are provided they
// will be used to explain the error.
func (obj *JQObject) MustNotContain(jsonExpected string, msgAndArgs ...interface{}) {
v := obj.Query("contains(" + jsonExpected + ")").data
require.IsType(obj.t, true, v)
if v.(bool) {
msg := fmt.Sprintf("json data contains: %s", jsonExpected)
require.FailNow(obj.t, msg, msgAndArgs...)
}
}
// LengthMustEqualTo tests if the size of JQObject match expectedLen. If the check
// is not successful the test will fail. If msgAndArgs are provided they
// will be used to explain the error.
func (obj *JQObject) LengthMustEqualTo(expectedLen int, msgAndArgs ...interface{}) {
v := obj.Query("length").data
require.IsType(obj.t, expectedLen, v)
if v.(int) != expectedLen {
msg := fmt.Sprintf("json data length does not match: expected=%d, actual=%d", expectedLen, v.(int))
require.FailNow(obj.t, msg, msgAndArgs...)
}
}
// MustBeEmpty test if the size of JQObject is 0. If the check
// is not successful the test will fail. If msgAndArgs are provided they
// will be used to explain the error.
func (obj *JQObject) MustBeEmpty(msgAndArgs ...interface{}) {
v := obj.Query("length").data
require.IsType(obj.t, 0, v)
if v.(int) != 0 {
require.FailNow(obj.t, "json data is not empty", msgAndArgs...)
}
}
// MustNotBeEmpty test if the size of JQObject is not 0. If the check
// is not successful the test will fail. If msgAndArgs are provided they
// will be used to explain the error.
func (obj *JQObject) MustNotBeEmpty(msgAndArgs ...interface{}) {
v := obj.Query("length").data
require.IsType(obj.t, 0, v)
if v.(int) == 0 {
require.FailNow(obj.t, "json data is empty", msgAndArgs...)
}
}
// IsTrue test if the JQObject is `true`.
func (obj *JQObject) IsTrue(msgAndArgs ...interface{}) {
obj.MustEqual("true", msgAndArgs...)
}
// IsFalse test if the JQObject is `false`.
func (obj *JQObject) IsFalse(msgAndArgs ...interface{}) {
obj.MustEqual("false", msgAndArgs...)
}
// ArrayMustContain checks if the give JQObject contains the given element.
func (obj *JQObject) ArrayMustContain(jsonElement string, msgAndArgs ...interface{}) {
obj.Query(`any(. == ` + jsonElement + `)`).IsTrue(msgAndArgs...)
}
// Query performs a test on a given json output. A jq-like query is performed
// on the given jsonData and the result is compared with jsonExpected.
// If the output doesn't match the test fails. If msgAndArgs are provided they
// will be used to explain the error.
func Query(t *testing.T, jsonData []byte, jqQuery string, jsonExpected string, msgAndArgs ...interface{}) {
data := Parse(t, jsonData)
v := data.Query(jqQuery)
v.MustEqual(jsonExpected, msgAndArgs...)
}
// Contains check if the json object is a subset of the jsonData.
// If the output doesn't match the test fails. If msgAndArgs are provided they
// will be used to explain the error.
func Contains(t *testing.T, jsonData []byte, jsonObject string, msgAndArgs ...interface{}) {
Parse(t, jsonData).MustContain(jsonObject, msgAndArgs...)
}
// NotContains check if the json object is NOT a subset of the jsonData.
// If the output match the test fails. If msgAndArgs are provided they
// will be used to explain the error.
func NotContains(t *testing.T, jsonData []byte, jsonObject string, msgAndArgs ...interface{}) {
Parse(t, jsonData).MustNotContain(jsonObject, msgAndArgs...)
}
// Len check if the size of the json object match the given value.
// If the lenght doesn't match the test fails. If msgAndArgs are provided they
// will be used to explain the error.
func Len(t *testing.T, jsonData []byte, expectedLen int, msgAndArgs ...interface{}) {
Parse(t, jsonData).LengthMustEqualTo(expectedLen, msgAndArgs...)
}
// Empty check if the size of the json object is zero.
// If the lenght is not zero the test fails. If msgAndArgs are provided they
// will be used to explain the error.
func Empty(t *testing.T, jsonData []byte, msgAndArgs ...interface{}) {
Parse(t, jsonData).MustBeEmpty(msgAndArgs...)
}
// NotEmpty check if the size of the json object is not zero.
// If the lenght is zero the test fails. If msgAndArgs are provided they
// will be used to explain the error.
func NotEmpty(t *testing.T, jsonData []byte, msgAndArgs ...interface{}) {
Parse(t, jsonData).MustNotBeEmpty(msgAndArgs...)
}
|