Skip to content

Commit

Permalink
support matching
Browse files Browse the repository at this point in the history
  • Loading branch information
fogfish committed Mar 13, 2023
1 parent d1cc839 commit 8b16eec
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 32 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ require (
github.com/fogfish/it/v2 v2.0.1
golang.org/x/net v0.7.0
)

require github.com/google/go-cmp v0.5.9
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ github.com/ajg/form v1.5.2-0.20200323032839-9aeb3cf462e1 h1:8Qzi+0Uch1VJvdrOhJ8U
github.com/ajg/form v1.5.2-0.20200323032839-9aeb3cf462e1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/fogfish/it/v2 v2.0.1 h1:vu3kV2xzYDPHoMHMABxXeu5CoMcTfRc4gkWkzOUkRJY=
github.com/fogfish/it/v2 v2.0.1/go.mod h1:h5FdKaEQT4sUEykiVkB8VV4jX27XabFVeWhoDZaRZtE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
97 changes: 76 additions & 21 deletions http/recv/arrows.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/ajg/form"
"github.com/fogfish/gurl/v2"
"github.com/fogfish/gurl/v2/http"
"github.com/google/go-cmp/cmp"
)

//-------------------------------------------------------------------
Expand All @@ -39,7 +40,13 @@ func Code(code ...http.StatusCode) http.Arrow {

status := cat.Response.StatusCode
if !hasCode(code, status) {
return http.NewStatusCode(status, code[0])
return &gurl.NoMatch{
ID: "http.Code",
Diff: fmt.Sprintf("+ Status Code: %d\n- Status Code: %d", status, code[0]),
Protocol: "StatusCode",
Expect: code[0],
Actual: status,
}
}
return nil
}
Expand Down Expand Up @@ -82,7 +89,13 @@ func (StatusCode) eval(code http.StatusCode, cat *http.Context) error {

status := cat.Response.StatusCode
if !hasCode([]http.StatusCode{code}, status) {
return http.NewStatusCode(status, code)
return &gurl.NoMatch{
ID: "http.Code",
Diff: fmt.Sprintf("+ Status Code: %d\n- Status Code: %d", status, code),
Protocol: "StatusCode",
Expect: code,
Actual: status,
}
}

return nil
Expand Down Expand Up @@ -311,15 +324,21 @@ func match(ctx *http.Context, header string, value string) error {
h := ctx.Response.Header.Get(string(header))
if h == "" {
return &gurl.NoMatch{
Diff: fmt.Sprintf("- %s: %s", string(header), value),
Payload: nil,
ID: "http.Header",
Diff: fmt.Sprintf("- %s: %s", string(header), value),
Protocol: header,
Expect: value,
Actual: nil,
}
}

if value != "*" && !strings.HasPrefix(h, value) {
return &gurl.NoMatch{
Diff: fmt.Sprintf("+ %s: %s\n- %s: %s", string(header), h, string(header), value),
Payload: map[string]string{string(header): h},
ID: "http.Header",
Diff: fmt.Sprintf("+ %s: %s\n- %s: %s", string(header), h, string(header), value),
Protocol: header,
Expect: value,
Actual: h,
}
}

Expand All @@ -331,8 +350,9 @@ func liftString(ctx *http.Context, header string, value *string) error {
val := ctx.Response.Header.Get(string(header))
if val == "" {
return &gurl.NoMatch{
Diff: fmt.Sprintf("- %s: *", string(header)),
Payload: nil,
ID: "http.Header",
Diff: fmt.Sprintf("- %s: *", string(header)),
Protocol: header,
}
}

Expand All @@ -344,8 +364,9 @@ func liftInt(ctx *http.Context, header string, value *int) error {
val := ctx.Response.Header.Get(string(header))
if val == "" {
return &gurl.NoMatch{
Diff: fmt.Sprintf("- %s: *", string(header)),
Payload: nil,
ID: "http.Header",
Diff: fmt.Sprintf("- %s: *", string(header)),
Protocol: header,
}
}

Expand All @@ -362,8 +383,9 @@ func liftTime(ctx *http.Context, header string, value *time.Time) error {
val := ctx.Response.Header.Get(string(header))
if val == "" {
return &gurl.NoMatch{
Diff: fmt.Sprintf("- %s: *", string(header)),
Payload: nil,
ID: "http.Header",
Diff: fmt.Sprintf("- %s: *", string(header)),
Protocol: header,
}
}

Expand Down Expand Up @@ -611,15 +633,41 @@ const (
// native Go data structure. The Content-Type header give a hint to decoder.
// Supply the pointer to data target data structure.
func Recv[T any](out *T) http.Arrow {
return func(cat *http.Context) (err error) {
err = decode(
return func(cat *http.Context) error {
err := decode(
cat.Response.Header.Get("Content-Type"),
cat.Response.Body,
out,
)
cat.Response.Body.Close()
cat.Response = nil
return
return err
}
}

func Expect[T any](expect T) http.Arrow {
return func(cat *http.Context) error {
var actual T
err := decode(
cat.Response.Header.Get("Content-Type"),
cat.Response.Body,
&actual,
)
cat.Response.Body.Close()
cat.Response = nil

diff := cmp.Diff(actual, expect)
if diff != "" {
return &gurl.NoMatch{
ID: "http.Recv",
Diff: diff,
Protocol: "body",
Expect: expect,
Actual: actual,
}
}

return err
}
}

Expand All @@ -631,8 +679,10 @@ func decode[T any](content string, stream io.ReadCloser, data *T) error {
return form.NewDecoder(stream).Decode(data)
default:
return &gurl.NoMatch{
Diff: fmt.Sprintf("- Content-Type: application/*\n+ Content-Type: %s", content),
Payload: map[string]string{"Content-Type": content},
ID: "http.Recv",
Diff: fmt.Sprintf("- Content-Type: application/{json | www-form}\n+ Content-Type: %s", content),
Protocol: "codec",
Actual: content,
}
}
}
Expand All @@ -649,13 +699,13 @@ func Bytes(val *[]byte) http.Arrow {

// Match received payload to defined pattern
func Match(val string) http.Arrow {
var pat map[string]any
var pat any
if err := json.Unmarshal([]byte(val), &pat); err != nil {
panic(err)
}

return func(cat *http.Context) (err error) {
var val map[string]any
var val any

err = decode(
cat.Response.Header.Get("Content-Type"),
Expand All @@ -665,8 +715,13 @@ func Match(val string) http.Arrow {
cat.Response.Body.Close()
cat.Response = nil

if !equiv(pat, val) {
return &gurl.NoMatch{}
if !equivVal(pat, val) {
return &gurl.NoMatch{
ID: "http.Match",
Protocol: "body",
Expect: pat,
Actual: val,
}
}

return
Expand Down
52 changes: 48 additions & 4 deletions http/recv/arrows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,10 @@ func TestCodeNoMatch(t *testing.T) {
ƒ.Status.OK,
)
cat := µ.New()
var err interface{ StatusCode() int }
f := func() error { return cat.IO(context.Background(), req) }
err := cat.IO(context.Background(), req)

it.Then(t).Should(
it.Fail(f).With(&err),
it.Equal(err.(µ.StatusCode).StatusCode(), µ.StatusBadRequest.StatusCode()),
it.Equal(err.Error(), "+ Status Code: 400\n- Status Code: 200"),
)
}

Expand Down Expand Up @@ -300,6 +298,52 @@ func TestRecvForm(t *testing.T) {
)
}

func TestExpectJSON(t *testing.T) {
type Site struct {
Site string `json:"site"`
}

ts := mock()
defer ts.Close()

req := µ.GET(
ø.URI("%s/json", ø.Authority(ts.URL)),
ƒ.Status.OK,
ƒ.ContentType.ApplicationJSON,
ƒ.ContentType.JSON,
ƒ.Expect(Site{"example.com"}),
)
cat := µ.New()
err := cat.IO(context.Background(), req)

it.Then(t).Should(
it.Nil(err),
)
}

func TestExpectJSONFailed(t *testing.T) {
type Site struct {
Site string `json:"site"`
}

ts := mock()
defer ts.Close()

req := µ.GET(
ø.URI("%s/json", ø.Authority(ts.URL)),
ƒ.Status.OK,
ƒ.ContentType.ApplicationJSON,
ƒ.ContentType.JSON,
ƒ.Expect(Site{"some.com"}),
)
cat := µ.New()
err := cat.IO(context.Background(), req)

it.Then(t).ShouldNot(
it.Equal(err.Error(), ""),
)
}

func TestRecvBytes(t *testing.T) {
ts := mock()
defer ts.Close()
Expand Down
6 changes: 4 additions & 2 deletions http/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ func decode[T any](content string, stream io.ReadCloser, data *T) error {
return form.NewDecoder(stream).Decode(data)
default:
return &gurl.NoMatch{
Diff: fmt.Sprintf("- Content-Type: application/*\n+ Content-Type: %s", content),
Payload: map[string]string{"Content-Type": content},
ID: "http.Recv",
Diff: fmt.Sprintf("- Content-Type: application/{json | www-form}\n+ Content-Type: %s", content),
Protocol: "codec",
Actual: content,
}
}
}
11 changes: 6 additions & 5 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ func (e *NotSupported) Error() string {

// Mismatch is returned by api if expectation at body value is failed
type NoMatch struct {
Diff string
Payload interface{}
ID string // unique ID of failed combinator
Protocol any // protocol primitive caused failure
Diff string // human readable difference between expected & actual values
Expect any // expected value
Actual any // actual value
}

func (e *NoMatch) Error() string {
return e.Diff
}
func (e *NoMatch) Error() string { return e.Diff }

0 comments on commit 8b16eec

Please sign in to comment.