2022-11-28 19:59:17 +02:00
package migratecmd_test
import (
"os"
"path/filepath"
2024-10-29 20:08:16 +02:00
"regexp"
2022-11-28 19:59:17 +02:00
"strings"
"testing"
2024-09-29 19:23:19 +03:00
"github.com/pocketbase/pocketbase/core"
2022-11-28 19:59:17 +02:00
"github.com/pocketbase/pocketbase/plugins/migratecmd"
"github.com/pocketbase/pocketbase/tests"
2024-10-29 20:08:16 +02:00
"github.com/pocketbase/pocketbase/tools/list"
2022-11-28 19:59:17 +02:00
"github.com/pocketbase/pocketbase/tools/types"
)
func TestAutomigrateCollectionCreate ( t * testing . T ) {
2024-09-29 19:23:19 +03:00
t . Parallel ( )
2022-11-28 19:59:17 +02:00
scenarios := [ ] struct {
lang string
expectedTemplate string
} {
{
migratecmd . TemplateLangJS ,
`
2023-06-27 14:45:04 +03:00
/// <reference path="../pb_data/types.d.ts" />
2024-09-29 19:23:19 +03:00
migrate ( ( app ) = > {
2022-12-05 13:57:09 +02:00
const collection = new Collection ( {
2024-09-29 19:23:19 +03:00
"authAlert" : {
"emailTemplate" : {
"body" : "<p>Hello,</p>\n<p>We noticed a login to your {APP_NAME} account from a new location.</p>\n<p>If this was you, you may disregard this email.</p>\n<p><strong>If this wasn't you, you should immediately change your {APP_NAME} account password to revoke access from all other locations.</strong></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Login from a new location"
} ,
"enabled" : true
} ,
"authRule" : "" ,
"authToken" : {
"duration" : 604800
} ,
"confirmEmailChangeTemplate" : {
"body" : "<p>Hello,</p>\n<p>Click on the button below to confirm your new email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-email-change/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Confirm new email</a>\n</p>\n<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Confirm your {APP_NAME} new email address"
} ,
"createRule" : null ,
"deleteRule" : null ,
"emailChangeToken" : {
"duration" : 1800
} ,
"fields" : [
{
"autogeneratePattern" : "[a-z0-9]{15}" ,
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "text@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 15 ,
"min" : 15 ,
"name" : "id" ,
"pattern" : "^[a-z0-9]+$" ,
"presentable" : false ,
"primaryKey" : true ,
"required" : true ,
"system" : true ,
"type" : "text"
} ,
{
"cost" : 0 ,
"hidden" : true ,
2024-10-29 20:08:16 +02:00
"id" : "password@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 0 ,
"min" : 8 ,
"name" : "password" ,
"pattern" : "" ,
"presentable" : false ,
"required" : true ,
"system" : true ,
"type" : "password"
} ,
{
"autogeneratePattern" : "[a-zA-Z0-9]{50}" ,
"hidden" : true ,
2024-10-29 20:08:16 +02:00
"id" : "text@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 60 ,
"min" : 30 ,
"name" : "tokenKey" ,
"pattern" : "" ,
"presentable" : false ,
"primaryKey" : false ,
"required" : true ,
"system" : true ,
"type" : "text"
} ,
{
"exceptDomains" : null ,
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "email@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"name" : "email" ,
"onlyDomains" : null ,
"presentable" : false ,
"required" : true ,
"system" : true ,
"type" : "email"
} ,
{
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "bool@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"name" : "emailVisibility" ,
"presentable" : false ,
"required" : false ,
"system" : true ,
"type" : "bool"
} ,
{
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "bool@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"name" : "verified" ,
"presentable" : false ,
"required" : false ,
"system" : true ,
"type" : "bool"
}
] ,
"fileToken" : {
"duration" : 180
} ,
2024-10-29 20:08:16 +02:00
"id" : "@TEST_RANDOM" ,
2023-03-19 16:02:29 +02:00
"indexes" : [
2024-09-29 19:23:19 +03:00
"create index test on new_name (id)" ,
2024-10-29 20:08:16 +02:00
"CREATE UNIQUE INDEX ` + " ` " + ` idx_tokenKey_ @ TEST_RANDOM ` + " ` " + ` ON ` + " ` " + ` new_name ` + " ` " + ` (` + " ` " + ` tokenKey ` + " ` " + `)" ,
"CREATE UNIQUE INDEX ` + " ` " + ` idx_email_ @ TEST_RANDOM ` + " ` " + ` ON ` + " ` " + ` new_name ` + " ` " + ` (` + " ` " + ` email ` + " ` " + `) WHERE ` + " ` " + ` email ` + " ` " + ` != ''"
2023-03-19 16:02:29 +02:00
] ,
2024-09-29 19:23:19 +03:00
"listRule" : "@request.auth.id != '' && 1 > 0 || 'backtick` + " ` " + ` test ' = 0 " ,
"manageRule" : "1 != 2" ,
"mfa" : {
"duration" : 1800 ,
"enabled" : false ,
"rule" : ""
} ,
"name" : "new_name" ,
"oauth2" : {
"enabled" : false ,
"mappedFields" : {
"avatarURL" : "" ,
"id" : "" ,
"name" : "" ,
"username" : ""
}
} ,
"otp" : {
"duration" : 180 ,
"emailTemplate" : {
"body" : "<p>Hello,</p>\n<p>Your one-time password is: <strong>{OTP}</strong></p>\n<p><i>If you didn't ask for the one-time password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "OTP for {APP_NAME}"
} ,
"enabled" : false ,
"length" : 8
} ,
"passwordAuth" : {
"enabled" : true ,
"identityFields" : [
"email"
]
} ,
"passwordResetToken" : {
"duration" : 1800
} ,
"resetPasswordTemplate" : {
"body" : "<p>Hello,</p>\n<p>Click on the button below to reset your password.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-password-reset/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Reset password</a>\n</p>\n<p><i>If you didn't ask to reset your password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Reset your {APP_NAME} password"
} ,
"system" : true ,
"type" : "auth" ,
2022-11-28 19:59:17 +02:00
"updateRule" : null ,
2024-09-29 19:23:19 +03:00
"verificationTemplate" : {
"body" : "<p>Hello,</p>\n<p>Thank you for joining us at {APP_NAME}.</p>\n<p>Click on the button below to verify your email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-verification/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Verify</a>\n</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Verify your {APP_NAME} email"
} ,
"verificationToken" : {
"duration" : 259200
} ,
"viewRule" : "id = \"1\""
2022-12-05 13:57:09 +02:00
} ) ;
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
return app . save ( collection ) ;
} , ( app ) = > {
2024-10-29 20:08:16 +02:00
const collection = app . findCollectionByNameOrId ( "@TEST_RANDOM" ) ;
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
return app . delete ( collection ) ;
2022-11-28 19:59:17 +02:00
} )
` ,
} ,
{
migratecmd . TemplateLangGo ,
`
package _test_migrations
import (
"encoding/json"
2024-09-29 19:23:19 +03:00
"github.com/pocketbase/pocketbase/core"
2022-11-28 19:59:17 +02:00
m "github.com/pocketbase/pocketbase/migrations"
)
func init ( ) {
2024-09-29 19:23:19 +03:00
m . Register ( func ( app core . App ) error {
2022-11-28 19:59:17 +02:00
jsonData := ` + " ` " + ` {
2024-09-29 19:23:19 +03:00
"authAlert" : {
"emailTemplate" : {
"body" : "<p>Hello,</p>\n<p>We noticed a login to your {APP_NAME} account from a new location.</p>\n<p>If this was you, you may disregard this email.</p>\n<p><strong>If this wasn't you, you should immediately change your {APP_NAME} account password to revoke access from all other locations.</strong></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Login from a new location"
} ,
"enabled" : true
} ,
"authRule" : "" ,
"authToken" : {
"duration" : 604800
} ,
"confirmEmailChangeTemplate" : {
"body" : "<p>Hello,</p>\n<p>Click on the button below to confirm your new email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-email-change/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Confirm new email</a>\n</p>\n<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Confirm your {APP_NAME} new email address"
} ,
"createRule" : null ,
"deleteRule" : null ,
"emailChangeToken" : {
"duration" : 1800
} ,
"fields" : [
{
"autogeneratePattern" : "[a-z0-9]{15}" ,
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "text@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 15 ,
"min" : 15 ,
"name" : "id" ,
"pattern" : "^[a-z0-9]+$" ,
"presentable" : false ,
"primaryKey" : true ,
"required" : true ,
"system" : true ,
"type" : "text"
} ,
{
"cost" : 0 ,
"hidden" : true ,
2024-10-29 20:08:16 +02:00
"id" : "password@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 0 ,
"min" : 8 ,
"name" : "password" ,
"pattern" : "" ,
"presentable" : false ,
"required" : true ,
"system" : true ,
"type" : "password"
} ,
{
"autogeneratePattern" : "[a-zA-Z0-9]{50}" ,
"hidden" : true ,
2024-10-29 20:08:16 +02:00
"id" : "text@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 60 ,
"min" : 30 ,
"name" : "tokenKey" ,
"pattern" : "" ,
"presentable" : false ,
"primaryKey" : false ,
"required" : true ,
"system" : true ,
"type" : "text"
} ,
{
"exceptDomains" : null ,
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "email@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"name" : "email" ,
"onlyDomains" : null ,
"presentable" : false ,
"required" : true ,
"system" : true ,
"type" : "email"
} ,
{
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "bool@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"name" : "emailVisibility" ,
"presentable" : false ,
"required" : false ,
"system" : true ,
"type" : "bool"
} ,
{
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "bool@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"name" : "verified" ,
"presentable" : false ,
"required" : false ,
"system" : true ,
"type" : "bool"
}
] ,
"fileToken" : {
"duration" : 180
} ,
2024-10-29 20:08:16 +02:00
"id" : "@TEST_RANDOM" ,
2023-03-19 16:02:29 +02:00
"indexes" : [
2024-09-29 19:23:19 +03:00
"create index test on new_name (id)" ,
2024-10-29 20:08:16 +02:00
"CREATE UNIQUE INDEX ` + " ` + \" ` \ " + `" + ` idx_tokenKey_@TEST_RANDOM ` + "` + \"`\" + `" + ` ON ` + "` + \"`\" + `" + ` new_name ` + "` + \"`\" + `" + ` ( ` + "` + \"`\" + `" + ` tokenKey ` + "` + \"`\" + `" + ` ) " ,
"CREATE UNIQUE INDEX ` + " ` + \" ` \ " + `" + ` idx_email_@TEST_RANDOM ` + "` + \"`\" + `" + ` ON ` + "` + \"`\" + `" + ` new_name ` + "` + \"`\" + `" + ` ( ` + "` + \"`\" + `" + ` email ` + "` + \"`\" + `" + ` ) WHERE ` + "` + \"`\" + `" + ` email ` + "` + \"`\" + `" + ` != ' ' "
2023-03-19 16:02:29 +02:00
] ,
2024-09-29 19:23:19 +03:00
"listRule" : "@request.auth.id != '' && 1 > 0 || 'backtick` + " ` + \" ` \ " + `" + ` test ' = 0 " ,
"manageRule" : "1 != 2" ,
"mfa" : {
"duration" : 1800 ,
"enabled" : false ,
"rule" : ""
} ,
"name" : "new_name" ,
"oauth2" : {
"enabled" : false ,
"mappedFields" : {
"avatarURL" : "" ,
"id" : "" ,
"name" : "" ,
"username" : ""
}
} ,
"otp" : {
"duration" : 180 ,
"emailTemplate" : {
"body" : "<p>Hello,</p>\n<p>Your one-time password is: <strong>{OTP}</strong></p>\n<p><i>If you didn't ask for the one-time password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "OTP for {APP_NAME}"
} ,
"enabled" : false ,
"length" : 8
} ,
"passwordAuth" : {
"enabled" : true ,
"identityFields" : [
"email"
]
} ,
"passwordResetToken" : {
"duration" : 1800
} ,
"resetPasswordTemplate" : {
"body" : "<p>Hello,</p>\n<p>Click on the button below to reset your password.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-password-reset/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Reset password</a>\n</p>\n<p><i>If you didn't ask to reset your password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Reset your {APP_NAME} password"
} ,
"system" : true ,
"type" : "auth" ,
2022-11-28 19:59:17 +02:00
"updateRule" : null ,
2024-09-29 19:23:19 +03:00
"verificationTemplate" : {
"body" : "<p>Hello,</p>\n<p>Thank you for joining us at {APP_NAME}.</p>\n<p>Click on the button below to verify your email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-verification/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Verify</a>\n</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Verify your {APP_NAME} email"
} ,
"verificationToken" : {
"duration" : 259200
} ,
"viewRule" : "id = \"1\""
2022-11-28 19:59:17 +02:00
} ` + " ` " + `
2024-09-29 19:23:19 +03:00
collection := & core . Collection { }
2022-11-28 19:59:17 +02:00
if err := json . Unmarshal ( [ ] byte ( jsonData ) , & collection ) ; err != nil {
return err
}
2024-09-29 19:23:19 +03:00
return app . Save ( collection )
} , func ( app core . App ) error {
2024-10-29 20:08:16 +02:00
collection , err := app . FindCollectionByNameOrId ( "@TEST_RANDOM" )
2022-11-28 19:59:17 +02:00
if err != nil {
return err
}
2024-09-29 19:23:19 +03:00
return app . Delete ( collection )
2022-11-28 19:59:17 +02:00
} )
}
` ,
} ,
}
2024-09-29 19:23:19 +03:00
for _ , s := range scenarios {
t . Run ( s . lang , func ( t * testing . T ) {
2023-12-06 11:57:04 +02:00
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
migrationsDir := filepath . Join ( app . DataDir ( ) , "_test_migrations" )
migratecmd . MustRegister ( app , nil , migratecmd . Config {
TemplateLang : s . lang ,
Automigrate : true ,
Dir : migrationsDir ,
} )
app . Bootstrap ( )
2024-09-29 19:23:19 +03:00
collection := core . NewAuthCollection ( "new_name" )
2023-12-06 11:57:04 +02:00
collection . System = true
2024-09-29 19:23:19 +03:00
collection . ListRule = types . Pointer ( "@request.auth.id != '' && 1 > 0 || 'backtick`test' = 0" )
2023-12-06 11:57:04 +02:00
collection . ViewRule = types . Pointer ( ` id = "1" ` )
2024-09-29 19:23:19 +03:00
collection . Indexes = types . JSONArray [ string ] { "create index test on new_name (id)" }
collection . ManageRule = types . Pointer ( "1 != 2" )
// should be ignored
collection . OAuth2 . Providers = [ ] core . OAuth2ProviderConfig { { Name : "gitlab" , ClientId : "abc" , ClientSecret : "123" } }
testSecret := strings . Repeat ( "a" , 30 )
collection . AuthToken . Secret = testSecret
collection . FileToken . Secret = testSecret
collection . EmailChangeToken . Secret = testSecret
collection . PasswordResetToken . Secret = testSecret
collection . VerificationToken . Secret = testSecret
// save the newly created dummy collection (with mock request event)
event := new ( core . CollectionRequestEvent )
event . RequestEvent = & core . RequestEvent { }
event . App = app
event . Collection = collection
err := app . OnCollectionCreateRequest ( ) . Trigger ( event , func ( e * core . CollectionRequestEvent ) error {
return e . App . Save ( e . Collection )
2023-12-06 11:57:04 +02:00
} )
2024-09-29 19:23:19 +03:00
if err != nil {
t . Fatalf ( "Failed to save the created dummy collection, got: %v" , err )
2023-12-06 11:57:04 +02:00
}
2022-11-28 19:59:17 +02:00
2023-12-06 11:57:04 +02:00
files , err := os . ReadDir ( migrationsDir )
if err != nil {
2023-12-09 22:30:37 +02:00
t . Fatalf ( "Expected migrationsDir to be created, got %v" , err )
2023-12-06 11:57:04 +02:00
}
2022-11-28 19:59:17 +02:00
2023-12-06 11:57:04 +02:00
if total := len ( files ) ; total != 1 {
2023-12-09 22:30:37 +02:00
t . Fatalf ( "Expected 1 file to be generated, got %d: %v" , total , files )
2023-12-06 11:57:04 +02:00
}
2022-11-28 19:59:17 +02:00
2023-12-06 11:57:04 +02:00
expectedName := "_created_new_name." + s . lang
if ! strings . Contains ( files [ 0 ] . Name ( ) , expectedName ) {
t . Fatalf ( "Expected filename to contains %q, got %q" , expectedName , files [ 0 ] . Name ( ) )
}
2022-11-28 19:59:17 +02:00
2023-12-06 11:57:04 +02:00
fullPath := filepath . Join ( migrationsDir , files [ 0 ] . Name ( ) )
content , err := os . ReadFile ( fullPath )
if err != nil {
t . Fatalf ( "Failed to read the generated migration file: %v" , err )
}
2024-10-29 20:08:16 +02:00
contentStr := strings . TrimSpace ( string ( content ) )
// replace @TEST_RANDOM placeholder with a regex pattern
expectedTemplate := strings . ReplaceAll (
"^" + regexp . QuoteMeta ( strings . TrimSpace ( s . expectedTemplate ) ) + "$" ,
"@TEST_RANDOM" ,
` \w+ ` ,
)
if ! list . ExistInSliceWithRegex ( contentStr , [ ] string { expectedTemplate } ) {
t . Fatalf ( "Expected template \n%v \ngot \n%v" , s . expectedTemplate , contentStr )
2023-12-06 11:57:04 +02:00
}
} )
2022-11-28 19:59:17 +02:00
}
}
func TestAutomigrateCollectionDelete ( t * testing . T ) {
2024-09-29 19:23:19 +03:00
t . Parallel ( )
2022-11-28 19:59:17 +02:00
scenarios := [ ] struct {
lang string
expectedTemplate string
} {
{
migratecmd . TemplateLangJS ,
`
2023-06-27 14:45:04 +03:00
/// <reference path="../pb_data/types.d.ts" />
2024-09-29 19:23:19 +03:00
migrate ( ( app ) = > {
2024-10-29 20:08:16 +02:00
const collection = app . findCollectionByNameOrId ( "@TEST_RANDOM" ) ;
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
return app . delete ( collection ) ;
} , ( app ) = > {
2022-12-05 13:57:09 +02:00
const collection = new Collection ( {
2024-09-29 19:23:19 +03:00
"authAlert" : {
"emailTemplate" : {
"body" : "<p>Hello,</p>\n<p>We noticed a login to your {APP_NAME} account from a new location.</p>\n<p>If this was you, you may disregard this email.</p>\n<p><strong>If this wasn't you, you should immediately change your {APP_NAME} account password to revoke access from all other locations.</strong></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Login from a new location"
} ,
"enabled" : true
} ,
"authRule" : "" ,
"authToken" : {
"duration" : 604800
} ,
"confirmEmailChangeTemplate" : {
"body" : "<p>Hello,</p>\n<p>Click on the button below to confirm your new email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-email-change/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Confirm new email</a>\n</p>\n<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Confirm your {APP_NAME} new email address"
} ,
"createRule" : null ,
"deleteRule" : null ,
"emailChangeToken" : {
"duration" : 1800
} ,
"fields" : [
{
"autogeneratePattern" : "[a-z0-9]{15}" ,
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "text@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 15 ,
"min" : 15 ,
"name" : "id" ,
"pattern" : "^[a-z0-9]+$" ,
"presentable" : false ,
"primaryKey" : true ,
"required" : true ,
"system" : true ,
"type" : "text"
} ,
{
"cost" : 0 ,
"hidden" : true ,
2024-10-29 20:08:16 +02:00
"id" : "password@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 0 ,
"min" : 8 ,
"name" : "password" ,
"pattern" : "" ,
"presentable" : false ,
"required" : true ,
"system" : true ,
"type" : "password"
} ,
{
"autogeneratePattern" : "[a-zA-Z0-9]{50}" ,
"hidden" : true ,
2024-10-29 20:08:16 +02:00
"id" : "text@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 60 ,
"min" : 30 ,
"name" : "tokenKey" ,
"pattern" : "" ,
"presentable" : false ,
"primaryKey" : false ,
"required" : true ,
"system" : true ,
"type" : "text"
} ,
{
"exceptDomains" : null ,
"hidden" : false ,
"id" : "email3885137012" ,
"name" : "email" ,
"onlyDomains" : null ,
"presentable" : false ,
"required" : true ,
"system" : true ,
"type" : "email"
} ,
{
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "bool@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"name" : "emailVisibility" ,
"presentable" : false ,
"required" : false ,
"system" : true ,
"type" : "bool"
} ,
{
"hidden" : false ,
"id" : "bool256245529" ,
"name" : "verified" ,
"presentable" : false ,
"required" : false ,
"system" : true ,
"type" : "bool"
}
] ,
"fileToken" : {
"duration" : 180
} ,
2024-10-29 20:08:16 +02:00
"id" : "@TEST_RANDOM" ,
2023-03-19 16:02:29 +02:00
"indexes" : [
2024-09-29 19:23:19 +03:00
"create index test on test123 (id)" ,
2024-10-29 20:08:16 +02:00
"CREATE UNIQUE INDEX ` + " ` " + ` idx_tokenKey_ @ TEST_RANDOM ` + " ` " + ` ON ` + " ` " + ` test123 ` + " ` " + ` (` + " ` " + ` tokenKey ` + " ` " + `)" ,
"CREATE UNIQUE INDEX ` + " ` " + ` idx_email_ @ TEST_RANDOM ` + " ` " + ` ON ` + " ` " + ` test123 ` + " ` " + ` (` + " ` " + ` email ` + " ` " + `) WHERE ` + " ` " + ` email ` + " ` " + ` != ''"
2023-03-19 16:02:29 +02:00
] ,
2024-09-29 19:23:19 +03:00
"listRule" : "@request.auth.id != '' && 1 > 0 || 'backtick` + " ` " + ` test ' = 0 " ,
"manageRule" : "1 != 2" ,
"mfa" : {
"duration" : 1800 ,
"enabled" : false ,
"rule" : ""
} ,
"name" : "test123" ,
"oauth2" : {
"enabled" : false ,
"mappedFields" : {
"avatarURL" : "" ,
"id" : "" ,
"name" : "" ,
"username" : ""
}
} ,
"otp" : {
"duration" : 180 ,
"emailTemplate" : {
"body" : "<p>Hello,</p>\n<p>Your one-time password is: <strong>{OTP}</strong></p>\n<p><i>If you didn't ask for the one-time password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "OTP for {APP_NAME}"
} ,
"enabled" : false ,
"length" : 8
} ,
"passwordAuth" : {
"enabled" : true ,
"identityFields" : [
"email"
]
} ,
"passwordResetToken" : {
"duration" : 1800
} ,
"resetPasswordTemplate" : {
"body" : "<p>Hello,</p>\n<p>Click on the button below to reset your password.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-password-reset/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Reset password</a>\n</p>\n<p><i>If you didn't ask to reset your password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Reset your {APP_NAME} password"
} ,
"system" : false ,
"type" : "auth" ,
2022-11-28 19:59:17 +02:00
"updateRule" : null ,
2024-09-29 19:23:19 +03:00
"verificationTemplate" : {
"body" : "<p>Hello,</p>\n<p>Thank you for joining us at {APP_NAME}.</p>\n<p>Click on the button below to verify your email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-verification/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Verify</a>\n</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Verify your {APP_NAME} email"
} ,
"verificationToken" : {
"duration" : 259200
} ,
"viewRule" : "id = \"1\""
2022-12-05 13:57:09 +02:00
} ) ;
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
return app . save ( collection ) ;
2022-11-28 19:59:17 +02:00
} )
` ,
} ,
{
migratecmd . TemplateLangGo ,
`
package _test_migrations
import (
"encoding/json"
2024-09-29 19:23:19 +03:00
"github.com/pocketbase/pocketbase/core"
2022-11-28 19:59:17 +02:00
m "github.com/pocketbase/pocketbase/migrations"
)
func init ( ) {
2024-09-29 19:23:19 +03:00
m . Register ( func ( app core . App ) error {
2024-10-29 20:08:16 +02:00
collection , err := app . FindCollectionByNameOrId ( "@TEST_RANDOM" )
2022-11-28 19:59:17 +02:00
if err != nil {
return err
}
2024-09-29 19:23:19 +03:00
return app . Delete ( collection )
} , func ( app core . App ) error {
2022-11-28 19:59:17 +02:00
jsonData := ` + " ` " + ` {
2024-09-29 19:23:19 +03:00
"authAlert" : {
"emailTemplate" : {
"body" : "<p>Hello,</p>\n<p>We noticed a login to your {APP_NAME} account from a new location.</p>\n<p>If this was you, you may disregard this email.</p>\n<p><strong>If this wasn't you, you should immediately change your {APP_NAME} account password to revoke access from all other locations.</strong></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Login from a new location"
} ,
"enabled" : true
} ,
"authRule" : "" ,
"authToken" : {
"duration" : 604800
} ,
"confirmEmailChangeTemplate" : {
"body" : "<p>Hello,</p>\n<p>Click on the button below to confirm your new email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-email-change/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Confirm new email</a>\n</p>\n<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Confirm your {APP_NAME} new email address"
} ,
"createRule" : null ,
"deleteRule" : null ,
"emailChangeToken" : {
"duration" : 1800
} ,
"fields" : [
{
"autogeneratePattern" : "[a-z0-9]{15}" ,
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "text@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 15 ,
"min" : 15 ,
"name" : "id" ,
"pattern" : "^[a-z0-9]+$" ,
"presentable" : false ,
"primaryKey" : true ,
"required" : true ,
"system" : true ,
"type" : "text"
} ,
{
"cost" : 0 ,
"hidden" : true ,
2024-10-29 20:08:16 +02:00
"id" : "password@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 0 ,
"min" : 8 ,
"name" : "password" ,
"pattern" : "" ,
"presentable" : false ,
"required" : true ,
"system" : true ,
"type" : "password"
} ,
{
"autogeneratePattern" : "[a-zA-Z0-9]{50}" ,
"hidden" : true ,
2024-10-29 20:08:16 +02:00
"id" : "text@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"max" : 60 ,
"min" : 30 ,
"name" : "tokenKey" ,
"pattern" : "" ,
"presentable" : false ,
"primaryKey" : false ,
"required" : true ,
"system" : true ,
"type" : "text"
} ,
{
"exceptDomains" : null ,
"hidden" : false ,
"id" : "email3885137012" ,
"name" : "email" ,
"onlyDomains" : null ,
"presentable" : false ,
"required" : true ,
"system" : true ,
"type" : "email"
} ,
{
"hidden" : false ,
2024-10-29 20:08:16 +02:00
"id" : "bool@TEST_RANDOM" ,
2024-09-29 19:23:19 +03:00
"name" : "emailVisibility" ,
"presentable" : false ,
"required" : false ,
"system" : true ,
"type" : "bool"
} ,
{
"hidden" : false ,
"id" : "bool256245529" ,
"name" : "verified" ,
"presentable" : false ,
"required" : false ,
"system" : true ,
"type" : "bool"
}
] ,
"fileToken" : {
"duration" : 180
} ,
2024-10-29 20:08:16 +02:00
"id" : "@TEST_RANDOM" ,
2023-03-19 16:02:29 +02:00
"indexes" : [
2024-09-29 19:23:19 +03:00
"create index test on test123 (id)" ,
2024-10-29 20:08:16 +02:00
"CREATE UNIQUE INDEX ` + " ` + \" ` \ " + `" + ` idx_tokenKey_@TEST_RANDOM ` + "` + \"`\" + `" + ` ON ` + "` + \"`\" + `" + ` test123 ` + "` + \"`\" + `" + ` ( ` + "` + \"`\" + `" + ` tokenKey ` + "` + \"`\" + `" + ` ) " ,
"CREATE UNIQUE INDEX ` + " ` + \" ` \ " + `" + ` idx_email_@TEST_RANDOM ` + "` + \"`\" + `" + ` ON ` + "` + \"`\" + `" + ` test123 ` + "` + \"`\" + `" + ` ( ` + "` + \"`\" + `" + ` email ` + "` + \"`\" + `" + ` ) WHERE ` + "` + \"`\" + `" + ` email ` + "` + \"`\" + `" + ` != ' ' "
2023-03-19 16:02:29 +02:00
] ,
2024-09-29 19:23:19 +03:00
"listRule" : "@request.auth.id != '' && 1 > 0 || 'backtick` + " ` + \" ` \ " + `" + ` test ' = 0 " ,
"manageRule" : "1 != 2" ,
"mfa" : {
"duration" : 1800 ,
"enabled" : false ,
"rule" : ""
} ,
"name" : "test123" ,
"oauth2" : {
"enabled" : false ,
"mappedFields" : {
"avatarURL" : "" ,
"id" : "" ,
"name" : "" ,
"username" : ""
}
} ,
"otp" : {
"duration" : 180 ,
"emailTemplate" : {
"body" : "<p>Hello,</p>\n<p>Your one-time password is: <strong>{OTP}</strong></p>\n<p><i>If you didn't ask for the one-time password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "OTP for {APP_NAME}"
} ,
"enabled" : false ,
"length" : 8
} ,
"passwordAuth" : {
"enabled" : true ,
"identityFields" : [
"email"
]
} ,
"passwordResetToken" : {
"duration" : 1800
} ,
"resetPasswordTemplate" : {
"body" : "<p>Hello,</p>\n<p>Click on the button below to reset your password.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-password-reset/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Reset password</a>\n</p>\n<p><i>If you didn't ask to reset your password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Reset your {APP_NAME} password"
} ,
"system" : false ,
"type" : "auth" ,
2022-11-28 19:59:17 +02:00
"updateRule" : null ,
2024-09-29 19:23:19 +03:00
"verificationTemplate" : {
"body" : "<p>Hello,</p>\n<p>Thank you for joining us at {APP_NAME}.</p>\n<p>Click on the button below to verify your email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-verification/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Verify</a>\n</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>" ,
"subject" : "Verify your {APP_NAME} email"
} ,
"verificationToken" : {
"duration" : 259200
} ,
"viewRule" : "id = \"1\""
2022-11-28 19:59:17 +02:00
} ` + " ` " + `
2024-09-29 19:23:19 +03:00
collection := & core . Collection { }
2022-11-28 19:59:17 +02:00
if err := json . Unmarshal ( [ ] byte ( jsonData ) , & collection ) ; err != nil {
return err
}
2024-09-29 19:23:19 +03:00
return app . Save ( collection )
2022-11-28 19:59:17 +02:00
} )
}
` ,
} ,
}
2024-09-29 19:23:19 +03:00
for _ , s := range scenarios {
t . Run ( s . lang , func ( t * testing . T ) {
2023-12-06 11:57:04 +02:00
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
migrationsDir := filepath . Join ( app . DataDir ( ) , "_test_migrations" )
2024-09-29 19:23:19 +03:00
// create dummy collection
collection := core . NewAuthCollection ( "test123" )
collection . ListRule = types . Pointer ( "@request.auth.id != '' && 1 > 0 || 'backtick`test' = 0" )
collection . ViewRule = types . Pointer ( ` id = "1" ` )
collection . Indexes = types . JSONArray [ string ] { "create index test on test123 (id)" }
collection . ManageRule = types . Pointer ( "1 != 2" )
if err := app . Save ( collection ) ; err != nil {
t . Fatalf ( "Failed to save dummy collection, got: %v" , err )
}
2023-12-06 11:57:04 +02:00
migratecmd . MustRegister ( app , nil , migratecmd . Config {
TemplateLang : s . lang ,
Automigrate : true ,
Dir : migrationsDir ,
} )
app . Bootstrap ( )
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
// delete the newly created dummy collection (with mock request event)
event := new ( core . CollectionRequestEvent )
event . RequestEvent = & core . RequestEvent { }
event . App = app
event . Collection = collection
err := app . OnCollectionDeleteRequest ( ) . Trigger ( event , func ( e * core . CollectionRequestEvent ) error {
return e . App . Delete ( e . Collection )
} )
if err != nil {
t . Fatalf ( "Failed to delete dummy collection, got: %v" , err )
2023-12-06 11:57:04 +02:00
}
2022-11-28 19:59:17 +02:00
2023-12-06 11:57:04 +02:00
files , err := os . ReadDir ( migrationsDir )
if err != nil {
t . Fatalf ( "Expected migrationsDir to be created, got: %v" , err )
}
2022-11-28 19:59:17 +02:00
2023-12-06 11:57:04 +02:00
if total := len ( files ) ; total != 1 {
t . Fatalf ( "Expected 1 file to be generated, got %d" , total )
}
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
expectedName := "_deleted_test123." + s . lang
2023-12-06 11:57:04 +02:00
if ! strings . Contains ( files [ 0 ] . Name ( ) , expectedName ) {
t . Fatalf ( "Expected filename to contains %q, got %q" , expectedName , files [ 0 ] . Name ( ) )
}
2022-11-28 19:59:17 +02:00
2023-12-06 11:57:04 +02:00
fullPath := filepath . Join ( migrationsDir , files [ 0 ] . Name ( ) )
content , err := os . ReadFile ( fullPath )
if err != nil {
t . Fatalf ( "Failed to read the generated migration file: %v" , err )
}
2024-10-29 20:08:16 +02:00
contentStr := strings . TrimSpace ( string ( content ) )
// replace @TEST_RANDOM placeholder with a regex pattern
expectedTemplate := strings . ReplaceAll (
"^" + regexp . QuoteMeta ( strings . TrimSpace ( s . expectedTemplate ) ) + "$" ,
"@TEST_RANDOM" ,
` \w+ ` ,
)
if ! list . ExistInSliceWithRegex ( contentStr , [ ] string { expectedTemplate } ) {
t . Fatalf ( "Expected template \n%v \ngot \n%v" , s . expectedTemplate , contentStr )
2023-12-06 11:57:04 +02:00
}
} )
2022-11-28 19:59:17 +02:00
}
}
func TestAutomigrateCollectionUpdate ( t * testing . T ) {
2024-09-29 19:23:19 +03:00
t . Parallel ( )
2022-11-28 19:59:17 +02:00
scenarios := [ ] struct {
lang string
expectedTemplate string
} {
{
migratecmd . TemplateLangJS ,
`
2023-06-27 14:45:04 +03:00
/// <reference path="../pb_data/types.d.ts" />
2024-09-29 19:23:19 +03:00
migrate ( ( app ) = > {
2024-10-29 20:08:16 +02:00
const collection = app . findCollectionByNameOrId ( "@TEST_RANDOM" )
2024-09-29 19:23:19 +03:00
// update collection data
unmarshal ( {
"createRule" : "id = \"nil_update\"" ,
"deleteRule" : null ,
"fileToken" : {
"duration" : 10
} ,
"indexes" : [
"create index test1 on test123_update (f1_name)" ,
2024-10-29 20:08:16 +02:00
"CREATE UNIQUE INDEX ` + " ` " + ` idx_tokenKey_ @ TEST_RANDOM ` + " ` " + ` ON ` + " ` " + ` test123_update ` + " ` " + ` (` + " ` " + ` tokenKey ` + " ` " + `)" ,
"CREATE UNIQUE INDEX ` + " ` " + ` idx_email_ @ TEST_RANDOM ` + " ` " + ` ON ` + " ` " + ` test123_update ` + " ` " + ` (` + " ` " + ` email ` + " ` " + `) WHERE ` + " ` " + ` email ` + " ` " + ` != ''"
2024-09-29 19:23:19 +03:00
] ,
"listRule" : "@request.auth.id != ''" ,
"name" : "test123_update" ,
"oauth2" : {
"enabled" : true
} ,
"updateRule" : "id = \"2_update\""
} , collection )
// remove field
collection . fields . removeById ( "f3_id" )
// add field
2024-11-24 12:41:57 +02:00
collection . fields . addAt ( 8 , new Field ( {
2024-09-29 19:23:19 +03:00
"autogeneratePattern" : "" ,
"hidden" : false ,
2022-11-28 19:59:17 +02:00
"id" : "f4_id" ,
2024-09-29 19:23:19 +03:00
"max" : 0 ,
"min" : 0 ,
2022-11-28 19:59:17 +02:00
"name" : "f4_name" ,
2024-09-29 19:23:19 +03:00
"pattern" : "` + " ` " + ` test backtick ` + " ` " + `123" ,
2023-08-21 12:58:18 +03:00
"presentable" : false ,
2024-09-29 19:23:19 +03:00
"primaryKey" : false ,
"required" : false ,
"system" : false ,
"type" : "text"
2022-12-05 13:57:09 +02:00
} ) )
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
// update field
2024-11-24 15:02:28 +02:00
collection . fields . addAt ( 7 , new Field ( {
2024-09-29 19:23:19 +03:00
"hidden" : false ,
2022-11-28 19:59:17 +02:00
"id" : "f2_id" ,
2024-09-29 19:23:19 +03:00
"max" : null ,
"min" : 10 ,
2022-11-28 19:59:17 +02:00
"name" : "f2_name_new" ,
2024-09-29 19:23:19 +03:00
"onlyInt" : false ,
2023-08-21 12:58:18 +03:00
"presentable" : false ,
2024-09-29 19:23:19 +03:00
"required" : false ,
"system" : false ,
"type" : "number"
2022-12-05 13:57:09 +02:00
} ) )
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
return app . save ( collection )
} , ( app ) = > {
2024-10-29 20:08:16 +02:00
const collection = app . findCollectionByNameOrId ( "@TEST_RANDOM" )
2024-09-29 19:23:19 +03:00
// update collection data
unmarshal ( {
"createRule" : null ,
"deleteRule" : "id = \"3\"" ,
"fileToken" : {
"duration" : 180
} ,
"indexes" : [
"create index test1 on test123 (f1_name)" ,
2024-10-29 20:08:16 +02:00
"CREATE UNIQUE INDEX ` + " ` " + ` idx_tokenKey_ @ TEST_RANDOM ` + " ` " + ` ON ` + " ` " + ` test123 ` + " ` " + ` (` + " ` " + ` tokenKey ` + " ` " + `)" ,
"CREATE UNIQUE INDEX ` + " ` " + ` idx_email_ @ TEST_RANDOM ` + " ` " + ` ON ` + " ` " + ` test123 ` + " ` " + ` (` + " ` " + ` email ` + " ` " + `) WHERE ` + " ` " + ` email ` + " ` " + ` != ''"
2024-09-29 19:23:19 +03:00
] ,
"listRule" : "@request.auth.id != '' && 1 != 2" ,
"name" : "test123" ,
"oauth2" : {
"enabled" : false
} ,
"updateRule" : "id = \"2\""
} , collection )
// add field
2024-11-24 12:41:57 +02:00
collection . fields . addAt ( 8 , new Field ( {
2024-09-29 19:23:19 +03:00
"hidden" : false ,
2022-11-28 19:59:17 +02:00
"id" : "f3_id" ,
"name" : "f3_name" ,
2023-08-21 12:58:18 +03:00
"presentable" : false ,
2024-09-29 19:23:19 +03:00
"required" : false ,
"system" : false ,
"type" : "bool"
2022-12-05 13:57:09 +02:00
} ) )
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
// remove field
collection . fields . removeById ( "f4_id" )
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
// update field
2024-11-24 15:02:28 +02:00
collection . fields . addAt ( 7 , new Field ( {
2024-09-29 19:23:19 +03:00
"hidden" : false ,
2022-11-28 19:59:17 +02:00
"id" : "f2_id" ,
2024-09-29 19:23:19 +03:00
"max" : null ,
"min" : 10 ,
2022-11-28 19:59:17 +02:00
"name" : "f2_name" ,
2024-09-29 19:23:19 +03:00
"onlyInt" : false ,
2023-08-21 12:58:18 +03:00
"presentable" : false ,
2024-09-29 19:23:19 +03:00
"required" : false ,
"system" : false ,
"type" : "number"
2022-12-05 13:57:09 +02:00
} ) )
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
return app . save ( collection )
2022-11-28 19:59:17 +02:00
} )
2024-09-29 19:23:19 +03:00
2022-11-28 19:59:17 +02:00
` ,
} ,
{
migratecmd . TemplateLangGo ,
`
package _test_migrations
import (
"encoding/json"
2024-09-29 19:23:19 +03:00
"github.com/pocketbase/pocketbase/core"
2022-11-28 19:59:17 +02:00
m "github.com/pocketbase/pocketbase/migrations"
)
func init ( ) {
2024-09-29 19:23:19 +03:00
m . Register ( func ( app core . App ) error {
2024-10-29 20:08:16 +02:00
collection , err := app . FindCollectionByNameOrId ( "@TEST_RANDOM" )
2022-11-28 19:59:17 +02:00
if err != nil {
return err
}
2024-09-29 19:23:19 +03:00
// update collection data
if err := json . Unmarshal ( [ ] byte ( ` + " ` " + ` {
"createRule" : "id = \"nil_update\"" ,
"deleteRule" : null ,
"fileToken" : {
"duration" : 10
} ,
"indexes" : [
"create index test1 on test123_update (f1_name)" ,
2024-10-29 20:08:16 +02:00
"CREATE UNIQUE INDEX ` + " ` + \" ` \ " + `" + ` idx_tokenKey_@TEST_RANDOM ` + "` + \"`\" + `" + ` ON ` + "` + \"`\" + `" + ` test123_update ` + "` + \"`\" + `" + ` ( ` + "` + \"`\" + `" + ` tokenKey ` + "` + \"`\" + `" + ` ) " ,
"CREATE UNIQUE INDEX ` + " ` + \" ` \ " + `" + ` idx_email_@TEST_RANDOM ` + "` + \"`\" + `" + ` ON ` + "` + \"`\" + `" + ` test123_update ` + "` + \"`\" + `" + ` ( ` + "` + \"`\" + `" + ` email ` + "` + \"`\" + `" + ` ) WHERE ` + "` + \"`\" + `" + ` email ` + "` + \"`\" + `" + ` != ' ' "
2024-09-29 19:23:19 +03:00
] ,
"listRule" : "@request.auth.id != ''" ,
"name" : "test123_update" ,
"oauth2" : {
"enabled" : true
} ,
"updateRule" : "id = \"2_update\""
} ` + " ` " + ` ) , & collection ) ; err != nil {
2024-02-29 04:17:59 +02:00
return err
}
2023-03-19 16:02:29 +02:00
2024-09-29 19:23:19 +03:00
// remove field
collection . Fields . RemoveById ( "f3_id" )
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
// add field
2024-11-24 12:41:57 +02:00
if err := collection . Fields . AddMarshaledJSONAt ( 8 , [ ] byte ( ` + " ` " + ` {
2024-09-29 19:23:19 +03:00
"autogeneratePattern" : "" ,
"hidden" : false ,
2022-11-28 19:59:17 +02:00
"id" : "f4_id" ,
2024-09-29 19:23:19 +03:00
"max" : 0 ,
"min" : 0 ,
2022-11-28 19:59:17 +02:00
"name" : "f4_name" ,
2024-09-29 19:23:19 +03:00
"pattern" : "` + " ` + \" ` \ " + `" + ` test backtick ` + "` + \"`\" + `" + ` 123 " ,
2023-08-21 12:58:18 +03:00
"presentable" : false ,
2024-09-29 19:23:19 +03:00
"primaryKey" : false ,
"required" : false ,
"system" : false ,
"type" : "text"
2024-10-09 17:11:14 +03:00
} ` + " ` " + ` ) ) ; err != nil {
2024-02-29 04:17:59 +02:00
return err
}
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
// update field
2024-11-24 15:02:28 +02:00
if err := collection . Fields . AddMarshaledJSONAt ( 7 , [ ] byte ( ` + " ` " + ` {
2024-09-29 19:23:19 +03:00
"hidden" : false ,
2022-11-28 19:59:17 +02:00
"id" : "f2_id" ,
2024-09-29 19:23:19 +03:00
"max" : null ,
"min" : 10 ,
2022-11-28 19:59:17 +02:00
"name" : "f2_name_new" ,
2024-09-29 19:23:19 +03:00
"onlyInt" : false ,
2023-08-21 12:58:18 +03:00
"presentable" : false ,
2024-09-29 19:23:19 +03:00
"required" : false ,
"system" : false ,
"type" : "number"
2024-10-09 17:11:14 +03:00
} ` + " ` " + ` ) ) ; err != nil {
2024-02-29 04:17:59 +02:00
return err
}
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
return app . Save ( collection )
} , func ( app core . App ) error {
2024-10-29 20:08:16 +02:00
collection , err := app . FindCollectionByNameOrId ( "@TEST_RANDOM" )
2022-11-28 19:59:17 +02:00
if err != nil {
return err
}
2024-09-29 19:23:19 +03:00
// update collection data
2024-02-29 04:17:59 +02:00
if err := json . Unmarshal ( [ ] byte ( ` + " ` " + ` {
2024-09-29 19:23:19 +03:00
"createRule" : null ,
"deleteRule" : "id = \"3\"" ,
"fileToken" : {
"duration" : 180
} ,
"indexes" : [
"create index test1 on test123 (f1_name)" ,
2024-10-29 20:08:16 +02:00
"CREATE UNIQUE INDEX ` + " ` + \" ` \ " + `" + ` idx_tokenKey_@TEST_RANDOM ` + "` + \"`\" + `" + ` ON ` + "` + \"`\" + `" + ` test123 ` + "` + \"`\" + `" + ` ( ` + "` + \"`\" + `" + ` tokenKey ` + "` + \"`\" + `" + ` ) " ,
"CREATE UNIQUE INDEX ` + " ` + \" ` \ " + `" + ` idx_email_@TEST_RANDOM ` + "` + \"`\" + `" + ` ON ` + "` + \"`\" + `" + ` test123 ` + "` + \"`\" + `" + ` ( ` + "` + \"`\" + `" + ` email ` + "` + \"`\" + `" + ` ) WHERE ` + "` + \"`\" + `" + ` email ` + "` + \"`\" + `" + ` != ' ' "
2024-09-29 19:23:19 +03:00
] ,
"listRule" : "@request.auth.id != '' && 1 != 2" ,
"name" : "test123" ,
"oauth2" : {
"enabled" : false
} ,
"updateRule" : "id = \"2\""
} ` + " ` " + ` ) , & collection ) ; err != nil {
2024-02-29 04:17:59 +02:00
return err
}
2023-03-19 16:02:29 +02:00
2024-09-29 19:23:19 +03:00
// add field
2024-11-24 12:41:57 +02:00
if err := collection . Fields . AddMarshaledJSONAt ( 8 , [ ] byte ( ` + " ` " + ` {
2024-09-29 19:23:19 +03:00
"hidden" : false ,
2022-11-28 19:59:17 +02:00
"id" : "f3_id" ,
"name" : "f3_name" ,
2023-08-21 12:58:18 +03:00
"presentable" : false ,
2024-09-29 19:23:19 +03:00
"required" : false ,
"system" : false ,
"type" : "bool"
2024-10-09 17:11:14 +03:00
} ` + " ` " + ` ) ) ; err != nil {
2024-02-29 04:17:59 +02:00
return err
}
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
// remove field
collection . Fields . RemoveById ( "f4_id" )
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
// update field
2024-11-24 15:02:28 +02:00
if err := collection . Fields . AddMarshaledJSONAt ( 7 , [ ] byte ( ` + " ` " + ` {
2024-09-29 19:23:19 +03:00
"hidden" : false ,
2022-11-28 19:59:17 +02:00
"id" : "f2_id" ,
2024-09-29 19:23:19 +03:00
"max" : null ,
"min" : 10 ,
2022-11-28 19:59:17 +02:00
"name" : "f2_name" ,
2024-09-29 19:23:19 +03:00
"onlyInt" : false ,
2023-08-21 12:58:18 +03:00
"presentable" : false ,
2024-09-29 19:23:19 +03:00
"required" : false ,
"system" : false ,
"type" : "number"
2024-10-09 17:11:14 +03:00
} ` + " ` " + ` ) ) ; err != nil {
2024-02-29 04:17:59 +02:00
return err
}
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
return app . Save ( collection )
2022-11-28 19:59:17 +02:00
} )
}
` ,
} ,
}
2024-09-29 19:23:19 +03:00
for _ , s := range scenarios {
t . Run ( s . lang , func ( t * testing . T ) {
2023-12-06 11:57:04 +02:00
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
migrationsDir := filepath . Join ( app . DataDir ( ) , "_test_migrations" )
// create dummy collection
2024-09-29 19:23:19 +03:00
collection := core . NewAuthCollection ( "test123" )
collection . ListRule = types . Pointer ( "@request.auth.id != '' && 1 != 2" )
2023-12-06 11:57:04 +02:00
collection . ViewRule = types . Pointer ( ` id = "1" ` )
collection . UpdateRule = types . Pointer ( ` id = "2" ` )
collection . CreateRule = nil
collection . DeleteRule = types . Pointer ( ` id = "3" ` )
2024-09-29 19:23:19 +03:00
collection . Indexes = types . JSONArray [ string ] { "create index test1 on test123 (f1_name)" }
collection . ManageRule = types . Pointer ( "1 != 2" )
collection . Fields . Add ( & core . TextField {
2023-12-06 11:57:04 +02:00
Id : "f1_id" ,
Name : "f1_name" ,
Required : true ,
} )
2024-09-29 19:23:19 +03:00
collection . Fields . Add ( & core . NumberField {
Id : "f2_id" ,
Name : "f2_name" ,
Min : types . Pointer ( 10.0 ) ,
2023-12-06 11:57:04 +02:00
} )
2024-09-29 19:23:19 +03:00
collection . Fields . Add ( & core . BoolField {
2023-12-06 11:57:04 +02:00
Id : "f3_id" ,
Name : "f3_name" ,
} )
2024-09-29 19:23:19 +03:00
if err := app . Save ( collection ) ; err != nil {
2023-12-06 11:57:04 +02:00
t . Fatalf ( "Failed to save dummy collection, got %v" , err )
}
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
// init plugin
migratecmd . MustRegister ( app , nil , migratecmd . Config {
TemplateLang : s . lang ,
Automigrate : true ,
Dir : migrationsDir ,
} )
2023-12-06 11:57:04 +02:00
app . Bootstrap ( )
2024-09-29 19:23:19 +03:00
// update the dummy collection
collection . Name = "test123_update"
2023-12-06 11:57:04 +02:00
collection . ListRule = types . Pointer ( "@request.auth.id != ''" )
collection . ViewRule = types . Pointer ( ` id = "1" ` ) // no change
collection . UpdateRule = types . Pointer ( ` id = "2_update" ` )
collection . CreateRule = types . Pointer ( ` id = "nil_update" ` )
collection . DeleteRule = nil
2024-09-29 19:23:19 +03:00
collection . Indexes = types . JSONArray [ string ] {
"create index test1 on test123_update (f1_name)" ,
2023-12-06 11:57:04 +02:00
}
2024-09-29 19:23:19 +03:00
collection . Fields . RemoveById ( "f3_id" )
collection . Fields . Add ( & core . TextField {
Id : "f4_id" ,
Name : "f4_name" ,
Pattern : "`test backtick`123" ,
2023-12-06 11:57:04 +02:00
} )
2024-09-29 19:23:19 +03:00
f := collection . Fields . GetById ( "f2_id" )
f . SetName ( "f2_name_new" )
collection . OAuth2 . Enabled = true
collection . FileToken . Duration = 10
// should be ignored
collection . OAuth2 . Providers = [ ] core . OAuth2ProviderConfig { { Name : "gitlab" , ClientId : "abc" , ClientSecret : "123" } }
testSecret := strings . Repeat ( "b" , 30 )
collection . AuthToken . Secret = testSecret
collection . FileToken . Secret = testSecret
collection . EmailChangeToken . Secret = testSecret
collection . PasswordResetToken . Secret = testSecret
collection . VerificationToken . Secret = testSecret
// save the changes and trigger automigrate (with mock request event)
event := new ( core . CollectionRequestEvent )
event . RequestEvent = & core . RequestEvent { }
event . App = app
event . Collection = collection
err := app . OnCollectionUpdateRequest ( ) . Trigger ( event , func ( e * core . CollectionRequestEvent ) error {
return e . App . Save ( e . Collection )
} )
if err != nil {
2023-12-06 11:57:04 +02:00
t . Fatalf ( "Failed to save dummy collection changes, got %v" , err )
}
2022-11-28 19:59:17 +02:00
2023-12-06 11:57:04 +02:00
files , err := os . ReadDir ( migrationsDir )
if err != nil {
t . Fatalf ( "Expected migrationsDir to be created, got: %v" , err )
}
2022-11-28 19:59:17 +02:00
2023-12-06 11:57:04 +02:00
if total := len ( files ) ; total != 1 {
t . Fatalf ( "Expected 1 file to be generated, got %d" , total )
}
2022-11-28 19:59:17 +02:00
2024-09-29 19:23:19 +03:00
expectedName := "_updated_test123." + s . lang
2023-12-06 11:57:04 +02:00
if ! strings . Contains ( files [ 0 ] . Name ( ) , expectedName ) {
t . Fatalf ( "Expected filename to contains %q, got %q" , expectedName , files [ 0 ] . Name ( ) )
}
2022-11-28 19:59:17 +02:00
2023-12-06 11:57:04 +02:00
fullPath := filepath . Join ( migrationsDir , files [ 0 ] . Name ( ) )
content , err := os . ReadFile ( fullPath )
if err != nil {
t . Fatalf ( "Failed to read the generated migration file: %v" , err )
}
2024-10-29 20:08:16 +02:00
contentStr := strings . TrimSpace ( string ( content ) )
// replace @TEST_RANDOM placeholder with a regex pattern
expectedTemplate := strings . ReplaceAll (
"^" + regexp . QuoteMeta ( strings . TrimSpace ( s . expectedTemplate ) ) + "$" ,
"@TEST_RANDOM" ,
` \w+ ` ,
)
if ! list . ExistInSliceWithRegex ( contentStr , [ ] string { expectedTemplate } ) {
t . Fatalf ( "Expected template \n%v \ngot \n%v" , s . expectedTemplate , contentStr )
2023-12-06 11:57:04 +02:00
}
2022-11-28 19:59:17 +02:00
} )
}
}
2022-12-02 12:36:57 +02:00
func TestAutomigrateCollectionNoChanges ( t * testing . T ) {
2024-09-29 19:23:19 +03:00
t . Parallel ( )
2022-12-02 12:36:57 +02:00
scenarios := [ ] struct {
lang string
} {
{
migratecmd . TemplateLangJS ,
} ,
{
migratecmd . TemplateLangGo ,
} ,
}
2024-09-29 19:23:19 +03:00
for _ , s := range scenarios {
t . Run ( s . lang , func ( t * testing . T ) {
app , _ := tests . NewTestApp ( )
defer app . Cleanup ( )
2022-12-02 12:36:57 +02:00
2024-09-29 19:23:19 +03:00
migrationsDir := filepath . Join ( app . DataDir ( ) , "_test_migrations" )
2022-12-02 12:36:57 +02:00
2024-09-29 19:23:19 +03:00
// create dummy collection
collection := core . NewAuthCollection ( "test123" )
2022-12-02 12:36:57 +02:00
2024-09-29 19:23:19 +03:00
if err := app . Save ( collection ) ; err != nil {
t . Fatalf ( "Failed to save dummy collection, got %v" , err )
}
2022-12-02 12:36:57 +02:00
2024-09-29 19:23:19 +03:00
// init plugin
migratecmd . MustRegister ( app , nil , migratecmd . Config {
TemplateLang : s . lang ,
Automigrate : true ,
Dir : migrationsDir ,
} )
app . Bootstrap ( )
2022-12-02 12:36:57 +02:00
2024-09-29 19:23:19 +03:00
// should be ignored
collection . OAuth2 . Providers = [ ] core . OAuth2ProviderConfig { { Name : "gitlab" , ClientId : "abc" , ClientSecret : "123" } }
testSecret := strings . Repeat ( "b" , 30 )
collection . AuthToken . Secret = testSecret
collection . FileToken . Secret = testSecret
collection . EmailChangeToken . Secret = testSecret
collection . PasswordResetToken . Secret = testSecret
collection . VerificationToken . Secret = testSecret
// resave without other changes and trigger automigrate (with mock request event)
event := new ( core . CollectionRequestEvent )
event . RequestEvent = & core . RequestEvent { }
event . App = app
event . Collection = collection
err := app . OnCollectionUpdateRequest ( ) . Trigger ( event , func ( e * core . CollectionRequestEvent ) error {
return e . App . Save ( e . Collection )
} )
if err != nil {
t . Fatalf ( "Failed to save dummy collection update, got %v" , err )
}
2022-12-02 12:36:57 +02:00
2024-09-29 19:23:19 +03:00
files , _ := os . ReadDir ( migrationsDir )
if total := len ( files ) ; total != 0 {
t . Fatalf ( "Expected 0 files to be generated, got %d" , total )
}
} )
2022-12-02 12:36:57 +02:00
}
}