mirror of
https://github.com/pocketbase/pocketbase.git
synced 2024-12-01 11:01:04 +02:00
1087 lines
28 KiB
Go
1087 lines
28 KiB
Go
package daos_test
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pocketbase/dbx"
|
|
"github.com/pocketbase/pocketbase/daos"
|
|
"github.com/pocketbase/pocketbase/models"
|
|
"github.com/pocketbase/pocketbase/models/schema"
|
|
"github.com/pocketbase/pocketbase/tests"
|
|
"github.com/pocketbase/pocketbase/tools/list"
|
|
"github.com/pocketbase/pocketbase/tools/types"
|
|
)
|
|
|
|
func TestRecordQuery(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
collection, err := app.Dao().FindCollectionByNameOrId("demo1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expected := fmt.Sprintf("SELECT `%s`.* FROM `%s`", collection.Name, collection.Name)
|
|
|
|
sql := app.Dao().RecordQuery(collection).Build().SQL()
|
|
if sql != expected {
|
|
t.Errorf("Expected sql %s, got %s", expected, sql)
|
|
}
|
|
}
|
|
|
|
func TestRecordQueryOneWithRecord(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
collection, err := app.Dao().FindCollectionByNameOrId("demo1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
id := "84nmscqy84lsi1t"
|
|
|
|
q := app.Dao().RecordQuery(collection).
|
|
Where(dbx.HashExp{"id": id})
|
|
|
|
record := &models.Record{}
|
|
if err := q.One(record); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if record.GetString("id") != id {
|
|
t.Fatalf("Expected record with id %q, got %q", id, record.GetString("id"))
|
|
}
|
|
}
|
|
|
|
func TestRecordQueryAllWithRecordsSlices(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
collection, err := app.Dao().FindCollectionByNameOrId("demo1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
id1 := "84nmscqy84lsi1t"
|
|
id2 := "al1h9ijdeojtsjy"
|
|
|
|
{
|
|
records := []models.Record{}
|
|
|
|
q := app.Dao().RecordQuery(collection).
|
|
Where(dbx.HashExp{"id": []any{id1, id2}}).
|
|
OrderBy("created asc")
|
|
|
|
if err := q.All(&records); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(records) != 2 {
|
|
t.Fatalf("Expected %d records, got %d", 2, len(records))
|
|
}
|
|
|
|
if records[0].Id != id1 {
|
|
t.Fatalf("Expected record with id %q, got %q", id1, records[0].Id)
|
|
}
|
|
|
|
if records[1].Id != id2 {
|
|
t.Fatalf("Expected record with id %q, got %q", id2, records[1].Id)
|
|
}
|
|
}
|
|
|
|
{
|
|
records := []*models.Record{}
|
|
|
|
q := app.Dao().RecordQuery(collection).
|
|
Where(dbx.HashExp{"id": []any{id1, id2}}).
|
|
OrderBy("created asc")
|
|
|
|
if err := q.All(&records); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(records) != 2 {
|
|
t.Fatalf("Expected %d records, got %d", 2, len(records))
|
|
}
|
|
|
|
if records[0].Id != id1 {
|
|
t.Fatalf("Expected record with id %q, got %q", id1, records[0].Id)
|
|
}
|
|
|
|
if records[1].Id != id2 {
|
|
t.Fatalf("Expected record with id %q, got %q", id2, records[1].Id)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindRecordById(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
scenarios := []struct {
|
|
collectionIdOrName string
|
|
id string
|
|
filter1 func(q *dbx.SelectQuery) error
|
|
filter2 func(q *dbx.SelectQuery) error
|
|
expectError bool
|
|
}{
|
|
{"demo2", "missing", nil, nil, true},
|
|
{"missing", "0yxhwia2amd8gec", nil, nil, true},
|
|
{"demo2", "0yxhwia2amd8gec", nil, nil, false},
|
|
{"demo2", "0yxhwia2amd8gec", func(q *dbx.SelectQuery) error {
|
|
q.AndWhere(dbx.HashExp{"title": "missing"})
|
|
return nil
|
|
}, nil, true},
|
|
{"demo2", "0yxhwia2amd8gec", func(q *dbx.SelectQuery) error {
|
|
return errors.New("test error")
|
|
}, nil, true},
|
|
{"demo2", "0yxhwia2amd8gec", func(q *dbx.SelectQuery) error {
|
|
q.AndWhere(dbx.HashExp{"title": "test3"})
|
|
return nil
|
|
}, nil, false},
|
|
{"demo2", "0yxhwia2amd8gec", func(q *dbx.SelectQuery) error {
|
|
q.AndWhere(dbx.HashExp{"title": "test3"})
|
|
return nil
|
|
}, func(q *dbx.SelectQuery) error {
|
|
q.AndWhere(dbx.HashExp{"active": false})
|
|
return nil
|
|
}, true},
|
|
{"sz5l5z67tg7gku0", "0yxhwia2amd8gec", func(q *dbx.SelectQuery) error {
|
|
q.AndWhere(dbx.HashExp{"title": "test3"})
|
|
return nil
|
|
}, func(q *dbx.SelectQuery) error {
|
|
q.AndWhere(dbx.HashExp{"active": true})
|
|
return nil
|
|
}, false},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
record, err := app.Dao().FindRecordById(
|
|
scenario.collectionIdOrName,
|
|
scenario.id,
|
|
scenario.filter1,
|
|
scenario.filter2,
|
|
)
|
|
|
|
hasErr := err != nil
|
|
if hasErr != scenario.expectError {
|
|
t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
|
|
}
|
|
|
|
if record != nil && record.Id != scenario.id {
|
|
t.Errorf("(%d) Expected record with id %s, got %s", i, scenario.id, record.Id)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindRecordsByIds(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
scenarios := []struct {
|
|
collectionIdOrName string
|
|
ids []string
|
|
filter1 func(q *dbx.SelectQuery) error
|
|
filter2 func(q *dbx.SelectQuery) error
|
|
expectTotal int
|
|
expectError bool
|
|
}{
|
|
{"demo2", []string{}, nil, nil, 0, false},
|
|
{"demo2", []string{""}, nil, nil, 0, false},
|
|
{"demo2", []string{"missing"}, nil, nil, 0, false},
|
|
{"missing", []string{"0yxhwia2amd8gec"}, nil, nil, 0, true},
|
|
{"demo2", []string{"0yxhwia2amd8gec"}, nil, nil, 1, false},
|
|
{"sz5l5z67tg7gku0", []string{"0yxhwia2amd8gec"}, nil, nil, 1, false},
|
|
{
|
|
"demo2",
|
|
[]string{"0yxhwia2amd8gec", "llvuca81nly1qls"},
|
|
nil,
|
|
nil,
|
|
2,
|
|
false,
|
|
},
|
|
{
|
|
"demo2",
|
|
[]string{"0yxhwia2amd8gec", "llvuca81nly1qls"},
|
|
func(q *dbx.SelectQuery) error {
|
|
return nil // empty filter
|
|
},
|
|
func(q *dbx.SelectQuery) error {
|
|
return errors.New("test error")
|
|
},
|
|
0,
|
|
true,
|
|
},
|
|
{
|
|
"demo2",
|
|
[]string{"0yxhwia2amd8gec", "llvuca81nly1qls"},
|
|
func(q *dbx.SelectQuery) error {
|
|
q.AndWhere(dbx.HashExp{"active": true})
|
|
return nil
|
|
},
|
|
nil,
|
|
1,
|
|
false,
|
|
},
|
|
{
|
|
"sz5l5z67tg7gku0",
|
|
[]string{"0yxhwia2amd8gec", "llvuca81nly1qls"},
|
|
func(q *dbx.SelectQuery) error {
|
|
q.AndWhere(dbx.HashExp{"active": true})
|
|
return nil
|
|
},
|
|
func(q *dbx.SelectQuery) error {
|
|
q.AndWhere(dbx.Not(dbx.HashExp{"title": ""}))
|
|
return nil
|
|
},
|
|
1,
|
|
false,
|
|
},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
records, err := app.Dao().FindRecordsByIds(
|
|
scenario.collectionIdOrName,
|
|
scenario.ids,
|
|
scenario.filter1,
|
|
scenario.filter2,
|
|
)
|
|
|
|
hasErr := err != nil
|
|
if hasErr != scenario.expectError {
|
|
t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
|
|
}
|
|
|
|
if len(records) != scenario.expectTotal {
|
|
t.Errorf("(%d) Expected %d records, got %d", i, scenario.expectTotal, len(records))
|
|
continue
|
|
}
|
|
|
|
for _, r := range records {
|
|
if !list.ExistInSlice(r.Id, scenario.ids) {
|
|
t.Errorf("(%d) Couldn't find id %s in %v", i, r.Id, scenario.ids)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindRecordsByExpr(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
scenarios := []struct {
|
|
collectionIdOrName string
|
|
expressions []dbx.Expression
|
|
expectIds []string
|
|
expectError bool
|
|
}{
|
|
{
|
|
"missing",
|
|
nil,
|
|
[]string{},
|
|
true,
|
|
},
|
|
{
|
|
"demo2",
|
|
nil,
|
|
[]string{
|
|
"achvryl401bhse3",
|
|
"llvuca81nly1qls",
|
|
"0yxhwia2amd8gec",
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"demo2",
|
|
[]dbx.Expression{
|
|
nil,
|
|
dbx.HashExp{"id": "123"},
|
|
},
|
|
[]string{},
|
|
false,
|
|
},
|
|
{
|
|
"sz5l5z67tg7gku0",
|
|
[]dbx.Expression{
|
|
dbx.Like("title", "test").Match(true, true),
|
|
dbx.HashExp{"active": true},
|
|
},
|
|
[]string{
|
|
"achvryl401bhse3",
|
|
"0yxhwia2amd8gec",
|
|
},
|
|
false,
|
|
},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
records, err := app.Dao().FindRecordsByExpr(scenario.collectionIdOrName, scenario.expressions...)
|
|
|
|
hasErr := err != nil
|
|
if hasErr != scenario.expectError {
|
|
t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
|
|
}
|
|
|
|
if len(records) != len(scenario.expectIds) {
|
|
t.Errorf("(%d) Expected %d records, got %d", i, len(scenario.expectIds), len(records))
|
|
continue
|
|
}
|
|
|
|
for _, r := range records {
|
|
if !list.ExistInSlice(r.Id, scenario.expectIds) {
|
|
t.Errorf("(%d) Couldn't find id %s in %v", i, r.Id, scenario.expectIds)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindFirstRecordByData(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
scenarios := []struct {
|
|
collectionIdOrName string
|
|
key string
|
|
value any
|
|
expectId string
|
|
expectError bool
|
|
}{
|
|
{
|
|
"missing",
|
|
"id",
|
|
"llvuca81nly1qls",
|
|
"llvuca81nly1qls",
|
|
true,
|
|
},
|
|
{
|
|
"demo2",
|
|
"",
|
|
"llvuca81nly1qls",
|
|
"",
|
|
true,
|
|
},
|
|
{
|
|
"demo2",
|
|
"id",
|
|
"invalid",
|
|
"",
|
|
true,
|
|
},
|
|
{
|
|
"demo2",
|
|
"id",
|
|
"llvuca81nly1qls",
|
|
"llvuca81nly1qls",
|
|
false,
|
|
},
|
|
{
|
|
"sz5l5z67tg7gku0",
|
|
"title",
|
|
"test3",
|
|
"0yxhwia2amd8gec",
|
|
false,
|
|
},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
record, err := app.Dao().FindFirstRecordByData(scenario.collectionIdOrName, scenario.key, scenario.value)
|
|
|
|
hasErr := err != nil
|
|
if hasErr != scenario.expectError {
|
|
t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
|
|
continue
|
|
}
|
|
|
|
if !scenario.expectError && record.Id != scenario.expectId {
|
|
t.Errorf("(%d) Expected record with id %s, got %v", i, scenario.expectId, record.Id)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindRecordsByFilter(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
scenarios := []struct {
|
|
name string
|
|
collectionIdOrName string
|
|
filter string
|
|
sort string
|
|
limit int
|
|
expectError bool
|
|
expectRecordIds []string
|
|
}{
|
|
{
|
|
"missing collection",
|
|
"missing",
|
|
"id != ''",
|
|
"",
|
|
0,
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"missing filter",
|
|
"demo2",
|
|
"",
|
|
"",
|
|
0,
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"invalid filter",
|
|
"demo2",
|
|
"someMissingField > 1",
|
|
"",
|
|
0,
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"simple filter",
|
|
"demo2",
|
|
"id != ''",
|
|
"",
|
|
0,
|
|
false,
|
|
[]string{
|
|
"llvuca81nly1qls",
|
|
"achvryl401bhse3",
|
|
"0yxhwia2amd8gec",
|
|
},
|
|
},
|
|
{
|
|
"multi-condition filter with sort",
|
|
"demo2",
|
|
"id != '' && active=true",
|
|
"-created,title",
|
|
-1, // should behave the same as 0
|
|
false,
|
|
[]string{
|
|
"0yxhwia2amd8gec",
|
|
"achvryl401bhse3",
|
|
},
|
|
},
|
|
{
|
|
"with limit",
|
|
"demo2",
|
|
"id != ''",
|
|
"title",
|
|
2,
|
|
false,
|
|
[]string{
|
|
"llvuca81nly1qls",
|
|
"achvryl401bhse3",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
records, err := app.Dao().FindRecordsByFilter(
|
|
s.collectionIdOrName,
|
|
s.filter,
|
|
s.sort,
|
|
s.limit,
|
|
)
|
|
|
|
hasErr := err != nil
|
|
if hasErr != s.expectError {
|
|
t.Errorf("[%s] Expected hasErr to be %v, got %v (%v)", s.name, s.expectError, hasErr, err)
|
|
continue
|
|
}
|
|
|
|
if hasErr {
|
|
continue
|
|
}
|
|
|
|
if len(records) != len(s.expectRecordIds) {
|
|
t.Errorf("[%s] Expected %d records, got %d", s.name, len(s.expectRecordIds), len(records))
|
|
continue
|
|
}
|
|
|
|
for i, id := range s.expectRecordIds {
|
|
if id != records[i].Id {
|
|
t.Errorf("[%s] Expected record with id %q, got %q at index %d", s.name, id, records[i].Id, i)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindFirstRecordByFilter(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
scenarios := []struct {
|
|
name string
|
|
collectionIdOrName string
|
|
filter string
|
|
expectError bool
|
|
expectRecordId string
|
|
}{
|
|
{
|
|
"missing collection",
|
|
"missing",
|
|
"id != ''",
|
|
true,
|
|
"",
|
|
},
|
|
{
|
|
"missing filter",
|
|
"demo2",
|
|
"",
|
|
true,
|
|
"",
|
|
},
|
|
{
|
|
"invalid filter",
|
|
"demo2",
|
|
"someMissingField > 1",
|
|
true,
|
|
"",
|
|
},
|
|
{
|
|
"valid filter but no matches",
|
|
"demo2",
|
|
"id = 'test'",
|
|
true,
|
|
"",
|
|
},
|
|
{
|
|
"valid filter and multiple matches",
|
|
"demo2",
|
|
"id != ''",
|
|
false,
|
|
"llvuca81nly1qls",
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
record, err := app.Dao().FindFirstRecordByFilter(s.collectionIdOrName, s.filter)
|
|
|
|
hasErr := err != nil
|
|
if hasErr != s.expectError {
|
|
t.Errorf("[%s] Expected hasErr to be %v, got %v (%v)", s.name, s.expectError, hasErr, err)
|
|
continue
|
|
}
|
|
|
|
if hasErr {
|
|
continue
|
|
}
|
|
|
|
if record.Id != s.expectRecordId {
|
|
t.Errorf("[%s] Expected record with id %q, got %q", s.name, s.expectRecordId, record.Id)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsRecordValueUnique(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
testManyRelsId1 := "bgs820n361vj1qd"
|
|
testManyRelsId2 := "4q1xlclmfloku33"
|
|
testManyRelsId3 := "oap640cot4yru2s"
|
|
|
|
scenarios := []struct {
|
|
collectionIdOrName string
|
|
key string
|
|
value any
|
|
excludeIds []string
|
|
expected bool
|
|
}{
|
|
{"demo2", "", "", nil, false},
|
|
{"demo2", "", "", []string{""}, false},
|
|
{"demo2", "missing", "unique", nil, false},
|
|
{"demo2", "title", "unique", nil, true},
|
|
{"demo2", "title", "unique", []string{}, true},
|
|
{"demo2", "title", "unique", []string{""}, true},
|
|
{"demo2", "title", "test1", []string{""}, false},
|
|
{"demo2", "title", "test1", []string{"llvuca81nly1qls"}, true},
|
|
{"demo1", "rel_many", []string{testManyRelsId3}, nil, false},
|
|
{"wsmn24bux7wo113", "rel_many", []any{testManyRelsId3}, []string{""}, false},
|
|
{"wsmn24bux7wo113", "rel_many", []any{testManyRelsId3}, []string{"84nmscqy84lsi1t"}, true},
|
|
// mixed json array order
|
|
{"demo1", "rel_many", []string{testManyRelsId1, testManyRelsId3, testManyRelsId2}, nil, true},
|
|
// username special case-insensitive match
|
|
{"users", "username", "test2_username", nil, false},
|
|
{"users", "username", "TEST2_USERNAME", nil, false},
|
|
{"users", "username", "new_username", nil, true},
|
|
{"users", "username", "TEST2_USERNAME", []string{"oap640cot4yru2s"}, true},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
result := app.Dao().IsRecordValueUnique(
|
|
scenario.collectionIdOrName,
|
|
scenario.key,
|
|
scenario.value,
|
|
scenario.excludeIds...,
|
|
)
|
|
|
|
if result != scenario.expected {
|
|
t.Errorf("(%d) Expected %v, got %v", i, scenario.expected, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindAuthRecordByToken(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
scenarios := []struct {
|
|
token string
|
|
baseKey string
|
|
expectedEmail string
|
|
expectError bool
|
|
}{
|
|
// invalid auth token
|
|
{
|
|
"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.H2KKcIXiAfxvuXMFzizo1SgsinDP4hcWhD3pYoP4Nqw",
|
|
app.Settings().RecordAuthToken.Secret,
|
|
"",
|
|
true,
|
|
},
|
|
// expired token
|
|
{
|
|
"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoxNjQwOTkxNjYxfQ.HqvpCpM0RAk3Qu9PfCMuZsk_DKh9UYuzFLwXBMTZd1w",
|
|
app.Settings().RecordAuthToken.Secret,
|
|
"",
|
|
true,
|
|
},
|
|
// wrong base key (password reset token secret instead of auth secret)
|
|
{
|
|
"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
|
|
app.Settings().RecordPasswordResetToken.Secret,
|
|
"",
|
|
true,
|
|
},
|
|
// valid token and base key but with deleted/missing collection
|
|
{
|
|
"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoibWlzc2luZyIsImV4cCI6MjIwODk4NTI2MX0.0oEHQpdpHp0Nb3VN8La0ssg-SjwWKiRl_k1mUGxdKlU",
|
|
app.Settings().RecordAuthToken.Secret,
|
|
"test@example.com",
|
|
true,
|
|
},
|
|
// valid token
|
|
{
|
|
"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
|
|
app.Settings().RecordAuthToken.Secret,
|
|
"test@example.com",
|
|
false,
|
|
},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
record, err := app.Dao().FindAuthRecordByToken(scenario.token, scenario.baseKey)
|
|
|
|
hasErr := err != nil
|
|
if hasErr != scenario.expectError {
|
|
t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
|
|
continue
|
|
}
|
|
|
|
if !scenario.expectError && record.Email() != scenario.expectedEmail {
|
|
t.Errorf("(%d) Expected record model %s, got %s", i, scenario.expectedEmail, record.Email())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindAuthRecordByEmail(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
scenarios := []struct {
|
|
collectionIdOrName string
|
|
email string
|
|
expectError bool
|
|
}{
|
|
{"missing", "test@example.com", true},
|
|
{"demo2", "test@example.com", true},
|
|
{"users", "missing@example.com", true},
|
|
{"users", "test@example.com", false},
|
|
{"clients", "test2@example.com", false},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
record, err := app.Dao().FindAuthRecordByEmail(scenario.collectionIdOrName, scenario.email)
|
|
|
|
hasErr := err != nil
|
|
if hasErr != scenario.expectError {
|
|
t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
|
|
continue
|
|
}
|
|
|
|
if !scenario.expectError && record.Email() != scenario.email {
|
|
t.Errorf("(%d) Expected record with email %s, got %s", i, scenario.email, record.Email())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindAuthRecordByUsername(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
scenarios := []struct {
|
|
collectionIdOrName string
|
|
username string
|
|
expectError bool
|
|
}{
|
|
{"missing", "test_username", true},
|
|
{"demo2", "test_username", true},
|
|
{"users", "missing", true},
|
|
{"users", "test2_username", false},
|
|
{"users", "TEST2_USERNAME", false}, // case insensitive check
|
|
{"clients", "clients43362", false},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
record, err := app.Dao().FindAuthRecordByUsername(scenario.collectionIdOrName, scenario.username)
|
|
|
|
hasErr := err != nil
|
|
if hasErr != scenario.expectError {
|
|
t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
|
|
continue
|
|
}
|
|
|
|
if !scenario.expectError && !strings.EqualFold(record.Username(), scenario.username) {
|
|
t.Errorf("(%d) Expected record with username %s, got %s", i, scenario.username, record.Username())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSuggestUniqueAuthRecordUsername(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
scenarios := []struct {
|
|
collectionIdOrName string
|
|
baseUsername string
|
|
expectedPattern string
|
|
}{
|
|
// missing collection
|
|
{"missing", "test2_username", `^test2_username\d{12}$`},
|
|
// not an auth collection
|
|
{"demo2", "test2_username", `^test2_username\d{12}$`},
|
|
// auth collection with unique base username
|
|
{"users", "new_username", `^new_username$`},
|
|
{"users", "NEW_USERNAME", `^NEW_USERNAME$`},
|
|
// auth collection with existing username
|
|
{"users", "test2_username", `^test2_username\d{3}$`},
|
|
{"users", "TEST2_USERNAME", `^TEST2_USERNAME\d{3}$`},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
username := app.Dao().SuggestUniqueAuthRecordUsername(
|
|
scenario.collectionIdOrName,
|
|
scenario.baseUsername,
|
|
)
|
|
|
|
pattern, err := regexp.Compile(scenario.expectedPattern)
|
|
if err != nil {
|
|
t.Errorf("[%d] Invalid username pattern %q: %v", i, scenario.expectedPattern, err)
|
|
}
|
|
if !pattern.MatchString(username) {
|
|
t.Fatalf("Expected username to match %s, got username %s", scenario.expectedPattern, username)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSaveRecord(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
collection, _ := app.Dao().FindCollectionByNameOrId("demo2")
|
|
|
|
// create
|
|
// ---
|
|
r1 := models.NewRecord(collection)
|
|
r1.Set("title", "test_new")
|
|
err1 := app.Dao().SaveRecord(r1)
|
|
if err1 != nil {
|
|
t.Fatal(err1)
|
|
}
|
|
newR1, _ := app.Dao().FindFirstRecordByData(collection.Id, "title", "test_new")
|
|
if newR1 == nil || newR1.Id != r1.Id || newR1.GetString("title") != r1.GetString("title") {
|
|
t.Fatalf("Expected to find record %v, got %v", r1, newR1)
|
|
}
|
|
|
|
// update
|
|
// ---
|
|
r2, _ := app.Dao().FindFirstRecordByData(collection.Id, "id", "0yxhwia2amd8gec")
|
|
r2.Set("title", "test_update")
|
|
err2 := app.Dao().SaveRecord(r2)
|
|
if err2 != nil {
|
|
t.Fatal(err2)
|
|
}
|
|
newR2, _ := app.Dao().FindFirstRecordByData(collection.Id, "title", "test_update")
|
|
if newR2 == nil || newR2.Id != r2.Id || newR2.GetString("title") != r2.GetString("title") {
|
|
t.Fatalf("Expected to find record %v, got %v", r2, newR2)
|
|
}
|
|
}
|
|
|
|
func TestSaveRecordWithIdFromOtherCollection(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
baseCollection, _ := app.Dao().FindCollectionByNameOrId("demo2")
|
|
authCollection, _ := app.Dao().FindCollectionByNameOrId("nologin")
|
|
|
|
// base collection test
|
|
r1 := models.NewRecord(baseCollection)
|
|
r1.Set("title", "test_new")
|
|
r1.Set("id", "mk5fmymtx4wsprk") // existing id of demo3 record
|
|
r1.MarkAsNew()
|
|
if err := app.Dao().SaveRecord(r1); err != nil {
|
|
t.Fatalf("Expected nil, got error %v", err)
|
|
}
|
|
|
|
// auth collection test
|
|
r2 := models.NewRecord(authCollection)
|
|
r2.Set("username", "test_new")
|
|
r2.Set("id", "gk390qegs4y47wn") // existing id of "clients" record
|
|
r2.MarkAsNew()
|
|
if err := app.Dao().SaveRecord(r2); err == nil {
|
|
t.Fatal("Expected error, got nil")
|
|
}
|
|
|
|
// try again with unique id
|
|
r2.Set("id", "unique_id")
|
|
if err := app.Dao().SaveRecord(r2); err != nil {
|
|
t.Fatalf("Expected nil, got error %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDeleteRecord(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
demoCollection, _ := app.Dao().FindCollectionByNameOrId("demo2")
|
|
|
|
// delete unsaved record
|
|
// ---
|
|
rec0 := models.NewRecord(demoCollection)
|
|
if err := app.Dao().DeleteRecord(rec0); err == nil {
|
|
t.Fatal("(rec0) Didn't expect to succeed deleting unsaved record")
|
|
}
|
|
|
|
// delete existing record + external auths
|
|
// ---
|
|
rec1, _ := app.Dao().FindRecordById("users", "4q1xlclmfloku33")
|
|
if err := app.Dao().DeleteRecord(rec1); err != nil {
|
|
t.Fatalf("(rec1) Expected nil, got error %v", err)
|
|
}
|
|
// check if it was really deleted
|
|
if refreshed, _ := app.Dao().FindRecordById(rec1.Collection().Id, rec1.Id); refreshed != nil {
|
|
t.Fatalf("(rec1) Expected record to be deleted, got %v", refreshed)
|
|
}
|
|
// check if the external auths were deleted
|
|
if auths, _ := app.Dao().FindAllExternalAuthsByRecord(rec1); len(auths) > 0 {
|
|
t.Fatalf("(rec1) Expected external auths to be deleted, got %v", auths)
|
|
}
|
|
|
|
// delete existing record while being part of a non-cascade required relation
|
|
// ---
|
|
rec2, _ := app.Dao().FindRecordById("demo3", "7nwo8tuiatetxdm")
|
|
if err := app.Dao().DeleteRecord(rec2); err == nil {
|
|
t.Fatalf("(rec2) Expected error, got nil")
|
|
}
|
|
|
|
// delete existing record + cascade
|
|
// ---
|
|
calledQueries := []string{}
|
|
app.Dao().NonconcurrentDB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {
|
|
calledQueries = append(calledQueries, sql)
|
|
}
|
|
app.Dao().ConcurrentDB().(*dbx.DB).QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {
|
|
calledQueries = append(calledQueries, sql)
|
|
}
|
|
app.Dao().NonconcurrentDB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {
|
|
calledQueries = append(calledQueries, sql)
|
|
}
|
|
app.Dao().ConcurrentDB().(*dbx.DB).ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {
|
|
calledQueries = append(calledQueries, sql)
|
|
}
|
|
rec3, _ := app.Dao().FindRecordById("users", "oap640cot4yru2s")
|
|
// delete
|
|
if err := app.Dao().DeleteRecord(rec3); err != nil {
|
|
t.Fatalf("(rec3) Expected nil, got error %v", err)
|
|
}
|
|
// check if it was really deleted
|
|
rec3, _ = app.Dao().FindRecordById(rec3.Collection().Id, rec3.Id)
|
|
if rec3 != nil {
|
|
t.Fatalf("(rec3) Expected record to be deleted, got %v", rec3)
|
|
}
|
|
// check if the operation cascaded
|
|
rel, _ := app.Dao().FindRecordById("demo1", "84nmscqy84lsi1t")
|
|
if rel != nil {
|
|
t.Fatalf("(rec3) Expected the delete to cascade, found relation %v", rel)
|
|
}
|
|
// ensure that the json rel fields were prefixed
|
|
joinedQueries := strings.Join(calledQueries, " ")
|
|
expectedRelManyPart := "`demo1` INNER JOIN json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END)"
|
|
if !strings.Contains(joinedQueries, expectedRelManyPart) {
|
|
t.Fatalf("(rec3) Expected the cascade delete to call the query \n%v, got \n%v", expectedRelManyPart, calledQueries)
|
|
}
|
|
expectedRelOnePart := "SELECT DISTINCT `demo1`.* FROM `demo1` WHERE (`demo1`.`rel_one`="
|
|
if !strings.Contains(joinedQueries, expectedRelOnePart) {
|
|
t.Fatalf("(rec3) Expected the cascade delete to call the query \n%v, got \n%v", expectedRelOnePart, calledQueries)
|
|
}
|
|
}
|
|
|
|
func TestDeleteRecordBatchProcessing(t *testing.T) {
|
|
app, _ := tests.NewTestApp()
|
|
defer app.Cleanup()
|
|
|
|
if err := createMockBatchProcessingData(app.Dao()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// find and delete the first c1 record to trigger cascade
|
|
mainRecord, _ := app.Dao().FindRecordById("c1", "a")
|
|
if err := app.Dao().DeleteRecord(mainRecord); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// check if the main record was deleted
|
|
_, err := app.Dao().FindRecordById(mainRecord.Collection().Id, mainRecord.Id)
|
|
if err == nil {
|
|
t.Fatal("The main record wasn't deleted")
|
|
}
|
|
|
|
// check if the c1 b rel field were updated
|
|
c1RecordB, err := app.Dao().FindRecordById("c1", "b")
|
|
if err != nil || c1RecordB.GetString("rel") != "" {
|
|
t.Fatalf("Expected c1RecordB.rel to be nil, got %v", c1RecordB.GetString("rel"))
|
|
}
|
|
|
|
// check if the c2 rel fields were updated
|
|
c2Records, err := app.Dao().FindRecordsByExpr("c2", nil)
|
|
if err != nil || len(c2Records) == 0 {
|
|
t.Fatalf("Failed to fetch c2 records: %v", err)
|
|
}
|
|
for _, r := range c2Records {
|
|
ids := r.GetStringSlice("rel")
|
|
if len(ids) != 1 || ids[0] != "b" {
|
|
t.Fatalf("Expected only 'b' rel id, got %v", ids)
|
|
}
|
|
}
|
|
|
|
// check if all c3 relations were deleted
|
|
c3Records, err := app.Dao().FindRecordsByExpr("c3", nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to fetch c3 records: %v", err)
|
|
}
|
|
if total := len(c3Records); total != 0 {
|
|
t.Fatalf("Expected c3 records to be deleted, found %d", total)
|
|
}
|
|
}
|
|
|
|
func createMockBatchProcessingData(dao *daos.Dao) error {
|
|
// create mock collection without relation
|
|
c1 := &models.Collection{}
|
|
c1.Id = "c1"
|
|
c1.Name = c1.Id
|
|
c1.Schema = schema.NewSchema(
|
|
&schema.SchemaField{
|
|
Name: "text",
|
|
Type: schema.FieldTypeText,
|
|
},
|
|
// self reference
|
|
&schema.SchemaField{
|
|
Name: "rel",
|
|
Type: schema.FieldTypeRelation,
|
|
Options: &schema.RelationOptions{
|
|
MaxSelect: types.Pointer(1),
|
|
CollectionId: "c1",
|
|
CascadeDelete: false, // should unset all rel fields
|
|
},
|
|
},
|
|
)
|
|
if err := dao.SaveCollection(c1); err != nil {
|
|
return err
|
|
}
|
|
|
|
// create mock collection with a multi-rel field
|
|
c2 := &models.Collection{}
|
|
c2.Id = "c2"
|
|
c2.Name = c2.Id
|
|
c2.Schema = schema.NewSchema(
|
|
&schema.SchemaField{
|
|
Name: "rel",
|
|
Type: schema.FieldTypeRelation,
|
|
Options: &schema.RelationOptions{
|
|
MaxSelect: types.Pointer(10),
|
|
CollectionId: "c1",
|
|
CascadeDelete: false, // should unset all rel fields
|
|
},
|
|
},
|
|
)
|
|
if err := dao.SaveCollection(c2); err != nil {
|
|
return err
|
|
}
|
|
|
|
// create mock collection with a single-rel field
|
|
c3 := &models.Collection{}
|
|
c3.Id = "c3"
|
|
c3.Name = c3.Id
|
|
c3.Schema = schema.NewSchema(
|
|
&schema.SchemaField{
|
|
Name: "rel",
|
|
Type: schema.FieldTypeRelation,
|
|
Options: &schema.RelationOptions{
|
|
MaxSelect: types.Pointer(1),
|
|
CollectionId: "c1",
|
|
CascadeDelete: true, // should delete all c3 records
|
|
},
|
|
},
|
|
)
|
|
if err := dao.SaveCollection(c3); err != nil {
|
|
return err
|
|
}
|
|
|
|
// insert mock records
|
|
c1RecordA := models.NewRecord(c1)
|
|
c1RecordA.Id = "a"
|
|
c1RecordA.Set("rel", c1RecordA.Id) // self reference
|
|
if err := dao.Save(c1RecordA); err != nil {
|
|
return err
|
|
}
|
|
c1RecordB := models.NewRecord(c1)
|
|
c1RecordB.Id = "b"
|
|
c1RecordB.Set("rel", c1RecordA.Id) // rel to another record from the same collection
|
|
if err := dao.Save(c1RecordB); err != nil {
|
|
return err
|
|
}
|
|
for i := 0; i < 4500; i++ {
|
|
c2Record := models.NewRecord(c2)
|
|
c2Record.Set("rel", []string{c1RecordA.Id, c1RecordB.Id})
|
|
if err := dao.Save(c2Record); err != nil {
|
|
return err
|
|
}
|
|
|
|
c3Record := models.NewRecord(c3)
|
|
c3Record.Set("rel", c1RecordA.Id)
|
|
if err := dao.Save(c3Record); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// set the same id as the relation for at least 1 record
|
|
// to check whether the correct condition will be added
|
|
c3Record := models.NewRecord(c3)
|
|
c3Record.Set("rel", c1RecordA.Id)
|
|
c3Record.Id = c1RecordA.Id
|
|
if err := dao.Save(c3Record); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|