replaced .* errors with constructors and added apisBinds tests
This commit is contained in:
parent
1d20124467
commit
9bfcdd086a
|
@ -57,8 +57,8 @@ func RequestData(c echo.Context) *models.RequestData {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordAuthResponse generates and writes a properly formatted record
|
// RecordAuthResponse writes standardised json record auth response
|
||||||
// auth response into the specified request context.
|
// into the specified request context.
|
||||||
func RecordAuthResponse(
|
func RecordAuthResponse(
|
||||||
app core.App,
|
app core.App,
|
||||||
c echo.Context,
|
c echo.Context,
|
||||||
|
|
|
@ -244,9 +244,28 @@ declare class ApiError implements apis.ApiError {
|
||||||
constructor(status?: number, message?: string, data?: any)
|
constructor(status?: number, message?: string, data?: any)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NotFoundError extends apis.NotFoundError{} // merge
|
||||||
|
declare class NotFoundError implements apis.NotFoundError {
|
||||||
|
constructor(message?: string, data?: any)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BadRequestError extends apis.BadRequestError{} // merge
|
||||||
|
declare class BadRequestError implements apis.BadRequestError {
|
||||||
|
constructor(message?: string, data?: any)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ForbiddenError extends apis.ForbiddenError{} // merge
|
||||||
|
declare class ForbiddenError implements apis.ForbiddenError {
|
||||||
|
constructor(message?: string, data?: any)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UnauthorizedError extends apis.UnauthorizedError{} // merge
|
||||||
|
declare class UnauthorizedError implements apis.UnauthorizedError {
|
||||||
|
constructor(message?: string, data?: any)
|
||||||
|
}
|
||||||
|
|
||||||
declare namespace $apis {
|
declare namespace $apis {
|
||||||
let requireRecordAuth: apis.requireRecordAuth
|
let requireRecordAuth: apis.requireRecordAuth
|
||||||
let requireSameContextRecordAuth: apis.requireSameContextRecordAuth
|
|
||||||
let requireAdminAuth: apis.requireAdminAuth
|
let requireAdminAuth: apis.requireAdminAuth
|
||||||
let requireAdminAuthOnlyIfAny: apis.requireAdminAuthOnlyIfAny
|
let requireAdminAuthOnlyIfAny: apis.requireAdminAuthOnlyIfAny
|
||||||
let requireAdminOrRecordAuth: apis.requireAdminOrRecordAuth
|
let requireAdminOrRecordAuth: apis.requireAdminOrRecordAuth
|
||||||
|
@ -256,10 +275,6 @@ declare namespace $apis {
|
||||||
let recordAuthResponse: apis.recordAuthResponse
|
let recordAuthResponse: apis.recordAuthResponse
|
||||||
let enrichRecord: apis.enrichRecord
|
let enrichRecord: apis.enrichRecord
|
||||||
let enrichRecords: apis.enrichRecords
|
let enrichRecords: apis.enrichRecords
|
||||||
let notFoundError: apis.newNotFoundError
|
|
||||||
let badRequestError: apis.newBadRequestError
|
|
||||||
let forbiddenError: apis.newForbiddenError
|
|
||||||
let unauthorizedError: apis.newUnauthorizedError
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
@ -261,11 +261,15 @@ func formsBinds(vm *goja.Runtime) {
|
||||||
registerFactoryAsConstructor(vm, "TestS3FilesystemForm", forms.NewTestS3Filesystem)
|
registerFactoryAsConstructor(vm, "TestS3FilesystemForm", forms.NewTestS3Filesystem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo add tests
|
|
||||||
func apisBinds(vm *goja.Runtime) {
|
func apisBinds(vm *goja.Runtime) {
|
||||||
obj := vm.NewObject()
|
obj := vm.NewObject()
|
||||||
vm.Set("$apis", obj)
|
vm.Set("$apis", obj)
|
||||||
|
|
||||||
|
vm.Set("Route", func(call goja.ConstructorCall) *goja.Object {
|
||||||
|
instance := &echo.Route{}
|
||||||
|
return structConstructor(vm, call, instance)
|
||||||
|
})
|
||||||
|
|
||||||
// middlewares
|
// middlewares
|
||||||
obj.Set("requireRecordAuth", apis.RequireRecordAuth)
|
obj.Set("requireRecordAuth", apis.RequireRecordAuth)
|
||||||
obj.Set("requireAdminAuth", apis.RequireAdminAuth)
|
obj.Set("requireAdminAuth", apis.RequireAdminAuth)
|
||||||
|
@ -281,42 +285,42 @@ func apisBinds(vm *goja.Runtime) {
|
||||||
obj.Set("enrichRecords", apis.EnrichRecords)
|
obj.Set("enrichRecords", apis.EnrichRecords)
|
||||||
|
|
||||||
// api errors
|
// api errors
|
||||||
vm.Set("ApiError", func(call goja.ConstructorCall) *goja.Object {
|
registerFactoryAsConstructor(vm, "ApiError", apis.NewApiError)
|
||||||
status, _ := call.Argument(0).Export().(int64)
|
registerFactoryAsConstructor(vm, "NotFoundError", apis.NewNotFoundError)
|
||||||
message, _ := call.Argument(1).Export().(string)
|
registerFactoryAsConstructor(vm, "BadRequestError", apis.NewBadRequestError)
|
||||||
data := call.Argument(2).Export()
|
registerFactoryAsConstructor(vm, "ForbiddenError", apis.NewForbiddenError)
|
||||||
|
registerFactoryAsConstructor(vm, "UnauthorizedError", apis.NewUnauthorizedError)
|
||||||
instance := apis.NewApiError(int(status), message, data)
|
|
||||||
instanceValue := vm.ToValue(instance).(*goja.Object)
|
|
||||||
instanceValue.SetPrototype(call.This.Prototype())
|
|
||||||
|
|
||||||
return instanceValue
|
|
||||||
})
|
|
||||||
obj.Set("notFoundError", apis.NewNotFoundError)
|
|
||||||
obj.Set("badRequestError", apis.NewBadRequestError)
|
|
||||||
obj.Set("forbiddenError", apis.NewForbiddenError)
|
|
||||||
obj.Set("unauthorizedError", apis.NewUnauthorizedError)
|
|
||||||
|
|
||||||
vm.Set("Route", func(call goja.ConstructorCall) *goja.Object {
|
|
||||||
instance := &echo.Route{}
|
|
||||||
return structConstructor(vm, call, instance)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
// registerFactoryAsConstructor registers the factory function as native JS constructor.
|
// registerFactoryAsConstructor registers the factory function as native JS constructor.
|
||||||
|
//
|
||||||
|
// If there is missing or nil arguments, their type zero value is used.
|
||||||
func registerFactoryAsConstructor(vm *goja.Runtime, constructorName string, factoryFunc any) {
|
func registerFactoryAsConstructor(vm *goja.Runtime, constructorName string, factoryFunc any) {
|
||||||
|
rv := reflect.ValueOf(factoryFunc)
|
||||||
|
rt := reflect.TypeOf(factoryFunc)
|
||||||
|
totalArgs := rt.NumIn()
|
||||||
|
|
||||||
vm.Set(constructorName, func(call goja.ConstructorCall) *goja.Object {
|
vm.Set(constructorName, func(call goja.ConstructorCall) *goja.Object {
|
||||||
f := reflect.ValueOf(factoryFunc)
|
args := make([]reflect.Value, totalArgs)
|
||||||
|
|
||||||
args := []reflect.Value{}
|
for i := 0; i < totalArgs; i++ {
|
||||||
|
v := call.Argument(i).Export()
|
||||||
|
|
||||||
for _, v := range call.Arguments {
|
// use the arg type zero value
|
||||||
args = append(args, reflect.ValueOf(v.Export()))
|
if v == nil {
|
||||||
|
args[i] = reflect.New(rt.In(i)).Elem()
|
||||||
|
} else if number, ok := v.(int64); ok {
|
||||||
|
// goja uses int64 for "int"-like numbers but we rarely do that and use int most of the times
|
||||||
|
// (at later stage we can use reflection on the arguments to validate the types in case this is not sufficient anymore)
|
||||||
|
args[i] = reflect.ValueOf(int(number))
|
||||||
|
} else {
|
||||||
|
args[i] = reflect.ValueOf(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result := f.Call(args)
|
result := rv.Call(args)
|
||||||
|
|
||||||
if len(result) != 1 {
|
if len(result) != 1 {
|
||||||
panic("the factory function should return only 1 item")
|
panic("the factory function should return only 1 item")
|
||||||
|
|
|
@ -4,11 +4,13 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/pocketbase/pocketbase/apis"
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"github.com/pocketbase/pocketbase/models/schema"
|
"github.com/pocketbase/pocketbase/models/schema"
|
||||||
|
@ -18,6 +20,19 @@ import (
|
||||||
"github.com/pocketbase/pocketbase/tools/security"
|
"github.com/pocketbase/pocketbase/tools/security"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func testBindsCount(vm *goja.Runtime, namespace string, count int, t *testing.T) {
|
||||||
|
v, err := vm.RunString(`Object.keys(` + namespace + `).length`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
total, _ := v.Export().(int64)
|
||||||
|
|
||||||
|
if int(total) != count {
|
||||||
|
t.Fatalf("Expected %d %s binds, got %d", count, namespace, total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// note: this test is useful as a reminder to update the tests in case
|
// note: this test is useful as a reminder to update the tests in case
|
||||||
// a new base binding is added.
|
// a new base binding is added.
|
||||||
func TestBaseBindsCount(t *testing.T) {
|
func TestBaseBindsCount(t *testing.T) {
|
||||||
|
@ -624,16 +639,105 @@ func TestFormsBinds(t *testing.T) {
|
||||||
testBindsCount(vm, "this", 20, t)
|
testBindsCount(vm, "this", 20, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBindsCount(vm *goja.Runtime, namespace string, count int, t *testing.T) {
|
func TestApisBindsCount(t *testing.T) {
|
||||||
v, err := vm.RunString(`Object.keys(` + namespace + `).length`)
|
app, _ := tests.NewTestApp()
|
||||||
|
defer app.Cleanup()
|
||||||
|
|
||||||
|
vm := goja.New()
|
||||||
|
apisBinds(vm)
|
||||||
|
|
||||||
|
testBindsCount(vm, "this", 7, t)
|
||||||
|
testBindsCount(vm, "$apis", 10, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApisBindsRoute(t *testing.T) {
|
||||||
|
app, _ := tests.NewTestApp()
|
||||||
|
defer app.Cleanup()
|
||||||
|
|
||||||
|
vm := goja.New()
|
||||||
|
baseBinds(vm)
|
||||||
|
apisBinds(vm)
|
||||||
|
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
let handlerCalls = 0;
|
||||||
|
|
||||||
|
const route = new Route({
|
||||||
|
method: "test_method",
|
||||||
|
path: "test_path",
|
||||||
|
handler: () => {
|
||||||
|
handlerCalls++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
route.handler();
|
||||||
|
|
||||||
|
if (handlerCalls != 1) {
|
||||||
|
throw new Error('Expected handlerCalls 1, got ' + handlerCalls);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.method != "test_method") {
|
||||||
|
throw new Error('Expected method "test_method", got ' + route.method);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.path != "test_path") {
|
||||||
|
throw new Error('Expected method "test_path", got ' + route.path);
|
||||||
|
}
|
||||||
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
total, _ := v.Export().(int64)
|
func TestApisBindsApiError(t *testing.T) {
|
||||||
|
app, _ := tests.NewTestApp()
|
||||||
|
defer app.Cleanup()
|
||||||
|
|
||||||
if int(total) != count {
|
vm := goja.New()
|
||||||
t.Fatalf("Expected %d %s binds, got %d", count, namespace, total)
|
apisBinds(vm)
|
||||||
|
|
||||||
|
scenarios := []struct {
|
||||||
|
js string
|
||||||
|
expectCode int
|
||||||
|
expectMessage string
|
||||||
|
expectData string
|
||||||
|
}{
|
||||||
|
{"new ApiError()", 0, "", "null"},
|
||||||
|
{"new ApiError(100, 'test', {'test': 1})", 100, "Test.", `{"test":1}`},
|
||||||
|
{"new NotFoundError()", 404, "The requested resource wasn't found.", "null"},
|
||||||
|
{"new NotFoundError('test', {'test': 1})", 404, "Test.", `{"test":1}`},
|
||||||
|
{"new BadRequestError()", 400, "Something went wrong while processing your request.", "null"},
|
||||||
|
{"new BadRequestError('test', {'test': 1})", 400, "Test.", `{"test":1}`},
|
||||||
|
{"new ForbiddenError()", 403, "You are not allowed to perform this request.", "null"},
|
||||||
|
{"new ForbiddenError('test', {'test': 1})", 403, "Test.", `{"test":1}`},
|
||||||
|
{"new UnauthorizedError()", 401, "Missing or invalid authentication token.", "null"},
|
||||||
|
{"new UnauthorizedError('test', {'test': 1})", 401, "Test.", `{"test":1}`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range scenarios {
|
||||||
|
v, err := vm.RunString(s.js)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%s] %v", s.js, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
apiErr, ok := v.Export().(*apis.ApiError)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("[%s] Expected ApiError, got %v", s.js, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiErr.Code != s.expectCode {
|
||||||
|
t.Errorf("[%s] Expected Code %d, got %d", s.js, s.expectCode, apiErr.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(apiErr.Message, s.expectMessage) {
|
||||||
|
t.Errorf("[%s] Expected Message %q, got %q", s.js, s.expectMessage, apiErr.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataRaw, _ := json.Marshal(apiErr.RawData())
|
||||||
|
if string(dataRaw) != s.expectData {
|
||||||
|
t.Errorf("[%s] Expected Data %q, got %q", s.js, s.expectData, dataRaw)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue