mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-03-19 22:19:23 +02:00
1619 lines
36 KiB
Go
1619 lines
36 KiB
Go
package jsvm
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/dop251/goja"
|
|
validation "github.com/go-ozzo/ozzo-validation/v4"
|
|
"github.com/pocketbase/pocketbase/apis"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
"github.com/pocketbase/pocketbase/tests"
|
|
"github.com/pocketbase/pocketbase/tools/filesystem"
|
|
"github.com/pocketbase/pocketbase/tools/mailer"
|
|
"github.com/pocketbase/pocketbase/tools/router"
|
|
"github.com/spf13/cast"
|
|
)
|
|
|
|
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
|
|
// a new base binding is added.
|
|
func TestBaseBindsCount(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
testBindsCount(vm, "this", 33, t)
|
|
}
|
|
|
|
func TestBaseBindsSleep(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
vm.Set("reader", strings.NewReader("test"))
|
|
|
|
start := time.Now()
|
|
_, err := vm.RunString(`
|
|
sleep(100);
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
lasted := time.Since(start).Milliseconds()
|
|
if lasted < 100 || lasted > 150 {
|
|
t.Fatalf("Expected to sleep for ~100ms, got %d", lasted)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsReaderToString(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
vm.Set("reader", strings.NewReader("test"))
|
|
|
|
_, err := vm.RunString(`
|
|
let result = readerToString(reader)
|
|
|
|
if (result != "test") {
|
|
throw new Error('Expected "test", got ' + result);
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsToString(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
vm.Set("scenarios", []struct {
|
|
Name string
|
|
Value any
|
|
Expected string
|
|
}{
|
|
{"null", nil, ""},
|
|
{"string", "test", "test"},
|
|
{"number", -12.4, "-12.4"},
|
|
{"bool", true, "true"},
|
|
{"arr", []int{1, 2, 3}, `[1,2,3]`},
|
|
{"obj", map[string]any{"test": 123}, `{"test":123}`},
|
|
{"reader", strings.NewReader("test"), "test"},
|
|
{"struct", struct {
|
|
Name string
|
|
private string
|
|
}{Name: "123", private: "456"}, `{"Name":"123"}`},
|
|
})
|
|
|
|
_, err := vm.RunString(`
|
|
for (let s of scenarios) {
|
|
let result = toString(s.value)
|
|
|
|
if (result != s.expected) {
|
|
throw new Error('[' + s.name + '] Expected string ' + s.expected + ', got ' + result);
|
|
}
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsUnmarshal(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
vm.Set("data", &map[string]any{"a": 123})
|
|
|
|
_, err := vm.RunString(`
|
|
unmarshal({"b": 456}, data)
|
|
|
|
if (data.a != 123) {
|
|
throw new Error('Expected data.a 123, got ' + data.a);
|
|
}
|
|
|
|
if (data.b != 456) {
|
|
throw new Error('Expected data.b 456, got ' + data.b);
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsContext(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
_, err := vm.RunString(`
|
|
const base = new Context(null, "a", 123);
|
|
const sub = new Context(base, "b", 456);
|
|
|
|
const scenarios = [
|
|
{key: "a", expected: 123},
|
|
{key: "b", expected: 456},
|
|
];
|
|
|
|
for (let s of scenarios) {
|
|
if (sub.value(s.key) != s.expected) {
|
|
throw new("Expected " +s.key + " value " + s.expected + ", got " + sub.value(s.key));
|
|
}
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsCookie(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
_, err := vm.RunString(`
|
|
const cookie = new Cookie({
|
|
name: "example_name",
|
|
value: "example_value",
|
|
path: "/example_path",
|
|
domain: "example.com",
|
|
maxAge: 10,
|
|
secure: true,
|
|
httpOnly: true,
|
|
sameSite: 3,
|
|
});
|
|
|
|
const result = cookie.string();
|
|
|
|
const expected = "example_name=example_value; Path=/example_path; Domain=example.com; Max-Age=10; HttpOnly; Secure; SameSite=Strict";
|
|
|
|
if (expected != result) {
|
|
throw new("Expected \n" + expected + "\ngot\n" + result);
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsSubscriptionMessage(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
vm.Set("bytesToString", func(b []byte) string {
|
|
return string(b)
|
|
})
|
|
|
|
_, err := vm.RunString(`
|
|
const payload = {
|
|
name: "test",
|
|
data: '{"test":123}'
|
|
}
|
|
|
|
const result = new SubscriptionMessage(payload);
|
|
|
|
if (result.name != payload.name) {
|
|
throw new("Expected name " + payload.name + ", got " + result.name);
|
|
}
|
|
|
|
if (bytesToString(result.data) != payload.data) {
|
|
throw new("Expected data '" + payload.data + "', got '" + bytesToString(result.data) + "'");
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsRecord(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
collection, err := app.FindCachedCollectionByNameOrId("users")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
vm.Set("collection", collection)
|
|
|
|
// without record data
|
|
// ---
|
|
v1, err := vm.RunString(`new Record(collection)`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
m1, ok := v1.Export().(*core.Record)
|
|
if !ok {
|
|
t.Fatalf("Expected m1 to be models.Record, got \n%v", m1)
|
|
}
|
|
|
|
// with record data
|
|
// ---
|
|
v2, err := vm.RunString(`new Record(collection, { email: "test@example.com" })`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
m2, ok := v2.Export().(*core.Record)
|
|
if !ok {
|
|
t.Fatalf("Expected m2 to be core.Record, got \n%v", m2)
|
|
}
|
|
|
|
if m2.Collection().Name != "users" {
|
|
t.Fatalf("Expected record with collection %q, got \n%v", "users", m2.Collection())
|
|
}
|
|
|
|
if m2.Email() != "test@example.com" {
|
|
t.Fatalf("Expected record with email field set to %q, got \n%v", "test@example.com", m2)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsCollection(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
v, err := vm.RunString(`new Collection({ name: "test", createRule: "@request.auth.id != ''", fields: [{name: "title", "type": "text"}] })`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
m, ok := v.Export().(*core.Collection)
|
|
if !ok {
|
|
t.Fatalf("Expected core.Collection, got %v", m)
|
|
}
|
|
|
|
if m.Name != "test" {
|
|
t.Fatalf("Expected collection with name %q, got %q", "test", m.Name)
|
|
}
|
|
|
|
expectedRule := "@request.auth.id != ''"
|
|
if m.CreateRule == nil || *m.CreateRule != expectedRule {
|
|
t.Fatalf("Expected create rule %q, got %v", "@request.auth.id != ''", m.CreateRule)
|
|
}
|
|
|
|
if f := m.Fields.GetByName("title"); f == nil {
|
|
t.Fatalf("Expected fields to be set, got %v", m.Fields)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsFieldsList(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
v, err := vm.RunString(`new FieldsList([{name: "title", "type": "text"}])`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
m, ok := v.Export().(*core.FieldsList)
|
|
if !ok {
|
|
t.Fatalf("Expected core.FieldsList, got %v", m)
|
|
}
|
|
|
|
if f := m.GetByName("title"); f == nil {
|
|
t.Fatalf("Expected fields list to be loaded, got %v", m)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsField(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
v, err := vm.RunString(`new Field({name: "test", "type": "bool"})`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
f, ok := v.Export().(*core.BoolField)
|
|
if !ok {
|
|
t.Fatalf("Expected *core.BoolField, got %v", f)
|
|
}
|
|
|
|
if f.Name != "test" {
|
|
t.Fatalf("Expected field %q, got %v", "test", f)
|
|
}
|
|
}
|
|
|
|
func isType[T any](v any) bool {
|
|
_, ok := v.(T)
|
|
return ok
|
|
}
|
|
|
|
func TestBaseBindsNamedFields(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
scenarios := []struct {
|
|
js string
|
|
typeFunc func(v any) bool
|
|
}{
|
|
{
|
|
"new NumberField({name: 'test'})",
|
|
isType[*core.NumberField],
|
|
},
|
|
{
|
|
"new BoolField({name: 'test'})",
|
|
isType[*core.BoolField],
|
|
},
|
|
{
|
|
"new TextField({name: 'test'})",
|
|
isType[*core.TextField],
|
|
},
|
|
{
|
|
"new URLField({name: 'test'})",
|
|
isType[*core.URLField],
|
|
},
|
|
{
|
|
"new EmailField({name: 'test'})",
|
|
isType[*core.EmailField],
|
|
},
|
|
{
|
|
"new EditorField({name: 'test'})",
|
|
isType[*core.EditorField],
|
|
},
|
|
{
|
|
"new PasswordField({name: 'test'})",
|
|
isType[*core.PasswordField],
|
|
},
|
|
{
|
|
"new DateField({name: 'test'})",
|
|
isType[*core.DateField],
|
|
},
|
|
{
|
|
"new AutodateField({name: 'test'})",
|
|
isType[*core.AutodateField],
|
|
},
|
|
{
|
|
"new JSONField({name: 'test'})",
|
|
isType[*core.JSONField],
|
|
},
|
|
{
|
|
"new RelationField({name: 'test'})",
|
|
isType[*core.RelationField],
|
|
},
|
|
{
|
|
"new SelectField({name: 'test'})",
|
|
isType[*core.SelectField],
|
|
},
|
|
{
|
|
"new FileField({name: 'test'})",
|
|
isType[*core.FileField],
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.js, func(t *testing.T) {
|
|
v, err := vm.RunString(s.js)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
f, ok := v.Export().(core.Field)
|
|
if !ok {
|
|
t.Fatalf("Expected core.Field instance, got %T (%v)", f, f)
|
|
}
|
|
|
|
if !s.typeFunc(f) {
|
|
t.Fatalf("Unexpected field type %T (%v)", f, f)
|
|
}
|
|
|
|
if f.GetName() != "test" {
|
|
t.Fatalf("Expected field %q, got %v", "test", f)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsMailerMessage(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
v, err := vm.RunString(`new MailerMessage({
|
|
from: {name: "test_from", address: "test_from@example.com"},
|
|
to: [
|
|
{name: "test_to1", address: "test_to1@example.com"},
|
|
{name: "test_to2", address: "test_to2@example.com"},
|
|
],
|
|
bcc: [
|
|
{name: "test_bcc1", address: "test_bcc1@example.com"},
|
|
{name: "test_bcc2", address: "test_bcc2@example.com"},
|
|
],
|
|
cc: [
|
|
{name: "test_cc1", address: "test_cc1@example.com"},
|
|
{name: "test_cc2", address: "test_cc2@example.com"},
|
|
],
|
|
subject: "test_subject",
|
|
html: "test_html",
|
|
text: "test_text",
|
|
headers: {
|
|
header1: "a",
|
|
header2: "b",
|
|
}
|
|
})`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
m, ok := v.Export().(*mailer.Message)
|
|
if !ok {
|
|
t.Fatalf("Expected mailer.Message, got %v", m)
|
|
}
|
|
|
|
raw, err := json.Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expected := `{"from":{"Name":"test_from","Address":"test_from@example.com"},"to":[{"Name":"test_to1","Address":"test_to1@example.com"},{"Name":"test_to2","Address":"test_to2@example.com"}],"bcc":[{"Name":"test_bcc1","Address":"test_bcc1@example.com"},{"Name":"test_bcc2","Address":"test_bcc2@example.com"}],"cc":[{"Name":"test_cc1","Address":"test_cc1@example.com"},{"Name":"test_cc2","Address":"test_cc2@example.com"}],"subject":"test_subject","html":"test_html","text":"test_text","headers":{"header1":"a","header2":"b"},"attachments":null,"inlineAttachments":null}`
|
|
|
|
if string(raw) != expected {
|
|
t.Fatalf("Expected \n%s, \ngot \n%s", expected, raw)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsCommand(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
_, err := vm.RunString(`
|
|
let runCalls = 0;
|
|
|
|
let cmd = new Command({
|
|
use: "test",
|
|
run: (c, args) => {
|
|
runCalls++;
|
|
}
|
|
});
|
|
|
|
cmd.run(null, []);
|
|
|
|
if (cmd.use != "test") {
|
|
throw new Error('Expected cmd.use "test", got: ' + cmd.use);
|
|
}
|
|
|
|
if (runCalls != 1) {
|
|
throw new Error('Expected runCalls 1, got: ' + runCalls);
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsRequestInfo(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
_, err := vm.RunString(`
|
|
const info = new RequestInfo({
|
|
body: {"name": "test2"}
|
|
});
|
|
|
|
if (info.body?.name != "test2") {
|
|
throw new Error('Expected info.body.name to be test2, got: ' + info.body?.name);
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsMiddleware(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
_, err := vm.RunString(`
|
|
const m = new Middleware(
|
|
(e) => {},
|
|
10,
|
|
"test"
|
|
);
|
|
|
|
if (!m) {
|
|
throw new Error('Expected non-empty Middleware instance');
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsTimezone(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
_, err := vm.RunString(`
|
|
const v0 = (new Timezone()).string();
|
|
if (v0 != "UTC") {
|
|
throw new Error("(v0) Expected UTC got " + v0)
|
|
}
|
|
|
|
const v1 = (new Timezone("invalid")).string();
|
|
if (v1 != "UTC") {
|
|
throw new Error("(v1) Expected UTC got " + v1)
|
|
}
|
|
|
|
const v2 = (new Timezone("EET")).string();
|
|
if (v2 != "EET") {
|
|
throw new Error("(v2) Expected EET got " + v2)
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsDateTime(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
_, err := vm.RunString(`
|
|
const v0 = new DateTime();
|
|
if (v0.isZero()) {
|
|
throw new Error('Expected to fallback to now, got zero value');
|
|
}
|
|
|
|
const v1 = new DateTime('2023-01-01 00:00:00.000Z');
|
|
const expected = "2023-01-01 00:00:00.000Z"
|
|
if (v1.string() != expected) {
|
|
throw new Error('Expected ' + expected + ', got ', v1.string());
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBaseBindsValidationError(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
|
|
scenarios := []struct {
|
|
js string
|
|
expectCode string
|
|
expectMessage string
|
|
}{
|
|
{
|
|
`new ValidationError()`,
|
|
"",
|
|
"",
|
|
},
|
|
{
|
|
`new ValidationError("test_code")`,
|
|
"test_code",
|
|
"",
|
|
},
|
|
{
|
|
`new ValidationError("test_code", "test_message")`,
|
|
"test_code",
|
|
"test_message",
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
v, err := vm.RunString(s.js)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
m, ok := v.Export().(validation.Error)
|
|
if !ok {
|
|
t.Fatalf("[%s] Expected validation.Error, got %v", s.js, m)
|
|
}
|
|
|
|
if m.Code() != s.expectCode {
|
|
t.Fatalf("[%s] Expected code %q, got %q", s.js, s.expectCode, m.Code())
|
|
}
|
|
|
|
if m.Message() != s.expectMessage {
|
|
t.Fatalf("[%s] Expected message %q, got %q", s.js, s.expectMessage, m.Message())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDbxBinds(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
vm := goja.New()
|
|
vm.Set("db", app.DB())
|
|
baseBinds(vm)
|
|
dbxBinds(vm)
|
|
|
|
testBindsCount(vm, "$dbx", 15, t)
|
|
|
|
sceneraios := []struct {
|
|
js string
|
|
expected string
|
|
}{
|
|
{
|
|
`$dbx.exp("a = 1").build(db, {})`,
|
|
"a = 1",
|
|
},
|
|
{
|
|
`$dbx.hashExp({
|
|
"a": 1,
|
|
b: null,
|
|
c: [1, 2, 3],
|
|
}).build(db, {})`,
|
|
"`a`={:p0} AND `b` IS NULL AND `c` IN ({:p1}, {:p2}, {:p3})",
|
|
},
|
|
{
|
|
`$dbx.not($dbx.exp("a = 1")).build(db, {})`,
|
|
"NOT (a = 1)",
|
|
},
|
|
{
|
|
`$dbx.and($dbx.exp("a = 1"), $dbx.exp("b = 2")).build(db, {})`,
|
|
"(a = 1) AND (b = 2)",
|
|
},
|
|
{
|
|
`$dbx.or($dbx.exp("a = 1"), $dbx.exp("b = 2")).build(db, {})`,
|
|
"(a = 1) OR (b = 2)",
|
|
},
|
|
{
|
|
`$dbx.in("a", 1, 2, 3).build(db, {})`,
|
|
"`a` IN ({:p0}, {:p1}, {:p2})",
|
|
},
|
|
{
|
|
`$dbx.notIn("a", 1, 2, 3).build(db, {})`,
|
|
"`a` NOT IN ({:p0}, {:p1}, {:p2})",
|
|
},
|
|
{
|
|
`$dbx.like("a", "test1", "test2").match(true, false).build(db, {})`,
|
|
"`a` LIKE {:p0} AND `a` LIKE {:p1}",
|
|
},
|
|
{
|
|
`$dbx.orLike("a", "test1", "test2").match(false, true).build(db, {})`,
|
|
"`a` LIKE {:p0} OR `a` LIKE {:p1}",
|
|
},
|
|
{
|
|
`$dbx.notLike("a", "test1", "test2").match(true, false).build(db, {})`,
|
|
"`a` NOT LIKE {:p0} AND `a` NOT LIKE {:p1}",
|
|
},
|
|
{
|
|
`$dbx.orNotLike("a", "test1", "test2").match(false, false).build(db, {})`,
|
|
"`a` NOT LIKE {:p0} OR `a` NOT LIKE {:p1}",
|
|
},
|
|
{
|
|
`$dbx.exists($dbx.exp("a = 1")).build(db, {})`,
|
|
"EXISTS (a = 1)",
|
|
},
|
|
{
|
|
`$dbx.notExists($dbx.exp("a = 1")).build(db, {})`,
|
|
"NOT EXISTS (a = 1)",
|
|
},
|
|
{
|
|
`$dbx.between("a", 1, 2).build(db, {})`,
|
|
"`a` BETWEEN {:p0} AND {:p1}",
|
|
},
|
|
{
|
|
`$dbx.notBetween("a", 1, 2).build(db, {})`,
|
|
"`a` NOT BETWEEN {:p0} AND {:p1}",
|
|
},
|
|
}
|
|
|
|
for _, s := range sceneraios {
|
|
result, err := vm.RunString(s.js)
|
|
if err != nil {
|
|
t.Fatalf("[%s] Failed to execute js script, got %v", s.js, err)
|
|
}
|
|
|
|
v, _ := result.Export().(string)
|
|
|
|
if v != s.expected {
|
|
t.Fatalf("[%s] Expected \n%s, \ngot \n%s", s.js, s.expected, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMailsBindsCount(t *testing.T) {
|
|
vm := goja.New()
|
|
mailsBinds(vm)
|
|
|
|
testBindsCount(vm, "$mails", 4, t)
|
|
}
|
|
|
|
func TestMailsBinds(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
record, err := app.FindAuthRecordByEmail("users", "test@example.com")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
mailsBinds(vm)
|
|
vm.Set("$app", app)
|
|
vm.Set("record", record)
|
|
|
|
_, vmErr := vm.RunString(`
|
|
$mails.sendRecordPasswordReset($app, record);
|
|
if (!$app.testMailer.lastMessage().html.includes("/_/#/auth/confirm-password-reset/")) {
|
|
throw new Error("Expected record password reset email, got:" + JSON.stringify($app.testMailer.lastMessage()))
|
|
}
|
|
|
|
$mails.sendRecordVerification($app, record);
|
|
if (!$app.testMailer.lastMessage().html.includes("/_/#/auth/confirm-verification/")) {
|
|
throw new Error("Expected record verification email, got:" + JSON.stringify($app.testMailer.lastMessage()))
|
|
}
|
|
|
|
$mails.sendRecordChangeEmail($app, record, "new@example.com");
|
|
if (!$app.testMailer.lastMessage().html.includes("/_/#/auth/confirm-email-change/")) {
|
|
throw new Error("Expected record email change email, got:" + JSON.stringify($app.testMailer.lastMessage()))
|
|
}
|
|
|
|
$mails.sendRecordOTP($app, record, "test_otp_id", "test_otp_pass");
|
|
if (!$app.testMailer.lastMessage().html.includes("test_otp_pass")) {
|
|
throw new Error("Expected record OTP email, got:" + JSON.stringify($app.testMailer.lastMessage()))
|
|
}
|
|
`)
|
|
if vmErr != nil {
|
|
t.Fatal(vmErr)
|
|
}
|
|
}
|
|
|
|
func TestSecurityBindsCount(t *testing.T) {
|
|
vm := goja.New()
|
|
securityBinds(vm)
|
|
|
|
testBindsCount(vm, "$security", 16, t)
|
|
}
|
|
|
|
func TestSecurityCryptoBinds(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
securityBinds(vm)
|
|
|
|
sceneraios := []struct {
|
|
js string
|
|
expected string
|
|
}{
|
|
{`$security.md5("123")`, "202cb962ac59075b964b07152d234b70"},
|
|
{`$security.sha256("123")`, "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"},
|
|
{`$security.sha512("123")`, "3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856bca5c44c2"},
|
|
{`$security.hs256("hello", "test")`, "f151ea24bda91a18e89b8bb5793ef324b2a02133cce15a28a719acbd2e58a986"},
|
|
{`$security.hs512("hello", "test")`, "44f280e11103e295c26cd61dd1cdd8178b531b860466867c13b1c37a26b6389f8af110efbe0bb0717b9d9c87f6fe1c97b3b1690936578890e5669abf279fe7fd"},
|
|
{`$security.equal("abc", "abc")`, "true"},
|
|
{`$security.equal("abc", "abcd")`, "false"},
|
|
}
|
|
|
|
for _, s := range sceneraios {
|
|
t.Run(s.js, func(t *testing.T) {
|
|
result, err := vm.RunString(s.js)
|
|
if err != nil {
|
|
t.Fatalf("Failed to execute js script, got %v", err)
|
|
}
|
|
|
|
v := cast.ToString(result.Export())
|
|
|
|
if v != s.expected {
|
|
t.Fatalf("Expected %v \ngot \n%v", s.expected, v)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSecurityRandomStringBinds(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
securityBinds(vm)
|
|
|
|
sceneraios := []struct {
|
|
js string
|
|
length int
|
|
}{
|
|
{`$security.randomString(6)`, 6},
|
|
{`$security.randomStringWithAlphabet(7, "abc")`, 7},
|
|
{`$security.pseudorandomString(8)`, 8},
|
|
{`$security.pseudorandomStringWithAlphabet(9, "abc")`, 9},
|
|
{`$security.randomStringByRegex("abc")`, 3},
|
|
}
|
|
|
|
for _, s := range sceneraios {
|
|
t.Run(s.js, func(t *testing.T) {
|
|
result, err := vm.RunString(s.js)
|
|
if err != nil {
|
|
t.Fatalf("Failed to execute js script, got %v", err)
|
|
}
|
|
|
|
v, _ := result.Export().(string)
|
|
|
|
if len(v) != s.length {
|
|
t.Fatalf("Expected %d length string, \ngot \n%v", s.length, v)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSecurityJWTBinds(t *testing.T) {
|
|
sceneraios := []struct {
|
|
name string
|
|
js string
|
|
}{
|
|
{
|
|
"$security.parseUnverifiedJWT",
|
|
`
|
|
const result = $security.parseUnverifiedJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.aXzC7q7z1lX_hxk5P0R368xEU7H1xRwnBQQcLAmG0EY")
|
|
if (result.name != "John Doe") {
|
|
throw new Error("Expected result.name 'John Doe', got " + result.name)
|
|
}
|
|
if (result.sub != "1234567890") {
|
|
throw new Error("Expected result.sub '1234567890', got " + result.sub)
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
"$security.parseJWT",
|
|
`
|
|
const result = $security.parseJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.aXzC7q7z1lX_hxk5P0R368xEU7H1xRwnBQQcLAmG0EY", "test")
|
|
if (result.name != "John Doe") {
|
|
throw new Error("Expected result.name 'John Doe', got " + result.name)
|
|
}
|
|
if (result.sub != "1234567890") {
|
|
throw new Error("Expected result.sub '1234567890', got " + result.sub)
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
"$security.createJWT",
|
|
`
|
|
// overwrite the exp claim for static token
|
|
const result = $security.createJWT({"exp": 123}, "test", 0)
|
|
|
|
const expected = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEyM30.7gbv7w672gApdBRASI6OniCtKwkKjhieSxsr6vxSrtw";
|
|
if (result != expected) {
|
|
throw new Error("Expected token \n" + expected + ", got \n" + result)
|
|
}
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, s := range sceneraios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
securityBinds(vm)
|
|
|
|
_, err := vm.RunString(s.js)
|
|
if err != nil {
|
|
t.Fatalf("Failed to execute js script, got %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSecurityEncryptAndDecryptBinds(t *testing.T) {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
securityBinds(vm)
|
|
|
|
_, err := vm.RunString(`
|
|
const key = "abcdabcdabcdabcdabcdabcdabcdabcd"
|
|
|
|
const encrypted = $security.encrypt("123", key)
|
|
|
|
const decrypted = $security.decrypt(encrypted, key)
|
|
|
|
if (decrypted != "123") {
|
|
throw new Error("Expected decrypted '123', got " + decrypted)
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestFilesystemBinds(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/error" {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
|
|
fmt.Fprintf(w, "test")
|
|
}))
|
|
defer srv.Close()
|
|
|
|
vm := goja.New()
|
|
vm.Set("mh", &multipart.FileHeader{Filename: "test"})
|
|
vm.Set("testFile", filepath.Join(app.DataDir(), "data.db"))
|
|
vm.Set("baseURL", srv.URL)
|
|
baseBinds(vm)
|
|
filesystemBinds(vm)
|
|
|
|
testBindsCount(vm, "$filesystem", 4, t)
|
|
|
|
// fileFromPath
|
|
{
|
|
v, err := vm.RunString(`$filesystem.fileFromPath(testFile)`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
file, _ := v.Export().(*filesystem.File)
|
|
|
|
if file == nil || file.OriginalName != "data.db" {
|
|
t.Fatalf("[fileFromPath] Expected file with name %q, got %v", file.OriginalName, file)
|
|
}
|
|
}
|
|
|
|
// fileFromBytes
|
|
{
|
|
v, err := vm.RunString(`$filesystem.fileFromBytes([1, 2, 3], "test")`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
file, _ := v.Export().(*filesystem.File)
|
|
|
|
if file == nil || file.OriginalName != "test" {
|
|
t.Fatalf("[fileFromBytes] Expected file with name %q, got %v", file.OriginalName, file)
|
|
}
|
|
}
|
|
|
|
// fileFromMultipart
|
|
{
|
|
v, err := vm.RunString(`$filesystem.fileFromMultipart(mh)`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
file, _ := v.Export().(*filesystem.File)
|
|
|
|
if file == nil || file.OriginalName != "test" {
|
|
t.Fatalf("[fileFromMultipart] Expected file with name %q, got %v", file.OriginalName, file)
|
|
}
|
|
}
|
|
|
|
// fileFromURL (success)
|
|
{
|
|
v, err := vm.RunString(`$filesystem.fileFromURL(baseURL + "/test")`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
file, _ := v.Export().(*filesystem.File)
|
|
|
|
if file == nil || file.OriginalName != "test" {
|
|
t.Fatalf("[fileFromURL] Expected file with name %q, got %v", file.OriginalName, file)
|
|
}
|
|
}
|
|
|
|
// fileFromURL (failure)
|
|
{
|
|
_, err := vm.RunString(`$filesystem.fileFromURL(baseURL + "/error")`)
|
|
if err == nil {
|
|
t.Fatal("Expected url fetch error")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormsBinds(t *testing.T) {
|
|
vm := goja.New()
|
|
formsBinds(vm)
|
|
|
|
testBindsCount(vm, "this", 4, t)
|
|
}
|
|
|
|
func TestApisBindsCount(t *testing.T) {
|
|
vm := goja.New()
|
|
apisBinds(vm)
|
|
|
|
testBindsCount(vm, "this", 8, t)
|
|
testBindsCount(vm, "$apis", 11, t)
|
|
}
|
|
|
|
func TestApisBindsApiError(t *testing.T) {
|
|
vm := goja.New()
|
|
apisBinds(vm)
|
|
|
|
scenarios := []struct {
|
|
js string
|
|
expectStatus 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.", "null"},
|
|
{"new UnauthorizedError('test', {'test': 1})", 401, "Test.", `{"test":1}`},
|
|
{"new TooManyRequestsError()", 429, "Too Many Requests.", "null"},
|
|
{"new TooManyRequestsError('test', {'test': 1})", 429, "Test.", `{"test":1}`},
|
|
{"new InternalServerError()", 500, "Something went wrong while processing your request.", "null"},
|
|
{"new InternalServerError('test', {'test': 1})", 500, "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().(*router.ApiError)
|
|
if !ok {
|
|
t.Errorf("[%s] Expected ApiError, got %v", s.js, v)
|
|
continue
|
|
}
|
|
|
|
if apiErr.Status != s.expectStatus {
|
|
t.Errorf("[%s] Expected Status %d, got %d", s.js, s.expectStatus, apiErr.Status)
|
|
}
|
|
|
|
if 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadingDynamicModel(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
dbxBinds(vm)
|
|
vm.Set("$app", app)
|
|
|
|
_, err := vm.RunString(`
|
|
let result = new DynamicModel({
|
|
text: "",
|
|
bool: false,
|
|
number: 0,
|
|
select_many: [],
|
|
json: [],
|
|
// custom map-like field
|
|
obj: {},
|
|
})
|
|
|
|
$app.db()
|
|
.select("text", "bool", "number", "select_many", "json", "('{\"test\": 1}') as obj")
|
|
.from("demo1")
|
|
.where($dbx.hashExp({"id": "84nmscqy84lsi1t"}))
|
|
.limit(1)
|
|
.one(result)
|
|
|
|
if (result.text != "test") {
|
|
throw new Error('Expected text "test", got ' + result.text);
|
|
}
|
|
|
|
if (result.bool != true) {
|
|
throw new Error('Expected bool true, got ' + result.bool);
|
|
}
|
|
|
|
if (result.number != 123456) {
|
|
throw new Error('Expected number 123456, got ' + result.number);
|
|
}
|
|
|
|
if (result.select_many.length != 2 || result.select_many[0] != "optionB" || result.select_many[1] != "optionC") {
|
|
throw new Error('Expected select_many ["optionB", "optionC"], got ' + result.select_many);
|
|
}
|
|
|
|
if (result.json.length != 3 || result.json[0] != 1 || result.json[1] != 2 || result.json[2] != 3) {
|
|
throw new Error('Expected json [1, 2, 3], got ' + result.json);
|
|
}
|
|
|
|
if (result.obj.get("test") != 1) {
|
|
throw new Error('Expected obj.get("test") 1, got ' + JSON.stringify(result.obj));
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestLoadingArrayOf(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
dbxBinds(vm)
|
|
vm.Set("$app", app)
|
|
|
|
_, err := vm.RunString(`
|
|
let result = arrayOf(new DynamicModel({
|
|
id: "",
|
|
text: "",
|
|
}))
|
|
|
|
$app.db()
|
|
.select("id", "text")
|
|
.from("demo1")
|
|
.where($dbx.exp("id='84nmscqy84lsi1t' OR id='al1h9ijdeojtsjy'"))
|
|
.limit(2)
|
|
.orderBy("text ASC")
|
|
.all(result)
|
|
|
|
if (result.length != 2) {
|
|
throw new Error('Expected 2 list items, got ' + result.length);
|
|
}
|
|
|
|
if (result[0].id != "84nmscqy84lsi1t") {
|
|
throw new Error('Expected 0.id "84nmscqy84lsi1t", got ' + result[0].id);
|
|
}
|
|
if (result[0].text != "test") {
|
|
throw new Error('Expected 0.text "test", got ' + result[0].text);
|
|
}
|
|
|
|
if (result[1].id != "al1h9ijdeojtsjy") {
|
|
throw new Error('Expected 1.id "al1h9ijdeojtsjy", got ' + result[1].id);
|
|
}
|
|
if (result[1].text != "test2") {
|
|
throw new Error('Expected 1.text "test2", got ' + result[1].text);
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestHttpClientBindsCount(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
vm := goja.New()
|
|
httpClientBinds(vm)
|
|
|
|
testBindsCount(vm, "this", 2, t) // + FormData
|
|
testBindsCount(vm, "$http", 1, t)
|
|
}
|
|
|
|
func TestHttpClientBindsSend(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// start a test server
|
|
server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
if req.URL.Query().Get("testError") != "" {
|
|
res.WriteHeader(400)
|
|
return
|
|
}
|
|
|
|
timeoutStr := req.URL.Query().Get("testTimeout")
|
|
timeout, _ := strconv.Atoi(timeoutStr)
|
|
if timeout > 0 {
|
|
time.Sleep(time.Duration(timeout) * time.Second)
|
|
}
|
|
|
|
bodyRaw, _ := io.ReadAll(req.Body)
|
|
defer req.Body.Close()
|
|
|
|
// normalize headers
|
|
headers := make(map[string]string, len(req.Header))
|
|
for k, v := range req.Header {
|
|
if len(v) > 0 {
|
|
headers[strings.ToLower(strings.ReplaceAll(k, "-", "_"))] = v[0]
|
|
}
|
|
}
|
|
|
|
info := map[string]any{
|
|
"method": req.Method,
|
|
"headers": headers,
|
|
"body": string(bodyRaw),
|
|
}
|
|
|
|
// add custom headers and cookies
|
|
res.Header().Add("X-Custom", "custom_header")
|
|
res.Header().Add("Set-Cookie", "sessionId=123456")
|
|
|
|
infoRaw, _ := json.Marshal(info)
|
|
|
|
// write back the submitted request
|
|
res.Write(infoRaw)
|
|
}))
|
|
defer server.Close()
|
|
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
httpClientBinds(vm)
|
|
vm.Set("testURL", server.URL)
|
|
|
|
_, err := vm.RunString(`
|
|
function getNestedVal(data, path) {
|
|
let result = data || {};
|
|
let parts = path.split(".");
|
|
|
|
for (const part of parts) {
|
|
if (
|
|
result == null ||
|
|
typeof result !== "object" ||
|
|
typeof result[part] === "undefined"
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
result = result[part];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
let testTimeout;
|
|
try {
|
|
$http.send({
|
|
url: testURL + "?testTimeout=3",
|
|
timeout: 1
|
|
})
|
|
} catch (err) {
|
|
testTimeout = err
|
|
}
|
|
if (!testTimeout) {
|
|
throw new Error("Expected timeout error")
|
|
}
|
|
|
|
// error response check
|
|
const test0 = $http.send({
|
|
url: testURL + "?testError=1",
|
|
})
|
|
|
|
// basic fields check
|
|
const test1 = $http.send({
|
|
method: "post",
|
|
url: testURL,
|
|
headers: {"header1": "123", "header2": "456"},
|
|
body: '789',
|
|
})
|
|
|
|
// with custom content-type header
|
|
const test2 = $http.send({
|
|
url: testURL,
|
|
headers: {"content-type": "text/plain"},
|
|
})
|
|
|
|
// with FormData
|
|
const formData = new FormData()
|
|
formData.append("title", "123")
|
|
const test3 = $http.send({
|
|
url: testURL,
|
|
body: formData,
|
|
headers: {"content-type": "text/plain"}, // should be ignored
|
|
})
|
|
|
|
const scenarios = [
|
|
[test0, {
|
|
"statusCode": "400",
|
|
}],
|
|
[test1, {
|
|
"statusCode": "200",
|
|
"headers.X-Custom.0": "custom_header",
|
|
"cookies.sessionId.value": "123456",
|
|
"json.method": "POST",
|
|
"json.headers.header1": "123",
|
|
"json.headers.header2": "456",
|
|
"json.body": "789",
|
|
}],
|
|
[test2, {
|
|
"statusCode": "200",
|
|
"headers.X-Custom.0": "custom_header",
|
|
"cookies.sessionId.value": "123456",
|
|
"json.method": "GET",
|
|
"json.headers.content_type": "text/plain",
|
|
}],
|
|
[test3, {
|
|
"statusCode": "200",
|
|
"headers.X-Custom.0": "custom_header",
|
|
"cookies.sessionId.value": "123456",
|
|
"json.method": "GET",
|
|
"json.body": [
|
|
"\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n123\r\n--",
|
|
],
|
|
"json.headers.content_type": [
|
|
"multipart/form-data; boundary="
|
|
],
|
|
}],
|
|
]
|
|
|
|
for (let scenario of scenarios) {
|
|
const result = scenario[0];
|
|
const expectations = scenario[1];
|
|
|
|
for (let key in expectations) {
|
|
const value = getNestedVal(result, key);
|
|
const expectation = expectations[key]
|
|
if (Array.isArray(expectation)) {
|
|
// check for partial match(es)
|
|
for (let exp of expectation) {
|
|
if (!value.includes(exp)) {
|
|
throw new Error('Expected ' + key + ' to contain ' + exp + ', got: ' + result.raw);
|
|
}
|
|
}
|
|
} else {
|
|
// check for direct match
|
|
if (value != expectation) {
|
|
throw new Error('Expected ' + key + ' ' + expectation + ', got: ' + result.raw);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestCronBindsCount(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
vm := goja.New()
|
|
|
|
pool := newPool(1, func() *goja.Runtime { return goja.New() })
|
|
|
|
cronBinds(app, vm, pool)
|
|
|
|
testBindsCount(vm, "this", 2, t)
|
|
|
|
pool.run(func(poolVM *goja.Runtime) error {
|
|
testBindsCount(poolVM, "this", 1, t)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestHooksBindsCount(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
vm := goja.New()
|
|
hooksBinds(app, vm, nil)
|
|
|
|
testBindsCount(vm, "this", 82, t)
|
|
}
|
|
|
|
func TestHooksBinds(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
result := &struct {
|
|
Called int
|
|
}{}
|
|
|
|
vmFactory := func() *goja.Runtime {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
vm.Set("$app", app)
|
|
vm.Set("result", result)
|
|
return vm
|
|
}
|
|
|
|
pool := newPool(1, vmFactory)
|
|
|
|
vm := vmFactory()
|
|
hooksBinds(app, vm, pool)
|
|
|
|
_, err := vm.RunString(`
|
|
onModelUpdate((e) => {
|
|
result.called++;
|
|
e.next()
|
|
}, "demo1")
|
|
|
|
onModelUpdate((e) => {
|
|
throw new Error("example");
|
|
}, "demo1")
|
|
|
|
onModelUpdate((e) => {
|
|
result.called++;
|
|
e.next();
|
|
}, "demo2")
|
|
|
|
onModelUpdate((e) => {
|
|
result.called++;
|
|
e.next()
|
|
}, "demo2")
|
|
|
|
onModelUpdate((e) => {
|
|
// stop propagation
|
|
}, "demo2")
|
|
|
|
onModelUpdate((e) => {
|
|
result.called++;
|
|
e.next();
|
|
}, "demo2")
|
|
|
|
onBootstrap((e) => {
|
|
e.next()
|
|
|
|
// check hooks propagation and tags filtering
|
|
const recordA = $app.findFirstRecordByFilter("demo2", "1=1")
|
|
recordA.set("title", "update")
|
|
$app.save(recordA)
|
|
if (result.called != 2) {
|
|
throw new Error("Expected result.called to be 2, got " + result.called)
|
|
}
|
|
|
|
// reset
|
|
result.called = 0;
|
|
|
|
// check error handling
|
|
let hasErr = false
|
|
try {
|
|
const recordB = $app.findFirstRecordByFilter("demo1", "1=1")
|
|
recordB.set("text", "update")
|
|
$app.save(recordB)
|
|
} catch (err) {
|
|
hasErr = true
|
|
}
|
|
if (!hasErr) {
|
|
throw new Error("Expected an error to be thrown")
|
|
}
|
|
if (result.called != 1) {
|
|
throw new Error("Expected result.called to be 1, got " + result.called)
|
|
}
|
|
})
|
|
|
|
$app.bootstrap();
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestHooksExceptionUnwrapping(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
goErr := errors.New("test")
|
|
|
|
vmFactory := func() *goja.Runtime {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
vm.Set("$app", app)
|
|
vm.Set("goErr", goErr)
|
|
return vm
|
|
}
|
|
|
|
pool := newPool(1, vmFactory)
|
|
|
|
vm := vmFactory()
|
|
hooksBinds(app, vm, pool)
|
|
|
|
_, err := vm.RunString(`
|
|
onModelUpdate((e) => {
|
|
throw goErr
|
|
}, "demo1")
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
record, err := app.FindFirstRecordByFilter("demo1", "1=1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
record.Set("text", "update")
|
|
|
|
err = app.Save(record)
|
|
if !errors.Is(err, goErr) {
|
|
t.Fatalf("Expected goError, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRouterBindsCount(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
vm := goja.New()
|
|
routerBinds(app, vm, nil)
|
|
|
|
testBindsCount(vm, "this", 2, t)
|
|
}
|
|
|
|
func TestRouterBinds(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
result := &struct {
|
|
AddCount int
|
|
WithCount int
|
|
}{}
|
|
|
|
vmFactory := func() *goja.Runtime {
|
|
vm := goja.New()
|
|
baseBinds(vm)
|
|
vm.Set("$app", app)
|
|
vm.Set("result", result)
|
|
return vm
|
|
}
|
|
|
|
pool := newPool(1, vmFactory)
|
|
|
|
vm := vmFactory()
|
|
routerBinds(app, vm, pool)
|
|
|
|
_, err := vm.RunString(`
|
|
routerAdd("GET", "/test", (e) => {
|
|
result.addCount++;
|
|
}, (e) => {
|
|
result.addCount++;
|
|
return e.next();
|
|
})
|
|
|
|
routerUse((e) => {
|
|
result.withCount++;
|
|
|
|
return e.next();
|
|
})
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
pbRouter, err := apis.NewRouter(app)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
serveEvent := new(core.ServeEvent)
|
|
serveEvent.App = app
|
|
serveEvent.Router = pbRouter
|
|
if err = app.OnServe().Trigger(serveEvent); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/test", nil)
|
|
|
|
mux, err := serveEvent.Router.BuildMux()
|
|
if err != nil {
|
|
t.Fatalf("Failed to build router mux: %v", err)
|
|
}
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if result.AddCount != 2 {
|
|
t.Fatalf("Expected AddCount %d, got %d", 2, result.AddCount)
|
|
}
|
|
|
|
if result.WithCount != 1 {
|
|
t.Fatalf("Expected WithCount %d, got %d", 1, result.WithCount)
|
|
}
|
|
}
|
|
|
|
func TestFilepathBindsCount(t *testing.T) {
|
|
vm := goja.New()
|
|
filepathBinds(vm)
|
|
|
|
testBindsCount(vm, "$filepath", 15, t)
|
|
}
|
|
|
|
func TestOsBindsCount(t *testing.T) {
|
|
vm := goja.New()
|
|
osBinds(vm)
|
|
|
|
testBindsCount(vm, "$os", 17, t)
|
|
}
|