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"
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
)
2023-07-13 22:38:55 +03:00
func TestRecordQueryWithDifferentCollectionValues ( t * testing . T ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-07-07 00:19:05 +03:00
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
2023-07-13 22:38:55 +03:00
scenarios := [ ] struct {
name any
collection any
expectedTotal int
expectError bool
} {
{ "with nil value" , nil , 0 , true } ,
{ "with invalid or missing collection id/name" , "missing" , 0 , true } ,
{ "with pointer model" , collection , 3 , false } ,
{ "with value model" , * collection , 3 , false } ,
{ "with name" , "demo1" , 3 , false } ,
{ "with id" , "wsmn24bux7wo113" , 3 , false } ,
}
for _ , s := range scenarios {
var records [ ] * models . Record
err := app . Dao ( ) . RecordQuery ( s . collection ) . All ( & records )
hasErr := err != nil
if hasErr != s . expectError {
t . Errorf ( "[%s] Expected hasError %v, got %v" , s . name , s . expectError , hasErr )
continue
}
2022-07-07 00:19:05 +03:00
2023-07-13 22:38:55 +03:00
if total := len ( records ) ; total != s . expectedTotal {
t . Errorf ( "[%s] Expected %d records, got %d" , s . name , s . expectedTotal , total )
}
2022-07-07 00:19:05 +03:00
}
}
2023-02-21 16:38:12 +02:00
func TestRecordQueryOneWithRecord ( t * testing . T ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2023-02-21 16:38:12 +02:00
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 ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2023-02-21 16:38:12 +02:00
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 )
}
}
}
2022-07-07 00:19:05 +03:00
func TestFindRecordById ( t * testing . T ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-07-07 00:19:05 +03:00
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 ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-07-07 00:19:05 +03:00
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 ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-07-07 00:19:05 +03:00
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 ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-07-07 00:19:05 +03:00
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 )
}
}
}
2023-05-31 11:47:16 +03:00
func TestFindRecordsByFilter ( t * testing . T ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2023-05-31 11:47:16 +03:00
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
scenarios := [ ] struct {
name string
collectionIdOrName string
filter string
sort string
limit int
2023-08-18 06:31:14 +03:00
offset int
params [ ] dbx . Params
2023-05-31 11:47:16 +03:00
expectError bool
expectRecordIds [ ] string
} {
{
"missing collection" ,
"missing" ,
"id != ''" ,
"" ,
0 ,
2023-08-18 06:31:14 +03:00
0 ,
nil ,
2023-05-31 11:47:16 +03:00
true ,
nil ,
} ,
{
"missing filter" ,
"demo2" ,
"" ,
"" ,
0 ,
2023-08-18 06:31:14 +03:00
0 ,
nil ,
2023-05-31 11:47:16 +03:00
true ,
nil ,
} ,
{
"invalid filter" ,
"demo2" ,
"someMissingField > 1" ,
"" ,
0 ,
2023-08-18 06:31:14 +03:00
0 ,
nil ,
2023-05-31 11:47:16 +03:00
true ,
nil ,
} ,
{
"simple filter" ,
"demo2" ,
"id != ''" ,
"" ,
0 ,
2023-08-18 06:31:14 +03:00
0 ,
nil ,
2023-05-31 11:47:16 +03:00
false ,
[ ] string {
"llvuca81nly1qls" ,
"achvryl401bhse3" ,
"0yxhwia2amd8gec" ,
} ,
} ,
{
"multi-condition filter with sort" ,
"demo2" ,
"id != '' && active=true" ,
"-created,title" ,
- 1 , // should behave the same as 0
2023-08-18 06:31:14 +03:00
0 ,
nil ,
2023-05-31 11:47:16 +03:00
false ,
[ ] string {
"0yxhwia2amd8gec" ,
"achvryl401bhse3" ,
} ,
} ,
{
2023-08-18 06:31:14 +03:00
"with limit and offset" ,
2023-05-31 11:47:16 +03:00
"demo2" ,
"id != ''" ,
"title" ,
2 ,
2023-08-18 06:31:14 +03:00
1 ,
nil ,
2023-05-31 11:47:16 +03:00
false ,
[ ] string {
"achvryl401bhse3" ,
2023-08-18 06:31:14 +03:00
"0yxhwia2amd8gec" ,
} ,
} ,
{
"with placeholder params" ,
"demo2" ,
"active = {:active}" ,
"" ,
10 ,
0 ,
[ ] dbx . Params { { "active" : false } } ,
false ,
[ ] string {
"llvuca81nly1qls" ,
2023-05-31 11:47:16 +03:00
} ,
} ,
2024-01-03 14:16:03 +02:00
{
"with json filter and sort" ,
"demo4" ,
"json_object != null && json_object.a.b = 'test'" ,
"-json_object.a" ,
10 ,
0 ,
[ ] dbx . Params { { "active" : false } } ,
false ,
[ ] string {
"i9naidtvr6qsgb4" ,
} ,
} ,
2023-05-31 11:47:16 +03:00
}
for _ , s := range scenarios {
2023-08-18 06:31:14 +03:00
t . Run ( s . name , func ( t * testing . T ) {
records , err := app . Dao ( ) . FindRecordsByFilter (
s . collectionIdOrName ,
s . filter ,
s . sort ,
s . limit ,
s . offset ,
s . params ... ,
)
hasErr := err != nil
if hasErr != s . expectError {
t . Fatalf ( "[%s] Expected hasErr to be %v, got %v (%v)" , s . name , s . expectError , hasErr , err )
}
2023-05-31 11:47:16 +03:00
2023-08-18 06:31:14 +03:00
if hasErr {
return
}
2023-05-31 11:47:16 +03:00
2023-08-18 06:31:14 +03:00
if len ( records ) != len ( s . expectRecordIds ) {
t . Fatalf ( "[%s] Expected %d records, got %d" , s . name , len ( s . expectRecordIds ) , len ( records ) )
}
2023-05-31 11:47:16 +03:00
2023-08-18 06:31:14 +03:00
for i , id := range s . expectRecordIds {
if id != records [ i ] . Id {
t . Fatalf ( "[%s] Expected record with id %q, got %q at index %d" , s . name , id , records [ i ] . Id , i )
}
2023-05-31 11:47:16 +03:00
}
2023-08-18 06:31:14 +03:00
} )
2023-05-31 11:47:16 +03:00
}
}
func TestFindFirstRecordByFilter ( t * testing . T ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2023-05-31 11:47:16 +03:00
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
scenarios := [ ] struct {
name string
collectionIdOrName string
filter string
2023-08-18 06:31:14 +03:00
params [ ] dbx . Params
2023-05-31 11:47:16 +03:00
expectError bool
expectRecordId string
} {
{
"missing collection" ,
"missing" ,
"id != ''" ,
2023-08-18 06:31:14 +03:00
nil ,
2023-05-31 11:47:16 +03:00
true ,
"" ,
} ,
{
"missing filter" ,
"demo2" ,
"" ,
2023-08-18 06:31:14 +03:00
nil ,
2023-05-31 11:47:16 +03:00
true ,
"" ,
} ,
{
"invalid filter" ,
"demo2" ,
"someMissingField > 1" ,
2023-08-18 06:31:14 +03:00
nil ,
2023-05-31 11:47:16 +03:00
true ,
"" ,
} ,
{
"valid filter but no matches" ,
"demo2" ,
"id = 'test'" ,
2023-08-18 06:31:14 +03:00
nil ,
2023-05-31 11:47:16 +03:00
true ,
"" ,
} ,
{
"valid filter and multiple matches" ,
"demo2" ,
"id != ''" ,
2023-08-18 06:31:14 +03:00
nil ,
false ,
"llvuca81nly1qls" ,
} ,
{
"with placeholder params" ,
"demo2" ,
"active = {:active}" ,
[ ] dbx . Params { { "active" : false } } ,
2023-05-31 11:47:16 +03:00
false ,
"llvuca81nly1qls" ,
} ,
}
for _ , s := range scenarios {
2023-08-18 06:31:14 +03:00
record , err := app . Dao ( ) . FindFirstRecordByFilter ( s . collectionIdOrName , s . filter , s . params ... )
2023-05-31 11:47:16 +03:00
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 )
}
}
}
2023-06-14 13:13:21 +03:00
func TestCanAccessRecord ( t * testing . T ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2023-06-14 13:13:21 +03:00
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
admin , err := app . Dao ( ) . FindAdminByEmail ( "test@example.com" )
if err != nil {
t . Fatal ( err )
}
authRecord , err := app . Dao ( ) . FindAuthRecordByEmail ( "users" , "test@example.com" )
if err != nil {
t . Fatal ( err )
}
record , err := app . Dao ( ) . FindRecordById ( "demo1" , "imy661ixudk5izi" )
if err != nil {
t . Fatal ( err )
}
scenarios := [ ] struct {
name string
record * models . Record
2023-07-17 23:13:39 +03:00
requestInfo * models . RequestInfo
2023-06-14 13:13:21 +03:00
rule * string
expected bool
2023-07-11 11:50:10 +03:00
expectError bool
2023-06-14 13:13:21 +03:00
} {
{
"as admin with nil rule" ,
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo {
2023-06-14 13:13:21 +03:00
Admin : admin ,
} ,
nil ,
true ,
2023-07-11 11:50:10 +03:00
false ,
2023-06-14 13:13:21 +03:00
} ,
{
"as admin with non-empty rule" ,
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo {
2023-06-14 13:13:21 +03:00
Admin : admin ,
} ,
types . Pointer ( "id = ''" ) , // the filter rule should be ignored
true ,
2023-07-11 11:50:10 +03:00
false ,
} ,
{
"as admin with invalid rule" ,
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo {
2023-07-11 11:50:10 +03:00
Admin : admin ,
} ,
types . Pointer ( "id ?!@ 1" ) , // the filter rule should be ignored
true ,
false ,
2023-06-14 13:13:21 +03:00
} ,
{
"as guest with nil rule" ,
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo { } ,
2023-06-14 13:13:21 +03:00
nil ,
false ,
2023-07-11 11:50:10 +03:00
false ,
2023-06-14 13:13:21 +03:00
} ,
{
"as guest with empty rule" ,
2023-07-11 11:50:10 +03:00
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo { } ,
2023-06-14 13:13:21 +03:00
types . Pointer ( "" ) ,
true ,
2023-07-11 11:50:10 +03:00
false ,
} ,
{
"as guest with invalid rule" ,
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo { } ,
2023-07-11 11:50:10 +03:00
types . Pointer ( "id ?!@ 1" ) ,
false ,
true ,
2023-06-14 13:13:21 +03:00
} ,
{
"as guest with mismatched rule" ,
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo { } ,
2023-06-14 13:13:21 +03:00
types . Pointer ( "@request.auth.id != ''" ) ,
false ,
2023-07-11 11:50:10 +03:00
false ,
2023-06-14 13:13:21 +03:00
} ,
{
"as guest with matched rule" ,
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo {
2023-06-14 13:13:21 +03:00
Data : map [ string ] any { "test" : 1 } ,
} ,
types . Pointer ( "@request.auth.id != '' || @request.data.test = 1" ) ,
true ,
2023-07-11 11:50:10 +03:00
false ,
2023-06-14 13:13:21 +03:00
} ,
{
"as auth record with nil rule" ,
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo {
2023-06-14 13:13:21 +03:00
AuthRecord : authRecord ,
} ,
nil ,
false ,
2023-07-11 11:50:10 +03:00
false ,
2023-06-14 13:13:21 +03:00
} ,
{
"as auth record with empty rule" ,
2023-07-11 11:50:10 +03:00
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo {
2023-06-14 13:13:21 +03:00
AuthRecord : authRecord ,
} ,
types . Pointer ( "" ) ,
true ,
2023-07-11 11:50:10 +03:00
false ,
} ,
{
"as auth record with invalid rule" ,
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo {
2023-07-11 11:50:10 +03:00
AuthRecord : authRecord ,
} ,
types . Pointer ( "id ?!@ 1" ) ,
false ,
true ,
2023-06-14 13:13:21 +03:00
} ,
{
"as auth record with mismatched rule" ,
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo {
2023-06-14 13:13:21 +03:00
AuthRecord : authRecord ,
Data : map [ string ] any { "test" : 1 } ,
} ,
types . Pointer ( "@request.auth.id != '' && @request.data.test > 1" ) ,
false ,
2023-07-11 11:50:10 +03:00
false ,
2023-06-14 13:13:21 +03:00
} ,
{
"as auth record with matched rule" ,
record ,
2023-07-17 23:13:39 +03:00
& models . RequestInfo {
2023-06-14 13:13:21 +03:00
AuthRecord : authRecord ,
Data : map [ string ] any { "test" : 2 } ,
} ,
types . Pointer ( "@request.auth.id != '' && @request.data.test > 1" ) ,
true ,
2023-07-11 11:50:10 +03:00
false ,
2023-06-14 13:13:21 +03:00
} ,
}
for _ , s := range scenarios {
2023-07-17 23:13:39 +03:00
result , err := app . Dao ( ) . CanAccessRecord ( s . record , s . requestInfo , s . rule )
2023-06-14 13:13:21 +03:00
if result != s . expected {
t . Errorf ( "[%s] Expected %v, got %v" , s . name , s . expected , result )
}
2023-07-11 11:50:10 +03:00
hasErr := err != nil
if hasErr != s . expectError {
t . Errorf ( "[%s] Expected hasErr %v, got %v (%v)" , s . name , s . expectError , hasErr , err )
}
2023-06-14 13:13:21 +03:00
}
}
2022-07-07 00:19:05 +03:00
func TestIsRecordValueUnique ( t * testing . T ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-07-07 00:19:05 +03:00
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 ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
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 ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-10-30 10:28:14 +02:00
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 ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-10-30 10:28:14 +02:00
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 ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-10-30 10:28:14 +02:00
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 ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-07-07 00:19:05 +03:00
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 ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-10-30 10:28:14 +02:00
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 ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
2022-07-07 00:19:05 +03:00
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 { }
2022-12-16 13:07:58 +02:00
app . Dao ( ) . NonconcurrentDB ( ) . ( * dbx . DB ) . QueryLogFunc = func ( ctx context . Context , t time . Duration , sql string , rows * sql . Rows , err error ) {
2022-12-09 19:09:43 +02:00
calledQueries = append ( calledQueries , sql )
}
2022-12-16 13:07:58 +02:00
app . Dao ( ) . ConcurrentDB ( ) . ( * dbx . DB ) . QueryLogFunc = func ( ctx context . Context , t time . Duration , sql string , rows * sql . Rows , err error ) {
2022-12-15 16:42:35 +02:00
calledQueries = append ( calledQueries , sql )
}
2022-12-16 13:07:58 +02:00
app . Dao ( ) . NonconcurrentDB ( ) . ( * dbx . DB ) . ExecLogFunc = func ( ctx context . Context , t time . Duration , sql string , result sql . Result , err error ) {
2022-12-15 16:42:35 +02:00
calledQueries = append ( calledQueries , sql )
}
2022-12-16 13:07:58 +02:00
app . Dao ( ) . ConcurrentDB ( ) . ( * dbx . DB ) . ExecLogFunc = func ( ctx context . Context , t time . Duration , sql string , result sql . Result , err error ) {
2022-12-09 19:09:43 +02:00
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 , " " )
2024-01-23 20:22:51 +02:00
expectedRelManyPart := "SELECT `demo1`.* FROM `demo1` WHERE EXISTS (SELECT 1 FROM json_each(CASE WHEN json_valid([[demo1.rel_many]]) THEN [[demo1.rel_many]] ELSE json_array([[demo1.rel_many]]) END) {{__je__}} WHERE [[__je__.value]]='"
2023-03-07 23:28:35 +02:00
if ! strings . Contains ( joinedQueries , expectedRelManyPart ) {
t . Fatalf ( "(rec3) Expected the cascade delete to call the query \n%v, got \n%v" , expectedRelManyPart , calledQueries )
2022-12-09 19:09:43 +02:00
}
2024-01-23 20:22:51 +02:00
expectedRelOnePart := "SELECT `demo1`.* FROM `demo1` WHERE (`demo1`.`rel_one`='"
2023-03-07 23:28:35 +02:00
if ! strings . Contains ( joinedQueries , expectedRelOnePart ) {
t . Fatalf ( "(rec3) Expected the cascade delete to call the query \n%v, got \n%v" , expectedRelOnePart , calledQueries )
2022-12-09 19:09:43 +02:00
}
2022-07-07 00:19:05 +03:00
}
2022-12-12 19:19:31 +02:00
func TestDeleteRecordBatchProcessing ( t * testing . T ) {
2024-01-03 04:30:20 +02:00
t . Parallel ( )
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" )
}
2023-01-26 00:05:20 +02:00
// 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" ) )
}
2022-12-12 19:19:31 +02:00
// 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
} ,
2023-01-26 00:05:20 +02:00
// 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
} ,
} ,
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"
2023-01-26 00:05:20 +02:00
c1RecordA . Set ( "rel" , c1RecordA . Id ) // self reference
2022-12-12 19:19:31 +02:00
if err := dao . Save ( c1RecordA ) ; err != nil {
return err
}
c1RecordB := models . NewRecord ( c1 )
c1RecordB . Id = "b"
2023-01-26 00:05:20 +02:00
c1RecordB . Set ( "rel" , c1RecordA . Id ) // rel to another record from the same collection
2022-12-12 19:19:31 +02:00
if err := dao . Save ( c1RecordB ) ; err != nil {
return err
}
2023-01-26 00:05:20 +02:00
for i := 0 ; i < 4500 ; i ++ {
2022-12-12 19:19:31 +02:00
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
2023-01-26 00:05:20 +02:00
// 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
}
2022-12-12 19:19:31 +02:00
return nil
2022-07-07 00:19:05 +03:00
}