2022-07-06 23:19:05 +02:00
|
|
|
package apis_test
|
|
|
|
|
|
|
|
import (
|
2023-07-20 21:01:58 +02:00
|
|
|
"database/sql"
|
2022-07-06 23:19:05 +02:00
|
|
|
"errors"
|
2023-04-15 13:44:07 +02:00
|
|
|
"fmt"
|
2022-07-06 23:19:05 +02:00
|
|
|
"net/http"
|
2023-04-15 13:44:07 +02:00
|
|
|
"strings"
|
2022-07-06 23:19:05 +02:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/labstack/echo/v5"
|
2022-10-30 10:28:14 +02:00
|
|
|
"github.com/pocketbase/pocketbase/apis"
|
2022-07-06 23:19:05 +02:00
|
|
|
"github.com/pocketbase/pocketbase/tests"
|
2024-01-20 15:03:45 +02:00
|
|
|
"github.com/pocketbase/pocketbase/tools/rest"
|
2023-04-15 13:44:07 +02:00
|
|
|
"github.com/spf13/cast"
|
2022-07-06 23:19:05 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func Test404(t *testing.T) {
|
2024-01-03 04:30:20 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2022-07-06 23:19:05 +02:00
|
|
|
scenarios := []tests.ApiScenario{
|
|
|
|
{
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/api/missing",
|
|
|
|
ExpectedStatus: 404,
|
|
|
|
ExpectedContent: []string{`"data":{}`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Method: http.MethodPost,
|
|
|
|
Url: "/api/missing",
|
|
|
|
ExpectedStatus: 404,
|
|
|
|
ExpectedContent: []string{`"data":{}`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Method: http.MethodPatch,
|
|
|
|
Url: "/api/missing",
|
|
|
|
ExpectedStatus: 404,
|
|
|
|
ExpectedContent: []string{`"data":{}`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Method: http.MethodDelete,
|
|
|
|
Url: "/api/missing",
|
|
|
|
ExpectedStatus: 404,
|
|
|
|
ExpectedContent: []string{`"data":{}`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Method: http.MethodHead,
|
|
|
|
Url: "/api/missing",
|
|
|
|
ExpectedStatus: 404,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, scenario := range scenarios {
|
|
|
|
scenario.Test(t)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCustomRoutesAndErrorsHandling(t *testing.T) {
|
2024-01-03 04:30:20 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2022-07-06 23:19:05 +02:00
|
|
|
scenarios := []tests.ApiScenario{
|
|
|
|
{
|
|
|
|
Name: "custom route",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/custom",
|
2022-09-07 19:31:05 +02:00
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
2022-07-06 23:19:05 +02:00
|
|
|
e.AddRoute(echo.Route{
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Path: "/custom",
|
|
|
|
Handler: func(c echo.Context) error {
|
|
|
|
return c.String(200, "test123")
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 200,
|
|
|
|
ExpectedContent: []string{"test123"},
|
|
|
|
},
|
2023-01-09 22:32:34 +02:00
|
|
|
{
|
|
|
|
Name: "custom route with url encoded parameter",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/a%2Bb%2Bc",
|
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.AddRoute(echo.Route{
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Path: "/:param",
|
|
|
|
Handler: func(c echo.Context) error {
|
|
|
|
return c.String(200, c.PathParam("param"))
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 200,
|
|
|
|
ExpectedContent: []string{"a+b+c"},
|
|
|
|
},
|
2022-07-06 23:19:05 +02:00
|
|
|
{
|
|
|
|
Name: "route with HTTPError",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/http-error",
|
2022-09-07 19:31:05 +02:00
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
2022-07-06 23:19:05 +02:00
|
|
|
e.AddRoute(echo.Route{
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Path: "/http-error",
|
|
|
|
Handler: func(c echo.Context) error {
|
|
|
|
return echo.ErrBadRequest
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 400,
|
|
|
|
ExpectedContent: []string{`{"code":400,"message":"Bad Request.","data":{}}`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "route with api error",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/api-error",
|
2022-09-07 19:31:05 +02:00
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
2022-07-06 23:19:05 +02:00
|
|
|
e.AddRoute(echo.Route{
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Path: "/api-error",
|
|
|
|
Handler: func(c echo.Context) error {
|
2022-10-30 10:28:14 +02:00
|
|
|
return apis.NewApiError(500, "test message", errors.New("internal_test"))
|
2022-07-06 23:19:05 +02:00
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 500,
|
|
|
|
ExpectedContent: []string{`{"code":500,"message":"Test message.","data":{}}`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "route with plain error",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/plain-error",
|
2022-09-07 19:31:05 +02:00
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
2022-07-06 23:19:05 +02:00
|
|
|
e.AddRoute(echo.Route{
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Path: "/plain-error",
|
|
|
|
Handler: func(c echo.Context) error {
|
|
|
|
return errors.New("Test error")
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 400,
|
|
|
|
ExpectedContent: []string{`{"code":400,"message":"Something went wrong while processing your request.","data":{}}`},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, scenario := range scenarios {
|
|
|
|
scenario.Test(t)
|
|
|
|
}
|
|
|
|
}
|
2023-03-15 18:09:16 +02:00
|
|
|
|
|
|
|
func TestRemoveTrailingSlashMiddleware(t *testing.T) {
|
2024-01-03 04:30:20 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2023-03-15 18:09:16 +02:00
|
|
|
scenarios := []tests.ApiScenario{
|
|
|
|
{
|
|
|
|
Name: "non /api/* route (exact match)",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/custom",
|
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.AddRoute(echo.Route{
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Path: "/custom",
|
|
|
|
Handler: func(c echo.Context) error {
|
|
|
|
return c.String(200, "test123")
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 200,
|
|
|
|
ExpectedContent: []string{"test123"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "non /api/* route (with trailing slash)",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/custom/",
|
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.AddRoute(echo.Route{
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Path: "/custom",
|
|
|
|
Handler: func(c echo.Context) error {
|
|
|
|
return c.String(200, "test123")
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 404,
|
|
|
|
ExpectedContent: []string{`"data":{}`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "/api/* route (exact match)",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/api/custom",
|
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.AddRoute(echo.Route{
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Path: "/api/custom",
|
|
|
|
Handler: func(c echo.Context) error {
|
|
|
|
return c.String(200, "test123")
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 200,
|
|
|
|
ExpectedContent: []string{"test123"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "/api/* route (with trailing slash)",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/api/custom/",
|
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.AddRoute(echo.Route{
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Path: "/api/custom",
|
|
|
|
Handler: func(c echo.Context) error {
|
|
|
|
return c.String(200, "test123")
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 200,
|
|
|
|
ExpectedContent: []string{"test123"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, scenario := range scenarios {
|
|
|
|
scenario.Test(t)
|
|
|
|
}
|
|
|
|
}
|
2023-04-15 13:44:07 +02:00
|
|
|
|
2024-01-20 15:03:45 +02:00
|
|
|
func TestMultiBinder(t *testing.T) {
|
2024-01-03 04:30:20 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2024-01-20 15:03:45 +02:00
|
|
|
rawJson := `{"name":"test123"}`
|
|
|
|
|
|
|
|
formData, mp, err := tests.MockMultipartData(map[string]string{
|
|
|
|
rest.MultipartJsonKey: rawJson,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2023-04-15 13:44:07 +02:00
|
|
|
scenarios := []tests.ApiScenario{
|
|
|
|
{
|
2024-01-20 15:03:45 +02:00
|
|
|
Name: "non-api group route",
|
2023-07-14 10:55:29 +02:00
|
|
|
Method: "POST",
|
2023-04-15 13:44:07 +02:00
|
|
|
Url: "/custom",
|
2024-01-20 15:03:45 +02:00
|
|
|
Body: strings.NewReader(rawJson),
|
2023-04-15 13:44:07 +02:00
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.AddRoute(echo.Route{
|
2023-07-14 10:55:29 +02:00
|
|
|
Method: "POST",
|
2023-04-15 13:44:07 +02:00
|
|
|
Path: "/custom",
|
|
|
|
Handler: func(c echo.Context) error {
|
|
|
|
data := &struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
}{}
|
|
|
|
|
|
|
|
if err := c.Bind(data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-01-20 15:03:45 +02:00
|
|
|
// try to read the body again
|
2023-07-17 22:13:39 +02:00
|
|
|
r := apis.RequestInfo(c)
|
2024-01-20 15:03:45 +02:00
|
|
|
if v := cast.ToString(r.Data["name"]); v != "test123" {
|
|
|
|
t.Fatalf("Expected request data with name %q, got, %q", "test123", v)
|
2023-04-15 13:44:07 +02:00
|
|
|
}
|
|
|
|
|
2023-07-14 10:55:29 +02:00
|
|
|
return c.NoContent(200)
|
2023-04-15 13:44:07 +02:00
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
2023-07-14 10:55:29 +02:00
|
|
|
ExpectedStatus: 200,
|
2023-04-15 13:44:07 +02:00
|
|
|
},
|
2023-07-14 10:55:29 +02:00
|
|
|
{
|
2024-01-20 15:03:45 +02:00
|
|
|
Name: "api group route",
|
2023-07-14 10:55:29 +02:00
|
|
|
Method: "GET",
|
|
|
|
Url: "/api/admins",
|
2024-01-20 15:03:45 +02:00
|
|
|
Body: strings.NewReader(rawJson),
|
2023-07-14 10:55:29 +02:00
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
|
|
return func(c echo.Context) error {
|
2023-07-14 11:10:37 +02:00
|
|
|
// it is not important whether the route handler return an error since
|
2023-07-17 22:13:39 +02:00
|
|
|
// we just need to ensure that the eagerRequestInfoCache was registered
|
2023-07-14 10:55:29 +02:00
|
|
|
next(c)
|
2023-04-15 13:44:07 +02:00
|
|
|
|
2023-07-14 11:10:37 +02:00
|
|
|
// ensure that the body was read at least once
|
2023-07-14 10:55:29 +02:00
|
|
|
data := &struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
}{}
|
|
|
|
c.Bind(data)
|
2023-04-15 13:44:07 +02:00
|
|
|
|
2024-01-20 15:03:45 +02:00
|
|
|
// try to read the body again
|
2023-07-17 22:13:39 +02:00
|
|
|
r := apis.RequestInfo(c)
|
2024-01-20 15:03:45 +02:00
|
|
|
if v := cast.ToString(r.Data["name"]); v != "test123" {
|
|
|
|
t.Fatalf("Expected request data with name %q, got, %q", "test123", v)
|
2023-07-14 10:55:29 +02:00
|
|
|
}
|
2023-04-15 13:44:07 +02:00
|
|
|
|
2023-07-14 10:55:29 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 200,
|
|
|
|
},
|
|
|
|
{
|
2024-01-20 15:03:45 +02:00
|
|
|
Name: "custom route with @jsonPayload as multipart body",
|
2023-07-14 10:55:29 +02:00
|
|
|
Method: "POST",
|
2024-01-20 15:03:45 +02:00
|
|
|
Url: "/custom",
|
|
|
|
Body: formData,
|
|
|
|
RequestHeaders: map[string]string{
|
|
|
|
"Content-Type": mp.FormDataContentType(),
|
|
|
|
},
|
2023-07-14 10:55:29 +02:00
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
2024-01-20 15:03:45 +02:00
|
|
|
e.AddRoute(echo.Route{
|
|
|
|
Method: "POST",
|
|
|
|
Path: "/custom",
|
|
|
|
Handler: func(c echo.Context) error {
|
2023-07-14 10:55:29 +02:00
|
|
|
data := &struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
}{}
|
2024-01-20 15:03:45 +02:00
|
|
|
|
|
|
|
if err := c.Bind(data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-07-14 10:55:29 +02:00
|
|
|
|
|
|
|
// try to read the body again
|
2023-07-17 22:13:39 +02:00
|
|
|
r := apis.RequestInfo(c)
|
2023-07-14 10:55:29 +02:00
|
|
|
if v := cast.ToString(r.Data["name"]); v != "test123" {
|
|
|
|
t.Fatalf("Expected request data with name %q, got, %q", "test123", v)
|
|
|
|
}
|
|
|
|
|
2024-01-20 15:03:45 +02:00
|
|
|
return c.NoContent(200)
|
|
|
|
},
|
2023-07-14 10:55:29 +02:00
|
|
|
})
|
2023-04-15 13:44:07 +02:00
|
|
|
},
|
2023-07-14 10:55:29 +02:00
|
|
|
ExpectedStatus: 200,
|
|
|
|
},
|
2023-04-15 13:44:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, scenario := range scenarios {
|
|
|
|
scenario.Test(t)
|
|
|
|
}
|
|
|
|
}
|
2023-07-20 21:01:58 +02:00
|
|
|
|
|
|
|
func TestErrorHandler(t *testing.T) {
|
2024-01-03 04:30:20 +02:00
|
|
|
t.Parallel()
|
|
|
|
|
2023-07-20 21:01:58 +02:00
|
|
|
scenarios := []tests.ApiScenario{
|
|
|
|
{
|
|
|
|
Name: "apis.ApiError",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/test",
|
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.GET("/test", func(c echo.Context) error {
|
|
|
|
return apis.NewApiError(418, "test", nil)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 418,
|
|
|
|
ExpectedContent: []string{`"message":"Test."`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "wrapped apis.ApiError",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/test",
|
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.GET("/test", func(c echo.Context) error {
|
|
|
|
return fmt.Errorf("example 123: %w", apis.NewApiError(418, "test", nil))
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 418,
|
|
|
|
ExpectedContent: []string{`"message":"Test."`},
|
|
|
|
NotExpectedContent: []string{"example", "123"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "echo.HTTPError",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/test",
|
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.GET("/test", func(c echo.Context) error {
|
|
|
|
return echo.NewHTTPError(418, "test")
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 418,
|
|
|
|
ExpectedContent: []string{`"message":"Test."`},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "wrapped echo.HTTPError",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/test",
|
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.GET("/test", func(c echo.Context) error {
|
|
|
|
return fmt.Errorf("example 123: %w", echo.NewHTTPError(418, "test"))
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 418,
|
|
|
|
ExpectedContent: []string{`"message":"Test."`},
|
|
|
|
NotExpectedContent: []string{"example", "123"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "wrapped sql.ErrNoRows",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/test",
|
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.GET("/test", func(c echo.Context) error {
|
|
|
|
return fmt.Errorf("example 123: %w", sql.ErrNoRows)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 404,
|
|
|
|
ExpectedContent: []string{`"data":{}`},
|
|
|
|
NotExpectedContent: []string{"example", "123"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "custom error",
|
|
|
|
Method: http.MethodGet,
|
|
|
|
Url: "/test",
|
|
|
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
|
|
|
e.GET("/test", func(c echo.Context) error {
|
|
|
|
return fmt.Errorf("example 123")
|
|
|
|
})
|
|
|
|
},
|
|
|
|
ExpectedStatus: 400,
|
|
|
|
ExpectedContent: []string{`"data":{}`},
|
|
|
|
NotExpectedContent: []string{"example", "123"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, scenario := range scenarios {
|
|
|
|
scenario.Test(t)
|
|
|
|
}
|
|
|
|
}
|