Skip to content

Commit

Permalink
Merge pull request #103 from maxatome/next
Browse files Browse the repository at this point in the history
Next release
  • Loading branch information
maxatome committed Jul 19, 2020
2 parents 00e9a69 + 4ecc564 commit ab54266
Show file tree
Hide file tree
Showing 36 changed files with 901 additions and 517 deletions.
4 changes: 2 additions & 2 deletions helpers/tdhttp/http_test.go
Expand Up @@ -17,12 +17,12 @@ import (

"github.com/maxatome/go-testdeep/helpers/tdhttp"
"github.com/maxatome/go-testdeep/helpers/tdutil"
"github.com/maxatome/go-testdeep/internal/ctxerr"
"github.com/maxatome/go-testdeep/internal/color"
"github.com/maxatome/go-testdeep/td"
)

func TestMain(m *testing.M) {
defer ctxerr.SaveColorState()()
defer color.SaveState()()
os.Exit(m.Run())
}

Expand Down
11 changes: 6 additions & 5 deletions helpers/tdhttp/request.go
Expand Up @@ -10,12 +10,13 @@ import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"

"github.com/maxatome/go-testdeep/internal/color"
)

func addHeaders(req *http.Request, headers []interface{}) *http.Request {
Expand All @@ -27,7 +28,7 @@ func addHeaders(req *http.Request, headers []interface{}) *http.Request {
if i < len(headers) {
var ok bool
if val, ok = headers[i].(string); !ok {
panic(fmt.Sprintf(`header "%s" should have a string value, not a %T (@ headers[%d])`,
panic(color.Bad(`header "%s" should have a string value, not a %T (@ headers[%d])`,
cur, headers[i], i))
}
}
Expand All @@ -39,7 +40,7 @@ func addHeaders(req *http.Request, headers []interface{}) *http.Request {
}

default:
panic(fmt.Sprintf("headers... can only contains string and http.Header, not %T (@ headers[%d])", cur, i))
panic(color.Bad("headers... can only contains string and http.Header, not %T (@ headers[%d])", cur, i))
}
}
return req
Expand Down Expand Up @@ -165,7 +166,7 @@ func Delete(target string, body io.Reader, headers ...interface{}) *http.Request
func NewJSONRequest(method, target string, body interface{}, headers ...interface{}) *http.Request {
b, err := json.Marshal(body)
if err != nil {
panic("JSON encoding failed: " + err.Error())
panic(color.Bad("JSON encoding failed: %s", err))
}

return addHeaders(NewRequest(method, target, bytes.NewBuffer(b)),
Expand Down Expand Up @@ -230,7 +231,7 @@ func DeleteJSON(target string, body interface{}, headers ...interface{}) *http.R
func NewXMLRequest(method, target string, body interface{}, headers ...interface{}) *http.Request {
b, err := xml.Marshal(body)
if err != nil {
panic("XML encoding failed: " + err.Error())
panic(color.Bad("XML encoding failed: %s", err))
}

return addHeaders(NewRequest(method, target, bytes.NewBuffer(b)),
Expand Down
69 changes: 69 additions & 0 deletions helpers/tdhttp/test_api.go
Expand Up @@ -21,6 +21,7 @@ import (
"time"

"github.com/maxatome/go-testdeep/helpers/tdutil"
"github.com/maxatome/go-testdeep/internal/color"
"github.com/maxatome/go-testdeep/internal/ctxerr"
"github.com/maxatome/go-testdeep/td"
)
Expand Down Expand Up @@ -662,6 +663,74 @@ func (t *TestAPI) NoBody() *TestAPI {
return t
}

// Or executes function "fn" if t.Failed() is true at the moment it is called.
//
// "fn" can have several types:
// - func(body string) or func(t *td.T, body string)
// → "fn" is called with response body as a string.
// If no response has been received yet, body is "";
// - func(body []byte) or func(t *td.T, body []byte)
// → "fn" is called with response body as a []byte.
// If no response has been received yet, body is nil;
// - func(t *td.T, resp *httptest.ResponseRecorder)
// → "fn" is called with the internal object containing the response.
// See net/http/httptest for details.
// If no response has been received yet, resp is nil.
//
// If "fn" type is not one of these types, it panics.
func (t *TestAPI) Or(fn interface{}) *TestAPI {
t.t.Helper()
switch fn := fn.(type) {
case func(string):
if t.Failed() {
var body string
if t.response != nil && t.response.Body != nil {
body = t.response.Body.String()
}
fn(body)
}

case func(*td.T, string):
if t.Failed() {
var body string
if t.response != nil && t.response.Body != nil {
body = t.response.Body.String()
}
fn(t.t, body)
}

case func([]byte):
if t.Failed() {
var body []byte
if t.response != nil && t.response.Body != nil {
body = t.response.Body.Bytes()
}
fn(body)
}

case func(*td.T, []byte):
if t.Failed() {
var body []byte
if t.response != nil && t.response.Body != nil {
body = t.response.Body.Bytes()
}
fn(t.t, body)
}

case func(*td.T, *httptest.ResponseRecorder):
if t.Failed() {
fn(t.t, t.response)
}

default:
panic(color.BadUsage(
"Or(func([*td.T,]string) | func([*td.T,][]byte) | func(*td.T,*httptest.ResponseRecorder))",
fn, 1, true))
}

return t
}

// Anchor returns a typed value allowing to anchor the TestDeep
// operator "operator" in a go classic litteral like a struct, slice,
// array or map value.
Expand Down
105 changes: 105 additions & 0 deletions helpers/tdhttp/test_api_test.go
Expand Up @@ -11,6 +11,7 @@ import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
Expand Down Expand Up @@ -464,3 +465,107 @@ func TestNewTestAPI(t *testing.T) {
td.CmpContains(t, mockT.LogBuf(), "Body cannot be empty when using CmpXMLBody")
})
}

func TestOr(t *testing.T) {
mux := server()

t.Run("Success", func(t *testing.T) {
var orCalled bool
for i, fn := range []interface{}{
func(body string) { orCalled = true },
func(t *td.T, body string) { orCalled = true },
func(body []byte) { orCalled = true },
func(t *td.T, body []byte) { orCalled = true },
func(t *td.T, r *httptest.ResponseRecorder) { orCalled = true },
} {
orCalled = false
// As CmpStatus succeeds, Or function is not called
td.CmpFalse(t,
tdhttp.NewTestAPI(tdutil.NewT("test"), mux).
Head("/any").
CmpStatus(200).
Or(fn).
Failed(),
"Not failed #%d", i)
td.CmpFalse(t, orCalled, "called #%d", i)
}
})

t.Run("No request sent", func(t *testing.T) {
var ok, orCalled bool
for i, fn := range []interface{}{
func(body string) { orCalled = true; ok = body == "" },
func(t *td.T, body string) { orCalled = true; ok = t != nil && body == "" },
func(body []byte) { orCalled = true; ok = body == nil },
func(t *td.T, body []byte) { orCalled = true; ok = t != nil && body == nil },
func(t *td.T, r *httptest.ResponseRecorder) { orCalled = true; ok = t != nil && r == nil },
} {
orCalled, ok = false, false
// Check status without sending a request → fail
td.CmpTrue(t,
tdhttp.NewTestAPI(tdutil.NewT("test"), mux).
CmpStatus(123).
Or(fn).
Failed(),
"Failed #%d", i)
td.CmpTrue(t, orCalled, "called #%d", i)
td.CmpTrue(t, ok, "OK #%d", i)
}
})

t.Run("Empty bodies", func(t *testing.T) {
var ok, orCalled bool
for i, fn := range []interface{}{
func(body string) { orCalled = true; ok = body == "" },
func(t *td.T, body string) { orCalled = true; ok = t != nil && body == "" },
func(body []byte) { orCalled = true; ok = body == nil },
func(t *td.T, body []byte) { orCalled = true; ok = t != nil && body == nil },
func(t *td.T, r *httptest.ResponseRecorder) {
orCalled = true
ok = t != nil && r != nil && r.Body.Len() == 0
},
} {
orCalled, ok = false, false
// HEAD /any = no body + CmpStatus fails
td.CmpTrue(t,
tdhttp.NewTestAPI(tdutil.NewT("test"), mux).
Head("/any").
CmpStatus(123).
Or(fn).
Failed(),
"Failed #%d", i)
td.CmpTrue(t, orCalled, "called #%d", i)
td.CmpTrue(t, ok, "OK #%d", i)
}
})

t.Run("Body", func(t *testing.T) {
var ok, orCalled bool
for i, fn := range []interface{}{
func(body string) { orCalled = true; ok = body == "GET!" },
func(t *td.T, body string) { orCalled = true; ok = t != nil && body == "GET!" },
func(body []byte) { orCalled = true; ok = string(body) == "GET!" },
func(t *td.T, body []byte) { orCalled = true; ok = t != nil && string(body) == "GET!" },
func(t *td.T, r *httptest.ResponseRecorder) {
orCalled = true
ok = t != nil && r != nil && r.Body.String() == "GET!"
},
} {
orCalled, ok = false, false
// GET /any = "GET!" body + CmpStatus fails
td.CmpTrue(t,
tdhttp.NewTestAPI(tdutil.NewT("test"), mux).
Get("/any").
CmpStatus(123).
Or(fn).
Failed(),
"Failed #%d", i)
td.CmpTrue(t, orCalled, "called #%d", i)
td.CmpTrue(t, ok, "OK #%d", i)
}
})

td.CmpPanic(t,
func() { tdhttp.NewTestAPI(tdutil.NewT("test"), mux).Or(123) },
"usage: Or(func([*td.T,]string) | func([*td.T,][]byte) | func(*td.T,*httptest.ResponseRecorder)), but received int as 1st parameter")
}
4 changes: 3 additions & 1 deletion internal/anchors/anchor.go
Expand Up @@ -11,6 +11,8 @@ import (
"math"
"reflect"
"sync"

"github.com/maxatome/go-testdeep/internal/color"
)

type anchor struct {
Expand Down Expand Up @@ -251,7 +253,7 @@ func (i *Info) build(typ reflect.Type) (reflect.Value, interface{}) {
return nvm, nvm.Interface()
}
}
panic(typ.String() + " struct type is not supported as an anchor. Try AddAnchorableStructType")
panic(color.Bad(typ.String() + " struct type is not supported as an anchor. Try AddAnchorableStructType"))

default:
panic(typ.Kind().String() + " kind is not supported as an anchor")
Expand Down

0 comments on commit ab54266

Please sign in to comment.