Update dependencies (#32)
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"Deadline": "30s",
|
||||
"LineLength": 90,
|
||||
"Enable": [
|
||||
"gotype",
|
||||
"goimports",
|
||||
"golint",
|
||||
"gosimple",
|
||||
"gofmt",
|
||||
"vet",
|
||||
"lll",
|
||||
"staticcheck",
|
||||
"ineffassign",
|
||||
"deadcode",
|
||||
"unused",
|
||||
"unconvert",
|
||||
"misspell",
|
||||
"test",
|
||||
"testify"
|
||||
]
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.7
|
||||
- 1.8
|
||||
|
||||
before_install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get github.com/alecthomas/gometalinter
|
||||
- ${GOPATH}/bin/gometalinter --install
|
||||
|
||||
script:
|
||||
- go get -v -t . ./_examples
|
||||
- go test -coverprofile profile.cov .
|
||||
- go test ./_examples
|
||||
- ${GOPATH}/bin/gometalinter --config .gometalinter
|
||||
- ${GOPATH}/bin/goveralls -coverprofile profile.cov -service=travis-ci
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Victor Gaydov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
all: update test check
|
||||
|
||||
update:
|
||||
go get -u -t . ./_examples
|
||||
|
||||
test:
|
||||
go test . ./_examples
|
||||
|
||||
check:
|
||||
go get -u github.com/alecthomas/gometalinter
|
||||
gometalinter --install
|
||||
gometalinter --config .gometalinter
|
||||
|
||||
fmt:
|
||||
gofmt -s -w . ./_examples
|
||||
+455
@@ -0,0 +1,455 @@
|
||||
# httpexpect [](https://godoc.org/github.com/gavv/httpexpect) [](https://gitter.im/gavv/httpexpect?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://travis-ci.org/gavv/httpexpect) [](https://coveralls.io/github/gavv/httpexpect?branch=master)
|
||||
|
||||
Concise, declarative, and easy to use end-to-end HTTP and REST API testing for Go (golang).
|
||||
|
||||
Basically, httpexpect is a set of chainable *builders* for HTTP requests and *assertions* for HTTP responses and payload, on top of net/http and several utility packages.
|
||||
|
||||
Workflow:
|
||||
|
||||
* Incrementally build HTTP requests.
|
||||
* Inspect HTTP responses.
|
||||
* Inspect response payload recursively.
|
||||
|
||||
## Features
|
||||
|
||||
##### Request builder
|
||||
|
||||
* URL path construction, with simple string interpolation provided by [`go-interpol`](https://github.com/imkira/go-interpol) package.
|
||||
* URL query parameters (encoding using [`go-querystring`](https://github.com/google/go-querystring) package).
|
||||
* Headers, cookies, payload: JSON, urlencoded or multipart forms (encoding using [`form`](https://github.com/ajg/form) package), plain text.
|
||||
* Create custom [request builders](#reusable-builders) that can be reused.
|
||||
|
||||
##### Response assertions
|
||||
|
||||
* Response status, predefined status ranges.
|
||||
* Headers, cookies, payload: JSON, JSONP, forms, text.
|
||||
* Round-trip time.
|
||||
|
||||
##### Payload assertions
|
||||
|
||||
* Type-specific assertions, supported types: object, array, string, number, boolean, null, datetime.
|
||||
* Regular expressions.
|
||||
* Simple JSON queries (using subset of [JSONPath](http://goessner.net/articles/JsonPath/)), provided by [`jsonpath`](https://github.com/yalp/jsonpath) package.
|
||||
* [JSON Schema](http://json-schema.org/) validation, provided by [`gojsonschema`](https://github.com/xeipuuv/gojsonschema) package.
|
||||
|
||||
##### Pretty printing
|
||||
|
||||
* Verbose error messages.
|
||||
* JSON diff is produced on failure using [`gojsondiff`](https://github.com/yudai/gojsondiff/) package.
|
||||
* Failures are reported using [`testify`](https://github.com/stretchr/testify/) (`assert` or `require` package) or standard `testing` package.
|
||||
* Dumping requests and responses in various formats, using [`httputil`](https://golang.org/pkg/net/http/httputil/), [`http2curl`](https://github.com/moul/http2curl), or simple compact logger.
|
||||
|
||||
##### Tuning
|
||||
|
||||
* Tests can communicate with server via real HTTP client or invoke `net/http` or [`fasthttp`](https://github.com/valyala/fasthttp/) handler directly.
|
||||
* Custom HTTP client, logger, printer, and failure reporter may be provided by user.
|
||||
* Custom HTTP request factory may be provided, e.g. from the Google App Engine testing.
|
||||
|
||||
## Status
|
||||
|
||||
Stable branches are available on [`gopkg.in`](http://labix.org/gopkg.in) and will not introduce backward-incompatible changes.
|
||||
|
||||
Current stable branch is [`v1`](http://gopkg.in/gavv/httpexpect.v1):
|
||||
|
||||
```go
|
||||
import "gopkg.in/gavv/httpexpect.v1"
|
||||
```
|
||||
|
||||
Development is done in `master` branch on github:
|
||||
|
||||
```go
|
||||
import "github.com/gavv/httpexpect"
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is available on [GoDoc](https://godoc.org/github.com/gavv/httpexpect). It contains an overview and reference.
|
||||
|
||||
## Examples
|
||||
|
||||
See [`_examples`](_examples) directory for complete standalone examples.
|
||||
|
||||
* [`fruits_test.go`](_examples/fruits_test.go)
|
||||
|
||||
Testing a simple CRUD server made with bare `net/http`.
|
||||
|
||||
* [`iris_test.go`](_examples/iris_test.go)
|
||||
|
||||
Testing a server made with [`iris`](https://github.com/kataras/iris/) framework. Example includes JSON queries and validation, URL and form parameters, basic auth, sessions, and streaming. Tests invoke the `http.Handler` directly.
|
||||
|
||||
* [`echo_test.go`](_examples/echo_test.go)
|
||||
|
||||
Testing a server with JWT authentication made with [`echo`](https://github.com/labstack/echo/) framework. Tests use either HTTP client or invoke the `http.Handler` directly.
|
||||
|
||||
* [`fasthttp_test.go`](_examples/fasthttp_test.go)
|
||||
|
||||
Testing a server made with [`fasthttp`](https://github.com/valyala/fasthttp) package. Tests invoke the `fasthttp.RequestHandler` directly.
|
||||
|
||||
* [`gae_test.go`](_examples/gae_test.go)
|
||||
|
||||
Testing a server running under the [Google App Engine](https://en.wikipedia.org/wiki/Google_App_Engine).
|
||||
|
||||
## Quick start
|
||||
|
||||
##### Hello, world!
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gavv/httpexpect"
|
||||
)
|
||||
|
||||
func TestFruits(t *testing.T) {
|
||||
// create http.Handler
|
||||
handler := FruitsHandler()
|
||||
|
||||
// run server using httptest
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
// create httpexpect instance
|
||||
e := httpexpect.New(t, server.URL)
|
||||
|
||||
// is it working?
|
||||
e.GET("/fruits").
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Array().Empty()
|
||||
}
|
||||
```
|
||||
|
||||
##### JSON
|
||||
|
||||
```go
|
||||
orange := map[string]interface{}{
|
||||
"weight": 100,
|
||||
}
|
||||
|
||||
e.PUT("/fruits/orange").WithJSON(orange).
|
||||
Expect().
|
||||
Status(http.StatusNoContent).NoContent()
|
||||
|
||||
e.GET("/fruits/orange").
|
||||
Expect().
|
||||
Status(http.StatusOK).
|
||||
JSON().Object().ContainsKey("weight").ValueEqual("weight", 100)
|
||||
|
||||
apple := map[string]interface{}{
|
||||
"colors": []interface{}{"green", "red"},
|
||||
"weight": 200,
|
||||
}
|
||||
|
||||
e.PUT("/fruits/apple").WithJSON(apple).
|
||||
Expect().
|
||||
Status(http.StatusNoContent).NoContent()
|
||||
|
||||
obj := e.GET("/fruits/apple").
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Object()
|
||||
|
||||
obj.Keys().ContainsOnly("colors", "weight")
|
||||
|
||||
obj.Value("colors").Array().Elements("green", "red")
|
||||
obj.Value("colors").Array().Element(0).String().Equal("green")
|
||||
obj.Value("colors").Array().Element(1).String().Equal("red")
|
||||
obj.Value("colors").Array().First().String().Equal("green")
|
||||
obj.Value("colors").Array().Last().String().Equal("red")
|
||||
```
|
||||
|
||||
##### JSON Schema and JSON Path
|
||||
|
||||
```go
|
||||
schema := `{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
...
|
||||
"private": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
repos := e.GET("/repos/octocat").
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON()
|
||||
|
||||
// validate JSON schema
|
||||
repos.Schema(schema)
|
||||
|
||||
// run JSONPath query and iterate results
|
||||
for _, private := range repos.Path("$..private").Array().Iter() {
|
||||
private.Boolean().False()
|
||||
}
|
||||
```
|
||||
|
||||
##### Forms
|
||||
|
||||
```go
|
||||
// post form encoded from struct or map
|
||||
e.POST("/form").WithForm(structOrMap).
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
// set individual fields
|
||||
e.POST("/form").WithFormField("foo", "hello").WithFormField("bar", 123).
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
// multipart form
|
||||
e.POST("/form").WithMultipart().
|
||||
WithFile("avatar", "./john.png").WithFormField("username", "john").
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
```
|
||||
|
||||
##### URL construction
|
||||
|
||||
```go
|
||||
// construct path using ordered parameters
|
||||
e.GET("/repos/{user}/{repo}", "octocat", "hello-world").
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
// construct path using named parameters
|
||||
e.GET("/repos/{user}/{repo}").
|
||||
WithPath("user", "octocat").WithPath("repo", "hello-world").
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
// set query parameters
|
||||
e.GET("/repos/{user}", "octocat").WithQuery("sort", "asc").
|
||||
Expect().
|
||||
Status(http.StatusOK) // "/repos/octocat?sort=asc"
|
||||
```
|
||||
|
||||
##### Headers
|
||||
|
||||
```go
|
||||
// set If-Match
|
||||
e.POST("/users/john").WithHeader("If-Match", etag).WithJSON(john).
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
// check ETag
|
||||
e.GET("/users/john").
|
||||
Expect().
|
||||
Status(http.StatusOK).Header("ETag").NotEmpty()
|
||||
|
||||
// check Date
|
||||
t := time.Now()
|
||||
|
||||
e.GET("/users/john").
|
||||
Expect().
|
||||
Status(http.StatusOK).Header("Date").DateTime().InRange(t, time.Now())
|
||||
```
|
||||
|
||||
##### Cookies
|
||||
|
||||
```go
|
||||
// set cookie
|
||||
t := time.Now()
|
||||
|
||||
e.POST("/users/john").WithCookie("session", sessionID).WithJSON(john).
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
// check cookies
|
||||
c := e.GET("/users/john").
|
||||
Expect().
|
||||
Status(http.StatusOK).Cookie("session")
|
||||
|
||||
c.Value().Equal(sessionID)
|
||||
c.Domain().Equal("example.com")
|
||||
c.Path().Equal("/")
|
||||
c.Expires().InRange(t, t.Add(time.Hour * 24))
|
||||
```
|
||||
|
||||
##### Regular expressions
|
||||
|
||||
```go
|
||||
// simple match
|
||||
e.GET("/users/john").
|
||||
Expect().
|
||||
Header("Location").
|
||||
Match("http://(.+)/users/(.+)").Values("example.com", "john")
|
||||
|
||||
// check capture groups by index or name
|
||||
m := e.GET("/users/john").
|
||||
Expect().
|
||||
Header("Location").Match("http://(?P<host>.+)/users/(?P<user>.+)")
|
||||
|
||||
m.Index(0).Equal("http://example.com/users/john")
|
||||
m.Index(1).Equal("example.com")
|
||||
m.Index(2).Equal("john")
|
||||
|
||||
m.Name("host").Equal("example.com")
|
||||
m.Name("user").Equal("john")
|
||||
```
|
||||
|
||||
##### Subdomains and per-request URL
|
||||
|
||||
```go
|
||||
e.GET("/path").WithURL("http://example.com").
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
e.GET("/path").WithURL("http://subdomain.example.com").
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
```
|
||||
|
||||
##### Reusable builders
|
||||
|
||||
```go
|
||||
e := httpexpect.New(t, "http://example.com")
|
||||
|
||||
r := e.POST("/login").WithForm(Login{"ford", "betelgeuse7"}).
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Object()
|
||||
|
||||
token := r.Value("token").String().Raw()
|
||||
|
||||
auth := e.Builder(func (req *httpexpect.Request) {
|
||||
req.WithHeader("Authorization", "Bearer "+token)
|
||||
})
|
||||
|
||||
auth.GET("/restricted").
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
e.GET("/restricted").
|
||||
Expect().
|
||||
Status(http.StatusUnauthorized)
|
||||
```
|
||||
|
||||
##### Custom config
|
||||
|
||||
```go
|
||||
e := httpexpect.WithConfig(httpexpect.Config{
|
||||
// prepend this url to all requests
|
||||
BaseURL: "http://example.com",
|
||||
|
||||
// use http.Client with a cookie jar and timeout
|
||||
Client: &http.Client{
|
||||
Jar: httpexpect.NewJar(),
|
||||
Timeout: time.Second * 30,
|
||||
},
|
||||
|
||||
// use fatal failures
|
||||
Reporter: httpexpect.NewRequireReporter(t),
|
||||
|
||||
// use verbose logging
|
||||
Printers: []httpexpect.Printer{
|
||||
httpexpect.NewCurlPrinter(t),
|
||||
httpexpect.NewDebugPrinter(t, true),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
##### Session support
|
||||
|
||||
```go
|
||||
// cookie jar is used to store cookies from server
|
||||
e := httpexpect.WithConfig(httpexpect.Config{
|
||||
Reporter: httpexpect.NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Jar: httpexpect.NewJar(), // used by default if Client is nil
|
||||
},
|
||||
})
|
||||
|
||||
// cookies are disabled
|
||||
e := httpexpect.WithConfig(httpexpect.Config{
|
||||
Reporter: httpexpect.NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Jar: nil,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
##### Use HTTP handler directly
|
||||
|
||||
```go
|
||||
// invoke http.Handler directly using httpexpect.Binder
|
||||
var handler http.Handler = MyHandler()
|
||||
|
||||
e := httpexpect.WithConfig(httpexpect.Config{
|
||||
Reporter: httpexpect.NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: httpexpect.NewBinder(handler),
|
||||
Jar: httpexpect.NewJar(),
|
||||
},
|
||||
})
|
||||
|
||||
// invoke fasthttp.RequestHandler directly using httpexpect.FastBinder
|
||||
var handler fasthttp.RequestHandler = myHandler()
|
||||
|
||||
e := httpexpect.WithConfig(httpexpect.Config{
|
||||
Reporter: httpexpect.NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: httpexpect.NewFastBinder(handler),
|
||||
Jar: httpexpect.NewJar(),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
##### Per-request client or handler
|
||||
|
||||
```go
|
||||
e := httpexpect.New(t, server.URL)
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DisableCompression: true,
|
||||
},
|
||||
}
|
||||
|
||||
// overwrite client
|
||||
e.GET("/path").WithClient(client).
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
// construct client that invokes a handler directly and overwrite client
|
||||
e.GET("/path").WithHandler(handler).
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
```
|
||||
|
||||
## Similar packages
|
||||
|
||||
* [`gorequest`](https://github.com/parnurzeal/gorequest)
|
||||
* [`gabs`](https://github.com/Jeffail/gabs)
|
||||
* [`gofight`](https://github.com/appleboy/gofight)
|
||||
* [`baloo`](https://github.com/h2non/baloo)
|
||||
* [`frisby`](https://github.com/verdverm/frisby)
|
||||
* [`forest`](https://github.com/emicklei/forest)
|
||||
* [`restit`](https://github.com/go-restit/restit)
|
||||
* [`supertest`](https://github.com/haoxins/supertest)
|
||||
* [`http-test`](https://github.com/vsco/http-test)
|
||||
* [`go-json-rest/rest/test`](https://godoc.org/github.com/ant0ine/go-json-rest/rest/test)
|
||||
|
||||
## Contributing
|
||||
|
||||
Feel free to report bugs, suggest improvements, and send pull requests! Please add documentation and tests for new features.
|
||||
|
||||
Update dependencies, build code, and run tests and linters:
|
||||
|
||||
```
|
||||
$ make
|
||||
```
|
||||
|
||||
Format code:
|
||||
|
||||
```
|
||||
$ make fmt
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package examples demonstrates httpexpect usage.
|
||||
package examples
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
// EchoHandler creates http.Handler using echo framework.
|
||||
//
|
||||
// Routes:
|
||||
// GET /login authenticate user and return JWT token
|
||||
// GET /restricted/hello return "hello, world!" (requires authentication)
|
||||
func EchoHandler() http.Handler {
|
||||
e := echo.New()
|
||||
|
||||
e.POST("/login", func(ctx echo.Context) error {
|
||||
username := ctx.FormValue("username")
|
||||
password := ctx.FormValue("password")
|
||||
|
||||
if username == "ford" && password == "betelgeuse7" {
|
||||
// create token
|
||||
token := jwt.New(jwt.SigningMethodHS256)
|
||||
|
||||
// generate encoded token and send it as response
|
||||
t, err := token.SignedString([]byte("secret"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.JSON(http.StatusOK, map[string]string{
|
||||
"token": t,
|
||||
})
|
||||
}
|
||||
|
||||
return echo.ErrUnauthorized
|
||||
})
|
||||
|
||||
r := e.Group("/restricted")
|
||||
|
||||
r.Use(middleware.JWT([]byte("secret")))
|
||||
|
||||
r.GET("/hello", func(ctx echo.Context) error {
|
||||
return ctx.String(http.StatusOK, "hello, world!")
|
||||
})
|
||||
|
||||
return e
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gavv/httpexpect"
|
||||
)
|
||||
|
||||
// Echo JWT token authentication tests.
|
||||
//
|
||||
// This test is executed for the EchoHandler() in two modes:
|
||||
// - via http client
|
||||
// - via http.Handler
|
||||
func testEcho(e *httpexpect.Expect) {
|
||||
type Login struct {
|
||||
Username string `form:"username"`
|
||||
Password string `form:"password"`
|
||||
}
|
||||
|
||||
e.POST("/login").WithForm(Login{"ford", "<bad password>"}).
|
||||
Expect().
|
||||
Status(http.StatusUnauthorized)
|
||||
|
||||
r := e.POST("/login").WithForm(Login{"ford", "betelgeuse7"}).
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Object()
|
||||
|
||||
r.Keys().ContainsOnly("token")
|
||||
|
||||
token := r.Value("token").String().Raw()
|
||||
|
||||
e.GET("/restricted/hello").
|
||||
Expect().
|
||||
Status(http.StatusBadRequest)
|
||||
|
||||
e.GET("/restricted/hello").WithHeader("Authorization", "Bearer <bad token>").
|
||||
Expect().
|
||||
Status(http.StatusUnauthorized)
|
||||
|
||||
e.GET("/restricted/hello").WithHeader("Authorization", "Bearer "+token).
|
||||
Expect().
|
||||
Status(http.StatusOK).Body().Equal("hello, world!")
|
||||
|
||||
auth := e.Builder(func(req *httpexpect.Request) {
|
||||
req.WithHeader("Authorization", "Bearer "+token)
|
||||
})
|
||||
|
||||
auth.GET("/restricted/hello").
|
||||
Expect().
|
||||
Status(http.StatusOK).Body().Equal("hello, world!")
|
||||
}
|
||||
|
||||
func TestEchoClient(t *testing.T) {
|
||||
handler := EchoHandler()
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
e := httpexpect.WithConfig(httpexpect.Config{
|
||||
BaseURL: server.URL,
|
||||
Reporter: httpexpect.NewAssertReporter(t),
|
||||
Printers: []httpexpect.Printer{
|
||||
httpexpect.NewDebugPrinter(t, true),
|
||||
},
|
||||
})
|
||||
|
||||
testEcho(e)
|
||||
}
|
||||
|
||||
func TestEchoHandler(t *testing.T) {
|
||||
handler := EchoHandler()
|
||||
|
||||
e := httpexpect.WithConfig(httpexpect.Config{
|
||||
Client: &http.Client{
|
||||
Transport: httpexpect.NewBinder(handler),
|
||||
Jar: httpexpect.NewJar(),
|
||||
},
|
||||
Reporter: httpexpect.NewAssertReporter(t),
|
||||
Printers: []httpexpect.Printer{
|
||||
httpexpect.NewDebugPrinter(t, true),
|
||||
},
|
||||
})
|
||||
|
||||
testEcho(e)
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// FastHTTPHandler creates fasthttp.RequestHandler.
|
||||
//
|
||||
// Routes:
|
||||
// GET /ping return "pong"
|
||||
func FastHTTPHandler() fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
switch string(ctx.Path()) {
|
||||
case "/ping":
|
||||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||||
ctx.SetContentType("text/plain")
|
||||
ctx.SetBody([]byte("pong"))
|
||||
|
||||
default:
|
||||
panic("unsupported path")
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gavv/httpexpect"
|
||||
)
|
||||
|
||||
// fastHTTPTester returns a new Expect instance to test FastHTTPHandler().
|
||||
func fastHTTPTester(t *testing.T) *httpexpect.Expect {
|
||||
return httpexpect.WithConfig(httpexpect.Config{
|
||||
// Pass requests directly to FastHTTPHandler.
|
||||
Client: &http.Client{
|
||||
Transport: httpexpect.NewFastBinder(FastHTTPHandler()),
|
||||
Jar: httpexpect.NewJar(),
|
||||
},
|
||||
// Report errors using testify.
|
||||
Reporter: httpexpect.NewAssertReporter(t),
|
||||
})
|
||||
}
|
||||
|
||||
func TestFastHTTP(t *testing.T) {
|
||||
e := fastHTTPTester(t)
|
||||
|
||||
e.GET("/ping").Expect().
|
||||
Status(200).
|
||||
Text().Equal("pong")
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
type (
|
||||
fruitMap map[string]interface{}
|
||||
)
|
||||
|
||||
// FruitsHandler creates http.Handler for the fruits server.
|
||||
//
|
||||
// Routes:
|
||||
// GET /fruits get fruit list
|
||||
// GET /fruits/{name} get fruit
|
||||
// PUT /fruits/{name} add or update fruit
|
||||
func FruitsHandler() http.Handler {
|
||||
fruits := fruitMap{}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/fruits", func(w http.ResponseWriter, r *http.Request) {
|
||||
handleFruitList(fruits, w, r)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/fruits/", func(w http.ResponseWriter, r *http.Request) {
|
||||
handleFruit(fruits, w, r)
|
||||
})
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func handleFruitList(fruits fruitMap, w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
ret := []string{}
|
||||
for k := range fruits {
|
||||
ret = append(ret, k)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(ret)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(b)
|
||||
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func handleFruit(fruits fruitMap, w http.ResponseWriter, r *http.Request) {
|
||||
_, name := path.Split(r.URL.Path)
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
if data, ok := fruits[name]; ok {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(b)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
case "PUT":
|
||||
var data map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fruits[name] = data
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gavv/httpexpect"
|
||||
)
|
||||
|
||||
func TestFruits(t *testing.T) {
|
||||
handler := FruitsHandler()
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
e := httpexpect.New(t, server.URL)
|
||||
|
||||
e.GET("/fruits").
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Array().Empty()
|
||||
|
||||
orange := map[string]interface{}{
|
||||
"weight": 100,
|
||||
}
|
||||
|
||||
e.PUT("/fruits/orange").WithJSON(orange).
|
||||
Expect().
|
||||
Status(http.StatusNoContent).NoContent()
|
||||
|
||||
apple := map[string]interface{}{
|
||||
"colors": []interface{}{"green", "red"},
|
||||
"weight": 200,
|
||||
}
|
||||
|
||||
e.PUT("/fruits/apple").WithJSON(apple).
|
||||
Expect().
|
||||
Status(http.StatusNoContent).NoContent()
|
||||
|
||||
e.GET("/fruits").
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Array().ContainsOnly("orange", "apple")
|
||||
|
||||
e.GET("/fruits/orange").
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Object().Equal(orange).NotEqual(apple)
|
||||
|
||||
e.GET("/fruits/orange").
|
||||
Expect().
|
||||
Status(http.StatusOK).
|
||||
JSON().Object().ContainsKey("weight").ValueEqual("weight", 100)
|
||||
|
||||
obj := e.GET("/fruits/apple").
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Object()
|
||||
|
||||
obj.Keys().ContainsOnly("colors", "weight")
|
||||
|
||||
obj.Value("colors").Array().Elements("green", "red")
|
||||
obj.Value("colors").Array().Element(0).String().Equal("green")
|
||||
obj.Value("colors").Array().Element(1).String().Equal("red")
|
||||
|
||||
obj.Value("weight").Number().Equal(200)
|
||||
|
||||
e.GET("/fruits/melon").
|
||||
Expect().
|
||||
Status(http.StatusNotFound)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"google.golang.org/appengine"
|
||||
)
|
||||
|
||||
// GaeHandler creates http.Handler to run in the Google App Engine.
|
||||
//
|
||||
// Routes:
|
||||
// GET /ping return "pong"
|
||||
func GaeHandler() http.Handler {
|
||||
m := http.NewServeMux()
|
||||
|
||||
m.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = appengine.NewContext(r)
|
||||
w.Write([]byte("pong"))
|
||||
})
|
||||
|
||||
return m
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gavv/httpexpect"
|
||||
"google.golang.org/appengine/aetest"
|
||||
)
|
||||
|
||||
// init() is used by GAE to start serving the app
|
||||
// added here for illustration purposes
|
||||
//
|
||||
// func init() {
|
||||
// http.Handle("/", GaeHandler())
|
||||
// }
|
||||
|
||||
// gaeInstance is our global dev_appserver instance.
|
||||
var gaeInstance aetest.Instance
|
||||
|
||||
// TestMain is called first to create the gaeInstance.
|
||||
func TestMain(m *testing.M) {
|
||||
// INFO: Remove the return to actually run the tests.
|
||||
// Requires installed Google Appengine SDK.
|
||||
// https://cloud.google.com/appengine/downloads
|
||||
return
|
||||
|
||||
var err error
|
||||
gaeInstance, err = aetest.NewInstance(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c := m.Run() // call all actual tests
|
||||
gaeInstance.Close()
|
||||
os.Exit(c)
|
||||
}
|
||||
|
||||
// gaeTester returns a new Expect instance to test GaeHandler().
|
||||
func gaeTester(t *testing.T) *httpexpect.Expect {
|
||||
return httpexpect.WithConfig(httpexpect.Config{
|
||||
// Use gaeInstance to create requests.
|
||||
// aetest.Instance is compatible with httpexpect.RequestFactory.
|
||||
RequestFactory: gaeInstance,
|
||||
|
||||
// Pass requests directly to GaeHandler.
|
||||
Client: &http.Client{
|
||||
Transport: httpexpect.NewBinder(GaeHandler()),
|
||||
Jar: httpexpect.NewJar(),
|
||||
},
|
||||
|
||||
// Report errors using testify.
|
||||
Reporter: httpexpect.NewAssertReporter(t),
|
||||
})
|
||||
}
|
||||
|
||||
func TestGae(t *testing.T) {
|
||||
e := gaeTester(t)
|
||||
|
||||
e.GET("/ping").Expect().
|
||||
Status(200).
|
||||
Text().Equal("pong")
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
// +build go1.8
|
||||
|
||||
package examples
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/sessions"
|
||||
"gopkg.in/kataras/iris.v6/middleware/basicauth"
|
||||
)
|
||||
|
||||
// IrisHandler tests iris v6's handler
|
||||
func IrisHandler() http.Handler {
|
||||
app := iris.New(iris.Configuration{VHost: "example.com"})
|
||||
|
||||
app.Adapt(iris.DevLogger())
|
||||
app.Adapt(httprouter.New())
|
||||
app.Adapt(sessions.New(sessions.Config{Cookie: "irissessionid"}))
|
||||
|
||||
app.Get("/things", func(c *iris.Context) {
|
||||
c.JSON(iris.StatusOK, []interface{}{
|
||||
iris.Map{
|
||||
"name": "foo",
|
||||
"description": "foo thing",
|
||||
},
|
||||
iris.Map{
|
||||
"name": "bar",
|
||||
"description": "bar thing",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
app.Post("/redirect", func(c *iris.Context) {
|
||||
c.Redirect("/things", iris.StatusFound)
|
||||
})
|
||||
|
||||
app.Post("/params/:x/:y", func(c *iris.Context) {
|
||||
c.JSON(iris.StatusOK, iris.Map{
|
||||
"x": c.Param("x"),
|
||||
"y": c.Param("y"),
|
||||
"q": c.URLParam("q"),
|
||||
"p1": c.FormValue("p1"),
|
||||
"p2": c.FormValue("p2"),
|
||||
})
|
||||
})
|
||||
|
||||
auth := basicauth.Default(map[string]string{
|
||||
"ford": "betelgeuse7",
|
||||
})
|
||||
|
||||
app.Get("/auth", auth, func(c *iris.Context) {
|
||||
c.Writef("authenticated!")
|
||||
})
|
||||
|
||||
app.Post("/session/set", func(c *iris.Context) {
|
||||
sess := iris.Map{}
|
||||
|
||||
if err := c.ReadJSON(&sess); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
c.Session().Set("name", sess["name"])
|
||||
})
|
||||
|
||||
app.Get("/session/get", func(c *iris.Context) {
|
||||
name := c.Session().GetString("name")
|
||||
|
||||
c.JSON(iris.StatusOK, iris.Map{
|
||||
"name": name,
|
||||
})
|
||||
})
|
||||
|
||||
app.Get("/stream", func(c *iris.Context) {
|
||||
c.StreamWriter(func(w io.Writer) bool {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Fprintf(w, "%d", i)
|
||||
}
|
||||
// return true to continue, return false to stop and flush
|
||||
return false
|
||||
})
|
||||
// if we had to write here then the StreamWriter callback should
|
||||
// return true
|
||||
})
|
||||
|
||||
app.Post("/stream", func(c *iris.Context) {
|
||||
body, err := ioutil.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
c.EmitError(iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
c.Write(body)
|
||||
})
|
||||
|
||||
sub := app.Party("subdomain.")
|
||||
|
||||
sub.Post("/set", func(c *iris.Context) {
|
||||
c.Session().Set("message", "hello from subdomain")
|
||||
})
|
||||
|
||||
sub.Get("/get", func(c *iris.Context) {
|
||||
c.Writef(c.Session().GetString("message"))
|
||||
})
|
||||
|
||||
app.Boot()
|
||||
return app.Router
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
// +build go1.8
|
||||
|
||||
package examples
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gavv/httpexpect"
|
||||
)
|
||||
|
||||
func irisTester(t *testing.T) *httpexpect.Expect {
|
||||
handler := IrisHandler()
|
||||
|
||||
return httpexpect.WithConfig(httpexpect.Config{
|
||||
BaseURL: "http://example.com",
|
||||
Client: &http.Client{
|
||||
Transport: httpexpect.NewBinder(handler),
|
||||
Jar: httpexpect.NewJar(),
|
||||
},
|
||||
Reporter: httpexpect.NewAssertReporter(t),
|
||||
Printers: []httpexpect.Printer{
|
||||
httpexpect.NewDebugPrinter(t, true),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestIrisThings(t *testing.T) {
|
||||
e := irisTester(t)
|
||||
|
||||
schema := `{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"description": {"type": "string"}
|
||||
},
|
||||
"required": ["name", "description"]
|
||||
}
|
||||
}`
|
||||
|
||||
things := e.GET("/things").
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON()
|
||||
|
||||
things.Schema(schema)
|
||||
|
||||
names := things.Path("$[*].name").Array()
|
||||
|
||||
names.Elements("foo", "bar")
|
||||
|
||||
for n, desc := range things.Path("$..description").Array().Iter() {
|
||||
m := desc.String().Match("(.+) (.+)")
|
||||
|
||||
m.Index(1).Equal(names.Element(n).String().Raw())
|
||||
m.Index(2).Equal("thing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIrisRedirect(t *testing.T) {
|
||||
e := irisTester(t)
|
||||
|
||||
things := e.POST("/redirect").
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Array()
|
||||
|
||||
things.Length().Equal(2)
|
||||
|
||||
things.Element(0).Object().ValueEqual("name", "foo")
|
||||
things.Element(1).Object().ValueEqual("name", "bar")
|
||||
}
|
||||
|
||||
func TestIrisParams(t *testing.T) {
|
||||
e := irisTester(t)
|
||||
|
||||
type Form struct {
|
||||
P1 string `form:"p1"`
|
||||
P2 string `form:"p2"`
|
||||
}
|
||||
|
||||
// POST /params/xxx/yyy?q=qqq
|
||||
// Form: p1=P1&p2=P2
|
||||
|
||||
r := e.POST("/params/{x}/{y}", "xxx", "yyy").
|
||||
WithQuery("q", "qqq").WithForm(Form{P1: "P1", P2: "P2"}).
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Object()
|
||||
|
||||
r.Value("x").Equal("xxx")
|
||||
r.Value("y").Equal("yyy")
|
||||
r.Value("q").Equal("qqq")
|
||||
|
||||
r.ValueEqual("p1", "P1")
|
||||
r.ValueEqual("p2", "P2")
|
||||
}
|
||||
|
||||
func TestIrisAuth(t *testing.T) {
|
||||
e := irisTester(t)
|
||||
|
||||
e.GET("/auth").
|
||||
Expect().
|
||||
Status(http.StatusUnauthorized)
|
||||
|
||||
e.GET("/auth").WithBasicAuth("ford", "<bad password>").
|
||||
Expect().
|
||||
Status(http.StatusUnauthorized)
|
||||
|
||||
e.GET("/auth").WithBasicAuth("ford", "betelgeuse7").
|
||||
Expect().
|
||||
Status(http.StatusOK).Body().Equal("authenticated!")
|
||||
}
|
||||
|
||||
func TestIrisSession(t *testing.T) {
|
||||
e := irisTester(t)
|
||||
|
||||
e.POST("/session/set").WithJSON(map[string]string{"name": "test"}).
|
||||
Expect().
|
||||
Status(http.StatusOK).Cookies().NotEmpty()
|
||||
|
||||
r := e.GET("/session/get").
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Object()
|
||||
|
||||
r.Equal(map[string]string{
|
||||
"name": "test",
|
||||
})
|
||||
}
|
||||
|
||||
func TestIrisStream(t *testing.T) {
|
||||
e := irisTester(t)
|
||||
|
||||
e.GET("/stream").
|
||||
Expect().
|
||||
Status(http.StatusOK).
|
||||
TransferEncoding("chunked"). // ensure server sent chunks
|
||||
Body().Equal("0123456789")
|
||||
|
||||
// send chunks to server
|
||||
e.POST("/stream").WithChunked(strings.NewReader("<long text>")).
|
||||
Expect().
|
||||
Status(http.StatusOK).Body().Equal("<long text>")
|
||||
}
|
||||
|
||||
func TestIrisSubdomain(t *testing.T) {
|
||||
e := irisTester(t)
|
||||
|
||||
sub := e.Builder(func(req *httpexpect.Request) {
|
||||
req.WithURL("http://subdomain.127.0.0.1")
|
||||
})
|
||||
|
||||
sub.POST("/set").
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
|
||||
sub.GET("/get").
|
||||
Expect().
|
||||
Status(http.StatusOK).
|
||||
Body().Equal("hello from subdomain")
|
||||
}
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Array provides methods to inspect attached []interface{} object
|
||||
// (Go representation of JSON array).
|
||||
type Array struct {
|
||||
chain chain
|
||||
value []interface{}
|
||||
}
|
||||
|
||||
// NewArray returns a new Array given a reporter used to report failures
|
||||
// and value to be inspected.
|
||||
//
|
||||
// Both reporter and value should not be nil. If value is nil, failure is
|
||||
// reported.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
func NewArray(reporter Reporter, value []interface{}) *Array {
|
||||
chain := makeChain(reporter)
|
||||
if value == nil {
|
||||
chain.fail("expected non-nil array value")
|
||||
} else {
|
||||
value, _ = canonArray(&chain, value)
|
||||
}
|
||||
return &Array{chain, value}
|
||||
}
|
||||
|
||||
// Raw returns underlying value attached to Array.
|
||||
// This is the value originally passed to NewArray, converted to canonical form.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
// assert.Equal(t, []interface{}{"foo", 123.0}, array.Raw())
|
||||
func (a *Array) Raw() []interface{} {
|
||||
return a.value
|
||||
}
|
||||
|
||||
// Path is similar to Value.Path.
|
||||
func (a *Array) Path(path string) *Value {
|
||||
return getPath(&a.chain, a.value, path)
|
||||
}
|
||||
|
||||
// Schema is similar to Value.Schema.
|
||||
func (a *Array) Schema(schema interface{}) *Array {
|
||||
checkSchema(&a.chain, a.value, schema)
|
||||
return a
|
||||
}
|
||||
|
||||
// Length returns a new Number object that may be used to inspect array length.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{1, 2, 3})
|
||||
// array.Length().Equal(3)
|
||||
func (a *Array) Length() *Number {
|
||||
return &Number{a.chain, float64(len(a.value))}
|
||||
}
|
||||
|
||||
// Element returns a new Value object that may be used to inspect array element
|
||||
// for given index.
|
||||
//
|
||||
// If index is out of array bounds, Element reports failure and returns empty
|
||||
// (but non-nil) value.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
// array.Element(0).String().Equal("foo")
|
||||
// array.Element(1).Number().Equal(123)
|
||||
func (a *Array) Element(index int) *Value {
|
||||
if index < 0 || index >= len(a.value) {
|
||||
a.chain.fail(
|
||||
"\narray index out of bounds:\n index %d\n\n bounds [%d; %d)",
|
||||
index,
|
||||
0,
|
||||
len(a.value))
|
||||
return &Value{a.chain, nil}
|
||||
}
|
||||
return &Value{a.chain, a.value[index]}
|
||||
}
|
||||
|
||||
// First returns a new Value object that may be used to inspect first element
|
||||
// of given array.
|
||||
//
|
||||
// If given array is empty, First reports failure and returns empty
|
||||
// (but non-nil) value.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
// array.First().String().Equal("foo")
|
||||
func (a *Array) First() *Value {
|
||||
if len(a.value) < 1 {
|
||||
a.chain.fail("\narray is empty")
|
||||
return &Value{a.chain, nil}
|
||||
}
|
||||
return &Value{a.chain, a.value[0]}
|
||||
}
|
||||
|
||||
// Last returns a new Value object that may be used to inspect last element
|
||||
// of given array.
|
||||
//
|
||||
// If given array is empty, Last reports failure and returns empty
|
||||
// (but non-nil) value.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
// array.Last().Number().Equal(123)
|
||||
func (a *Array) Last() *Value {
|
||||
if len(a.value) < 1 {
|
||||
a.chain.fail("\narray is empty")
|
||||
return &Value{a.chain, nil}
|
||||
}
|
||||
return &Value{a.chain, a.value[len(a.value)-1]}
|
||||
}
|
||||
|
||||
// Iter returns a new slice of Values attached to array elements.
|
||||
//
|
||||
// Example:
|
||||
// strings := []interface{}{"foo", "bar"}
|
||||
// array := NewArray(t, strings)
|
||||
//
|
||||
// for n, val := range array.Iter() {
|
||||
// val.String().Equal(strings[n])
|
||||
// }
|
||||
func (a *Array) Iter() []Value {
|
||||
if a.chain.failed() {
|
||||
return []Value{}
|
||||
}
|
||||
ret := []Value{}
|
||||
for n := range a.value {
|
||||
ret = append(ret, Value{a.chain, a.value[n]})
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Empty succeeds if array is empty.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{})
|
||||
// array.Empty()
|
||||
func (a *Array) Empty() *Array {
|
||||
return a.Equal([]interface{}{})
|
||||
}
|
||||
|
||||
// NotEmpty succeeds if array is non-empty.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
// array.NotEmpty()
|
||||
func (a *Array) NotEmpty() *Array {
|
||||
return a.NotEqual([]interface{}{})
|
||||
}
|
||||
|
||||
// Equal succeeds if array is equal to another array.
|
||||
// Before comparison, both arrays are converted to canonical form.
|
||||
//
|
||||
// value should be slice of any type.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
// array.Equal([]interface{}{"foo", 123})
|
||||
//
|
||||
// array := NewArray(t, []interface{}{"foo", "bar"})
|
||||
// array.Equal([]string{}{"foo", "bar"})
|
||||
//
|
||||
// array := NewArray(t, []interface{}{123, 456})
|
||||
// array.Equal([]int{}{123, 456})
|
||||
func (a *Array) Equal(value interface{}) *Array {
|
||||
expected, ok := canonArray(&a.chain, value)
|
||||
if !ok {
|
||||
return a
|
||||
}
|
||||
if !reflect.DeepEqual(expected, a.value) {
|
||||
a.chain.fail("\nexpected array equal to:\n%s\n\nbut got:\n%s\n\ndiff:\n%s",
|
||||
dumpValue(expected),
|
||||
dumpValue(a.value),
|
||||
diffValues(expected, a.value))
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// NotEqual succeeds if array is not equal to another array.
|
||||
// Before comparison, both arrays are converted to canonical form.
|
||||
//
|
||||
// value should be slice of any type.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
// array.NotEqual([]interface{}{123, "foo"})
|
||||
func (a *Array) NotEqual(value interface{}) *Array {
|
||||
expected, ok := canonArray(&a.chain, value)
|
||||
if !ok {
|
||||
return a
|
||||
}
|
||||
if reflect.DeepEqual(expected, a.value) {
|
||||
a.chain.fail("\nexpected array not equal to:\n%s",
|
||||
dumpValue(expected))
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Elements succeeds if array contains all given elements, in given order, and only them.
|
||||
// Before comparison, array and all elements are converted to canonical form.
|
||||
//
|
||||
// For partial or unordered comparison, see Contains and ContainsOnly.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
// array.Elements("foo", 123)
|
||||
//
|
||||
// This calls are equivalent:
|
||||
// array.Elelems("a", "b")
|
||||
// array.Equal([]interface{}{"a", "b"})
|
||||
func (a *Array) Elements(values ...interface{}) *Array {
|
||||
return a.Equal(values)
|
||||
}
|
||||
|
||||
// Contains succeeds if array contains all given elements (in any order).
|
||||
// Before comparison, array and all elements are converted to canonical form.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
// array.Contains(123, "foo")
|
||||
func (a *Array) Contains(values ...interface{}) *Array {
|
||||
elements, ok := canonArray(&a.chain, values)
|
||||
if !ok {
|
||||
return a
|
||||
}
|
||||
for _, e := range elements {
|
||||
if !a.containsElement(e) {
|
||||
a.chain.fail("\nexpected array containing element:\n%s\n\nbut got:\n%s",
|
||||
dumpValue(e), dumpValue(a.value))
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// NotContains succeeds if array contains none of given elements.
|
||||
// Before comparison, array and all elements are converted to canonical form.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
// array.NotContains("bar") // success
|
||||
// array.NotContains("bar", "foo") // failure (array contains "foo")
|
||||
func (a *Array) NotContains(values ...interface{}) *Array {
|
||||
elements, ok := canonArray(&a.chain, values)
|
||||
if !ok {
|
||||
return a
|
||||
}
|
||||
for _, e := range elements {
|
||||
if a.containsElement(e) {
|
||||
a.chain.fail("\nexpected array not containing element:\n%s\n\nbut got:\n%s",
|
||||
dumpValue(e), dumpValue(a.value))
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// ContainsOnly succeeds if array contains all given elements, in any order, and only
|
||||
// them. Before comparison, array and all elements are converted to canonical form.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(t, []interface{}{"foo", 123})
|
||||
// array.ContainsOnly(123, "foo")
|
||||
//
|
||||
// This calls are equivalent:
|
||||
// array.ContainsOnly("a", "b")
|
||||
// array.ContainsOnly("b", "a")
|
||||
func (a *Array) ContainsOnly(values ...interface{}) *Array {
|
||||
elements, ok := canonArray(&a.chain, values)
|
||||
if !ok {
|
||||
return a
|
||||
}
|
||||
if len(elements) != len(a.value) {
|
||||
a.chain.fail("\nexpected array of length == %d:\n%s\n\n"+
|
||||
"but got array of length %d:\n%s",
|
||||
len(elements), dumpValue(elements),
|
||||
len(a.value), dumpValue(a.value))
|
||||
return a
|
||||
}
|
||||
for _, e := range elements {
|
||||
if !a.containsElement(e) {
|
||||
a.chain.fail("\nexpected array containing element:\n%s\n\nbut got:\n%s",
|
||||
dumpValue(e), dumpValue(a.value))
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Array) containsElement(expected interface{}) bool {
|
||||
for _, e := range a.value {
|
||||
if reflect.DeepEqual(expected, e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+454
@@ -0,0 +1,454 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestArrayFailed(t *testing.T) {
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
chain.fail("fail")
|
||||
|
||||
value := &Array{chain, nil}
|
||||
|
||||
value.chain.assertFailed(t)
|
||||
|
||||
value.Path("$").chain.assertFailed(t)
|
||||
value.Schema("")
|
||||
|
||||
assert.False(t, value.Length() == nil)
|
||||
assert.False(t, value.Element(0) == nil)
|
||||
assert.False(t, value.Iter() == nil)
|
||||
assert.True(t, len(value.Iter()) == 0)
|
||||
|
||||
value.Length().chain.assertFailed(t)
|
||||
value.Element(0).chain.assertFailed(t)
|
||||
value.First().chain.assertFailed(t)
|
||||
value.Last().chain.assertFailed(t)
|
||||
|
||||
value.Empty()
|
||||
value.NotEmpty()
|
||||
value.Equal(nil)
|
||||
value.NotEqual(nil)
|
||||
value.Elements("foo")
|
||||
value.Contains("foo")
|
||||
value.NotContains("foo")
|
||||
value.ContainsOnly("foo")
|
||||
}
|
||||
|
||||
func TestArrayGetters(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
a := []interface{}{"foo", 123.0}
|
||||
|
||||
value := NewArray(reporter, a)
|
||||
|
||||
assert.Equal(t, a, value.Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, a, value.Path("$").Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Schema(`{"type": "array"}`)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Schema(`{"type": "object"}`)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, 2.0, value.Length().Raw())
|
||||
|
||||
assert.Equal(t, "foo", value.Element(0).Raw())
|
||||
assert.Equal(t, 123.0, value.Element(1).Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, nil, value.Element(2).Raw())
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
it := value.Iter()
|
||||
assert.Equal(t, 2, len(it))
|
||||
assert.Equal(t, "foo", it[0].Raw())
|
||||
assert.Equal(t, 123.0, it[1].Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, "foo", value.First().Raw())
|
||||
assert.Equal(t, 123.0, value.Last().Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestArrayEmpty(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value1 := NewArray(reporter, nil)
|
||||
|
||||
_ = value1
|
||||
value1.chain.assertFailed(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value2 := NewArray(reporter, []interface{}{})
|
||||
|
||||
value2.Empty()
|
||||
value2.chain.assertOK(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value2.NotEmpty()
|
||||
value2.chain.assertFailed(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value3 := NewArray(reporter, []interface{}{""})
|
||||
|
||||
value3.Empty()
|
||||
value3.chain.assertFailed(t)
|
||||
value3.chain.reset()
|
||||
|
||||
value3.NotEmpty()
|
||||
value3.chain.assertOK(t)
|
||||
value3.chain.reset()
|
||||
}
|
||||
|
||||
func TestArrayEqualEmpty(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewArray(reporter, []interface{}{})
|
||||
|
||||
assert.Equal(t, []interface{}{}, value.Raw())
|
||||
|
||||
value.Equal([]interface{}{})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual([]interface{}{})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal([]interface{}{""})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual([]interface{}{""})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestArrayEqualNotEmpty(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewArray(reporter, []interface{}{"foo", "bar"})
|
||||
|
||||
assert.Equal(t, []interface{}{"foo", "bar"}, value.Raw())
|
||||
|
||||
value.Equal([]interface{}{})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual([]interface{}{})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal([]interface{}{"foo"})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual([]interface{}{"foo"})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal([]interface{}{"bar", "foo"})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual([]interface{}{"bar", "foo"})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal([]interface{}{"foo", "bar"})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual([]interface{}{"foo", "bar"})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestArrayEqualTypes(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value1 := NewArray(reporter, []interface{}{"foo", "bar"})
|
||||
value2 := NewArray(reporter, []interface{}{123, 456})
|
||||
value3 := NewArray(reporter, []interface{}{
|
||||
map[string]interface{}{
|
||||
"foo": 123,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": 456,
|
||||
},
|
||||
})
|
||||
|
||||
value1.Equal([]string{"foo", "bar"})
|
||||
value1.chain.assertOK(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value1.Equal([]string{"bar", "foo"})
|
||||
value1.chain.assertFailed(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value1.NotEqual([]string{"foo", "bar"})
|
||||
value1.chain.assertFailed(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value1.NotEqual([]string{"bar", "foo"})
|
||||
value1.chain.assertOK(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value2.Equal([]int{123, 456})
|
||||
value2.chain.assertOK(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value2.Equal([]int{456, 123})
|
||||
value2.chain.assertFailed(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value2.NotEqual([]int{123, 456})
|
||||
value2.chain.assertFailed(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value2.NotEqual([]int{456, 123})
|
||||
value2.chain.assertOK(t)
|
||||
value2.chain.reset()
|
||||
|
||||
type S struct {
|
||||
Foo int `json:"foo"`
|
||||
}
|
||||
|
||||
value3.Equal([]S{{123}, {456}})
|
||||
value3.chain.assertOK(t)
|
||||
value3.chain.reset()
|
||||
|
||||
value3.Equal([]S{{456}, {123}})
|
||||
value3.chain.assertFailed(t)
|
||||
value3.chain.reset()
|
||||
|
||||
value3.NotEqual([]S{{123}, {456}})
|
||||
value3.chain.assertFailed(t)
|
||||
value3.chain.reset()
|
||||
|
||||
value3.NotEqual([]S{{456}, {123}})
|
||||
value3.chain.assertOK(t)
|
||||
value3.chain.reset()
|
||||
}
|
||||
|
||||
func TestArrayElements(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewArray(reporter, []interface{}{123, "foo"})
|
||||
|
||||
value.Elements(123)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Elements("foo")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Elements("foo", 123)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Elements(123, "foo", "foo")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Elements(123, "foo")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestArrayContains(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewArray(reporter, []interface{}{123, "foo"})
|
||||
|
||||
value.Contains(123)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContains(123)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Contains("foo", 123)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContains("foo", 123)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Contains("foo", "foo")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContains("foo", "foo")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Contains(123, "foo", "FOO")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContains(123, "foo", "FOO")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContains("FOO")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Contains([]interface{}{123, "foo"})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContains([]interface{}{123, "foo"})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestArrayContainsOnly(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewArray(reporter, []interface{}{123, "foo"})
|
||||
|
||||
value.ContainsOnly(123)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsOnly("foo")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsOnly(123, "foo", "foo")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsOnly(123, "foo")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsOnly("foo", 123)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestArrayConvertEqual(t *testing.T) {
|
||||
type (
|
||||
myArray []interface{}
|
||||
myInt int
|
||||
)
|
||||
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewArray(reporter, []interface{}{123, 456})
|
||||
|
||||
assert.Equal(t, []interface{}{123.0, 456.0}, value.Raw())
|
||||
|
||||
value.Equal(myArray{myInt(123), 456.0})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(myArray{myInt(123), 456.0})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal([]interface{}{"123", "456"})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual([]interface{}{"123", "456"})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(nil)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(nil)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestArrayConvertElements(t *testing.T) {
|
||||
type (
|
||||
myInt int
|
||||
)
|
||||
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewArray(reporter, []interface{}{123, 456})
|
||||
|
||||
assert.Equal(t, []interface{}{123.0, 456.0}, value.Raw())
|
||||
|
||||
value.Elements(myInt(123), 456.0)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Elements(func() {})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestArrayConvertContains(t *testing.T) {
|
||||
type (
|
||||
myInt int
|
||||
)
|
||||
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewArray(reporter, []interface{}{123, 456})
|
||||
|
||||
assert.Equal(t, []interface{}{123.0, 456.0}, value.Raw())
|
||||
|
||||
value.Contains(myInt(123), 456.0)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContains(myInt(123), 456.0)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsOnly(myInt(123), 456.0)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Contains("123")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContains("123")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsOnly("123.0", "456.0")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Contains(func() {})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContains(func() {})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsOnly(func() {})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// Binder implements networkless http.RoundTripper attached directly to
|
||||
// http.Handler.
|
||||
//
|
||||
// Binder emulates network communication by invoking given http.Handler
|
||||
// directly. It passes httptest.ResponseRecorder as http.ResponseWriter
|
||||
// to the handler, and then constructs http.Response from recorded data.
|
||||
type Binder struct {
|
||||
// HTTP handler invoked for every request.
|
||||
Handler http.Handler
|
||||
// TLS connection state used for https:// requests.
|
||||
TLS *tls.ConnectionState
|
||||
}
|
||||
|
||||
// NewBinder returns a new Binder given a http.Handler.
|
||||
//
|
||||
// Example:
|
||||
// client := &http.Client{
|
||||
// Transport: NewBinder(handler),
|
||||
// }
|
||||
func NewBinder(handler http.Handler) Binder {
|
||||
return Binder{Handler: handler}
|
||||
}
|
||||
|
||||
// RoundTrip implements http.RoundTripper.RoundTrip.
|
||||
func (binder Binder) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if req.Proto == "" {
|
||||
req.Proto = fmt.Sprintf("HTTP/%d.%d", req.ProtoMajor, req.ProtoMinor)
|
||||
}
|
||||
|
||||
if req.Body != nil {
|
||||
if req.ContentLength == -1 {
|
||||
req.TransferEncoding = []string{"chunked"}
|
||||
}
|
||||
} else {
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader(nil))
|
||||
}
|
||||
|
||||
if req.URL != nil && req.URL.Scheme == "https" && binder.TLS != nil {
|
||||
req.TLS = binder.TLS
|
||||
}
|
||||
|
||||
if req.RequestURI == "" {
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
binder.Handler.ServeHTTP(recorder, req)
|
||||
|
||||
resp := http.Response{
|
||||
Request: req,
|
||||
StatusCode: recorder.Code,
|
||||
Status: http.StatusText(recorder.Code),
|
||||
Header: recorder.HeaderMap,
|
||||
}
|
||||
|
||||
if recorder.Flushed {
|
||||
resp.TransferEncoding = []string{"chunked"}
|
||||
}
|
||||
|
||||
if recorder.Body != nil {
|
||||
resp.Body = ioutil.NopCloser(recorder.Body)
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// FastBinder implements networkless http.RoundTripper attached directly
|
||||
// to fasthttp.RequestHandler.
|
||||
//
|
||||
// FastBinder emulates network communication by invoking given fasthttp.RequestHandler
|
||||
// directly. It converts http.Request to fasthttp.Request, invokes handler, and then
|
||||
// converts fasthttp.Response to http.Response.
|
||||
type FastBinder struct {
|
||||
// FastHTTP handler invoked for every request.
|
||||
Handler fasthttp.RequestHandler
|
||||
// TLS connection state used for https:// requests.
|
||||
TLS *tls.ConnectionState
|
||||
}
|
||||
|
||||
// NewFastBinder returns a new FastBinder given a fasthttp.RequestHandler.
|
||||
//
|
||||
// Example:
|
||||
// client := &http.Client{
|
||||
// Transport: NewFastBinder(fasthandler),
|
||||
// }
|
||||
func NewFastBinder(handler fasthttp.RequestHandler) FastBinder {
|
||||
return FastBinder{Handler: handler}
|
||||
}
|
||||
|
||||
// RoundTrip implements http.RoundTripper.RoundTrip.
|
||||
func (binder FastBinder) RoundTrip(stdreq *http.Request) (*http.Response, error) {
|
||||
fastreq := std2fast(stdreq)
|
||||
|
||||
var conn net.Conn
|
||||
if stdreq.URL != nil && stdreq.URL.Scheme == "https" && binder.TLS != nil {
|
||||
conn = connTLS{state: binder.TLS}
|
||||
} else {
|
||||
conn = connNonTLS{}
|
||||
}
|
||||
|
||||
ctx := fasthttp.RequestCtx{}
|
||||
ctx.Init2(conn, fastLogger{}, true)
|
||||
fastreq.CopyTo(&ctx.Request)
|
||||
|
||||
if stdreq.ContentLength >= 0 {
|
||||
ctx.Request.Header.SetContentLength(int(stdreq.ContentLength))
|
||||
} else {
|
||||
ctx.Request.Header.Add("Transfer-Encoding", "chunked")
|
||||
}
|
||||
|
||||
if stdreq.Body != nil {
|
||||
b, err := ioutil.ReadAll(stdreq.Body)
|
||||
if err == nil {
|
||||
ctx.Request.SetBody(b)
|
||||
}
|
||||
}
|
||||
|
||||
binder.Handler(&ctx)
|
||||
|
||||
return fast2std(stdreq, &ctx.Response), nil
|
||||
}
|
||||
|
||||
func std2fast(stdreq *http.Request) *fasthttp.Request {
|
||||
fastreq := &fasthttp.Request{}
|
||||
fastreq.SetRequestURI(stdreq.URL.String())
|
||||
|
||||
fastreq.Header.SetMethod(stdreq.Method)
|
||||
|
||||
for k, a := range stdreq.Header {
|
||||
for n, v := range a {
|
||||
if n == 0 {
|
||||
fastreq.Header.Set(k, v)
|
||||
} else {
|
||||
fastreq.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fastreq
|
||||
}
|
||||
|
||||
func fast2std(stdreq *http.Request, fastresp *fasthttp.Response) *http.Response {
|
||||
status := fastresp.Header.StatusCode()
|
||||
body := fastresp.Body()
|
||||
|
||||
stdresp := &http.Response{
|
||||
Request: stdreq,
|
||||
StatusCode: status,
|
||||
Status: http.StatusText(status),
|
||||
}
|
||||
|
||||
fastresp.Header.VisitAll(func(k, v []byte) {
|
||||
sk := string(k)
|
||||
sv := string(v)
|
||||
if stdresp.Header == nil {
|
||||
stdresp.Header = make(http.Header)
|
||||
}
|
||||
stdresp.Header.Add(sk, sv)
|
||||
})
|
||||
|
||||
if fastresp.Header.ContentLength() == -1 {
|
||||
stdresp.TransferEncoding = []string{"chunked"}
|
||||
}
|
||||
|
||||
if body != nil {
|
||||
stdresp.Body = ioutil.NopCloser(bytes.NewReader(body))
|
||||
} else {
|
||||
stdresp.Body = ioutil.NopCloser(bytes.NewReader(nil))
|
||||
}
|
||||
|
||||
return stdresp
|
||||
}
|
||||
|
||||
type fastLogger struct{}
|
||||
|
||||
func (fastLogger) Printf(format string, args ...interface{}) {
|
||||
_, _ = format, args
|
||||
}
|
||||
|
||||
type connNonTLS struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (connNonTLS) RemoteAddr() net.Addr {
|
||||
return &net.TCPAddr{IP: net.IPv4zero}
|
||||
}
|
||||
|
||||
func (connNonTLS) LocalAddr() net.Addr {
|
||||
return &net.TCPAddr{IP: net.IPv4zero}
|
||||
}
|
||||
|
||||
type connTLS struct {
|
||||
connNonTLS
|
||||
state *tls.ConnectionState
|
||||
}
|
||||
|
||||
func (c connTLS) ConnectionState() tls.ConnectionState {
|
||||
return *c.state
|
||||
}
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type mockHandler struct {
|
||||
t *testing.T
|
||||
https bool
|
||||
http10 bool
|
||||
chunked bool
|
||||
}
|
||||
|
||||
func (c *mockHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
assert.True(c.t, err == nil)
|
||||
|
||||
if c.http10 {
|
||||
assert.Equal(c.t, "HTTP/1.0", req.Proto)
|
||||
assert.Equal(c.t, 1, req.ProtoMajor)
|
||||
assert.Equal(c.t, 0, req.ProtoMinor)
|
||||
} else {
|
||||
assert.Equal(c.t, "HTTP/1.1", req.Proto)
|
||||
assert.Equal(c.t, 1, req.ProtoMajor)
|
||||
assert.Equal(c.t, 1, req.ProtoMinor)
|
||||
}
|
||||
if c.https {
|
||||
assert.Equal(c.t, "https://example.com/path", req.URL.String())
|
||||
} else {
|
||||
assert.Equal(c.t, "http://example.com/path", req.URL.String())
|
||||
}
|
||||
assert.Equal(c.t, "/path", req.URL.RequestURI())
|
||||
assert.Equal(c.t, "/path", req.RequestURI)
|
||||
assert.Equal(c.t, "GET", req.Method)
|
||||
assert.Equal(c.t, "body", string(body))
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err = w.Write([]byte(`{"hello":"world"}`))
|
||||
if c.chunked {
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
assert.True(c.t, err == nil)
|
||||
}
|
||||
|
||||
func TestBinder(t *testing.T) {
|
||||
handler := &mockHandler{
|
||||
t: t,
|
||||
http10: true,
|
||||
chunked: false,
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: NewBinder(handler),
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://example.com/path", strings.NewReader("body"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req.ProtoMajor = 1
|
||||
req.ProtoMinor = 0
|
||||
req.Proto = ""
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
header := http.Header{
|
||||
"Content-Type": {"application/json"},
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, header, resp.Header)
|
||||
assert.Equal(t, `{"hello":"world"}`, string(b))
|
||||
|
||||
assert.Equal(t, []string(nil), resp.TransferEncoding)
|
||||
}
|
||||
|
||||
func TestBinderTLS(t *testing.T) {
|
||||
handler := &mockHandler{t: t}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &Binder{
|
||||
Handler: handler,
|
||||
TLS: nil,
|
||||
},
|
||||
}
|
||||
|
||||
tlsState := &tls.ConnectionState{}
|
||||
|
||||
httpsClient := &http.Client{
|
||||
Transport: &Binder{
|
||||
Handler: handler,
|
||||
TLS: tlsState,
|
||||
},
|
||||
}
|
||||
|
||||
handler.https = false
|
||||
req, _ := http.NewRequest("GET", "http://example.com/path", strings.NewReader("body"))
|
||||
resp, err := httpClient.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Nil(t, req.TLS)
|
||||
|
||||
handler.https = true
|
||||
req, _ = http.NewRequest("GET", "https://example.com/path", strings.NewReader("body"))
|
||||
resp, err = httpClient.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Nil(t, req.TLS)
|
||||
|
||||
handler.https = false
|
||||
req, _ = http.NewRequest("GET", "http://example.com/path", strings.NewReader("body"))
|
||||
resp, err = httpsClient.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Nil(t, req.TLS)
|
||||
|
||||
handler.https = true
|
||||
req, _ = http.NewRequest("GET", "https://example.com/path", strings.NewReader("body"))
|
||||
resp, err = httpsClient.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.NotNil(t, req.TLS)
|
||||
assert.Equal(t, tlsState, req.TLS)
|
||||
}
|
||||
|
||||
func TestBinderChunked(t *testing.T) {
|
||||
handler := &mockHandler{
|
||||
t: t,
|
||||
chunked: true,
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: NewBinder(handler),
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://example.com/path", strings.NewReader("body"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req.ContentLength = -1
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, []string{"chunked"}, req.TransferEncoding)
|
||||
assert.Equal(t, []string{"chunked"}, resp.TransferEncoding)
|
||||
}
|
||||
|
||||
func TestFastBinder(t *testing.T) {
|
||||
handler := func(ctx *fasthttp.RequestCtx) {
|
||||
assert.Equal(t, "POST", string(ctx.Request.Header.Method()))
|
||||
assert.Equal(t, "http://example.com/path", string(ctx.Request.Header.RequestURI()))
|
||||
|
||||
assert.Equal(t, "application/x-www-form-urlencoded",
|
||||
string(ctx.Request.Header.ContentType()))
|
||||
|
||||
headers := map[string][]string{}
|
||||
|
||||
ctx.Request.Header.VisitAll(func(k, v []byte) {
|
||||
headers[string(k)] = append(headers[string(k)], string(v))
|
||||
})
|
||||
|
||||
expected := map[string][]string{
|
||||
"Content-Type": {"application/x-www-form-urlencoded"},
|
||||
"Content-Length": {"7"},
|
||||
"Some-Header": {"foo", "bar"},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, headers)
|
||||
|
||||
assert.Equal(t, "bar", string(ctx.FormValue("foo")))
|
||||
assert.Equal(t, "foo=bar", string(ctx.Request.Body()))
|
||||
|
||||
ctx.Response.Header.Set("Content-Type", "application/json")
|
||||
ctx.Response.SetBody([]byte(`{"hello":"world"}`))
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: NewFastBinder(handler),
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(
|
||||
"POST", "http://example.com/path", strings.NewReader("foo=bar"))
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Add("Some-Header", "foo")
|
||||
req.Header.Add("Some-Header", "bar")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
header := http.Header{
|
||||
"Content-Type": {"application/json"},
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, header, resp.Header)
|
||||
assert.Equal(t, `{"hello":"world"}`, string(b))
|
||||
|
||||
assert.Equal(t, []string(nil), resp.TransferEncoding)
|
||||
}
|
||||
|
||||
func TestFastBinderTLS(t *testing.T) {
|
||||
var isHTTPS, isTLS bool
|
||||
|
||||
tlsState := &tls.ConnectionState{}
|
||||
|
||||
handler := func(ctx *fasthttp.RequestCtx) {
|
||||
isHTTPS = strings.HasPrefix(string(ctx.Request.Header.RequestURI()), "https://")
|
||||
isTLS = ctx.IsTLS()
|
||||
if isTLS {
|
||||
assert.Equal(t, *tlsState, *ctx.TLSConnectionState())
|
||||
}
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &FastBinder{
|
||||
Handler: handler,
|
||||
TLS: nil,
|
||||
},
|
||||
}
|
||||
|
||||
httpsClient := &http.Client{
|
||||
Transport: &FastBinder{
|
||||
Handler: handler,
|
||||
TLS: tlsState,
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", "http://example.com/path", strings.NewReader("body"))
|
||||
resp, err := httpClient.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.False(t, isHTTPS)
|
||||
assert.False(t, isTLS)
|
||||
|
||||
req, _ = http.NewRequest("GET", "https://example.com/path", strings.NewReader("body"))
|
||||
resp, err = httpClient.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.True(t, isHTTPS)
|
||||
assert.False(t, isTLS)
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://example.com/path", strings.NewReader("body"))
|
||||
resp, err = httpsClient.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.False(t, isHTTPS)
|
||||
assert.False(t, isTLS)
|
||||
|
||||
req, _ = http.NewRequest("GET", "https://example.com/path", strings.NewReader("body"))
|
||||
resp, err = httpsClient.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.True(t, isHTTPS)
|
||||
assert.True(t, isTLS)
|
||||
}
|
||||
|
||||
func TestFastBinderChunked(t *testing.T) {
|
||||
handler := func(ctx *fasthttp.RequestCtx) {
|
||||
assert.Equal(t, "POST", string(ctx.Request.Header.Method()))
|
||||
assert.Equal(t, "http://example.com/path", string(ctx.Request.Header.RequestURI()))
|
||||
|
||||
assert.Equal(t, "application/x-www-form-urlencoded",
|
||||
string(ctx.Request.Header.ContentType()))
|
||||
|
||||
headers := map[string][]string{}
|
||||
|
||||
ctx.Request.Header.VisitAll(func(k, v []byte) {
|
||||
headers[string(k)] = append(headers[string(k)], string(v))
|
||||
})
|
||||
|
||||
expected := map[string][]string{
|
||||
"Content-Type": {"application/x-www-form-urlencoded"},
|
||||
"Transfer-Encoding": {"chunked"},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, headers)
|
||||
|
||||
assert.Equal(t, "bar", string(ctx.FormValue("foo")))
|
||||
assert.Equal(t, "foo=bar", string(ctx.Request.Body()))
|
||||
|
||||
ctx.Response.Header.Set("Content-Type", "application/json")
|
||||
ctx.Response.SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||
w.WriteString(`[1, `)
|
||||
w.Flush()
|
||||
w.WriteString(`2]`)
|
||||
})
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: NewFastBinder(handler),
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(
|
||||
"POST", "http://example.com/path", strings.NewReader("foo=bar"))
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
req.ContentLength = -1
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, []string{"chunked"}, resp.TransferEncoding)
|
||||
}
|
||||
|
||||
func TestFastBinderEmptyResponse(t *testing.T) {
|
||||
handler := func(*fasthttp.RequestCtx) {}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: NewFastBinder(handler),
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "http://example.com/path", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.False(t, resp.Body == nil)
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "", string(b))
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package httpexpect
|
||||
|
||||
// Boolean provides methods to inspect attached bool value
|
||||
// (Go representation of JSON boolean).
|
||||
type Boolean struct {
|
||||
chain chain
|
||||
value bool
|
||||
}
|
||||
|
||||
// NewBoolean returns a new Boolean given a reporter used to report
|
||||
// failures and value to be inspected.
|
||||
//
|
||||
// reporter should not be nil.
|
||||
//
|
||||
// Example:
|
||||
// boolean := NewBoolean(t, true)
|
||||
func NewBoolean(reporter Reporter, value bool) *Boolean {
|
||||
return &Boolean{makeChain(reporter), value}
|
||||
}
|
||||
|
||||
// Raw returns underlying value attached to Boolean.
|
||||
// This is the value originally passed to NewBoolean.
|
||||
//
|
||||
// Example:
|
||||
// boolean := NewBoolean(t, true)
|
||||
// assert.Equal(t, true, boolean.Raw())
|
||||
func (b *Boolean) Raw() bool {
|
||||
return b.value
|
||||
}
|
||||
|
||||
// Path is similar to Value.Path.
|
||||
func (b *Boolean) Path(path string) *Value {
|
||||
return getPath(&b.chain, b.value, path)
|
||||
}
|
||||
|
||||
// Schema is similar to Value.Schema.
|
||||
func (b *Boolean) Schema(schema interface{}) *Boolean {
|
||||
checkSchema(&b.chain, b.value, schema)
|
||||
return b
|
||||
}
|
||||
|
||||
// Equal succeeds if boolean is equal to given value.
|
||||
//
|
||||
// Example:
|
||||
// boolean := NewBoolean(t, true)
|
||||
// boolean.Equal(true)
|
||||
func (b *Boolean) Equal(value bool) *Boolean {
|
||||
if !(b.value == value) {
|
||||
b.chain.fail("expected boolean == %v, but got %v", value, b.value)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// NotEqual succeeds if boolean is not equal to given value.
|
||||
//
|
||||
// Example:
|
||||
// boolean := NewBoolean(t, true)
|
||||
// boolean.NotEqual(false)
|
||||
func (b *Boolean) NotEqual(value bool) *Boolean {
|
||||
if !(b.value != value) {
|
||||
b.chain.fail("expected boolean != %v, but got %v", value, b.value)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// True succeeds if boolean is true.
|
||||
//
|
||||
// Example:
|
||||
// boolean := NewBoolean(t, true)
|
||||
// boolean.True()
|
||||
func (b *Boolean) True() *Boolean {
|
||||
return b.Equal(true)
|
||||
}
|
||||
|
||||
// False succeeds if boolean is false.
|
||||
//
|
||||
// Example:
|
||||
// boolean := NewBoolean(t, false)
|
||||
// boolean.False()
|
||||
func (b *Boolean) False() *Boolean {
|
||||
return b.Equal(false)
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBooleanFailed(t *testing.T) {
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
chain.fail("fail")
|
||||
|
||||
value := &Boolean{chain, false}
|
||||
|
||||
value.chain.assertFailed(t)
|
||||
|
||||
value.Path("$").chain.assertFailed(t)
|
||||
value.Schema("")
|
||||
|
||||
value.Equal(false)
|
||||
value.NotEqual(false)
|
||||
value.True()
|
||||
value.False()
|
||||
}
|
||||
|
||||
func TestBooleanGetters(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewBoolean(reporter, true)
|
||||
|
||||
assert.Equal(t, true, value.Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, true, value.Path("$").Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Schema(`{"type": "boolean"}`)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Schema(`{"type": "object"}`)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestBooleanTrue(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewBoolean(reporter, true)
|
||||
|
||||
assert.Equal(t, true, value.Raw())
|
||||
|
||||
value.Equal(true)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(false)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(false)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(true)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.True()
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.False()
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestBooleanFalse(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewBoolean(reporter, false)
|
||||
|
||||
assert.Equal(t, false, value.Raw())
|
||||
|
||||
value.Equal(true)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(false)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(false)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(true)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.True()
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.False()
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package httpexpect
|
||||
|
||||
type chain struct {
|
||||
reporter Reporter
|
||||
failbit bool
|
||||
}
|
||||
|
||||
func makeChain(reporter Reporter) chain {
|
||||
return chain{reporter, false}
|
||||
}
|
||||
|
||||
func (c *chain) failed() bool {
|
||||
return c.failbit
|
||||
}
|
||||
|
||||
func (c *chain) fail(message string, args ...interface{}) {
|
||||
if c.failbit {
|
||||
return
|
||||
}
|
||||
c.failbit = true
|
||||
c.reporter.Errorf(message, args...)
|
||||
}
|
||||
|
||||
func (c *chain) reset() {
|
||||
c.failbit = false
|
||||
}
|
||||
|
||||
func (c *chain) assertFailed(r Reporter) {
|
||||
if !c.failbit {
|
||||
r.Errorf("expected chain is failed, but it's ok")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *chain) assertOK(r Reporter) {
|
||||
if c.failbit {
|
||||
r.Errorf("expected chain is ok, but it's failed")
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestChainFail(t *testing.T) {
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
assert.False(t, chain.failed())
|
||||
|
||||
chain.fail("fail")
|
||||
assert.True(t, chain.failed())
|
||||
|
||||
chain.fail("fail")
|
||||
assert.True(t, chain.failed())
|
||||
}
|
||||
|
||||
func TestChainCopy(t *testing.T) {
|
||||
chain1 := makeChain(newMockReporter(t))
|
||||
chain2 := chain1
|
||||
|
||||
assert.False(t, chain1.failed())
|
||||
assert.False(t, chain2.failed())
|
||||
|
||||
chain1.fail("fail")
|
||||
|
||||
assert.True(t, chain1.failed())
|
||||
assert.False(t, chain2.failed())
|
||||
|
||||
chain2.fail("fail")
|
||||
|
||||
assert.True(t, chain1.failed())
|
||||
assert.True(t, chain2.failed())
|
||||
}
|
||||
|
||||
func TestChainReport(t *testing.T) {
|
||||
r0 := newMockReporter(t)
|
||||
|
||||
chain := makeChain(r0)
|
||||
|
||||
r1 := newMockReporter(t)
|
||||
|
||||
chain.assertOK(r1)
|
||||
assert.False(t, r1.reported)
|
||||
|
||||
chain.assertFailed(r1)
|
||||
assert.True(t, r1.reported)
|
||||
|
||||
assert.False(t, chain.failed())
|
||||
|
||||
chain.fail("fail")
|
||||
assert.True(t, r0.reported)
|
||||
|
||||
r2 := newMockReporter(t)
|
||||
|
||||
chain.assertFailed(r2)
|
||||
assert.False(t, r2.reported)
|
||||
|
||||
chain.assertOK(r2)
|
||||
assert.True(t, r2.reported)
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cookie provides methods to inspect attached http.Cookie value.
|
||||
type Cookie struct {
|
||||
chain chain
|
||||
value *http.Cookie
|
||||
}
|
||||
|
||||
// NewCookie returns a new Cookie object given a reporter used to report
|
||||
// failures and cookie value to be inspected.
|
||||
//
|
||||
// reporter and value should not be nil.
|
||||
//
|
||||
// Example:
|
||||
// cookie := NewCookie(reporter, &http.Cookie{...})
|
||||
// cookie.Domain().Equal("example.com")
|
||||
// cookie.Path().Equal("/")
|
||||
// cookie.Expires().InRange(time.Now(), time.Now().Add(time.Hour * 24))
|
||||
func NewCookie(reporter Reporter, value *http.Cookie) *Cookie {
|
||||
chain := makeChain(reporter)
|
||||
if value == nil {
|
||||
chain.fail("expected non-nil cookie")
|
||||
}
|
||||
return &Cookie{chain, value}
|
||||
}
|
||||
|
||||
// Raw returns underlying http.Cookie value attached to Cookie.
|
||||
// This is the value originally passed to NewCookie.
|
||||
//
|
||||
// Example:
|
||||
// cookie := NewCookie(t, c)
|
||||
// assert.Equal(t, c, cookie.Raw())
|
||||
func (c *Cookie) Raw() *http.Cookie {
|
||||
return c.value
|
||||
}
|
||||
|
||||
// Name returns a new String object that may be used to inspect
|
||||
// cookie name.
|
||||
//
|
||||
// Example:
|
||||
// cookie := NewCookie(t, &http.Cookie{...})
|
||||
// cookie.Name().Equal("session")
|
||||
func (c *Cookie) Name() *String {
|
||||
if c.chain.failed() {
|
||||
return &String{c.chain, ""}
|
||||
}
|
||||
return &String{c.chain, c.value.Name}
|
||||
}
|
||||
|
||||
// Value returns a new String object that may be used to inspect
|
||||
// cookie value.
|
||||
//
|
||||
// Example:
|
||||
// cookie := NewCookie(t, &http.Cookie{...})
|
||||
// cookie.Value().Equal("gH6z7Y")
|
||||
func (c *Cookie) Value() *String {
|
||||
if c.chain.failed() {
|
||||
return &String{c.chain, ""}
|
||||
}
|
||||
return &String{c.chain, c.value.Value}
|
||||
}
|
||||
|
||||
// Domain returns a new String object that may be used to inspect
|
||||
// cookie domain.
|
||||
//
|
||||
// Example:
|
||||
// cookie := NewCookie(t, &http.Cookie{...})
|
||||
// cookie.Domain().Equal("example.com")
|
||||
func (c *Cookie) Domain() *String {
|
||||
if c.chain.failed() {
|
||||
return &String{c.chain, ""}
|
||||
}
|
||||
return &String{c.chain, c.value.Domain}
|
||||
}
|
||||
|
||||
// Path returns a new String object that may be used to inspect
|
||||
// cookie path.
|
||||
//
|
||||
// Example:
|
||||
// cookie := NewCookie(t, &http.Cookie{...})
|
||||
// cookie.Path().Equal("/foo")
|
||||
func (c *Cookie) Path() *String {
|
||||
if c.chain.failed() {
|
||||
return &String{c.chain, ""}
|
||||
}
|
||||
return &String{c.chain, c.value.Path}
|
||||
}
|
||||
|
||||
// Expires returns a new DateTime object that may be used to inspect
|
||||
// cookie expiration date.
|
||||
//
|
||||
// Example:
|
||||
// cookie := NewCookie(t, &http.Cookie{...})
|
||||
// cookie.Expires().InRange(time.Now(), time.Now().Add(time.Hour * 24))
|
||||
func (c *Cookie) Expires() *DateTime {
|
||||
if c.chain.failed() {
|
||||
return &DateTime{c.chain, time.Unix(0, 0)}
|
||||
}
|
||||
return &DateTime{c.chain, c.value.Expires}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCookieFailed(t *testing.T) {
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
chain.fail("fail")
|
||||
|
||||
value := &Cookie{chain, nil}
|
||||
|
||||
assert.True(t, value.Raw() == nil)
|
||||
assert.True(t, value.Name() != nil)
|
||||
assert.True(t, value.Value() != nil)
|
||||
assert.True(t, value.Domain() != nil)
|
||||
assert.True(t, value.Path() != nil)
|
||||
assert.True(t, value.Expires() != nil)
|
||||
}
|
||||
|
||||
func TestCookieGetters(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
NewCookie(reporter, nil).chain.assertFailed(t)
|
||||
|
||||
value := NewCookie(reporter, &http.Cookie{
|
||||
Name: "name",
|
||||
Value: "value",
|
||||
Domain: "example.com",
|
||||
Path: "/path",
|
||||
Expires: time.Unix(1234, 0),
|
||||
})
|
||||
|
||||
value.chain.assertOK(t)
|
||||
|
||||
value.Name().chain.assertOK(t)
|
||||
value.Value().chain.assertOK(t)
|
||||
value.Domain().chain.assertOK(t)
|
||||
value.Path().chain.assertOK(t)
|
||||
value.Expires().chain.assertOK(t)
|
||||
|
||||
assert.Equal(t, "name", value.Name().Raw())
|
||||
assert.Equal(t, "value", value.Value().Raw())
|
||||
assert.Equal(t, "example.com", value.Domain().Raw())
|
||||
assert.Equal(t, "/path", value.Path().Raw())
|
||||
assert.True(t, time.Unix(1234, 0).Equal(value.Expires().Raw()))
|
||||
|
||||
value.chain.assertOK(t)
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// DateTime provides methods to inspect attached time.Time value.
|
||||
type DateTime struct {
|
||||
chain chain
|
||||
value time.Time
|
||||
}
|
||||
|
||||
// NewDateTime returns a new DateTime object given a reporter used to report
|
||||
// failures and time.Time value to be inspected.
|
||||
//
|
||||
// reporter should not be nil.
|
||||
//
|
||||
// Example:
|
||||
// dt := NewDateTime(reporter, time.Now())
|
||||
// dt.Le(time.Now())
|
||||
//
|
||||
// time.Sleep(time.Second)
|
||||
// dt.Lt(time.Now())
|
||||
func NewDateTime(reporter Reporter, value time.Time) *DateTime {
|
||||
return &DateTime{makeChain(reporter), value}
|
||||
}
|
||||
|
||||
// Raw returns underlying time.Time value attached to DateTime.
|
||||
// This is the value originally passed to NewDateTime.
|
||||
//
|
||||
// Example:
|
||||
// dt := NewDateTime(t, timestamp)
|
||||
// assert.Equal(t, timestamp, dt.Raw())
|
||||
func (dt *DateTime) Raw() time.Time {
|
||||
return dt.value
|
||||
}
|
||||
|
||||
// Equal succeeds if DateTime is equal to given value.
|
||||
//
|
||||
// Example:
|
||||
// dt := NewDateTime(t, time.Unix(0, 1))
|
||||
// dt.Equal(time.Unix(0, 1))
|
||||
func (dt *DateTime) Equal(value time.Time) *DateTime {
|
||||
if !dt.value.Equal(value) {
|
||||
dt.chain.fail("\nexpected datetime equal to:\n %s\n\nbut got:\n %s",
|
||||
value, dt.value)
|
||||
}
|
||||
return dt
|
||||
}
|
||||
|
||||
// NotEqual succeeds if DateTime is not equal to given value.
|
||||
//
|
||||
// Example:
|
||||
// dt := NewDateTime(t, time.Unix(0, 1))
|
||||
// dt.NotEqual(time.Unix(0, 2))
|
||||
func (dt *DateTime) NotEqual(value time.Time) *DateTime {
|
||||
if dt.value.Equal(value) {
|
||||
dt.chain.fail("\nexpected datetime not equal to:\n %s", value)
|
||||
}
|
||||
return dt
|
||||
}
|
||||
|
||||
// Gt succeeds if DateTime is greater than given value.
|
||||
//
|
||||
// Example:
|
||||
// dt := NewDateTime(t, time.Unix(0, 2))
|
||||
// dt.Gt(time.Unix(0, 1))
|
||||
func (dt *DateTime) Gt(value time.Time) *DateTime {
|
||||
if !dt.value.After(value) {
|
||||
dt.chain.fail("\nexpected datetime > then:\n %s\n\nbut got:\n %s",
|
||||
value, dt.value)
|
||||
}
|
||||
return dt
|
||||
}
|
||||
|
||||
// Ge succeeds if DateTime is greater than or equal to given value.
|
||||
//
|
||||
// Example:
|
||||
// dt := NewDateTime(t, time.Unix(0, 2))
|
||||
// dt.Ge(time.Unix(0, 1))
|
||||
func (dt *DateTime) Ge(value time.Time) *DateTime {
|
||||
if !(dt.value.After(value) || dt.value.Equal(value)) {
|
||||
dt.chain.fail("\nexpected datetime >= then:\n %s\n\nbut got:\n %s",
|
||||
value, dt.value)
|
||||
}
|
||||
return dt
|
||||
}
|
||||
|
||||
// Lt succeeds if DateTime is lesser than given value.
|
||||
//
|
||||
// Example:
|
||||
// dt := NewDateTime(t, time.Unix(0, 1))
|
||||
// dt.Lt(time.Unix(0, 2))
|
||||
func (dt *DateTime) Lt(value time.Time) *DateTime {
|
||||
if !dt.value.Before(value) {
|
||||
dt.chain.fail("\nexpected datetime < then:\n %s\n\nbut got:\n %s",
|
||||
value, dt.value)
|
||||
}
|
||||
return dt
|
||||
}
|
||||
|
||||
// Le succeeds if DateTime is lesser than or equal to given value.
|
||||
//
|
||||
// Example:
|
||||
// dt := NewDateTime(t, time.Unix(0, 1))
|
||||
// dt.Le(time.Unix(0, 2))
|
||||
func (dt *DateTime) Le(value time.Time) *DateTime {
|
||||
if !(dt.value.Before(value) || dt.value.Equal(value)) {
|
||||
dt.chain.fail("\nexpected datetime <= then:\n %s\n\nbut got:\n %s",
|
||||
value, dt.value)
|
||||
}
|
||||
return dt
|
||||
}
|
||||
|
||||
// InRange succeeds if DateTime is in given range [min; max].
|
||||
//
|
||||
// Example:
|
||||
// dt := NewDateTime(t, time.Unix(0, 2))
|
||||
// dt.InRange(time.Unix(0, 1), time.Unix(0, 3))
|
||||
// dt.InRange(time.Unix(0, 2), time.Unix(0, 2))
|
||||
func (dt *DateTime) InRange(min, max time.Time) *DateTime {
|
||||
if !((dt.value.After(min) || dt.value.Equal(min)) &&
|
||||
(dt.value.Before(max) || dt.value.Equal(max))) {
|
||||
dt.chain.fail(
|
||||
"\nexpected datetime in range:\n min: %s\n max: %s\n\nbut got: %s",
|
||||
min, max, dt.value)
|
||||
}
|
||||
return dt
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDateTimeFailed(t *testing.T) {
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
chain.fail("fail")
|
||||
|
||||
ts := time.Unix(0, 0)
|
||||
|
||||
value := &DateTime{chain, ts}
|
||||
|
||||
value.chain.assertFailed(t)
|
||||
|
||||
value.Equal(ts)
|
||||
value.NotEqual(ts)
|
||||
value.Gt(ts)
|
||||
value.Ge(ts)
|
||||
value.Lt(ts)
|
||||
value.Le(ts)
|
||||
value.InRange(ts, ts)
|
||||
}
|
||||
|
||||
func TestDateTimeEqual(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewDateTime(reporter, time.Unix(0, 1234))
|
||||
|
||||
assert.True(t, time.Unix(0, 1234).Equal(value.Raw()))
|
||||
|
||||
value.Equal(time.Unix(0, 1234))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(time.Unix(0, 4321))
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(time.Unix(0, 4321))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(time.Unix(0, 1234))
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestDateTimeGreater(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewDateTime(reporter, time.Unix(0, 1234))
|
||||
|
||||
value.Gt(time.Unix(0, 1234-1))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Gt(time.Unix(0, 1234))
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Ge(time.Unix(0, 1234-1))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Ge(time.Unix(0, 1234))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Ge(time.Unix(0, 1234+1))
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestDateTimeLesser(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewDateTime(reporter, time.Unix(0, 1234))
|
||||
|
||||
value.Lt(time.Unix(0, 1234+1))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Lt(time.Unix(0, 1234))
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Le(time.Unix(0, 1234+1))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Le(time.Unix(0, 1234))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Le(time.Unix(0, 1234-1))
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestDateTimeInRange(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewDateTime(reporter, time.Unix(0, 1234))
|
||||
|
||||
value.InRange(time.Unix(0, 1234), time.Unix(0, 1234))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(time.Unix(0, 1234-1), time.Unix(0, 1234))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(time.Unix(0, 1234), time.Unix(0, 1234+1))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(time.Unix(0, 1234+1), time.Unix(0, 1234+2))
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(time.Unix(0, 1234-2), time.Unix(0, 1234-1))
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(time.Unix(0, 1234+1), time.Unix(0, 1234-1))
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
// Package httpexpect helps with end-to-end HTTP and REST API testing.
|
||||
//
|
||||
// Usage examples
|
||||
//
|
||||
// See example directory:
|
||||
// - https://godoc.org/github.com/gavv/httpexpect/_examples
|
||||
// - https://github.com/gavv/httpexpect/tree/master/_examples
|
||||
//
|
||||
// Communication mode
|
||||
//
|
||||
// There are two common ways to test API with httpexpect:
|
||||
// - start HTTP server and instruct httpexpect to use HTTP client for communication
|
||||
// - don't start server and instruct httpexpect to invoke http handler directly
|
||||
//
|
||||
// The second approach works only if the server is a Go module and its handler can
|
||||
// be imported in tests.
|
||||
//
|
||||
// Concrete behaviour is determined by Client implementation passed to Config struct.
|
||||
// If you're using http.Client, set its Transport field (http.RoundTriper) to one of
|
||||
// the following:
|
||||
// 1. default (nil) - use HTTP transport from net/http (you should start server)
|
||||
// 2. httpexpect.Binder - invoke given http.Handler directly
|
||||
// 3. httpexpect.FastBinder - invoke given fasthttp.RequestHandler directly
|
||||
//
|
||||
// Note that http handler can be usually obtained from http framework you're using.
|
||||
// E.g., echo framework provides either http.Handler or fasthttp.RequestHandler.
|
||||
//
|
||||
// You can also provide your own implementation of RequestFactory (creates http.Request),
|
||||
// or Client (gets http.Request and returns http.Response).
|
||||
//
|
||||
// If you're starting server from tests, it's very handy to use net/http/httptest.
|
||||
//
|
||||
// Value equality
|
||||
//
|
||||
// Whenever values are checked for equality in httpexpect, they are converted
|
||||
// to "canonical form":
|
||||
// - structs are converted to map[string]interface{}
|
||||
// - type aliases are removed
|
||||
// - numeric types are converted to float64
|
||||
// - non-nil interfaces pointing to nil slices and maps are replaced with
|
||||
// nil interfaces
|
||||
//
|
||||
// This is equivalent to subsequently json.Marshal() and json.Unmarshal() the value
|
||||
// and currently is implemented so.
|
||||
//
|
||||
// Failure handling
|
||||
//
|
||||
// When some check fails, failure is reported. If non-fatal failures are used
|
||||
// (see Reporter interface), execution is continued and instance that was checked
|
||||
// is marked as failed.
|
||||
//
|
||||
// If specific instance is marked as failed, all subsequent checks are ignored
|
||||
// for this instance and for any child instances retrieved after failure.
|
||||
//
|
||||
// Example:
|
||||
// array := NewArray(NewAssertReporter(t), []interface{}{"foo", 123})
|
||||
//
|
||||
// e0 := array.Element(0) // success
|
||||
// e1 := array.Element(1) // success
|
||||
//
|
||||
// s0 := e0.String() // success
|
||||
// s1 := e1.String() // failure; e1 and s1 are marked as failed, e0 and s0 are not
|
||||
//
|
||||
// s0.Equal("foo") // success
|
||||
// s1.Equal("bar") // this check is ignored because s1 is marked as failed
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// Expect is a toplevel object that contains user Config and allows
|
||||
// to construct Request objects.
|
||||
type Expect struct {
|
||||
config Config
|
||||
builders []func(*Request)
|
||||
}
|
||||
|
||||
// Config contains various settings.
|
||||
type Config struct {
|
||||
// BaseURL is a URL to prepended to all request. My be empty. If
|
||||
// non-empty, trailing slash is allowed but not required and is
|
||||
// appended automatically.
|
||||
BaseURL string
|
||||
|
||||
// RequestFactory is used to pass in a custom *http.Request generation func.
|
||||
// May be nil.
|
||||
//
|
||||
// You can use DefaultRequestFactory, or provide custom implementation.
|
||||
// Useful for Google App Engine testing for example.
|
||||
RequestFactory RequestFactory
|
||||
|
||||
// Client is used to send http.Request and receive http.Response.
|
||||
// Should not be nil.
|
||||
//
|
||||
// You can use http.DefaultClient or http.Client, or provide
|
||||
// custom implementation.
|
||||
Client Client
|
||||
|
||||
// Reporter is used to report failures.
|
||||
// Should not be nil.
|
||||
//
|
||||
// You can use AssertReporter, RequireReporter (they use testify),
|
||||
// or testing.TB, or provide custom implementation.
|
||||
Reporter Reporter
|
||||
|
||||
// Printers are used to print requests and responses.
|
||||
// May be nil.
|
||||
//
|
||||
// You can use CompactPrinter, DebugPrinter, CurlPrinter, or provide
|
||||
// custom implementation.
|
||||
//
|
||||
// You can also use builtin printers with alternative Logger if
|
||||
// you're happy with their format, but want to send logs somewhere
|
||||
// else instead of testing.TB.
|
||||
Printers []Printer
|
||||
}
|
||||
|
||||
// RequestFactory is used to create all http.Request objects.
|
||||
// aetest.Instance from the Google App Engine implements this interface.
|
||||
type RequestFactory interface {
|
||||
NewRequest(method, urlStr string, body io.Reader) (*http.Request, error)
|
||||
}
|
||||
|
||||
// Client is used to send http.Request and receive http.Response.
|
||||
// http.Client, Binder, and FastBinder implement this interface.
|
||||
type Client interface {
|
||||
// Do sends request and returns response.
|
||||
Do(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// Printer is used to print requests and responses.
|
||||
// CompactPrinter, DebugPrinter, and CurlPrinter implement this interface.
|
||||
type Printer interface {
|
||||
// Request is called before request is sent.
|
||||
Request(*http.Request)
|
||||
|
||||
// Response is called after response is received.
|
||||
Response(*http.Response, time.Duration)
|
||||
}
|
||||
|
||||
// Logger is used as output backend for Printer.
|
||||
// testing.TB implements this interface.
|
||||
type Logger interface {
|
||||
// Logf writes message to log.
|
||||
Logf(fmt string, args ...interface{})
|
||||
}
|
||||
|
||||
// Reporter is used to report failures.
|
||||
// testing.TB, AssertReporter, and RequireReporter implement this interface.
|
||||
type Reporter interface {
|
||||
// Errorf reports failure.
|
||||
// Allowed to return normally or terminate test using t.FailNow().
|
||||
Errorf(message string, args ...interface{})
|
||||
}
|
||||
|
||||
// LoggerReporter combines Logger and Reporter interfaces.
|
||||
type LoggerReporter interface {
|
||||
Logger
|
||||
Reporter
|
||||
}
|
||||
|
||||
// DefaultRequestFactory is the default RequestFactory implementation which just
|
||||
// calls http.NewRequest.
|
||||
type DefaultRequestFactory struct{}
|
||||
|
||||
// NewRequest implements RequestFactory.NewRequest.
|
||||
func (DefaultRequestFactory) NewRequest(
|
||||
method, urlStr string, body io.Reader) (*http.Request, error) {
|
||||
return http.NewRequest(method, urlStr, body)
|
||||
}
|
||||
|
||||
// New returns a new Expect object.
|
||||
//
|
||||
// baseURL specifies URL to prepended to all request. My be empty. If non-empty,
|
||||
// trailing slash is allowed but not required and is appended automatically.
|
||||
//
|
||||
// New is a shorthand for WithConfig. It uses:
|
||||
// - CompactPrinter as Printer, with testing.TB as Logger
|
||||
// - AssertReporter as Reporter
|
||||
// - DefaultRequestFactory as RequestFactory
|
||||
//
|
||||
// Client is set to a default client with a non-nil Jar:
|
||||
// &http.Client{
|
||||
// Jar: httpexpect.NewJar(),
|
||||
// }
|
||||
//
|
||||
// Example:
|
||||
// func TestSomething(t *testing.T) {
|
||||
// e := httpexpect.New(t, "http://example.com/")
|
||||
//
|
||||
// e.GET("/path").
|
||||
// Expect().
|
||||
// Status(http.StatusOK)
|
||||
// }
|
||||
func New(t LoggerReporter, baseURL string) *Expect {
|
||||
return WithConfig(Config{
|
||||
BaseURL: baseURL,
|
||||
Reporter: NewAssertReporter(t),
|
||||
Printers: []Printer{
|
||||
NewCompactPrinter(t),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// WithConfig returns a new Expect object with given config.
|
||||
//
|
||||
// Reporter should not be nil.
|
||||
//
|
||||
// If RequestFactory is nil, it's set to a DefaultRequestFactory instance.
|
||||
//
|
||||
// If Client is nil, it's set to a default client with a non-nil Jar:
|
||||
// &http.Client{
|
||||
// Jar: httpexpect.NewJar(),
|
||||
// }
|
||||
//
|
||||
// Example:
|
||||
// func TestSomething(t *testing.T) {
|
||||
// e := httpexpect.WithConfig(httpexpect.Config{
|
||||
// BaseURL: "http://example.com/",
|
||||
// Client: &http.Client{
|
||||
// Transport: httpexpect.NewBinder(myHandler()),
|
||||
// Jar: httpexpect.NewJar(),
|
||||
// },
|
||||
// Reporter: httpexpect.NewAssertReporter(t),
|
||||
// Printers: []httpexpect.Printer{
|
||||
// httpexpect.NewCurlPrinter(t),
|
||||
// httpexpect.NewDebugPrinter(t, true)
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// e.GET("/path").
|
||||
// Expect().
|
||||
// Status(http.StatusOK)
|
||||
// }
|
||||
func WithConfig(config Config) *Expect {
|
||||
if config.Reporter == nil {
|
||||
panic("config.Reporter is nil")
|
||||
}
|
||||
if config.RequestFactory == nil {
|
||||
config.RequestFactory = DefaultRequestFactory{}
|
||||
}
|
||||
if config.Client == nil {
|
||||
config.Client = &http.Client{
|
||||
Jar: NewJar(),
|
||||
}
|
||||
}
|
||||
return &Expect{
|
||||
config: config,
|
||||
builders: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// NewJar returns a new http.CookieJar.
|
||||
//
|
||||
// Returned jar is implemented in net/http/cookiejar. PublicSuffixList is
|
||||
// implemented in golang.org/x/net/publicsuffix.
|
||||
//
|
||||
// Note that this jar ignores cookies when request url is empty.
|
||||
func NewJar() http.CookieJar {
|
||||
jar, err := cookiejar.New(&cookiejar.Options{
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return jar
|
||||
}
|
||||
|
||||
// Builder returns a copy of Expect instance with given builder attached to it.
|
||||
// Returned copy contains all previously attached builders plus a new one.
|
||||
// Builders are invoked from Request method, after constructing every new request.
|
||||
//
|
||||
// Example:
|
||||
// e := httpexpect.New(t, "http://example.com")
|
||||
//
|
||||
// token := e.POST("/login").WithForm(Login{"ford", "betelgeuse7"}).
|
||||
// Expect().
|
||||
// Status(http.StatusOK).JSON().Object().Value("token").String().Raw()
|
||||
//
|
||||
// auth := e.Builder(func (req *httpexpect.Request) {
|
||||
// req.WithHeader("Authorization", "Bearer "+token)
|
||||
// })
|
||||
//
|
||||
// auth.GET("/restricted").
|
||||
// Expect().
|
||||
// Status(http.StatusOK)
|
||||
func (e *Expect) Builder(builder func(*Request)) *Expect {
|
||||
ret := *e
|
||||
ret.builders = append(e.builders, builder)
|
||||
return &ret
|
||||
}
|
||||
|
||||
// Request returns a new Request object.
|
||||
// Arguments a similar to NewRequest.
|
||||
// After creating request, all builders attached to Expect object are invoked.
|
||||
// See Builder.
|
||||
func (e *Expect) Request(method, path string, pathargs ...interface{}) *Request {
|
||||
req := NewRequest(e.config, method, path, pathargs...)
|
||||
|
||||
for _, builder := range e.builders {
|
||||
builder(req)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
// OPTIONS is a shorthand for e.Request("OPTIONS", path, pathargs...).
|
||||
func (e *Expect) OPTIONS(path string, pathargs ...interface{}) *Request {
|
||||
return e.Request("OPTIONS", path, pathargs...)
|
||||
}
|
||||
|
||||
// HEAD is a shorthand for e.Request("HEAD", path, pathargs...).
|
||||
func (e *Expect) HEAD(path string, pathargs ...interface{}) *Request {
|
||||
return e.Request("HEAD", path, pathargs...)
|
||||
}
|
||||
|
||||
// GET is a shorthand for e.Request("GET", path, pathargs...).
|
||||
func (e *Expect) GET(path string, pathargs ...interface{}) *Request {
|
||||
return e.Request("GET", path, pathargs...)
|
||||
}
|
||||
|
||||
// POST is a shorthand for e.Request("POST", path, pathargs...).
|
||||
func (e *Expect) POST(path string, pathargs ...interface{}) *Request {
|
||||
return e.Request("POST", path, pathargs...)
|
||||
}
|
||||
|
||||
// PUT is a shorthand for e.Request("PUT", path, pathargs...).
|
||||
func (e *Expect) PUT(path string, pathargs ...interface{}) *Request {
|
||||
return e.Request("PUT", path, pathargs...)
|
||||
}
|
||||
|
||||
// PATCH is a shorthand for e.Request("PATCH", path, pathargs...).
|
||||
func (e *Expect) PATCH(path string, pathargs ...interface{}) *Request {
|
||||
return e.Request("PATCH", path, pathargs...)
|
||||
}
|
||||
|
||||
// DELETE is a shorthand for e.Request("DELETE", path, pathargs...).
|
||||
func (e *Expect) DELETE(path string, pathargs ...interface{}) *Request {
|
||||
return e.Request("DELETE", path, pathargs...)
|
||||
}
|
||||
|
||||
// Value is a shorthand for NewValue(e.config.Reporter, value).
|
||||
func (e *Expect) Value(value interface{}) *Value {
|
||||
return NewValue(e.config.Reporter, value)
|
||||
}
|
||||
|
||||
// Object is a shorthand for NewObject(e.config.Reporter, value).
|
||||
func (e *Expect) Object(value map[string]interface{}) *Object {
|
||||
return NewObject(e.config.Reporter, value)
|
||||
}
|
||||
|
||||
// Array is a shorthand for NewArray(e.config.Reporter, value).
|
||||
func (e *Expect) Array(value []interface{}) *Array {
|
||||
return NewArray(e.config.Reporter, value)
|
||||
}
|
||||
|
||||
// String is a shorthand for NewString(e.config.Reporter, value).
|
||||
func (e *Expect) String(value string) *String {
|
||||
return NewString(e.config.Reporter, value)
|
||||
}
|
||||
|
||||
// Number is a shorthand for NewNumber(e.config.Reporter, value).
|
||||
func (e *Expect) Number(value float64) *Number {
|
||||
return NewNumber(e.config.Reporter, value)
|
||||
}
|
||||
|
||||
// Boolean is a shorthand for NewBoolean(e.config.Reporter, value).
|
||||
func (e *Expect) Boolean(value bool) *Boolean {
|
||||
return NewBoolean(e.config.Reporter, value)
|
||||
}
|
||||
+866
@@ -0,0 +1,866 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/valyala/fasthttp"
|
||||
"github.com/valyala/fasthttp/fasthttpadaptor"
|
||||
)
|
||||
|
||||
func TestExpectMethods(t *testing.T) {
|
||||
client := &mockClient{}
|
||||
|
||||
reporter := NewAssertReporter(t)
|
||||
|
||||
config := Config{
|
||||
BaseURL: "http://example.com",
|
||||
Client: client,
|
||||
Reporter: reporter,
|
||||
}
|
||||
|
||||
var reqs [8]*Request
|
||||
|
||||
e := WithConfig(config)
|
||||
|
||||
reqs[0] = e.Request("METHOD", "/url")
|
||||
reqs[1] = e.OPTIONS("/url")
|
||||
reqs[2] = e.HEAD("/url")
|
||||
reqs[3] = e.GET("/url")
|
||||
reqs[4] = e.POST("/url")
|
||||
reqs[5] = e.PUT("/url")
|
||||
reqs[6] = e.PATCH("/url")
|
||||
reqs[7] = e.DELETE("/url")
|
||||
|
||||
assert.Equal(t, "METHOD", reqs[0].http.Method)
|
||||
assert.Equal(t, "OPTIONS", reqs[1].http.Method)
|
||||
assert.Equal(t, "HEAD", reqs[2].http.Method)
|
||||
assert.Equal(t, "GET", reqs[3].http.Method)
|
||||
assert.Equal(t, "POST", reqs[4].http.Method)
|
||||
assert.Equal(t, "PUT", reqs[5].http.Method)
|
||||
assert.Equal(t, "PATCH", reqs[6].http.Method)
|
||||
assert.Equal(t, "DELETE", reqs[7].http.Method)
|
||||
}
|
||||
|
||||
func TestExpectBuilders(t *testing.T) {
|
||||
client := &mockClient{}
|
||||
|
||||
reporter := NewAssertReporter(t)
|
||||
|
||||
config := Config{
|
||||
Client: client,
|
||||
Reporter: reporter,
|
||||
}
|
||||
|
||||
e := WithConfig(config)
|
||||
|
||||
var reqs1 []*Request
|
||||
|
||||
e1 := e.Builder(func(r *Request) {
|
||||
reqs1 = append(reqs1, r)
|
||||
})
|
||||
|
||||
var reqs2 []*Request
|
||||
|
||||
e2 := e1.Builder(func(r *Request) {
|
||||
reqs2 = append(reqs2, r)
|
||||
})
|
||||
|
||||
e.Request("METHOD", "/url")
|
||||
|
||||
r1 := e1.Request("METHOD", "/url")
|
||||
r2 := e2.Request("METHOD", "/url")
|
||||
|
||||
assert.Equal(t, 2, int(len(reqs1)))
|
||||
assert.Equal(t, 1, int(len(reqs2)))
|
||||
|
||||
assert.Equal(t, r1, reqs1[0])
|
||||
assert.Equal(t, r2, reqs1[1])
|
||||
assert.Equal(t, r1, reqs2[0])
|
||||
}
|
||||
|
||||
func TestExpectValues(t *testing.T) {
|
||||
client := &mockClient{}
|
||||
|
||||
r := NewAssertReporter(t)
|
||||
|
||||
config := Config{
|
||||
Client: client,
|
||||
Reporter: r,
|
||||
}
|
||||
|
||||
e := WithConfig(config)
|
||||
|
||||
m := map[string]interface{}{}
|
||||
a := []interface{}{}
|
||||
s := ""
|
||||
n := 0.0
|
||||
b := false
|
||||
|
||||
assert.Equal(t, NewValue(r, m), e.Value(m))
|
||||
assert.Equal(t, NewObject(r, m), e.Object(m))
|
||||
assert.Equal(t, NewArray(r, a), e.Array(a))
|
||||
assert.Equal(t, NewString(r, s), e.String(s))
|
||||
assert.Equal(t, NewNumber(r, n), e.Number(n))
|
||||
assert.Equal(t, NewBoolean(r, b), e.Boolean(b))
|
||||
}
|
||||
|
||||
func TestExpectTraverse(t *testing.T) {
|
||||
client := &mockClient{}
|
||||
|
||||
reporter := NewAssertReporter(t)
|
||||
|
||||
config := Config{
|
||||
BaseURL: "http://example.com",
|
||||
Client: client,
|
||||
Reporter: reporter,
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"foo": []interface{}{"bar", 123, false, nil},
|
||||
"bar": "hello",
|
||||
"baz": 456,
|
||||
}
|
||||
|
||||
resp := WithConfig(config).GET("/url").WithJSON(data).Expect()
|
||||
|
||||
m := resp.JSON().Object()
|
||||
|
||||
m.Equal(data)
|
||||
|
||||
m.ContainsKey("foo")
|
||||
m.ContainsKey("bar")
|
||||
m.ContainsKey("foo")
|
||||
|
||||
m.ValueEqual("foo", data["foo"])
|
||||
m.ValueEqual("bar", data["bar"])
|
||||
m.ValueEqual("baz", data["baz"])
|
||||
|
||||
m.Keys().ContainsOnly("foo", "bar", "baz")
|
||||
m.Values().ContainsOnly(data["foo"], data["bar"], data["baz"])
|
||||
|
||||
m.Value("foo").Array().Elements("bar", 123, false, nil)
|
||||
m.Value("bar").String().Equal("hello")
|
||||
m.Value("baz").Number().Equal(456)
|
||||
|
||||
m.Value("foo").Array().Element(2).Boolean().False()
|
||||
m.Value("foo").Array().Element(3).Null()
|
||||
}
|
||||
|
||||
func TestExpectBranches(t *testing.T) {
|
||||
client := &mockClient{}
|
||||
|
||||
config := Config{
|
||||
BaseURL: "http://example.com",
|
||||
Client: client,
|
||||
Reporter: newMockReporter(t),
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"foo": []interface{}{"bar", 123, false, nil},
|
||||
"bar": "hello",
|
||||
"baz": 456,
|
||||
}
|
||||
|
||||
resp := WithConfig(config).GET("/url").WithJSON(data).Expect()
|
||||
|
||||
m1 := resp.JSON().Array()
|
||||
m2 := resp.JSON().Object()
|
||||
|
||||
e1 := m2.Value("foo").Object()
|
||||
e2 := m2.Value("foo").Array().Element(999).String()
|
||||
e3 := m2.Value("foo").Array().Element(0).Number()
|
||||
e4 := m2.Value("foo").Array().Element(0).String()
|
||||
|
||||
e4.Equal("bar")
|
||||
|
||||
m1.chain.assertFailed(t)
|
||||
m2.chain.assertOK(t)
|
||||
|
||||
e1.chain.assertFailed(t)
|
||||
e2.chain.assertFailed(t)
|
||||
e3.chain.assertFailed(t)
|
||||
e4.chain.assertOK(t)
|
||||
}
|
||||
|
||||
func TestExpectStdCompat(_ *testing.T) {
|
||||
New(&testing.T{}, "")
|
||||
New(&testing.B{}, "")
|
||||
New(testing.TB(&testing.T{}), "")
|
||||
}
|
||||
|
||||
type testRequestFactory struct {
|
||||
lastreq *http.Request
|
||||
fail bool
|
||||
}
|
||||
|
||||
func (f *testRequestFactory) NewRequest(
|
||||
method, urlStr string, body io.Reader) (*http.Request, error) {
|
||||
if f.fail {
|
||||
return nil, errors.New("testRequestFactory")
|
||||
}
|
||||
f.lastreq = httptest.NewRequest(method, urlStr, body)
|
||||
return f.lastreq, nil
|
||||
}
|
||||
|
||||
func TestExpectRequestFactory(t *testing.T) {
|
||||
e1 := WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
})
|
||||
r1 := e1.Request("GET", "/")
|
||||
r1.chain.assertOK(t)
|
||||
assert.NotNil(t, r1.http)
|
||||
|
||||
f2 := &testRequestFactory{}
|
||||
e2 := WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
RequestFactory: f2,
|
||||
})
|
||||
r2 := e2.Request("GET", "/")
|
||||
r2.chain.assertOK(t)
|
||||
assert.NotNil(t, f2.lastreq)
|
||||
assert.True(t, f2.lastreq == r2.http)
|
||||
|
||||
f3 := &testRequestFactory{
|
||||
fail: true,
|
||||
}
|
||||
e3 := WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: newMockReporter(t),
|
||||
RequestFactory: f3,
|
||||
})
|
||||
r3 := e3.Request("GET", "/")
|
||||
r3.chain.assertFailed(t)
|
||||
assert.Nil(t, f3.lastreq)
|
||||
}
|
||||
|
||||
func createBasicHandler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/foo", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"foo":123}`))
|
||||
})
|
||||
|
||||
mux.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
w.Write([]byte(`field1=` + r.FormValue("field1")))
|
||||
w.Write([]byte(`&field2=` + r.PostFormValue("field2")))
|
||||
})
|
||||
|
||||
mux.HandleFunc("/baz/qux", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`[true, false]`))
|
||||
|
||||
case "PUT":
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
var m map[string]interface{}
|
||||
if err := decoder.Decode(&m); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
} else if m["test"] != "ok" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`ok`))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
mux.HandleFunc("/wee", func(w http.ResponseWriter, r *http.Request) {
|
||||
if u, p, ok := r.BasicAuth(); ok {
|
||||
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
w.Write([]byte(`username=` + u))
|
||||
w.Write([]byte(`&password=` + p))
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
})
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func testBasicHandler(e *Expect) {
|
||||
e.GET("/foo")
|
||||
e.GET("/foo").Expect()
|
||||
e.GET("/foo").Expect().Status(http.StatusOK)
|
||||
|
||||
e.GET("/foo").Expect().
|
||||
Status(http.StatusOK).JSON().Object().ValueEqual("foo", 123)
|
||||
|
||||
e.PUT("/bar").WithQuery("field1", "hello").WithFormField("field2", "world").
|
||||
Expect().
|
||||
Status(http.StatusOK).
|
||||
Form().ValueEqual("field1", "hello").ValueEqual("field2", "world")
|
||||
|
||||
e.GET("/{a}/{b}", "baz", "qux").
|
||||
Expect().
|
||||
Status(http.StatusOK).JSON().Array().Elements(true, false)
|
||||
|
||||
e.PUT("/{a}/{b}").
|
||||
WithPath("a", "baz").
|
||||
WithPath("b", "qux").
|
||||
WithJSON(map[string]string{"test": "ok"}).
|
||||
Expect().
|
||||
Status(http.StatusOK).Body().Equal("ok")
|
||||
|
||||
auth := e.Builder(func(req *Request) {
|
||||
req.WithBasicAuth("john", "secret")
|
||||
})
|
||||
|
||||
auth.PUT("/wee").
|
||||
Expect().
|
||||
Status(http.StatusOK).
|
||||
Form().ValueEqual("username", "john").ValueEqual("password", "secret")
|
||||
}
|
||||
|
||||
func TestExpectBasicHandlerLiveDefault(t *testing.T) {
|
||||
handler := createBasicHandler()
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
testBasicHandler(New(t, server.URL))
|
||||
}
|
||||
|
||||
func TestExpectBasicHandlerLiveConfig(t *testing.T) {
|
||||
handler := createBasicHandler()
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
testBasicHandler(WithConfig(Config{
|
||||
BaseURL: server.URL,
|
||||
Reporter: NewAssertReporter(t),
|
||||
Printers: []Printer{
|
||||
NewCurlPrinter(t),
|
||||
NewDebugPrinter(t, true),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestExpectBasicHandlerLiveTLS(t *testing.T) {
|
||||
handler := createBasicHandler()
|
||||
|
||||
server := httptest.NewTLSServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
testBasicHandler(WithConfig(Config{
|
||||
BaseURL: server.URL,
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestExpectBasicHandlerLiveLongRun(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
||||
handler := createBasicHandler()
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
e := New(t, server.URL)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
testBasicHandler(e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpectBasicHandlerBinderStandard(t *testing.T) {
|
||||
handler := createBasicHandler()
|
||||
|
||||
testBasicHandler(WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: NewBinder(handler),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestExpectBasicHandlerBinderFast(t *testing.T) {
|
||||
handler := fasthttpadaptor.NewFastHTTPHandler(createBasicHandler())
|
||||
|
||||
testBasicHandler(WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: NewFastBinder(handler),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func createRedirectHandler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/foo", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write([]byte(`hello`))
|
||||
})
|
||||
|
||||
mux.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/foo", http.StatusFound)
|
||||
})
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func createRedirectFastHandler() fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
switch string(ctx.Path()) {
|
||||
case "/foo":
|
||||
ctx.SetBody([]byte(`hello`))
|
||||
|
||||
case "/bar":
|
||||
ctx.Redirect("/foo", http.StatusFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testRedirectHandler(e *Expect) {
|
||||
e.POST("/bar").
|
||||
Expect().
|
||||
Status(http.StatusOK).Body().Equal(`hello`)
|
||||
}
|
||||
|
||||
func TestExpectRedirectHandlerLive(t *testing.T) {
|
||||
handler := createRedirectHandler()
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
testRedirectHandler(New(t, server.URL))
|
||||
}
|
||||
|
||||
func TestExpectRedirectHandlerBinderStandard(t *testing.T) {
|
||||
handler := createRedirectHandler()
|
||||
|
||||
testRedirectHandler(WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: NewBinder(handler),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestExpectRedirectHandlerBinderFast(t *testing.T) {
|
||||
handler := createRedirectFastHandler()
|
||||
|
||||
testRedirectHandler(WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: NewFastBinder(handler),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func createAutoTLSHandler(https string) http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/tls", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.TLS == nil {
|
||||
w.Write([]byte(`no`))
|
||||
} else {
|
||||
w.Write([]byte(`yes`))
|
||||
}
|
||||
})
|
||||
|
||||
mux.HandleFunc("/protected", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.TLS == nil {
|
||||
http.Redirect(w, r, https+r.RequestURI, http.StatusFound)
|
||||
} else {
|
||||
w.Write([]byte(`hello`))
|
||||
}
|
||||
})
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func createAutoTLSFastHandler(https string) fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
switch string(ctx.Path()) {
|
||||
case "/tls":
|
||||
if !ctx.IsTLS() {
|
||||
ctx.SetBody([]byte(`no`))
|
||||
} else {
|
||||
ctx.SetBody([]byte(`yes`))
|
||||
}
|
||||
|
||||
case "/protected":
|
||||
if !ctx.IsTLS() {
|
||||
ctx.Redirect(https+string(ctx.Request.RequestURI()), http.StatusFound)
|
||||
} else {
|
||||
ctx.SetBody([]byte(`hello`))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testAutoTLSHandler(config Config) {
|
||||
e := WithConfig(config)
|
||||
|
||||
tls := e.POST("/tls").
|
||||
Expect().
|
||||
Status(http.StatusOK).Body()
|
||||
|
||||
if strings.HasPrefix(config.BaseURL, "https://") {
|
||||
tls.Equal(`yes`)
|
||||
} else {
|
||||
tls.Equal(`no`)
|
||||
}
|
||||
|
||||
e.POST("/protected").
|
||||
Expect().
|
||||
Status(http.StatusOK).Body().Equal(`hello`)
|
||||
}
|
||||
|
||||
func TestExpectAutoTLSHandlerLive(t *testing.T) {
|
||||
httpsServ := httptest.NewTLSServer(createAutoTLSHandler(""))
|
||||
defer httpsServ.Close()
|
||||
|
||||
httpServ := httptest.NewServer(createAutoTLSHandler(httpsServ.URL))
|
||||
defer httpServ.Close()
|
||||
|
||||
assert.True(t, strings.HasPrefix(httpsServ.URL, "https://"))
|
||||
assert.True(t, strings.HasPrefix(httpServ.URL, "http://"))
|
||||
|
||||
for _, url := range []string{httpsServ.URL, httpServ.URL} {
|
||||
testAutoTLSHandler(Config{
|
||||
BaseURL: url,
|
||||
Reporter: NewRequireReporter(t),
|
||||
Printers: []Printer{
|
||||
NewDebugPrinter(t, true),
|
||||
},
|
||||
Client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpectAutoTLSHandlerBinderStandard(t *testing.T) {
|
||||
handler := createAutoTLSHandler("https://example.com")
|
||||
|
||||
for _, url := range []string{"https://example.com", "http://example.com"} {
|
||||
testAutoTLSHandler(Config{
|
||||
BaseURL: url,
|
||||
Reporter: NewRequireReporter(t),
|
||||
Printers: []Printer{
|
||||
NewDebugPrinter(t, true),
|
||||
},
|
||||
Client: &http.Client{
|
||||
Transport: &Binder{
|
||||
Handler: handler,
|
||||
TLS: &tls.ConnectionState{},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpectAutoTLSHandlerBinderFast(t *testing.T) {
|
||||
handler := createAutoTLSFastHandler("https://example.com")
|
||||
|
||||
for _, url := range []string{"https://example.com", "http://example.com"} {
|
||||
testAutoTLSHandler(Config{
|
||||
BaseURL: url,
|
||||
Reporter: NewRequireReporter(t),
|
||||
Printers: []Printer{
|
||||
NewDebugPrinter(t, true),
|
||||
},
|
||||
Client: &http.Client{
|
||||
Transport: &FastBinder{
|
||||
Handler: handler,
|
||||
TLS: &tls.ConnectionState{},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createChunkedHandler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Proto != "HTTP/1.1" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
} else if len(r.TransferEncoding) != 1 || r.TransferEncoding[0] != "chunked" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
} else if r.PostFormValue("key") != "value" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`[1, `))
|
||||
w.(http.Flusher).Flush()
|
||||
w.Write([]byte(`2]`))
|
||||
}
|
||||
})
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func createChunkedFastHandler(t *testing.T) fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
headers := map[string][]string{}
|
||||
|
||||
ctx.Request.Header.VisitAll(func(k, v []byte) {
|
||||
headers[string(k)] = append(headers[string(k)], string(v))
|
||||
})
|
||||
|
||||
assert.Equal(t, []string{"chunked"}, headers["Transfer-Encoding"])
|
||||
assert.Equal(t, "value", string(ctx.FormValue("key")))
|
||||
assert.Equal(t, "key=value", string(ctx.Request.Body()))
|
||||
|
||||
ctx.Response.Header.Set("Content-Type", "application/json")
|
||||
ctx.Response.SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||
w.WriteString(`[1, `)
|
||||
w.Flush()
|
||||
w.WriteString(`2]`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testChunkedHandler(e *Expect) {
|
||||
e.PUT("/").
|
||||
WithHeader("Content-Type", "application/x-www-form-urlencoded").
|
||||
WithChunked(strings.NewReader("key=value")).
|
||||
Expect().
|
||||
Status(http.StatusOK).
|
||||
ContentType("application/json").
|
||||
TransferEncoding("chunked").
|
||||
JSON().Array().Elements(1, 2)
|
||||
}
|
||||
|
||||
func TestExpectChunkedHandlerLive(t *testing.T) {
|
||||
handler := createChunkedHandler()
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
testChunkedHandler(New(t, server.URL))
|
||||
}
|
||||
|
||||
func TestExpectChunkedHandlerBinderStandard(t *testing.T) {
|
||||
handler := createChunkedHandler()
|
||||
|
||||
testChunkedHandler(WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: NewBinder(handler),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestExpectChunkedHandlerBinderFast(t *testing.T) {
|
||||
handler := createChunkedFastHandler(t)
|
||||
|
||||
testChunkedHandler(WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: NewFastBinder(handler),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func createCookieHandler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "myname",
|
||||
Value: "myvalue",
|
||||
Path: "/",
|
||||
Expires: time.Date(3000, 0, 0, 0, 0, 0, 0, time.UTC),
|
||||
})
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie("myname")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(cookie.Value))
|
||||
}
|
||||
})
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func testCookieHandler(e *Expect, enabled bool) {
|
||||
r := e.PUT("/set").Expect().Status(http.StatusNoContent)
|
||||
|
||||
r.Cookies().ContainsOnly("myname")
|
||||
c := r.Cookie("myname")
|
||||
c.Value().Equal("myvalue")
|
||||
c.Path().Equal("/")
|
||||
c.Expires().Equal(time.Date(3000, 0, 0, 0, 0, 0, 0, time.UTC))
|
||||
|
||||
if enabled {
|
||||
e.GET("/get").Expect().Status(http.StatusOK).Text().Equal("myvalue")
|
||||
} else {
|
||||
e.GET("/get").Expect().Status(http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpectCookieHandlerLiveDisabled(t *testing.T) {
|
||||
handler := createCookieHandler()
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
e := WithConfig(Config{
|
||||
BaseURL: server.URL,
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Jar: nil,
|
||||
},
|
||||
})
|
||||
|
||||
testCookieHandler(e, false)
|
||||
}
|
||||
|
||||
func TestExpecCookieHandlerLiveEnabled(t *testing.T) {
|
||||
handler := createCookieHandler()
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
e := WithConfig(Config{
|
||||
BaseURL: server.URL,
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Jar: NewJar(),
|
||||
},
|
||||
})
|
||||
|
||||
testCookieHandler(e, true)
|
||||
}
|
||||
|
||||
func TestExpectCookieHandlerBinderStandardDisabled(t *testing.T) {
|
||||
handler := createCookieHandler()
|
||||
|
||||
e := WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: NewBinder(handler),
|
||||
Jar: nil,
|
||||
},
|
||||
})
|
||||
|
||||
testCookieHandler(e, false)
|
||||
}
|
||||
|
||||
func TestExpectCookieHandlerBinderStandardEnabled(t *testing.T) {
|
||||
handler := createCookieHandler()
|
||||
|
||||
e := WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: NewBinder(handler),
|
||||
Jar: NewJar(),
|
||||
},
|
||||
})
|
||||
|
||||
testCookieHandler(e, true)
|
||||
}
|
||||
|
||||
func TestExpectCookieHandlerBinderFastDisabled(t *testing.T) {
|
||||
handler := fasthttpadaptor.NewFastHTTPHandler(createCookieHandler())
|
||||
|
||||
e := WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: NewFastBinder(handler),
|
||||
Jar: nil,
|
||||
},
|
||||
})
|
||||
|
||||
testCookieHandler(e, false)
|
||||
}
|
||||
|
||||
func TestExpectCookieHandlerBinderFastEnabled(t *testing.T) {
|
||||
handler := fasthttpadaptor.NewFastHTTPHandler(createCookieHandler())
|
||||
|
||||
e := WithConfig(Config{
|
||||
BaseURL: "http://example.com",
|
||||
Reporter: NewAssertReporter(t),
|
||||
Client: &http.Client{
|
||||
Transport: NewFastBinder(handler),
|
||||
Jar: NewJar(),
|
||||
},
|
||||
})
|
||||
|
||||
testCookieHandler(e, true)
|
||||
}
|
||||
|
||||
func TestExpectStaticFastBinder(t *testing.T) {
|
||||
tempdir, err := ioutil.TempDir("", "httpexpect")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempdir)
|
||||
|
||||
if err := ioutil.WriteFile(
|
||||
path.Join(tempdir, "hello"), []byte("hello, world!"), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fs := &fasthttp.FS{
|
||||
Root: tempdir,
|
||||
}
|
||||
|
||||
handler := fs.NewRequestHandler()
|
||||
|
||||
e := WithConfig(Config{
|
||||
Client: &http.Client{
|
||||
Transport: NewFastBinder(handler),
|
||||
Jar: NewJar(),
|
||||
},
|
||||
Reporter: NewAssertReporter(t),
|
||||
Printers: []Printer{
|
||||
NewDebugPrinter(t, true),
|
||||
},
|
||||
})
|
||||
|
||||
e.GET("/hello").
|
||||
Expect().
|
||||
Status(http.StatusOK).
|
||||
Text().Equal("hello, world!")
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
"github.com/yalp/jsonpath"
|
||||
"github.com/yudai/gojsondiff"
|
||||
"github.com/yudai/gojsondiff/formatter"
|
||||
)
|
||||
|
||||
func toString(str interface{}) (s string, ok bool) {
|
||||
ok = true
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ok = false
|
||||
}
|
||||
}()
|
||||
s = reflect.ValueOf(str).Convert(reflect.TypeOf("")).String()
|
||||
return
|
||||
}
|
||||
|
||||
func getPath(chain *chain, value interface{}, path string) *Value {
|
||||
if chain.failed() {
|
||||
return &Value{*chain, nil}
|
||||
}
|
||||
|
||||
result, err := jsonpath.Read(value, path)
|
||||
if err != nil {
|
||||
chain.fail(err.Error())
|
||||
return &Value{*chain, nil}
|
||||
}
|
||||
|
||||
return &Value{*chain, result}
|
||||
}
|
||||
|
||||
func checkSchema(chain *chain, value, schema interface{}) {
|
||||
if chain.failed() {
|
||||
return
|
||||
}
|
||||
|
||||
valueLoader := gojsonschema.NewGoLoader(value)
|
||||
|
||||
var schemaLoader gojsonschema.JSONLoader
|
||||
|
||||
if str, ok := toString(schema); ok {
|
||||
if ok, _ := regexp.MatchString(`^\w+://`, str); ok {
|
||||
schemaLoader = gojsonschema.NewReferenceLoader(str)
|
||||
} else {
|
||||
schemaLoader = gojsonschema.NewStringLoader(str)
|
||||
}
|
||||
} else {
|
||||
schemaLoader = gojsonschema.NewGoLoader(schema)
|
||||
}
|
||||
|
||||
result, err := gojsonschema.Validate(schemaLoader, valueLoader)
|
||||
if err != nil {
|
||||
chain.fail("\n%s\n\nschema:\n%s\n\nvalue:\n%s",
|
||||
err.Error(),
|
||||
dumpSchema(schema),
|
||||
dumpValue(value))
|
||||
return
|
||||
}
|
||||
|
||||
if !result.Valid() {
|
||||
errors := ""
|
||||
for _, err := range result.Errors() {
|
||||
errors += fmt.Sprintf(" %s\n", err)
|
||||
}
|
||||
|
||||
chain.fail(
|
||||
"\njson schema validation failed, schema:\n%s\n\nvalue:%s\n\nerrors:\n%s",
|
||||
dumpSchema(schema),
|
||||
dumpValue(value),
|
||||
errors)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func dumpSchema(schema interface{}) string {
|
||||
if s, ok := toString(schema); ok {
|
||||
schema = s
|
||||
}
|
||||
return regexp.MustCompile(`(?m:^)`).
|
||||
ReplaceAllString(fmt.Sprintf("%v", schema), " ")
|
||||
}
|
||||
|
||||
func canonNumber(chain *chain, number interface{}) (f float64, ok bool) {
|
||||
ok = true
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
chain.fail("%v", err)
|
||||
ok = false
|
||||
}
|
||||
}()
|
||||
f = reflect.ValueOf(number).Convert(reflect.TypeOf(float64(0))).Float()
|
||||
return
|
||||
}
|
||||
|
||||
func canonArray(chain *chain, in interface{}) ([]interface{}, bool) {
|
||||
var out []interface{}
|
||||
data, ok := canonValue(chain, in)
|
||||
if ok {
|
||||
out, ok = data.([]interface{})
|
||||
if !ok {
|
||||
chain.fail("expected array, got %v", out)
|
||||
}
|
||||
}
|
||||
return out, ok
|
||||
}
|
||||
|
||||
func canonMap(chain *chain, in interface{}) (map[string]interface{}, bool) {
|
||||
var out map[string]interface{}
|
||||
data, ok := canonValue(chain, in)
|
||||
if ok {
|
||||
out, ok = data.(map[string]interface{})
|
||||
if !ok {
|
||||
chain.fail("expected map, got %v", out)
|
||||
}
|
||||
}
|
||||
return out, ok
|
||||
}
|
||||
|
||||
func canonValue(chain *chain, in interface{}) (interface{}, bool) {
|
||||
b, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
chain.fail(err.Error())
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var out interface{}
|
||||
if err := json.Unmarshal(b, &out); err != nil {
|
||||
chain.fail(err.Error())
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return out, true
|
||||
}
|
||||
|
||||
func dumpValue(value interface{}) string {
|
||||
b, err := json.MarshalIndent(value, " ", " ")
|
||||
if err != nil {
|
||||
return " " + fmt.Sprintf("%#v", value)
|
||||
}
|
||||
return " " + string(b)
|
||||
}
|
||||
|
||||
func diffValues(expected, actual interface{}) string {
|
||||
differ := gojsondiff.New()
|
||||
|
||||
var diff gojsondiff.Diff
|
||||
|
||||
if ve, ok := expected.(map[string]interface{}); ok {
|
||||
if va, ok := actual.(map[string]interface{}); ok {
|
||||
diff = differ.CompareObjects(ve, va)
|
||||
} else {
|
||||
return " (unavailable)"
|
||||
}
|
||||
} else if ve, ok := expected.([]interface{}); ok {
|
||||
if va, ok := actual.([]interface{}); ok {
|
||||
diff = differ.CompareArrays(ve, va)
|
||||
} else {
|
||||
return " (unavailable)"
|
||||
}
|
||||
} else {
|
||||
return " (unavailable)"
|
||||
}
|
||||
|
||||
config := formatter.AsciiFormatterConfig{
|
||||
ShowArrayIndex: true,
|
||||
}
|
||||
f := formatter.NewAsciiFormatter(expected, config)
|
||||
|
||||
str, err := f.Format(diff)
|
||||
if err != nil {
|
||||
return " (unavailable)"
|
||||
}
|
||||
|
||||
return "--- expected\n+++ actual\n" + str
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCanonNumber(t *testing.T) {
|
||||
type (
|
||||
myInt int
|
||||
)
|
||||
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
d1, ok := canonNumber(&chain, 123)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 123.0, d1)
|
||||
chain.assertOK(t)
|
||||
chain.reset()
|
||||
|
||||
d2, ok := canonNumber(&chain, 123.0)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 123.0, d2)
|
||||
chain.assertOK(t)
|
||||
chain.reset()
|
||||
|
||||
d3, ok := canonNumber(&chain, myInt(123))
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 123.0, d3)
|
||||
chain.assertOK(t)
|
||||
chain.reset()
|
||||
|
||||
_, ok = canonNumber(&chain, "123")
|
||||
assert.False(t, ok)
|
||||
chain.assertFailed(t)
|
||||
chain.reset()
|
||||
|
||||
_, ok = canonNumber(&chain, nil)
|
||||
assert.False(t, ok)
|
||||
chain.assertFailed(t)
|
||||
chain.reset()
|
||||
}
|
||||
|
||||
func TestCanonArray(t *testing.T) {
|
||||
type (
|
||||
myArray []interface{}
|
||||
myInt int
|
||||
)
|
||||
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
d1, ok := canonArray(&chain, []interface{}{123.0, 456.0})
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, []interface{}{123.0, 456.0}, d1)
|
||||
chain.assertOK(t)
|
||||
chain.reset()
|
||||
|
||||
d2, ok := canonArray(&chain, myArray{myInt(123), 456.0})
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, []interface{}{123.0, 456.0}, d2)
|
||||
chain.assertOK(t)
|
||||
chain.reset()
|
||||
|
||||
_, ok = canonArray(&chain, "123")
|
||||
assert.False(t, ok)
|
||||
chain.assertFailed(t)
|
||||
chain.reset()
|
||||
|
||||
_, ok = canonArray(&chain, func() {})
|
||||
assert.False(t, ok)
|
||||
chain.assertFailed(t)
|
||||
chain.reset()
|
||||
|
||||
_, ok = canonArray(&chain, nil)
|
||||
assert.False(t, ok)
|
||||
chain.assertFailed(t)
|
||||
chain.reset()
|
||||
|
||||
_, ok = canonArray(&chain, []interface{}(nil))
|
||||
assert.False(t, ok)
|
||||
chain.assertFailed(t)
|
||||
chain.reset()
|
||||
}
|
||||
|
||||
func TestCanonMap(t *testing.T) {
|
||||
type (
|
||||
myMap map[string]interface{}
|
||||
myInt int
|
||||
)
|
||||
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
d1, ok := canonMap(&chain, map[string]interface{}{"foo": 123.0})
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, map[string]interface{}{"foo": 123.0}, d1)
|
||||
chain.assertOK(t)
|
||||
chain.reset()
|
||||
|
||||
d2, ok := canonMap(&chain, myMap{"foo": myInt(123)})
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, map[string]interface{}{"foo": 123.0}, d2)
|
||||
chain.assertOK(t)
|
||||
chain.reset()
|
||||
|
||||
_, ok = canonMap(&chain, "123")
|
||||
assert.False(t, ok)
|
||||
chain.assertFailed(t)
|
||||
chain.reset()
|
||||
|
||||
_, ok = canonMap(&chain, func() {})
|
||||
assert.False(t, ok)
|
||||
chain.assertFailed(t)
|
||||
chain.reset()
|
||||
|
||||
_, ok = canonMap(&chain, nil)
|
||||
assert.False(t, ok)
|
||||
chain.assertFailed(t)
|
||||
chain.reset()
|
||||
|
||||
_, ok = canonMap(&chain, map[string]interface{}(nil))
|
||||
assert.False(t, ok)
|
||||
chain.assertFailed(t)
|
||||
chain.reset()
|
||||
}
|
||||
|
||||
func TestDiffErrors(t *testing.T) {
|
||||
na := " (unavailable)"
|
||||
|
||||
assert.Equal(t, na, diffValues(map[string]interface{}{}, []interface{}{}))
|
||||
assert.Equal(t, na, diffValues([]interface{}{}, map[string]interface{}{}))
|
||||
assert.Equal(t, na, diffValues("foo", "bar"))
|
||||
assert.Equal(t, na, diffValues(func() {}, func() {}))
|
||||
|
||||
assert.NotEqual(t, na, diffValues(map[string]interface{}{}, map[string]interface{}{}))
|
||||
assert.NotEqual(t, na, diffValues([]interface{}{}, []interface{}{}))
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Match provides methods to inspect attached regexp match results.
|
||||
type Match struct {
|
||||
chain chain
|
||||
submatches []string
|
||||
names map[string]int
|
||||
}
|
||||
|
||||
// NewMatch returns a new Match object given a reporter used to report
|
||||
// failures and submatches to be inspected.
|
||||
//
|
||||
// reporter should not be nil. submatches and names may be nil.
|
||||
//
|
||||
// Example:
|
||||
// s := "http://example.com/users/john"
|
||||
// r := regexp.MustCompile(`http://(?P<host>.+)/users/(?P<user>.+)`)
|
||||
// m := NewMatch(reporter, r.FindStringSubmatch(s), r.SubexpNames())
|
||||
//
|
||||
// m.NotEmpty()
|
||||
// m.Length().Equal(3)
|
||||
//
|
||||
// m.Index(0).Equal("http://example.com/users/john")
|
||||
// m.Index(1).Equal("example.com")
|
||||
// m.Index(2).Equal("john")
|
||||
//
|
||||
// m.Name("host").Equal("example.com")
|
||||
// m.Name("user").Equal("john")
|
||||
func NewMatch(reporter Reporter, submatches []string, names []string) *Match {
|
||||
return makeMatch(makeChain(reporter), submatches, names)
|
||||
}
|
||||
|
||||
func makeMatch(chain chain, submatches []string, names []string) *Match {
|
||||
if submatches == nil {
|
||||
submatches = []string{}
|
||||
}
|
||||
namemap := map[string]int{}
|
||||
for n, name := range names {
|
||||
if name != "" {
|
||||
namemap[name] = n
|
||||
}
|
||||
}
|
||||
return &Match{chain, submatches, namemap}
|
||||
}
|
||||
|
||||
// Raw returns underlying submatches attached to Match.
|
||||
// This is the value originally passed to NewMatch.
|
||||
//
|
||||
// Example:
|
||||
// m := NewMatch(t, submatches, names)
|
||||
// assert.Equal(t, submatches, m.Raw())
|
||||
func (m *Match) Raw() []string {
|
||||
return m.submatches
|
||||
}
|
||||
|
||||
// Length returns a new Number object that may be used to inspect
|
||||
// number of submatches.
|
||||
//
|
||||
// Example:
|
||||
// m := NewMatch(t, submatches, names)
|
||||
// m.Length().Equal(len(submatches))
|
||||
func (m *Match) Length() *Number {
|
||||
return &Number{m.chain, float64(len(m.submatches))}
|
||||
}
|
||||
|
||||
// Index returns a new String object that may be used to inspect submatch
|
||||
// with given index.
|
||||
//
|
||||
// Note that submatch with index 0 contains the whole match. If index is out
|
||||
// of bounds, Index reports failure and returns empty (but non-nil) value.
|
||||
//
|
||||
// Example:
|
||||
// s := "http://example.com/users/john"
|
||||
//
|
||||
// r := regexp.MustCompile(`http://(.+)/users/(.+)`)
|
||||
// m := NewMatch(t, r.FindStringSubmatch(s), nil)
|
||||
//
|
||||
// m.Index(0).Equal("http://example.com/users/john")
|
||||
// m.Index(1).Equal("example.com")
|
||||
// m.Index(2).Equal("john")
|
||||
func (m *Match) Index(index int) *String {
|
||||
if index < 0 || index >= len(m.submatches) {
|
||||
m.chain.fail(
|
||||
"\nsubmatch index out of bounds:\n index %d\n\n bounds [%d; %d)",
|
||||
index,
|
||||
0,
|
||||
len(m.submatches))
|
||||
return &String{m.chain, ""}
|
||||
}
|
||||
return &String{m.chain, m.submatches[index]}
|
||||
}
|
||||
|
||||
// Name returns a new String object that may be used to inspect submatch
|
||||
// with given name.
|
||||
//
|
||||
// If there is no submatch with given name, Name reports failure and returns
|
||||
// empty (but non-nil) value.
|
||||
//
|
||||
// Example:
|
||||
// s := "http://example.com/users/john"
|
||||
//
|
||||
// r := regexp.MustCompile(`http://(?P<host>.+)/users/(?P<user>.+)`)
|
||||
// m := NewMatch(t, r.FindStringSubmatch(s), r.SubexpNames())
|
||||
//
|
||||
// m.Name("host").Equal("example.com")
|
||||
// m.Name("user").Equal("john")
|
||||
func (m *Match) Name(name string) *String {
|
||||
index, ok := m.names[name]
|
||||
if !ok {
|
||||
m.chain.fail(
|
||||
"\nsubmatch name not found:\n %q\n\navailable names:\n%s",
|
||||
name,
|
||||
dumpValue(m.names))
|
||||
return &String{m.chain, ""}
|
||||
}
|
||||
return m.Index(index)
|
||||
}
|
||||
|
||||
// Empty succeeds if submatches array is empty.
|
||||
//
|
||||
// Example:
|
||||
// m := NewMatch(t, submatches, names)
|
||||
// m.Empty()
|
||||
func (m *Match) Empty() *Match {
|
||||
if len(m.submatches) != 0 {
|
||||
m.chain.fail("\nexpected zero submatches, but got:\n %s",
|
||||
dumpValue(m.submatches))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NotEmpty succeeds if submatches array is non-empty.
|
||||
//
|
||||
// Example:
|
||||
// m := NewMatch(t, submatches, names)
|
||||
// m.NotEmpty()
|
||||
func (m *Match) NotEmpty() *Match {
|
||||
if len(m.submatches) == 0 {
|
||||
m.chain.fail("expected non-zero submatches")
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Values succeeds if submatches array, starting from index 1, is equal to
|
||||
// given array.
|
||||
//
|
||||
// Note that submatch with index 0 contains the whole match and is not
|
||||
// included into this check.
|
||||
//
|
||||
// Example:
|
||||
// s := "http://example.com/users/john"
|
||||
// r := regexp.MustCompile(`http://(.+)/users/(.+)`)
|
||||
// m := NewMatch(t, r.FindStringSubmatch(s), nil)
|
||||
// m.Values("example.com", "john")
|
||||
func (m *Match) Values(values ...string) *Match {
|
||||
if values == nil {
|
||||
values = []string{}
|
||||
}
|
||||
if !reflect.DeepEqual(values, m.getValues()) {
|
||||
m.chain.fail("\nexpected submatches equal to:\n%s\n\nbut got:\n%s",
|
||||
dumpValue(values),
|
||||
dumpValue(m.getValues()))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NotValues succeeds if submatches array, starting from index 1, is not
|
||||
// equal to given array.
|
||||
//
|
||||
// Note that submatch with index 0 contains the whole match and is not
|
||||
// included into this check.
|
||||
//
|
||||
// Example:
|
||||
// s := "http://example.com/users/john"
|
||||
// r := regexp.MustCompile(`http://(.+)/users/(.+)`)
|
||||
// m := NewMatch(t, r.FindStringSubmatch(s), nil)
|
||||
// m.NotValues("example.com", "bob")
|
||||
func (m *Match) NotValues(values ...string) *Match {
|
||||
if values == nil {
|
||||
values = []string{}
|
||||
}
|
||||
if reflect.DeepEqual(values, m.getValues()) {
|
||||
m.chain.fail("\nexpected submatches not equal to:\n%s",
|
||||
dumpValue(values))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Match) getValues() []string {
|
||||
if len(m.submatches) > 1 {
|
||||
return m.submatches[1:]
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMatchFailed(t *testing.T) {
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
chain.fail("fail")
|
||||
|
||||
value := &Match{chain, nil, nil}
|
||||
|
||||
value.chain.assertFailed(t)
|
||||
|
||||
assert.False(t, value.Length() == nil)
|
||||
assert.False(t, value.Index(0) == nil)
|
||||
assert.False(t, value.Name("") == nil)
|
||||
|
||||
value.Length().chain.assertFailed(t)
|
||||
value.Index(0).chain.assertFailed(t)
|
||||
value.Name("").chain.assertFailed(t)
|
||||
|
||||
value.Empty()
|
||||
value.NotEmpty()
|
||||
value.Values("")
|
||||
value.NotValues("")
|
||||
}
|
||||
|
||||
func TestMatchGetters(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
matches := []string{"m0", "m1", "m2"}
|
||||
names := []string{"", "n1", "n2"}
|
||||
|
||||
value := NewMatch(reporter, matches, names)
|
||||
|
||||
assert.Equal(t, matches, value.Raw())
|
||||
|
||||
assert.Equal(t, 3.0, value.Length().Raw())
|
||||
|
||||
assert.Equal(t, "m0", value.Index(0).Raw())
|
||||
assert.Equal(t, "m1", value.Index(1).Raw())
|
||||
assert.Equal(t, "m2", value.Index(2).Raw())
|
||||
value.chain.assertOK(t)
|
||||
|
||||
assert.Equal(t, "m1", value.Name("n1").Raw())
|
||||
assert.Equal(t, "m2", value.Name("n2").Raw())
|
||||
value.chain.assertOK(t)
|
||||
|
||||
assert.Equal(t, "", value.Index(-1).Raw())
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, "", value.Index(3).Raw())
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, "", value.Name("").Raw())
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, "", value.Name("bad").Raw())
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestMatchEmpty(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value1 := NewMatch(reporter, []string{"m"}, nil)
|
||||
value2 := NewMatch(reporter, []string{}, nil)
|
||||
value3 := NewMatch(reporter, nil, nil)
|
||||
|
||||
assert.Equal(t, []string{}, value2.Raw())
|
||||
assert.Equal(t, []string{}, value3.Raw())
|
||||
|
||||
value1.Empty()
|
||||
value1.chain.assertFailed(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value1.NotEmpty()
|
||||
value1.chain.assertOK(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value2.Empty()
|
||||
value2.chain.assertOK(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value2.NotEmpty()
|
||||
value2.chain.assertFailed(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value3.Empty()
|
||||
value3.chain.assertOK(t)
|
||||
value3.chain.reset()
|
||||
|
||||
value3.NotEmpty()
|
||||
value3.chain.assertFailed(t)
|
||||
value3.chain.reset()
|
||||
}
|
||||
|
||||
func TestMatchValues(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewMatch(reporter, []string{"m0", "m1", "m2"}, nil)
|
||||
|
||||
value.Values("m1", "m2")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Values("m2", "m1")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Values("m1")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Values()
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotValues("m1", "m2")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotValues("m2", "m1")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotValues("m1")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotValues()
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestMatchValuesEmpty(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value1 := NewMatch(reporter, nil, nil)
|
||||
value2 := NewMatch(reporter, []string{}, nil)
|
||||
value3 := NewMatch(reporter, []string{"m0"}, nil)
|
||||
|
||||
value1.Values()
|
||||
value1.chain.assertOK(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value1.Values("")
|
||||
value1.chain.assertFailed(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value2.Values()
|
||||
value2.chain.assertOK(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value2.Values("")
|
||||
value2.chain.assertFailed(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value3.Values()
|
||||
value3.chain.assertOK(t)
|
||||
value3.chain.reset()
|
||||
|
||||
value3.Values("m0")
|
||||
value3.chain.assertFailed(t)
|
||||
value3.chain.reset()
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockClient struct {
|
||||
req *http.Request
|
||||
resp http.Response
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *mockClient) Do(req *http.Request) (*http.Response, error) {
|
||||
c.req = req
|
||||
if c.err == nil {
|
||||
c.resp.Header = c.req.Header
|
||||
c.resp.Body = c.req.Body
|
||||
return &c.resp, nil
|
||||
}
|
||||
return nil, c.err
|
||||
}
|
||||
|
||||
type mockReporter struct {
|
||||
testing *testing.T
|
||||
reported bool
|
||||
}
|
||||
|
||||
func newMockReporter(t *testing.T) *mockReporter {
|
||||
return &mockReporter{t, false}
|
||||
}
|
||||
|
||||
func (r *mockReporter) Errorf(message string, args ...interface{}) {
|
||||
r.testing.Logf("Fail: "+message, args...)
|
||||
r.reported = true
|
||||
}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Number provides methods to inspect attached float64 value
|
||||
// (Go representation of JSON number).
|
||||
type Number struct {
|
||||
chain chain
|
||||
value float64
|
||||
}
|
||||
|
||||
// NewNumber returns a new Number given a reporter used to report
|
||||
// failures and value to be inspected.
|
||||
//
|
||||
// reporter should not be nil.
|
||||
//
|
||||
// Example:
|
||||
// number := NewNumber(t, 123.4)
|
||||
func NewNumber(reporter Reporter, value float64) *Number {
|
||||
return &Number{makeChain(reporter), value}
|
||||
}
|
||||
|
||||
// Raw returns underlying value attached to Number.
|
||||
// This is the value originally passed to NewNumber.
|
||||
//
|
||||
// Example:
|
||||
// number := NewNumber(t, 123.4)
|
||||
// assert.Equal(t, 123.4, number.Raw())
|
||||
func (n *Number) Raw() float64 {
|
||||
return n.value
|
||||
}
|
||||
|
||||
// Path is similar to Value.Path.
|
||||
func (n *Number) Path(path string) *Value {
|
||||
return getPath(&n.chain, n.value, path)
|
||||
}
|
||||
|
||||
// Schema is similar to Value.Schema.
|
||||
func (n *Number) Schema(schema interface{}) *Number {
|
||||
checkSchema(&n.chain, n.value, schema)
|
||||
return n
|
||||
}
|
||||
|
||||
// Equal succeeds if number is equal to given value.
|
||||
//
|
||||
// value should have numeric type convertible to float64. Before comparison,
|
||||
// it is converted to float64.
|
||||
//
|
||||
// Example:
|
||||
// number := NewNumber(t, 123)
|
||||
// number.Equal(float64(123))
|
||||
// number.Equal(int32(123))
|
||||
func (n *Number) Equal(value interface{}) *Number {
|
||||
v, ok := canonNumber(&n.chain, value)
|
||||
if !ok {
|
||||
return n
|
||||
}
|
||||
if !(n.value == v) {
|
||||
n.chain.fail("\nexpected number equal to:\n %v\n\nbut got:\n %v",
|
||||
v, n.value)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// NotEqual succeeds if number is not equal to given value.
|
||||
//
|
||||
// value should have numeric type convertible to float64. Before comparison,
|
||||
// it is converted to float64.
|
||||
//
|
||||
// Example:
|
||||
// number := NewNumber(t, 123)
|
||||
// number.NotEqual(float64(321))
|
||||
// number.NotEqual(int32(321))
|
||||
func (n *Number) NotEqual(value interface{}) *Number {
|
||||
v, ok := canonNumber(&n.chain, value)
|
||||
if !ok {
|
||||
return n
|
||||
}
|
||||
if !(n.value != v) {
|
||||
n.chain.fail("\nexpected number not equal to:\n %v\n\nbut got:\n %v",
|
||||
v, n.value)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// EqualDelta succeeds if two numerals are within delta of each other.
|
||||
//
|
||||
// Example:
|
||||
// number := NewNumber(t, 123.0)
|
||||
// number.EqualDelta(123.2, 0.3)
|
||||
func (n *Number) EqualDelta(value, delta float64) *Number {
|
||||
if math.IsNaN(n.value) || math.IsNaN(value) || math.IsNaN(delta) {
|
||||
n.chain.fail("\nexpected number equal to:\n %v\n\nbut got:\n %v\n\ndelta:\n %v",
|
||||
value, n.value, delta)
|
||||
return n
|
||||
}
|
||||
|
||||
diff := (n.value - value)
|
||||
|
||||
if diff < -delta || diff > delta {
|
||||
n.chain.fail("\nexpected number equal to:\n %v\n\nbut got:\n %v\n\ndelta:\n %v",
|
||||
value, n.value, delta)
|
||||
return n
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// NotEqualDelta succeeds if two numerals are not within delta of each other.
|
||||
//
|
||||
// Example:
|
||||
// number := NewNumber(t, 123.0)
|
||||
// number.NotEqualDelta(123.2, 0.1)
|
||||
func (n *Number) NotEqualDelta(value, delta float64) *Number {
|
||||
if math.IsNaN(n.value) || math.IsNaN(value) || math.IsNaN(delta) {
|
||||
n.chain.fail(
|
||||
"\nexpected number not equal to:\n %v\n\nbut got:\n %v\n\ndelta:\n %v",
|
||||
value, n.value, delta)
|
||||
return n
|
||||
}
|
||||
|
||||
diff := (n.value - value)
|
||||
|
||||
if !(diff < -delta || diff > delta) {
|
||||
n.chain.fail(
|
||||
"\nexpected number not equal to:\n %v\n\nbut got:\n %v\n\ndelta:\n %v",
|
||||
value, n.value, delta)
|
||||
return n
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// Gt succeeds if number is greater than given value.
|
||||
//
|
||||
// value should have numeric type convertible to float64. Before comparison,
|
||||
// it is converted to float64.
|
||||
//
|
||||
// Example:
|
||||
// number := NewNumber(t, 123)
|
||||
// number.Gt(float64(122))
|
||||
// number.Gt(int32(122))
|
||||
func (n *Number) Gt(value interface{}) *Number {
|
||||
v, ok := canonNumber(&n.chain, value)
|
||||
if !ok {
|
||||
return n
|
||||
}
|
||||
if !(n.value > v) {
|
||||
n.chain.fail("\nexpected number > then:\n %v\n\nbut got:\n %v",
|
||||
v, n.value)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Ge succeeds if number is greater than or equal to given value.
|
||||
//
|
||||
// value should have numeric type convertible to float64. Before comparison,
|
||||
// it is converted to float64.
|
||||
//
|
||||
// Example:
|
||||
// number := NewNumber(t, 123)
|
||||
// number.Ge(float64(122))
|
||||
// number.Ge(int32(122))
|
||||
func (n *Number) Ge(value interface{}) *Number {
|
||||
v, ok := canonNumber(&n.chain, value)
|
||||
if !ok {
|
||||
return n
|
||||
}
|
||||
if !(n.value >= v) {
|
||||
n.chain.fail("\nexpected number >= then:\n %v\n\nbut got:\n %v",
|
||||
v, n.value)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Lt succeeds if number is lesser than given value.
|
||||
//
|
||||
// value should have numeric type convertible to float64. Before comparison,
|
||||
// it is converted to float64.
|
||||
//
|
||||
// Example:
|
||||
// number := NewNumber(t, 123)
|
||||
// number.Lt(float64(124))
|
||||
// number.Lt(int32(124))
|
||||
func (n *Number) Lt(value interface{}) *Number {
|
||||
v, ok := canonNumber(&n.chain, value)
|
||||
if !ok {
|
||||
return n
|
||||
}
|
||||
if !(n.value < v) {
|
||||
n.chain.fail("\nexpected number < then:\n %v\n\nbut got:\n %v",
|
||||
v, n.value)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Le succeeds if number is lesser than or equal to given value.
|
||||
//
|
||||
// value should have numeric type convertible to float64. Before comparison,
|
||||
// it is converted to float64.
|
||||
//
|
||||
// Example:
|
||||
// number := NewNumber(t, 123)
|
||||
// number.Le(float64(124))
|
||||
// number.Le(int32(124))
|
||||
func (n *Number) Le(value interface{}) *Number {
|
||||
v, ok := canonNumber(&n.chain, value)
|
||||
if !ok {
|
||||
return n
|
||||
}
|
||||
if !(n.value <= v) {
|
||||
n.chain.fail("\nexpected number <= then:\n %v\n\nbut got:\n %v",
|
||||
v, n.value)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// InRange succeeds if number is in given range [min; max].
|
||||
//
|
||||
// min and max should have numeric type convertible to float64. Before comparison,
|
||||
// they are converted to float64.
|
||||
//
|
||||
// Example:
|
||||
// number := NewNumber(t, 123)
|
||||
// number.InRange(float32(100), int32(200)) // success
|
||||
// number.InRange(100, 200) // success
|
||||
// number.InRange(123, 123) // success
|
||||
func (n *Number) InRange(min, max interface{}) *Number {
|
||||
a, ok := canonNumber(&n.chain, min)
|
||||
if !ok {
|
||||
return n
|
||||
}
|
||||
b, ok := canonNumber(&n.chain, max)
|
||||
if !ok {
|
||||
return n
|
||||
}
|
||||
if !(n.value >= a && n.value <= b) {
|
||||
n.chain.fail("\nexpected number in range:\n [%v; %v]\n\nbut got:\n %v",
|
||||
a, b, n.value)
|
||||
}
|
||||
return n
|
||||
}
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNumberFailed(t *testing.T) {
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
chain.fail("fail")
|
||||
|
||||
value := &Number{chain, 0}
|
||||
|
||||
value.chain.assertFailed(t)
|
||||
|
||||
value.Path("$").chain.assertFailed(t)
|
||||
value.Schema("")
|
||||
|
||||
value.Equal(0)
|
||||
value.NotEqual(0)
|
||||
value.Gt(0)
|
||||
value.Ge(0)
|
||||
value.Lt(0)
|
||||
value.Le(0)
|
||||
value.InRange(0, 0)
|
||||
}
|
||||
|
||||
func TestNumberGetters(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewNumber(reporter, 123.0)
|
||||
|
||||
assert.Equal(t, 123.0, value.Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, 123.0, value.Path("$").Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Schema(`{"type": "number"}`)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Schema(`{"type": "object"}`)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestNumberEqual(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewNumber(reporter, 1234)
|
||||
|
||||
assert.Equal(t, 1234, int(value.Raw()))
|
||||
|
||||
value.Equal(1234)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(4321)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(4321)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(1234)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestNumberEqualDelta(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewNumber(reporter, 1234.5)
|
||||
|
||||
value.EqualDelta(1234.3, 0.3)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.EqualDelta(1234.7, 0.3)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.EqualDelta(1234.3, 0.1)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.EqualDelta(1234.7, 0.1)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqualDelta(1234.3, 0.3)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqualDelta(1234.7, 0.3)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqualDelta(1234.3, 0.1)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqualDelta(1234.7, 0.1)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestNumberEqualNaN(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
v1 := NewNumber(reporter, math.NaN())
|
||||
v1.Equal(1234.5)
|
||||
v1.chain.assertFailed(t)
|
||||
|
||||
v2 := NewNumber(reporter, 1234.5)
|
||||
v2.Equal(math.NaN())
|
||||
v2.chain.assertFailed(t)
|
||||
|
||||
v3 := NewNumber(reporter, math.NaN())
|
||||
v3.EqualDelta(1234.0, 0.1)
|
||||
v3.chain.assertFailed(t)
|
||||
|
||||
v4 := NewNumber(reporter, 1234.5)
|
||||
v4.EqualDelta(math.NaN(), 0.1)
|
||||
v4.chain.assertFailed(t)
|
||||
|
||||
v5 := NewNumber(reporter, 1234.5)
|
||||
v5.EqualDelta(1234.5, math.NaN())
|
||||
v5.chain.assertFailed(t)
|
||||
|
||||
v6 := NewNumber(reporter, math.NaN())
|
||||
v6.NotEqualDelta(1234.0, 0.1)
|
||||
v6.chain.assertFailed(t)
|
||||
|
||||
v7 := NewNumber(reporter, 1234.5)
|
||||
v7.NotEqualDelta(math.NaN(), 0.1)
|
||||
v7.chain.assertFailed(t)
|
||||
|
||||
v8 := NewNumber(reporter, 1234.5)
|
||||
v8.NotEqualDelta(1234.5, math.NaN())
|
||||
v8.chain.assertFailed(t)
|
||||
}
|
||||
|
||||
func TestNumberGreater(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewNumber(reporter, 1234)
|
||||
|
||||
value.Gt(1234 - 1)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Gt(1234)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Ge(1234 - 1)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Ge(1234)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Ge(1234 + 1)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestNumberLesser(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewNumber(reporter, 1234)
|
||||
|
||||
value.Lt(1234 + 1)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Lt(1234)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Le(1234 + 1)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Le(1234)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Le(1234 - 1)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestNumberInRange(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewNumber(reporter, 1234)
|
||||
|
||||
value.InRange(1234, 1234)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(1234-1, 1234)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(1234, 1234+1)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(1234+1, 1234+2)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(1234-2, 1234-1)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(1234+1, 1234-1)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestNumberConvertEqual(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewNumber(reporter, 1234)
|
||||
|
||||
value.Equal(int64(1234))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(float32(1234))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal("1234")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(int64(4321))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(float32(4321))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual("4321")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestNumberConvertGreater(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewNumber(reporter, 1234)
|
||||
|
||||
value.Gt(int64(1233))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Gt(float32(1233))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Gt("1233")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Ge(int64(1233))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Ge(float32(1233))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Ge("1233")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestNumberConvertLesser(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewNumber(reporter, 1234)
|
||||
|
||||
value.Lt(int64(1235))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Lt(float32(1235))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Lt("1235")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Le(int64(1235))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Le(float32(1235))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Le("1235")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestNumberConvertInRange(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewNumber(reporter, 1234)
|
||||
|
||||
value.InRange(int64(1233), float32(1235))
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(int64(1233), "1235")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.InRange(nil, 1235)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Object provides methods to inspect attached map[string]interface{} object
|
||||
// (Go representation of JSON object).
|
||||
type Object struct {
|
||||
chain chain
|
||||
value map[string]interface{}
|
||||
}
|
||||
|
||||
// NewObject returns a new Object given a reporter used to report failures
|
||||
// and value to be inspected.
|
||||
//
|
||||
// Both reporter and value should not be nil. If value is nil, failure is
|
||||
// reported.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
||||
func NewObject(reporter Reporter, value map[string]interface{}) *Object {
|
||||
chain := makeChain(reporter)
|
||||
if value == nil {
|
||||
chain.fail("expected non-nil map value")
|
||||
} else {
|
||||
value, _ = canonMap(&chain, value)
|
||||
}
|
||||
return &Object{chain, value}
|
||||
}
|
||||
|
||||
// Raw returns underlying value attached to Object.
|
||||
// This is the value originally passed to NewObject, converted to canonical form.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
||||
// assert.Equal(t, map[string]interface{}{"foo": 123.0}, object.Raw())
|
||||
func (o *Object) Raw() map[string]interface{} {
|
||||
return o.value
|
||||
}
|
||||
|
||||
// Path is similar to Value.Path.
|
||||
func (o *Object) Path(path string) *Value {
|
||||
return getPath(&o.chain, o.value, path)
|
||||
}
|
||||
|
||||
// Schema is similar to Value.Schema.
|
||||
func (o *Object) Schema(schema interface{}) *Object {
|
||||
checkSchema(&o.chain, o.value, schema)
|
||||
return o
|
||||
}
|
||||
|
||||
// Keys returns a new Array object that may be used to inspect objects keys.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123, "bar": 456})
|
||||
// object.Keys().ContainsOnly("foo", "bar")
|
||||
func (o *Object) Keys() *Array {
|
||||
keys := []interface{}{}
|
||||
for k := range o.value {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return &Array{o.chain, keys}
|
||||
}
|
||||
|
||||
// Values returns a new Array object that may be used to inspect objects values.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123, "bar": 456})
|
||||
// object.Values().ContainsOnly(123, 456)
|
||||
func (o *Object) Values() *Array {
|
||||
values := []interface{}{}
|
||||
for _, v := range o.value {
|
||||
values = append(values, v)
|
||||
}
|
||||
return &Array{o.chain, values}
|
||||
}
|
||||
|
||||
// Value returns a new Value object that may be used to inspect single value
|
||||
// for given key.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
||||
// object.Value("foo").Number().Equal(123)
|
||||
func (o *Object) Value(key string) *Value {
|
||||
value, ok := o.value[key]
|
||||
if !ok {
|
||||
o.chain.fail("\nexpected object containing key '%s', but got:\n%s",
|
||||
key, dumpValue(o.value))
|
||||
return &Value{o.chain, nil}
|
||||
}
|
||||
return &Value{o.chain, value}
|
||||
}
|
||||
|
||||
// Empty succeeds if object is empty.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{})
|
||||
// object.Empty()
|
||||
func (o *Object) Empty() *Object {
|
||||
return o.Equal(map[string]interface{}{})
|
||||
}
|
||||
|
||||
// NotEmpty succeeds if object is non-empty.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
||||
// object.NotEmpty()
|
||||
func (o *Object) NotEmpty() *Object {
|
||||
return o.NotEqual(map[string]interface{}{})
|
||||
}
|
||||
|
||||
// Equal succeeds if object is equal to another object.
|
||||
// Before comparison, both objects are converted to canonical form.
|
||||
//
|
||||
// value should map[string]interface{} or struct.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
||||
// object.Equal(map[string]interface{}{"foo": 123})
|
||||
func (o *Object) Equal(value interface{}) *Object {
|
||||
expected, ok := canonMap(&o.chain, value)
|
||||
if !ok {
|
||||
return o
|
||||
}
|
||||
if !reflect.DeepEqual(expected, o.value) {
|
||||
o.chain.fail("\nexpected object equal to:\n%s\n\nbut got:\n%s\n\ndiff:\n%s",
|
||||
dumpValue(expected),
|
||||
dumpValue(o.value),
|
||||
diffValues(expected, o.value))
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// NotEqual succeeds if object is not equal to another object.
|
||||
// Before comparison, both objects are converted to canonical form.
|
||||
//
|
||||
// value should map[string]interface{} or struct.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
||||
// object.Equal(map[string]interface{}{"bar": 123})
|
||||
func (o *Object) NotEqual(v interface{}) *Object {
|
||||
expected, ok := canonMap(&o.chain, v)
|
||||
if !ok {
|
||||
return o
|
||||
}
|
||||
if reflect.DeepEqual(expected, o.value) {
|
||||
o.chain.fail("\nexpected object not equal to:\n%s",
|
||||
dumpValue(expected))
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// ContainsKey succeeds if object contains given key.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
||||
// object.ContainsKey("foo")
|
||||
func (o *Object) ContainsKey(key string) *Object {
|
||||
if !o.containsKey(key) {
|
||||
o.chain.fail("\nexpected object containing key '%s', but got:\n%s",
|
||||
key, dumpValue(o.value))
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// NotContainsKey succeeds if object doesn't contain given key.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
||||
// object.NotContainsKey("bar")
|
||||
func (o *Object) NotContainsKey(key string) *Object {
|
||||
if o.containsKey(key) {
|
||||
o.chain.fail(
|
||||
"\nexpected object not containing key '%s', but got:\n%s", key,
|
||||
dumpValue(o.value))
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// ContainsMap succeeds if object contains given sub-object.
|
||||
// Before comparison, both objects are converted to canonical form.
|
||||
//
|
||||
// value should map[string]interface{} or struct.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{
|
||||
// "foo": 123,
|
||||
// "bar": []interface{}{"x", "y"},
|
||||
// "bar": map[string]interface{}{
|
||||
// "a": true,
|
||||
// "b": false,
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// object.ContainsMap(map[string]interface{}{ // success
|
||||
// "foo": 123,
|
||||
// "bar": map[string]interface{}{
|
||||
// "a": true,
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// object.ContainsMap(map[string]interface{}{ // failure
|
||||
// "foo": 123,
|
||||
// "qux": 456,
|
||||
// })
|
||||
//
|
||||
// object.ContainsMap(map[string]interface{}{ // failure, slices should match exactly
|
||||
// "bar": []interface{}{"x"},
|
||||
// })
|
||||
func (o *Object) ContainsMap(value interface{}) *Object {
|
||||
if !o.containsMap(value) {
|
||||
o.chain.fail("\nexpected object containing sub-object:\n%s\n\nbut got:\n%s",
|
||||
dumpValue(value), dumpValue(o.value))
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// NotContainsMap succeeds if object doesn't contain given sub-object exactly.
|
||||
// Before comparison, both objects are converted to canonical form.
|
||||
//
|
||||
// value should map[string]interface{} or struct.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123, "bar": 456})
|
||||
// object.NotContainsMap(map[string]interface{}{"foo": 123, "bar": "no-no-no"})
|
||||
func (o *Object) NotContainsMap(value interface{}) *Object {
|
||||
if o.containsMap(value) {
|
||||
o.chain.fail("\nexpected object not containing sub-object:\n%s\n\nbut got:\n%s",
|
||||
dumpValue(value), dumpValue(o.value))
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// ValueEqual succeeds if object's value for given key is equal to given value.
|
||||
// Before comparison, both values are converted to canonical form.
|
||||
//
|
||||
// value should map[string]interface{} or struct.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
||||
// object.ValueEqual("foo", 123)
|
||||
func (o *Object) ValueEqual(key string, value interface{}) *Object {
|
||||
if !o.containsKey(key) {
|
||||
o.chain.fail("\nexpected object containing key '%s', but got:\n%s",
|
||||
key, dumpValue(o.value))
|
||||
return o
|
||||
}
|
||||
expected, ok := canonValue(&o.chain, value)
|
||||
if !ok {
|
||||
return o
|
||||
}
|
||||
if !reflect.DeepEqual(expected, o.value[key]) {
|
||||
o.chain.fail(
|
||||
"\nexpected value for key '%s' equal to:\n%s\n\nbut got:\n%s\n\ndiff:\n%s",
|
||||
key,
|
||||
dumpValue(expected),
|
||||
dumpValue(o.value[key]),
|
||||
diffValues(expected, o.value[key]))
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// ValueNotEqual succeeds if object's value for given key is not equal to given value.
|
||||
// Before comparison, both values are converted to canonical form.
|
||||
//
|
||||
// value should map[string]interface{} or struct.
|
||||
//
|
||||
// If object doesn't contain any value for given key, failure is reported.
|
||||
//
|
||||
// Example:
|
||||
// object := NewObject(t, map[string]interface{}{"foo": 123})
|
||||
// object.ValueNotEqual("foo", "bad value") // success
|
||||
// object.ValueNotEqual("bar", "bad value") // failure! (key is missing)
|
||||
func (o *Object) ValueNotEqual(key string, value interface{}) *Object {
|
||||
if !o.containsKey(key) {
|
||||
o.chain.fail("\nexpected object containing key '%s', but got:\n%s",
|
||||
key, dumpValue(o.value))
|
||||
return o
|
||||
}
|
||||
expected, ok := canonValue(&o.chain, value)
|
||||
if !ok {
|
||||
return o
|
||||
}
|
||||
if reflect.DeepEqual(expected, o.value[key]) {
|
||||
o.chain.fail("\nexpected value for key '%s' not equal to:\n%s",
|
||||
key, dumpValue(expected))
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *Object) containsKey(key string) bool {
|
||||
for k := range o.value {
|
||||
if k == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *Object) containsMap(sm interface{}) bool {
|
||||
submap, ok := canonMap(&o.chain, sm)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return checkContainsMap(o.value, submap)
|
||||
}
|
||||
|
||||
func checkContainsMap(outer, inner map[string]interface{}) bool {
|
||||
for k, iv := range inner {
|
||||
ov, ok := outer[k]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if ovm, ok := ov.(map[string]interface{}); ok {
|
||||
if ivm, ok := iv.(map[string]interface{}); ok {
|
||||
if !checkContainsMap(ovm, ivm) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(ov, iv) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+655
@@ -0,0 +1,655 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestObjectFailed(t *testing.T) {
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
chain.fail("fail")
|
||||
|
||||
value := &Object{chain, nil}
|
||||
|
||||
value.chain.assertFailed(t)
|
||||
|
||||
value.Path("$").chain.assertFailed(t)
|
||||
value.Schema("")
|
||||
|
||||
assert.False(t, value.Keys() == nil)
|
||||
assert.False(t, value.Values() == nil)
|
||||
assert.False(t, value.Value("foo") == nil)
|
||||
|
||||
value.Keys().chain.assertFailed(t)
|
||||
value.Values().chain.assertFailed(t)
|
||||
value.Value("foo").chain.assertFailed(t)
|
||||
|
||||
value.Empty()
|
||||
value.NotEmpty()
|
||||
value.Equal(nil)
|
||||
value.NotEqual(nil)
|
||||
value.ContainsKey("foo")
|
||||
value.NotContainsKey("foo")
|
||||
value.ContainsMap(nil)
|
||||
value.NotContainsMap(nil)
|
||||
value.ValueEqual("foo", nil)
|
||||
value.ValueNotEqual("foo", nil)
|
||||
}
|
||||
|
||||
func TestObjectGetters(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
m := map[string]interface{}{
|
||||
"foo": 123.0,
|
||||
"bar": []interface{}{"456", 789.0},
|
||||
"baz": map[string]interface{}{
|
||||
"a": "b",
|
||||
},
|
||||
}
|
||||
|
||||
value := NewObject(reporter, m)
|
||||
|
||||
keys := []interface{}{"foo", "bar", "baz"}
|
||||
|
||||
values := []interface{}{
|
||||
123.0,
|
||||
[]interface{}{"456", 789.0},
|
||||
map[string]interface{}{
|
||||
"a": "b",
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, m, value.Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, m, value.Path("$").Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Schema(`{"type": "object"}`)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Schema(`{"type": "array"}`)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Keys().ContainsOnly(keys...)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Values().ContainsOnly(values...)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, m["foo"], value.Value("foo").Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, m["bar"], value.Value("bar").Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, m["baz"], value.Value("baz").Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, nil, value.Value("BAZ").Raw())
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectEmpty(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value1 := NewObject(reporter, nil)
|
||||
|
||||
_ = value1
|
||||
value1.chain.assertFailed(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value2 := NewObject(reporter, map[string]interface{}{})
|
||||
|
||||
value2.Empty()
|
||||
value2.chain.assertOK(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value2.NotEmpty()
|
||||
value2.chain.assertFailed(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value3 := NewObject(reporter, map[string]interface{}{"": nil})
|
||||
|
||||
value3.Empty()
|
||||
value3.chain.assertFailed(t)
|
||||
value3.chain.reset()
|
||||
|
||||
value3.NotEmpty()
|
||||
value3.chain.assertOK(t)
|
||||
value3.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectEqualEmpty(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{})
|
||||
|
||||
assert.Equal(t, map[string]interface{}{}, value.Raw())
|
||||
|
||||
value.Equal(map[string]interface{}{})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(map[string]interface{}{})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(map[string]interface{}{"": nil})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(map[string]interface{}{"": nil})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectEqual(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{"foo": 123.0})
|
||||
|
||||
assert.Equal(t, map[string]interface{}{"foo": 123.0}, value.Raw())
|
||||
|
||||
value.Equal(map[string]interface{}{})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(map[string]interface{}{})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(map[string]interface{}{"FOO": 123.0})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(map[string]interface{}{"FOO": 123.0})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(map[string]interface{}{"foo": 456.0})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(map[string]interface{}{"foo": 456.0})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(map[string]interface{}{"foo": 123.0})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(map[string]interface{}{"foo": 123.0})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(nil)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(nil)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectEqualStruct(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{
|
||||
"foo": 123,
|
||||
"bar": map[string]interface{}{
|
||||
"baz": []interface{}{true, false},
|
||||
},
|
||||
})
|
||||
|
||||
type (
|
||||
Bar struct {
|
||||
Baz []bool `json:"baz"`
|
||||
}
|
||||
|
||||
S struct {
|
||||
Foo int `json:"foo"`
|
||||
Bar Bar `json:"bar"`
|
||||
}
|
||||
)
|
||||
|
||||
s := S{
|
||||
Foo: 123,
|
||||
Bar: Bar{
|
||||
Baz: []bool{true, false},
|
||||
},
|
||||
}
|
||||
|
||||
value.Equal(s)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(s)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(S{})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(S{})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectContainsKey(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{"foo": 123, "bar": ""})
|
||||
|
||||
value.ContainsKey("foo")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsKey("foo")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsKey("bar")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsKey("bar")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsKey("BAR")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsKey("BAR")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectContainsMapSuccess(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{
|
||||
"foo": 123,
|
||||
"bar": []interface{}{"456", 789},
|
||||
"baz": map[string]interface{}{
|
||||
"a": map[string]interface{}{
|
||||
"b": 333,
|
||||
"c": 444,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
submap1 := map[string]interface{}{
|
||||
"foo": 123,
|
||||
"bar": []interface{}{"456", 789},
|
||||
}
|
||||
|
||||
value.ContainsMap(submap1)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsMap(submap1)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
submap2 := map[string]interface{}{
|
||||
"bar": []interface{}{"456", 789},
|
||||
"baz": map[string]interface{}{
|
||||
"a": map[string]interface{}{
|
||||
"c": 444,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
value.ContainsMap(submap2)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsMap(submap2)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectContainsMapFailed(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{
|
||||
"foo": 123,
|
||||
"bar": []interface{}{"456", 789},
|
||||
"baz": map[string]interface{}{
|
||||
"a": map[string]interface{}{
|
||||
"b": 333,
|
||||
"c": 444,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
submap1 := map[string]interface{}{
|
||||
"foo": 123,
|
||||
"qux": 456,
|
||||
}
|
||||
|
||||
value.ContainsMap(submap1)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsMap(submap1)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
submap2 := map[string]interface{}{
|
||||
"foo": 123,
|
||||
"bar": []interface{}{"456", "789"},
|
||||
}
|
||||
|
||||
value.ContainsMap(submap2)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsMap(submap2)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
submap3 := map[string]interface{}{
|
||||
"baz": map[string]interface{}{
|
||||
"a": map[string]interface{}{
|
||||
"b": "333",
|
||||
"c": 444,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
value.ContainsMap(submap3)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsMap(submap3)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsMap(nil)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsMap(nil)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectContainsMapStruct(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{
|
||||
"foo": 123,
|
||||
"bar": []interface{}{"456", 789},
|
||||
"baz": map[string]interface{}{
|
||||
"a": map[string]interface{}{
|
||||
"b": 333,
|
||||
"c": 444,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
type (
|
||||
A struct {
|
||||
B int `json:"b"`
|
||||
}
|
||||
|
||||
Baz struct {
|
||||
A A `json:"a"`
|
||||
}
|
||||
|
||||
S struct {
|
||||
Foo int `json:"foo"`
|
||||
Baz Baz `json:"baz"`
|
||||
}
|
||||
)
|
||||
|
||||
submap := S{
|
||||
Foo: 123,
|
||||
Baz: Baz{
|
||||
A: A{
|
||||
B: 333,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
value.ContainsMap(submap)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsMap(submap)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsMap(S{})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsMap(S{})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectValueEqual(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{
|
||||
"foo": 123,
|
||||
"bar": []interface{}{"456", 789},
|
||||
"baz": map[string]interface{}{
|
||||
"a": "b",
|
||||
},
|
||||
})
|
||||
|
||||
value.ValueEqual("foo", 123)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueNotEqual("foo", 123)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueEqual("bar", []interface{}{"456", 789})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueNotEqual("bar", []interface{}{"456", 789})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueEqual("baz", map[string]interface{}{"a": "b"})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueNotEqual("baz", map[string]interface{}{"a": "b"})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueEqual("baz", func() {})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueNotEqual("baz", func() {})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueEqual("BAZ", 777)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueNotEqual("BAZ", 777)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectValueEqualStruct(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{
|
||||
"foo": 123,
|
||||
"bar": []interface{}{"456", 789},
|
||||
"baz": map[string]interface{}{
|
||||
"a": map[string]interface{}{
|
||||
"b": 333,
|
||||
"c": 444,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
type (
|
||||
A struct {
|
||||
B int `json:"b"`
|
||||
C int `json:"c"`
|
||||
}
|
||||
|
||||
Baz struct {
|
||||
A A `json:"a"`
|
||||
}
|
||||
)
|
||||
|
||||
baz := Baz{
|
||||
A: A{
|
||||
B: 333,
|
||||
C: 444,
|
||||
},
|
||||
}
|
||||
|
||||
value.ValueEqual("baz", baz)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueNotEqual("baz", baz)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueEqual("baz", Baz{})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueNotEqual("baz", Baz{})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectConvertEqual(t *testing.T) {
|
||||
type (
|
||||
myMap map[string]interface{}
|
||||
myInt int
|
||||
)
|
||||
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{"foo": 123})
|
||||
|
||||
value.Equal(map[string]interface{}{"foo": "123"})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(map[string]interface{}{"foo": "123"})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(map[string]interface{}{"foo": 123.0})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(map[string]interface{}{"foo": 123.0})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(map[string]interface{}{"foo": 123})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(map[string]interface{}{"foo": 123})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal(myMap{"foo": myInt(123)})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual(myMap{"foo": myInt(123)})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectConvertContainsMap(t *testing.T) {
|
||||
type (
|
||||
myArray []interface{}
|
||||
myMap map[string]interface{}
|
||||
myInt int
|
||||
)
|
||||
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{
|
||||
"foo": 123,
|
||||
"bar": []interface{}{"456", 789},
|
||||
"baz": map[string]interface{}{
|
||||
"a": "b",
|
||||
},
|
||||
})
|
||||
|
||||
submap := myMap{
|
||||
"foo": myInt(123),
|
||||
"bar": myArray{"456", myInt(789)},
|
||||
}
|
||||
|
||||
value.ContainsMap(submap)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsMap(submap)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestObjectConvertValueEqual(t *testing.T) {
|
||||
type (
|
||||
myArray []interface{}
|
||||
myMap map[string]interface{}
|
||||
myInt int
|
||||
)
|
||||
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewObject(reporter, map[string]interface{}{
|
||||
"foo": 123,
|
||||
"bar": []interface{}{"456", 789},
|
||||
"baz": map[string]interface{}{
|
||||
"a": "b",
|
||||
},
|
||||
})
|
||||
|
||||
value.ValueEqual("bar", myArray{"456", myInt(789)})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueNotEqual("bar", myArray{"456", myInt(789)})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueEqual("baz", myMap{"a": "b"})
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ValueNotEqual("baz", myMap{"a": "b"})
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/moul/http2curl"
|
||||
)
|
||||
|
||||
// CompactPrinter implements Printer. It prints requests in compact form.
|
||||
type CompactPrinter struct {
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// NewCompactPrinter returns a new CompactPrinter given a logger.
|
||||
func NewCompactPrinter(logger Logger) CompactPrinter {
|
||||
return CompactPrinter{logger}
|
||||
}
|
||||
|
||||
// Request implements Printer.Request.
|
||||
func (p CompactPrinter) Request(req *http.Request) {
|
||||
if req != nil {
|
||||
p.logger.Logf("%s %s", req.Method, req.URL)
|
||||
}
|
||||
}
|
||||
|
||||
// Response implements Printer.Response.
|
||||
func (CompactPrinter) Response(*http.Response, time.Duration) {
|
||||
}
|
||||
|
||||
// DebugPrinter implements Printer. Uses net/http/httputil to dump
|
||||
// both requests and responses.
|
||||
type DebugPrinter struct {
|
||||
logger Logger
|
||||
body bool
|
||||
}
|
||||
|
||||
// NewDebugPrinter returns a new DebugPrinter given a logger and body
|
||||
// flag. If body is true, request and response body is also printed.
|
||||
func NewDebugPrinter(logger Logger, body bool) DebugPrinter {
|
||||
return DebugPrinter{logger, body}
|
||||
}
|
||||
|
||||
// Request implements Printer.Request.
|
||||
func (p DebugPrinter) Request(req *http.Request) {
|
||||
if req == nil {
|
||||
return
|
||||
}
|
||||
|
||||
dump, err := httputil.DumpRequest(req, p.body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.logger.Logf("%s", dump)
|
||||
}
|
||||
|
||||
// Response implements Printer.Response.
|
||||
func (p DebugPrinter) Response(resp *http.Response, duration time.Duration) {
|
||||
if resp == nil {
|
||||
return
|
||||
}
|
||||
|
||||
dump, err := httputil.DumpResponse(resp, p.body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
text := strings.Replace(string(dump), "\r\n", "\n", -1)
|
||||
lines := strings.SplitN(text, "\n", 2)
|
||||
|
||||
p.logger.Logf("%s %s\n%s", lines[0], duration, lines[1])
|
||||
}
|
||||
|
||||
// CurlPrinter implements Printer. Uses http2curl to dump requests as
|
||||
// curl commands.
|
||||
type CurlPrinter struct {
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// NewCurlPrinter returns a new CurlPrinter given a logger.
|
||||
func NewCurlPrinter(logger Logger) CurlPrinter {
|
||||
return CurlPrinter{logger}
|
||||
}
|
||||
|
||||
// Request implements Printer.Request.
|
||||
func (p CurlPrinter) Request(req *http.Request) {
|
||||
if req != nil {
|
||||
cmd, err := http2curl.GetCurlCommand(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.logger.Logf("%s", cmd.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Response implements Printer.Response.
|
||||
func (CurlPrinter) Response(*http.Response, time.Duration) {
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompactPrinter(t *testing.T) {
|
||||
printer := NewCompactPrinter(t)
|
||||
|
||||
body1 := bytes.NewBufferString("body1")
|
||||
body2 := bytes.NewBufferString("body2")
|
||||
|
||||
req1, _ := http.NewRequest("GET", "http://example.com", body1)
|
||||
req2, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
|
||||
printer.Request(req1)
|
||||
printer.Request(req2)
|
||||
printer.Request(nil)
|
||||
|
||||
printer.Response(&http.Response{Body: ioutil.NopCloser(body2)}, 0)
|
||||
printer.Response(&http.Response{}, 0)
|
||||
printer.Response(nil, 0)
|
||||
}
|
||||
|
||||
func TestDebugPrinter(t *testing.T) {
|
||||
printer := NewDebugPrinter(t, true)
|
||||
|
||||
body1 := bytes.NewBufferString("body1")
|
||||
body2 := bytes.NewBufferString("body2")
|
||||
|
||||
req1, _ := http.NewRequest("GET", "http://example.com", body1)
|
||||
req2, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
|
||||
printer.Request(req1)
|
||||
printer.Request(req2)
|
||||
printer.Request(nil)
|
||||
|
||||
printer.Response(&http.Response{Body: ioutil.NopCloser(body2)}, 0)
|
||||
printer.Response(&http.Response{}, 0)
|
||||
printer.Response(nil, 0)
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// AssertReporter implements Reporter interface using `testify/assert'
|
||||
// package. Failures are non-fatal with this reporter.
|
||||
type AssertReporter struct {
|
||||
backend *assert.Assertions
|
||||
}
|
||||
|
||||
// NewAssertReporter returns a new AssertReporter object.
|
||||
func NewAssertReporter(t assert.TestingT) *AssertReporter {
|
||||
return &AssertReporter{assert.New(t)}
|
||||
}
|
||||
|
||||
// Errorf implements Reporter.Errorf.
|
||||
func (r *AssertReporter) Errorf(message string, args ...interface{}) {
|
||||
r.backend.Fail(fmt.Sprintf(message, args...))
|
||||
}
|
||||
|
||||
// RequireReporter implements Reporter interface using `testify/require'
|
||||
// package. Failures fatal with this reporter.
|
||||
type RequireReporter struct {
|
||||
backend *require.Assertions
|
||||
}
|
||||
|
||||
// NewRequireReporter returns a new RequireReporter object.
|
||||
func NewRequireReporter(t require.TestingT) *RequireReporter {
|
||||
return &RequireReporter{require.New(t)}
|
||||
}
|
||||
|
||||
// Errorf implements Reporter.Errorf.
|
||||
func (r *RequireReporter) Errorf(message string, args ...interface{}) {
|
||||
r.backend.FailNow(fmt.Sprintf(message, args...))
|
||||
}
|
||||
+912
@@ -0,0 +1,912 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ajg/form"
|
||||
"github.com/fatih/structs"
|
||||
"github.com/gavv/monotime"
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/imkira/go-interpol"
|
||||
)
|
||||
|
||||
// Request provides methods to incrementally build http.Request object,
|
||||
// send it, and receive response.
|
||||
type Request struct {
|
||||
config Config
|
||||
chain chain
|
||||
http *http.Request
|
||||
path string
|
||||
query url.Values
|
||||
form url.Values
|
||||
formbuf *bytes.Buffer
|
||||
multipart *multipart.Writer
|
||||
forcetype bool
|
||||
typesetter string
|
||||
bodysetter string
|
||||
}
|
||||
|
||||
// NewRequest returns a new Request object.
|
||||
//
|
||||
// method defines the HTTP method (GET, POST, PUT, etc.). path defines url path.
|
||||
//
|
||||
// Simple interpolation is allowed for {named} parameters in path:
|
||||
// - if pathargs is given, it's used to substitute first len(pathargs) parameters,
|
||||
// regardless of their names
|
||||
// - if WithPath() or WithPathObject() is called, it's used to substitute given
|
||||
// parameters by name
|
||||
//
|
||||
// For example:
|
||||
// req := NewRequest(config, "POST", "/repos/{user}/{repo}", "gavv", "httpexpect")
|
||||
// // path will be "/repos/gavv/httpexpect"
|
||||
//
|
||||
// Or:
|
||||
// req := NewRequest(config, "POST", "/repos/{user}/{repo}")
|
||||
// req.WithPath("user", "gavv")
|
||||
// req.WithPath("repo", "httpexpect")
|
||||
// // path will be "/repos/gavv/httpexpect"
|
||||
//
|
||||
// After interpolation, path is urlencoded and appended to Config.BaseURL,
|
||||
// separated by slash. If BaseURL ends with a slash and path (after interpolation)
|
||||
// starts with a slash, only single slash is inserted.
|
||||
func NewRequest(config Config, method, path string, pathargs ...interface{}) *Request {
|
||||
if config.RequestFactory == nil {
|
||||
panic("config.RequestFactory == nil")
|
||||
}
|
||||
|
||||
if config.Client == nil {
|
||||
panic("config.Client == nil")
|
||||
}
|
||||
|
||||
chain := makeChain(config.Reporter)
|
||||
|
||||
n := 0
|
||||
path, err := interpol.WithFunc(path, func(k string, w io.Writer) error {
|
||||
if n < len(pathargs) {
|
||||
if pathargs[n] == nil {
|
||||
chain.fail(
|
||||
"\nunexpected nil argument for url path format string:\n"+
|
||||
" Request(\"%s\", %v...)", method, pathargs)
|
||||
} else {
|
||||
w.Write([]byte(fmt.Sprint(pathargs[n])))
|
||||
}
|
||||
} else {
|
||||
w.Write([]byte("{"))
|
||||
w.Write([]byte(k))
|
||||
w.Write([]byte("}"))
|
||||
}
|
||||
n++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
chain.fail(err.Error())
|
||||
}
|
||||
|
||||
hr, err := config.RequestFactory.NewRequest(method, config.BaseURL, nil)
|
||||
if err != nil {
|
||||
chain.fail(err.Error())
|
||||
}
|
||||
|
||||
return &Request{
|
||||
config: config,
|
||||
chain: chain,
|
||||
path: path,
|
||||
http: hr,
|
||||
}
|
||||
}
|
||||
|
||||
// WithClient sets client.
|
||||
//
|
||||
// The new client overwrites Config.Client. It will be use once to send the
|
||||
// request and receive a response.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "GET", "/path")
|
||||
// req.WithClient(&http.Client{
|
||||
// Transport: &http.Transport{
|
||||
// DisableCompression: true,
|
||||
// },
|
||||
// })
|
||||
func (r *Request) WithClient(client Client) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
if client == nil {
|
||||
r.chain.fail("\nunexpected nil client in WithClient")
|
||||
return r
|
||||
}
|
||||
r.config.Client = client
|
||||
return r
|
||||
}
|
||||
|
||||
// WithHandler configures client to invoke the given handler directly.
|
||||
//
|
||||
// If Config.Client is http.Client, then only its Transport field is overwritten
|
||||
// because the client may contain some state shared among requests like a cookie
|
||||
// jar. Otherwise, the whole client is overwritten with a new client.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "GET", "/path")
|
||||
// req.WithHandler(myServer.someHandler)
|
||||
func (r *Request) WithHandler(handler http.Handler) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
if handler == nil {
|
||||
r.chain.fail("\nunexpected nil handler in WithHandler")
|
||||
return r
|
||||
}
|
||||
if client, ok := r.config.Client.(*http.Client); ok {
|
||||
client.Transport = NewBinder(handler)
|
||||
} else {
|
||||
r.config.Client = &http.Client{
|
||||
Transport: NewBinder(handler),
|
||||
Jar: NewJar(),
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// WithPath substitutes named parameters in url path.
|
||||
//
|
||||
// value is converted to string using fmt.Sprint(). If there is no named
|
||||
// parameter '{key}' in url path, failure is reported.
|
||||
//
|
||||
// Named parameters are case-insensitive.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "POST", "/repos/{user}/{repo}")
|
||||
// req.WithPath("user", "gavv")
|
||||
// req.WithPath("repo", "httpexpect")
|
||||
// // path will be "/repos/gavv/httpexpect"
|
||||
func (r *Request) WithPath(key string, value interface{}) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
ok := false
|
||||
path, err := interpol.WithFunc(r.path, func(k string, w io.Writer) error {
|
||||
if strings.EqualFold(k, key) {
|
||||
if value == nil {
|
||||
r.chain.fail(
|
||||
"\nunexpected nil argument for url path format string:\n"+
|
||||
" WithPath(\"%s\", %v)", key, value)
|
||||
} else {
|
||||
w.Write([]byte(fmt.Sprint(value)))
|
||||
ok = true
|
||||
}
|
||||
} else {
|
||||
w.Write([]byte("{"))
|
||||
w.Write([]byte(k))
|
||||
w.Write([]byte("}"))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
r.path = path
|
||||
} else {
|
||||
r.chain.fail(err.Error())
|
||||
return r
|
||||
}
|
||||
if !ok {
|
||||
r.chain.fail("\nunexpected key for url path format string:\n"+
|
||||
" WithPath(\"%s\", %v)\n\npath:\n %q",
|
||||
key, value, r.path)
|
||||
return r
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// WithPathObject substitutes multiple named parameters in url path.
|
||||
//
|
||||
// object should be map or struct. If object is struct, it's converted
|
||||
// to map using https://github.com/fatih/structs. Structs may contain
|
||||
// "path" struct tag, similar to "json" struct tag for json.Marshal().
|
||||
//
|
||||
// Each map value is converted to string using fmt.Sprint(). If there
|
||||
// is no named parameter for some map '{key}' in url path, failure is
|
||||
// reported.
|
||||
//
|
||||
// Named parameters are case-insensitive.
|
||||
//
|
||||
// Example:
|
||||
// type MyPath struct {
|
||||
// Login string `path:"user"`
|
||||
// Repo string
|
||||
// }
|
||||
//
|
||||
// req := NewRequest(config, "POST", "/repos/{user}/{repo}")
|
||||
// req.WithPathObject(MyPath{"gavv", "httpexpect"})
|
||||
// // path will be "/repos/gavv/httpexpect"
|
||||
//
|
||||
// req := NewRequest(config, "POST", "/repos/{user}/{repo}")
|
||||
// req.WithPathObject(map[string]string{"user": "gavv", "repo": "httpexpect"})
|
||||
// // path will be "/repos/gavv/httpexpect"
|
||||
func (r *Request) WithPathObject(object interface{}) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
if object == nil {
|
||||
return r
|
||||
}
|
||||
var (
|
||||
m map[string]interface{}
|
||||
ok bool
|
||||
)
|
||||
if reflect.Indirect(reflect.ValueOf(object)).Kind() == reflect.Struct {
|
||||
s := structs.New(object)
|
||||
s.TagName = "path"
|
||||
m = s.Map()
|
||||
} else {
|
||||
m, ok = canonMap(&r.chain, object)
|
||||
if !ok {
|
||||
return r
|
||||
}
|
||||
}
|
||||
for k, v := range m {
|
||||
r.WithPath(k, v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// WithQuery adds query parameter to request URL.
|
||||
//
|
||||
// value is converted to string using fmt.Sprint() and urlencoded.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithQuery("a", 123)
|
||||
// req.WithQuery("b", "foo")
|
||||
// // URL is now http://example.com/path?a=123&b=foo
|
||||
func (r *Request) WithQuery(key string, value interface{}) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
if r.query == nil {
|
||||
r.query = make(url.Values)
|
||||
}
|
||||
r.query.Add(key, fmt.Sprint(value))
|
||||
return r
|
||||
}
|
||||
|
||||
// WithQueryObject adds multiple query parameters to request URL.
|
||||
//
|
||||
// object is converted to query string using github.com/google/go-querystring
|
||||
// if it's a struct or pointer to struct, or github.com/ajg/form otherwise.
|
||||
//
|
||||
// Various object types are supported. Structs may contain "url" struct tag,
|
||||
// similar to "json" struct tag for json.Marshal().
|
||||
//
|
||||
// Example:
|
||||
// type MyURL struct {
|
||||
// A int `url:"a"`
|
||||
// B string `url:"b"`
|
||||
// }
|
||||
//
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithQueryObject(MyURL{A: 123, B: "foo"})
|
||||
// // URL is now http://example.com/path?a=123&b=foo
|
||||
//
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithQueryObject(map[string]interface{}{"a": 123, "b": "foo"})
|
||||
// // URL is now http://example.com/path?a=123&b=foo
|
||||
func (r *Request) WithQueryObject(object interface{}) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
if object == nil {
|
||||
return r
|
||||
}
|
||||
var (
|
||||
q url.Values
|
||||
err error
|
||||
)
|
||||
if reflect.Indirect(reflect.ValueOf(object)).Kind() == reflect.Struct {
|
||||
q, err = query.Values(object)
|
||||
if err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return r
|
||||
}
|
||||
} else {
|
||||
q, err = form.EncodeToValues(object)
|
||||
if err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return r
|
||||
}
|
||||
}
|
||||
if r.query == nil {
|
||||
r.query = make(url.Values)
|
||||
}
|
||||
for k, v := range q {
|
||||
r.query[k] = append(r.query[k], v...)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// WithQueryString parses given query string and adds it to request URL.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithQuery("a", 11)
|
||||
// req.WithQueryString("b=22&c=33")
|
||||
// // URL is now http://example.com/path?a=11&bb=22&c=33
|
||||
func (r *Request) WithQueryString(query string) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
v, err := url.ParseQuery(query)
|
||||
if err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return r
|
||||
}
|
||||
if r.query == nil {
|
||||
r.query = make(url.Values)
|
||||
}
|
||||
for k, v := range v {
|
||||
r.query[k] = append(r.query[k], v...)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// WithURL sets request URL.
|
||||
//
|
||||
// This URL overwrites Config.BaseURL. Request path passed to NewRequest()
|
||||
// is appended to this URL, separated by slash if necessary.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "/path")
|
||||
// req.WithURL("http://example.com")
|
||||
// // URL is now http://example.com/path
|
||||
func (r *Request) WithURL(urlStr string) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
if u, err := url.Parse(urlStr); err == nil {
|
||||
r.http.URL = u
|
||||
} else {
|
||||
r.chain.fail(err.Error())
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// WithHeaders adds given headers to request.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithHeaders(map[string]string{
|
||||
// "Content-Type": "application/json",
|
||||
// })
|
||||
func (r *Request) WithHeaders(headers map[string]string) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
for k, v := range headers {
|
||||
r.WithHeader(k, v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// WithHeader adds given single header to request.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithHeader("Content-Type": "application/json")
|
||||
func (r *Request) WithHeader(k, v string) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
switch http.CanonicalHeaderKey(k) {
|
||||
case "Host":
|
||||
r.http.Host = v
|
||||
case "Content-Type":
|
||||
if !r.forcetype {
|
||||
delete(r.http.Header, "Content-Type")
|
||||
}
|
||||
r.forcetype = true
|
||||
r.typesetter = "WithHeader"
|
||||
r.http.Header.Add(k, v)
|
||||
default:
|
||||
r.http.Header.Add(k, v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// WithCookies adds given cookies to request.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithCookies(map[string]string{
|
||||
// "foo": "aa",
|
||||
// "bar": "bb",
|
||||
// })
|
||||
func (r *Request) WithCookies(cookies map[string]string) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
for k, v := range cookies {
|
||||
r.WithCookie(k, v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// WithCookie adds given single cookie to request.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithCookie("name", "value")
|
||||
func (r *Request) WithCookie(k, v string) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
r.http.AddCookie(&http.Cookie{
|
||||
Name: k,
|
||||
Value: v,
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
// WithBasicAuth sets the request's Authorization header to use HTTP
|
||||
// Basic Authentication with the provided username and password.
|
||||
//
|
||||
// With HTTP Basic Authentication the provided username and password
|
||||
// are not encrypted.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithBasicAuth("john", "secret")
|
||||
func (r *Request) WithBasicAuth(username, password string) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
r.http.SetBasicAuth(username, password)
|
||||
return r
|
||||
}
|
||||
|
||||
// WithProto sets HTTP protocol version.
|
||||
//
|
||||
// proto should have form of "HTTP/{major}.{minor}", e.g. "HTTP/1.1".
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithProto("HTTP/2.0")
|
||||
func (r *Request) WithProto(proto string) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
major, minor, ok := http.ParseHTTPVersion(proto)
|
||||
if !ok {
|
||||
r.chain.fail(
|
||||
"\nunexpected protocol version %q, expected \"HTTP/{major}.{minor}\"",
|
||||
proto)
|
||||
return r
|
||||
}
|
||||
r.http.ProtoMajor = major
|
||||
r.http.ProtoMinor = minor
|
||||
return r
|
||||
}
|
||||
|
||||
// WithChunked enables chunked encoding and sets request body reader.
|
||||
//
|
||||
// Expect() will read all available data from given reader. Content-Length
|
||||
// is not set, and "chunked" Transfer-Encoding is used.
|
||||
//
|
||||
// If protocol version is not at least HTTP/1.1 (required for chunked
|
||||
// encoding), failure is reported.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/upload")
|
||||
// fh, _ := os.Open("data")
|
||||
// defer fh.Close()
|
||||
// req.WithHeader("Content-Type": "application/octet-stream")
|
||||
// req.WithChunked(fh)
|
||||
func (r *Request) WithChunked(reader io.Reader) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
if !r.http.ProtoAtLeast(1, 1) {
|
||||
r.chain.fail("chunked Transfer-Encoding requires at least \"HTTP/1.1\","+
|
||||
"but \"HTTP/%d.%d\" is enabled", r.http.ProtoMajor, r.http.ProtoMinor)
|
||||
return r
|
||||
}
|
||||
r.setBody("WithChunked", reader, -1, false)
|
||||
return r
|
||||
}
|
||||
|
||||
// WithBytes sets request body to given slice of bytes.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithHeader("Content-Type": "application/json")
|
||||
// req.WithBytes([]byte(`{"foo": 123}`))
|
||||
func (r *Request) WithBytes(b []byte) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
if b == nil {
|
||||
r.setBody("WithBytes", nil, 0, false)
|
||||
} else {
|
||||
r.setBody("WithBytes", bytes.NewReader(b), len(b), false)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// WithText sets Content-Type header to "text/plain; charset=utf-8" and
|
||||
// sets body to given string.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithText("hello, world!")
|
||||
func (r *Request) WithText(s string) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
r.setType("WithText", "text/plain; charset=utf-8", false)
|
||||
r.setBody("WithText", strings.NewReader(s), len(s), false)
|
||||
return r
|
||||
}
|
||||
|
||||
// WithJSON sets Content-Type header to "application/json; charset=utf-8"
|
||||
// and sets body to object, marshaled using json.Marshal().
|
||||
//
|
||||
// Example:
|
||||
// type MyJSON struct {
|
||||
// Foo int `json:"foo"`
|
||||
// }
|
||||
//
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithJSON(MyJSON{Foo: 123})
|
||||
//
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithJSON(map[string]interface{}{"foo": 123})
|
||||
func (r *Request) WithJSON(object interface{}) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
b, err := json.Marshal(object)
|
||||
if err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return r
|
||||
}
|
||||
|
||||
r.setType("WithJSON", "application/json; charset=utf-8", false)
|
||||
r.setBody("WithJSON", bytes.NewReader(b), len(b), false)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// WithForm sets Content-Type header to "application/x-www-form-urlencoded"
|
||||
// or (if WithMultipart() was called) "multipart/form-data", converts given
|
||||
// object to url.Values using github.com/ajg/form, and adds it to request body.
|
||||
//
|
||||
// Various object types are supported, including maps and structs. Structs may
|
||||
// contain "form" struct tag, similar to "json" struct tag for json.Marshal().
|
||||
// See https://github.com/ajg/form for details.
|
||||
//
|
||||
// Multiple WithForm(), WithFormField(), and WithFile() calls may be combined.
|
||||
// If WithMultipart() is called, it should be called first.
|
||||
//
|
||||
// Example:
|
||||
// type MyForm struct {
|
||||
// Foo int `form:"foo"`
|
||||
// }
|
||||
//
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithForm(MyForm{Foo: 123})
|
||||
//
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithForm(map[string]interface{}{"foo": 123})
|
||||
func (r *Request) WithForm(object interface{}) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
|
||||
f, err := form.EncodeToValues(object)
|
||||
if err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return r
|
||||
}
|
||||
|
||||
if r.multipart != nil {
|
||||
r.setType("WithForm", "multipart/form-data", false)
|
||||
|
||||
var keys []string
|
||||
for k := range f {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
if err := r.multipart.WriteField(k, f[k][0]); err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return r
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.setType("WithForm", "application/x-www-form-urlencoded", false)
|
||||
|
||||
if r.form == nil {
|
||||
r.form = make(url.Values)
|
||||
}
|
||||
for k, v := range f {
|
||||
r.form[k] = append(r.form[k], v...)
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// WithFormField sets Content-Type header to "application/x-www-form-urlencoded"
|
||||
// or (if WithMultipart() was called) "multipart/form-data", converts given
|
||||
// value to string using fmt.Sprint(), and adds it to request body.
|
||||
//
|
||||
// Multiple WithForm(), WithFormField(), and WithFile() calls may be combined.
|
||||
// If WithMultipart() is called, it should be called first.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithFormField("foo", 123).
|
||||
// WithFormField("bar", 456)
|
||||
func (r *Request) WithFormField(key string, value interface{}) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
if r.multipart != nil {
|
||||
r.setType("WithFormField", "multipart/form-data", false)
|
||||
|
||||
err := r.multipart.WriteField(key, fmt.Sprint(value))
|
||||
if err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return r
|
||||
}
|
||||
} else {
|
||||
r.setType("WithFormField", "application/x-www-form-urlencoded", false)
|
||||
|
||||
if r.form == nil {
|
||||
r.form = make(url.Values)
|
||||
}
|
||||
r.form[key] = append(r.form[key], fmt.Sprint(value))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// WithFile sets Content-Type header to "multipart/form-data", reads given
|
||||
// file and adds its contents to request body.
|
||||
//
|
||||
// If reader is given, it's used to read file contents. Otherwise, os.Open()
|
||||
// is used to read a file with given path.
|
||||
//
|
||||
// Multiple WithForm(), WithFormField(), and WithFile() calls may be combined.
|
||||
// WithMultipart() should be called before WithFile(), otherwise WithFile()
|
||||
// fails.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithFile("avatar", "./john.png")
|
||||
//
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// fh, _ := os.Open("./john.png")
|
||||
// req.WithMultipart().
|
||||
// WithFile("avatar", "john.png", fh)
|
||||
// fh.Close()
|
||||
func (r *Request) WithFile(key, path string, reader ...io.Reader) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
|
||||
r.setType("WithFile", "multipart/form-data", false)
|
||||
|
||||
if r.multipart == nil {
|
||||
r.chain.fail("WithFile requires WithMultipart to be called first")
|
||||
return r
|
||||
}
|
||||
|
||||
wr, err := r.multipart.CreateFormFile(key, path)
|
||||
if err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return r
|
||||
}
|
||||
|
||||
var rd io.Reader
|
||||
if len(reader) != 0 && reader[0] != nil {
|
||||
rd = reader[0]
|
||||
} else {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return r
|
||||
}
|
||||
rd = f
|
||||
defer f.Close()
|
||||
}
|
||||
|
||||
if _, err := io.Copy(wr, rd); err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return r
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// WithFileBytes is like WithFile, but uses given slice of bytes as the
|
||||
// file contents.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// fh, _ := os.Open("./john.png")
|
||||
// b, _ := ioutil.ReadAll(fh)
|
||||
// req.WithMultipart().
|
||||
// WithFileBytes("avatar", "john.png", b)
|
||||
// fh.Close()
|
||||
func (r *Request) WithFileBytes(key, path string, data []byte) *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
return r.WithFile(key, path, bytes.NewReader(data))
|
||||
}
|
||||
|
||||
// WithMultipart sets Content-Type header to "multipart/form-data".
|
||||
//
|
||||
// After this call, WithForm() and WithFormField() switch to multipart
|
||||
// form instead of urlencoded form.
|
||||
//
|
||||
// If WithMultipart() is called, it should be called before WithForm(),
|
||||
// WithFormField(), and WithFile().
|
||||
//
|
||||
// WithFile() always requires WithMultipart() to be called first.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithMultipart().
|
||||
// WithForm(map[string]interface{}{"foo": 123})
|
||||
func (r *Request) WithMultipart() *Request {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
|
||||
r.setType("WithMultipart", "multipart/form-data", false)
|
||||
|
||||
if r.multipart == nil {
|
||||
r.formbuf = new(bytes.Buffer)
|
||||
r.multipart = multipart.NewWriter(r.formbuf)
|
||||
r.setBody("WithMultipart", r.formbuf, 0, false)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Expect constructs http.Request, sends it, receives http.Response, and
|
||||
// returns a new Response object to inspect received response.
|
||||
//
|
||||
// Request is sent using Config.Client interface.
|
||||
//
|
||||
// Example:
|
||||
// req := NewRequest(config, "PUT", "http://example.com/path")
|
||||
// req.WithJSON(map[string]interface{}{"foo": 123})
|
||||
// resp := req.Expect()
|
||||
// resp.Status(http.StatusOK)
|
||||
func (r *Request) Expect() *Response {
|
||||
r.encodeRequest()
|
||||
|
||||
resp, elapsed := r.sendRequest()
|
||||
|
||||
return makeResponse(r.chain, resp, elapsed)
|
||||
}
|
||||
|
||||
func (r *Request) encodeRequest() {
|
||||
if r.chain.failed() {
|
||||
return
|
||||
}
|
||||
|
||||
r.http.URL.Path = concatPaths(r.http.URL.Path, r.path)
|
||||
|
||||
if r.query != nil {
|
||||
r.http.URL.RawQuery = r.query.Encode()
|
||||
}
|
||||
|
||||
if r.multipart != nil {
|
||||
if err := r.multipart.Close(); err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
r.setType("Expect", r.multipart.FormDataContentType(), true)
|
||||
r.setBody("Expect", r.formbuf, r.formbuf.Len(), true)
|
||||
} else if r.form != nil {
|
||||
s := r.form.Encode()
|
||||
r.setBody("WithForm or WithFormField", strings.NewReader(s), len(s), false)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) sendRequest() (resp *http.Response, elapsed time.Duration) {
|
||||
if r.chain.failed() {
|
||||
return
|
||||
}
|
||||
|
||||
for _, printer := range r.config.Printers {
|
||||
printer.Request(r.http)
|
||||
}
|
||||
|
||||
start := monotime.Now()
|
||||
|
||||
resp, err := r.config.Client.Do(r.http)
|
||||
|
||||
elapsed = monotime.Since(start)
|
||||
|
||||
if err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, printer := range r.config.Printers {
|
||||
printer.Response(resp, elapsed)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Request) setType(newSetter, newType string, overwrite bool) {
|
||||
if r.forcetype {
|
||||
return
|
||||
}
|
||||
|
||||
if !overwrite {
|
||||
previousType := r.http.Header.Get("Content-Type")
|
||||
|
||||
if previousType != "" && previousType != newType {
|
||||
r.chain.fail(
|
||||
"\nambiguous request \"Content-Type\" header values:\n %q (set by %s)\n\n"+
|
||||
"and:\n %q (wanted by %s)",
|
||||
previousType, r.typesetter,
|
||||
newType, newSetter)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.typesetter = newSetter
|
||||
r.http.Header["Content-Type"] = []string{newType}
|
||||
}
|
||||
|
||||
func (r *Request) setBody(setter string, reader io.Reader, len int, overwrite bool) {
|
||||
if !overwrite && r.bodysetter != "" {
|
||||
r.chain.fail(
|
||||
"\nambiguous request body contents:\n set by %s\n overwritten by %s",
|
||||
r.bodysetter, setter)
|
||||
return
|
||||
}
|
||||
|
||||
if len > 0 && reader == nil {
|
||||
panic("invalid length")
|
||||
}
|
||||
|
||||
if reader == nil {
|
||||
r.http.Body = nil
|
||||
r.http.ContentLength = 0
|
||||
} else {
|
||||
r.http.Body = ioutil.NopCloser(reader)
|
||||
r.http.ContentLength = int64(len)
|
||||
}
|
||||
|
||||
r.bodysetter = setter
|
||||
}
|
||||
|
||||
func concatPaths(a, b string) string {
|
||||
if a == "" {
|
||||
return b
|
||||
}
|
||||
if b == "" {
|
||||
return a
|
||||
}
|
||||
a = strings.TrimSuffix(a, "/")
|
||||
b = strings.TrimPrefix(b, "/")
|
||||
return a + "/" + b
|
||||
}
|
||||
+1422
File diff suppressed because it is too large
Load Diff
+511
@@ -0,0 +1,511 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ajg/form"
|
||||
)
|
||||
|
||||
// StatusRange is enum for response status ranges.
|
||||
type StatusRange int
|
||||
|
||||
const (
|
||||
// Status1xx defines "Informational" status codes.
|
||||
Status1xx StatusRange = 100
|
||||
|
||||
// Status2xx defines "Success" status codes.
|
||||
Status2xx StatusRange = 200
|
||||
|
||||
// Status3xx defines "Redirection" status codes.
|
||||
Status3xx StatusRange = 300
|
||||
|
||||
// Status4xx defines "Client Error" status codes.
|
||||
Status4xx StatusRange = 400
|
||||
|
||||
// Status5xx defines "Server Error" status codes.
|
||||
Status5xx StatusRange = 500
|
||||
)
|
||||
|
||||
// Response provides methods to inspect attached http.Response object.
|
||||
type Response struct {
|
||||
chain chain
|
||||
resp *http.Response
|
||||
content []byte
|
||||
cookies []*http.Cookie
|
||||
time time.Duration
|
||||
}
|
||||
|
||||
// NewResponse returns a new Response given a reporter used to report
|
||||
// failures and http.Response to be inspected.
|
||||
//
|
||||
// Both reporter and response should not be nil. If response is nil,
|
||||
// failure is reported.
|
||||
//
|
||||
// If duration is given, it defines response time to be reported by
|
||||
// response.Duration().
|
||||
func NewResponse(
|
||||
reporter Reporter, response *http.Response, duration ...time.Duration) *Response {
|
||||
var dr time.Duration
|
||||
if len(duration) > 0 {
|
||||
dr = duration[0]
|
||||
}
|
||||
return makeResponse(makeChain(reporter), response, dr)
|
||||
}
|
||||
|
||||
func makeResponse(
|
||||
chain chain, response *http.Response, duration time.Duration) *Response {
|
||||
var content []byte
|
||||
var cookies []*http.Cookie
|
||||
if response != nil {
|
||||
content = getContent(&chain, response)
|
||||
cookies = response.Cookies()
|
||||
} else {
|
||||
chain.fail("expected non-nil response")
|
||||
}
|
||||
return &Response{
|
||||
chain: chain,
|
||||
resp: response,
|
||||
content: content,
|
||||
cookies: cookies,
|
||||
time: duration,
|
||||
}
|
||||
}
|
||||
|
||||
func getContent(chain *chain, resp *http.Response) []byte {
|
||||
if resp.Body == nil {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
chain.fail(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
// Raw returns underlying http.Response object.
|
||||
// This is the value originally passed to NewResponse.
|
||||
func (r *Response) Raw() *http.Response {
|
||||
return r.resp
|
||||
}
|
||||
|
||||
// Duration returns a new Number object that may be used to inspect
|
||||
// response time, in nanoseconds.
|
||||
//
|
||||
// Response time is a time interval starting just before request is sent
|
||||
// and ending right after response is received, retrieved from monotonic
|
||||
// clock source.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response, time.Duration(10000000))
|
||||
// resp.Duration().Equal(10 * time.Millisecond)
|
||||
func (r *Response) Duration() *Number {
|
||||
return &Number{r.chain, float64(r.time)}
|
||||
}
|
||||
|
||||
// Status succeeds if response contains given status code.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response)
|
||||
// resp.Status(http.StatusOK)
|
||||
func (r *Response) Status(status int) *Response {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
r.checkEqual("status", statusCodeText(status), statusCodeText(r.resp.StatusCode))
|
||||
return r
|
||||
}
|
||||
|
||||
// StatusRange succeeds if response status belongs to given range.
|
||||
//
|
||||
// Supported ranges:
|
||||
// - Status1xx - Informational
|
||||
// - Status2xx - Success
|
||||
// - Status3xx - Redirection
|
||||
// - Status4xx - Client Error
|
||||
// - Status5xx - Server Error
|
||||
//
|
||||
// See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response)
|
||||
// resp.StatusRange(Status2xx)
|
||||
func (r *Response) StatusRange(rn StatusRange) *Response {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
|
||||
status := statusCodeText(r.resp.StatusCode)
|
||||
|
||||
actual := statusRangeText(r.resp.StatusCode)
|
||||
expected := statusRangeText(int(rn))
|
||||
|
||||
if actual == "" || actual != expected {
|
||||
if actual == "" {
|
||||
r.chain.fail("\nexpected status from range:\n %q\n\nbut got:\n %q",
|
||||
expected, status)
|
||||
} else {
|
||||
r.chain.fail(
|
||||
"\nexpected status from range:\n %q\n\nbut got:\n %q (%q)",
|
||||
expected, actual, status)
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func statusCodeText(code int) string {
|
||||
if s := http.StatusText(code); s != "" {
|
||||
return strconv.Itoa(code) + " " + s
|
||||
}
|
||||
return strconv.Itoa(code)
|
||||
}
|
||||
|
||||
func statusRangeText(code int) string {
|
||||
switch {
|
||||
case code >= 100 && code < 200:
|
||||
return "1xx Informational"
|
||||
case code >= 200 && code < 300:
|
||||
return "2xx Success"
|
||||
case code >= 300 && code < 400:
|
||||
return "3xx Redirection"
|
||||
case code >= 400 && code < 500:
|
||||
return "4xx Client Error"
|
||||
case code >= 500 && code < 600:
|
||||
return "5xx Server Error"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Headers returns a new Object that may be used to inspect header map.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response)
|
||||
// resp.Headers().Value("Content-Type").String().Equal("application-json")
|
||||
func (r *Response) Headers() *Object {
|
||||
var value map[string]interface{}
|
||||
if !r.chain.failed() {
|
||||
value, _ = canonMap(&r.chain, r.resp.Header)
|
||||
}
|
||||
return &Object{r.chain, value}
|
||||
}
|
||||
|
||||
// Header returns a new String object that may be used to inspect given header.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response)
|
||||
// resp.Header("Content-Type").Equal("application-json")
|
||||
// resp.Header("Date").DateTime().Le(time.Now())
|
||||
func (r *Response) Header(header string) *String {
|
||||
value := ""
|
||||
if !r.chain.failed() {
|
||||
value = r.resp.Header.Get(header)
|
||||
}
|
||||
return &String{r.chain, value}
|
||||
}
|
||||
|
||||
// Cookies returns a new Array object with all cookie names set by this response.
|
||||
// Returned Array contains a String value for every cookie name.
|
||||
//
|
||||
// Note that this returns only cookies set by Set-Cookie headers of this response.
|
||||
// It doesn't return session cookies from previous responses, which may be stored
|
||||
// in a cookie jar.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response)
|
||||
// resp.Cookies().Contains("session")
|
||||
func (r *Response) Cookies() *Array {
|
||||
if r.chain.failed() {
|
||||
return &Array{r.chain, nil}
|
||||
}
|
||||
names := []interface{}{}
|
||||
for _, c := range r.cookies {
|
||||
names = append(names, c.Name)
|
||||
}
|
||||
return &Array{r.chain, names}
|
||||
}
|
||||
|
||||
// Cookie returns a new Cookie object that may be used to inspect given cookie
|
||||
// set by this response.
|
||||
//
|
||||
// Note that this returns only cookies set by Set-Cookie headers of this response.
|
||||
// It doesn't return session cookies from previous responses, which may be stored
|
||||
// in a cookie jar.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response)
|
||||
// resp.Cookie("session").Domain().Equal("example.com")
|
||||
func (r *Response) Cookie(name string) *Cookie {
|
||||
if r.chain.failed() {
|
||||
return &Cookie{r.chain, nil}
|
||||
}
|
||||
names := []string{}
|
||||
for _, c := range r.cookies {
|
||||
if c.Name == name {
|
||||
return &Cookie{r.chain, c}
|
||||
}
|
||||
names = append(names, c.Name)
|
||||
}
|
||||
r.chain.fail("\nexpected response with cookie:\n %q\n\nbut got only cookies:\n%s",
|
||||
name, dumpValue(names))
|
||||
return &Cookie{r.chain, nil}
|
||||
}
|
||||
|
||||
// Body returns a new String object that may be used to inspect response body.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response)
|
||||
// resp.Body().NotEmpty()
|
||||
// resp.Body().Length().Equal(100)
|
||||
func (r *Response) Body() *String {
|
||||
return &String{r.chain, string(r.content)}
|
||||
}
|
||||
|
||||
// NoContent succeeds if response contains empty Content-Type header and
|
||||
// empty body.
|
||||
func (r *Response) NoContent() *Response {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
|
||||
contentType := r.resp.Header.Get("Content-Type")
|
||||
|
||||
r.checkEqual("\"Content-Type\" header", "", contentType)
|
||||
r.checkEqual("body", "", string(r.content))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// ContentType succeeds if response contains Content-Type header with given
|
||||
// media type and charset.
|
||||
//
|
||||
// If charset is omitted, and mediaType is non-empty, Content-Type header
|
||||
// should contain empty or utf-8 charset.
|
||||
//
|
||||
// If charset is omitted, and mediaType is also empty, Content-Type header
|
||||
// should contain no charset.
|
||||
func (r *Response) ContentType(mediaType string, charset ...string) *Response {
|
||||
r.checkContentType(mediaType, charset...)
|
||||
return r
|
||||
}
|
||||
|
||||
// ContentEncoding succeeds if response has exactly given Content-Encoding list.
|
||||
// Common values are empty, "gzip", "compress", "deflate", "identity" and "br".
|
||||
func (r *Response) ContentEncoding(encoding ...string) *Response {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
r.checkEqual("\"Content-Encoding\" header", encoding, r.resp.Header["Content-Encoding"])
|
||||
return r
|
||||
}
|
||||
|
||||
// TransferEncoding succeeds if response contains given Transfer-Encoding list.
|
||||
// Common values are empty, "chunked" and "identity".
|
||||
func (r *Response) TransferEncoding(encoding ...string) *Response {
|
||||
if r.chain.failed() {
|
||||
return r
|
||||
}
|
||||
r.checkEqual("\"Transfer-Encoding\" header", encoding, r.resp.TransferEncoding)
|
||||
return r
|
||||
}
|
||||
|
||||
// Text returns a new String object that may be used to inspect response body.
|
||||
//
|
||||
// Text succeeds if response contains "text/plain" Content-Type header
|
||||
// with empty or "utf-8" charset.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response)
|
||||
// resp.Text().Equal("hello, world!")
|
||||
func (r *Response) Text() *String {
|
||||
var content string
|
||||
|
||||
if !r.chain.failed() && r.checkContentType("text/plain") {
|
||||
content = string(r.content)
|
||||
}
|
||||
|
||||
return &String{r.chain, content}
|
||||
}
|
||||
|
||||
// Form returns a new Object that may be used to inspect form contents
|
||||
// of response.
|
||||
//
|
||||
// Form succeeds if response contains "application/x-www-form-urlencoded"
|
||||
// Content-Type header and if form may be decoded from response body.
|
||||
// Decoding is performed using https://github.com/ajg/form.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response)
|
||||
// resp.Form().Value("foo").Equal("bar")
|
||||
func (r *Response) Form() *Object {
|
||||
object := r.getForm()
|
||||
return &Object{r.chain, object}
|
||||
}
|
||||
|
||||
func (r *Response) getForm() map[string]interface{} {
|
||||
if r.chain.failed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !r.checkContentType("application/x-www-form-urlencoded", "") {
|
||||
return nil
|
||||
}
|
||||
|
||||
decoder := form.NewDecoder(bytes.NewReader(r.content))
|
||||
|
||||
var object map[string]interface{}
|
||||
if err := decoder.Decode(&object); err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
// JSON returns a new Value object that may be used to inspect JSON contents
|
||||
// of response.
|
||||
//
|
||||
// JSON succeeds if response contains "application/json" Content-Type header
|
||||
// with empty or "utf-8" charset and if JSON may be decoded from response body.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response)
|
||||
// resp.JSON().Array().Elements("foo", "bar")
|
||||
func (r *Response) JSON() *Value {
|
||||
value := r.getJSON()
|
||||
return &Value{r.chain, value}
|
||||
}
|
||||
|
||||
func (r *Response) getJSON() interface{} {
|
||||
if r.chain.failed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !r.checkContentType("application/json") {
|
||||
return nil
|
||||
}
|
||||
|
||||
var value interface{}
|
||||
if err := json.Unmarshal(r.content, &value); err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// JSONP returns a new Value object that may be used to inspect JSONP contents
|
||||
// of response.
|
||||
//
|
||||
// JSONP succeeds if response contains "application/javascript" Content-Type
|
||||
// header with empty or "utf-8" charset and response body of the following form:
|
||||
// callback(<valid json>);
|
||||
// or:
|
||||
// callback(<valid json>)
|
||||
//
|
||||
// Whitespaces are allowed.
|
||||
//
|
||||
// Example:
|
||||
// resp := NewResponse(t, response)
|
||||
// resp.JSONP("myCallback").Array().Elements("foo", "bar")
|
||||
func (r *Response) JSONP(callback string) *Value {
|
||||
value := r.getJSONP(callback)
|
||||
return &Value{r.chain, value}
|
||||
}
|
||||
|
||||
var (
|
||||
jsonp = regexp.MustCompile(`^\s*([^\s(]+)\s*\((.*)\)\s*;*\s*$`)
|
||||
)
|
||||
|
||||
func (r *Response) getJSONP(callback string) interface{} {
|
||||
if r.chain.failed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !r.checkContentType("application/javascript") {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := jsonp.FindSubmatch(r.content)
|
||||
if len(m) != 3 || string(m[1]) != callback {
|
||||
r.chain.fail(
|
||||
"\nexpected JSONP body in form of:\n \"%s(<valid json>)\"\n\nbut got:\n %q\n",
|
||||
callback,
|
||||
string(r.content))
|
||||
return nil
|
||||
}
|
||||
|
||||
var value interface{}
|
||||
if err := json.Unmarshal(m[2], &value); err != nil {
|
||||
r.chain.fail(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func (r *Response) checkContentType(expectedType string, expectedCharset ...string) bool {
|
||||
if r.chain.failed() {
|
||||
return false
|
||||
}
|
||||
|
||||
contentType := r.resp.Header.Get("Content-Type")
|
||||
|
||||
if expectedType == "" && len(expectedCharset) == 0 {
|
||||
if contentType == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
mediaType, params, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
r.chain.fail("\ngot invalid \"Content-Type\" header %q", contentType)
|
||||
return false
|
||||
}
|
||||
|
||||
if mediaType != expectedType {
|
||||
r.chain.fail(
|
||||
"\nexpected \"Content-Type\" header with %q media type,"+
|
||||
"\nbut got %q", expectedType, mediaType)
|
||||
return false
|
||||
}
|
||||
|
||||
charset := params["charset"]
|
||||
|
||||
if len(expectedCharset) == 0 {
|
||||
if charset != "" && !strings.EqualFold(charset, "utf-8") {
|
||||
r.chain.fail(
|
||||
"\nexpected \"Content-Type\" header with \"utf-8\" or empty charset,"+
|
||||
"\nbut got %q", charset)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if !strings.EqualFold(charset, expectedCharset[0]) {
|
||||
r.chain.fail(
|
||||
"\nexpected \"Content-Type\" header with %q charset,"+
|
||||
"\nbut got %q", expectedCharset[0], charset)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *Response) checkEqual(what string, expected, actual interface{}) {
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
r.chain.fail("\nexpected %s equal to:\n%s\n\nbut got:\n%s", what,
|
||||
dumpValue(expected), dumpValue(actual))
|
||||
}
|
||||
}
|
||||
+872
@@ -0,0 +1,872 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestResponseFailed(t *testing.T) {
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
chain.fail("fail")
|
||||
|
||||
resp := &Response{chain, nil, nil, nil, 0}
|
||||
|
||||
resp.chain.assertFailed(t)
|
||||
|
||||
assert.False(t, resp.Duration() == nil)
|
||||
assert.False(t, resp.Headers() == nil)
|
||||
assert.False(t, resp.Header("foo") == nil)
|
||||
assert.False(t, resp.Cookies() == nil)
|
||||
assert.False(t, resp.Cookie("foo") == nil)
|
||||
assert.False(t, resp.Body() == nil)
|
||||
assert.False(t, resp.JSON() == nil)
|
||||
assert.False(t, resp.JSONP("") == nil)
|
||||
|
||||
resp.Headers().chain.assertFailed(t)
|
||||
resp.Header("foo").chain.assertFailed(t)
|
||||
resp.Cookies().chain.assertFailed(t)
|
||||
resp.Cookie("foo").chain.assertFailed(t)
|
||||
resp.Body().chain.assertFailed(t)
|
||||
resp.Text().chain.assertFailed(t)
|
||||
resp.JSON().chain.assertFailed(t)
|
||||
resp.JSONP("").chain.assertFailed(t)
|
||||
|
||||
resp.Status(123)
|
||||
resp.StatusRange(Status2xx)
|
||||
resp.NoContent()
|
||||
resp.ContentType("", "")
|
||||
resp.ContentEncoding("")
|
||||
resp.TransferEncoding("")
|
||||
}
|
||||
|
||||
func TestResponseDuration(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
duration := time.Duration(10000000)
|
||||
|
||||
resp := NewResponse(reporter, &http.Response{}, duration)
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
rt := resp.Duration()
|
||||
|
||||
assert.Equal(t, float64(duration), rt.Raw())
|
||||
|
||||
rt.Equal(10 * time.Millisecond)
|
||||
rt.chain.assertOK(t)
|
||||
}
|
||||
|
||||
func TestResponseStatusRange(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
ranges := []StatusRange{
|
||||
Status1xx,
|
||||
Status2xx,
|
||||
Status3xx,
|
||||
Status4xx,
|
||||
Status5xx,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
Status int
|
||||
Range StatusRange
|
||||
}{
|
||||
{99, StatusRange(-1)},
|
||||
{100, Status1xx},
|
||||
{199, Status1xx},
|
||||
{200, Status2xx},
|
||||
{299, Status2xx},
|
||||
{300, Status3xx},
|
||||
{399, Status3xx},
|
||||
{400, Status4xx},
|
||||
{499, Status4xx},
|
||||
{500, Status5xx},
|
||||
{599, Status5xx},
|
||||
{600, StatusRange(-1)},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
for _, r := range ranges {
|
||||
resp := NewResponse(reporter, &http.Response{
|
||||
StatusCode: test.Status,
|
||||
})
|
||||
|
||||
resp.StatusRange(r)
|
||||
|
||||
if test.Range == r {
|
||||
resp.chain.assertOK(t)
|
||||
} else {
|
||||
resp.chain.assertFailed(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseHeaders(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"First-Header": {"foo"},
|
||||
"Second-Header": {"bar"},
|
||||
}
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: nil,
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.Equal(t, httpResp, resp.Raw())
|
||||
|
||||
resp.Status(http.StatusOK)
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.Status(http.StatusNotFound)
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.Headers().Equal(headers).chain.assertOK(t)
|
||||
|
||||
for k, v := range headers {
|
||||
for _, h := range []string{k, strings.ToLower(k), strings.ToUpper(k)} {
|
||||
resp.Header(h).Equal(v[0]).chain.assertOK(t)
|
||||
}
|
||||
}
|
||||
|
||||
resp.Header("Bad-Header").Empty().chain.assertOK(t)
|
||||
}
|
||||
|
||||
func TestResponseCookies(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Set-Cookie": {
|
||||
"foo=aaa",
|
||||
"bar=bbb; expires=Fri, 31 Dec 2010 23:59:59 GMT; " +
|
||||
"path=/xxx; domain=example.com",
|
||||
},
|
||||
}
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: nil,
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.Equal(t, []interface{}{"foo", "bar"}, resp.Cookies().Raw())
|
||||
resp.chain.assertOK(t)
|
||||
|
||||
c1 := resp.Cookie("foo")
|
||||
resp.chain.assertOK(t)
|
||||
assert.Equal(t, "foo", c1.Raw().Name)
|
||||
assert.Equal(t, "aaa", c1.Raw().Value)
|
||||
assert.Equal(t, "", c1.Raw().Domain)
|
||||
assert.Equal(t, "", c1.Raw().Path)
|
||||
|
||||
c2 := resp.Cookie("bar")
|
||||
resp.chain.assertOK(t)
|
||||
assert.Equal(t, "bar", c2.Raw().Name)
|
||||
assert.Equal(t, "bbb", c2.Raw().Value)
|
||||
assert.Equal(t, "example.com", c2.Raw().Domain)
|
||||
assert.Equal(t, "/xxx", c2.Raw().Path)
|
||||
assert.True(t, time.Date(2010, 12, 31, 23, 59, 59, 0, time.UTC).
|
||||
Equal(c2.Raw().Expires))
|
||||
|
||||
c3 := resp.Cookie("baz")
|
||||
resp.chain.assertFailed(t)
|
||||
c3.chain.assertFailed(t)
|
||||
assert.True(t, c3.Raw() == nil)
|
||||
}
|
||||
|
||||
func TestResponseNoCookies(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: nil,
|
||||
Body: nil,
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.Equal(t, []interface{}{}, resp.Cookies().Raw())
|
||||
resp.chain.assertOK(t)
|
||||
|
||||
c := resp.Cookie("foo")
|
||||
resp.chain.assertFailed(t)
|
||||
c.chain.assertFailed(t)
|
||||
assert.True(t, c.Raw() == nil)
|
||||
}
|
||||
|
||||
func TestResponseBody(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString("body")),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
assert.Equal(t, "body", resp.Body().Raw())
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
}
|
||||
|
||||
func TestResponseNoContentEmpty(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {""},
|
||||
}
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString("")),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
assert.Equal(t, "", resp.Body().Raw())
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.NoContent()
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.Text()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.Form()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.JSON()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.JSONP("")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
}
|
||||
|
||||
func TestResponseNoContentNil(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {""},
|
||||
}
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: nil,
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
assert.Equal(t, "", resp.Body().Raw())
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.NoContent()
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.Text()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.Form()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.JSON()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.JSONP("")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
}
|
||||
|
||||
func TestResponseNoContentFailed(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"text/plain; charset=utf-8"},
|
||||
}
|
||||
|
||||
body := ``
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
assert.Equal(t, body, resp.Body().Raw())
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.NoContent()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
}
|
||||
|
||||
func TestResponseContentType(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"text/plain; charset=utf-8"},
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, &http.Response{
|
||||
Header: http.Header(headers),
|
||||
})
|
||||
|
||||
resp.ContentType("text/plain")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("text/plain", "utf-8")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("text/plain", "UTF-8")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("bad")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("text/plain", "bad")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("text/plain", "")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
}
|
||||
|
||||
func TestResponseContentTypeEmptyCharset(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"text/plain"},
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, &http.Response{
|
||||
Header: http.Header(headers),
|
||||
})
|
||||
|
||||
resp.ContentType("text/plain")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("text/plain", "")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("text/plain", "utf-8")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
}
|
||||
|
||||
func TestResponseContentTypeInvalid(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers1 := map[string][]string{
|
||||
"Content-Type": {";"},
|
||||
}
|
||||
|
||||
headers2 := map[string][]string{
|
||||
"Content-Type": {"charset=utf-8"},
|
||||
}
|
||||
|
||||
resp1 := NewResponse(reporter, &http.Response{
|
||||
Header: http.Header(headers1),
|
||||
})
|
||||
|
||||
resp2 := NewResponse(reporter, &http.Response{
|
||||
Header: http.Header(headers2),
|
||||
})
|
||||
|
||||
resp1.ContentType("")
|
||||
resp1.chain.assertFailed(t)
|
||||
resp1.chain.reset()
|
||||
|
||||
resp1.ContentType("", "")
|
||||
resp1.chain.assertFailed(t)
|
||||
resp1.chain.reset()
|
||||
|
||||
resp2.ContentType("")
|
||||
resp2.chain.assertFailed(t)
|
||||
resp2.chain.reset()
|
||||
|
||||
resp2.ContentType("", "")
|
||||
resp2.chain.assertFailed(t)
|
||||
resp2.chain.reset()
|
||||
}
|
||||
|
||||
func TestResponseContentEncoding(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Encoding": {"gzip", "deflate"},
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, &http.Response{
|
||||
Header: http.Header(headers),
|
||||
})
|
||||
|
||||
resp.ContentEncoding("gzip", "deflate")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentEncoding("deflate", "gzip")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentEncoding("gzip")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentEncoding()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
}
|
||||
|
||||
func TestResponseTransferEncoding(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
resp := NewResponse(reporter, &http.Response{
|
||||
TransferEncoding: []string{"foo", "bar"},
|
||||
})
|
||||
|
||||
resp.TransferEncoding("foo", "bar")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.TransferEncoding("foo")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.TransferEncoding()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
}
|
||||
|
||||
func TestResponseText(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"text/plain; charset=utf-8"},
|
||||
}
|
||||
|
||||
body := `hello, world!`
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
assert.Equal(t, body, resp.Body().Raw())
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("text/plain")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("text/plain", "utf-8")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("application/json")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.Text()
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.Equal(t, "hello, world!", resp.Text().Raw())
|
||||
}
|
||||
|
||||
func TestResponseForm(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"application/x-www-form-urlencoded"},
|
||||
}
|
||||
|
||||
body := `a=1&b=2`
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
assert.Equal(t, body, resp.Body().Raw())
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("application/x-www-form-urlencoded")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("application/x-www-form-urlencoded", "")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("text/plain")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.Form()
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, resp.Form().Raw())
|
||||
}
|
||||
|
||||
func TestResponseFormBadBody(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"application/x-www-form-urlencoded"},
|
||||
}
|
||||
|
||||
body := "%"
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
resp.Form()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.True(t, resp.Form().Raw() == nil)
|
||||
}
|
||||
|
||||
func TestResponseFormBadType(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"bad"},
|
||||
}
|
||||
|
||||
body := "foo=bar"
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
resp.Form()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.True(t, resp.Form().Raw() == nil)
|
||||
}
|
||||
|
||||
func TestResponseJSON(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"application/json; charset=utf-8"},
|
||||
}
|
||||
|
||||
body := `{"key": "value"}`
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
assert.Equal(t, body, resp.Body().Raw())
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("application/json")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("application/json", "utf-8")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("text/plain")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.JSON()
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{"key": "value"}, resp.JSON().Object().Raw())
|
||||
}
|
||||
|
||||
func TestResponseJSONBadBody(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"application/json; charset=utf-8"},
|
||||
}
|
||||
|
||||
body := "{"
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
resp.JSON()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.True(t, resp.JSON().Raw() == nil)
|
||||
}
|
||||
|
||||
func TestResponseJSONCharsetEmpty(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"application/json"},
|
||||
}
|
||||
|
||||
body := `{"key": "value"}`
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
resp.JSON()
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{"key": "value"}, resp.JSON().Object().Raw())
|
||||
}
|
||||
|
||||
func TestResponseJSONCharsetBad(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"application/json; charset=bad"},
|
||||
}
|
||||
|
||||
body := `{"key": "value"}`
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
resp.JSON()
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.Equal(t, nil, resp.JSON().Raw())
|
||||
}
|
||||
|
||||
func TestResponseJSONP(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"application/javascript; charset=utf-8"},
|
||||
}
|
||||
|
||||
body1 := `foo({"key": "value"})`
|
||||
body2 := `foo({"key": "value"});`
|
||||
body3 := ` foo ( {"key": "value"} ) ; `
|
||||
|
||||
for _, body := range []string{body1, body2, body3} {
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
assert.Equal(t, body, resp.Body().Raw())
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("application/javascript")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("application/javascript", "utf-8")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.ContentType("text/plain")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.JSONP("foo")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{"key": "value"}, resp.JSONP("foo").Object().Raw())
|
||||
|
||||
resp.JSONP("fo")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
resp.JSONP("")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseJSONPBadBody(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"application/javascript; charset=utf-8"},
|
||||
}
|
||||
|
||||
body1 := `foo`
|
||||
body2 := `foo();`
|
||||
body3 := `foo(`
|
||||
body4 := `foo({);`
|
||||
|
||||
for _, body := range []string{body1, body2, body3, body4} {
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
resp.JSONP("foo")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.True(t, resp.JSONP("foo").Raw() == nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseJSONPCharsetEmpty(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"application/javascript"},
|
||||
}
|
||||
|
||||
body := `foo({"key": "value"})`
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
resp.JSONP("foo")
|
||||
resp.chain.assertOK(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{"key": "value"}, resp.JSONP("foo").Object().Raw())
|
||||
}
|
||||
|
||||
func TestResponseJSONPCharsetBad(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": {"application/javascript; charset=bad"},
|
||||
}
|
||||
|
||||
body := `foo({"key": "value"})`
|
||||
|
||||
httpResp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header(headers),
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
|
||||
resp := NewResponse(reporter, httpResp)
|
||||
|
||||
resp.JSONP("foo")
|
||||
resp.chain.assertFailed(t)
|
||||
resp.chain.reset()
|
||||
|
||||
assert.Equal(t, nil, resp.JSONP("foo").Raw())
|
||||
}
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// String provides methods to inspect attached string value
|
||||
// (Go representation of JSON string).
|
||||
type String struct {
|
||||
chain chain
|
||||
value string
|
||||
}
|
||||
|
||||
// NewString returns a new String given a reporter used to report failures
|
||||
// and value to be inspected.
|
||||
//
|
||||
// reporter should not be nil.
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
func NewString(reporter Reporter, value string) *String {
|
||||
return &String{makeChain(reporter), value}
|
||||
}
|
||||
|
||||
// Raw returns underlying value attached to String.
|
||||
// This is the value originally passed to NewString.
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
// assert.Equal(t, "Hello", str.Raw())
|
||||
func (s *String) Raw() string {
|
||||
return s.value
|
||||
}
|
||||
|
||||
// Path is similar to Value.Path.
|
||||
func (s *String) Path(path string) *Value {
|
||||
return getPath(&s.chain, s.value, path)
|
||||
}
|
||||
|
||||
// Schema is similar to Value.Schema.
|
||||
func (s *String) Schema(schema interface{}) *String {
|
||||
checkSchema(&s.chain, s.value, schema)
|
||||
return s
|
||||
}
|
||||
|
||||
// Length returns a new Number object that may be used to inspect string length.
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
// str.Length().Equal(5)
|
||||
func (s *String) Length() *Number {
|
||||
return &Number{s.chain, float64(len(s.value))}
|
||||
}
|
||||
|
||||
// DateTime parses date/time from string an returns a new DateTime object.
|
||||
//
|
||||
// If layout is given, DateTime() uses time.Parse() with given layout.
|
||||
// Otherwise, it uses http.ParseTime(). If pasing error occurred,
|
||||
// DateTime reports failure and returns empty (but non-nil) object.
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Tue, 15 Nov 1994 08:12:31 GMT")
|
||||
// str.DateTime().Lt(time.Now())
|
||||
//
|
||||
// str := NewString(t, "15 Nov 94 08:12 GMT")
|
||||
// str.DateTime(time.RFC822).Lt(time.Now())
|
||||
func (s *String) DateTime(layout ...string) *DateTime {
|
||||
if s.chain.failed() {
|
||||
return &DateTime{s.chain, time.Unix(0, 0)}
|
||||
}
|
||||
var (
|
||||
t time.Time
|
||||
err error
|
||||
)
|
||||
if len(layout) != 0 {
|
||||
t, err = time.Parse(layout[0], s.value)
|
||||
} else {
|
||||
t, err = http.ParseTime(s.value)
|
||||
}
|
||||
if err != nil {
|
||||
s.chain.fail(err.Error())
|
||||
return &DateTime{s.chain, time.Unix(0, 0)}
|
||||
}
|
||||
return &DateTime{s.chain, t}
|
||||
}
|
||||
|
||||
// Empty succeeds if string is empty.
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "")
|
||||
// str.Empty()
|
||||
func (s *String) Empty() *String {
|
||||
return s.Equal("")
|
||||
}
|
||||
|
||||
// NotEmpty succeeds if string is non-empty.
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
// str.NotEmpty()
|
||||
func (s *String) NotEmpty() *String {
|
||||
return s.NotEqual("")
|
||||
}
|
||||
|
||||
// Equal succeeds if string is equal to another str.
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
// str.Equal("Hello")
|
||||
func (s *String) Equal(value string) *String {
|
||||
if !(s.value == value) {
|
||||
s.chain.fail("\nexpected string equal to:\n %q\n\nbut got:\n %q",
|
||||
value, s.value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NotEqual succeeds if string is not equal to another str.
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
// str.NotEqual("Goodbye")
|
||||
func (s *String) NotEqual(value string) *String {
|
||||
if !(s.value != value) {
|
||||
s.chain.fail("\nexpected string not equal to:\n %q", value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// EqualFold succeeds if string is equal to another string under Unicode case-folding
|
||||
// (case-insensitive match).
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
// str.EqualFold("hELLo")
|
||||
func (s *String) EqualFold(value string) *String {
|
||||
if !strings.EqualFold(s.value, value) {
|
||||
s.chain.fail(
|
||||
"\nexpected string equal to (case-insensitive):\n %q\n\nbut got:\n %q",
|
||||
value, s.value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NotEqualFold succeeds if string is not equal to another string under Unicode
|
||||
// case-folding (case-insensitive match).
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
// str.NotEqualFold("gOODBYe")
|
||||
func (s *String) NotEqualFold(value string) *String {
|
||||
if strings.EqualFold(s.value, value) {
|
||||
s.chain.fail(
|
||||
"\nexpected string not equal to (case-insensitive):\n %q\n\nbut got:\n %q",
|
||||
value, s.value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Contains succeeds if string contains given substr.
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
// str.Contains("ell")
|
||||
func (s *String) Contains(value string) *String {
|
||||
if !strings.Contains(s.value, value) {
|
||||
s.chain.fail(
|
||||
"\nexpected string containing substring:\n %q\n\nbut got:\n %q",
|
||||
value, s.value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NotContains succeeds if string doesn't contain given substr.
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
// str.NotContains("bye")
|
||||
func (s *String) NotContains(value string) *String {
|
||||
if strings.Contains(s.value, value) {
|
||||
s.chain.fail(
|
||||
"\nexpected string not containing substring:\n %q\n\nbut got:\n %q",
|
||||
value, s.value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ContainsFold succeeds if string contains given substring under Unicode case-folding
|
||||
// (case-insensitive match).
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
// str.ContainsFold("ELL")
|
||||
func (s *String) ContainsFold(value string) *String {
|
||||
if !strings.Contains(strings.ToLower(s.value), strings.ToLower(value)) {
|
||||
s.chain.fail(
|
||||
"\nexpected string containing substring (case-insensitive):\n %q"+
|
||||
"\n\nbut got:\n %q", value, s.value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NotContainsFold succeeds if string doesn't contain given substring under Unicode
|
||||
// case-folding (case-insensitive match).
|
||||
//
|
||||
// Example:
|
||||
// str := NewString(t, "Hello")
|
||||
// str.NotContainsFold("BYE")
|
||||
func (s *String) NotContainsFold(value string) *String {
|
||||
if strings.Contains(strings.ToLower(s.value), strings.ToLower(value)) {
|
||||
s.chain.fail(
|
||||
"\nexpected string not containing substring (case-insensitive):\n %q"+
|
||||
"\n\nbut got:\n %q", value, s.value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Match matches the string with given regexp and returns a new Match object
|
||||
// with found submatches.
|
||||
//
|
||||
// If regexp is invalid or string doesn't match regexp, Match fails and returns
|
||||
// empty (but non-nil) object. regexp.Compile is used to construct regexp, and
|
||||
// Regexp.FindStringSubmatch is used to construct matches.
|
||||
//
|
||||
// Example:
|
||||
// s := NewString(t, "http://example.com/users/john")
|
||||
// m := s.Match(`http://(?P<host>.+)/users/(?P<user>.+)`)
|
||||
//
|
||||
// m.NotEmpty()
|
||||
// m.Length().Equal(3)
|
||||
//
|
||||
// m.Index(0).Equal("http://example.com/users/john")
|
||||
// m.Index(1).Equal("example.com")
|
||||
// m.Index(2).Equal("john")
|
||||
//
|
||||
// m.Name("host").Equal("example.com")
|
||||
// m.Name("user").Equal("john")
|
||||
func (s *String) Match(re string) *Match {
|
||||
r, err := regexp.Compile(re)
|
||||
if err != nil {
|
||||
s.chain.fail(err.Error())
|
||||
return makeMatch(s.chain, nil, nil)
|
||||
}
|
||||
|
||||
m := r.FindStringSubmatch(s.value)
|
||||
if m == nil {
|
||||
s.chain.fail("\nexpected string matching regexp:\n `%s`\n\nbut got:\n %q",
|
||||
re, s.value)
|
||||
return makeMatch(s.chain, nil, nil)
|
||||
}
|
||||
|
||||
return makeMatch(s.chain, m, r.SubexpNames())
|
||||
}
|
||||
|
||||
// MatchAll find all matches in string for given regexp and returns a list
|
||||
// of found matches.
|
||||
//
|
||||
// If regexp is invalid or string doesn't match regexp, MatchAll fails and
|
||||
// returns empty (but non-nil) slice. regexp.Compile is used to construct
|
||||
// regexp, and Regexp.FindAllStringSubmatch is used to find matches.
|
||||
//
|
||||
// Example:
|
||||
// s := NewString(t,
|
||||
// "http://example.com/users/john http://example.com/users/bob")
|
||||
//
|
||||
// m := s.MatchAll(`http://(?P<host>\S+)/users/(?P<user>\S+)`)
|
||||
//
|
||||
// m[0].Name("user").Equal("john")
|
||||
// m[1].Name("user").Equal("bob")
|
||||
func (s *String) MatchAll(re string) []Match {
|
||||
r, err := regexp.Compile(re)
|
||||
if err != nil {
|
||||
s.chain.fail(err.Error())
|
||||
return []Match{}
|
||||
}
|
||||
|
||||
matches := r.FindAllStringSubmatch(s.value, -1)
|
||||
if matches == nil {
|
||||
s.chain.fail("\nexpected string matching regexp:\n `%s`\n\nbut got:\n %q",
|
||||
re, s.value)
|
||||
return []Match{}
|
||||
}
|
||||
|
||||
ret := []Match{}
|
||||
for _, m := range matches {
|
||||
ret = append(ret, *makeMatch(
|
||||
s.chain,
|
||||
m,
|
||||
r.SubexpNames()))
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// NotMatch succeeds if the string doesn't match to given regexp.
|
||||
//
|
||||
// regexp.Compile is used to construct regexp, and Regexp.MatchString
|
||||
// is used to perform match.
|
||||
//
|
||||
// Example:
|
||||
// s := NewString(t, "a")
|
||||
// s.NotMatch(`[^a]`)
|
||||
func (s *String) NotMatch(re string) *String {
|
||||
r, err := regexp.Compile(re)
|
||||
if err != nil {
|
||||
s.chain.fail(err.Error())
|
||||
return s
|
||||
}
|
||||
|
||||
if r.MatchString(s.value) {
|
||||
s.chain.fail("\nexpected string not matching regexp:\n `%s`\n\nbut got:\n %q",
|
||||
re, s.value)
|
||||
return s
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStringFailed(t *testing.T) {
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
chain.fail("fail")
|
||||
|
||||
value := &String{chain, ""}
|
||||
|
||||
value.Path("$").chain.assertFailed(t)
|
||||
value.Schema("")
|
||||
|
||||
value.DateTime()
|
||||
value.Empty()
|
||||
value.NotEmpty()
|
||||
value.Equal("")
|
||||
value.NotEqual("")
|
||||
value.EqualFold("")
|
||||
value.NotEqualFold("")
|
||||
value.Contains("")
|
||||
value.NotContains("")
|
||||
value.ContainsFold("")
|
||||
value.NotContainsFold("")
|
||||
}
|
||||
|
||||
func TestStringGetters(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewString(reporter, "foo")
|
||||
|
||||
assert.Equal(t, "foo", value.Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, "foo", value.Path("$").Raw())
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Schema(`{"type": "string"}`)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Schema(`{"type": "object"}`)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestStringEmpty(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value1 := NewString(reporter, "")
|
||||
|
||||
value1.Empty()
|
||||
value1.chain.assertOK(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value1.NotEmpty()
|
||||
value1.chain.assertFailed(t)
|
||||
value1.chain.reset()
|
||||
|
||||
value2 := NewString(reporter, "a")
|
||||
|
||||
value2.Empty()
|
||||
value2.chain.assertFailed(t)
|
||||
value2.chain.reset()
|
||||
|
||||
value2.NotEmpty()
|
||||
value2.chain.assertOK(t)
|
||||
value2.chain.reset()
|
||||
}
|
||||
|
||||
func TestStringEqual(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewString(reporter, "foo")
|
||||
|
||||
assert.Equal(t, "foo", value.Raw())
|
||||
|
||||
value.Equal("foo")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Equal("FOO")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual("FOO")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqual("foo")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestStringEqualFold(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewString(reporter, "foo")
|
||||
|
||||
value.EqualFold("foo")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.EqualFold("FOO")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.EqualFold("foo2")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqualFold("foo")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqualFold("FOO")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotEqualFold("foo2")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestStringContains(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewString(reporter, "11-foo-22")
|
||||
|
||||
value.Contains("foo")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Contains("FOO")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContains("FOO")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContains("foo")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestStringContainsFold(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewString(reporter, "11-foo-22")
|
||||
|
||||
value.ContainsFold("foo")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsFold("FOO")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.ContainsFold("foo3")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsFold("foo")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsFold("FOO")
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotContainsFold("foo3")
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
|
||||
func TestStringLength(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewString(reporter, "1234567")
|
||||
|
||||
num := value.Length()
|
||||
value.chain.assertOK(t)
|
||||
num.chain.assertOK(t)
|
||||
assert.Equal(t, 7.0, num.Raw())
|
||||
}
|
||||
|
||||
func TestStringDateTime(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value1 := NewString(reporter, "Tue, 15 Nov 1994 08:12:31 GMT")
|
||||
dt1 := value1.DateTime()
|
||||
value1.chain.assertOK(t)
|
||||
dt1.chain.assertOK(t)
|
||||
assert.True(t, time.Date(1994, 11, 15, 8, 12, 31, 0, time.UTC).Equal(dt1.Raw()))
|
||||
|
||||
value2 := NewString(reporter, "15 Nov 94 08:12 GMT")
|
||||
dt2 := value2.DateTime(time.RFC822)
|
||||
value2.chain.assertOK(t)
|
||||
dt2.chain.assertOK(t)
|
||||
assert.True(t, time.Date(1994, 11, 15, 8, 12, 0, 0, time.UTC).Equal(dt2.Raw()))
|
||||
|
||||
value3 := NewString(reporter, "bad")
|
||||
dt3 := value3.DateTime()
|
||||
value3.chain.assertFailed(t)
|
||||
dt3.chain.assertFailed(t)
|
||||
assert.True(t, time.Unix(0, 0).Equal(dt3.Raw()))
|
||||
}
|
||||
|
||||
func TestStringMatchOne(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewString(reporter, "http://example.com/users/john")
|
||||
|
||||
m1 := value.Match(`http://(?P<host>.+)/users/(?P<user>.+)`)
|
||||
m1.chain.assertOK(t)
|
||||
|
||||
assert.Equal(t,
|
||||
[]string{"http://example.com/users/john", "example.com", "john"},
|
||||
m1.submatches)
|
||||
|
||||
m2 := value.Match(`http://(.+)/users/(.+)`)
|
||||
m2.chain.assertOK(t)
|
||||
|
||||
assert.Equal(t,
|
||||
[]string{"http://example.com/users/john", "example.com", "john"},
|
||||
m2.submatches)
|
||||
}
|
||||
|
||||
func TestStringMatchAll(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewString(reporter,
|
||||
"http://example.com/users/john http://example.com/users/bob")
|
||||
|
||||
m := value.MatchAll(`http://(\S+)/users/(\S+)`)
|
||||
|
||||
assert.Equal(t, 2, len(m))
|
||||
|
||||
m[0].chain.assertOK(t)
|
||||
m[1].chain.assertOK(t)
|
||||
|
||||
assert.Equal(t,
|
||||
[]string{"http://example.com/users/john", "example.com", "john"},
|
||||
m[0].submatches)
|
||||
|
||||
assert.Equal(t,
|
||||
[]string{"http://example.com/users/bob", "example.com", "bob"},
|
||||
m[1].submatches)
|
||||
}
|
||||
|
||||
func TestStringMatchStatus(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewString(reporter, "a")
|
||||
|
||||
value.Match(`a`)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.MatchAll(`a`)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotMatch(`a`)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.Match(`[^a]`)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.MatchAll(`[^a]`)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotMatch(`[^a]`)
|
||||
value.chain.assertOK(t)
|
||||
value.chain.reset()
|
||||
|
||||
assert.Equal(t, []string{}, value.Match(`[^a]`).submatches)
|
||||
assert.Equal(t, []Match{}, value.MatchAll(`[^a]`))
|
||||
}
|
||||
|
||||
func TestStringMatchInvalid(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewString(reporter, "a")
|
||||
|
||||
value.Match(`[`)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.MatchAll(`[`)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
|
||||
value.NotMatch(`[`)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Value provides methods to inspect attached interface{} object
|
||||
// (Go representation of arbitrary JSON value) and cast it to
|
||||
// concrete type.
|
||||
type Value struct {
|
||||
chain chain
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// NewValue returns a new Value given a reporter used to report failures
|
||||
// and value to be inspected.
|
||||
//
|
||||
// reporter should not be nil, but value may be nil.
|
||||
//
|
||||
// Example:
|
||||
// value := NewValue(t, map[string]interface{}{"foo": 123})
|
||||
// value.Object()
|
||||
//
|
||||
// value := NewValue(t, []interface{}{"foo", 123})
|
||||
// value.Array()
|
||||
//
|
||||
// value := NewValue(t, "foo")
|
||||
// value.String()
|
||||
//
|
||||
// value := NewValue(t, 123)
|
||||
// value.Number()
|
||||
//
|
||||
// value := NewValue(t, true)
|
||||
// value.Boolean()
|
||||
//
|
||||
// value := NewValue(t, nil)
|
||||
// value.Null()
|
||||
func NewValue(reporter Reporter, value interface{}) *Value {
|
||||
chain := makeChain(reporter)
|
||||
if value != nil {
|
||||
value, _ = canonValue(&chain, value)
|
||||
}
|
||||
return &Value{chain, value}
|
||||
}
|
||||
|
||||
// Raw returns underlying value attached to Value.
|
||||
// This is the value originally passed to NewValue, converted to canonical form.
|
||||
//
|
||||
// Example:
|
||||
// value := NewValue(t, "foo")
|
||||
// assert.Equal(t, "foo", number.Raw().(string))
|
||||
func (v *Value) Raw() interface{} {
|
||||
return v.value
|
||||
}
|
||||
|
||||
// Path returns a new Value object for child object(s) matching given
|
||||
// JSONPath expression.
|
||||
//
|
||||
// JSONPath is a simple XPath-like query language.
|
||||
// See http://goessner.net/articles/JsonPath/.
|
||||
//
|
||||
// We currently use https://github.com/yalp/jsonpath, which implements
|
||||
// only a subset of JSONPath, yet useful for simple queries. It doesn't
|
||||
// support filters and requires double quotes for strings.
|
||||
//
|
||||
// Example 1:
|
||||
// json := `{"users": [{"name": "john"}, {"name": "bob"}]}`
|
||||
// value := NewValue(t, json)
|
||||
//
|
||||
// value.Path("$.users[0].name").String().Equal("john")
|
||||
// value.Path("$.users[1].name").String().Equal("bob")
|
||||
//
|
||||
// Example 2:
|
||||
// json := `{"yfGH2a": {"user": "john"}, "f7GsDd": {"user": "john"}}`
|
||||
// value := NewValue(t, json)
|
||||
//
|
||||
// for _, user := range value.Path("$..user").Array().Iter() {
|
||||
// user.String().Equal("john")
|
||||
// }
|
||||
func (v *Value) Path(path string) *Value {
|
||||
return getPath(&v.chain, v.value, path)
|
||||
}
|
||||
|
||||
// Schema succeeds if value matches given JSON Schema.
|
||||
//
|
||||
// JSON Schema specifies a JSON-based format to define the structure of
|
||||
// JSON data. See http://json-schema.org/.
|
||||
// We use https://github.com/xeipuuv/gojsonschema implementation.
|
||||
//
|
||||
// schema should be one of the following:
|
||||
// - go value that can be json.Marshal-ed to a valid schema
|
||||
// - type convertible to string containing valid schema
|
||||
// - type convertible to string containing valid http:// or file:// URI,
|
||||
// pointing to reachable and valid schema
|
||||
//
|
||||
// Example 1:
|
||||
// schema := `{
|
||||
// "type": "object",
|
||||
// "properties": {
|
||||
// "foo": {
|
||||
// "type": "string"
|
||||
// },
|
||||
// "bar": {
|
||||
// "type": "integer"
|
||||
// }
|
||||
// },
|
||||
// "require": ["foo", "bar"]
|
||||
// }`
|
||||
//
|
||||
// value := NewValue(t, map[string]interface{}{
|
||||
// "foo": "a",
|
||||
// "bar": 1,
|
||||
// })
|
||||
//
|
||||
// value.Schema(schema)
|
||||
//
|
||||
// Example 2:
|
||||
// value := NewValue(t, data)
|
||||
// value.Schema("http://example.com/schema.json")
|
||||
func (v *Value) Schema(schema interface{}) *Value {
|
||||
checkSchema(&v.chain, v.value, schema)
|
||||
return v
|
||||
}
|
||||
|
||||
// Object returns a new Object attached to underlying value.
|
||||
//
|
||||
// If underlying value is not an object (map[string]interface{}), failure is reported
|
||||
// and empty (but non-nil) value is returned.
|
||||
//
|
||||
// Example:
|
||||
// value := NewValue(t, map[string]interface{}{"foo": 123})
|
||||
// value.Object().ContainsKey("foo")
|
||||
func (v *Value) Object() *Object {
|
||||
data, ok := v.value.(map[string]interface{})
|
||||
if !ok {
|
||||
v.chain.fail("\nexpected object value (map or struct), but got:\n%s",
|
||||
dumpValue(v.value))
|
||||
}
|
||||
return &Object{v.chain, data}
|
||||
}
|
||||
|
||||
// Array returns a new Array attached to underlying value.
|
||||
//
|
||||
// If underlying value is not an array ([]interface{}), failure is reported and empty
|
||||
// (but non-nil) value is returned.
|
||||
//
|
||||
// Example:
|
||||
// value := NewValue(t, []interface{}{"foo", 123})
|
||||
// value.Array().Elements("foo", 123)
|
||||
func (v *Value) Array() *Array {
|
||||
data, ok := v.value.([]interface{})
|
||||
if !ok {
|
||||
v.chain.fail("\nexpected array value, but got:\n%s",
|
||||
dumpValue(v.value))
|
||||
}
|
||||
return &Array{v.chain, data}
|
||||
}
|
||||
|
||||
// String returns a new String attached to underlying value.
|
||||
//
|
||||
// If underlying value is not string, failure is reported and empty (but non-nil)
|
||||
// value is returned.
|
||||
//
|
||||
// Example:
|
||||
// value := NewValue(t, "foo")
|
||||
// value.String().EqualFold("FOO")
|
||||
func (v *Value) String() *String {
|
||||
data, ok := v.value.(string)
|
||||
if !ok {
|
||||
v.chain.fail("\nexpected string value, but got:\n%s",
|
||||
dumpValue(v.value))
|
||||
}
|
||||
return &String{v.chain, data}
|
||||
}
|
||||
|
||||
// Number returns a new Number attached to underlying value.
|
||||
//
|
||||
// If underlying value is not a number (numeric type convertible to float64), failure
|
||||
// is reported and empty (but non-nil) value is returned.
|
||||
//
|
||||
// Example:
|
||||
// value := NewValue(t, 123)
|
||||
// value.Number().InRange(100, 200)
|
||||
func (v *Value) Number() *Number {
|
||||
data, ok := v.value.(float64)
|
||||
if !ok {
|
||||
v.chain.fail("\nexpected numeric value, but got:\n%s",
|
||||
dumpValue(v.value))
|
||||
}
|
||||
return &Number{v.chain, data}
|
||||
}
|
||||
|
||||
// Boolean returns a new Boolean attached to underlying value.
|
||||
//
|
||||
// If underlying value is not a bool, failure is reported and empty (but non-nil)
|
||||
// value is returned.
|
||||
//
|
||||
// Example:
|
||||
// value := NewValue(t, true)
|
||||
// value.Boolean().True()
|
||||
func (v *Value) Boolean() *Boolean {
|
||||
data, ok := v.value.(bool)
|
||||
if !ok {
|
||||
v.chain.fail("\nexpected boolean value, but got:\n%s",
|
||||
dumpValue(v.value))
|
||||
}
|
||||
return &Boolean{v.chain, data}
|
||||
}
|
||||
|
||||
// Null succeeds if value is nil.
|
||||
//
|
||||
// Note that non-nil interface{} that points to nil value (e.g. nil slice or map)
|
||||
// is also treated as null value. Empty (non-nil) slice or map, empty string, and
|
||||
// zero number are not treated as null value.
|
||||
//
|
||||
// Example:
|
||||
// value := NewValue(t, nil)
|
||||
// value.Null()
|
||||
//
|
||||
// value := NewValue(t, []interface{}(nil))
|
||||
// value.Null()
|
||||
func (v *Value) Null() *Value {
|
||||
if v.value != nil {
|
||||
v.chain.fail("\nexpected nil value, but got:\n%s",
|
||||
dumpValue(v.value))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// NotNull succeeds if value is not nil.
|
||||
//
|
||||
// Note that non-nil interface{} that points to nil value (e.g. nil slice or map)
|
||||
// is also treated as null value. Empty (non-nil) slice or map, empty string, and
|
||||
// zero number are not treated as null value.
|
||||
//
|
||||
// Example:
|
||||
// value := NewValue(t, "")
|
||||
// value.NotNull()
|
||||
//
|
||||
// value := NewValue(t, make([]interface{}, 0)
|
||||
// value.Null()
|
||||
func (v *Value) NotNull() *Value {
|
||||
if v.value == nil {
|
||||
v.chain.fail("\nexpected non-nil value, but got:\n%s",
|
||||
dumpValue(v.value))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Equal succeeds if value is equal to another value.
|
||||
// Before comparison, both values are converted to canonical form.
|
||||
//
|
||||
// Example:
|
||||
// value := NewValue(t, "foo")
|
||||
// value.Equal("foo")
|
||||
func (v *Value) Equal(value interface{}) *Value {
|
||||
expected, ok := canonValue(&v.chain, value)
|
||||
if !ok {
|
||||
return v
|
||||
}
|
||||
if !reflect.DeepEqual(expected, v.value) {
|
||||
v.chain.fail("\nexpected value equal to:\n%s\n\nbut got:\n%s\n\ndiff:\n%s",
|
||||
dumpValue(expected),
|
||||
dumpValue(v.value),
|
||||
diffValues(expected, v.value))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// NotEqual succeeds if value is not equal to another value.
|
||||
// Before comparison, both values are converted to canonical form.
|
||||
//
|
||||
// Example:
|
||||
// value := NewValue(t, "foo")
|
||||
// value.NorEqual("bar")
|
||||
func (v *Value) NotEqual(value interface{}) *Value {
|
||||
expected, ok := canonValue(&v.chain, value)
|
||||
if !ok {
|
||||
return v
|
||||
}
|
||||
if reflect.DeepEqual(expected, v.value) {
|
||||
v.chain.fail("\nexpected value not equal to:\n%s",
|
||||
dumpValue(expected))
|
||||
}
|
||||
return v
|
||||
}
|
||||
+465
@@ -0,0 +1,465 @@
|
||||
package httpexpect
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValueFailed(t *testing.T) {
|
||||
chain := makeChain(newMockReporter(t))
|
||||
|
||||
chain.fail("fail")
|
||||
|
||||
value := &Value{chain, nil}
|
||||
|
||||
value.chain.assertFailed(t)
|
||||
|
||||
value.Path("$").chain.assertFailed(t)
|
||||
value.Schema("")
|
||||
|
||||
assert.False(t, value.Object() == nil)
|
||||
assert.False(t, value.Array() == nil)
|
||||
assert.False(t, value.String() == nil)
|
||||
assert.False(t, value.Number() == nil)
|
||||
assert.False(t, value.Boolean() == nil)
|
||||
assert.False(t, value.Path("/") == nil)
|
||||
|
||||
value.Object().chain.assertFailed(t)
|
||||
value.Array().chain.assertFailed(t)
|
||||
value.String().chain.assertFailed(t)
|
||||
value.Number().chain.assertFailed(t)
|
||||
value.Boolean().chain.assertFailed(t)
|
||||
|
||||
value.Null()
|
||||
value.NotNull()
|
||||
|
||||
value.Equal(nil)
|
||||
value.NotEqual(nil)
|
||||
}
|
||||
|
||||
func TestValueCastNull(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
var data interface{}
|
||||
|
||||
NewValue(reporter, data).Object().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Array().chain.assertFailed(t)
|
||||
NewValue(reporter, data).String().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Number().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Boolean().chain.assertFailed(t)
|
||||
NewValue(reporter, data).NotNull().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Null().chain.assertOK(t)
|
||||
}
|
||||
|
||||
func TestValueCastIndirectNull(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
var data []interface{}
|
||||
|
||||
NewValue(reporter, data).Object().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Array().chain.assertFailed(t)
|
||||
NewValue(reporter, data).String().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Number().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Boolean().chain.assertFailed(t)
|
||||
NewValue(reporter, data).NotNull().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Null().chain.assertOK(t)
|
||||
}
|
||||
|
||||
func TestValueCastBad(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data := func() {}
|
||||
|
||||
NewValue(reporter, data).Object().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Array().chain.assertFailed(t)
|
||||
NewValue(reporter, data).String().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Number().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Boolean().chain.assertFailed(t)
|
||||
NewValue(reporter, data).NotNull().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Null().chain.assertFailed(t)
|
||||
}
|
||||
|
||||
func TestValueCastObject(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data := map[string]interface{}{}
|
||||
|
||||
NewValue(reporter, data).Object().chain.assertOK(t)
|
||||
NewValue(reporter, data).Array().chain.assertFailed(t)
|
||||
NewValue(reporter, data).String().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Number().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Boolean().chain.assertFailed(t)
|
||||
NewValue(reporter, data).NotNull().chain.assertOK(t)
|
||||
NewValue(reporter, data).Null().chain.assertFailed(t)
|
||||
}
|
||||
|
||||
func TestValueCastArray(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data := []interface{}{}
|
||||
|
||||
NewValue(reporter, data).Object().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Array().chain.assertOK(t)
|
||||
NewValue(reporter, data).String().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Number().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Boolean().chain.assertFailed(t)
|
||||
NewValue(reporter, data).NotNull().chain.assertOK(t)
|
||||
NewValue(reporter, data).Null().chain.assertFailed(t)
|
||||
}
|
||||
|
||||
func TestValueCastString(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data := ""
|
||||
|
||||
NewValue(reporter, data).Object().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Array().chain.assertFailed(t)
|
||||
NewValue(reporter, data).String().chain.assertOK(t)
|
||||
NewValue(reporter, data).Number().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Boolean().chain.assertFailed(t)
|
||||
NewValue(reporter, data).NotNull().chain.assertOK(t)
|
||||
NewValue(reporter, data).Null().chain.assertFailed(t)
|
||||
}
|
||||
|
||||
func TestValueCastNumber(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data := 0.0
|
||||
|
||||
NewValue(reporter, data).Object().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Array().chain.assertFailed(t)
|
||||
NewValue(reporter, data).String().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Number().chain.assertOK(t)
|
||||
NewValue(reporter, data).Boolean().chain.assertFailed(t)
|
||||
NewValue(reporter, data).NotNull().chain.assertOK(t)
|
||||
NewValue(reporter, data).Null().chain.assertFailed(t)
|
||||
}
|
||||
|
||||
func TestValueCastBoolean(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data := false
|
||||
|
||||
NewValue(reporter, data).Object().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Array().chain.assertFailed(t)
|
||||
NewValue(reporter, data).String().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Number().chain.assertFailed(t)
|
||||
NewValue(reporter, data).Boolean().chain.assertOK(t)
|
||||
NewValue(reporter, data).NotNull().chain.assertOK(t)
|
||||
NewValue(reporter, data).Null().chain.assertFailed(t)
|
||||
}
|
||||
|
||||
func TestValueGetObject(t *testing.T) {
|
||||
type (
|
||||
myMap map[string]interface{}
|
||||
)
|
||||
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data1 := map[string]interface{}{"foo": 123.0}
|
||||
|
||||
value1 := NewValue(reporter, data1)
|
||||
inner1 := value1.Object()
|
||||
|
||||
inner1.chain.assertOK(t)
|
||||
inner1.chain.reset()
|
||||
assert.Equal(t, data1, inner1.Raw())
|
||||
|
||||
data2 := myMap{"foo": 123.0}
|
||||
|
||||
value2 := NewValue(reporter, data2)
|
||||
inner2 := value2.Object()
|
||||
|
||||
inner2.chain.assertOK(t)
|
||||
inner2.chain.reset()
|
||||
assert.Equal(t, map[string]interface{}(data2), inner2.Raw())
|
||||
}
|
||||
|
||||
func TestValueGetArray(t *testing.T) {
|
||||
type (
|
||||
myArray []interface{}
|
||||
)
|
||||
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data1 := []interface{}{"foo", 123.0}
|
||||
|
||||
value1 := NewValue(reporter, data1)
|
||||
inner1 := value1.Array()
|
||||
|
||||
inner1.chain.assertOK(t)
|
||||
inner1.chain.reset()
|
||||
assert.Equal(t, data1, inner1.Raw())
|
||||
|
||||
data2 := myArray{"foo", 123.0}
|
||||
|
||||
value2 := NewValue(reporter, data2)
|
||||
inner2 := value2.Array()
|
||||
|
||||
inner2.chain.assertOK(t)
|
||||
inner2.chain.reset()
|
||||
assert.Equal(t, []interface{}(data2), inner2.Raw())
|
||||
}
|
||||
|
||||
func TestValueGetString(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewValue(reporter, "foo")
|
||||
inner := value.String()
|
||||
|
||||
inner.chain.assertOK(t)
|
||||
inner.chain.reset()
|
||||
assert.Equal(t, "foo", inner.Raw())
|
||||
}
|
||||
|
||||
func TestValueGetNumber(t *testing.T) {
|
||||
type (
|
||||
myInt int
|
||||
)
|
||||
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data1 := 123.0
|
||||
|
||||
value1 := NewValue(reporter, data1)
|
||||
inner1 := value1.Number()
|
||||
|
||||
inner1.chain.assertOK(t)
|
||||
inner1.chain.reset()
|
||||
assert.Equal(t, data1, inner1.Raw())
|
||||
|
||||
data2 := 123
|
||||
|
||||
value2 := NewValue(reporter, data2)
|
||||
inner2 := value2.Number()
|
||||
|
||||
inner2.chain.assertOK(t)
|
||||
inner2.chain.reset()
|
||||
assert.Equal(t, float64(data2), inner2.Raw())
|
||||
|
||||
data3 := myInt(123)
|
||||
|
||||
value3 := NewValue(reporter, data3)
|
||||
inner3 := value3.Number()
|
||||
|
||||
inner3.chain.assertOK(t)
|
||||
inner3.chain.reset()
|
||||
assert.Equal(t, float64(data3), inner3.Raw())
|
||||
}
|
||||
|
||||
func TestValueGetBoolean(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value1 := NewValue(reporter, true)
|
||||
inner1 := value1.Boolean()
|
||||
|
||||
inner1.chain.assertOK(t)
|
||||
inner1.chain.reset()
|
||||
assert.Equal(t, true, inner1.Raw())
|
||||
|
||||
value2 := NewValue(reporter, false)
|
||||
inner2 := value2.Boolean()
|
||||
|
||||
inner2.chain.assertOK(t)
|
||||
inner2.chain.reset()
|
||||
assert.Equal(t, false, inner2.Raw())
|
||||
}
|
||||
|
||||
func TestValueEqual(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data1 := map[string]interface{}{"foo": "bar"}
|
||||
data2 := "baz"
|
||||
|
||||
NewValue(reporter, data1).Equal(data1).chain.assertOK(t)
|
||||
NewValue(reporter, data2).Equal(data2).chain.assertOK(t)
|
||||
|
||||
NewValue(reporter, data1).NotEqual(data1).chain.assertFailed(t)
|
||||
NewValue(reporter, data2).NotEqual(data2).chain.assertFailed(t)
|
||||
|
||||
NewValue(reporter, data1).Equal(data2).chain.assertFailed(t)
|
||||
NewValue(reporter, data2).Equal(data1).chain.assertFailed(t)
|
||||
|
||||
NewValue(reporter, data1).NotEqual(data2).chain.assertOK(t)
|
||||
NewValue(reporter, data2).NotEqual(data1).chain.assertOK(t)
|
||||
|
||||
NewValue(reporter, nil).Equal(nil).chain.assertOK(t)
|
||||
|
||||
NewValue(reporter, nil).Equal(map[string]interface{}(nil)).chain.assertOK(t)
|
||||
NewValue(reporter, nil).Equal(map[string]interface{}{}).chain.assertFailed(t)
|
||||
|
||||
NewValue(reporter, data1).Equal(func() {}).chain.assertFailed(t)
|
||||
NewValue(reporter, data1).NotEqual(func() {}).chain.assertFailed(t)
|
||||
}
|
||||
|
||||
func TestValuePathObject(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
user0 := map[string]interface{}{"name": "john"}
|
||||
user1 := map[string]interface{}{"name": "bob"}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"users": []interface{}{
|
||||
user0,
|
||||
user1,
|
||||
},
|
||||
}
|
||||
|
||||
value := NewValue(reporter, data)
|
||||
|
||||
assert.Equal(t, data, value.Path("$").Raw())
|
||||
assert.Equal(t, data["users"], value.Path("$.users").Raw())
|
||||
assert.Equal(t, user0, value.Path("$.users[0]").Raw())
|
||||
assert.Equal(t, "john", value.Path("$.users[0].name").Raw())
|
||||
assert.Equal(t, []interface{}{"john", "bob"}, value.Path("$.users[*].name").Raw())
|
||||
assert.Equal(t, []interface{}{"john", "bob"}, value.Path("$..name").Raw())
|
||||
value.chain.assertOK(t)
|
||||
|
||||
names := value.Path("$..name").Array().Iter()
|
||||
names[0].String().Equal("john").chain.assertOK(t)
|
||||
names[1].String().Equal("bob").chain.assertOK(t)
|
||||
value.chain.assertOK(t)
|
||||
|
||||
for _, key := range []string{"$.bad", "!"} {
|
||||
bad := value.Path(key)
|
||||
assert.True(t, bad != nil)
|
||||
assert.True(t, bad.Raw() == nil)
|
||||
value.chain.assertFailed(t)
|
||||
value.chain.reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestValuePathArray(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
user0 := map[string]interface{}{"name": "john"}
|
||||
user1 := map[string]interface{}{"name": "bob"}
|
||||
|
||||
data := []interface{}{
|
||||
user0,
|
||||
user1,
|
||||
}
|
||||
|
||||
value := NewValue(reporter, data)
|
||||
|
||||
assert.Equal(t, data, value.Path("$").Raw())
|
||||
assert.Equal(t, user0, value.Path("$[0]").Raw())
|
||||
assert.Equal(t, "john", value.Path("$[0].name").Raw())
|
||||
assert.Equal(t, []interface{}{"john", "bob"}, value.Path("$[*].name").Raw())
|
||||
assert.Equal(t, []interface{}{"john", "bob"}, value.Path("$..name").Raw())
|
||||
value.chain.assertOK(t)
|
||||
}
|
||||
|
||||
func TestValuePathString(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data := "foo"
|
||||
|
||||
value := NewValue(reporter, data)
|
||||
|
||||
assert.Equal(t, data, value.Path("$").Raw())
|
||||
value.chain.assertOK(t)
|
||||
}
|
||||
|
||||
func TestValuePathNumber(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data := 123
|
||||
|
||||
value := NewValue(reporter, data)
|
||||
|
||||
assert.Equal(t, float64(data), value.Path("$").Raw())
|
||||
value.chain.assertOK(t)
|
||||
}
|
||||
|
||||
func TestValuePathBoolean(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data := true
|
||||
|
||||
value := NewValue(reporter, data)
|
||||
|
||||
assert.Equal(t, data, value.Path("$").Raw())
|
||||
value.chain.assertOK(t)
|
||||
}
|
||||
|
||||
func TestValuePathNull(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
value := NewValue(reporter, nil)
|
||||
|
||||
assert.Equal(t, nil, value.Path("$").Raw())
|
||||
value.chain.assertOK(t)
|
||||
}
|
||||
|
||||
func TestValuePathError(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
data := "foo"
|
||||
|
||||
value := NewValue(reporter, data)
|
||||
|
||||
for _, key := range []string{"$.bad", "!"} {
|
||||
bad := value.Path(key)
|
||||
assert.True(t, bad != nil)
|
||||
assert.True(t, bad.Raw() == nil)
|
||||
value.chain.assertFailed(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueSchema(t *testing.T) {
|
||||
reporter := newMockReporter(t)
|
||||
|
||||
schema := `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
},
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"require": ["foo", "bar"]
|
||||
}`
|
||||
|
||||
data1 := map[string]interface{}{
|
||||
"foo": "a",
|
||||
"bar": 1,
|
||||
}
|
||||
|
||||
data2 := map[string]interface{}{
|
||||
"foo": "a",
|
||||
"bar": "b",
|
||||
}
|
||||
|
||||
NewValue(reporter, data1).Schema(schema).chain.assertOK(t)
|
||||
NewValue(reporter, data2).Schema(schema).chain.assertFailed(t)
|
||||
|
||||
NewValue(reporter, data1).Schema([]byte(schema)).chain.assertOK(t)
|
||||
NewValue(reporter, data2).Schema([]byte(schema)).chain.assertFailed(t)
|
||||
|
||||
var b interface{}
|
||||
json.Unmarshal([]byte(schema), &b)
|
||||
|
||||
NewValue(reporter, data1).Schema(b).chain.assertOK(t)
|
||||
NewValue(reporter, data2).Schema(b).chain.assertFailed(t)
|
||||
|
||||
tmp, _ := ioutil.TempFile("", "httpexpect")
|
||||
defer os.Remove(tmp.Name())
|
||||
|
||||
tmp.Write([]byte(schema))
|
||||
tmp.Close()
|
||||
|
||||
url := "file://" + tmp.Name()
|
||||
|
||||
NewValue(reporter, data1).Schema(url).chain.assertOK(t)
|
||||
NewValue(reporter, data2).Schema(url).chain.assertFailed(t)
|
||||
|
||||
NewValue(reporter, data1).Schema("file:///bad/path").chain.assertFailed(t)
|
||||
NewValue(reporter, data1).Schema("{ bad json").chain.assertFailed(t)
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.7
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go get -v -t .
|
||||
- go test -v .
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# monotime [](https://godoc.org/github.com/gavv/monotime) [](https://travis-ci.org/gavv/monotime)
|
||||
|
||||
This tiny Go package is a standalone and slightly enhanced version of [`goarista/monotime`](https://github.com/aristanetworks/goarista#monotime).
|
||||
|
||||
It provides `monotime.Now()` function, which returns current time from monotonic clock source. It's implemented using unexported `runtime.nanotime()` function from Go runtime. It works on all platforms.
|
||||
|
||||
## Not needed for Go 1.9+
|
||||
|
||||
Starting from Go 1.9, the standad `time` package transparently uses [Monotonic Clocks](https://golang.org/pkg/time/#hdr-Monotonic_Clocks) when necessary, so this package is no longer relevant.
|
||||
|
||||
## Synopsis
|
||||
|
||||
In Go versions before 1.9, `time.Now()` function from standard library returns *real time* (`CLOCK_REALTIME` in POSIX) which can jump forwards and backwards as the system time is changed.
|
||||
|
||||
For time measurements, *monotonic time* (`CLOCK_MONOTONIC` or `CLOCK_MONOTONIC_RAW` on Linux) is often preferred, which is strictly increasing, without (notable) jumps.
|
||||
|
||||
## Documentation
|
||||
|
||||
See [GoDoc](https://godoc.org/github.com/gavv/monotime).
|
||||
|
||||
## Usage example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gavv/monotime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var start, elapsed time.Duration
|
||||
|
||||
start = monotime.Now()
|
||||
time.Sleep(time.Millisecond)
|
||||
elapsed = monotime.Since(start)
|
||||
|
||||
fmt.Println(elapsed)
|
||||
// Prints: 1.062759ms
|
||||
}
|
||||
```
|
||||
|
||||
## Similar packages
|
||||
|
||||
* [`aristanetworks/goarista/monotime`](https://github.com/aristanetworks/goarista#monotime) (this package is based on it)
|
||||
* [`spacemonkeygo/monotime`](https://github.com/spacemonkeygo/monotime) (current `runtime.nanotime()` is more complete)
|
||||
* [`davecheney/junk/clock`](https://github.com/davecheney/junk/tree/master/clock) (Linux-only)
|
||||
* [`jaracil/clk`](https://github.com/jaracil/clk) (Linux-only)
|
||||
|
||||
## License
|
||||
|
||||
[Apache 2.0](https://github.com/gavv/monotime/blob/master/LICENSE)
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2016 Arista Networks, Inc.
|
||||
// Use of this source code is governed by the Apache License 2.0
|
||||
// that can be found in the COPYING file.
|
||||
|
||||
// Package monotime provides functions to access monotonic clock source.
|
||||
package monotime
|
||||
|
||||
import (
|
||||
"time"
|
||||
_ "unsafe" // required to use //go:linkname
|
||||
)
|
||||
|
||||
//go:noescape
|
||||
//go:linkname nanotime runtime.nanotime
|
||||
func nanotime() int64
|
||||
|
||||
// Now returns the current time in nanoseconds from a monotonic clock.
|
||||
//
|
||||
// The time returned is based on some arbitrary platform-specific point in the
|
||||
// past. The time returned is guaranteed to increase monotonically without
|
||||
// notable jumps, unlike time.Now() from the Go standard library, which may
|
||||
// jump forward or backward significantly due to system time changes or leap
|
||||
// seconds.
|
||||
//
|
||||
// It's implemented using runtime.nanotime(), which uses CLOCK_MONOTONIC on
|
||||
// Linux. Note that unlike CLOCK_MONOTONIC_RAW, CLOCK_MONOTONIC is affected
|
||||
// by time changes. However, time changes never cause clock jumps; instead,
|
||||
// clock frequency is adjusted slowly.
|
||||
func Now() time.Duration {
|
||||
return time.Duration(nanotime())
|
||||
}
|
||||
|
||||
// Since returns the time elapsed since t, obtained previously using Now.
|
||||
func Since(t time.Duration) time.Duration {
|
||||
return Now() - t
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// Copyright (C) 2016 Arista Networks, Inc.
|
||||
// Use of this source code is governed by the Apache License 2.0
|
||||
// that can be found in the COPYING file.
|
||||
|
||||
// This file is intentionally empty.
|
||||
// It's a workaround for https://github.com/golang/go/issues/15006
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2016 Arista Networks, Inc.
|
||||
// Use of this source code is governed by the Apache License 2.0
|
||||
// that can be found in the COPYING file.
|
||||
|
||||
package monotime
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNow(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
t1 := Now()
|
||||
t2 := Now()
|
||||
// I honestly thought that we needed >= here, but in some environments
|
||||
// two consecutive calls can return the same value!
|
||||
if t1 > t2 {
|
||||
t.Fatalf("t1=%d should have been less than or equal to t2=%d", t1, t2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSince(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
ts := Now()
|
||||
d := Since(ts)
|
||||
if d < 0 {
|
||||
t.Fatalf("d=%d should be greater than or equal to zero", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user