2015-03-27 23:35:15 +02:00
|
|
|
package echo
|
|
|
|
|
|
|
|
import (
|
2015-03-30 16:38:53 +02:00
|
|
|
"bytes"
|
2018-04-03 13:09:21 +02:00
|
|
|
stdContext "context"
|
2018-02-21 20:44:17 +02:00
|
|
|
"errors"
|
2020-11-05 06:15:32 +02:00
|
|
|
"fmt"
|
2021-07-15 22:34:01 +02:00
|
|
|
"io/fs"
|
2020-12-17 03:07:41 +02:00
|
|
|
"net"
|
2015-03-27 23:35:15 +02:00
|
|
|
"net/http"
|
2016-09-23 07:53:44 +02:00
|
|
|
"net/http/httptest"
|
2021-04-06 09:05:33 +02:00
|
|
|
"net/url"
|
2020-12-29 11:46:09 +02:00
|
|
|
"os"
|
2015-05-30 01:00:02 +02:00
|
|
|
"strings"
|
2018-02-21 20:44:17 +02:00
|
|
|
"testing"
|
2017-01-18 22:17:44 +02:00
|
|
|
"time"
|
|
|
|
|
2015-05-30 19:54:55 +02:00
|
|
|
"github.com/stretchr/testify/assert"
|
2015-03-27 23:35:15 +02:00
|
|
|
)
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
type user struct {
|
|
|
|
ID int `json:"id" xml:"id" form:"id" query:"id" param:"id" header:"id"`
|
|
|
|
Name string `json:"name" xml:"name" form:"name" query:"name" param:"name" header:"name"`
|
|
|
|
}
|
2015-03-27 23:35:15 +02:00
|
|
|
|
2016-05-01 21:38:51 +02:00
|
|
|
const (
|
2018-09-28 19:41:13 +02:00
|
|
|
userJSON = `{"id":1,"name":"Jon Snow"}`
|
2021-04-06 09:05:33 +02:00
|
|
|
usersJSON = `[{"id":1,"name":"Jon Snow"}]`
|
2018-09-28 19:41:13 +02:00
|
|
|
userXML = `<user><id>1</id><name>Jon Snow</name></user>`
|
|
|
|
userForm = `id=1&name=Jon Snow`
|
|
|
|
invalidContent = "invalid content"
|
|
|
|
userJSONInvalidType = `{"id":"1","name":"Jon Snow"}`
|
|
|
|
userXMLConvertNumberError = `<user><id>Number one</id><name>Jon Snow</name></user>`
|
|
|
|
userXMLUnsupportedTypeError = `<user><>Number one</><name>Jon Snow</name></user>`
|
2016-05-01 21:38:51 +02:00
|
|
|
)
|
|
|
|
|
2016-12-09 21:13:55 +02:00
|
|
|
const userJSONPretty = `{
|
2017-04-28 06:41:46 +02:00
|
|
|
"id": 1,
|
|
|
|
"name": "Jon Snow"
|
2016-12-09 21:13:55 +02:00
|
|
|
}`
|
|
|
|
|
|
|
|
const userXMLPretty = `<user>
|
2017-04-28 06:41:46 +02:00
|
|
|
<id>1</id>
|
|
|
|
<name>Jon Snow</name>
|
2016-12-09 21:13:55 +02:00
|
|
|
</user>`
|
|
|
|
|
2021-04-06 09:05:33 +02:00
|
|
|
var dummyQuery = url.Values{"dummy": []string{"useless"}}
|
|
|
|
|
2015-05-31 00:20:36 +02:00
|
|
|
func TestEcho(t *testing.T) {
|
|
|
|
e := New()
|
2018-10-14 17:16:58 +02:00
|
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
2016-09-23 07:53:44 +02:00
|
|
|
rec := httptest.NewRecorder()
|
2016-04-24 19:21:23 +02:00
|
|
|
c := e.NewContext(req, rec)
|
2015-05-31 00:20:36 +02:00
|
|
|
|
|
|
|
// Router
|
|
|
|
assert.NotNil(t, e.Router())
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
e.HTTPErrorHandler(c, errors.New("error"))
|
|
|
|
|
2016-09-23 07:53:44 +02:00
|
|
|
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
2015-05-31 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func TestEcho_StaticFS(t *testing.T) {
|
2020-12-13 15:49:11 +02:00
|
|
|
var testCases = []struct {
|
|
|
|
name string
|
|
|
|
givenPrefix string
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs fs.FS
|
|
|
|
givenFsRoot string
|
2020-12-13 15:49:11 +02:00
|
|
|
whenURL string
|
|
|
|
expectStatus int
|
|
|
|
expectHeaderLocation string
|
|
|
|
expectBodyStartsWith string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "ok",
|
|
|
|
givenPrefix: "/images",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("./_fixture/images"),
|
2020-12-13 15:49:11 +02:00
|
|
|
whenURL: "/images/walle.png",
|
|
|
|
expectStatus: http.StatusOK,
|
|
|
|
expectBodyStartsWith: string([]byte{0x89, 0x50, 0x4e, 0x47}),
|
|
|
|
},
|
2022-03-13 15:05:12 +02:00
|
|
|
{
|
2021-07-15 22:34:01 +02:00
|
|
|
name: "ok, from sub fs",
|
2022-03-13 15:05:12 +02:00
|
|
|
givenPrefix: "/images",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: MustSubFS(os.DirFS("./_fixture/"), "images"),
|
2022-03-13 15:05:12 +02:00
|
|
|
whenURL: "/images/walle.png",
|
|
|
|
expectStatus: http.StatusOK,
|
|
|
|
expectBodyStartsWith: string([]byte{0x89, 0x50, 0x4e, 0x47}),
|
|
|
|
},
|
2020-12-13 15:49:11 +02:00
|
|
|
{
|
|
|
|
name: "No file",
|
|
|
|
givenPrefix: "/images",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture/scripts"),
|
2020-12-13 15:49:11 +02:00
|
|
|
whenURL: "/images/bolt.png",
|
|
|
|
expectStatus: http.StatusNotFound,
|
|
|
|
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Directory",
|
|
|
|
givenPrefix: "/images",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture/images"),
|
2020-12-13 15:49:11 +02:00
|
|
|
whenURL: "/images/",
|
|
|
|
expectStatus: http.StatusNotFound,
|
|
|
|
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Directory Redirect",
|
|
|
|
givenPrefix: "/",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture/"),
|
2020-12-13 15:49:11 +02:00
|
|
|
whenURL: "/folder",
|
|
|
|
expectStatus: http.StatusMovedPermanently,
|
2020-12-16 03:37:26 +02:00
|
|
|
expectHeaderLocation: "/folder/",
|
2020-12-16 03:21:26 +02:00
|
|
|
expectBodyStartsWith: "",
|
|
|
|
},
|
2021-01-03 01:25:29 +02:00
|
|
|
{
|
|
|
|
name: "Directory Redirect with non-root path",
|
|
|
|
givenPrefix: "/static",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture"),
|
2021-01-03 01:25:29 +02:00
|
|
|
whenURL: "/static",
|
|
|
|
expectStatus: http.StatusMovedPermanently,
|
|
|
|
expectHeaderLocation: "/static/",
|
|
|
|
expectBodyStartsWith: "",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Prefixed directory 404 (request URL without slash)",
|
|
|
|
givenPrefix: "/folder/", // trailing slash will intentionally not match "/folder"
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture"),
|
2021-01-03 01:25:29 +02:00
|
|
|
whenURL: "/folder", // no trailing slash
|
|
|
|
expectStatus: http.StatusNotFound,
|
|
|
|
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Prefixed directory redirect (without slash redirect to slash)",
|
|
|
|
givenPrefix: "/folder", // no trailing slash shall match /folder and /folder/*
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture"),
|
2021-01-03 01:25:29 +02:00
|
|
|
whenURL: "/folder", // no trailing slash
|
|
|
|
expectStatus: http.StatusMovedPermanently,
|
|
|
|
expectHeaderLocation: "/folder/",
|
|
|
|
expectBodyStartsWith: "",
|
|
|
|
},
|
2020-12-13 15:49:11 +02:00
|
|
|
{
|
|
|
|
name: "Directory with index.html",
|
|
|
|
givenPrefix: "/",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture"),
|
2020-12-13 15:49:11 +02:00
|
|
|
whenURL: "/",
|
|
|
|
expectStatus: http.StatusOK,
|
|
|
|
expectBodyStartsWith: "<!doctype html>",
|
|
|
|
},
|
2021-01-03 01:25:29 +02:00
|
|
|
{
|
|
|
|
name: "Prefixed directory with index.html (prefix ending with slash)",
|
|
|
|
givenPrefix: "/assets/",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture"),
|
2021-01-03 01:25:29 +02:00
|
|
|
whenURL: "/assets/",
|
|
|
|
expectStatus: http.StatusOK,
|
|
|
|
expectBodyStartsWith: "<!doctype html>",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Prefixed directory with index.html (prefix ending without slash)",
|
|
|
|
givenPrefix: "/assets",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture"),
|
2021-01-03 01:25:29 +02:00
|
|
|
whenURL: "/assets/",
|
|
|
|
expectStatus: http.StatusOK,
|
|
|
|
expectBodyStartsWith: "<!doctype html>",
|
|
|
|
},
|
2020-12-13 15:49:11 +02:00
|
|
|
{
|
|
|
|
name: "Sub-directory with index.html",
|
|
|
|
givenPrefix: "/",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture"),
|
2020-12-13 15:49:11 +02:00
|
|
|
whenURL: "/folder/",
|
|
|
|
expectStatus: http.StatusOK,
|
|
|
|
expectBodyStartsWith: "<!doctype html>",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "do not allow directory traversal (backslash - windows separator)",
|
|
|
|
givenPrefix: "/",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture/"),
|
2020-12-13 15:49:11 +02:00
|
|
|
whenURL: `/..\\middleware/basic_auth.go`,
|
|
|
|
expectStatus: http.StatusNotFound,
|
|
|
|
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "do not allow directory traversal (slash - unix separator)",
|
|
|
|
givenPrefix: "/",
|
2021-07-15 22:34:01 +02:00
|
|
|
givenFs: os.DirFS("_fixture/"),
|
2020-12-13 15:49:11 +02:00
|
|
|
whenURL: `/../middleware/basic_auth.go`,
|
|
|
|
expectStatus: http.StatusNotFound,
|
|
|
|
expectBodyStartsWith: "{\"message\":\"Not Found\"}\n",
|
|
|
|
},
|
|
|
|
}
|
2016-03-12 15:14:15 +02:00
|
|
|
|
2020-12-13 15:49:11 +02:00
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
tmpFs := tc.givenFs
|
|
|
|
if tc.givenFsRoot != "" {
|
|
|
|
tmpFs = MustSubFS(tmpFs, tc.givenFsRoot)
|
|
|
|
}
|
|
|
|
e.StaticFS(tc.givenPrefix, tmpFs)
|
|
|
|
|
2020-12-13 15:49:11 +02:00
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
|
|
rec := httptest.NewRecorder()
|
2021-07-15 22:34:01 +02:00
|
|
|
|
2020-12-13 15:49:11 +02:00
|
|
|
e.ServeHTTP(rec, req)
|
2021-07-15 22:34:01 +02:00
|
|
|
|
2020-12-13 15:49:11 +02:00
|
|
|
assert.Equal(t, tc.expectStatus, rec.Code)
|
|
|
|
body := rec.Body.String()
|
|
|
|
if tc.expectBodyStartsWith != "" {
|
|
|
|
assert.True(t, strings.HasPrefix(body, tc.expectBodyStartsWith))
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, "", body)
|
|
|
|
}
|
2020-08-28 02:53:48 +02:00
|
|
|
|
2020-12-13 15:49:11 +02:00
|
|
|
if tc.expectHeaderLocation != "" {
|
|
|
|
assert.Equal(t, tc.expectHeaderLocation, rec.Result().Header["Location"][0])
|
|
|
|
} else {
|
|
|
|
_, ok := rec.Result().Header["Location"]
|
|
|
|
assert.False(t, ok)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2016-03-12 15:14:15 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func TestEcho_FileFS(t *testing.T) {
|
|
|
|
var testCases = []struct {
|
|
|
|
name string
|
|
|
|
whenPath string
|
|
|
|
whenFile string
|
|
|
|
whenFS fs.FS
|
|
|
|
givenURL string
|
|
|
|
expectCode int
|
|
|
|
expectStartsWith []byte
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "ok",
|
|
|
|
whenPath: "/walle",
|
|
|
|
whenFS: os.DirFS("_fixture/images"),
|
|
|
|
whenFile: "walle.png",
|
|
|
|
givenURL: "/walle",
|
|
|
|
expectCode: http.StatusOK,
|
|
|
|
expectStartsWith: []byte{0x89, 0x50, 0x4e},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "nok, requesting invalid path",
|
|
|
|
whenPath: "/walle",
|
|
|
|
whenFS: os.DirFS("_fixture/images"),
|
|
|
|
whenFile: "walle.png",
|
|
|
|
givenURL: "/walle.png",
|
|
|
|
expectCode: http.StatusNotFound,
|
|
|
|
expectStartsWith: []byte(`{"message":"Not Found"}`),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "nok, serving not existent file from filesystem",
|
|
|
|
whenPath: "/walle",
|
|
|
|
whenFS: os.DirFS("_fixture/images"),
|
|
|
|
whenFile: "not-existent.png",
|
|
|
|
givenURL: "/walle",
|
|
|
|
expectCode: http.StatusNotFound,
|
|
|
|
expectStartsWith: []byte(`{"message":"Not Found"}`),
|
|
|
|
},
|
|
|
|
}
|
2021-01-03 01:25:29 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
e := New()
|
|
|
|
e.FileFS(tc.whenPath, tc.whenFile, tc.whenFS)
|
2021-01-03 01:25:29 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.givenURL, nil)
|
|
|
|
rec := httptest.NewRecorder()
|
2021-01-03 01:25:29 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
e.ServeHTTP(rec, req)
|
2021-01-03 01:25:29 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
assert.Equal(t, tc.expectCode, rec.Code)
|
2021-01-03 01:25:29 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
body := rec.Body.Bytes()
|
|
|
|
if len(body) > len(tc.expectStartsWith) {
|
|
|
|
body = body[:len(tc.expectStartsWith)]
|
|
|
|
}
|
|
|
|
assert.Equal(t, tc.expectStartsWith, body)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-01-03 01:25:29 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func TestEcho_StaticPanic(t *testing.T) {
|
|
|
|
var testCases = []struct {
|
|
|
|
name string
|
|
|
|
givenRoot string
|
|
|
|
expectError string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "panics for ../",
|
|
|
|
givenRoot: "../assets",
|
|
|
|
expectError: "can not create sub FS, invalid root given, err: sub ../assets: invalid name",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "panics for /",
|
|
|
|
givenRoot: "/assets",
|
|
|
|
expectError: "can not create sub FS, invalid root given, err: sub /assets: invalid name",
|
|
|
|
},
|
|
|
|
}
|
2021-01-03 01:25:29 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
e := New()
|
|
|
|
e.Filesystem = os.DirFS("./")
|
|
|
|
|
|
|
|
assert.PanicsWithError(t, tc.expectError, func() {
|
|
|
|
e.Static("../assets", tc.givenRoot)
|
|
|
|
})
|
|
|
|
})
|
2021-01-03 01:25:29 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
}
|
2021-01-03 01:25:29 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func TestEchoStaticRedirectIndex(t *testing.T) {
|
|
|
|
e := New()
|
|
|
|
|
|
|
|
// HandlerFunc
|
|
|
|
ri := e.Static("/static", "_fixture")
|
|
|
|
assert.Equal(t, http.MethodGet, ri.Method())
|
|
|
|
assert.Equal(t, "/static*", ri.Path())
|
|
|
|
assert.Equal(t, "GET:/static*", ri.Name())
|
|
|
|
assert.Equal(t, []string{"*"}, ri.Params())
|
|
|
|
|
|
|
|
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), 200*time.Millisecond)
|
|
|
|
defer cancel()
|
|
|
|
addr, err := startOnRandomPort(ctx, e)
|
|
|
|
if err != nil {
|
|
|
|
assert.Fail(t, err.Error())
|
2021-01-03 01:25:29 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
code, body, err := doGet(fmt.Sprintf("http://%v/static", addr))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.True(t, strings.HasPrefix(body, "<!doctype html>"))
|
|
|
|
assert.Equal(t, http.StatusOK, code)
|
2021-01-03 01:25:29 +02:00
|
|
|
}
|
|
|
|
|
2016-03-12 15:14:15 +02:00
|
|
|
func TestEchoFile(t *testing.T) {
|
2022-03-13 15:05:12 +02:00
|
|
|
var testCases = []struct {
|
|
|
|
name string
|
|
|
|
givenPath string
|
|
|
|
givenFile string
|
|
|
|
whenPath string
|
|
|
|
expectCode int
|
|
|
|
expectStartsWith string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "ok",
|
|
|
|
givenPath: "/walle",
|
|
|
|
givenFile: "_fixture/images/walle.png",
|
|
|
|
whenPath: "/walle",
|
|
|
|
expectCode: http.StatusOK,
|
|
|
|
expectStartsWith: string([]byte{0x89, 0x50, 0x4e}),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ok with relative path",
|
|
|
|
givenPath: "/",
|
|
|
|
givenFile: "./go.mod",
|
|
|
|
whenPath: "/",
|
|
|
|
expectCode: http.StatusOK,
|
|
|
|
expectStartsWith: "module github.com/labstack/echo/v",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "nok file does not exist",
|
|
|
|
givenPath: "/",
|
|
|
|
givenFile: "./this-file-does-not-exist",
|
|
|
|
whenPath: "/",
|
|
|
|
expectCode: http.StatusNotFound,
|
|
|
|
expectStartsWith: "{\"message\":\"Not Found\"}\n",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
e := New() // we are using echo.defaultFS instance
|
|
|
|
e.File(tc.givenPath, tc.givenFile)
|
|
|
|
|
|
|
|
c, b := request(http.MethodGet, tc.whenPath, e)
|
|
|
|
assert.Equal(t, tc.expectCode, c)
|
|
|
|
|
|
|
|
if len(b) > len(tc.expectStartsWith) {
|
|
|
|
b = b[:len(tc.expectStartsWith)]
|
|
|
|
}
|
|
|
|
assert.Equal(t, tc.expectStartsWith, b)
|
|
|
|
})
|
|
|
|
}
|
2016-03-12 15:14:15 +02:00
|
|
|
}
|
|
|
|
|
2015-03-30 05:44:55 +02:00
|
|
|
func TestEchoMiddleware(t *testing.T) {
|
2015-03-30 08:35:08 +02:00
|
|
|
e := New()
|
2015-05-31 00:20:36 +02:00
|
|
|
buf := new(bytes.Buffer)
|
2015-03-30 05:44:55 +02:00
|
|
|
|
2016-09-23 14:31:48 +02:00
|
|
|
e.Pre(func(next HandlerFunc) HandlerFunc {
|
|
|
|
return func(c Context) error {
|
2021-07-15 22:34:01 +02:00
|
|
|
// before route match is found RouteInfo does not exist
|
|
|
|
assert.Equal(t, nil, c.RouteInfo())
|
2016-09-23 14:31:48 +02:00
|
|
|
buf.WriteString("-1")
|
|
|
|
return next(c)
|
|
|
|
}
|
|
|
|
})
|
2016-03-17 22:24:52 +02:00
|
|
|
|
2016-09-23 14:31:48 +02:00
|
|
|
e.Use(func(next HandlerFunc) HandlerFunc {
|
|
|
|
return func(c Context) error {
|
|
|
|
buf.WriteString("1")
|
|
|
|
return next(c)
|
|
|
|
}
|
|
|
|
})
|
2016-03-17 22:24:52 +02:00
|
|
|
|
2016-09-23 14:31:48 +02:00
|
|
|
e.Use(func(next HandlerFunc) HandlerFunc {
|
|
|
|
return func(c Context) error {
|
|
|
|
buf.WriteString("2")
|
|
|
|
return next(c)
|
|
|
|
}
|
|
|
|
})
|
2016-03-17 22:24:52 +02:00
|
|
|
|
2016-09-23 14:31:48 +02:00
|
|
|
e.Use(func(next HandlerFunc) HandlerFunc {
|
|
|
|
return func(c Context) error {
|
|
|
|
buf.WriteString("3")
|
|
|
|
return next(c)
|
|
|
|
}
|
|
|
|
})
|
2015-03-30 08:35:08 +02:00
|
|
|
|
2015-03-30 05:44:55 +02:00
|
|
|
// Route
|
2016-04-19 01:59:58 +02:00
|
|
|
e.GET("/", func(c Context) error {
|
2016-01-09 19:44:18 +02:00
|
|
|
return c.String(http.StatusOK, "OK")
|
2016-04-02 23:19:39 +02:00
|
|
|
})
|
2015-03-30 05:44:55 +02:00
|
|
|
|
2018-10-14 17:16:58 +02:00
|
|
|
c, b := request(http.MethodGet, "/", e)
|
2016-03-17 22:24:52 +02:00
|
|
|
assert.Equal(t, "-1123", buf.String())
|
2015-05-31 00:20:36 +02:00
|
|
|
assert.Equal(t, http.StatusOK, c)
|
2016-01-09 19:44:18 +02:00
|
|
|
assert.Equal(t, "OK", b)
|
2016-03-17 22:24:52 +02:00
|
|
|
}
|
2015-05-31 00:20:36 +02:00
|
|
|
|
2016-03-17 22:24:52 +02:00
|
|
|
func TestEchoMiddlewareError(t *testing.T) {
|
|
|
|
e := New()
|
2016-09-23 14:31:48 +02:00
|
|
|
e.Use(func(next HandlerFunc) HandlerFunc {
|
|
|
|
return func(c Context) error {
|
|
|
|
return errors.New("error")
|
|
|
|
}
|
|
|
|
})
|
2021-07-15 22:34:01 +02:00
|
|
|
e.GET("/", notFoundHandler)
|
2018-10-14 17:16:58 +02:00
|
|
|
c, _ := request(http.MethodGet, "/", e)
|
2015-05-31 00:20:36 +02:00
|
|
|
assert.Equal(t, http.StatusInternalServerError, c)
|
2015-03-30 08:35:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEchoHandler(t *testing.T) {
|
|
|
|
e := New()
|
|
|
|
|
2015-04-29 20:22:07 +02:00
|
|
|
// HandlerFunc
|
2016-04-19 01:59:58 +02:00
|
|
|
e.GET("/ok", func(c Context) error {
|
2016-01-09 19:44:18 +02:00
|
|
|
return c.String(http.StatusOK, "OK")
|
2016-04-02 23:19:39 +02:00
|
|
|
})
|
2015-04-19 01:47:48 +02:00
|
|
|
|
2018-10-14 17:16:58 +02:00
|
|
|
c, b := request(http.MethodGet, "/ok", e)
|
2016-01-09 19:44:18 +02:00
|
|
|
assert.Equal(t, http.StatusOK, c)
|
|
|
|
assert.Equal(t, "OK", b)
|
2015-03-30 05:44:55 +02:00
|
|
|
}
|
|
|
|
|
2016-09-23 14:31:48 +02:00
|
|
|
func TestEchoWrapHandler(t *testing.T) {
|
|
|
|
e := New()
|
2018-10-14 17:16:58 +02:00
|
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
2016-09-23 14:31:48 +02:00
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
c := e.NewContext(req, rec)
|
|
|
|
h := WrapHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write([]byte("test"))
|
|
|
|
}))
|
|
|
|
if assert.NoError(t, h(c)) {
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
assert.Equal(t, "test", rec.Body.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestEchoWrapMiddleware(t *testing.T) {
|
|
|
|
e := New()
|
2018-10-14 17:16:58 +02:00
|
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
2016-09-23 14:31:48 +02:00
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
c := e.NewContext(req, rec)
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
mw := WrapMiddleware(func(h http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
buf.Write([]byte("mw"))
|
|
|
|
h.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
h := mw(func(c Context) error {
|
|
|
|
return c.String(http.StatusOK, "OK")
|
|
|
|
})
|
|
|
|
if assert.NoError(t, h(c)) {
|
|
|
|
assert.Equal(t, "mw", buf.String())
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
assert.Equal(t, "OK", rec.Body.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func TestEchoGet_routeInfoIsImmutable(t *testing.T) {
|
|
|
|
e := New()
|
|
|
|
ri := e.GET("/test", handlerFunc)
|
|
|
|
assert.Equal(t, "GET:/test", ri.Name())
|
|
|
|
|
|
|
|
riFromRouter, err := e.Router().Routes().FindByMethodPath(http.MethodGet, "/test")
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "GET:/test", riFromRouter.Name())
|
|
|
|
|
|
|
|
rInfo := ri.(routeInfo)
|
|
|
|
rInfo.name = "changed" // this change should not change other returned values
|
|
|
|
|
|
|
|
assert.Equal(t, "GET:/test", ri.Name())
|
|
|
|
|
|
|
|
riFromRouter, err = e.Router().Routes().FindByMethodPath(http.MethodGet, "/test")
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "GET:/test", riFromRouter.Name())
|
|
|
|
}
|
|
|
|
|
2015-05-30 01:00:02 +02:00
|
|
|
func TestEchoConnect(t *testing.T) {
|
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
ri := e.CONNECT("/", func(c Context) error {
|
|
|
|
return c.String(http.StatusTeapot, "OK")
|
|
|
|
})
|
|
|
|
|
|
|
|
assert.Equal(t, http.MethodConnect, ri.Method())
|
|
|
|
assert.Equal(t, "/", ri.Path())
|
|
|
|
assert.Equal(t, http.MethodConnect+":/", ri.Name())
|
|
|
|
assert.Nil(t, ri.Params())
|
|
|
|
|
|
|
|
status, body := request(http.MethodConnect, "/", e)
|
|
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
|
|
assert.Equal(t, "OK", body)
|
2015-05-30 01:00:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEchoDelete(t *testing.T) {
|
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
ri := e.DELETE("/", func(c Context) error {
|
|
|
|
return c.String(http.StatusTeapot, "OK")
|
|
|
|
})
|
|
|
|
|
|
|
|
assert.Equal(t, http.MethodDelete, ri.Method())
|
|
|
|
assert.Equal(t, "/", ri.Path())
|
|
|
|
assert.Equal(t, http.MethodDelete+":/", ri.Name())
|
|
|
|
assert.Nil(t, ri.Params())
|
|
|
|
|
|
|
|
status, body := request(http.MethodDelete, "/", e)
|
|
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
|
|
assert.Equal(t, "OK", body)
|
2015-05-30 01:00:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEchoGet(t *testing.T) {
|
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
ri := e.GET("/", func(c Context) error {
|
|
|
|
return c.String(http.StatusTeapot, "OK")
|
|
|
|
})
|
|
|
|
|
|
|
|
assert.Equal(t, http.MethodGet, ri.Method())
|
|
|
|
assert.Equal(t, "/", ri.Path())
|
|
|
|
assert.Equal(t, http.MethodGet+":/", ri.Name())
|
|
|
|
assert.Nil(t, ri.Params())
|
|
|
|
|
|
|
|
status, body := request(http.MethodGet, "/", e)
|
|
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
|
|
assert.Equal(t, "OK", body)
|
2015-05-30 01:00:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEchoHead(t *testing.T) {
|
2015-04-06 00:30:03 +02:00
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
ri := e.HEAD("/", func(c Context) error {
|
|
|
|
return c.String(http.StatusTeapot, "OK")
|
|
|
|
})
|
|
|
|
|
|
|
|
assert.Equal(t, http.MethodHead, ri.Method())
|
|
|
|
assert.Equal(t, "/", ri.Path())
|
|
|
|
assert.Equal(t, http.MethodHead+":/", ri.Name())
|
|
|
|
assert.Nil(t, ri.Params())
|
|
|
|
|
|
|
|
status, body := request(http.MethodHead, "/", e)
|
|
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
|
|
assert.Equal(t, "OK", body)
|
2015-05-30 01:00:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEchoOptions(t *testing.T) {
|
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
ri := e.OPTIONS("/", func(c Context) error {
|
|
|
|
return c.String(http.StatusTeapot, "OK")
|
|
|
|
})
|
|
|
|
|
|
|
|
assert.Equal(t, http.MethodOptions, ri.Method())
|
|
|
|
assert.Equal(t, "/", ri.Path())
|
|
|
|
assert.Equal(t, http.MethodOptions+":/", ri.Name())
|
|
|
|
assert.Nil(t, ri.Params())
|
|
|
|
|
|
|
|
status, body := request(http.MethodOptions, "/", e)
|
|
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
|
|
assert.Equal(t, "OK", body)
|
2015-05-30 01:00:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEchoPatch(t *testing.T) {
|
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
ri := e.PATCH("/", func(c Context) error {
|
|
|
|
return c.String(http.StatusTeapot, "OK")
|
|
|
|
})
|
|
|
|
|
|
|
|
assert.Equal(t, http.MethodPatch, ri.Method())
|
|
|
|
assert.Equal(t, "/", ri.Path())
|
|
|
|
assert.Equal(t, http.MethodPatch+":/", ri.Name())
|
|
|
|
assert.Nil(t, ri.Params())
|
|
|
|
|
|
|
|
status, body := request(http.MethodPatch, "/", e)
|
|
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
|
|
assert.Equal(t, "OK", body)
|
2015-05-30 01:00:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEchoPost(t *testing.T) {
|
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
ri := e.POST("/", func(c Context) error {
|
|
|
|
return c.String(http.StatusTeapot, "OK")
|
|
|
|
})
|
|
|
|
|
|
|
|
assert.Equal(t, http.MethodPost, ri.Method())
|
|
|
|
assert.Equal(t, "/", ri.Path())
|
|
|
|
assert.Equal(t, http.MethodPost+":/", ri.Name())
|
|
|
|
assert.Nil(t, ri.Params())
|
|
|
|
|
|
|
|
status, body := request(http.MethodPost, "/", e)
|
|
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
|
|
assert.Equal(t, "OK", body)
|
2015-05-30 01:00:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEchoPut(t *testing.T) {
|
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
ri := e.PUT("/", func(c Context) error {
|
|
|
|
return c.String(http.StatusTeapot, "OK")
|
|
|
|
})
|
|
|
|
|
|
|
|
assert.Equal(t, http.MethodPut, ri.Method())
|
|
|
|
assert.Equal(t, "/", ri.Path())
|
|
|
|
assert.Equal(t, http.MethodPut+":/", ri.Name())
|
|
|
|
assert.Nil(t, ri.Params())
|
|
|
|
|
|
|
|
status, body := request(http.MethodPut, "/", e)
|
|
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
|
|
assert.Equal(t, "OK", body)
|
2015-05-30 01:00:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEchoTrace(t *testing.T) {
|
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
ri := e.TRACE("/", func(c Context) error {
|
|
|
|
return c.String(http.StatusTeapot, "OK")
|
|
|
|
})
|
|
|
|
|
|
|
|
assert.Equal(t, http.MethodTrace, ri.Method())
|
|
|
|
assert.Equal(t, "/", ri.Path())
|
|
|
|
assert.Equal(t, http.MethodTrace+":/", ri.Name())
|
|
|
|
assert.Nil(t, ri.Params())
|
|
|
|
|
|
|
|
status, body := request(http.MethodTrace, "/", e)
|
|
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
|
|
assert.Equal(t, "OK", body)
|
2015-05-30 01:00:02 +02:00
|
|
|
}
|
|
|
|
|
2015-08-26 05:36:15 +02:00
|
|
|
func TestEchoAny(t *testing.T) { // JFC
|
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
ris := e.Any("/", func(c Context) error {
|
2015-08-26 05:36:15 +02:00
|
|
|
return c.String(http.StatusOK, "Any")
|
2016-04-02 23:19:39 +02:00
|
|
|
})
|
2021-07-15 22:34:01 +02:00
|
|
|
assert.Len(t, ris, 11)
|
2015-08-26 05:36:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEchoMatch(t *testing.T) { // JFC
|
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
ris := e.Match([]string{http.MethodGet, http.MethodPost}, "/", func(c Context) error {
|
2015-08-26 05:36:15 +02:00
|
|
|
return c.String(http.StatusOK, "Match")
|
2016-04-02 23:19:39 +02:00
|
|
|
})
|
2021-07-15 22:34:01 +02:00
|
|
|
assert.Len(t, ris, 2)
|
2015-08-26 05:36:15 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func TestEcho_Routers_HandleHostsProperly(t *testing.T) {
|
2021-06-25 22:56:07 +02:00
|
|
|
e := New()
|
|
|
|
h := e.Host("route.com")
|
|
|
|
routes := []*Route{
|
2021-07-15 22:34:01 +02:00
|
|
|
{Method: http.MethodGet, Path: "/users/:user/events"},
|
|
|
|
{Method: http.MethodGet, Path: "/users/:user/events/public"},
|
|
|
|
{Method: http.MethodPost, Path: "/repos/:owner/:repo/git/refs"},
|
|
|
|
{Method: http.MethodPost, Path: "/repos/:owner/:repo/git/tags"},
|
2021-06-25 22:56:07 +02:00
|
|
|
}
|
|
|
|
for _, r := range routes {
|
|
|
|
h.Add(r.Method, r.Path, func(c Context) error {
|
|
|
|
return c.String(http.StatusOK, "OK")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
routers := e.Routers()
|
|
|
|
|
|
|
|
routeCom, ok := routers["route.com"]
|
|
|
|
assert.True(t, ok)
|
|
|
|
|
|
|
|
if assert.Equal(t, len(routes), len(routeCom.Routes())) {
|
|
|
|
for _, r := range routeCom.Routes() {
|
2021-06-25 22:56:07 +02:00
|
|
|
found := false
|
|
|
|
for _, rr := range routes {
|
2021-07-15 22:34:01 +02:00
|
|
|
if r.Method() == rr.Method && r.Path() == rr.Path {
|
2021-06-25 22:56:07 +02:00
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
2021-07-15 22:34:01 +02:00
|
|
|
t.Errorf("Route %s %s not found", r.Method(), r.Path())
|
2021-06-25 22:56:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-08 03:01:02 +02:00
|
|
|
func TestEchoServeHTTPPathEncoding(t *testing.T) {
|
2017-06-07 17:23:43 +02:00
|
|
|
e := New()
|
2021-03-08 03:01:02 +02:00
|
|
|
e.GET("/with/slash", func(c Context) error {
|
|
|
|
return c.String(http.StatusOK, "/with/slash")
|
|
|
|
})
|
2017-06-07 17:23:43 +02:00
|
|
|
e.GET("/:id", func(c Context) error {
|
2021-07-15 22:34:01 +02:00
|
|
|
return c.String(http.StatusOK, c.PathParam("id"))
|
2017-06-07 17:23:43 +02:00
|
|
|
})
|
2021-03-08 03:01:02 +02:00
|
|
|
|
|
|
|
var testCases = []struct {
|
|
|
|
name string
|
|
|
|
whenURL string
|
|
|
|
expectURL string
|
|
|
|
expectStatus int
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "url with encoding is not decoded for routing",
|
|
|
|
whenURL: "/with%2Fslash",
|
|
|
|
expectURL: "with%2Fslash", // `%2F` is not decoded to `/` for routing
|
|
|
|
expectStatus: http.StatusOK,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "url without encoding is used as is",
|
|
|
|
whenURL: "/with/slash",
|
|
|
|
expectURL: "/with/slash",
|
|
|
|
expectStatus: http.StatusOK,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
|
|
|
|
e.ServeHTTP(rec, req)
|
|
|
|
|
|
|
|
assert.Equal(t, tc.expectStatus, rec.Code)
|
|
|
|
assert.Equal(t, tc.expectURL, rec.Body.String())
|
|
|
|
})
|
|
|
|
}
|
2017-06-07 17:23:43 +02:00
|
|
|
}
|
|
|
|
|
2021-06-25 22:56:07 +02:00
|
|
|
func TestEchoHost(t *testing.T) {
|
|
|
|
okHandler := func(c Context) error { return c.String(http.StatusOK, http.StatusText(http.StatusOK)) }
|
|
|
|
teapotHandler := func(c Context) error { return c.String(http.StatusTeapot, http.StatusText(http.StatusTeapot)) }
|
|
|
|
acceptHandler := func(c Context) error { return c.String(http.StatusAccepted, http.StatusText(http.StatusAccepted)) }
|
|
|
|
teapotMiddleware := MiddlewareFunc(func(next HandlerFunc) HandlerFunc { return teapotHandler })
|
|
|
|
|
|
|
|
e := New()
|
|
|
|
e.GET("/", acceptHandler)
|
|
|
|
e.GET("/foo", acceptHandler)
|
|
|
|
|
|
|
|
ok := e.Host("ok.com")
|
|
|
|
ok.GET("/", okHandler)
|
|
|
|
ok.GET("/foo", okHandler)
|
|
|
|
|
|
|
|
teapot := e.Host("teapot.com")
|
|
|
|
teapot.GET("/", teapotHandler)
|
|
|
|
teapot.GET("/foo", teapotHandler)
|
|
|
|
|
|
|
|
middle := e.Host("middleware.com", teapotMiddleware)
|
|
|
|
middle.GET("/", okHandler)
|
|
|
|
middle.GET("/foo", okHandler)
|
|
|
|
|
|
|
|
var testCases = []struct {
|
|
|
|
name string
|
|
|
|
whenHost string
|
|
|
|
whenPath string
|
|
|
|
expectBody string
|
|
|
|
expectStatus int
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "No Host Root",
|
|
|
|
whenHost: "",
|
|
|
|
whenPath: "/",
|
|
|
|
expectBody: http.StatusText(http.StatusAccepted),
|
|
|
|
expectStatus: http.StatusAccepted,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "No Host Foo",
|
|
|
|
whenHost: "",
|
|
|
|
whenPath: "/foo",
|
|
|
|
expectBody: http.StatusText(http.StatusAccepted),
|
|
|
|
expectStatus: http.StatusAccepted,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "OK Host Root",
|
|
|
|
whenHost: "ok.com",
|
|
|
|
whenPath: "/",
|
|
|
|
expectBody: http.StatusText(http.StatusOK),
|
|
|
|
expectStatus: http.StatusOK,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "OK Host Foo",
|
|
|
|
whenHost: "ok.com",
|
|
|
|
whenPath: "/foo",
|
|
|
|
expectBody: http.StatusText(http.StatusOK),
|
|
|
|
expectStatus: http.StatusOK,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Teapot Host Root",
|
|
|
|
whenHost: "teapot.com",
|
|
|
|
whenPath: "/",
|
|
|
|
expectBody: http.StatusText(http.StatusTeapot),
|
|
|
|
expectStatus: http.StatusTeapot,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Teapot Host Foo",
|
|
|
|
whenHost: "teapot.com",
|
|
|
|
whenPath: "/foo",
|
|
|
|
expectBody: http.StatusText(http.StatusTeapot),
|
|
|
|
expectStatus: http.StatusTeapot,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Middleware Host",
|
|
|
|
whenHost: "middleware.com",
|
|
|
|
whenPath: "/",
|
|
|
|
expectBody: http.StatusText(http.StatusTeapot),
|
|
|
|
expectStatus: http.StatusTeapot,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Middleware Host Foo",
|
|
|
|
whenHost: "middleware.com",
|
|
|
|
whenPath: "/foo",
|
|
|
|
expectBody: http.StatusText(http.StatusTeapot),
|
|
|
|
expectStatus: http.StatusTeapot,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.whenPath, nil)
|
|
|
|
req.Host = tc.whenHost
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
|
|
|
|
e.ServeHTTP(rec, req)
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
assert.Equal(t, tc.expectStatus, rec.Code)
|
|
|
|
assert.Equal(t, tc.expectBody, rec.Body.String())
|
2021-06-25 22:56:07 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-01 09:07:53 +02:00
|
|
|
func TestEchoGroup(t *testing.T) {
|
|
|
|
e := New()
|
|
|
|
buf := new(bytes.Buffer)
|
2016-09-23 14:31:48 +02:00
|
|
|
e.Use(MiddlewareFunc(func(next HandlerFunc) HandlerFunc {
|
2016-04-02 23:19:39 +02:00
|
|
|
return func(c Context) error {
|
2016-01-29 09:46:11 +02:00
|
|
|
buf.WriteString("0")
|
2016-09-23 14:31:48 +02:00
|
|
|
return next(c)
|
2016-04-02 23:19:39 +02:00
|
|
|
}
|
2016-02-16 03:12:15 +02:00
|
|
|
}))
|
2016-04-02 23:19:39 +02:00
|
|
|
h := func(c Context) error {
|
2016-01-29 09:46:11 +02:00
|
|
|
return c.NoContent(http.StatusOK)
|
2016-04-02 23:19:39 +02:00
|
|
|
}
|
2015-06-01 09:07:53 +02:00
|
|
|
|
|
|
|
//--------
|
|
|
|
// Routes
|
|
|
|
//--------
|
|
|
|
|
2016-04-19 01:59:58 +02:00
|
|
|
e.GET("/users", h)
|
2015-06-01 09:07:53 +02:00
|
|
|
|
|
|
|
// Group
|
|
|
|
g1 := e.Group("/group1")
|
2016-09-23 14:31:48 +02:00
|
|
|
g1.Use(func(next HandlerFunc) HandlerFunc {
|
|
|
|
return func(c Context) error {
|
|
|
|
buf.WriteString("1")
|
|
|
|
return next(c)
|
|
|
|
}
|
|
|
|
})
|
2016-10-13 22:51:37 +02:00
|
|
|
g1.GET("", h)
|
2015-06-01 09:07:53 +02:00
|
|
|
|
2016-03-17 05:15:38 +02:00
|
|
|
// Nested groups with middleware
|
2016-02-15 18:11:29 +02:00
|
|
|
g2 := e.Group("/group2")
|
2016-09-23 14:31:48 +02:00
|
|
|
g2.Use(func(next HandlerFunc) HandlerFunc {
|
|
|
|
return func(c Context) error {
|
|
|
|
buf.WriteString("2")
|
|
|
|
return next(c)
|
|
|
|
}
|
|
|
|
})
|
2016-02-15 18:11:29 +02:00
|
|
|
g3 := g2.Group("/group3")
|
2016-09-23 14:31:48 +02:00
|
|
|
g3.Use(func(next HandlerFunc) HandlerFunc {
|
|
|
|
return func(c Context) error {
|
|
|
|
buf.WriteString("3")
|
|
|
|
return next(c)
|
|
|
|
}
|
|
|
|
})
|
2016-10-13 22:51:37 +02:00
|
|
|
g3.GET("", h)
|
2015-06-01 09:07:53 +02:00
|
|
|
|
2018-10-14 17:16:58 +02:00
|
|
|
request(http.MethodGet, "/users", e)
|
2015-06-01 09:07:53 +02:00
|
|
|
assert.Equal(t, "0", buf.String())
|
|
|
|
|
|
|
|
buf.Reset()
|
2018-10-14 17:16:58 +02:00
|
|
|
request(http.MethodGet, "/group1", e)
|
2015-06-01 09:07:53 +02:00
|
|
|
assert.Equal(t, "01", buf.String())
|
|
|
|
|
|
|
|
buf.Reset()
|
2018-10-14 17:16:58 +02:00
|
|
|
request(http.MethodGet, "/group2/group3", e)
|
2016-03-17 05:15:38 +02:00
|
|
|
assert.Equal(t, "023", buf.String())
|
2015-06-01 09:07:53 +02:00
|
|
|
}
|
|
|
|
|
2015-04-06 05:08:52 +02:00
|
|
|
func TestEchoNotFound(t *testing.T) {
|
2015-03-31 05:54:38 +02:00
|
|
|
e := New()
|
2018-10-14 17:16:58 +02:00
|
|
|
req := httptest.NewRequest(http.MethodGet, "/files", nil)
|
2016-09-23 07:53:44 +02:00
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
e.ServeHTTP(rec, req)
|
|
|
|
assert.Equal(t, http.StatusNotFound, rec.Code)
|
2015-03-31 05:54:38 +02:00
|
|
|
}
|
|
|
|
|
2015-10-06 15:48:33 +02:00
|
|
|
func TestEchoMethodNotAllowed(t *testing.T) {
|
2015-11-04 02:26:20 +02:00
|
|
|
e := New()
|
2021-12-04 20:02:11 +02:00
|
|
|
|
2016-04-19 01:59:58 +02:00
|
|
|
e.GET("/", func(c Context) error {
|
2015-11-04 02:26:20 +02:00
|
|
|
return c.String(http.StatusOK, "Echo!")
|
2016-04-02 23:19:39 +02:00
|
|
|
})
|
2018-10-14 17:16:58 +02:00
|
|
|
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
2016-09-23 07:53:44 +02:00
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
e.ServeHTTP(rec, req)
|
2021-12-04 20:02:11 +02:00
|
|
|
|
2016-09-23 07:53:44 +02:00
|
|
|
assert.Equal(t, http.StatusMethodNotAllowed, rec.Code)
|
2021-12-04 20:02:11 +02:00
|
|
|
assert.Equal(t, "OPTIONS, GET", rec.Header().Get(HeaderAllow))
|
2015-07-24 21:03:36 +02:00
|
|
|
}
|
|
|
|
|
2016-06-07 01:54:24 +02:00
|
|
|
func TestEchoContext(t *testing.T) {
|
|
|
|
e := New()
|
|
|
|
c := e.AcquireContext()
|
2021-07-15 22:34:01 +02:00
|
|
|
assert.IsType(t, new(DefaultContext), c)
|
2016-06-07 01:54:24 +02:00
|
|
|
e.ReleaseContext(c)
|
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func TestEcho_Start(t *testing.T) {
|
2021-02-28 20:13:04 +02:00
|
|
|
e := New()
|
|
|
|
e.GET("/", func(c Context) error {
|
2021-07-15 22:34:01 +02:00
|
|
|
return c.String(http.StatusTeapot, "OK")
|
2021-02-28 20:13:04 +02:00
|
|
|
})
|
2021-07-15 22:34:01 +02:00
|
|
|
rndPort, err := net.Listen("tcp", ":0")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer rndPort.Close()
|
|
|
|
errChan := make(chan error, 1)
|
2021-02-28 20:13:04 +02:00
|
|
|
go func() {
|
2021-07-15 22:34:01 +02:00
|
|
|
errChan <- e.Start(rndPort.Addr().String())
|
2021-02-28 20:13:04 +02:00
|
|
|
}()
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
select {
|
|
|
|
case <-time.After(250 * time.Millisecond):
|
|
|
|
t.Fatal("start did not error out")
|
|
|
|
case err := <-errChan:
|
|
|
|
assert.Contains(t, err.Error(), "bind: address already in use")
|
|
|
|
}
|
2021-02-28 20:13:04 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func request(method, path string, e *Echo) (int, string) {
|
|
|
|
req := httptest.NewRequest(method, path, nil)
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
e.ServeHTTP(rec, req)
|
|
|
|
return rec.Code, rec.Body.String()
|
|
|
|
}
|
2019-02-15 19:51:54 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func TestDefaultHTTPErrorHandler(t *testing.T) {
|
|
|
|
var testCases = []struct {
|
|
|
|
name string
|
|
|
|
givenExposeError bool
|
|
|
|
givenLoggerFunc bool
|
|
|
|
whenMethod string
|
|
|
|
whenError error
|
|
|
|
expectBody string
|
|
|
|
expectStatus int
|
|
|
|
expectLogged string
|
2019-02-15 19:51:54 +02:00
|
|
|
}{
|
|
|
|
{
|
2021-07-15 22:34:01 +02:00
|
|
|
name: "ok, expose error = true, HTTPError",
|
|
|
|
givenExposeError: true,
|
|
|
|
whenError: NewHTTPError(http.StatusTeapot, "my_error"),
|
|
|
|
expectStatus: http.StatusTeapot,
|
|
|
|
expectBody: `{"error":"code=418, message=my_error","message":"my_error"}` + "\n",
|
2019-02-15 19:51:54 +02:00
|
|
|
},
|
|
|
|
{
|
2021-07-15 22:34:01 +02:00
|
|
|
name: "ok, expose error = true, HTTPError + internal error",
|
|
|
|
givenExposeError: true,
|
|
|
|
whenError: NewHTTPError(http.StatusTeapot, "my_error").WithInternal(errors.New("internal_error")),
|
|
|
|
expectStatus: http.StatusTeapot,
|
|
|
|
expectBody: `{"error":"code=418, message=my_error, internal=internal_error","message":"my_error"}` + "\n",
|
2019-02-15 19:51:54 +02:00
|
|
|
},
|
|
|
|
{
|
2021-07-15 22:34:01 +02:00
|
|
|
name: "ok, expose error = true, HTTPError + internal HTTPError",
|
|
|
|
givenExposeError: true,
|
|
|
|
whenError: NewHTTPError(http.StatusTeapot, "my_error").WithInternal(NewHTTPError(http.StatusTooEarly, "early_error")),
|
|
|
|
expectStatus: http.StatusTooEarly,
|
|
|
|
expectBody: `{"error":"code=418, message=my_error, internal=code=425, message=early_error","message":"early_error"}` + "\n",
|
2019-02-15 19:51:54 +02:00
|
|
|
},
|
|
|
|
{
|
2021-07-15 22:34:01 +02:00
|
|
|
name: "ok, expose error = false, HTTPError",
|
|
|
|
whenError: NewHTTPError(http.StatusTeapot, "my_error"),
|
|
|
|
expectStatus: http.StatusTeapot,
|
|
|
|
expectBody: `{"message":"my_error"}` + "\n",
|
2019-02-15 19:51:54 +02:00
|
|
|
},
|
|
|
|
{
|
2021-07-15 22:34:01 +02:00
|
|
|
name: "ok, expose error = false, HTTPError + internal HTTPError",
|
|
|
|
whenError: NewHTTPError(http.StatusTeapot, "my_error").WithInternal(NewHTTPError(http.StatusTooEarly, "early_error")),
|
|
|
|
expectStatus: http.StatusTooEarly,
|
|
|
|
expectBody: `{"message":"early_error"}` + "\n",
|
2019-02-15 19:51:54 +02:00
|
|
|
},
|
2020-12-29 11:46:09 +02:00
|
|
|
{
|
2021-07-15 22:34:01 +02:00
|
|
|
name: "ok, expose error = true, Error",
|
|
|
|
givenExposeError: true,
|
|
|
|
whenError: fmt.Errorf("my errors wraps: %w", errors.New("internal_error")),
|
|
|
|
expectStatus: http.StatusInternalServerError,
|
|
|
|
expectBody: `{"error":"my errors wraps: internal_error","message":"Internal Server Error"}` + "\n",
|
2020-12-29 11:46:09 +02:00
|
|
|
},
|
|
|
|
{
|
2021-07-15 22:34:01 +02:00
|
|
|
name: "ok, expose error = false, Error",
|
|
|
|
whenError: fmt.Errorf("my errors wraps: %w", errors.New("internal_error")),
|
|
|
|
expectStatus: http.StatusInternalServerError,
|
|
|
|
expectBody: `{"message":"Internal Server Error"}` + "\n",
|
2020-12-29 11:46:09 +02:00
|
|
|
},
|
|
|
|
{
|
2021-07-15 22:34:01 +02:00
|
|
|
name: "ok, http.HEAD, expose error = true, Error",
|
|
|
|
givenExposeError: true,
|
|
|
|
whenMethod: http.MethodHead,
|
|
|
|
whenError: fmt.Errorf("my errors wraps: %w", errors.New("internal_error")),
|
|
|
|
expectStatus: http.StatusInternalServerError,
|
|
|
|
expectBody: ``,
|
2020-12-29 11:46:09 +02:00
|
|
|
},
|
|
|
|
}
|
2020-01-29 00:46:00 +02:00
|
|
|
|
2020-12-29 11:46:09 +02:00
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
2021-07-15 22:34:01 +02:00
|
|
|
buf := new(bytes.Buffer)
|
2020-12-29 11:46:09 +02:00
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
e.Logger = &jsonLogger{writer: buf}
|
|
|
|
e.Any("/path", func(c Context) error {
|
|
|
|
return tc.whenError
|
2020-11-05 06:15:32 +02:00
|
|
|
})
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
e.HTTPErrorHandler = DefaultHTTPErrorHandler(tc.givenExposeError)
|
2020-11-05 06:15:32 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
method := http.MethodGet
|
|
|
|
if tc.whenMethod != "" {
|
|
|
|
method = tc.whenMethod
|
2020-11-05 06:15:32 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
c, b := request(method, "/path", e)
|
2020-11-05 06:15:32 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
assert.Equal(t, tc.expectStatus, c)
|
|
|
|
assert.Equal(t, tc.expectBody, b)
|
|
|
|
assert.Equal(t, tc.expectLogged, buf.String())
|
2020-11-05 06:15:32 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
type myCustomContext struct {
|
|
|
|
DefaultContext
|
2020-11-05 06:15:32 +02:00
|
|
|
}
|
2020-12-10 06:10:13 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func (c *myCustomContext) QueryParam(name string) string {
|
|
|
|
return "prefix_" + c.DefaultContext.QueryParam(name)
|
2020-12-29 11:46:09 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func TestEcho_customContext(t *testing.T) {
|
2020-12-29 11:46:09 +02:00
|
|
|
e := New()
|
2021-07-15 22:34:01 +02:00
|
|
|
e.NewContextFunc = func(ec *Echo, pathParamAllocSize int) ServableContext {
|
|
|
|
return &myCustomContext{
|
|
|
|
DefaultContext: *NewDefaultContext(ec, pathParamAllocSize),
|
|
|
|
}
|
2020-12-29 11:46:09 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
e.GET("/info/:id/:file", func(c Context) error {
|
|
|
|
return c.String(http.StatusTeapot, c.QueryParam("param"))
|
|
|
|
})
|
2020-12-29 11:46:09 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
status, body := request(http.MethodGet, "/info/1/a.csv?param=123", e)
|
|
|
|
assert.Equal(t, http.StatusTeapot, status)
|
|
|
|
assert.Equal(t, "prefix_123", body)
|
2020-12-29 11:46:09 +02:00
|
|
|
}
|
2021-03-08 03:01:02 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func benchmarkEchoRoutes(b *testing.B, routes []testRoute) {
|
2021-03-08 03:01:02 +02:00
|
|
|
e := New()
|
|
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
|
|
u := req.URL
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
|
|
|
|
// Add routes
|
|
|
|
for _, route := range routes {
|
|
|
|
e.Add(route.Method, route.Path, func(c Context) error {
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find routes
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
for _, route := range routes {
|
|
|
|
req.Method = route.Method
|
|
|
|
u.Path = route.Path
|
|
|
|
e.ServeHTTP(w, req)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkEchoStaticRoutes(b *testing.B) {
|
|
|
|
benchmarkEchoRoutes(b, staticRoutes)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkEchoStaticRoutesMisses(b *testing.B) {
|
|
|
|
benchmarkEchoRoutes(b, staticRoutes)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkEchoGitHubAPI(b *testing.B) {
|
|
|
|
benchmarkEchoRoutes(b, gitHubAPI)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkEchoGitHubAPIMisses(b *testing.B) {
|
|
|
|
benchmarkEchoRoutes(b, gitHubAPI)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkEchoParseAPI(b *testing.B) {
|
|
|
|
benchmarkEchoRoutes(b, parseAPI)
|
|
|
|
}
|