Tons of unit tests

This commit is contained in:
Manu Mtz-Almeida 2015-04-09 12:15:02 +02:00
parent ac1ee3fb86
commit 0a192fb0fa
26 changed files with 1477 additions and 86 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
Godeps/* Godeps/*
!Godeps/Godeps.json !Godeps/Godeps.json
coverage.out
count.out

View File

@ -73,6 +73,10 @@ func TestBasicAuthSearchCredential(t *testing.T) {
user, found = pairs.searchCredential(authorizationHeader("foo", "bar ")) user, found = pairs.searchCredential(authorizationHeader("foo", "bar "))
assert.Empty(t, user) assert.Empty(t, user)
assert.False(t, found) assert.False(t, found)
user, found = pairs.searchCredential("")
assert.Empty(t, user)
assert.False(t, found)
} }
func TestBasicAuthAuthorizationHeader(t *testing.T) { func TestBasicAuthAuthorizationHeader(t *testing.T) {

View File

@ -50,3 +50,10 @@ func Default(method, contentType string) Binding {
} }
} }
} }
func Validate(obj interface{}) error {
if err := _validator.ValidateStruct(obj); err != nil {
return error(err)
}
return nil
}

79
binding/binding_test.go Normal file
View File

@ -0,0 +1,79 @@
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package binding
import (
"bytes"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
type FooStruct struct {
Foo string `json:"foo" form:"foo" xml:"foo" binding:"required"`
}
func TestBindingDefault(t *testing.T) {
assert.Equal(t, Default("GET", ""), GETForm)
assert.Equal(t, Default("GET", MIMEJSON), GETForm)
assert.Equal(t, Default("POST", MIMEJSON), JSON)
assert.Equal(t, Default("PUT", MIMEJSON), JSON)
assert.Equal(t, Default("POST", MIMEXML), XML)
assert.Equal(t, Default("PUT", MIMEXML2), XML)
assert.Equal(t, Default("POST", MIMEPOSTForm), POSTForm)
assert.Equal(t, Default("DELETE", MIMEPOSTForm), POSTForm)
}
func TestBindingJSON(t *testing.T) {
testBinding(t,
JSON, "json",
"/", "/",
`{"foo": "bar"}`, `{"bar": "foo"}`)
}
func TestBindingPOSTForm(t *testing.T) {
testBinding(t,
POSTForm, "post_form",
"/", "/",
"foo=bar", "bar=foo")
}
func TestBindingGETForm(t *testing.T) {
testBinding(t,
GETForm, "get_form",
"/?foo=bar", "/?bar=foo",
"", "")
}
func TestBindingXML(t *testing.T) {
testBinding(t,
XML, "xml",
"/", "/",
"<map><foo>bar</foo></map>", "<map><bar>foo</bar></map>")
}
func testBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, b.Name(), name)
obj := FooStruct{}
req := requestWithBody(path, body)
err := b.Bind(req, &obj)
assert.NoError(t, err)
assert.Equal(t, obj.Foo, "bar")
obj = FooStruct{}
req = requestWithBody(badPath, badBody)
err = JSON.Bind(req, &obj)
assert.Error(t, err)
}
func requestWithBody(path, body string) (req *http.Request) {
req, _ = http.NewRequest("POST", path, bytes.NewBufferString(body))
return
}

View File

@ -19,8 +19,5 @@ func (_ getFormBinding) Bind(req *http.Request, obj interface{}) error {
if err := mapForm(obj, req.Form); err != nil { if err := mapForm(obj, req.Form); err != nil {
return err return err
} }
if err := _validator.ValidateStruct(obj); err != nil { return Validate(obj)
return error(err)
}
return nil
} }

View File

@ -21,8 +21,5 @@ func (_ jsonBinding) Bind(req *http.Request, obj interface{}) error {
if err := decoder.Decode(obj); err != nil { if err := decoder.Decode(obj); err != nil {
return err return err
} }
if err := _validator.ValidateStruct(obj); err != nil { return Validate(obj)
return error(err)
}
return nil
} }

View File

@ -19,8 +19,5 @@ func (_ postFormBinding) Bind(req *http.Request, obj interface{}) error {
if err := mapForm(obj, req.PostForm); err != nil { if err := mapForm(obj, req.PostForm); err != nil {
return err return err
} }
if err := _validator.ValidateStruct(obj); err != nil { return Validate(obj)
return error(err)
}
return nil
} }

53
binding/validate_test.go Normal file
View File

@ -0,0 +1,53 @@
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package binding
import (
"testing"
"github.com/stretchr/testify/assert"
)
type struct1 struct {
Value float64 `binding:"required"`
}
type struct2 struct {
RequiredValue string `binding:"required"`
Value float64
}
type struct3 struct {
Integer int
String string
BasicSlice []int
Boolean bool
RequiredInteger int `binding:"required"`
RequiredString string `binding:"required"`
RequiredAnotherStruct struct1 `binding:"required"`
RequiredBasicSlice []int `binding:"required"`
RequiredComplexSlice []struct2 `binding:"required"`
RequiredBoolean bool `binding:"required"`
}
func createStruct() struct3 {
return struct3{
RequiredInteger: 2,
RequiredString: "hello",
RequiredAnotherStruct: struct1{1.5},
RequiredBasicSlice: []int{1, 2, 3, 4},
RequiredComplexSlice: []struct2{
{RequiredValue: "A"},
{RequiredValue: "B"},
},
RequiredBoolean: true,
}
}
func TestValidateGoodObject(t *testing.T) {
test := createStruct()
assert.Nil(t, Validate(&test))
}

View File

@ -20,8 +20,5 @@ func (_ xmlBinding) Bind(req *http.Request, obj interface{}) error {
if err := decoder.Decode(obj); err != nil { if err := decoder.Decode(obj); err != nil {
return err return err
} }
if err := _validator.ValidateStruct(obj); err != nil { return Validate(obj)
return error(err)
}
return nil
} }

View File

@ -61,6 +61,9 @@ func (c *Context) reset() {
func (c *Context) Copy() *Context { func (c *Context) Copy() *Context {
var cp Context = *c var cp Context = *c
cp.writermem.ResponseWriter = nil
cp.Writer = &cp.writermem
cp.Input.context = &cp
cp.index = AbortIndex cp.index = AbortIndex
cp.handlers = nil cp.handlers = nil
return &cp return &cp
@ -161,7 +164,7 @@ func (c *Context) MustGet(key string) interface{} {
if value, exists := c.Get(key); exists { if value, exists := c.Get(key); exists {
return value return value
} else { } else {
panic("Key " + key + " does not exist") panic("Key \"" + key + "\" does not exist")
} }
} }

View File

@ -16,6 +16,11 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// Unit tes TODO
// func (c *Context) File(filepath string) {
// func (c *Context) Negotiate(code int, config Negotiate) {
// BAD case: func (c *Context) Render(code int, render render.Render, obj ...interface{}) {
func createTestContext() (c *Context, w *httptest.ResponseRecorder, r *Engine) { func createTestContext() (c *Context, w *httptest.ResponseRecorder, r *Engine) {
w = httptest.NewRecorder() w = httptest.NewRecorder()
r = New() r = New()
@ -64,6 +69,25 @@ func TestContextSetGet(t *testing.T) {
assert.Panics(t, func() { c.MustGet("no_exist") }) assert.Panics(t, func() { c.MustGet("no_exist") })
} }
func TestContextCopy(t *testing.T) {
c, _, _ := createTestContext()
c.index = 2
c.Request, _ = http.NewRequest("POST", "/hola", nil)
c.handlers = []HandlerFunc{func(c *Context) {}}
c.Params = Params{Param{Key: "foo", Value: "bar"}}
c.Set("foo", "bar")
cp := c.Copy()
assert.Nil(t, cp.handlers)
assert.Equal(t, cp.Request, c.Request)
assert.Equal(t, cp.index, AbortIndex)
assert.Equal(t, &cp.writermem, cp.Writer.(*responseWriter))
assert.Equal(t, cp.Input.context, cp)
assert.Equal(t, cp.Keys, c.Keys)
assert.Equal(t, cp.Engine, c.Engine)
assert.Equal(t, cp.Params, c.Params)
}
// Tests that the response is serialized as JSON // Tests that the response is serialized as JSON
// and Content-Type is set to application/json // and Content-Type is set to application/json
func TestContextRenderJSON(t *testing.T) { func TestContextRenderJSON(t *testing.T) {
@ -79,7 +103,7 @@ func TestContextRenderJSON(t *testing.T) {
// and responds with Content-Type set to text/html // and responds with Content-Type set to text/html
func TestContextRenderHTML(t *testing.T) { func TestContextRenderHTML(t *testing.T) {
c, w, router := createTestContext() c, w, router := createTestContext()
templ, _ := template.New("t").Parse(`Hello {{.name}}`) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
c.HTML(201, "t", H{"name": "alexandernyquist"}) c.HTML(201, "t", H{"name": "alexandernyquist"})
@ -160,6 +184,7 @@ func TestContextNegotiationFormat(t *testing.T) {
c, _, _ := createTestContext() c, _, _ := createTestContext()
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "", nil)
assert.Panics(t, func() { c.NegotiateFormat() })
assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON) assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON)
assert.Equal(t, c.NegotiateFormat(MIMEHTML, MIMEJSON), MIMEHTML) assert.Equal(t, c.NegotiateFormat(MIMEHTML, MIMEJSON), MIMEHTML)
} }
@ -203,13 +228,19 @@ func TestContextAbortWithStatus(t *testing.T) {
func TestContextError(t *testing.T) { func TestContextError(t *testing.T) {
c, _, _ := createTestContext() c, _, _ := createTestContext()
assert.Nil(t, c.LastError())
assert.Empty(t, c.Errors.String())
c.Error(errors.New("first error"), "some data") c.Error(errors.New("first error"), "some data")
assert.Equal(t, c.LastError().Error(), "first error") assert.Equal(t, c.LastError().Error(), "first error")
assert.Len(t, c.Errors, 1) assert.Len(t, c.Errors, 1)
assert.Equal(t, c.Errors.String(), "Error #01: first error\n Meta: some data\n")
c.Error(errors.New("second error"), "some data 2") c.Error(errors.New("second error"), "some data 2")
assert.Equal(t, c.LastError().Error(), "second error") assert.Equal(t, c.LastError().Error(), "second error")
assert.Len(t, c.Errors, 2) assert.Len(t, c.Errors, 2)
assert.Equal(t, c.Errors.String(), "Error #01: first error\n Meta: some data\n"+
"Error #02: second error\n Meta: some data 2\n")
assert.Equal(t, c.Errors[0].Err, "first error") assert.Equal(t, c.Errors[0].Err, "first error")
assert.Equal(t, c.Errors[0].Meta, "some data") assert.Equal(t, c.Errors[0].Meta, "some data")

View File

@ -10,6 +10,10 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// TODO
// func debugRoute(httpMethod, absolutePath string, handlers []HandlerFunc) {
// func debugPrint(format string, values ...interface{}) {
func TestIsDebugging(t *testing.T) { func TestIsDebugging(t *testing.T) {
SetMode(DebugMode) SetMode(DebugMode)
assert.True(t, IsDebugging()) assert.True(t, IsDebugging())

View File

@ -1,49 +0,0 @@
package main
import (
"net/http"
"github.com/flosch/pongo2"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/render"
)
func main() {
router := gin.Default()
router.HTMLRender = newPongoRender()
router.GET("/index", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{
"title": "Gin meets pongo2 !",
"name": c.Input.Get("name"),
})
})
router.Run(":8080")
}
type pongoRender struct {
cache map[string]*pongo2.Template
}
func newPongoRender() *pongoRender {
return &pongoRender{map[string]*pongo2.Template{}}
}
func (p *pongoRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
file := data[0].(string)
ctx := data[1].(pongo2.Context)
var t *pongo2.Template
if tmpl, ok := p.cache[file]; ok {
t = tmpl
} else {
tmpl, err := pongo2.FromFile(file)
if err != nil {
return err
}
p.cache[file] = tmpl
t = tmpl
}
render.WriteHeader(w, code, "text/html")
return t.ExecuteWriter(ctx, w)
}

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
<meta name="keywords" content="">
<meta name="description" content="">
</head>
<body>
Hello {{ name }} !
</body>
</html>

7
gin.go
View File

@ -144,6 +144,13 @@ func (engine *Engine) handle(method, path string, handlers []HandlerFunc) {
if path[0] != '/' { if path[0] != '/' {
panic("path must begin with '/'") panic("path must begin with '/'")
} }
if method == "" {
panic("HTTP method can not be empty")
}
if len(handlers) == 0 {
panic("there must be at least one handler")
}
root := engine.trees[method] root := engine.trees[method]
if root == nil { if root == nil {
root = new(node) root = new(node)

View File

@ -10,6 +10,12 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
//TODO
// func (engine *Engine) LoadHTMLGlob(pattern string) {
// func (engine *Engine) LoadHTMLFiles(files ...string) {
// func (engine *Engine) Run(addr string) error {
// func (engine *Engine) RunTLS(addr string, cert string, key string) error {
func init() { func init() {
SetMode(TestMode) SetMode(TestMode)
} }
@ -20,9 +26,9 @@ func TestCreateEngine(t *testing.T) {
assert.Equal(t, router.engine, router) assert.Equal(t, router.engine, router)
assert.Empty(t, router.Handlers) assert.Empty(t, router.Handlers)
// TODO assert.Panics(t, func() { router.handle("", "/", []HandlerFunc{func(_ *Context) {}}) })
// assert.Equal(t, router.router.NotFound, router.handle404) assert.Panics(t, func() { router.handle("GET", "", []HandlerFunc{func(_ *Context) {}}) })
// assert.Equal(t, router.router.MethodNotAllowed, router.handle405) assert.Panics(t, func() { router.handle("GET", "/", []HandlerFunc{}) })
} }
func TestCreateDefaultRouter(t *testing.T) { func TestCreateDefaultRouter(t *testing.T) {

344
githubapi_test.go Normal file
View File

@ -0,0 +1,344 @@
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package gin
import (
"bytes"
"fmt"
"math/rand"
"testing"
"github.com/stretchr/testify/assert"
)
type route struct {
method string
path string
}
// http://developer.github.com/v3/
var githubAPI = []route{
// OAuth Authorizations
{"GET", "/authorizations"},
{"GET", "/authorizations/:id"},
{"POST", "/authorizations"},
//{"PUT", "/authorizations/clients/:client_id"},
//{"PATCH", "/authorizations/:id"},
{"DELETE", "/authorizations/:id"},
{"GET", "/applications/:client_id/tokens/:access_token"},
{"DELETE", "/applications/:client_id/tokens"},
{"DELETE", "/applications/:client_id/tokens/:access_token"},
// Activity
{"GET", "/events"},
{"GET", "/repos/:owner/:repo/events"},
{"GET", "/networks/:owner/:repo/events"},
{"GET", "/orgs/:org/events"},
{"GET", "/users/:user/received_events"},
{"GET", "/users/:user/received_events/public"},
{"GET", "/users/:user/events"},
{"GET", "/users/:user/events/public"},
{"GET", "/users/:user/events/orgs/:org"},
{"GET", "/feeds"},
{"GET", "/notifications"},
{"GET", "/repos/:owner/:repo/notifications"},
{"PUT", "/notifications"},
{"PUT", "/repos/:owner/:repo/notifications"},
{"GET", "/notifications/threads/:id"},
//{"PATCH", "/notifications/threads/:id"},
{"GET", "/notifications/threads/:id/subscription"},
{"PUT", "/notifications/threads/:id/subscription"},
{"DELETE", "/notifications/threads/:id/subscription"},
{"GET", "/repos/:owner/:repo/stargazers"},
{"GET", "/users/:user/starred"},
{"GET", "/user/starred"},
{"GET", "/user/starred/:owner/:repo"},
{"PUT", "/user/starred/:owner/:repo"},
{"DELETE", "/user/starred/:owner/:repo"},
{"GET", "/repos/:owner/:repo/subscribers"},
{"GET", "/users/:user/subscriptions"},
{"GET", "/user/subscriptions"},
{"GET", "/repos/:owner/:repo/subscription"},
{"PUT", "/repos/:owner/:repo/subscription"},
{"DELETE", "/repos/:owner/:repo/subscription"},
{"GET", "/user/subscriptions/:owner/:repo"},
{"PUT", "/user/subscriptions/:owner/:repo"},
{"DELETE", "/user/subscriptions/:owner/:repo"},
// Gists
{"GET", "/users/:user/gists"},
{"GET", "/gists"},
//{"GET", "/gists/public"},
//{"GET", "/gists/starred"},
{"GET", "/gists/:id"},
{"POST", "/gists"},
//{"PATCH", "/gists/:id"},
{"PUT", "/gists/:id/star"},
{"DELETE", "/gists/:id/star"},
{"GET", "/gists/:id/star"},
{"POST", "/gists/:id/forks"},
{"DELETE", "/gists/:id"},
// Git Data
{"GET", "/repos/:owner/:repo/git/blobs/:sha"},
{"POST", "/repos/:owner/:repo/git/blobs"},
{"GET", "/repos/:owner/:repo/git/commits/:sha"},
{"POST", "/repos/:owner/:repo/git/commits"},
//{"GET", "/repos/:owner/:repo/git/refs/*ref"},
{"GET", "/repos/:owner/:repo/git/refs"},
{"POST", "/repos/:owner/:repo/git/refs"},
//{"PATCH", "/repos/:owner/:repo/git/refs/*ref"},
//{"DELETE", "/repos/:owner/:repo/git/refs/*ref"},
{"GET", "/repos/:owner/:repo/git/tags/:sha"},
{"POST", "/repos/:owner/:repo/git/tags"},
{"GET", "/repos/:owner/:repo/git/trees/:sha"},
{"POST", "/repos/:owner/:repo/git/trees"},
// Issues
{"GET", "/issues"},
{"GET", "/user/issues"},
{"GET", "/orgs/:org/issues"},
{"GET", "/repos/:owner/:repo/issues"},
{"GET", "/repos/:owner/:repo/issues/:number"},
{"POST", "/repos/:owner/:repo/issues"},
//{"PATCH", "/repos/:owner/:repo/issues/:number"},
{"GET", "/repos/:owner/:repo/assignees"},
{"GET", "/repos/:owner/:repo/assignees/:assignee"},
{"GET", "/repos/:owner/:repo/issues/:number/comments"},
//{"GET", "/repos/:owner/:repo/issues/comments"},
//{"GET", "/repos/:owner/:repo/issues/comments/:id"},
{"POST", "/repos/:owner/:repo/issues/:number/comments"},
//{"PATCH", "/repos/:owner/:repo/issues/comments/:id"},
//{"DELETE", "/repos/:owner/:repo/issues/comments/:id"},
{"GET", "/repos/:owner/:repo/issues/:number/events"},
//{"GET", "/repos/:owner/:repo/issues/events"},
//{"GET", "/repos/:owner/:repo/issues/events/:id"},
{"GET", "/repos/:owner/:repo/labels"},
{"GET", "/repos/:owner/:repo/labels/:name"},
{"POST", "/repos/:owner/:repo/labels"},
//{"PATCH", "/repos/:owner/:repo/labels/:name"},
{"DELETE", "/repos/:owner/:repo/labels/:name"},
{"GET", "/repos/:owner/:repo/issues/:number/labels"},
{"POST", "/repos/:owner/:repo/issues/:number/labels"},
{"DELETE", "/repos/:owner/:repo/issues/:number/labels/:name"},
{"PUT", "/repos/:owner/:repo/issues/:number/labels"},
{"DELETE", "/repos/:owner/:repo/issues/:number/labels"},
{"GET", "/repos/:owner/:repo/milestones/:number/labels"},
{"GET", "/repos/:owner/:repo/milestones"},
{"GET", "/repos/:owner/:repo/milestones/:number"},
{"POST", "/repos/:owner/:repo/milestones"},
//{"PATCH", "/repos/:owner/:repo/milestones/:number"},
{"DELETE", "/repos/:owner/:repo/milestones/:number"},
// Miscellaneous
{"GET", "/emojis"},
{"GET", "/gitignore/templates"},
{"GET", "/gitignore/templates/:name"},
{"POST", "/markdown"},
{"POST", "/markdown/raw"},
{"GET", "/meta"},
{"GET", "/rate_limit"},
// Organizations
{"GET", "/users/:user/orgs"},
{"GET", "/user/orgs"},
{"GET", "/orgs/:org"},
//{"PATCH", "/orgs/:org"},
{"GET", "/orgs/:org/members"},
{"GET", "/orgs/:org/members/:user"},
{"DELETE", "/orgs/:org/members/:user"},
{"GET", "/orgs/:org/public_members"},
{"GET", "/orgs/:org/public_members/:user"},
{"PUT", "/orgs/:org/public_members/:user"},
{"DELETE", "/orgs/:org/public_members/:user"},
{"GET", "/orgs/:org/teams"},
{"GET", "/teams/:id"},
{"POST", "/orgs/:org/teams"},
//{"PATCH", "/teams/:id"},
{"DELETE", "/teams/:id"},
{"GET", "/teams/:id/members"},
{"GET", "/teams/:id/members/:user"},
{"PUT", "/teams/:id/members/:user"},
{"DELETE", "/teams/:id/members/:user"},
{"GET", "/teams/:id/repos"},
{"GET", "/teams/:id/repos/:owner/:repo"},
{"PUT", "/teams/:id/repos/:owner/:repo"},
{"DELETE", "/teams/:id/repos/:owner/:repo"},
{"GET", "/user/teams"},
// Pull Requests
{"GET", "/repos/:owner/:repo/pulls"},
{"GET", "/repos/:owner/:repo/pulls/:number"},
{"POST", "/repos/:owner/:repo/pulls"},
//{"PATCH", "/repos/:owner/:repo/pulls/:number"},
{"GET", "/repos/:owner/:repo/pulls/:number/commits"},
{"GET", "/repos/:owner/:repo/pulls/:number/files"},
{"GET", "/repos/:owner/:repo/pulls/:number/merge"},
{"PUT", "/repos/:owner/:repo/pulls/:number/merge"},
{"GET", "/repos/:owner/:repo/pulls/:number/comments"},
//{"GET", "/repos/:owner/:repo/pulls/comments"},
//{"GET", "/repos/:owner/:repo/pulls/comments/:number"},
{"PUT", "/repos/:owner/:repo/pulls/:number/comments"},
//{"PATCH", "/repos/:owner/:repo/pulls/comments/:number"},
//{"DELETE", "/repos/:owner/:repo/pulls/comments/:number"},
// Repositories
{"GET", "/user/repos"},
{"GET", "/users/:user/repos"},
{"GET", "/orgs/:org/repos"},
{"GET", "/repositories"},
{"POST", "/user/repos"},
{"POST", "/orgs/:org/repos"},
{"GET", "/repos/:owner/:repo"},
//{"PATCH", "/repos/:owner/:repo"},
{"GET", "/repos/:owner/:repo/contributors"},
{"GET", "/repos/:owner/:repo/languages"},
{"GET", "/repos/:owner/:repo/teams"},
{"GET", "/repos/:owner/:repo/tags"},
{"GET", "/repos/:owner/:repo/branches"},
{"GET", "/repos/:owner/:repo/branches/:branch"},
{"DELETE", "/repos/:owner/:repo"},
{"GET", "/repos/:owner/:repo/collaborators"},
{"GET", "/repos/:owner/:repo/collaborators/:user"},
{"PUT", "/repos/:owner/:repo/collaborators/:user"},
{"DELETE", "/repos/:owner/:repo/collaborators/:user"},
{"GET", "/repos/:owner/:repo/comments"},
{"GET", "/repos/:owner/:repo/commits/:sha/comments"},
{"POST", "/repos/:owner/:repo/commits/:sha/comments"},
{"GET", "/repos/:owner/:repo/comments/:id"},
//{"PATCH", "/repos/:owner/:repo/comments/:id"},
{"DELETE", "/repos/:owner/:repo/comments/:id"},
{"GET", "/repos/:owner/:repo/commits"},
{"GET", "/repos/:owner/:repo/commits/:sha"},
{"GET", "/repos/:owner/:repo/readme"},
//{"GET", "/repos/:owner/:repo/contents/*path"},
//{"PUT", "/repos/:owner/:repo/contents/*path"},
//{"DELETE", "/repos/:owner/:repo/contents/*path"},
//{"GET", "/repos/:owner/:repo/:archive_format/:ref"},
{"GET", "/repos/:owner/:repo/keys"},
{"GET", "/repos/:owner/:repo/keys/:id"},
{"POST", "/repos/:owner/:repo/keys"},
//{"PATCH", "/repos/:owner/:repo/keys/:id"},
{"DELETE", "/repos/:owner/:repo/keys/:id"},
{"GET", "/repos/:owner/:repo/downloads"},
{"GET", "/repos/:owner/:repo/downloads/:id"},
{"DELETE", "/repos/:owner/:repo/downloads/:id"},
{"GET", "/repos/:owner/:repo/forks"},
{"POST", "/repos/:owner/:repo/forks"},
{"GET", "/repos/:owner/:repo/hooks"},
{"GET", "/repos/:owner/:repo/hooks/:id"},
{"POST", "/repos/:owner/:repo/hooks"},
//{"PATCH", "/repos/:owner/:repo/hooks/:id"},
{"POST", "/repos/:owner/:repo/hooks/:id/tests"},
{"DELETE", "/repos/:owner/:repo/hooks/:id"},
{"POST", "/repos/:owner/:repo/merges"},
{"GET", "/repos/:owner/:repo/releases"},
{"GET", "/repos/:owner/:repo/releases/:id"},
{"POST", "/repos/:owner/:repo/releases"},
//{"PATCH", "/repos/:owner/:repo/releases/:id"},
{"DELETE", "/repos/:owner/:repo/releases/:id"},
{"GET", "/repos/:owner/:repo/releases/:id/assets"},
{"GET", "/repos/:owner/:repo/stats/contributors"},
{"GET", "/repos/:owner/:repo/stats/commit_activity"},
{"GET", "/repos/:owner/:repo/stats/code_frequency"},
{"GET", "/repos/:owner/:repo/stats/participation"},
{"GET", "/repos/:owner/:repo/stats/punch_card"},
{"GET", "/repos/:owner/:repo/statuses/:ref"},
{"POST", "/repos/:owner/:repo/statuses/:ref"},
// Search
{"GET", "/search/repositories"},
{"GET", "/search/code"},
{"GET", "/search/issues"},
{"GET", "/search/users"},
{"GET", "/legacy/issues/search/:owner/:repository/:state/:keyword"},
{"GET", "/legacy/repos/search/:keyword"},
{"GET", "/legacy/user/search/:keyword"},
{"GET", "/legacy/user/email/:email"},
// Users
{"GET", "/users/:user"},
{"GET", "/user"},
//{"PATCH", "/user"},
{"GET", "/users"},
{"GET", "/user/emails"},
{"POST", "/user/emails"},
{"DELETE", "/user/emails"},
{"GET", "/users/:user/followers"},
{"GET", "/user/followers"},
{"GET", "/users/:user/following"},
{"GET", "/user/following"},
{"GET", "/user/following/:user"},
{"GET", "/users/:user/following/:target_user"},
{"PUT", "/user/following/:user"},
{"DELETE", "/user/following/:user"},
{"GET", "/users/:user/keys"},
{"GET", "/user/keys"},
{"GET", "/user/keys/:id"},
{"POST", "/user/keys"},
//{"PATCH", "/user/keys/:id"},
{"DELETE", "/user/keys/:id"},
}
func TestGithubAPI(t *testing.T) {
router := New()
for _, route := range githubAPI {
router.Handle(route.method, route.path, []HandlerFunc{func(c *Context) {
output := H{"status": "good"}
for _, param := range c.Params {
output[param.Key] = param.Value
}
c.JSON(200, output)
}})
}
for _, route := range githubAPI {
path, values := exampleFromPath(route.path)
w := performRequest(router, route.method, path)
// TEST
assert.Contains(t, w.Body.String(), "\"status\":\"good\"")
for _, value := range values {
str := fmt.Sprintf("\"%s\":\"%s\"", value.Key, value.Value)
assert.Contains(t, w.Body.String(), str)
}
}
}
func exampleFromPath(path string) (string, Params) {
output := new(bytes.Buffer)
params := make(Params, 0, 6)
start := -1
for i, c := range path {
if c == ':' {
start = i + 1
}
if start >= 0 {
if c == '/' {
value := fmt.Sprint(rand.Intn(100000))
params = append(params, Param{
Key: path[start:i],
Value: value,
})
output.WriteString(value)
output.WriteRune(c)
start = -1
}
} else {
output.WriteRune(c)
}
}
if start >= 0 {
value := fmt.Sprint(rand.Intn(100000))
params = append(params, Param{
Key: path[start:len(path)],
Value: value,
})
output.WriteString(value)
}
return output.String(), params
}

35
logger_test.go Normal file
View File

@ -0,0 +1,35 @@
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package gin
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
//TODO
// func (engine *Engine) LoadHTMLGlob(pattern string) {
// func (engine *Engine) LoadHTMLFiles(files ...string) {
// func (engine *Engine) Run(addr string) error {
// func (engine *Engine) RunTLS(addr string, cert string, key string) error {
func init() {
SetMode(TestMode)
}
func TestLogger(t *testing.T) {
buffer := new(bytes.Buffer)
router := New()
router.Use(LoggerWithFile(buffer))
router.GET("/example", func(c *Context) {})
performRequest(router, "GET", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "GET")
assert.Contains(t, buffer.String(), "/example")
}

88
path_test.go Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2013 Julien Schmidt. All rights reserved.
// Based on the path package, Copyright 2009 The Go Authors.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.
package gin
import (
"runtime"
"testing"
"github.com/stretchr/testify/assert"
)
var cleanTests = []struct {
path, result string
}{
// Already clean
{"/", "/"},
{"/abc", "/abc"},
{"/a/b/c", "/a/b/c"},
{"/abc/", "/abc/"},
{"/a/b/c/", "/a/b/c/"},
// missing root
{"", "/"},
{"abc", "/abc"},
{"abc/def", "/abc/def"},
{"a/b/c", "/a/b/c"},
// Remove doubled slash
{"//", "/"},
{"/abc//", "/abc/"},
{"/abc/def//", "/abc/def/"},
{"/a/b/c//", "/a/b/c/"},
{"/abc//def//ghi", "/abc/def/ghi"},
{"//abc", "/abc"},
{"///abc", "/abc"},
{"//abc//", "/abc/"},
// Remove . elements
{".", "/"},
{"./", "/"},
{"/abc/./def", "/abc/def"},
{"/./abc/def", "/abc/def"},
{"/abc/.", "/abc/"},
// Remove .. elements
{"..", "/"},
{"../", "/"},
{"../../", "/"},
{"../..", "/"},
{"../../abc", "/abc"},
{"/abc/def/ghi/../jkl", "/abc/def/jkl"},
{"/abc/def/../ghi/../jkl", "/abc/jkl"},
{"/abc/def/..", "/abc"},
{"/abc/def/../..", "/"},
{"/abc/def/../../..", "/"},
{"/abc/def/../../..", "/"},
{"/abc/def/../../../ghi/jkl/../../../mno", "/mno"},
// Combinations
{"abc/./../def", "/def"},
{"abc//./../def", "/def"},
{"abc/../../././../def", "/def"},
}
func TestPathClean(t *testing.T) {
for _, test := range cleanTests {
assert.Equal(t, CleanPath(test.path), test.result)
assert.Equal(t, CleanPath(test.result), test.result)
}
}
func TestPathCleanMallocs(t *testing.T) {
if testing.Short() {
t.Skip("skipping malloc count in short mode")
}
if runtime.GOMAXPROCS(0) > 1 {
t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
return
}
for _, test := range cleanTests {
allocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) })
assert.Equal(t, allocs, 0)
}
}

79
render/render_test.go Normal file
View File

@ -0,0 +1,79 @@
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package render
import (
"html/template"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRenderJSON(t *testing.T) {
w := httptest.NewRecorder()
err := JSON.Render(w, 201, map[string]interface{}{
"foo": "bar",
})
assert.NoError(t, err)
assert.Equal(t, w.Code, 201)
assert.Equal(t, w.Body.String(), "{\"foo\":\"bar\"}\n")
assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8")
}
func TestRenderIndentedJSON(t *testing.T) {
w := httptest.NewRecorder()
err := IndentedJSON.Render(w, 202, map[string]interface{}{
"foo": "bar",
"bar": "foo",
})
assert.NoError(t, err)
assert.Equal(t, w.Code, 202)
assert.Equal(t, w.Body.String(), "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}")
assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8")
}
func TestRenderPlain(t *testing.T) {
w := httptest.NewRecorder()
err := Plain.Render(w, 400, "hola %s %d", []interface{}{"manu", 2})
assert.NoError(t, err)
assert.Equal(t, w.Code, 400)
assert.Equal(t, w.Body.String(), "hola manu 2")
assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8")
}
func TestRenderPlainHTML(t *testing.T) {
w := httptest.NewRecorder()
err := HTMLPlain.Render(w, 401, "hola %s %d", []interface{}{"manu", 2})
assert.NoError(t, err)
assert.Equal(t, w.Code, 401)
assert.Equal(t, w.Body.String(), "hola manu 2")
assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
}
func TestRenderHTMLTemplate(t *testing.T) {
w := httptest.NewRecorder()
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
htmlRender := HTMLRender{Template: templ}
err := htmlRender.Render(w, 402, "t", map[string]interface{}{
"name": "alexandernyquist",
})
assert.NoError(t, err)
assert.Equal(t, w.Code, 402)
assert.Equal(t, w.Body.String(), "Hello alexandernyquist")
assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
}
func TestRenderJoinStrings(t *testing.T) {
assert.Equal(t, joinStrings("a", "BB", "c"), "aBBc")
assert.Equal(t, joinStrings("a", "", "c"), "ac")
assert.Equal(t, joinStrings("text/html", "; charset=utf-8"), "text/html; charset=utf-8")
}

View File

@ -12,6 +12,11 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// TODO
// func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
// func (w *responseWriter) CloseNotify() <-chan bool {
// func (w *responseWriter) Flush() {
var _ ResponseWriter = &responseWriter{} var _ ResponseWriter = &responseWriter{}
var _ http.ResponseWriter = &responseWriter{} var _ http.ResponseWriter = &responseWriter{}
var _ http.ResponseWriter = ResponseWriter(&responseWriter{}) var _ http.ResponseWriter = ResponseWriter(&responseWriter{})

View File

@ -119,9 +119,10 @@ func (group *RouterGroup) createStaticHandler(absolutePath, root string) func(*C
func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc { func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc {
finalSize := len(group.Handlers) + len(handlers) finalSize := len(group.Handlers) + len(handlers)
mergedHandlers := make([]HandlerFunc, 0, finalSize) mergedHandlers := make([]HandlerFunc, finalSize)
mergedHandlers = append(mergedHandlers, group.Handlers...) copy(mergedHandlers, group.Handlers)
return append(mergedHandlers, handlers...) copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
} }
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string { func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {

98
routergroup_test.go Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package gin
import (
"testing"
"github.com/stretchr/testify/assert"
)
func init() {
SetMode(TestMode)
}
func TestRouterGroupBasic(t *testing.T) {
router := New()
group := router.Group("/hola", func(c *Context) {})
group.Use(func(c *Context) {})
assert.Len(t, group.Handlers, 2)
assert.Equal(t, group.absolutePath, "/hola")
assert.Equal(t, group.engine, router)
group2 := group.Group("manu")
group2.Use(func(c *Context) {}, func(c *Context) {})
assert.Len(t, group2.Handlers, 4)
assert.Equal(t, group2.absolutePath, "/hola/manu")
assert.Equal(t, group2.engine, router)
}
func TestRouterGroupBasicHandle(t *testing.T) {
performRequestInGroup(t, "GET")
performRequestInGroup(t, "POST")
performRequestInGroup(t, "PUT")
performRequestInGroup(t, "PATCH")
performRequestInGroup(t, "DELETE")
performRequestInGroup(t, "HEAD")
performRequestInGroup(t, "OPTIONS")
performRequestInGroup(t, "LINK")
performRequestInGroup(t, "UNLINK")
}
func performRequestInGroup(t *testing.T, method string) {
router := New()
v1 := router.Group("v1", func(c *Context) {})
assert.Equal(t, v1.absolutePath, "/v1")
login := v1.Group("/login/", func(c *Context) {}, func(c *Context) {})
assert.Equal(t, login.absolutePath, "/v1/login/")
handler := func(c *Context) {
c.String(400, "the method was %s and index %d", c.Request.Method, c.index)
}
switch method {
case "GET":
v1.GET("/test", handler)
login.GET("/test", handler)
case "POST":
v1.POST("/test", handler)
login.POST("/test", handler)
case "PUT":
v1.PUT("/test", handler)
login.PUT("/test", handler)
case "PATCH":
v1.PATCH("/test", handler)
login.PATCH("/test", handler)
case "DELETE":
v1.DELETE("/test", handler)
login.DELETE("/test", handler)
case "HEAD":
v1.HEAD("/test", handler)
login.HEAD("/test", handler)
case "OPTIONS":
v1.OPTIONS("/test", handler)
login.OPTIONS("/test", handler)
case "LINK":
v1.LINK("/test", handler)
login.LINK("/test", handler)
case "UNLINK":
v1.UNLINK("/test", handler)
login.UNLINK("/test", handler)
default:
panic("unknown method")
}
w := performRequest(router, method, "/v1/login/test")
assert.Equal(t, w.Code, 400)
assert.Equal(t, w.Body.String(), "the method was "+method+" and index 3")
w = performRequest(router, method, "/v1/test")
assert.Equal(t, w.Code, 400)
assert.Equal(t, w.Body.String(), "the method was "+method+" and index 1")
}

608
tree_test.go Normal file
View File

@ -0,0 +1,608 @@
// Copyright 2013 Julien Schmidt. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.
package gin
import (
"fmt"
"reflect"
"strings"
"testing"
)
func printChildren(n *node, prefix string) {
fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handlers, n.wildChild, n.nType)
for l := len(n.path); l > 0; l-- {
prefix += " "
}
for _, child := range n.children {
printChildren(child, prefix)
}
}
// Used as a workaround since we can't compare functions or their adresses
var fakeHandlerValue string
func fakeHandler(val string) []HandlerFunc {
return []HandlerFunc{func(c *Context) {
fakeHandlerValue = val
}}
}
type testRequests []struct {
path string
nilHandler bool
route string
ps Params
}
func checkRequests(t *testing.T, tree *node, requests testRequests) {
for _, request := range requests {
handler, ps, _ := tree.getValue(request.path, nil)
if handler == nil {
if !request.nilHandler {
t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
}
} else if request.nilHandler {
t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
} else {
handler[0](nil)
if fakeHandlerValue != request.route {
t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
}
}
if !reflect.DeepEqual(ps, request.ps) {
t.Errorf("Params mismatch for route '%s'", request.path)
}
}
}
func checkPriorities(t *testing.T, n *node) uint32 {
var prio uint32
for i := range n.children {
prio += checkPriorities(t, n.children[i])
}
if n.handlers != nil {
prio++
}
if n.priority != prio {
t.Errorf(
"priority mismatch for node '%s': is %d, should be %d",
n.path, n.priority, prio,
)
}
return prio
}
func checkMaxParams(t *testing.T, n *node) uint8 {
var maxParams uint8
for i := range n.children {
params := checkMaxParams(t, n.children[i])
if params > maxParams {
maxParams = params
}
}
if n.nType != static && !n.wildChild {
maxParams++
}
if n.maxParams != maxParams {
t.Errorf(
"maxParams mismatch for node '%s': is %d, should be %d",
n.path, n.maxParams, maxParams,
)
}
return maxParams
}
func TestCountParams(t *testing.T) {
if countParams("/path/:param1/static/*catch-all") != 2 {
t.Fail()
}
if countParams(strings.Repeat("/:param", 256)) != 255 {
t.Fail()
}
}
func TestTreeAddAndGet(t *testing.T) {
tree := &node{}
routes := [...]string{
"/hi",
"/contact",
"/co",
"/c",
"/a",
"/ab",
"/doc/",
"/doc/go_faq.html",
"/doc/go1.html",
"/α",
"/β",
}
for _, route := range routes {
tree.addRoute(route, fakeHandler(route))
}
//printChildren(tree, "")
checkRequests(t, tree, testRequests{
{"/a", false, "/a", nil},
{"/", true, "", nil},
{"/hi", false, "/hi", nil},
{"/contact", false, "/contact", nil},
{"/co", false, "/co", nil},
{"/con", true, "", nil}, // key mismatch
{"/cona", true, "", nil}, // key mismatch
{"/no", true, "", nil}, // no matching child
{"/ab", false, "/ab", nil},
{"/α", false, "/α", nil},
{"/β", false, "/β", nil},
})
checkPriorities(t, tree)
checkMaxParams(t, tree)
}
func TestTreeWildcard(t *testing.T) {
tree := &node{}
routes := [...]string{
"/",
"/cmd/:tool/:sub",
"/cmd/:tool/",
"/src/*filepath",
"/search/",
"/search/:query",
"/user_:name",
"/user_:name/about",
"/files/:dir/*filepath",
"/doc/",
"/doc/go_faq.html",
"/doc/go1.html",
"/info/:user/public",
"/info/:user/project/:project",
}
for _, route := range routes {
tree.addRoute(route, fakeHandler(route))
}
//printChildren(tree, "")
checkRequests(t, tree, testRequests{
{"/", false, "/", nil},
{"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}},
{"/cmd/test", true, "", Params{Param{"tool", "test"}}},
{"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{"tool", "test"}, Param{"sub", "3"}}},
{"/src/", false, "/src/*filepath", Params{Param{"filepath", "/"}}},
{"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}},
{"/search/", false, "/search/", nil},
{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
{"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
{"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}},
{"/user_gopher/about", false, "/user_:name/about", Params{Param{"name", "gopher"}}},
{"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{"dir", "js"}, Param{"filepath", "/inc/framework.js"}}},
{"/info/gordon/public", false, "/info/:user/public", Params{Param{"user", "gordon"}}},
{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}},
})
checkPriorities(t, tree)
checkMaxParams(t, tree)
}
func catchPanic(testFunc func()) (recv interface{}) {
defer func() {
recv = recover()
}()
testFunc()
return
}
type testRoute struct {
path string
conflict bool
}
func testRoutes(t *testing.T, routes []testRoute) {
tree := &node{}
for _, route := range routes {
recv := catchPanic(func() {
tree.addRoute(route.path, nil)
})
if route.conflict {
if recv == nil {
t.Errorf("no panic for conflicting route '%s'", route.path)
}
} else if recv != nil {
t.Errorf("unexpected panic for route '%s': %v", route.path, recv)
}
}
//printChildren(tree, "")
}
func TestTreeWildcardConflict(t *testing.T) {
routes := []testRoute{
{"/cmd/:tool/:sub", false},
{"/cmd/vet", true},
{"/src/*filepath", false},
{"/src/*filepathx", true},
{"/src/", true},
{"/src1/", false},
{"/src1/*filepath", true},
{"/src2*filepath", true},
{"/search/:query", false},
{"/search/invalid", true},
{"/user_:name", false},
{"/user_x", true},
{"/user_:name", false},
{"/id:id", false},
{"/id/:id", true},
}
testRoutes(t, routes)
}
func TestTreeChildConflict(t *testing.T) {
routes := []testRoute{
{"/cmd/vet", false},
{"/cmd/:tool/:sub", true},
{"/src/AUTHORS", false},
{"/src/*filepath", true},
{"/user_x", false},
{"/user_:name", true},
{"/id/:id", false},
{"/id:id", true},
{"/:id", true},
{"/*filepath", true},
}
testRoutes(t, routes)
}
func TestTreeDupliatePath(t *testing.T) {
tree := &node{}
routes := [...]string{
"/",
"/doc/",
"/src/*filepath",
"/search/:query",
"/user_:name",
}
for _, route := range routes {
recv := catchPanic(func() {
tree.addRoute(route, fakeHandler(route))
})
if recv != nil {
t.Fatalf("panic inserting route '%s': %v", route, recv)
}
// Add again
recv = catchPanic(func() {
tree.addRoute(route, nil)
})
if recv == nil {
t.Fatalf("no panic while inserting duplicate route '%s", route)
}
}
//printChildren(tree, "")
checkRequests(t, tree, testRequests{
{"/", false, "/", nil},
{"/doc/", false, "/doc/", nil},
{"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}},
{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
{"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}},
})
}
func TestEmptyWildcardName(t *testing.T) {
tree := &node{}
routes := [...]string{
"/user:",
"/user:/",
"/cmd/:/",
"/src/*",
}
for _, route := range routes {
recv := catchPanic(func() {
tree.addRoute(route, nil)
})
if recv == nil {
t.Fatalf("no panic while inserting route with empty wildcard name '%s", route)
}
}
}
func TestTreeCatchAllConflict(t *testing.T) {
routes := []testRoute{
{"/src/*filepath/x", true},
{"/src2/", false},
{"/src2/*filepath/x", true},
}
testRoutes(t, routes)
}
func TestTreeCatchAllConflictRoot(t *testing.T) {
routes := []testRoute{
{"/", false},
{"/*filepath", true},
}
testRoutes(t, routes)
}
func TestTreeDoubleWildcard(t *testing.T) {
const panicMsg = "only one wildcard per path segment is allowed"
routes := [...]string{
"/:foo:bar",
"/:foo:bar/",
"/:foo*bar",
}
for _, route := range routes {
tree := &node{}
recv := catchPanic(func() {
tree.addRoute(route, nil)
})
if rs, ok := recv.(string); !ok || rs != panicMsg {
t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsg, route, recv)
}
}
}
/*func TestTreeDuplicateWildcard(t *testing.T) {
tree := &node{}
routes := [...]string{
"/:id/:name/:id",
}
for _, route := range routes {
...
}
}*/
func TestTreeTrailingSlashRedirect(t *testing.T) {
tree := &node{}
routes := [...]string{
"/hi",
"/b/",
"/search/:query",
"/cmd/:tool/",
"/src/*filepath",
"/x",
"/x/y",
"/y/",
"/y/z",
"/0/:id",
"/0/:id/1",
"/1/:id/",
"/1/:id/2",
"/aa",
"/a/",
"/doc",
"/doc/go_faq.html",
"/doc/go1.html",
"/no/a",
"/no/b",
"/api/hello/:name",
}
for _, route := range routes {
recv := catchPanic(func() {
tree.addRoute(route, fakeHandler(route))
})
if recv != nil {
t.Fatalf("panic inserting route '%s': %v", route, recv)
}
}
//printChildren(tree, "")
tsrRoutes := [...]string{
"/hi/",
"/b",
"/search/gopher/",
"/cmd/vet",
"/src",
"/x/",
"/y",
"/0/go/",
"/1/go",
"/a",
"/doc/",
}
for _, route := range tsrRoutes {
handler, _, tsr := tree.getValue(route, nil)
if handler != nil {
t.Fatalf("non-nil handler for TSR route '%s", route)
} else if !tsr {
t.Errorf("expected TSR recommendation for route '%s'", route)
}
}
noTsrRoutes := [...]string{
"/",
"/no",
"/no/",
"/_",
"/_/",
"/api/world/abc",
}
for _, route := range noTsrRoutes {
handler, _, tsr := tree.getValue(route, nil)
if handler != nil {
t.Fatalf("non-nil handler for No-TSR route '%s", route)
} else if tsr {
t.Errorf("expected no TSR recommendation for route '%s'", route)
}
}
}
func TestTreeFindCaseInsensitivePath(t *testing.T) {
tree := &node{}
routes := [...]string{
"/hi",
"/b/",
"/ABC/",
"/search/:query",
"/cmd/:tool/",
"/src/*filepath",
"/x",
"/x/y",
"/y/",
"/y/z",
"/0/:id",
"/0/:id/1",
"/1/:id/",
"/1/:id/2",
"/aa",
"/a/",
"/doc",
"/doc/go_faq.html",
"/doc/go1.html",
"/doc/go/away",
"/no/a",
"/no/b",
}
for _, route := range routes {
recv := catchPanic(func() {
tree.addRoute(route, fakeHandler(route))
})
if recv != nil {
t.Fatalf("panic inserting route '%s': %v", route, recv)
}
}
// Check out == in for all registered routes
// With fixTrailingSlash = true
for _, route := range routes {
out, found := tree.findCaseInsensitivePath(route, true)
if !found {
t.Errorf("Route '%s' not found!", route)
} else if string(out) != route {
t.Errorf("Wrong result for route '%s': %s", route, string(out))
}
}
// With fixTrailingSlash = false
for _, route := range routes {
out, found := tree.findCaseInsensitivePath(route, false)
if !found {
t.Errorf("Route '%s' not found!", route)
} else if string(out) != route {
t.Errorf("Wrong result for route '%s': %s", route, string(out))
}
}
tests := []struct {
in string
out string
found bool
slash bool
}{
{"/HI", "/hi", true, false},
{"/HI/", "/hi", true, true},
{"/B", "/b/", true, true},
{"/B/", "/b/", true, false},
{"/abc", "/ABC/", true, true},
{"/abc/", "/ABC/", true, false},
{"/aBc", "/ABC/", true, true},
{"/aBc/", "/ABC/", true, false},
{"/abC", "/ABC/", true, true},
{"/abC/", "/ABC/", true, false},
{"/SEARCH/QUERY", "/search/QUERY", true, false},
{"/SEARCH/QUERY/", "/search/QUERY", true, true},
{"/CMD/TOOL/", "/cmd/TOOL/", true, false},
{"/CMD/TOOL", "/cmd/TOOL/", true, true},
{"/SRC/FILE/PATH", "/src/FILE/PATH", true, false},
{"/x/Y", "/x/y", true, false},
{"/x/Y/", "/x/y", true, true},
{"/X/y", "/x/y", true, false},
{"/X/y/", "/x/y", true, true},
{"/X/Y", "/x/y", true, false},
{"/X/Y/", "/x/y", true, true},
{"/Y/", "/y/", true, false},
{"/Y", "/y/", true, true},
{"/Y/z", "/y/z", true, false},
{"/Y/z/", "/y/z", true, true},
{"/Y/Z", "/y/z", true, false},
{"/Y/Z/", "/y/z", true, true},
{"/y/Z", "/y/z", true, false},
{"/y/Z/", "/y/z", true, true},
{"/Aa", "/aa", true, false},
{"/Aa/", "/aa", true, true},
{"/AA", "/aa", true, false},
{"/AA/", "/aa", true, true},
{"/aA", "/aa", true, false},
{"/aA/", "/aa", true, true},
{"/A/", "/a/", true, false},
{"/A", "/a/", true, true},
{"/DOC", "/doc", true, false},
{"/DOC/", "/doc", true, true},
{"/NO", "", false, true},
{"/DOC/GO", "", false, true},
}
// With fixTrailingSlash = true
for _, test := range tests {
out, found := tree.findCaseInsensitivePath(test.in, true)
if found != test.found || (found && (string(out) != test.out)) {
t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
test.in, string(out), found, test.out, test.found)
return
}
}
// With fixTrailingSlash = false
for _, test := range tests {
out, found := tree.findCaseInsensitivePath(test.in, false)
if test.slash {
if found { // test needs a trailingSlash fix. It must not be found!
t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out))
}
} else {
if found != test.found || (found && (string(out) != test.out)) {
t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
test.in, string(out), found, test.out, test.found)
return
}
}
}
}
func TestTreeInvalidNodeType(t *testing.T) {
tree := &node{}
tree.addRoute("/", fakeHandler("/"))
tree.addRoute("/:page", fakeHandler("/:page"))
// set invalid node type
tree.children[0].nType = 42
// normal lookup
recv := catchPanic(func() {
tree.getValue("/test", nil)
})
if rs, ok := recv.(string); !ok || rs != "Invalid node type" {
t.Fatalf(`Expected panic "Invalid node type", got "%v"`, recv)
}
// case-insensitive lookup
recv = catchPanic(func() {
tree.findCaseInsensitivePath("/test", true)
})
if rs, ok := recv.(string); !ok || rs != "Invalid node type" {
t.Fatalf(`Expected panic "Invalid node type", got "%v"`, recv)
}
}

View File

@ -45,7 +45,17 @@ func TestFilterFlags(t *testing.T) {
assert.Equal(t, result, "text/html") assert.Equal(t, result, "text/html")
} }
func TestFunctionName(t *testing.T) {
assert.Equal(t, nameOfFunction(somefunction), "github.com/gin-gonic/gin.somefunction")
}
func somefunction() {
}
func TestJoinPaths(t *testing.T) { func TestJoinPaths(t *testing.T) {
assert.Equal(t, joinPaths("", ""), "")
assert.Equal(t, joinPaths("", "/"), "/")
assert.Equal(t, joinPaths("/a", ""), "/a") assert.Equal(t, joinPaths("/a", ""), "/a")
assert.Equal(t, joinPaths("/a/", ""), "/a/") assert.Equal(t, joinPaths("/a/", ""), "/a/")
assert.Equal(t, joinPaths("/a/", "/"), "/a/") assert.Equal(t, joinPaths("/a/", "/"), "/a/")