2022-07-07 00:19:05 +03:00
package daos_test
import (
2022-12-09 19:09:43 +02:00
"context"
"database/sql"
2022-07-07 00:19:05 +03:00
"errors"
"fmt"
2022-10-30 10:28:14 +02:00
"regexp"
"strings"
2022-07-07 00:19:05 +03:00
"testing"
2022-12-09 19:09:43 +02:00
"time"
2022-07-07 00:19:05 +03:00
"github.com/pocketbase/dbx"
2022-12-12 19:19:31 +02:00
"github.com/pocketbase/pocketbase/daos"
2022-07-07 00:19:05 +03:00
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/models/schema"
"github.com/pocketbase/pocketbase/tests"
"github.com/pocketbase/pocketbase/tools/list"
2022-12-12 19:19:31 +02:00
"github.com/pocketbase/pocketbase/tools/types"
2022-07-07 00:19:05 +03:00
)
func TestRecordQuery ( t * testing . T ) {
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
2022-10-30 10:28:14 +02:00
collection , err := app . Dao ( ) . FindCollectionByNameOrId ( "demo1" )
if err != nil {
t . Fatal ( err )
}
2022-07-07 00:19:05 +03:00
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 TestFindRecordById ( t * testing . T ) {
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
scenarios := [ ] struct {
2022-10-30 10:28:14 +02:00
collectionIdOrName string
id string
filter1 func ( q * dbx . SelectQuery ) error
filter2 func ( q * dbx . SelectQuery ) error
expectError bool
2022-07-07 00:19:05 +03:00
} {
2022-10-30 10:28:14 +02:00
{ "demo2" , "missing" , nil , nil , true } ,
{ "missing" , "0yxhwia2amd8gec" , nil , nil , true } ,
{ "demo2" , "0yxhwia2amd8gec" , nil , nil , false } ,
{ "demo2" , "0yxhwia2amd8gec" , func ( q * dbx . SelectQuery ) error {
2022-07-07 00:19:05 +03:00
q . AndWhere ( dbx . HashExp { "title" : "missing" } )
return nil
2022-10-30 10:28:14 +02:00
} , nil , true } ,
{ "demo2" , "0yxhwia2amd8gec" , func ( q * dbx . SelectQuery ) error {
2022-07-07 00:19:05 +03:00
return errors . New ( "test error" )
2022-10-30 10:28:14 +02:00
} , 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
2022-07-07 00:19:05 +03:00
} , true } ,
2022-10-30 10:28:14 +02:00
{ "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 } )
2022-07-07 00:19:05 +03:00
return nil
} , false } ,
}
for i , scenario := range scenarios {
2022-10-30 10:28:14 +02:00
record , err := app . Dao ( ) . FindRecordById (
scenario . collectionIdOrName ,
scenario . id ,
scenario . filter1 ,
scenario . filter2 ,
)
2022-07-07 00:19:05 +03:00
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 {
2022-10-30 10:28:14 +02:00
collectionIdOrName string
ids [ ] string
filter1 func ( q * dbx . SelectQuery ) error
filter2 func ( q * dbx . SelectQuery ) error
expectTotal int
expectError bool
2022-07-07 00:19:05 +03:00
} {
2022-10-30 10:28:14 +02:00
{ "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 } ,
2022-07-07 00:19:05 +03:00
{
2022-10-30 10:28:14 +02:00
"demo2" ,
[ ] string { "0yxhwia2amd8gec" , "llvuca81nly1qls" } ,
nil ,
2022-07-07 00:19:05 +03:00
nil ,
2 ,
false ,
} ,
{
2022-10-30 10:28:14 +02:00
"demo2" ,
[ ] string { "0yxhwia2amd8gec" , "llvuca81nly1qls" } ,
func ( q * dbx . SelectQuery ) error {
return nil // empty filter
} ,
2022-07-07 00:19:05 +03:00
func ( q * dbx . SelectQuery ) error {
return errors . New ( "test error" )
} ,
0 ,
true ,
} ,
{
2022-10-30 10:28:14 +02:00
"demo2" ,
[ ] string { "0yxhwia2amd8gec" , "llvuca81nly1qls" } ,
2022-07-07 00:19:05 +03:00
func ( q * dbx . SelectQuery ) error {
2022-10-30 10:28:14 +02:00
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" : "" } ) )
2022-07-07 00:19:05 +03:00
return nil
} ,
1 ,
false ,
} ,
}
for i , scenario := range scenarios {
2022-10-30 10:28:14 +02:00
records , err := app . Dao ( ) . FindRecordsByIds (
scenario . collectionIdOrName ,
scenario . ids ,
scenario . filter1 ,
scenario . filter2 ,
)
2022-07-07 00:19:05 +03:00
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 {
2022-10-30 10:28:14 +02:00
collectionIdOrName string
expressions [ ] dbx . Expression
expectIds [ ] string
expectError bool
2022-07-07 00:19:05 +03:00
} {
{
2022-10-30 10:28:14 +02:00
"missing" ,
2022-07-07 00:19:05 +03:00
nil ,
[ ] string { } ,
true ,
} ,
{
2022-10-30 10:28:14 +02:00
"demo2" ,
nil ,
[ ] string {
"achvryl401bhse3" ,
"llvuca81nly1qls" ,
"0yxhwia2amd8gec" ,
} ,
false ,
} ,
{
"demo2" ,
[ ] dbx . Expression {
nil ,
dbx . HashExp { "id" : "123" } ,
} ,
2022-07-07 00:19:05 +03:00
[ ] string { } ,
false ,
} ,
{
2022-10-30 10:28:14 +02:00
"sz5l5z67tg7gku0" ,
[ ] dbx . Expression {
dbx . Like ( "title" , "test" ) . Match ( true , true ) ,
dbx . HashExp { "active" : true } ,
} ,
2022-07-07 00:19:05 +03:00
[ ] string {
2022-10-30 10:28:14 +02:00
"achvryl401bhse3" ,
"0yxhwia2amd8gec" ,
2022-07-07 00:19:05 +03:00
} ,
false ,
} ,
}
for i , scenario := range scenarios {
2022-10-30 10:28:14 +02:00
records , err := app . Dao ( ) . FindRecordsByExpr ( scenario . collectionIdOrName , scenario . expressions ... )
2022-07-07 00:19:05 +03:00
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 {
2022-10-30 10:28:14 +02:00
collectionIdOrName string
key string
value any
expectId string
expectError bool
2022-07-07 00:19:05 +03:00
} {
{
2022-10-30 10:28:14 +02:00
"missing" ,
"id" ,
"llvuca81nly1qls" ,
"llvuca81nly1qls" ,
true ,
} ,
{
"demo2" ,
2022-07-07 00:19:05 +03:00
"" ,
2022-10-30 10:28:14 +02:00
"llvuca81nly1qls" ,
2022-07-07 00:19:05 +03:00
"" ,
true ,
} ,
{
2022-10-30 10:28:14 +02:00
"demo2" ,
2022-07-07 00:19:05 +03:00
"id" ,
"invalid" ,
"" ,
true ,
} ,
{
2022-10-30 10:28:14 +02:00
"demo2" ,
2022-07-07 00:19:05 +03:00
"id" ,
2022-10-30 10:28:14 +02:00
"llvuca81nly1qls" ,
"llvuca81nly1qls" ,
2022-07-07 00:19:05 +03:00
false ,
} ,
{
2022-10-30 10:28:14 +02:00
"sz5l5z67tg7gku0" ,
2022-07-07 00:19:05 +03:00
"title" ,
2022-10-30 10:28:14 +02:00
"test3" ,
"0yxhwia2amd8gec" ,
2022-07-07 00:19:05 +03:00
false ,
} ,
}
for i , scenario := range scenarios {
2022-10-30 10:28:14 +02:00
record , err := app . Dao ( ) . FindFirstRecordByData ( scenario . collectionIdOrName , scenario . key , scenario . value )
2022-07-07 00:19:05 +03:00
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 TestIsRecordValueUnique ( t * testing . T ) {
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
2022-10-30 10:28:14 +02:00
testManyRelsId1 := "bgs820n361vj1qd"
testManyRelsId2 := "4q1xlclmfloku33"
testManyRelsId3 := "oap640cot4yru2s"
2022-07-07 00:19:05 +03:00
scenarios := [ ] struct {
2022-10-30 10:28:14 +02:00
collectionIdOrName string
key string
value any
excludeIds [ ] string
expected bool
2022-07-07 00:19:05 +03:00
} {
2022-10-30 10:28:14 +02:00
{ "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 } ,
2022-07-07 00:19:05 +03:00
}
for i , scenario := range scenarios {
2022-10-30 10:28:14 +02:00
result := app . Dao ( ) . IsRecordValueUnique (
scenario . collectionIdOrName ,
scenario . key ,
scenario . value ,
scenario . excludeIds ... ,
)
2022-07-07 00:19:05 +03:00
if result != scenario . expected {
t . Errorf ( "(%d) Expected %v, got %v" , i , scenario . expected , result )
}
}
}
2022-10-30 10:28:14 +02:00
func TestFindAuthRecordByToken ( t * testing . T ) {
2022-07-07 00:19:05 +03:00
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
2022-10-30 10:28:14 +02:00
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 ( )
2022-07-07 00:19:05 +03:00
scenarios := [ ] struct {
2022-10-30 10:28:14 +02:00
collectionIdOrName string
email string
expectError bool
2022-07-07 00:19:05 +03:00
} {
2022-10-30 10:28:14 +02:00
{ "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 } ,
2022-07-07 00:19:05 +03:00
}
for i , scenario := range scenarios {
2022-10-30 10:28:14 +02:00
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 ( ) )
2022-07-07 00:19:05 +03:00
}
2022-10-30 10:28:14 +02:00
}
}
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 } ,
}
2022-07-07 00:19:05 +03:00
2022-10-30 10:28:14 +02:00
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 )
2022-07-07 00:19:05 +03:00
continue
}
2022-10-30 10:28:14 +02:00
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 )
2022-07-07 00:19:05 +03:00
}
}
}
func TestSaveRecord ( t * testing . T ) {
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
2022-10-30 10:28:14 +02:00
collection , _ := app . Dao ( ) . FindCollectionByNameOrId ( "demo2" )
2022-07-07 00:19:05 +03:00
// create
// ---
r1 := models . NewRecord ( collection )
2022-10-30 10:28:14 +02:00
r1 . Set ( "title" , "test_new" )
2022-07-07 00:19:05 +03:00
err1 := app . Dao ( ) . SaveRecord ( r1 )
if err1 != nil {
t . Fatal ( err1 )
}
2022-10-30 10:28:14 +02:00
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 )
2022-07-07 00:19:05 +03:00
}
// update
// ---
2022-10-30 10:28:14 +02:00
r2 , _ := app . Dao ( ) . FindFirstRecordByData ( collection . Id , "id" , "0yxhwia2amd8gec" )
r2 . Set ( "title" , "test_update" )
2022-07-07 00:19:05 +03:00
err2 := app . Dao ( ) . SaveRecord ( r2 )
if err2 != nil {
t . Fatal ( err2 )
}
2022-10-30 10:28:14 +02:00
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 )
2022-07-07 00:19:05 +03:00
}
}
func TestDeleteRecord ( t * testing . T ) {
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
2022-10-30 10:28:14 +02:00
demoCollection , _ := app . Dao ( ) . FindCollectionByNameOrId ( "demo2" )
2022-07-07 00:19:05 +03:00
// delete unsaved record
// ---
2022-10-30 10:28:14 +02:00
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 )
2022-07-07 00:19:05 +03:00
}
// delete existing record while being part of a non-cascade required relation
// ---
2022-10-30 10:28:14 +02:00
rec2 , _ := app . Dao ( ) . FindRecordById ( "demo3" , "7nwo8tuiatetxdm" )
if err := app . Dao ( ) . DeleteRecord ( rec2 ) ; err == nil {
2022-07-07 00:19:05 +03:00
t . Fatalf ( "(rec2) Expected error, got nil" )
}
2022-10-30 10:28:14 +02:00
// delete existing record + cascade
2022-07-07 00:19:05 +03:00
// ---
2022-12-09 19:09:43 +02:00
calledQueries := [ ] string { }
app . DB ( ) . QueryLogFunc = func ( ctx context . Context , t time . Duration , sql string , rows * sql . Rows , err error ) {
calledQueries = append ( calledQueries , sql )
}
app . DB ( ) . ExecLogFunc = func ( ctx context . Context , t time . Duration , sql string , result sql . Result , err error ) {
calledQueries = append ( calledQueries , sql )
}
2022-10-30 10:28:14 +02:00
rec3 , _ := app . Dao ( ) . FindRecordById ( "users" , "oap640cot4yru2s" )
2022-12-09 19:09:43 +02:00
// delete
2022-10-30 10:28:14 +02:00
if err := app . Dao ( ) . DeleteRecord ( rec3 ) ; err != nil {
t . Fatalf ( "(rec3) Expected nil, got error %v" , err )
2022-07-07 00:19:05 +03:00
}
// check if it was really deleted
2022-10-30 10:28:14 +02:00
rec3 , _ = app . Dao ( ) . FindRecordById ( rec3 . Collection ( ) . Id , rec3 . Id )
2022-07-07 00:19:05 +03:00
if rec3 != nil {
t . Fatalf ( "(rec3) Expected record to be deleted, got %v" , rec3 )
}
// check if the operation cascaded
2022-10-30 10:28:14 +02:00
rel , _ := app . Dao ( ) . FindRecordById ( "demo1" , "84nmscqy84lsi1t" )
2022-07-07 00:19:05 +03:00
if rel != nil {
t . Fatalf ( "(rec3) Expected the delete to cascade, found relation %v" , rel )
}
2022-12-09 19:09:43 +02:00
// ensure that the json rel fields were prefixed
joinedQueries := strings . Join ( calledQueries , " " )
2022-12-09 19:15:24 +02:00
expectedRelManyJoin := "`demo1` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END)"
2022-12-09 19:09:43 +02:00
if ! strings . Contains ( joinedQueries , expectedRelManyJoin ) {
t . Fatalf ( "(rec3) Expected the cascade delete to call the query \n%v, got \n%v" , expectedRelManyJoin , calledQueries )
}
2022-12-09 19:15:24 +02:00
expectedRelOneJoin := "`demo1` LEFT JOIN json_each(CASE WHEN json_valid([[demo1.rel_one]]) THEN [[demo1.rel_one]] ELSE json_array([[demo1.rel_one]]) END)"
2022-12-09 19:09:43 +02:00
if ! strings . Contains ( joinedQueries , expectedRelOneJoin ) {
t . Fatalf ( "(rec3) Expected the cascade delete to call the query \n%v, got \n%v" , expectedRelOneJoin , calledQueries )
}
2022-07-07 00:19:05 +03:00
}
2022-12-12 19:19:31 +02:00
func TestDeleteRecordBatchProcessing ( t * testing . T ) {
2022-07-07 00:19:05 +03:00
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
2022-12-12 19:19:31 +02:00
if err := createMockBatchProcessingData ( app . Dao ( ) ) ; err != nil {
2022-07-07 00:19:05 +03:00
t . Fatal ( err )
}
2022-12-12 19:19:31 +02:00
// find and delete the first c1 record to trigger cascade
mainRecord , _ := app . Dao ( ) . FindRecordById ( "c1" , "a" )
if err := app . Dao ( ) . DeleteRecord ( mainRecord ) ; err != nil {
2022-07-09 22:17:41 +08:00
t . Fatal ( err )
}
2022-12-12 19:19:31 +02:00
// 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 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 (
2022-07-07 00:19:05 +03:00
& schema . SchemaField {
2022-12-12 19:19:31 +02:00
Name : "text" ,
Type : schema . FieldTypeText ,
2022-07-07 00:19:05 +03:00
} ,
)
2022-12-12 19:19:31 +02:00
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 (
2022-07-07 00:19:05 +03:00
& schema . SchemaField {
2022-12-12 19:19:31 +02:00
Name : "rel" ,
Type : schema . FieldTypeRelation ,
Options : & schema . RelationOptions {
MaxSelect : types . Pointer ( 10 ) ,
CollectionId : "c1" ,
CascadeDelete : false , // should unset all rel fields
} ,
2022-07-07 00:19:05 +03:00
} ,
)
2022-12-12 19:19:31 +02:00
if err := dao . SaveCollection ( c2 ) ; err != nil {
return err
}
2022-07-07 00:19:05 +03:00
2022-12-12 19:19:31 +02:00
// 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
2022-10-30 10:28:14 +02:00
} ,
} ,
2022-12-12 19:19:31 +02:00
)
if err := dao . SaveCollection ( c3 ) ; err != nil {
return err
}
// insert mock records
c1RecordA := models . NewRecord ( c1 )
c1RecordA . Id = "a"
if err := dao . Save ( c1RecordA ) ; err != nil {
return err
}
c1RecordB := models . NewRecord ( c1 )
c1RecordB . Id = "b"
if err := dao . Save ( c1RecordB ) ; err != nil {
return err
}
for i := 0 ; i < 2400 ; i ++ {
c2Record := models . NewRecord ( c2 )
c2Record . Set ( "rel" , [ ] string { c1RecordA . Id , c1RecordB . Id } )
if err := dao . Save ( c2Record ) ; err != nil {
return err
2022-07-07 00:19:05 +03:00
}
2022-12-12 19:19:31 +02:00
c3Record := models . NewRecord ( c3 )
c3Record . Set ( "rel" , c1RecordA . Id )
if err := dao . Save ( c3Record ) ; err != nil {
return err
2022-07-07 00:19:05 +03:00
}
}
2022-12-12 19:19:31 +02:00
return nil
2022-07-07 00:19:05 +03:00
}