Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,49 @@
on: [push, pull_request]
name: Test

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
test:
strategy:
fail-fast: false
matrix:
go-version: [1.13.x, 1.14.x]
go-version: ["1.23", "stable"]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}

steps:
- name: Install Go
uses: actions/setup-go@v2
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2

- name: Test
run: go test ./...
run: go test -race ./...

lint:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

# The linter is built against a specific Go release and cannot type-check a newer
# standard library, so pin its toolchain to the module's Go version rather than "stable".
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.10
22 changes: 22 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: "2"

run:
timeout: 5m

linters:
default: standard
enable:
- copyloopvar
- errorlint
- misspell
- modernize
- nilerr
- unconvert
- unparam
- usestdlibvars
- wastedassign

formatters:
enable:
- gofmt
- goimports
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
.PHONY: test lint

test:
go test -v .
go vet ./...
go test -race ./...

lint:
golangci-lint run ./...
45 changes: 38 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
![Test](https://github.com/steinfletcher/apitest-jsonpath/workflows/Test/badge.svg)
[![Test](https://github.com/steinfletcher/apitest-jsonpath/actions/workflows/ci.yml/badge.svg)](https://github.com/steinfletcher/apitest-jsonpath/actions/workflows/ci.yml)

# apitest-jsonpath

Expand All @@ -7,7 +7,7 @@ This library provides jsonpath assertions for [apitest](https://github.com/stein
# Installation

```bash
go get -u github.com/steinfletcher/apitest-jsonpath
go get github.com/steinfletcher/apitest-jsonpath
```

## Examples
Expand All @@ -17,7 +17,7 @@ go get -u github.com/steinfletcher/apitest-jsonpath
`Equal` checks for value equality when the json path expression returns a single result. Given the response is `{"id": 12345}`

```go
apitest.New(handler).
apitest.Handler(handler).
Get("/hello").
Expect(t).
Assert(jsonpath.Equal(`$.id`, float64(12345))).
Expand All @@ -31,7 +31,7 @@ apitest.New().
Handler(handler).
Get("/hello").
Expect(t).
Assert(jsonpath.Equal(`$`, map[string]interface{}{"message": "hello", "id": float64(12345)})).
Assert(jsonpath.Equal(`$`, map[string]any{"message": "hello", "id": float64(12345)})).
End()
```

Expand All @@ -40,7 +40,7 @@ apitest.New().
`NotEqual` checks that the json path expression value is not equal to given value

```go
apitest.New(handler).
apitest.Handler(handler).
Get("/hello").
Expect(t).
Assert(jsonpath.NotEqual(`$.a`, float64(56789))).
Expand All @@ -54,7 +54,7 @@ apitest.New().
Handler(handler).
Get("/hello").
Expect(t).
Assert(jsonpath.NotEqual(`$`, map[string]interface{}{"a": "hello", "b": float64(56789)})).
Assert(jsonpath.NotEqual(`$`, map[string]any{"a": "hello", "b": float64(56789)})).
End()
```

Expand Down Expand Up @@ -193,4 +193,35 @@ Assert(
Equal("f", "c").
End(),
).
```
```

A dot is inserted between the root and each sub-expression unless the sub-expression starts with a bracket, so array
elements under the root can be addressed directly:

```go
Assert(
jsonpath.Root("$.items").
Equal("[0].id", float64(1)).
Equal("[1].id", float64(2)).
End(),
).
```

### Matching mock request bodies

The `mocks` package provides the same assertions as `apitest.Matcher` functions, for matching the JSON body of a
request made to an [apitest mock](https://github.com/steinfletcher/apitest#mocking-external-http-calls).

```go
import "github.com/steinfletcher/apitest-jsonpath/mocks"

var createUser = apitest.NewMock().
Post("/user-api").
AddMatcher(mocks.Equal(`$.name`, "jon")).
AddMatcher(mocks.Contains(`$.roles`, "admin")).
RespondWith().
Status(http.StatusCreated).
End()
```

`mocks.Equal`, `mocks.NotEqual`, `mocks.Contains`, `mocks.Len` and `mocks.GreaterThan` are available.
12 changes: 9 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
module github.com/steinfletcher/apitest-jsonpath

go 1.13
go 1.23

require (
github.com/PaesslerAG/jsonpath v0.1.1
github.com/steinfletcher/apitest v1.5.10
github.com/stretchr/testify v1.7.0
github.com/steinfletcher/apitest v1.6.1
github.com/stretchr/testify v1.12.1
)

require (
github.com/PaesslerAG/gval v1.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
)
21 changes: 6 additions & 15 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
github.com/PaesslerAG/gval v1.0.0 h1:GEKnRwkWDdf9dOmKcNrar9EA1bz1z9DqPIO1+iLzhd8=
github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I=
github.com/PaesslerAG/jsonpath v0.1.0 h1:gADYeifvlqK3R3i2cR5B4DGgxLXIPb3TRTH1mGi0jPI=
github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk=
github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/steinfletcher/apitest v1.5.10 h1:uxEm/boegmZI9csm1fLVywB5b07ijcrcHo3PZO6sfns=
github.com/steinfletcher/apitest v1.5.10/go.mod h1:cf7Bneo52IIAgpqhP8xaLlzWgAiQ9fHtsDMjeDnZ3so=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
github.com/steinfletcher/apitest v1.6.1 h1:gZRLz/Q4sVl2QVa2NYGIGZ1/UOV0rn15Xzd1SO51pvI=
github.com/steinfletcher/apitest v1.6.1/go.mod h1:ToZZZP3/cTb6+gTkDUYrlcgFX9C2Z4965/PtmCQ9Onw=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
20 changes: 12 additions & 8 deletions http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package http

import (
"bytes"
"io/ioutil"
"io"
"net/http"
"net/url"
)
Expand All @@ -14,29 +14,33 @@ func CopyResponse(response *http.Response) *http.Response {

var resBodyBytes []byte
if response.Body != nil {
resBodyBytes, _ = ioutil.ReadAll(response.Body)
response.Body = ioutil.NopCloser(bytes.NewBuffer(resBodyBytes))
resBodyBytes, _ = io.ReadAll(response.Body)
response.Body = io.NopCloser(bytes.NewBuffer(resBodyBytes))
}

resCopy := &http.Response{
Header: map[string][]string{},
StatusCode: response.StatusCode,
Status: response.Status,
Body: ioutil.NopCloser(bytes.NewBuffer(resBodyBytes)),
Body: io.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
resCopy.Header[name] = append([]string(nil), values...)
}

return resCopy
}

func CopyRequest(request *http.Request) *http.Request {
if request == nil {
return nil
}

resCopy := &http.Request{
Method: request.Method,
Host: request.Host,
Expand All @@ -49,9 +53,9 @@ func CopyRequest(request *http.Request) *http.Request {
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))
bodyBytes, _ := io.ReadAll(request.Body)
resCopy.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
}

if request.URL != nil {
Expand Down
56 changes: 56 additions & 0 deletions http/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package http

import (
"bytes"
"io"
nethttp "net/http"
"net/http/httptest"
"testing"
)

func TestCopyResponse(t *testing.T) {
original := &nethttp.Response{
StatusCode: nethttp.StatusOK,
Header: nethttp.Header{"X-Custom": {"a"}},
Body: io.NopCloser(bytes.NewBufferString("body")),
}

copied := CopyResponse(original)
copied.Header["X-Custom"][0] = "changed"
copied.Header.Add("X-Custom", "b")

if got := original.Header["X-Custom"]; len(got) != 1 || got[0] != "a" {
t.Fatalf("expected the original headers to be untouched, got %v", got)
}
for name, body := range map[string]*nethttp.Response{"copy": copied, "original": original} {
b, _ := io.ReadAll(body.Body)
if string(b) != "body" {
t.Fatalf("expected %s body to be readable, got %q", name, b)
}
}
if CopyResponse(nil) != nil {
t.Fatal("expected a nil response to copy as nil")
}
}

func TestCopyRequest(t *testing.T) {
original := httptest.NewRequest(nethttp.MethodPost, "/path?a=1", bytes.NewBufferString("body"))
original.Header.Set("X-Custom", "a")

copied := CopyRequest(original)
copied.Header.Set("X-Custom", "changed")
copied.URL.Path = "/other"

if original.Header.Get("X-Custom") != "a" || original.URL.Path != "/path" {
t.Fatalf("expected the original request to be untouched, got %v %s", original.Header, original.URL)
}
for name, req := range map[string]*nethttp.Request{"copy": copied, "original": original} {
b, _ := io.ReadAll(req.Body)
if string(b) != "body" {
t.Fatalf("expected %s body to be readable, got %q", name, b)
}
}
if CopyRequest(nil) != nil {
t.Fatal("expected a nil request to copy as nil")
}
}
Loading
Loading