mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-02-16 01:19:46 +02:00
3521 lines
116 KiB
Go
3521 lines
116 KiB
Go
package apis_test
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pocketbase/pocketbase/apis"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
"github.com/pocketbase/pocketbase/tests"
|
|
"github.com/pocketbase/pocketbase/tools/router"
|
|
"github.com/pocketbase/pocketbase/tools/types"
|
|
)
|
|
|
|
func TestRecordCrudList(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "missing collection",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/missing/records",
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "unauthenticated trying to access nil rule collection (aka. need superuser auth)",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo1/records",
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authenticated record trying to access nil rule collection (aka. need superuser auth)",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo1/records",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "public collection but with superuser only filter param (aka. @collection, @request, etc.)",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo2/records?filter=%40collection.demo2.title='test1'",
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "public collection but with superuser only sort param (aka. @collection, @request, etc.)",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo2/records?sort=@request.auth.title",
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "public collection but with ENCODED superuser only filter/sort (aka. @collection)",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo2/records?filter=%40collection.demo2.title%3D%27test1%27",
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "public collection",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo2/records",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":3`,
|
|
`"items":[{`,
|
|
`"id":"0yxhwia2amd8gec"`,
|
|
`"id":"achvryl401bhse3"`,
|
|
`"id":"llvuca81nly1qls"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 3,
|
|
},
|
|
},
|
|
{
|
|
Name: "public collection (using the collection id)",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/sz5l5z67tg7gku0/records",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":3`,
|
|
`"items":[{`,
|
|
`"id":"0yxhwia2amd8gec"`,
|
|
`"id":"achvryl401bhse3"`,
|
|
`"id":"llvuca81nly1qls"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 3,
|
|
},
|
|
},
|
|
{
|
|
Name: "authorized as superuser trying to access nil rule collection (aka. need superuser auth)",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo1/records",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":3`,
|
|
`"items":[{`,
|
|
`"id":"al1h9ijdeojtsjy"`,
|
|
`"id":"84nmscqy84lsi1t"`,
|
|
`"id":"imy661ixudk5izi"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 3,
|
|
},
|
|
},
|
|
{
|
|
Name: "valid query params",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo1/records?filter=text~'test'&sort=-bool",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalItems":2`,
|
|
`"items":[{`,
|
|
`"id":"al1h9ijdeojtsjy"`,
|
|
`"id":"84nmscqy84lsi1t"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 2,
|
|
},
|
|
},
|
|
{
|
|
Name: "invalid filter",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo1/records?filter=invalid~'test'",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "expand relations",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo1/records?expand=rel_one,rel_many.rel,missing&perPage=2&sort=created",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":2`,
|
|
`"totalPages":2`,
|
|
`"totalItems":3`,
|
|
`"items":[{`,
|
|
`"collectionName":"demo1"`,
|
|
`"id":"84nmscqy84lsi1t"`,
|
|
`"id":"al1h9ijdeojtsjy"`,
|
|
`"expand":{`,
|
|
`"rel_one":""`,
|
|
`"rel_one":{"`,
|
|
`"rel_many":[{`,
|
|
`"rel":{`,
|
|
`"rel":""`,
|
|
`"json":[1,2,3]`,
|
|
`"select_many":["optionB","optionC"]`,
|
|
`"select_many":["optionB"]`,
|
|
// subrel items
|
|
`"id":"0yxhwia2amd8gec"`,
|
|
`"id":"llvuca81nly1qls"`,
|
|
// email visibility should be ignored for superusers even in expanded rels
|
|
`"email":"test@example.com"`,
|
|
`"email":"test2@example.com"`,
|
|
`"email":"test3@example.com"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 8,
|
|
},
|
|
},
|
|
{
|
|
Name: "authenticated record model that DOESN'T match the collection list rule",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo3/records",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalItems":0`,
|
|
`"items":[]`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "authenticated record that matches the collection list rule",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo3/records",
|
|
Headers: map[string]string{
|
|
// clients, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImdrMzkwcWVnczR5NDd3biIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoidjg1MXE0cjc5MHJoa25sIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.0ONnm_BsvPRZyDNT31GN1CKUB6uQRxvVvQ-Wc9AZfG0",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":4`,
|
|
`"items":[{`,
|
|
`"id":"1tmknxy2868d869"`,
|
|
`"id":"lcl9d87w22ml6jy"`,
|
|
`"id":"7nwo8tuiatetxdm"`,
|
|
`"id":"mk5fmymtx4wsprk"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 4,
|
|
},
|
|
},
|
|
{
|
|
Name: "authenticated regular record that matches the collection list rule with hidden field",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo3/records",
|
|
Headers: map[string]string{
|
|
// clients, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImdrMzkwcWVnczR5NDd3biIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoidjg1MXE0cjc5MHJoa25sIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.0ONnm_BsvPRZyDNT31GN1CKUB6uQRxvVvQ-Wc9AZfG0",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
col, err := app.FindCollectionByNameOrId("demo3")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// mock hidden field
|
|
col.Fields.GetByName("title").SetHidden(true)
|
|
|
|
col.ListRule = types.Pointer("title ~ 'test'")
|
|
|
|
if err = app.Save(col); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":4`,
|
|
`"items":[{`,
|
|
`"id":"1tmknxy2868d869"`,
|
|
`"id":"lcl9d87w22ml6jy"`,
|
|
`"id":"7nwo8tuiatetxdm"`,
|
|
`"id":"mk5fmymtx4wsprk"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 4,
|
|
},
|
|
},
|
|
{
|
|
Name: "authenticated regular record filtering with a hidden field",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo3/records?filter=title~'test'",
|
|
Headers: map[string]string{
|
|
// clients, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImdrMzkwcWVnczR5NDd3biIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoidjg1MXE0cjc5MHJoa25sIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.0ONnm_BsvPRZyDNT31GN1CKUB6uQRxvVvQ-Wc9AZfG0",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
col, err := app.FindCollectionByNameOrId("demo3")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// mock hidden field
|
|
col.Fields.GetByName("title").SetHidden(true)
|
|
|
|
if err = app.Save(col); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "superuser filtering with a hidden field",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo3/records?filter=title~'test'",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
col, err := app.FindCollectionByNameOrId("demo3")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// mock hidden field
|
|
col.Fields.GetByName("title").SetHidden(true)
|
|
|
|
if err = app.Save(col); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":4`,
|
|
`"items":[{`,
|
|
`"id":"1tmknxy2868d869"`,
|
|
`"id":"lcl9d87w22ml6jy"`,
|
|
`"id":"7nwo8tuiatetxdm"`,
|
|
`"id":"mk5fmymtx4wsprk"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 4,
|
|
},
|
|
},
|
|
{
|
|
Name: ":rule modifer",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo5/records",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":1`,
|
|
`"items":[{`,
|
|
`"id":"qjeql998mtp1azp"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "multi-match - at least one of",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo4/records?filter=" + url.QueryEscape("rel_many_no_cascade_required.files:length?=2"),
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":1`,
|
|
`"items":[{`,
|
|
`"id":"qzaqccwrmva4o1n"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "multi-match - all",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo4/records?filter=" + url.QueryEscape("rel_many_no_cascade_required.files:length=2"),
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":0`,
|
|
`"totalItems":0`,
|
|
`"items":[]`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
},
|
|
},
|
|
|
|
// auth collection
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "check email visibility as guest",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/nologin/records",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":3`,
|
|
`"items":[{`,
|
|
`"id":"phhq3wr65cap535"`,
|
|
`"id":"dc49k6jgejn40h3"`,
|
|
`"id":"oos036e9xvqeexy"`,
|
|
`"email":"test2@example.com"`,
|
|
`"emailVisibility":true`,
|
|
`"emailVisibility":false`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
`"email":"test@example.com"`,
|
|
`"email":"test3@example.com"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 3,
|
|
},
|
|
},
|
|
{
|
|
Name: "check email visibility as any authenticated record",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/nologin/records",
|
|
Headers: map[string]string{
|
|
// clients, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImdrMzkwcWVnczR5NDd3biIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoidjg1MXE0cjc5MHJoa25sIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.0ONnm_BsvPRZyDNT31GN1CKUB6uQRxvVvQ-Wc9AZfG0",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":3`,
|
|
`"items":[{`,
|
|
`"id":"phhq3wr65cap535"`,
|
|
`"id":"dc49k6jgejn40h3"`,
|
|
`"id":"oos036e9xvqeexy"`,
|
|
`"email":"test2@example.com"`,
|
|
`"emailVisibility":true`,
|
|
`"emailVisibility":false`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey":"`,
|
|
`"password":""`,
|
|
`"email":"test@example.com"`,
|
|
`"email":"test3@example.com"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 3,
|
|
},
|
|
},
|
|
{
|
|
Name: "check email visibility as manage auth record",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/nologin/records",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":3`,
|
|
`"items":[{`,
|
|
`"id":"phhq3wr65cap535"`,
|
|
`"id":"dc49k6jgejn40h3"`,
|
|
`"id":"oos036e9xvqeexy"`,
|
|
`"email":"test@example.com"`,
|
|
`"email":"test2@example.com"`,
|
|
`"email":"test3@example.com"`,
|
|
`"emailVisibility":true`,
|
|
`"emailVisibility":false`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 3,
|
|
},
|
|
},
|
|
{
|
|
Name: "check email visibility as superuser",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/nologin/records",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":3`,
|
|
`"items":[{`,
|
|
`"id":"phhq3wr65cap535"`,
|
|
`"id":"dc49k6jgejn40h3"`,
|
|
`"id":"oos036e9xvqeexy"`,
|
|
`"email":"test@example.com"`,
|
|
`"email":"test2@example.com"`,
|
|
`"email":"test3@example.com"`,
|
|
`"emailVisibility":true`,
|
|
`"emailVisibility":false`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 3,
|
|
},
|
|
},
|
|
{
|
|
Name: "check self email visibility resolver",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/nologin/records",
|
|
Headers: map[string]string{
|
|
// nologin, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImRjNDlrNmpnZWpuNDBoMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoia3B2NzA5c2sybHFicWs4IiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.fdUPFLDx5b6RM_XFqnqsyiyNieyKA2HIIkRmUh9kIoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":3`,
|
|
`"items":[{`,
|
|
`"id":"phhq3wr65cap535"`,
|
|
`"id":"dc49k6jgejn40h3"`,
|
|
`"id":"oos036e9xvqeexy"`,
|
|
`"email":"test2@example.com"`,
|
|
`"email":"test@example.com"`,
|
|
`"emailVisibility":true`,
|
|
`"emailVisibility":false`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
`"email":"test3@example.com"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 3,
|
|
},
|
|
},
|
|
|
|
// view collection
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "public view records",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/view2/records?filter=state=false",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":2`,
|
|
`"items":[{`,
|
|
`"id":"al1h9ijdeojtsjy"`,
|
|
`"id":"imy661ixudk5izi"`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"created"`,
|
|
`"updated"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 2,
|
|
},
|
|
},
|
|
{
|
|
Name: "guest that doesn't match the view collection list rule",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/view1/records",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":0`,
|
|
`"totalItems":0`,
|
|
`"items":[]`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "authenticated record that matches the view collection list rule",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/view1/records",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":1`,
|
|
`"items":[{`,
|
|
`"id":"84nmscqy84lsi1t"`,
|
|
`"bool":true`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "view collection with numeric ids",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/numeric_id_view/records",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"page":1`,
|
|
`"perPage":30`,
|
|
`"totalPages":1`,
|
|
`"totalItems":2`,
|
|
`"items":[{`,
|
|
`"id":"1"`,
|
|
`"id":"2"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordsListRequest": 1,
|
|
"OnRecordEnrich": 2,
|
|
},
|
|
},
|
|
|
|
// rate limit checks
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "RateLimit rule - view2:list",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/view2/records",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Settings().RateLimits.Enabled = true
|
|
app.Settings().RateLimits.Rules = []core.RateLimitRule{
|
|
{MaxRequests: 100, Label: "abc"},
|
|
{MaxRequests: 100, Label: "*:list"},
|
|
{MaxRequests: 0, Label: "view2:list"},
|
|
}
|
|
},
|
|
ExpectedStatus: 429,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "RateLimit rule - *:list",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/view2/records",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Settings().RateLimits.Enabled = true
|
|
app.Settings().RateLimits.Rules = []core.RateLimitRule{
|
|
{MaxRequests: 100, Label: "abc"},
|
|
{MaxRequests: 0, Label: "*:list"},
|
|
}
|
|
},
|
|
ExpectedStatus: 429,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|
|
|
|
func TestRecordCrudView(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "missing collection",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/missing/records/0yxhwia2amd8gec",
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "missing record",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo2/records/missing",
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "unauthenticated trying to access nil rule collection (aka. need superuser auth)",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo1/records/imy661ixudk5izi",
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authenticated record trying to access nil rule collection (aka. need superuser auth)",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo1/records/imy661ixudk5izi",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authenticated record that doesn't match the collection view rule",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/users/records/bgs820n361vj1qd",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "public collection view",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo2/records/0yxhwia2amd8gec",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"0yxhwia2amd8gec"`,
|
|
`"collectionName":"demo2"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "public collection view (using the collection id)",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/sz5l5z67tg7gku0/records/0yxhwia2amd8gec",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"0yxhwia2amd8gec"`,
|
|
`"collectionName":"demo2"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "authorized as superuser trying to access nil rule collection view (aka. need superuser auth)",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo1/records/imy661ixudk5izi",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"imy661ixudk5izi"`,
|
|
`"collectionName":"demo1"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "authenticated record that does match the collection view rule",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/users/records/4q1xlclmfloku33",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"4q1xlclmfloku33"`,
|
|
`"collectionName":"users"`,
|
|
// owners can always view their email
|
|
`"emailVisibility":false`,
|
|
`"email":"test@example.com"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "expand relations",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/demo1/records/al1h9ijdeojtsjy?expand=rel_one,rel_many.rel,missing&perPage=2&sort=created",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"al1h9ijdeojtsjy"`,
|
|
`"collectionName":"demo1"`,
|
|
`"rel_many":[{`,
|
|
`"rel_one":{`,
|
|
`"collectionName":"users"`,
|
|
`"id":"bgs820n361vj1qd"`,
|
|
`"expand":{"rel":{`,
|
|
`"id":"0yxhwia2amd8gec"`,
|
|
`"collectionName":"demo2"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 7,
|
|
},
|
|
},
|
|
|
|
// auth collection
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "check email visibility as guest",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/nologin/records/oos036e9xvqeexy",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"oos036e9xvqeexy"`,
|
|
`"emailVisibility":false`,
|
|
`"verified":true`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
`"email":"test3@example.com"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "check email visibility as any authenticated record",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/nologin/records/oos036e9xvqeexy",
|
|
Headers: map[string]string{
|
|
// clients, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImdrMzkwcWVnczR5NDd3biIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoidjg1MXE0cjc5MHJoa25sIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.0ONnm_BsvPRZyDNT31GN1CKUB6uQRxvVvQ-Wc9AZfG0",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"oos036e9xvqeexy"`,
|
|
`"emailVisibility":false`,
|
|
`"verified":true`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
`"email":"test3@example.com"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "check email visibility as manage auth record",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/nologin/records/oos036e9xvqeexy",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"oos036e9xvqeexy"`,
|
|
`"emailVisibility":false`,
|
|
`"email":"test3@example.com"`,
|
|
`"verified":true`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "check email visibility as superuser",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/nologin/records/oos036e9xvqeexy",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"oos036e9xvqeexy"`,
|
|
`"emailVisibility":false`,
|
|
`"email":"test3@example.com"`,
|
|
`"verified":true`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "check self email visibility resolver",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/nologin/records/dc49k6jgejn40h3",
|
|
Headers: map[string]string{
|
|
// nologin, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImRjNDlrNmpnZWpuNDBoMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoia3B2NzA5c2sybHFicWs4IiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.fdUPFLDx5b6RM_XFqnqsyiyNieyKA2HIIkRmUh9kIoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"dc49k6jgejn40h3"`,
|
|
`"email":"test@example.com"`,
|
|
`"emailVisibility":false`,
|
|
`"verified":false`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
|
|
// view collection
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "public view record",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/view2/records/84nmscqy84lsi1t",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"84nmscqy84lsi1t"`,
|
|
`"state":true`,
|
|
`"file_many":["`,
|
|
`"rel_many":["`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"created"`,
|
|
`"updated"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "guest that doesn't match the view collection view rule",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/view1/records/84nmscqy84lsi1t",
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authenticated record that matches the view collection view rule",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/view1/records/84nmscqy84lsi1t",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"84nmscqy84lsi1t"`,
|
|
`"bool":true`,
|
|
`"text":"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "view record with numeric id",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/numeric_id_view/records/1",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"1"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordViewRequest": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
|
|
// rate limit checks
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "RateLimit rule - numeric_id_view:view",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/numeric_id_view/records/1",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Settings().RateLimits.Enabled = true
|
|
app.Settings().RateLimits.Rules = []core.RateLimitRule{
|
|
{MaxRequests: 100, Label: "abc"},
|
|
{MaxRequests: 100, Label: "*:view"},
|
|
{MaxRequests: 0, Label: "numeric_id_view:view"},
|
|
}
|
|
},
|
|
ExpectedStatus: 429,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "RateLimit rule - *:view",
|
|
Method: http.MethodGet,
|
|
URL: "/api/collections/numeric_id_view/records/1",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Settings().RateLimits.Enabled = true
|
|
app.Settings().RateLimits.Rules = []core.RateLimitRule{
|
|
{MaxRequests: 100, Label: "abc"},
|
|
{MaxRequests: 0, Label: "*:view"},
|
|
}
|
|
},
|
|
ExpectedStatus: 429,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|
|
|
|
func TestRecordCrudDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ensureDeletedFiles := func(app *tests.TestApp, collectionId string, recordId string) {
|
|
storageDir := filepath.Join(app.DataDir(), "storage", collectionId, recordId)
|
|
|
|
entries, _ := os.ReadDir(storageDir)
|
|
if len(entries) != 0 {
|
|
t.Errorf("Expected empty/deleted dir, found: %d\n%v", len(entries), entries)
|
|
}
|
|
}
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "missing collection",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/missing/records/0yxhwia2amd8gec",
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "missing record",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/demo2/records/missing",
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "unauthenticated trying to delete nil rule collection (aka. need superuser auth)",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/demo1/records/imy661ixudk5izi",
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authenticated record trying to delete nil rule collection (aka. need superuser auth)",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/demo1/records/imy661ixudk5izi",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authenticated record that doesn't match the collection delete rule",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/users/records/bgs820n361vj1qd",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "trying to delete a view collection record",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/view1/records/imy661ixudk5izi",
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "public collection record delete",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/nologin/records/dc49k6jgejn40h3",
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordDeleteRequest": 1,
|
|
"OnModelDelete": 1,
|
|
"OnModelDeleteExecute": 1,
|
|
"OnModelAfterDeleteSuccess": 1,
|
|
"OnRecordDelete": 1,
|
|
"OnRecordDeleteExecute": 1,
|
|
"OnRecordAfterDeleteSuccess": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "public collection record delete (using the collection id as identifier)",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/kpv709sk2lqbqk8/records/dc49k6jgejn40h3",
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordDeleteRequest": 1,
|
|
"OnModelDelete": 1,
|
|
"OnModelDeleteExecute": 1,
|
|
"OnModelAfterDeleteSuccess": 1,
|
|
"OnRecordDelete": 1,
|
|
"OnRecordDeleteExecute": 1,
|
|
"OnRecordAfterDeleteSuccess": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "authorized as superuser trying to delete nil rule collection view (aka. need superuser auth)",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/clients/records/o1y0dd0spd786md",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordDeleteRequest": 1,
|
|
"OnModelDelete": 1,
|
|
"OnModelDeleteExecute": 1,
|
|
"OnModelAfterDeleteSuccess": 1,
|
|
"OnRecordDelete": 1,
|
|
"OnRecordDeleteExecute": 1,
|
|
"OnRecordAfterDeleteSuccess": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "OnRecordAfterDeleteSuccessRequest error response",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/clients/records/o1y0dd0spd786md",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.OnRecordDeleteRequest().BindFunc(func(e *core.RecordRequestEvent) error {
|
|
return errors.New("error")
|
|
})
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordDeleteRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "authenticated record that match the collection delete rule",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/users/records/4q1xlclmfloku33",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
Delay: 100 * time.Millisecond,
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordDeleteRequest": 1,
|
|
"OnModelDelete": 3, // +2 for the externalAuths
|
|
"OnModelDeleteExecute": 3,
|
|
"OnModelAfterDeleteSuccess": 3,
|
|
"OnRecordDelete": 3,
|
|
"OnRecordDeleteExecute": 3,
|
|
"OnRecordAfterDeleteSuccess": 3,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
ensureDeletedFiles(app, "_pb_users_auth_", "4q1xlclmfloku33")
|
|
|
|
// check if all the external auths records were deleted
|
|
collection, _ := app.FindCollectionByNameOrId("users")
|
|
record := core.NewRecord(collection)
|
|
record.Set("id", "4q1xlclmfloku33")
|
|
externalAuths, err := app.FindAllExternalAuthsByRecord(record)
|
|
if err != nil {
|
|
t.Errorf("Failed to fetch external auths: %v", err)
|
|
}
|
|
if len(externalAuths) > 0 {
|
|
t.Errorf("Expected the linked external auths to be deleted, got %d", len(externalAuths))
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "@request :isset (rule failure check)",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/demo5/records/la4y2w4o98acwuj",
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "@request :isset (rule pass check)",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/demo5/records/la4y2w4o98acwuj?test=1",
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordDeleteRequest": 1,
|
|
"OnModelDelete": 1,
|
|
"OnModelDeleteExecute": 1,
|
|
"OnModelAfterDeleteSuccess": 1,
|
|
"OnRecordDelete": 1,
|
|
"OnRecordDeleteExecute": 1,
|
|
"OnRecordAfterDeleteSuccess": 1,
|
|
},
|
|
},
|
|
|
|
// cascade delete checks
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "trying to delete a record while being part of a non-cascade required relation",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/demo3/records/7nwo8tuiatetxdm",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordDeleteRequest": 1,
|
|
"OnModelDelete": 2, // the record itself + rel_one_cascade of test1 record
|
|
"OnModelDeleteExecute": 2,
|
|
"OnModelAfterDeleteError": 2,
|
|
"OnRecordDelete": 2,
|
|
"OnRecordDeleteExecute": 2,
|
|
"OnRecordAfterDeleteError": 2,
|
|
"OnModelUpdate": 2, // self_rel_many update of test1 record + rel_one_cascade demo4 cascaded in demo5
|
|
"OnModelUpdateExecute": 2,
|
|
"OnModelAfterUpdateError": 2,
|
|
"OnRecordUpdate": 2,
|
|
"OnRecordUpdateExecute": 2,
|
|
"OnRecordAfterUpdateError": 2,
|
|
},
|
|
},
|
|
{
|
|
Name: "delete a record with non-cascade references",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/demo3/records/1tmknxy2868d869",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordDeleteRequest": 1,
|
|
"OnModelDelete": 1,
|
|
"OnModelDeleteExecute": 1,
|
|
"OnModelAfterDeleteSuccess": 1,
|
|
"OnRecordDelete": 1,
|
|
"OnRecordDeleteExecute": 1,
|
|
"OnRecordAfterDeleteSuccess": 1,
|
|
"OnModelUpdate": 2,
|
|
"OnModelUpdateExecute": 2,
|
|
"OnModelAfterUpdateSuccess": 2,
|
|
"OnRecordUpdate": 2,
|
|
"OnRecordUpdateExecute": 2,
|
|
"OnRecordAfterUpdateSuccess": 2,
|
|
},
|
|
},
|
|
{
|
|
Name: "delete a record with cascade references",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/users/records/oap640cot4yru2s",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
Delay: 100 * time.Millisecond,
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordDeleteRequest": 1,
|
|
"OnModelDelete": 2,
|
|
"OnModelDeleteExecute": 2,
|
|
"OnModelAfterDeleteSuccess": 2,
|
|
"OnRecordDelete": 2,
|
|
"OnRecordDeleteExecute": 2,
|
|
"OnRecordAfterDeleteSuccess": 2,
|
|
"OnModelUpdate": 2,
|
|
"OnModelUpdateExecute": 2,
|
|
"OnModelAfterUpdateSuccess": 2,
|
|
"OnRecordUpdate": 2,
|
|
"OnRecordUpdateExecute": 2,
|
|
"OnRecordAfterUpdateSuccess": 2,
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
recId := "84nmscqy84lsi1t"
|
|
rec, _ := app.FindRecordById("demo1", recId, nil)
|
|
if rec != nil {
|
|
t.Errorf("Expected record %s to be cascade deleted", recId)
|
|
}
|
|
ensureDeletedFiles(app, "wsmn24bux7wo113", recId)
|
|
ensureDeletedFiles(app, "_pb_users_auth_", "oap640cot4yru2s")
|
|
},
|
|
},
|
|
|
|
// rate limit checks
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "RateLimit rule - demo5:delete",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/demo5/records/la4y2w4o98acwuj?test=1",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Settings().RateLimits.Enabled = true
|
|
app.Settings().RateLimits.Rules = []core.RateLimitRule{
|
|
{MaxRequests: 100, Label: "abc"},
|
|
{MaxRequests: 100, Label: "*:delete"},
|
|
{MaxRequests: 0, Label: "demo5:delete"},
|
|
}
|
|
},
|
|
ExpectedStatus: 429,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "RateLimit rule - *:delete",
|
|
Method: http.MethodDelete,
|
|
URL: "/api/collections/demo5/records/la4y2w4o98acwuj?test=1",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Settings().RateLimits.Enabled = true
|
|
app.Settings().RateLimits.Rules = []core.RateLimitRule{
|
|
{MaxRequests: 100, Label: "abc"},
|
|
{MaxRequests: 0, Label: "*:delete"},
|
|
}
|
|
},
|
|
ExpectedStatus: 429,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|
|
|
|
func TestRecordCrudCreate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
formData, mp, err := tests.MockMultipartData(map[string]string{
|
|
"title": "title_test",
|
|
}, "files")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
formData2, mp2, err2 := tests.MockMultipartData(map[string]string{
|
|
router.JSONPayloadKey: `{"title": "title_test2", "testPayload": 123}`,
|
|
}, "files")
|
|
if err2 != nil {
|
|
t.Fatal(err2)
|
|
}
|
|
|
|
formData3, mp3, err3 := tests.MockMultipartData(map[string]string{
|
|
router.JSONPayloadKey: `{"title": "title_test3", "testPayload": 123}`,
|
|
}, "files")
|
|
if err3 != nil {
|
|
t.Fatal(err3)
|
|
}
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "missing collection",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/missing/records",
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "guest trying to access nil-rule collection",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo1/records",
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "auth record trying to access nil-rule collection",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo1/records",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "trying to create a new view collection record",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/view1/records",
|
|
Body: strings.NewReader(`{"text":"new"}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "submit invalid body",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo2/records",
|
|
Body: strings.NewReader(`{"`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "submit nil body",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo2/records",
|
|
Body: nil,
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"title":{"code":"validation_required"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelValidate": 1,
|
|
"OnModelAfterCreateError": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordAfterCreateError": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "submit empty json body",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/nologin/records",
|
|
Body: strings.NewReader(`{}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"password":{"code":"validation_required"`,
|
|
`"passwordConfirm":{"code":"validation_required"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "guest submit in public collection",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo2/records",
|
|
Body: strings.NewReader(`{"title":"new"}`),
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":`,
|
|
`"title":"new"`,
|
|
`"active":false`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "guest trying to submit in restricted collection",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo3/records",
|
|
Body: strings.NewReader(`{"title":"test123"}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "auth record submit in restricted collection (rule failure check)",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo3/records",
|
|
Body: strings.NewReader(`{"title":"test123"}`),
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "auth record submit in restricted collection (rule pass check) + expand relations",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo4/records?expand=missing,rel_one_no_cascade,rel_many_no_cascade_required",
|
|
Body: strings.NewReader(`{
|
|
"title":"test123",
|
|
"rel_one_no_cascade":"mk5fmymtx4wsprk",
|
|
"rel_one_no_cascade_required":"7nwo8tuiatetxdm",
|
|
"rel_one_cascade":"mk5fmymtx4wsprk",
|
|
"rel_many_no_cascade":"mk5fmymtx4wsprk",
|
|
"rel_many_no_cascade_required":["7nwo8tuiatetxdm","lcl9d87w22ml6jy"],
|
|
"rel_many_cascade":"lcl9d87w22ml6jy"
|
|
}`),
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":`,
|
|
`"title":"test123"`,
|
|
`"expand":{}`, // empty expand even because of the query param
|
|
`"rel_one_no_cascade":"mk5fmymtx4wsprk"`,
|
|
`"rel_one_no_cascade_required":"7nwo8tuiatetxdm"`,
|
|
`"rel_one_cascade":"mk5fmymtx4wsprk"`,
|
|
`"rel_many_no_cascade":["mk5fmymtx4wsprk"]`,
|
|
`"rel_many_no_cascade_required":["7nwo8tuiatetxdm","lcl9d87w22ml6jy"]`,
|
|
`"rel_many_cascade":["lcl9d87w22ml6jy"]`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
// the users auth records don't have access to view the demo3 expands
|
|
`"missing"`,
|
|
`"id":"mk5fmymtx4wsprk"`,
|
|
`"id":"7nwo8tuiatetxdm"`,
|
|
`"id":"lcl9d87w22ml6jy"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "superuser submit in restricted collection (rule skip check) + expand relations",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo4/records?expand=missing,rel_one_no_cascade,rel_many_no_cascade_required",
|
|
Body: strings.NewReader(`{
|
|
"title":"test123",
|
|
"rel_one_no_cascade":"mk5fmymtx4wsprk",
|
|
"rel_one_no_cascade_required":"7nwo8tuiatetxdm",
|
|
"rel_one_cascade":"mk5fmymtx4wsprk",
|
|
"rel_many_no_cascade":"mk5fmymtx4wsprk",
|
|
"rel_many_no_cascade_required":["7nwo8tuiatetxdm","lcl9d87w22ml6jy"],
|
|
"rel_many_cascade":"lcl9d87w22ml6jy"
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":`,
|
|
`"title":"test123"`,
|
|
`"rel_one_no_cascade":"mk5fmymtx4wsprk"`,
|
|
`"rel_one_no_cascade_required":"7nwo8tuiatetxdm"`,
|
|
`"rel_one_cascade":"mk5fmymtx4wsprk"`,
|
|
`"rel_many_no_cascade":["mk5fmymtx4wsprk"]`,
|
|
`"rel_many_no_cascade_required":["7nwo8tuiatetxdm","lcl9d87w22ml6jy"]`,
|
|
`"rel_many_cascade":["lcl9d87w22ml6jy"]`,
|
|
`"expand":{`,
|
|
`"id":"mk5fmymtx4wsprk"`,
|
|
`"id":"7nwo8tuiatetxdm"`,
|
|
`"id":"lcl9d87w22ml6jy"`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"missing"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 4,
|
|
},
|
|
},
|
|
{
|
|
Name: "superuser submit via multipart form data",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo3/records",
|
|
Body: formData,
|
|
Headers: map[string]string{
|
|
"Content-Type": mp.FormDataContentType(),
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"`,
|
|
`"title":"title_test"`,
|
|
`"files":["`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "submit via multipart form data with @jsonPayload key and unsatisfied @request.body rule",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo3/records",
|
|
Body: formData2,
|
|
Headers: map[string]string{
|
|
"Content-Type": mp2.FormDataContentType(),
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
collection, err := app.FindCollectionByNameOrId("demo3")
|
|
if err != nil {
|
|
t.Fatalf("failed to find demo3 collection: %v", err)
|
|
}
|
|
collection.CreateRule = types.Pointer("@request.body.testPayload != 123")
|
|
if err := app.Save(collection); err != nil {
|
|
t.Fatalf("failed to update demo3 collection create rule: %v", err)
|
|
}
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "submit via multipart form data with @jsonPayload key and satisfied @request.body rule",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo3/records",
|
|
Body: formData3,
|
|
Headers: map[string]string{
|
|
"Content-Type": mp3.FormDataContentType(),
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
collection, err := app.FindCollectionByNameOrId("demo3")
|
|
if err != nil {
|
|
t.Fatalf("failed to find demo3 collection: %v", err)
|
|
}
|
|
collection.CreateRule = types.Pointer("@request.body.testPayload = 123")
|
|
if err := app.Save(collection); err != nil {
|
|
t.Fatalf("failed to update demo3 collection create rule: %v", err)
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"`,
|
|
`"title":"title_test3"`,
|
|
`"files":["`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "unique field error check",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo2/records",
|
|
Body: strings.NewReader(`{
|
|
"title":"test2"
|
|
}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"title":{`,
|
|
`"code":"validation_not_unique"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateError": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateError": 1,
|
|
"OnRecordValidate": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "OnRecordAfterCreateSuccessRequest error response",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo2/records",
|
|
Body: strings.NewReader(`{"title":"new"}`),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.OnRecordCreateRequest().BindFunc(func(e *core.RecordRequestEvent) error {
|
|
return errors.New("error")
|
|
})
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
},
|
|
},
|
|
|
|
// ID checks
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "invalid custom insertion id (less than 15 chars)",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo3/records",
|
|
Body: strings.NewReader(`{
|
|
"id": "12345678901234",
|
|
"title": "test"
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"id":{"code":"validation_min_text_constraint"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelValidate": 1,
|
|
"OnModelAfterCreateError": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordAfterCreateError": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "invalid custom insertion id (more than 15 chars)",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo3/records",
|
|
Body: strings.NewReader(`{
|
|
"id": "1234567890123456",
|
|
"title": "test"
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"id":{"code":"validation_max_text_constraint"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelValidate": 1,
|
|
"OnModelAfterCreateError": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordAfterCreateError": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "valid custom insertion id (exactly 15 chars)",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo3/records",
|
|
Body: strings.NewReader(`{
|
|
"id": "123456789012345",
|
|
"title": "test"
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"123456789012345"`,
|
|
`"title":"test"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "valid custom insertion id existing in another non-auth collection",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo3/records",
|
|
Body: strings.NewReader(`{
|
|
"id": "0yxhwia2amd8gec",
|
|
"title": "test"
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"0yxhwia2amd8gec"`,
|
|
`"title":"test"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "valid custom insertion auth id duplicating in another auth collection",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/users/records",
|
|
Body: strings.NewReader(`{
|
|
"id":"o1y0dd0spd786md",
|
|
"title":"test",
|
|
"password":"1234567890",
|
|
"passwordConfirm":"1234567890"
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"id":{"code":"validation_invalid_auth_id"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1, // unique constraints are handled on db level
|
|
"OnModelAfterCreateError": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateError": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
},
|
|
},
|
|
|
|
// check whether if @request.body modifer fields are properly resolved
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "@request.body.field with compute modifers (rule failure check)",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo5/records",
|
|
Body: strings.NewReader(`{
|
|
"total+":4,
|
|
"total-":2
|
|
}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{}`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "@request.body.field with compute modifers (rule pass check)",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo5/records",
|
|
Body: strings.NewReader(`{
|
|
"total+":4,
|
|
"total-":1
|
|
}`),
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"`,
|
|
`"collectionName":"demo5"`,
|
|
`"total":3`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
|
|
// auth records
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "auth record with invalid form data",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/users/records",
|
|
Body: strings.NewReader(`{
|
|
"password":"1234567",
|
|
"passwordConfirm":"1234560",
|
|
"email":"invalid",
|
|
"username":"Users75657"
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"passwordConfirm":{"code":"validation_values_mismatch"`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
// record fields are not checked if the base auth form fields have errors
|
|
`"rel":`,
|
|
`"email":`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "auth record with valid form data but invalid record fields",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/users/records",
|
|
Body: strings.NewReader(`{
|
|
"password":"1234567",
|
|
"passwordConfirm":"1234567",
|
|
"rel":"invalid"
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"rel":{"code":`,
|
|
`"password":{"code":`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelValidate": 1,
|
|
"OnModelAfterCreateError": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordAfterCreateError": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "auth record with valid data and explicitly verified state by guest",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/users/records",
|
|
Body: strings.NewReader(`{
|
|
"password":"12345678",
|
|
"passwordConfirm":"12345678",
|
|
"verified":true
|
|
}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"verified":{"code":`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
// no validation hooks because it should fail before save by the form auth fields validator
|
|
},
|
|
},
|
|
{
|
|
Name: "auth record with valid data and explicitly verified state by random user",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/users/records",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
Body: strings.NewReader(`{
|
|
"password":"12345678",
|
|
"passwordConfirm":"12345678",
|
|
"emailVisibility":true,
|
|
"verified":true
|
|
}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"verified":{"code":`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"emailVisibility":{"code":`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
// no validation hooks because it should fail before save by the form auth fields validator
|
|
},
|
|
},
|
|
{
|
|
Name: "auth record with valid data by superuser",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/users/records",
|
|
Body: strings.NewReader(`{
|
|
"id":"o1o1y0pd78686mq",
|
|
"username":"test.valid",
|
|
"email":"new@example.com",
|
|
"password":"12345678",
|
|
"passwordConfirm":"12345678",
|
|
"rel":"achvryl401bhse3",
|
|
"emailVisibility":true,
|
|
"verified":true
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"o1o1y0pd78686mq"`,
|
|
`"username":"test.valid"`,
|
|
`"email":"new@example.com"`,
|
|
`"rel":"achvryl401bhse3"`,
|
|
`"emailVisibility":true`,
|
|
`"verified":true`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
`"passwordConfirm"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "auth record with valid data by auth record with manage access",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/nologin/records",
|
|
Body: strings.NewReader(`{
|
|
"email":"new@example.com",
|
|
"password":"12345678",
|
|
"passwordConfirm":"12345678",
|
|
"name":"test_name",
|
|
"emailVisibility":true,
|
|
"verified":true
|
|
}`),
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"`,
|
|
`"username":"`,
|
|
`"email":"new@example.com"`,
|
|
`"name":"test_name"`,
|
|
`"emailVisibility":true`,
|
|
`"verified":true`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
`"passwordConfirm"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
|
|
// ensure that hidden fields cannot be set by non-superusers
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "create with hidden field as regular user",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo3/records",
|
|
Body: strings.NewReader(`{
|
|
"id": "abcde1234567890",
|
|
"title": "test_create"
|
|
}`),
|
|
Headers: map[string]string{
|
|
// clients, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImdrMzkwcWVnczR5NDd3biIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoidjg1MXE0cjc5MHJoa25sIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.0ONnm_BsvPRZyDNT31GN1CKUB6uQRxvVvQ-Wc9AZfG0",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
col, err := app.FindCollectionByNameOrId("demo3")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// mock hidden field
|
|
col.Fields.GetByName("title").SetHidden(true)
|
|
|
|
if err = app.Save(col); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
record, err := app.FindRecordById("demo3", "abcde1234567890")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// ensure that the title wasn't saved
|
|
if v := record.GetString("title"); v != "" {
|
|
t.Fatalf("Expected empty title, got %q", v)
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"abcde1234567890"`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"title"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "create with hidden field as superuser",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo3/records",
|
|
Body: strings.NewReader(`{
|
|
"id": "abcde1234567890",
|
|
"title": "test_create"
|
|
}`),
|
|
Headers: map[string]string{
|
|
// superusers, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
col, err := app.FindCollectionByNameOrId("demo3")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// mock hidden field
|
|
col.Fields.GetByName("title").SetHidden(true)
|
|
|
|
if err = app.Save(col); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
record, err := app.FindRecordById("demo3", "abcde1234567890")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// ensure that the title was saved
|
|
if v := record.GetString("title"); v != "test_create" {
|
|
t.Fatalf("Expected title %q, got %q", "test_create", v)
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"abcde1234567890"`,
|
|
`"title":"test_create"`,
|
|
},
|
|
NotExpectedContent: []string{},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordCreateRequest": 1,
|
|
"OnModelCreate": 1,
|
|
"OnModelCreateExecute": 1,
|
|
"OnModelAfterCreateSuccess": 1,
|
|
"OnRecordCreate": 1,
|
|
"OnRecordCreateExecute": 1,
|
|
"OnRecordAfterCreateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
|
|
// rate limit checks
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "RateLimit rule - demo2:create",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo2/records",
|
|
Body: strings.NewReader(`{"title":"new"}`),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Settings().RateLimits.Enabled = true
|
|
app.Settings().RateLimits.Rules = []core.RateLimitRule{
|
|
{MaxRequests: 100, Label: "abc"},
|
|
{MaxRequests: 100, Label: "*:create"},
|
|
{MaxRequests: 0, Label: "demo2:create"},
|
|
}
|
|
},
|
|
ExpectedStatus: 429,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "RateLimit rule - *:create",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo2/records",
|
|
Body: strings.NewReader(`{"title":"new"}`),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Settings().RateLimits.Enabled = true
|
|
app.Settings().RateLimits.Rules = []core.RateLimitRule{
|
|
{MaxRequests: 100, Label: "abc"},
|
|
{MaxRequests: 0, Label: "*:create"},
|
|
}
|
|
},
|
|
ExpectedStatus: 429,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
|
|
// dynamic body limit checks
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "body > collection BodyLimit",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo1/records",
|
|
// the exact body doesn't matter as long as it returns 413
|
|
Body: bytes.NewReader(make([]byte, apis.DefaultMaxBodySize+5+20+2+1)),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
collection, err := app.FindCollectionByNameOrId("demo1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// adjust field sizes for the test
|
|
// ---
|
|
fileOneField := collection.Fields.GetByName("file_one").(*core.FileField)
|
|
fileOneField.MaxSize = 5
|
|
|
|
fileManyField := collection.Fields.GetByName("file_many").(*core.FileField)
|
|
fileManyField.MaxSize = 10
|
|
fileManyField.MaxSelect = 2
|
|
|
|
jsonField := collection.Fields.GetByName("json").(*core.JSONField)
|
|
jsonField.MaxSize = 2
|
|
|
|
err = app.Save(collection)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 413,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "body <= collection BodyLimit",
|
|
Method: http.MethodPost,
|
|
URL: "/api/collections/demo1/records",
|
|
// the exact body doesn't matter as long as it doesn't return 413
|
|
Body: bytes.NewReader(make([]byte, apis.DefaultMaxBodySize+5+20+2)),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
collection, err := app.FindCollectionByNameOrId("demo1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// adjust field sizes for the test
|
|
// ---
|
|
fileOneField := collection.Fields.GetByName("file_one").(*core.FileField)
|
|
fileOneField.MaxSize = 5
|
|
|
|
fileManyField := collection.Fields.GetByName("file_many").(*core.FileField)
|
|
fileManyField.MaxSize = 10
|
|
fileManyField.MaxSelect = 2
|
|
|
|
jsonField := collection.Fields.GetByName("json").(*core.JSONField)
|
|
jsonField.MaxSize = 2
|
|
|
|
err = app.Save(collection)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|
|
|
|
func TestRecordCrudUpdate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
formData, mp, err := tests.MockMultipartData(map[string]string{
|
|
"title": "title_test",
|
|
}, "files")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
formData2, mp2, err2 := tests.MockMultipartData(map[string]string{
|
|
router.JSONPayloadKey: `{"title": "title_test2", "testPayload": 123}`,
|
|
}, "files")
|
|
if err2 != nil {
|
|
t.Fatal(err2)
|
|
}
|
|
|
|
formData3, mp3, err3 := tests.MockMultipartData(map[string]string{
|
|
router.JSONPayloadKey: `{"title": "title_test3", "testPayload": 123, "files":"300_JdfBOieXAW.png"}`,
|
|
}, "files")
|
|
if err3 != nil {
|
|
t.Fatal(err3)
|
|
}
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "missing collection",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/missing/records/0yxhwia2amd8gec",
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "guest trying to access nil-rule collection record",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo1/records/imy661ixudk5izi",
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "auth record trying to access nil-rule collection",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo1/records/imy661ixudk5izi",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "trying to update a view collection record",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/view1/records/imy661ixudk5izi",
|
|
Body: strings.NewReader(`{"text":"new"}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "submit invalid body",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo2/records/0yxhwia2amd8gec",
|
|
Body: strings.NewReader(`{"`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "submit nil body (aka. no fields change)",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo2/records/0yxhwia2amd8gec",
|
|
Body: nil,
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"collectionName":"demo2"`,
|
|
`"id":"0yxhwia2amd8gec"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "submit empty body (aka. no fields change)",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo2/records/0yxhwia2amd8gec",
|
|
Body: strings.NewReader(`{}`),
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"collectionName":"demo2"`,
|
|
`"id":"0yxhwia2amd8gec"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "trigger field validation",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo2/records/0yxhwia2amd8gec",
|
|
Body: strings.NewReader(`{"title":"a"}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`data":{`,
|
|
`"title":{"code":"validation_min_text_constraint"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelValidate": 1,
|
|
"OnModelAfterUpdateError": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordAfterUpdateError": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "guest submit in public collection",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo2/records/0yxhwia2amd8gec",
|
|
Body: strings.NewReader(`{"title":"new"}`),
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"0yxhwia2amd8gec"`,
|
|
`"title":"new"`,
|
|
`"active":true`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "guest trying to submit in restricted collection",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo3/records/mk5fmymtx4wsprk",
|
|
Body: strings.NewReader(`{"title":"new"}`),
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "auth record submit in restricted collection (rule failure check)",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo3/records/mk5fmymtx4wsprk",
|
|
Body: strings.NewReader(`{"title":"new"}`),
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "auth record submit in restricted collection (rule pass check) + expand relations",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo4/records/i9naidtvr6qsgb4?expand=missing,rel_one_no_cascade,rel_many_no_cascade_required",
|
|
Body: strings.NewReader(`{
|
|
"title":"test123",
|
|
"rel_one_no_cascade":"mk5fmymtx4wsprk",
|
|
"rel_one_no_cascade_required":"7nwo8tuiatetxdm",
|
|
"rel_one_cascade":"mk5fmymtx4wsprk",
|
|
"rel_many_no_cascade":"mk5fmymtx4wsprk",
|
|
"rel_many_no_cascade_required":["7nwo8tuiatetxdm","lcl9d87w22ml6jy"],
|
|
"rel_many_cascade":"lcl9d87w22ml6jy"
|
|
}`),
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"i9naidtvr6qsgb4"`,
|
|
`"title":"test123"`,
|
|
`"expand":{}`, // empty expand even because of the query param
|
|
`"rel_one_no_cascade":"mk5fmymtx4wsprk"`,
|
|
`"rel_one_no_cascade_required":"7nwo8tuiatetxdm"`,
|
|
`"rel_one_cascade":"mk5fmymtx4wsprk"`,
|
|
`"rel_many_no_cascade":["mk5fmymtx4wsprk"]`,
|
|
`"rel_many_no_cascade_required":["7nwo8tuiatetxdm","lcl9d87w22ml6jy"]`,
|
|
`"rel_many_cascade":["lcl9d87w22ml6jy"]`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
// the users auth records don't have access to view the demo3 expands
|
|
`"missing"`,
|
|
`"id":"mk5fmymtx4wsprk"`,
|
|
`"id":"7nwo8tuiatetxdm"`,
|
|
`"id":"lcl9d87w22ml6jy"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "superuser submit in restricted collection (rule skip check) + expand relations",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo4/records/i9naidtvr6qsgb4?expand=missing,rel_one_no_cascade,rel_many_no_cascade_required",
|
|
Body: strings.NewReader(`{
|
|
"title":"test123",
|
|
"rel_one_no_cascade":"mk5fmymtx4wsprk",
|
|
"rel_one_no_cascade_required":"7nwo8tuiatetxdm",
|
|
"rel_one_cascade":"mk5fmymtx4wsprk",
|
|
"rel_many_no_cascade":"mk5fmymtx4wsprk",
|
|
"rel_many_no_cascade_required":["7nwo8tuiatetxdm","lcl9d87w22ml6jy"],
|
|
"rel_many_cascade":"lcl9d87w22ml6jy"
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"i9naidtvr6qsgb4"`,
|
|
`"title":"test123"`,
|
|
`"rel_one_no_cascade":"mk5fmymtx4wsprk"`,
|
|
`"rel_one_no_cascade_required":"7nwo8tuiatetxdm"`,
|
|
`"rel_one_cascade":"mk5fmymtx4wsprk"`,
|
|
`"rel_many_no_cascade":["mk5fmymtx4wsprk"]`,
|
|
`"rel_many_no_cascade_required":["7nwo8tuiatetxdm","lcl9d87w22ml6jy"]`,
|
|
`"rel_many_cascade":["lcl9d87w22ml6jy"]`,
|
|
`"expand":{`,
|
|
`"id":"mk5fmymtx4wsprk"`,
|
|
`"id":"7nwo8tuiatetxdm"`,
|
|
`"id":"lcl9d87w22ml6jy"`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"missing"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 4,
|
|
},
|
|
},
|
|
{
|
|
Name: "superuser submit via multipart form data",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo3/records/mk5fmymtx4wsprk",
|
|
Body: formData,
|
|
Headers: map[string]string{
|
|
"Content-Type": mp.FormDataContentType(),
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"mk5fmymtx4wsprk"`,
|
|
`"title":"title_test"`,
|
|
`"files":["`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "submit via multipart form data with @jsonPayload key and unsatisfied @request.body rule",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo3/records/mk5fmymtx4wsprk",
|
|
Body: formData2,
|
|
Headers: map[string]string{
|
|
"Content-Type": mp2.FormDataContentType(),
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
collection, err := app.FindCollectionByNameOrId("demo3")
|
|
if err != nil {
|
|
t.Fatalf("failed to find demo3 collection: %v", err)
|
|
}
|
|
collection.UpdateRule = types.Pointer("@request.body.testPayload != 123")
|
|
if err := app.Save(collection); err != nil {
|
|
t.Fatalf("failed to update demo3 collection update rule: %v", err)
|
|
}
|
|
},
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "submit via multipart form data with @jsonPayload key and satisfied @request.body rule",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo3/records/mk5fmymtx4wsprk",
|
|
Body: formData3,
|
|
Headers: map[string]string{
|
|
"Content-Type": mp3.FormDataContentType(),
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
collection, err := app.FindCollectionByNameOrId("demo3")
|
|
if err != nil {
|
|
t.Fatalf("failed to find demo3 collection: %v", err)
|
|
}
|
|
collection.UpdateRule = types.Pointer("@request.body.testPayload = 123")
|
|
if err := app.Save(collection); err != nil {
|
|
t.Fatalf("failed to update demo3 collection update rule: %v", err)
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"mk5fmymtx4wsprk"`,
|
|
`"title":"title_test3"`,
|
|
`"files":["`,
|
|
`"300_JdfBOieXAW.png"`,
|
|
`"tmpfile_`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "OnRecordAfterUpdateSuccessRequest error response",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo2/records/0yxhwia2amd8gec",
|
|
Body: strings.NewReader(`{"title":"new"}`),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.OnRecordUpdateRequest().BindFunc(func(e *core.RecordRequestEvent) error {
|
|
return errors.New("error")
|
|
})
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "try to change the id of an existing record",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo3/records/mk5fmymtx4wsprk",
|
|
Body: strings.NewReader(`{
|
|
"id": "mk5fmymtx4wspra"
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"id":{"code":"validation_pk_change"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelValidate": 1,
|
|
"OnModelAfterUpdateError": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordAfterUpdateError": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "unique field error check",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo2/records/llvuca81nly1qls",
|
|
Body: strings.NewReader(`{
|
|
"title":"test2"
|
|
}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"title":{`,
|
|
`"code":"validation_not_unique"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateError": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateError": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
},
|
|
},
|
|
|
|
// check whether if @request.body modifer fields are properly resolved
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "@request.body.field with compute modifers (rule failure check)",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo5/records/la4y2w4o98acwuj",
|
|
Body: strings.NewReader(`{
|
|
"total+":3,
|
|
"total-":1
|
|
}`),
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{
|
|
`"data":{}`,
|
|
},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "@request.body.field with compute modifers (rule pass check)",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo5/records/la4y2w4o98acwuj",
|
|
Body: strings.NewReader(`{
|
|
"total+":2,
|
|
"total-":1
|
|
}`),
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"la4y2w4o98acwuj"`,
|
|
`"collectionName":"demo5"`,
|
|
`"total":3`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
|
|
// auth records
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "auth record with invalid form data",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/users/records/bgs820n361vj1qd",
|
|
Body: strings.NewReader(`{
|
|
"password":"",
|
|
"passwordConfirm":"1234560",
|
|
"email":"invalid",
|
|
"verified":false
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"passwordConfirm":{`,
|
|
`"password":{`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
// record fields are not checked if the base auth form fields have errors
|
|
`"email":`,
|
|
"verified", // superusers are allowed to change the verified state
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "auth record with valid form data but invalid record fields",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/users/records/bgs820n361vj1qd",
|
|
Body: strings.NewReader(`{
|
|
"password":"1234567",
|
|
"passwordConfirm":"1234567",
|
|
"rel":"invalid"
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"rel":{"code":`,
|
|
`"password":{"code":`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelValidate": 1,
|
|
"OnModelAfterUpdateError": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordAfterUpdateError": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "try to change account managing fields by guest",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/nologin/records/phhq3wr65cap535",
|
|
Body: strings.NewReader(`{
|
|
"password":"12345678",
|
|
"passwordConfirm":"12345678",
|
|
"emailVisibility":true,
|
|
"verified":true
|
|
}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"verified":{"code":`,
|
|
`"oldPassword":{"code":`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"emailVisibility":{"code":`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "try to change account managing fields by auth record (owner)",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/users/records/4q1xlclmfloku33",
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
Body: strings.NewReader(`{
|
|
"password":"12345678",
|
|
"passwordConfirm":"12345678",
|
|
"emailVisibility":true,
|
|
"verified":true
|
|
}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"verified":{"code":`,
|
|
`"oldPassword":{"code":`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"emailVisibility":{"code":`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "try to unset/downgrade email and verified fields (owner)",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/users/records/oap640cot4yru2s",
|
|
Headers: map[string]string{
|
|
// users, test2@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6Im9hcDY0MGNvdDR5cnUycyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.GfJo6EHIobgas_AXt-M-tj5IoQendPnrkMSe9ExuSEY",
|
|
},
|
|
Body: strings.NewReader(`{
|
|
"email":"",
|
|
"verified":false
|
|
}`),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"email":{"code":`,
|
|
`"verified":{"code":`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "try to change account managing fields by auth record with managing rights",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/nologin/records/phhq3wr65cap535",
|
|
Body: strings.NewReader(`{
|
|
"email":"new@example.com",
|
|
"password":"12345678",
|
|
"passwordConfirm":"12345678",
|
|
"name":"test_name",
|
|
"emailVisibility":true,
|
|
"verified":true
|
|
}`),
|
|
Headers: map[string]string{
|
|
// users, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"email":"new@example.com"`,
|
|
`"name":"test_name"`,
|
|
`"emailVisibility":true`,
|
|
`"verified":true`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
`"passwordConfirm"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
record, _ := app.FindRecordById("nologin", "phhq3wr65cap535")
|
|
if !record.ValidatePassword("12345678") {
|
|
t.Fatal("Password update failed.")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "update auth record with valid data by superuser",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/users/records/oap640cot4yru2s",
|
|
Body: strings.NewReader(`{
|
|
"username":"test.valid",
|
|
"email":"new@example.com",
|
|
"password":"12345678",
|
|
"passwordConfirm":"12345678",
|
|
"rel":"achvryl401bhse3",
|
|
"emailVisibility":true,
|
|
"verified":false
|
|
}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"username":"test.valid"`,
|
|
`"email":"new@example.com"`,
|
|
`"rel":"achvryl401bhse3"`,
|
|
`"emailVisibility":true`,
|
|
`"verified":false`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
record, _ := app.FindRecordById("users", "oap640cot4yru2s")
|
|
if !record.ValidatePassword("12345678") {
|
|
t.Fatal("Password update failed.")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "update auth record with valid data by guest (empty update filter + auth origins check)",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/nologin/records/dc49k6jgejn40h3",
|
|
Body: strings.NewReader(`{
|
|
"username":"test_new",
|
|
"emailVisibility":true,
|
|
"name":"test"
|
|
}`),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
nologin, err := app.FindCollectionByNameOrId("nologin")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// add dummy auth origins for the record
|
|
for i := 0; i < 3; i++ {
|
|
d := core.NewAuthOrigin(app)
|
|
d.SetCollectionRef(nologin.Id)
|
|
d.SetRecordRef("dc49k6jgejn40h3")
|
|
d.SetFingerprint("abc_" + strconv.Itoa(i))
|
|
if err = app.Save(d); err != nil {
|
|
t.Fatalf("Failed to save dummy auth origin %d: %v", i, err)
|
|
}
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"username":"test_new"`,
|
|
`"email":"test@example.com"`, // the email should be visible since we updated the emailVisibility
|
|
`"emailVisibility":true`,
|
|
`"verified":false`,
|
|
`"name":"test"`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
`"passwordConfirm"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
record, _ := app.FindRecordById("nologin", "dc49k6jgejn40h3")
|
|
|
|
// the dummy auth origins should NOT have been removed since we didn't change the password
|
|
devices, err := app.FindAllAuthOriginsByRecord(record)
|
|
if err != nil {
|
|
t.Fatalf("Failed to retrieve dummy auth origins: %v", err)
|
|
}
|
|
if len(devices) != 3 {
|
|
t.Fatalf("Expected %d auth origins, got %d", 3, len(devices))
|
|
}
|
|
},
|
|
},
|
|
{
|
|
Name: "success password change with oldPassword (+authOrigins reset check)",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/nologin/records/dc49k6jgejn40h3",
|
|
Body: strings.NewReader(`{
|
|
"password":"123456789",
|
|
"passwordConfirm":"123456789",
|
|
"oldPassword":"1234567890"
|
|
}`),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
nologin, err := app.FindCollectionByNameOrId("nologin")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// add dummy auth origins for the record
|
|
for i := 0; i < 3; i++ {
|
|
d := core.NewAuthOrigin(app)
|
|
d.SetCollectionRef(nologin.Id)
|
|
d.SetRecordRef("dc49k6jgejn40h3")
|
|
d.SetFingerprint("abc_" + strconv.Itoa(i))
|
|
if err = app.Save(d); err != nil {
|
|
t.Fatalf("Failed to save dummy auth origin %d: %v", i, err)
|
|
}
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"dc49k6jgejn40h3"`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"tokenKey"`,
|
|
`"password"`,
|
|
`"passwordConfirm"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
// auth origins
|
|
"OnModelDelete": 3,
|
|
"OnModelDeleteExecute": 3,
|
|
"OnModelAfterDeleteSuccess": 3,
|
|
"OnRecordDelete": 3,
|
|
"OnRecordDeleteExecute": 3,
|
|
"OnRecordAfterDeleteSuccess": 3,
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
record, _ := app.FindRecordById("nologin", "dc49k6jgejn40h3")
|
|
if !record.ValidatePassword("123456789") {
|
|
t.Fatal("Password update failed.")
|
|
}
|
|
|
|
// the dummy auth origins should have been removed
|
|
devices, err := app.FindAllAuthOriginsByRecord(record)
|
|
if err != nil {
|
|
t.Fatalf("Failed to retrieve dummy auth origins: %v", err)
|
|
}
|
|
if len(devices) > 0 {
|
|
t.Fatalf("Expected auth origins to be removed, got %d", len(devices))
|
|
}
|
|
},
|
|
},
|
|
|
|
// ensure that hidden fields cannot be set by non-superusers
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "update with hidden field as regular user",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo3/records/1tmknxy2868d869",
|
|
Body: strings.NewReader(`{
|
|
"title": "test_update"
|
|
}`),
|
|
Headers: map[string]string{
|
|
// clients, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6ImdrMzkwcWVnczR5NDd3biIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoidjg1MXE0cjc5MHJoa25sIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.0ONnm_BsvPRZyDNT31GN1CKUB6uQRxvVvQ-Wc9AZfG0",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
col, err := app.FindCollectionByNameOrId("demo3")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// mock hidden field
|
|
col.Fields.GetByName("title").SetHidden(true)
|
|
|
|
if err = app.Save(col); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
record, err := app.FindRecordById("demo3", "1tmknxy2868d869")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// ensure that the title wasn't saved
|
|
if v := record.GetString("title"); v != "test1" {
|
|
t.Fatalf("Expected no title change, got %q", v)
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"1tmknxy2868d869"`,
|
|
},
|
|
NotExpectedContent: []string{
|
|
`"title"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "update with hidden field as superuser",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo3/records/1tmknxy2868d869",
|
|
Body: strings.NewReader(`{
|
|
"title": "test_update"
|
|
}`),
|
|
Headers: map[string]string{
|
|
// superusers, test@example.com
|
|
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoicGJjXzMxNDI2MzU4MjMiLCJleHAiOjI1MjQ2MDQ0NjEsInJlZnJlc2hhYmxlIjp0cnVlfQ.UXgO3j-0BumcugrFjbd7j0M4MQvbrLggLlcu_YNGjoY",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
col, err := app.FindCollectionByNameOrId("demo3")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// mock hidden field
|
|
col.Fields.GetByName("title").SetHidden(true)
|
|
|
|
if err = app.Save(col); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
record, err := app.FindRecordById("demo3", "1tmknxy2868d869")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// ensure that the title has been updated
|
|
if v := record.GetString("title"); v != "test_update" {
|
|
t.Fatalf("Expected title %q, got %q", "test_update", v)
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"id":"1tmknxy2868d869"`,
|
|
`"title":"test_update"`,
|
|
},
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnRecordUpdateRequest": 1,
|
|
"OnModelUpdate": 1,
|
|
"OnModelUpdateExecute": 1,
|
|
"OnModelAfterUpdateSuccess": 1,
|
|
"OnRecordUpdate": 1,
|
|
"OnRecordUpdateExecute": 1,
|
|
"OnRecordAfterUpdateSuccess": 1,
|
|
"OnModelValidate": 1,
|
|
"OnRecordValidate": 1,
|
|
"OnRecordEnrich": 1,
|
|
},
|
|
},
|
|
|
|
// rate limit checks
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "RateLimit rule - demo2:update",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo2/records/0yxhwia2amd8gec",
|
|
Body: strings.NewReader(`{"title":"new"}`),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Settings().RateLimits.Enabled = true
|
|
app.Settings().RateLimits.Rules = []core.RateLimitRule{
|
|
{MaxRequests: 100, Label: "abc"},
|
|
{MaxRequests: 100, Label: "*:update"},
|
|
{MaxRequests: 0, Label: "demo2:update"},
|
|
}
|
|
},
|
|
ExpectedStatus: 429,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "RateLimit rule - *:update",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo2/records/0yxhwia2amd8gec",
|
|
Body: strings.NewReader(`{"title":"new"}`),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Settings().RateLimits.Enabled = true
|
|
app.Settings().RateLimits.Rules = []core.RateLimitRule{
|
|
{MaxRequests: 100, Label: "abc"},
|
|
{MaxRequests: 0, Label: "*:update"},
|
|
}
|
|
},
|
|
ExpectedStatus: 429,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
|
|
// dynamic body limit checks
|
|
// -----------------------------------------------------------
|
|
{
|
|
Name: "body > collection BodyLimit",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo1/records/imy661ixudk5izi",
|
|
// the exact body doesn't matter as long as it returns 413
|
|
Body: bytes.NewReader(make([]byte, apis.DefaultMaxBodySize+5+20+2+1)),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
collection, err := app.FindCollectionByNameOrId("demo1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// adjust field sizes for the test
|
|
// ---
|
|
fileOneField := collection.Fields.GetByName("file_one").(*core.FileField)
|
|
fileOneField.MaxSize = 5
|
|
|
|
fileManyField := collection.Fields.GetByName("file_many").(*core.FileField)
|
|
fileManyField.MaxSize = 10
|
|
fileManyField.MaxSelect = 2
|
|
|
|
jsonField := collection.Fields.GetByName("json").(*core.JSONField)
|
|
jsonField.MaxSize = 2
|
|
|
|
err = app.Save(collection)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 413,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "body <= collection BodyLimit",
|
|
Method: http.MethodPatch,
|
|
URL: "/api/collections/demo1/records/imy661ixudk5izi",
|
|
// the exact body doesn't matter as long as it doesn't return 413
|
|
Body: bytes.NewReader(make([]byte, apis.DefaultMaxBodySize+5+20+2)),
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
collection, err := app.FindCollectionByNameOrId("demo1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// adjust field sizes for the test
|
|
// ---
|
|
fileOneField := collection.Fields.GetByName("file_one").(*core.FileField)
|
|
fileOneField.MaxSize = 5
|
|
|
|
fileManyField := collection.Fields.GetByName("file_many").(*core.FileField)
|
|
fileManyField.MaxSize = 10
|
|
fileManyField.MaxSelect = 2
|
|
|
|
jsonField := collection.Fields.GetByName("json").(*core.JSONField)
|
|
jsonField.MaxSize = 2
|
|
|
|
err = app.Save(collection)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|