Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/json assertor #12

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
coverage.out
profile.out
.idea
.vscode
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,34 @@ httpfake
<img align="right" width="125px" src="https://raw.githubusercontent.com/maxcnunes/httpfake/master/logo/gopher-httpfake.png">

[![LICENSE](https://img.shields.io/badge/license-MIT-orange.svg)](LICENSE)
[![Godocs](https://img.shields.io/badge/golang-documentation-blue.svg)](https://godoc.org/github.com/maxcnunes/httpfake)
[![Godocs](https://img.shields.io/badge/golang-documentation-blue.svg)](https://godoc.org/github.com/voronelf/httpfake)
[![Build Status](https://travis-ci.org/maxcnunes/httpfake.svg?branch=master)](https://travis-ci.org/maxcnunes/httpfake)
[![Coverage Status](https://coveralls.io/repos/github/maxcnunes/httpfake/badge.svg?branch=master)](https://coveralls.io/github/maxcnunes/httpfake?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/maxcnunes/httpfake)](https://goreportcard.com/report/github.com/maxcnunes/httpfake)
[![Go Report Card](https://goreportcard.com/badge/github.com/voronelf/httpfake)](https://goreportcard.com/report/github.com/voronelf/httpfake)

httpfake provides is a simple wrapper for [httptest](https://golang.org/pkg/net/http/httptest/) with a handful chainable API for setting up handlers to a fake server. This package is aimed to be used in tests where the original external server must not be reached. Instead is used in its place a fake server which can be configured to handle any request as desired.

## Installation

```
go get -u github.com/maxcnunes/httpfake
go get -u github.com/voronelf/httpfake
```

or

```
govendor fetch github.com/maxcnunes/httpfake
govendor fetch github.com/voronelf/httpfake
```

> If possible give preference for using vendor. This way the version is locked up as a dependency in your project.

## Changelog

See [Releases](https://github.com/maxcnunes/httpfake/releases) for detailed history changes.
See [Releases](https://github.com/voronelf/httpfake/releases) for detailed history changes.

## API

See [godoc reference](https://godoc.org/github.com/maxcnunes/httpfake) for detailed API documentation.
See [godoc reference](https://godoc.org/github.com/voronelf/httpfake) for detailed API documentation.

## Assertions

Expand All @@ -44,15 +44,15 @@ supported assertions are:
* HTTP header and its expected value
* The expected body of your request

[WithTesting](https://godoc.org/github.com/maxcnunes/httpfake#WithTesting) **must** be provided as a server
[WithTesting](https://godoc.org/github.com/voronelf/httpfake#WithTesting) **must** be provided as a server
option when creating the test server if you intend to set request assertions. Failing to set the option
when using request assertions will result in a panic.

### Custom Assertions

You can also provide your own assertions by creating a type that implements the
[Assertor interface](https://godoc.org/github.com/maxcnunes/httpfake#Assertor) or utilizing the
[CustomAssertor function type](https://pkg.go.dev/github.com/maxcnunes/httpfake#CustomAssertor). The `Assertor.Log` method will be
[Assertor interface](https://godoc.org/github.com/voronelf/httpfake#Assertor) or utilizing the
[CustomAssertor function type](https://pkg.go.dev/github.com/voronelf/httpfake#CustomAssertor). The `Assertor.Log` method will be
called for each assertion before it's processed. The `Assertor.Error` method will only be called if the
`Assertor.Assert` method returns an error.

Expand Down
118 changes: 90 additions & 28 deletions assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,30 @@ package httpfake
import (
"bytes"
"fmt"
"io/ioutil"
"io"
"net/http"
"reflect"
"strings"
"testing"

"github.com/tidwall/gjson"
)

const assertErrorTemplate = "assertion error: %s"

// Assertor provides an interface for setting assertions for http requests
// Assertor provides an interface for setting assertions for http requests.
type Assertor interface {
Assert(r *http.Request) error
Log(t testing.TB)
Error(t testing.TB, err error)
}

// requiredHeaders provides an Assertor for the presence of the provided http header keys
// requiredHeaders provides an Assertor for the presence of the provided http header keys.
type requiredHeaders struct {
Keys []string
}

// Assert runs the required headers assertion against the provided request
// Assert runs the required headers assertion against the provided request.
func (h *requiredHeaders) Assert(r *http.Request) error {
var missingHeaders []string

Expand All @@ -40,23 +43,23 @@ func (h *requiredHeaders) Assert(r *http.Request) error {
return nil
}

// Log prints a testing info log for the requiredHeaders Assertor
// Log prints a testing info log for the requiredHeaders Assertor.
func (h *requiredHeaders) Log(t testing.TB) {
t.Log("Testing request for required headers")
}

// Error prints a testing error for the requiredHeaders Assertor
// Error prints a testing error for the requiredHeaders Assertor.
func (h *requiredHeaders) Error(t testing.TB, err error) {
t.Errorf(assertErrorTemplate, err)
}

// requiredHeaderValue provides an Assertor for a header and its expected value
// requiredHeaderValue provides an Assertor for a header and its expected value.
type requiredHeaderValue struct {
Key string
ExpectedValue string
}

// Assert runs the required header value assertion against the provided request
// Assert runs the required header value assertion against the provided request.
func (h *requiredHeaderValue) Assert(r *http.Request) error {
if value := r.Header.Get(h.Key); value != h.ExpectedValue {
return fmt.Errorf("header %s does not have the expected value; expected %s to equal %s",
Expand All @@ -68,22 +71,22 @@ func (h *requiredHeaderValue) Assert(r *http.Request) error {
return nil
}

// Log prints a testing info log for the requiredHeaderValue Assertor
// Log prints a testing info log for the requiredHeaderValue Assertor.
func (h *requiredHeaderValue) Log(t testing.TB) {
t.Logf("Testing request for a required header value [%s: %s]", h.Key, h.ExpectedValue)
}

// Error prints a testing error for the requiredHeaderValue Assertor
// Error prints a testing error for the requiredHeaderValue Assertor.
func (h *requiredHeaderValue) Error(t testing.TB, err error) {
t.Errorf(assertErrorTemplate, err)
}

// requiredQueries provides an Assertor for the presence of the provided query parameter keys
// requiredQueries provides an Assertor for the presence of the provided query parameter keys.
type requiredQueries struct {
Keys []string
}

// Assert runs the required queries assertion against the provided request
// Assert runs the required queries assertion against the provided request.
func (q *requiredQueries) Assert(r *http.Request) error {
queryVals := r.URL.Query()
var missingParams []string
Expand All @@ -100,71 +103,71 @@ func (q *requiredQueries) Assert(r *http.Request) error {
return nil
}

// Log prints a testing info log for the requiredQueries Assertor
// Log prints a testing info log for the requiredQueries Assertor.
func (q *requiredQueries) Log(t testing.TB) {
t.Log("Testing request for required query parameters")
}

// Error prints a testing error for the requiredQueries Assertor
// Error prints a testing error for the requiredQueries Assertor.
func (q *requiredQueries) Error(t testing.TB, err error) {
t.Errorf(assertErrorTemplate, err)
}

// requiredQueryValue provides an Assertor for a query parameter and its expected value
// requiredQueryValue provides an Assertor for a query parameter and its expected value.
type requiredQueryValue struct {
Key string
ExpectedValue string
}

// Assert runs the required query value assertion against the provided request
// Assert runs the required query value assertion against the provided request.
func (q *requiredQueryValue) Assert(r *http.Request) error {
if value := r.URL.Query().Get(q.Key); value != q.ExpectedValue {
return fmt.Errorf("query %s does not have the expected value; expected %s to equal %s", q.Key, value, q.ExpectedValue)
}
return nil
}

// Log prints a testing info log for the requiredQueryValue Assertor
// Log prints a testing info log for the requiredQueryValue Assertor.
func (q *requiredQueryValue) Log(t testing.TB) {
t.Logf("Testing request for a required query parameter value [%s: %s]", q.Key, q.ExpectedValue)
}

// Error prints a testing error for the requiredQueryValue Assertor
// Error prints a testing error for the requiredQueryValue Assertor.
func (q *requiredQueryValue) Error(t testing.TB, err error) {
t.Errorf(assertErrorTemplate, err)
}

// requiredBody provides an Assertor for the expected value of the request body
// requiredBody provides an Assertor for the expected value of the request body.
type requiredBody struct {
ExpectedBody []byte
}

// Assert runs the required body assertion against the provided request
// Assert runs the required body assertion against the provided request.
func (b *requiredBody) Assert(r *http.Request) error {
if r.Body == nil {
return fmt.Errorf("error reading the request body; the request body is nil")
}

body, err := ioutil.ReadAll(r.Body)
body, err := io.ReadAll(r.Body)
if err != nil {
return fmt.Errorf("error reading the request body: %s", err.Error())
}

if !bytes.EqualFold(b.ExpectedBody, body) {
return fmt.Errorf("request body does not have the expected value; expected %s to equal %s",
string(body[:]),
string(b.ExpectedBody[:]))
string(body),
string(b.ExpectedBody))
}

return nil
}

// Log prints a testing info log for the requiredBody Assertor
// Log prints a testing info log for the requiredBody Assertor.
func (b *requiredBody) Log(t testing.TB) {
t.Log("Testing request for a required body value")
}

// Error prints a testing error for the requiredBody Assertor
// Error prints a testing error for the requiredBody Assertor.
func (b *requiredBody) Error(t testing.TB, err error) {
t.Errorf(assertErrorTemplate, err)
}
Expand All @@ -173,17 +176,76 @@ func (b *requiredBody) Error(t testing.TB, err error) {
// adhoc creation of a custom assertion for use with the AssertCustom assertor.
type CustomAssertor func(r *http.Request) error

// Assert runs the CustomAssertor assertion against the provided request
// Assert runs the CustomAssertor assertion against the provided request.
func (c CustomAssertor) Assert(r *http.Request) error {
return c(r)
}

// Log prints a testing info log for the CustomAssertor
// Log prints a testing info log for the CustomAssertor.
func (c CustomAssertor) Log(t testing.TB) {
t.Log("Testing request with a custom assertor")
}

// Error prints a testing error for the CustomAssertor
// Error prints a testing error for the CustomAssertor.
func (c CustomAssertor) Error(t testing.TB, err error) {
t.Errorf(assertErrorTemplate, err)
}

type subJSON struct {
necessaryFields map[string]any
}

type gjsonForEachFunc func(key, value gjson.Result) bool

func (j *subJSON) fillFunc(keyPrefix string) gjsonForEachFunc {
return func(key, value gjson.Result) bool {
keyString := key.String()

if keyPrefix != "" {
keyString = fmt.Sprintf("%s.%s", keyPrefix, keyString)
}

if value.IsArray() || value.IsObject() {
value.ForEach(j.fillFunc(keyString))
return true
}

j.necessaryFields[keyString] = value.Value()

return true
}
}

func (j *subJSON) Assert(r *http.Request) error {
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
return err
}

_ = r.Body.Close()

r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

js := gjson.ParseBytes(bodyBytes)

var assertionError error

for key, value := range j.necessaryFields {
bodyValue := js.Get(key).Value()

if !reflect.DeepEqual(bodyValue, value) {
assertionError = fmt.Errorf(`json assertion failed for "%s" field: expected "%v", got "%v"`, key, value, bodyValue)
break
}
}

return assertionError
}

func (j *subJSON) Log(t testing.TB) {
t.Log("Testing request for a required json fields")
}

func (j *subJSON) Error(t testing.TB, err error) {
t.Errorf(assertErrorTemplate, err)
}
Loading