snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s

This commit is contained in:
upstream-mirror
2026-09-18 01:22:23 +05:00
commit 7e385fc126
8391 changed files with 1766236 additions and 0 deletions

View File

@@ -0,0 +1 @@
.idea/

View File

@@ -0,0 +1,2 @@
test:
go test -v .

View File

@@ -0,0 +1,196 @@
![Test](https://github.com/steinfletcher/apitest-jsonpath/workflows/Test/badge.svg)
# apitest-jsonpath
This library provides jsonpath assertions for [apitest](https://github.com/steinfletcher/apitest).
# Installation
```bash
go get -u github.com/steinfletcher/apitest-jsonpath
```
## Examples
### Equal
`Equal` checks for value equality when the json path expression returns a single result. Given the response is `{"id": 12345}`
```go
apitest.New(handler).
Get("/hello").
Expect(t).
Assert(jsonpath.Equal(`$.id`, float64(12345))).
End()
```
We can also provide more complex expected values. Given the response `{"message": "hello", "id": 12345}`.
```go
apitest.New().
Handler(handler).
Get("/hello").
Expect(t).
Assert(jsonpath.Equal(`$`, map[string]interface{}{"message": "hello", "id": float64(12345)})).
End()
```
### NotEqual
`NotEqual` checks that the json path expression value is not equal to given value
```go
apitest.New(handler).
Get("/hello").
Expect(t).
Assert(jsonpath.NotEqual(`$.a`, float64(56789))).
End()
```
we can also provide more complex expected values
```go
apitest.New().
Handler(handler).
Get("/hello").
Expect(t).
Assert(jsonpath.NotEqual(`$`, map[string]interface{}{"a": "hello", "b": float64(56789)})).
End()
```
given the response is `{"a": "hello", "b": 12345}`
### Contains
When the jsonpath expression returns an array, use `Contains` to assert that the expected value is contained in the result. Given the response is `{"a": 12345, "b": [{"key": "c", "value": "result"}]}`, we can assert on the result like so
```go
apitest.New().
Handler(handler).
Get("/hello").
Expect(t).
Assert(jsonpath.Contains(`$.b[? @.key=="c"].value`, "result")).
End()
```
### Present / NotPresent
Use `Present` and `NotPresent` to check the presence of a field in the response without evaluating its value.
```go
apitest.New().
Handler(handler).
Get("/hello").
Expect(t).
Assert(jsonpath.Present(`$.a`)).
Assert(jsonpath.NotPresent(`$.password`)).
End()
```
### Matches
Use `Matches` to check that a single path element of type string, number or bool matches a regular expression.
```go
apitest.New().
Handler(handler).
Get("/hello").
Expect(t).
Assert(jsonpath.Matches(`$.a`, `^[abc]{1,3}$`)).
End()
```
### Len
Use `Len` to check to the length of the returned value. Given the response is `{"items": [1, 2, 3]}`, we can assert on the length of items like so
```go
apitest.New().
Handler(handler).
Get("/articles?category=golang").
Expect(t).
Assert(jsonpath.Len(`$.items`, 3)).
End()
```
### GreaterThan
Use `GreaterThan` to enforce a minimum length on the returned value.
```go
apitest.New().
Handler(handler).
Get("/articles?category=golang").
Expect(t).
Assert(jsonpath.GreaterThan(`$.items`, 2)).
End()
```
### LessThan
Use `LessThan` to enforce a maximum length on the returned value.
```go
apitest.New().
Handler(handler).
Get("/articles?category=golang").
Expect(t).
Assert(jsonpath.LessThan(`$.items`, 4)).
End()
```
### JWT matchers
`JWTHeaderEqual` and `JWTPayloadEqual` can be used to assert on the contents of the JWT in the response (it does not verify a JWT).
```go
func TestX(t *testing.T) {
apitest.New().
HandlerFunc(myHandler).
Post("/login").
Expect(t).
Assert(jsonpath.JWTPayloadEqual(fromAuthHeader, `$.sub`, "1234567890")).
Assert(jsonpath.JWTHeaderEqual(fromAuthHeader, `$.alg`, "HS256")).
End()
}
func fromAuthHeader(res *http.Response) (string, error) {
return res.Header.Get("Authorization"), nil
}
```
### Chain
`Chain` is used to provide several assertions at once
```go
Assert(
jsonpath.Chain().
Equal("a", "1").
NotEqual("b", "2").
Present("c").
End(),
).
```
### Root
`Root` is used to avoid duplicated paths in body expectations. For example, instead of writing:
```go
Assert(jsonpath.Equal("a.b.c.d", "a").
Assert(jsonpath.Equal("a.b.c.e", "b").
Assert(jsonpath.Equal("a.b.c.f", "c").
```
it is possible to define a root path like so
```go
Assert(
jsonpath.Root("$.a.b.c").
Equal("d", "a").
Equal("e", "b").
Equal("f", "c").
End(),
).
```

View File

@@ -0,0 +1,73 @@
package http
import (
"bytes"
"io/ioutil"
"net/http"
"net/url"
)
func CopyResponse(response *http.Response) *http.Response {
if response == nil {
return nil
}
var resBodyBytes []byte
if response.Body != nil {
resBodyBytes, _ = ioutil.ReadAll(response.Body)
response.Body = ioutil.NopCloser(bytes.NewBuffer(resBodyBytes))
}
resCopy := &http.Response{
Header: map[string][]string{},
StatusCode: response.StatusCode,
Status: response.Status,
Body: ioutil.NopCloser(bytes.NewBuffer(resBodyBytes)),
Proto: response.Proto,
ProtoMinor: response.ProtoMinor,
ProtoMajor: response.ProtoMajor,
ContentLength: response.ContentLength,
}
for name, values := range response.Header {
resCopy.Header[name] = values
}
return resCopy
}
func CopyRequest(request *http.Request) *http.Request {
resCopy := &http.Request{
Method: request.Method,
Host: request.Host,
Proto: request.Proto,
ProtoMinor: request.ProtoMinor,
ProtoMajor: request.ProtoMajor,
ContentLength: request.ContentLength,
RemoteAddr: request.RemoteAddr,
}
resCopy = resCopy.WithContext(request.Context())
if request.Body != nil {
bodyBytes, _ := ioutil.ReadAll(request.Body)
resCopy.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
}
if request.URL != nil {
r2URL := new(url.URL)
*r2URL = *request.URL
resCopy.URL = r2URL
}
headers := make(http.Header)
for k, values := range request.Header {
for _, hValue := range values {
headers.Add(k, hValue)
}
}
resCopy.Header = headers
return resCopy
}

View File

@@ -0,0 +1,169 @@
package jsonpath
import (
"errors"
"fmt"
httputil "github.com/steinfletcher/apitest-jsonpath/http"
"github.com/steinfletcher/apitest-jsonpath/jsonpath"
"net/http"
"reflect"
regex "regexp"
)
// Contains is a convenience function to assert that a jsonpath expression extracts a value in an array
func Contains(expression string, expected interface{}) func(*http.Response, *http.Request) error {
return func(res *http.Response, req *http.Request) error {
return jsonpath.Contains(expression, expected, res.Body)
}
}
// Equal is a convenience function to assert that a jsonpath expression extracts a value
func Equal(expression string, expected interface{}) func(*http.Response, *http.Request) error {
return func(res *http.Response, req *http.Request) error {
return jsonpath.Equal(expression, expected, res.Body)
}
}
// NotEqual is a function to check json path expression value is not equal to given value
func NotEqual(expression string, expected interface{}) func(*http.Response, *http.Request) error {
return func(res *http.Response, req *http.Request) error {
return jsonpath.NotEqual(expression, expected, res.Body)
}
}
// Len asserts that value is the expected length, determined by reflect.Len
func Len(expression string, expectedLength int) func(*http.Response, *http.Request) error {
return func(res *http.Response, req *http.Request) error {
return jsonpath.Length(expression, expectedLength, res.Body)
}
}
// GreaterThan asserts that value is greater than the given length, determined by reflect.Len
func GreaterThan(expression string, minimumLength int) func(*http.Response, *http.Request) error {
return func(res *http.Response, req *http.Request) error {
return jsonpath.GreaterThan(expression, minimumLength, res.Body)
}
}
// LessThan asserts that value is less than the given length, determined by reflect.Len
func LessThan(expression string, maximumLength int) func(*http.Response, *http.Request) error {
return func(res *http.Response, req *http.Request) error {
return jsonpath.LessThan(expression, maximumLength, res.Body)
}
}
// Present asserts that value returned by the expression is present
func Present(expression string) func(*http.Response, *http.Request) error {
return func(res *http.Response, req *http.Request) error {
return jsonpath.Present(expression, res.Body)
}
}
// NotPresent asserts that value returned by the expression is not present
func NotPresent(expression string) func(*http.Response, *http.Request) error {
return func(res *http.Response, req *http.Request) error {
return jsonpath.NotPresent(expression, res.Body)
}
}
// Matches asserts that the value matches the given regular expression
func Matches(expression string, regexp string) func(*http.Response, *http.Request) error {
return func(res *http.Response, req *http.Request) error {
pattern, err := regex.Compile(regexp)
if err != nil {
return errors.New(fmt.Sprintf("invalid pattern: '%s'", regexp))
}
value, _ := jsonpath.JsonPath(res.Body, expression)
if value == nil {
return errors.New(fmt.Sprintf("no match for pattern: '%s'", expression))
}
kind := reflect.ValueOf(value).Kind()
switch kind {
case reflect.Bool,
reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Uintptr,
reflect.Float32,
reflect.Float64,
reflect.String:
if !pattern.Match([]byte(fmt.Sprintf("%v", value))) {
return errors.New(fmt.Sprintf("value '%v' does not match pattern '%v'", value, regexp))
}
return nil
default:
return errors.New(fmt.Sprintf("unable to match using type: %s", kind.String()))
}
}
}
// Chain creates a new assertion chain
func Chain() *AssertionChain {
return &AssertionChain{rootExpression: ""}
}
// Root creates a new assertion chain prefixed with the given expression
func Root(expression string) *AssertionChain {
return &AssertionChain{rootExpression: expression + "."}
}
// AssertionChain supports chaining assertions and root expressions
type AssertionChain struct {
rootExpression string
assertions []func(*http.Response, *http.Request) error
}
// Equal adds an Equal assertion to the chain
func (r *AssertionChain) Equal(expression string, expected interface{}) *AssertionChain {
r.assertions = append(r.assertions, Equal(r.rootExpression+expression, expected))
return r
}
// NotEqual adds an NotEqual assertion to the chain
func (r *AssertionChain) NotEqual(expression string, expected interface{}) *AssertionChain {
r.assertions = append(r.assertions, NotEqual(r.rootExpression+expression, expected))
return r
}
// Contains adds an Contains assertion to the chain
func (r *AssertionChain) Contains(expression string, expected interface{}) *AssertionChain {
r.assertions = append(r.assertions, Contains(r.rootExpression+expression, expected))
return r
}
// Present adds an Present assertion to the chain
func (r *AssertionChain) Present(expression string) *AssertionChain {
r.assertions = append(r.assertions, Present(r.rootExpression+expression))
return r
}
// NotPresent adds an NotPresent assertion to the chain
func (r *AssertionChain) NotPresent(expression string) *AssertionChain {
r.assertions = append(r.assertions, NotPresent(r.rootExpression+expression))
return r
}
// Matches adds an Matches assertion to the chain
func (r *AssertionChain) Matches(expression, regexp string) *AssertionChain {
r.assertions = append(r.assertions, Matches(r.rootExpression+expression, regexp))
return r
}
// End returns an func(*http.Response, *http.Request) error which is a combination of the registered assertions
func (r *AssertionChain) End() func(*http.Response, *http.Request) error {
return func(res *http.Response, req *http.Request) error {
for _, assertion := range r.assertions {
if err := assertion(httputil.CopyResponse(res), httputil.CopyRequest(req)); err != nil {
return err
}
}
return nil
}
}

View File

@@ -0,0 +1,200 @@
package jsonpath
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/PaesslerAG/jsonpath"
"io"
"io/ioutil"
"reflect"
"strings"
)
func Contains(expression string, expected interface{}, data io.Reader) error {
value, err := JsonPath(data, expression)
if err != nil {
return err
}
ok, found := IncludesElement(value, expected)
if !ok {
return errors.New(fmt.Sprintf("\"%s\" could not be applied builtin len()", expected))
}
if !found {
return errors.New(fmt.Sprintf("\"%s\" does not contain \"%s\"", value, expected))
}
return nil
}
func Equal(expression string, expected interface{}, data io.Reader) error {
value, err := JsonPath(data, expression)
if err != nil {
return err
}
if !ObjectsAreEqual(value, expected) {
return errors.New(fmt.Sprintf("\"%s\" not equal to \"%s\"", value, expected))
}
return nil
}
func NotEqual(expression string, expected interface{}, data io.Reader) error {
value, err := JsonPath(data, expression)
if err != nil {
return err
}
if ObjectsAreEqual(value, expected) {
return errors.New(fmt.Sprintf("\"%s\" value is equal to \"%s\"", expression, expected))
}
return nil
}
func Length(expression string, expectedLength int, data io.Reader) error {
value, err := JsonPath(data, expression)
if err != nil {
return err
}
v := reflect.ValueOf(value)
if v.Len() != expectedLength {
return errors.New(fmt.Sprintf("\"%d\" not equal to \"%d\"", v.Len(), expectedLength))
}
return nil
}
func GreaterThan(expression string, minimumLength int, data io.Reader) error {
value, err := JsonPath(data, expression)
if err != nil {
return err
}
v := reflect.ValueOf(value)
if v.Len() < minimumLength {
return errors.New(fmt.Sprintf("\"%d\" is greater than \"%d\"", v.Len(), minimumLength))
}
return nil
}
func LessThan(expression string, maximumLength int, data io.Reader) error {
value, err := JsonPath(data, expression)
if err != nil {
return err
}
v := reflect.ValueOf(value)
if v.Len() > maximumLength {
return errors.New(fmt.Sprintf("\"%d\" is less than \"%d\"", v.Len(), maximumLength))
}
return nil
}
func Present(expression string, data io.Reader) error {
value, _ := JsonPath(data, expression)
if isEmpty(value) {
return errors.New(fmt.Sprintf("value not present for expression: '%s'", expression))
}
return nil
}
func NotPresent(expression string, data io.Reader) error {
value, _ := JsonPath(data, expression)
if !isEmpty(value) {
return errors.New(fmt.Sprintf("value present for expression: '%s'", expression))
}
return nil
}
func JsonPath(reader io.Reader, expression string) (interface{}, error) {
v := interface{}(nil)
b, err := ioutil.ReadAll(reader)
if err != nil {
return nil, err
}
err = json.Unmarshal(b, &v)
if err != nil {
return nil, err
}
value, err := jsonpath.Get(expression, v)
if err != nil {
return nil, fmt.Errorf("evaluating '%s' resulted in error: '%s'", expression, err)
}
return value, nil
}
// courtesy of github.com/stretchr/testify
func IncludesElement(list interface{}, element interface{}) (ok, found bool) {
listValue := reflect.ValueOf(list)
elementValue := reflect.ValueOf(element)
defer func() {
if e := recover(); e != nil {
ok = false
found = false
}
}()
if reflect.TypeOf(list).Kind() == reflect.String {
return true, strings.Contains(listValue.String(), elementValue.String())
}
if reflect.TypeOf(list).Kind() == reflect.Map {
mapKeys := listValue.MapKeys()
for i := 0; i < len(mapKeys); i++ {
if ObjectsAreEqual(mapKeys[i].Interface(), element) {
return true, true
}
}
return true, false
}
for i := 0; i < listValue.Len(); i++ {
if ObjectsAreEqual(listValue.Index(i).Interface(), element) {
return true, true
}
}
return true, false
}
func ObjectsAreEqual(expected, actual interface{}) bool {
if expected == nil || actual == nil {
return expected == actual
}
exp, ok := expected.([]byte)
if !ok {
return reflect.DeepEqual(expected, actual)
}
act, ok := actual.([]byte)
if !ok {
return false
}
if exp == nil || act == nil {
return exp == nil && act == nil
}
return bytes.Equal(exp, act)
}
func isEmpty(object interface{}) bool {
if object == nil {
return true
}
objValue := reflect.ValueOf(object)
switch objValue.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
return objValue.Len() == 0
case reflect.Ptr:
if objValue.IsNil() {
return true
}
deref := objValue.Elem().Interface()
return isEmpty(deref)
default:
zero := reflect.Zero(objValue.Type())
return reflect.DeepEqual(object, zero.Interface())
}
}

View File

@@ -0,0 +1,68 @@
package jsonpath
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"github.com/steinfletcher/apitest-jsonpath/jsonpath"
"net/http"
"strings"
)
const (
jwtHeaderIndex = 0
jwtPayloadIndex = 1
)
func JWTHeaderEqual(tokenSelector func(*http.Response) (string, error), expression string, expected interface{}) func(*http.Response, *http.Request) error {
return jwtEqual(tokenSelector, expression, expected, jwtHeaderIndex)
}
func JWTPayloadEqual(tokenSelector func(*http.Response) (string, error), expression string, expected interface{}) func(*http.Response, *http.Request) error {
return jwtEqual(tokenSelector, expression, expected, jwtPayloadIndex)
}
func jwtEqual(tokenSelector func(*http.Response) (string, error), expression string, expected interface{}, index int) func(*http.Response, *http.Request) error {
return func(response *http.Response, request *http.Request) error {
token, err := tokenSelector(response)
if err != nil {
return err
}
parts := strings.Split(token, ".")
if len(parts) != 3 {
splitErr := errors.New("Invalid token: token should contain header, payload and secret")
return splitErr
}
decodedPayload, PayloadErr := base64Decode(parts[index])
if PayloadErr != nil {
return fmt.Errorf("Invalid jwt: %s", PayloadErr.Error())
}
value, err := jsonpath.JsonPath(bytes.NewReader(decodedPayload), expression)
if err != nil {
return err
}
if !jsonpath.ObjectsAreEqual(value, expected) {
return errors.New(fmt.Sprintf("\"%s\" not equal to \"%s\"", value, expected))
}
return nil
}
}
func base64Decode(src string) ([]byte, error) {
if l := len(src) % 4; l > 0 {
src += strings.Repeat("=", 4-l)
}
decoded, err := base64.URLEncoding.DecodeString(src)
if err != nil {
errMsg := fmt.Errorf("Decoding Error %s", err)
return nil, errMsg
}
return decoded, nil
}