package commands import ( "fmt" "io" "os" "os/exec" "path/filepath" "strings" "testing" "time" "github.com/leandro-lugaresi/hub" "github.com/stretchr/testify/assert" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" ) func TestMigrationCommand(t *testing.T) { t.Run("NoMigrateSettings", func(t *testing.T) { // Run command with test context. output, err := RunWithTestContext(MigrationsCommands, []string{"migrations", "transfer"}) // Check command output for plausibility. // t.Logf(output) assert.Error(t, err) if err != nil { assert.Contains(t, err.Error(), "config: transfer config must be provided") } assert.Equal(t, "", output) }) t.Run("InvalidCommand", func(t *testing.T) { // Run command with test context. output, err := RunWithTestContext(MigrationsCommands, []string{"migrations", "--magles"}) // Check command output for plausibility. // t.Logf(output) assert.Error(t, err) if err != nil { assert.Contains(t, err.Error(), "flag provided but not defined: -magles") } assert.Contains(t, output, "flag provided but not defined: -magles") }) t.Run("TargetPopulated", func(t *testing.T) { // Setup target database os.Remove("/go/src/github.com/photoprism/photoprism/storage/targetpopulated.test.db") if err := copyFile("/go/src/github.com/photoprism/photoprism/internal/commands/testdata/transfer_sqlite3", "/go/src/github.com/photoprism/photoprism/storage/targetpopulated.test.db"); err != nil { t.Fatal(err.Error()) } // Run command with test context. log = event.Log appArgs := []string{"photoprism", "--database-driver", "mysql", "--database-dsn", "migrate:migrate@tcp(mariadb:4001)/migrate?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s", "--transfer-driver", "sqlite", "--transfer-dsn", "/go/src/github.com/photoprism/photoprism/storage/targetpopulated.test.db?_busy_timeout=5000&_foreign_keys=on"} cmdArgs := []string{"migrations", "transfer"} ctx := NewTestContextWithParse(appArgs, cmdArgs) s := event.Subscribe("log.info") defer event.Unsubscribe(s) var l string assert.IsType(t, hub.Subscription{}, s) go func() { for msg := range s.Receiver { l += msg.Fields["message"].(string) + "\n" } }() output, err := RunWithProvidedTestContext(ctx, MigrationsCommands, cmdArgs) // Check command output for plausibility. // t.Logf(output) assert.Error(t, err) assert.Contains(t, err.Error(), "migrate: transfer target database is not empty") assert.NotContains(t, output, "Usage") time.Sleep(time.Second) // Check command output. if l == "" { t.Fatal("log output missing") } assert.Contains(t, l, "migrate: transfer batch size set to 100") if !t.Failed() { os.Remove("/go/src/github.com/photoprism/photoprism/storage/targetpopulated.test.db") } }) t.Run("TargetPopulatedBatch500", func(t *testing.T) { // Setup target database os.Remove("/go/src/github.com/photoprism/photoprism/storage/targetpopulated.test.db") if err := copyFile("/go/src/github.com/photoprism/photoprism/internal/commands/testdata/transfer_sqlite3", "/go/src/github.com/photoprism/photoprism/storage/targetpopulated.test.db"); err != nil { t.Fatal(err.Error()) } // Run command with test context. log = event.Log appArgs := []string{"photoprism", "--database-driver", "mysql", "--database-dsn", "migrate:migrate@tcp(mariadb:4001)/migrate?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s", "--transfer-driver", "sqlite", "--transfer-dsn", "/go/src/github.com/photoprism/photoprism/storage/targetpopulated.test.db?_busy_timeout=5000&_foreign_keys=on"} cmdArgs := []string{"migrations", "transfer", "-batch", "500"} ctx := NewTestContextWithParse(appArgs, cmdArgs) s := event.Subscribe("log.info") defer event.Unsubscribe(s) var l string assert.IsType(t, hub.Subscription{}, s) go func() { for msg := range s.Receiver { l += msg.Fields["message"].(string) + "\n" } }() output, err := RunWithProvidedTestContext(ctx, MigrationsCommands, cmdArgs) // Check command output for plausibility. // t.Logf(output) assert.Error(t, err) assert.Contains(t, err.Error(), "migrate: transfer target database is not empty") assert.NotContains(t, output, "Usage") time.Sleep(time.Second) // Check command output. if l == "" { t.Fatal("log output missing") } assert.Contains(t, l, "migrate: transfer batch size set to 500") if !t.Failed() { os.Remove("/go/src/github.com/photoprism/photoprism/storage/targetpopulated.test.db") } }) t.Run("MySQLtoPostgreSQL", func(t *testing.T) { // Load migrate database as source if dumpName, err := filepath.Abs("./testdata/transfer_mysql"); err != nil { t.Fatal(err) } else if err = exec.Command("mariadb", "-u", "migrate", "-pmigrate", "migrate", "-e", "source "+dumpName).Run(); err != nil { t.Fatal(err) } // Clear PostgreSQL target (migrate) if dumpName, err := filepath.Abs("./testdata/reset-migrate.postgresql.sql"); err != nil { t.Fatal(err) } else { if err = exec.Command("psql", "postgresql://photoprism:photoprism@postgres:5432/postgres", "--file="+dumpName).Run(); err != nil { t.Fatal(err) } } // Run command with test context. log = event.Log appArgs := []string{"photoprism", "--database-driver", "mysql", "--database-dsn", "migrate:migrate@tcp(mariadb:4001)/migrate?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s", "--transfer-driver", "postgres", "--transfer-dsn", "postgresql://migrate:migrate@postgres:5432/migrate?TimeZone=UTC&connect_timeout=15&lock_timeout=5000&sslmode=disable"} cmdArgs := []string{"migrations", "transfer", "-batch", "10"} ctx := NewTestContextWithParse(appArgs, cmdArgs) s := event.Subscribe("log.info") defer event.Unsubscribe(s) var l string assert.IsType(t, hub.Subscription{}, s) go func() { for msg := range s.Receiver { l += msg.Fields["message"].(string) + "\n" } }() output, err := RunWithProvidedTestContext(ctx, MigrationsCommands, cmdArgs) // Check command output for plausibility. //t.Logf(output) if err != nil { assert.NoError(t, err) t.FailNow() } assert.NotContains(t, output, "Usage") time.Sleep(time.Second) // Check command output. if l == "" { t.Fatal("log output missing") } // t.Logf(l) assert.Contains(t, l, "migrate: transfer batch size set to 10") assert.Contains(t, l, "migrate: number of albums transfered 31") assert.Contains(t, l, "migrate: number of albumusers transfered 0") assert.Contains(t, l, "migrate: number of cameras transfered 6") assert.Contains(t, l, "migrate: number of categories transfered 1") assert.Contains(t, l, "migrate: number of cells transfered 9") assert.Contains(t, l, "migrate: number of clients transfered 7") assert.Contains(t, l, "migrate: number of countries transfered 1") assert.Contains(t, l, "migrate: number of duplicates transfered 0") assert.Contains(t, l, "migrate: number of errors transfered 0") assert.Contains(t, l, "migrate: number of faces transfered 7") assert.Contains(t, l, "migrate: number of files transfered 71") assert.Contains(t, l, "migrate: number of fileshares transfered 2") assert.Contains(t, l, "migrate: number of filesyncs transfered 3") assert.Contains(t, l, "migrate: number of folders transfered 3") assert.Contains(t, l, "migrate: number of keywords transfered 26") assert.Contains(t, l, "migrate: number of labels transfered 32") assert.Contains(t, l, "migrate: number of lenses transfered 2") assert.Contains(t, l, "migrate: number of links transfered 5") assert.Contains(t, l, "migrate: number of markers transfered 18") assert.Contains(t, l, "migrate: number of passcodes transfered 3") assert.Contains(t, l, "migrate: number of passwords transfered 11") assert.Contains(t, l, "migrate: number of photos transfered 58") assert.Contains(t, l, "migrate: number of photousers transfered 0") assert.Contains(t, l, "migrate: number of places transfered 10") assert.Contains(t, l, "migrate: number of reactions transfered 3") assert.Contains(t, l, "migrate: number of sessions transfered 21") assert.Contains(t, l, "migrate: number of services transfered 2") assert.Contains(t, l, "migrate: number of subjects transfered 6") assert.Contains(t, l, "migrate: number of users transfered 11") assert.Contains(t, l, "migrate: number of userdetails transfered 9") assert.Contains(t, l, "migrate: number of usersettings transfered 13") assert.Contains(t, l, "migrate: number of usershares transfered 1") // Make sure that a sequence update has worked. testdb, err := gorm.Open(postgres.Open("postgresql://migrate:migrate@postgres:5432/migrate?TimeZone=UTC&connect_timeout=15&lock_timeout=5000&sslmode=disable"), &gorm.Config{}) if err != nil { assert.NoError(t, err) t.FailNow() } lens := entity.Lens{LensSlug: "PhotoPrismTest Data Slug For Lens", LensName: "PhotoPrism Biocular", LensMake: "PhotoPrism", LensModel: "Short", LensType: "Mono", LensDescription: "Special Test Lens"} if result := testdb.Create(&lens); result.Error != nil { assert.NoError(t, result.Error) t.FailNow() } }) t.Run("MySQLtoSQLite", func(t *testing.T) { // Remove target database file os.Remove("/go/src/github.com/photoprism/photoprism/storage/mysqltosqlite.test.db") // Load migrate database as source if dumpName, err := filepath.Abs("./testdata/transfer_mysql"); err != nil { t.Fatal(err) } else if err = exec.Command("mariadb", "-u", "migrate", "-pmigrate", "migrate", "-e", "source "+dumpName).Run(); err != nil { t.Fatal(err) } // Run command with test context. log = event.Log appArgs := []string{"photoprism", "--database-driver", "mysql", "--database-dsn", "migrate:migrate@tcp(mariadb:4001)/migrate?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s", "--transfer-driver", "sqlite", "--transfer-dsn", "/go/src/github.com/photoprism/photoprism/storage/mysqltosqlite.test.db?_busy_timeout=5000&_foreign_keys=on"} cmdArgs := []string{"migrations", "transfer", "-batch", "1000"} ctx := NewTestContextWithParse(appArgs, cmdArgs) s := event.Subscribe("log.info") defer event.Unsubscribe(s) var l string assert.IsType(t, hub.Subscription{}, s) go func() { for msg := range s.Receiver { l += msg.Fields["message"].(string) + "\n" } }() output, err := RunWithProvidedTestContext(ctx, MigrationsCommands, cmdArgs) // Check command output for plausibility. //t.Logf(output) if err != nil { assert.NoError(t, err) t.FailNow() } assert.NotContains(t, output, "Usage") time.Sleep(time.Second) // Check command output. if l == "" { t.Fatal("log output missing") } // t.Logf(l) assert.Contains(t, l, "migrate: transfer batch size set to 1000") assert.Contains(t, l, "migrate: number of albums transfered 31") assert.Contains(t, l, "migrate: number of albumusers transfered 0") assert.Contains(t, l, "migrate: number of cameras transfered 6") assert.Contains(t, l, "migrate: number of categories transfered 1") assert.Contains(t, l, "migrate: number of cells transfered 9") assert.Contains(t, l, "migrate: number of clients transfered 7") assert.Contains(t, l, "migrate: number of countries transfered 1") assert.Contains(t, l, "migrate: number of duplicates transfered 0") assert.Contains(t, l, "migrate: number of errors transfered 0") assert.Contains(t, l, "migrate: number of faces transfered 7") assert.Contains(t, l, "migrate: number of files transfered 71") assert.Contains(t, l, "migrate: number of fileshares transfered 2") assert.Contains(t, l, "migrate: number of filesyncs transfered 3") assert.Contains(t, l, "migrate: number of folders transfered 3") assert.Contains(t, l, "migrate: number of keywords transfered 26") assert.Contains(t, l, "migrate: number of labels transfered 32") assert.Contains(t, l, "migrate: number of lenses transfered 2") assert.Contains(t, l, "migrate: number of links transfered 5") assert.Contains(t, l, "migrate: number of markers transfered 18") assert.Contains(t, l, "migrate: number of passcodes transfered 3") assert.Contains(t, l, "migrate: number of passwords transfered 11") assert.Contains(t, l, "migrate: number of photos transfered 58") assert.Contains(t, l, "migrate: number of photousers transfered 0") assert.Contains(t, l, "migrate: number of places transfered 10") assert.Contains(t, l, "migrate: number of reactions transfered 3") assert.Contains(t, l, "migrate: number of sessions transfered 21") assert.Contains(t, l, "migrate: number of services transfered 2") assert.Contains(t, l, "migrate: number of subjects transfered 6") assert.Contains(t, l, "migrate: number of users transfered 11") assert.Contains(t, l, "migrate: number of userdetails transfered 9") assert.Contains(t, l, "migrate: number of usersettings transfered 13") assert.Contains(t, l, "migrate: number of usershares transfered 1") // Make sure that a sequence update has worked. testdb, err := gorm.Open(sqlite.Open("/go/src/github.com/photoprism/photoprism/storage/mysqltosqlite.test.db?_busy_timeout=5000&_foreign_keys=on"), &gorm.Config{}) if err != nil { assert.NoError(t, err) t.FailNow() } lens := entity.Lens{LensSlug: "PhotoPrismTest Data Slug For Lens", LensName: "PhotoPrism Biocular", LensMake: "PhotoPrism", LensModel: "Short", LensType: "Mono", LensDescription: "Special Test Lens"} if result := testdb.Create(&lens); result.Error != nil { assert.NoError(t, result.Error) t.FailNow() } // Remove target database file if !t.Failed() { os.Remove("/go/src/github.com/photoprism/photoprism/storage/mysqltosqlite.test.db") } }) t.Run("MySQLtoSQLitePopulated", func(t *testing.T) { // Remove target database file os.Remove("/go/src/github.com/photoprism/photoprism/storage/mysqltosqlitepopulated.test.db") if err := copyFile("/go/src/github.com/photoprism/photoprism/internal/commands/testdata/transfer_sqlite3", "/go/src/github.com/photoprism/photoprism/storage/mysqltosqlitepopulated.test.db"); err != nil { t.Fatal(err.Error()) } // Load migrate database as source if dumpName, err := filepath.Abs("./testdata/transfer_mysql"); err != nil { t.Fatal(err) } else if err = exec.Command("mariadb", "-u", "migrate", "-pmigrate", "migrate", "-e", "source "+dumpName).Run(); err != nil { t.Fatal(err) } // Run command with test context. log = event.Log appArgs := []string{"photoprism", "--database-driver", "mysql", "--database-dsn", "migrate:migrate@tcp(mariadb:4001)/migrate?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s", "--transfer-driver", "sqlite", "--transfer-dsn", "/go/src/github.com/photoprism/photoprism/storage/mysqltosqlitepopulated.test.db?_busy_timeout=5000&_foreign_keys=on"} cmdArgs := []string{"migrations", "transfer", "-force"} ctx := NewTestContextWithParse(appArgs, cmdArgs) s := event.Subscribe("log.info") defer event.Unsubscribe(s) var l string assert.IsType(t, hub.Subscription{}, s) go func() { for msg := range s.Receiver { l += msg.Fields["message"].(string) + "\n" } }() output, err := RunWithProvidedTestContext(ctx, MigrationsCommands, cmdArgs) // Check command output for plausibility. //t.Logf(output) if err != nil { assert.NoError(t, err) t.FailNow() } assert.NotContains(t, output, "Usage") time.Sleep(time.Second) // Check command output. if l == "" { t.Fatal("log output missing") } // t.Logf(l) assert.Contains(t, l, "migrate: number of albums transfered 31") assert.Contains(t, l, "migrate: number of albumusers transfered 0") assert.Contains(t, l, "migrate: number of cameras transfered 6") assert.Contains(t, l, "migrate: number of categories transfered 1") assert.Contains(t, l, "migrate: number of cells transfered 9") assert.Contains(t, l, "migrate: number of clients transfered 7") assert.Contains(t, l, "migrate: number of countries transfered 1") assert.Contains(t, l, "migrate: number of duplicates transfered 0") assert.Contains(t, l, "migrate: number of errors transfered 0") assert.Contains(t, l, "migrate: number of faces transfered 7") assert.Contains(t, l, "migrate: number of files transfered 71") assert.Contains(t, l, "migrate: number of fileshares transfered 2") assert.Contains(t, l, "migrate: number of filesyncs transfered 3") assert.Contains(t, l, "migrate: number of folders transfered 3") assert.Contains(t, l, "migrate: number of keywords transfered 26") assert.Contains(t, l, "migrate: number of labels transfered 32") assert.Contains(t, l, "migrate: number of lenses transfered 2") assert.Contains(t, l, "migrate: number of links transfered 5") assert.Contains(t, l, "migrate: number of markers transfered 18") assert.Contains(t, l, "migrate: number of passcodes transfered 3") assert.Contains(t, l, "migrate: number of passwords transfered 11") assert.Contains(t, l, "migrate: number of photos transfered 58") assert.Contains(t, l, "migrate: number of photousers transfered 0") assert.Contains(t, l, "migrate: number of places transfered 10") assert.Contains(t, l, "migrate: number of reactions transfered 3") assert.Contains(t, l, "migrate: number of sessions transfered 21") assert.Contains(t, l, "migrate: number of services transfered 2") assert.Contains(t, l, "migrate: number of subjects transfered 6") assert.Contains(t, l, "migrate: number of users transfered 11") assert.Contains(t, l, "migrate: number of userdetails transfered 9") assert.Contains(t, l, "migrate: number of usersettings transfered 13") assert.Contains(t, l, "migrate: number of usershares transfered 1") // Make sure that a sequence update has worked. testdb, err := gorm.Open(sqlite.Open("/go/src/github.com/photoprism/photoprism/storage/mysqltosqlitepopulated.test.db?_busy_timeout=5000&_foreign_keys=on"), &gorm.Config{}) if err != nil { assert.NoError(t, err) t.FailNow() } lens := entity.Lens{LensSlug: "PhotoPrismTest Data Slug For Lens", LensName: "PhotoPrism Biocular", LensMake: "PhotoPrism", LensModel: "Short", LensType: "Mono", LensDescription: "Special Test Lens"} if result := testdb.Create(&lens); result.Error != nil { assert.NoError(t, result.Error) t.FailNow() } // Remove target database file if !t.Failed() { os.Remove("/go/src/github.com/photoprism/photoprism/storage/mysqltosqlitepopulated.test.db") } }) t.Run("PostgreSQLtoMySQL", func(t *testing.T) { // Load migrate database as source if dumpName, err := filepath.Abs("./testdata/transfer_postgresql"); err != nil { t.Fatal(err) } else { if err = exec.Command("psql", "postgresql://photoprism:photoprism@postgres:5432/postgres", "--file="+dumpName).Run(); err != nil { t.Fatal(err) } } // Clear MySQL target (migrate) if dumpName, err := filepath.Abs("./testdata/reset-migrate.mysql.sql"); err != nil { t.Fatal(err) } else { resetFile, err := os.Open(dumpName) if err != nil { t.Log("unable to open reset file") t.Fatal(err) } defer resetFile.Close() cmd := exec.Command("mysql") cmd.Stdin = resetFile output, err := cmd.CombinedOutput() if err != nil { t.Fatal(err) } t.Log(output) } // Run command with test context. log = event.Log appArgs := []string{"photoprism", "--database-driver", "postgres", "--database-dsn", "postgresql://migrate:migrate@postgres:5432/migrate?TimeZone=UTC&connect_timeout=15&lock_timeout=5000&sslmode=disable", "--transfer-driver", "mysql", "--transfer-dsn", "migrate:migrate@tcp(mariadb:4001)/migrate?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s"} cmdArgs := []string{"migrations", "transfer"} ctx := NewTestContextWithParse(appArgs, cmdArgs) s := event.Subscribe("log.info") defer event.Unsubscribe(s) var l string assert.IsType(t, hub.Subscription{}, s) go func() { for msg := range s.Receiver { l += msg.Fields["message"].(string) + "\n" } }() output, err := RunWithProvidedTestContext(ctx, MigrationsCommands, cmdArgs) // Check command output for plausibility. //t.Logf(output) if err != nil { assert.NoError(t, err) t.FailNow() } assert.NotContains(t, output, "Usage") time.Sleep(time.Second) // Check command output. if l == "" { t.Fatal("log output missing") } // t.Logf(l) assert.Contains(t, l, "migrate: number of albums transfered 31") assert.Contains(t, l, "migrate: number of albumusers transfered 0") assert.Contains(t, l, "migrate: number of cameras transfered 6") assert.Contains(t, l, "migrate: number of categories transfered 1") assert.Contains(t, l, "migrate: number of cells transfered 9") assert.Contains(t, l, "migrate: number of clients transfered 7") assert.Contains(t, l, "migrate: number of countries transfered 1") assert.Contains(t, l, "migrate: number of duplicates transfered 0") assert.Contains(t, l, "migrate: number of errors transfered 0") assert.Contains(t, l, "migrate: number of faces transfered 7") assert.Contains(t, l, "migrate: number of files transfered 71") assert.Contains(t, l, "migrate: number of fileshares transfered 2") assert.Contains(t, l, "migrate: number of filesyncs transfered 3") assert.Contains(t, l, "migrate: number of folders transfered 3") assert.Contains(t, l, "migrate: number of keywords transfered 26") assert.Contains(t, l, "migrate: number of labels transfered 32") assert.Contains(t, l, "migrate: number of lenses transfered 2") assert.Contains(t, l, "migrate: number of links transfered 5") assert.Contains(t, l, "migrate: number of markers transfered 18") assert.Contains(t, l, "migrate: number of passcodes transfered 3") assert.Contains(t, l, "migrate: number of passwords transfered 11") assert.Contains(t, l, "migrate: number of photos transfered 58") assert.Contains(t, l, "migrate: number of photousers transfered 0") assert.Contains(t, l, "migrate: number of places transfered 10") assert.Contains(t, l, "migrate: number of reactions transfered 3") assert.Contains(t, l, "migrate: number of sessions transfered 21") assert.Contains(t, l, "migrate: number of services transfered 2") assert.Contains(t, l, "migrate: number of subjects transfered 6") assert.Contains(t, l, "migrate: number of users transfered 11") assert.Contains(t, l, "migrate: number of userdetails transfered 9") assert.Contains(t, l, "migrate: number of usersettings transfered 13") assert.Contains(t, l, "migrate: number of usershares transfered 1") // Make sure that a sequence update has worked. testdb, err := gorm.Open(mysql.Open("migrate:migrate@tcp(mariadb:4001)/migrate?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s"), &gorm.Config{}) if err != nil { assert.NoError(t, err) t.FailNow() } lens := entity.Lens{LensSlug: "PhotoPrismTest Data Slug For Lens", LensName: "PhotoPrism Biocular", LensMake: "PhotoPrism", LensModel: "Short", LensType: "Mono", LensDescription: "Special Test Lens"} if result := testdb.Create(&lens); result.Error != nil { assert.NoError(t, result.Error) t.FailNow() } }) t.Run("PostgreSQLtoSQLite", func(t *testing.T) { // Remove target database file os.Remove("/go/src/github.com/photoprism/photoprism/storage/postgresqltosqlite.test.db") // Load migrate database as source if dumpName, err := filepath.Abs("./testdata/transfer_postgresql"); err != nil { t.Fatal(err) } else { if err = exec.Command("psql", "postgresql://photoprism:photoprism@postgres:5432/postgres", "--file="+dumpName).Run(); err != nil { t.Fatal(err) } } // Run command with test context. log = event.Log appArgs := []string{"photoprism", "--database-driver", "postgres", "--database-dsn", "postgresql://migrate:migrate@postgres:5432/migrate?TimeZone=UTC&connect_timeout=15&lock_timeout=5000&sslmode=disable", "--transfer-driver", "sqlite", "--transfer-dsn", "/go/src/github.com/photoprism/photoprism/storage/postgresqltosqlite.test.db?_busy_timeout=5000&_foreign_keys=on"} cmdArgs := []string{"migrations", "transfer"} ctx := NewTestContextWithParse(appArgs, cmdArgs) s := event.Subscribe("log.info") defer event.Unsubscribe(s) var l string assert.IsType(t, hub.Subscription{}, s) go func() { for msg := range s.Receiver { l += msg.Fields["message"].(string) + "\n" } }() output, err := RunWithProvidedTestContext(ctx, MigrationsCommands, cmdArgs) // Check command output for plausibility. //t.Logf(output) if err != nil { assert.NoError(t, err) t.FailNow() } assert.NotContains(t, output, "Usage") time.Sleep(time.Second) // Check command output. if l == "" { t.Fatal("log output missing") } // t.Logf(l) assert.Contains(t, l, "migrate: number of albums transfered 31") assert.Contains(t, l, "migrate: number of albumusers transfered 0") assert.Contains(t, l, "migrate: number of cameras transfered 6") assert.Contains(t, l, "migrate: number of categories transfered 1") assert.Contains(t, l, "migrate: number of cells transfered 9") assert.Contains(t, l, "migrate: number of clients transfered 7") assert.Contains(t, l, "migrate: number of countries transfered 1") assert.Contains(t, l, "migrate: number of duplicates transfered 0") assert.Contains(t, l, "migrate: number of errors transfered 0") assert.Contains(t, l, "migrate: number of faces transfered 7") assert.Contains(t, l, "migrate: number of files transfered 71") assert.Contains(t, l, "migrate: number of fileshares transfered 2") assert.Contains(t, l, "migrate: number of filesyncs transfered 3") assert.Contains(t, l, "migrate: number of folders transfered 3") assert.Contains(t, l, "migrate: number of keywords transfered 26") assert.Contains(t, l, "migrate: number of labels transfered 32") assert.Contains(t, l, "migrate: number of lenses transfered 2") assert.Contains(t, l, "migrate: number of links transfered 5") assert.Contains(t, l, "migrate: number of markers transfered 18") assert.Contains(t, l, "migrate: number of passcodes transfered 3") assert.Contains(t, l, "migrate: number of passwords transfered 11") assert.Contains(t, l, "migrate: number of photos transfered 58") assert.Contains(t, l, "migrate: number of photousers transfered 0") assert.Contains(t, l, "migrate: number of places transfered 10") assert.Contains(t, l, "migrate: number of reactions transfered 3") assert.Contains(t, l, "migrate: number of sessions transfered 21") assert.Contains(t, l, "migrate: number of services transfered 2") assert.Contains(t, l, "migrate: number of subjects transfered 6") assert.Contains(t, l, "migrate: number of users transfered 11") assert.Contains(t, l, "migrate: number of userdetails transfered 9") assert.Contains(t, l, "migrate: number of usersettings transfered 13") assert.Contains(t, l, "migrate: number of usershares transfered 1") // Make sure that a sequence update has worked. testdb, err := gorm.Open(sqlite.Open("/go/src/github.com/photoprism/photoprism/storage/postgresqltosqlite.test.db?_busy_timeout=5000&_foreign_keys=on"), &gorm.Config{}) if err != nil { assert.NoError(t, err) t.FailNow() } lens := entity.Lens{LensSlug: "PhotoPrismTest Data Slug For Lens", LensName: "PhotoPrism Biocular", LensMake: "PhotoPrism", LensModel: "Short", LensType: "Mono", LensDescription: "Special Test Lens"} if result := testdb.Create(&lens); result.Error != nil { assert.NoError(t, result.Error) t.FailNow() } // Remove target database file if !t.Failed() { os.Remove("/go/src/github.com/photoprism/photoprism/storage/postgresqltosqlite.test.db") } }) t.Run("SQLiteToMySQL", func(t *testing.T) { // Remove target database file os.Remove("/go/src/github.com/photoprism/photoprism/storage/sqlitetomysql.test.db") // Load migrate database as source if err := copyFile("/go/src/github.com/photoprism/photoprism/internal/commands/testdata/transfer_sqlite3", "/go/src/github.com/photoprism/photoprism/storage/sqlitetomysql.test.db"); err != nil { t.Fatal(err.Error()) } // Clear MySQL target (migrate) if dumpName, err := filepath.Abs("./testdata/reset-migrate.mysql.sql"); err != nil { t.Fatal(err) } else { resetFile, err := os.Open(dumpName) if err != nil { t.Log("unable to open reset file") t.Fatal(err) } defer resetFile.Close() cmd := exec.Command("mysql") cmd.Stdin = resetFile output, err := cmd.CombinedOutput() if err != nil { t.Fatal(err) } t.Log(output) } // Run command with test context. log = event.Log appArgs := []string{"photoprism", "--database-driver", "sqlite", "--database-dsn", "/go/src/github.com/photoprism/photoprism/storage/sqlitetomysql.test.db?_busy_timeout=5000&_foreign_keys=on", "--transfer-driver", "mysql", "--transfer-dsn", "migrate:migrate@tcp(mariadb:4001)/migrate?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s"} cmdArgs := []string{"migrations", "transfer"} ctx := NewTestContextWithParse(appArgs, cmdArgs) s := event.Subscribe("log.info") defer event.Unsubscribe(s) var l string assert.IsType(t, hub.Subscription{}, s) go func() { for msg := range s.Receiver { l += msg.Fields["message"].(string) + "\n" } }() output, err := RunWithProvidedTestContext(ctx, MigrationsCommands, cmdArgs) // Check command output for plausibility. //t.Logf(output) if err != nil { assert.NoError(t, err) t.FailNow() } assert.NotContains(t, output, "Usage") time.Sleep(time.Second) // Check command output. if l == "" { t.Fatal("log output missing") } // t.Logf(l) assert.Contains(t, l, "migrate: number of albums transfered 31") assert.Contains(t, l, "migrate: number of albumusers transfered 0") assert.Contains(t, l, "migrate: number of cameras transfered 6") assert.Contains(t, l, "migrate: number of categories transfered 1") assert.Contains(t, l, "migrate: number of cells transfered 9") assert.Contains(t, l, "migrate: number of clients transfered 7") assert.Contains(t, l, "migrate: number of countries transfered 1") assert.Contains(t, l, "migrate: number of duplicates transfered 0") assert.Contains(t, l, "migrate: number of errors transfered 0") assert.Contains(t, l, "migrate: number of faces transfered 7") assert.Contains(t, l, "migrate: number of files transfered 71") assert.Contains(t, l, "migrate: number of fileshares transfered 2") assert.Contains(t, l, "migrate: number of filesyncs transfered 3") assert.Contains(t, l, "migrate: number of folders transfered 3") assert.Contains(t, l, "migrate: number of keywords transfered 26") assert.Contains(t, l, "migrate: number of labels transfered 32") assert.Contains(t, l, "migrate: number of lenses transfered 2") assert.Contains(t, l, "migrate: number of links transfered 5") assert.Contains(t, l, "migrate: number of markers transfered 18") assert.Contains(t, l, "migrate: number of passcodes transfered 3") assert.Contains(t, l, "migrate: number of passwords transfered 11") assert.Contains(t, l, "migrate: number of photos transfered 58") assert.Contains(t, l, "migrate: number of photousers transfered 0") assert.Contains(t, l, "migrate: number of places transfered 10") assert.Contains(t, l, "migrate: number of reactions transfered 3") assert.Contains(t, l, "migrate: number of sessions transfered 21") assert.Contains(t, l, "migrate: number of services transfered 2") assert.Contains(t, l, "migrate: number of subjects transfered 6") assert.Contains(t, l, "migrate: number of users transfered 11") assert.Contains(t, l, "migrate: number of userdetails transfered 9") assert.Contains(t, l, "migrate: number of usersettings transfered 13") assert.Contains(t, l, "migrate: number of usershares transfered 1") // Make sure that a sequence update has worked. testdb, err := gorm.Open(mysql.Open("migrate:migrate@tcp(mariadb:4001)/migrate?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s"), &gorm.Config{}) if err != nil { assert.NoError(t, err) t.FailNow() } lens := entity.Lens{LensSlug: "PhotoPrismTest Data Slug For Lens", LensName: "PhotoPrism Biocular", LensMake: "PhotoPrism", LensModel: "Short", LensType: "Mono", LensDescription: "Special Test Lens"} if result := testdb.Create(&lens); result.Error != nil { assert.NoError(t, result.Error) t.FailNow() } // Remove target database file if !t.Failed() { os.Remove("/go/src/github.com/photoprism/photoprism/storage/sqlitetomysql.test.db") } }) t.Run("SQLiteToPostgreSQL", func(t *testing.T) { // Remove target database file os.Remove("/go/src/github.com/photoprism/photoprism/storage/sqlitetopostgresql.test.db") // Load migrate database as source if err := copyFile("/go/src/github.com/photoprism/photoprism/internal/commands/testdata/transfer_sqlite3", "/go/src/github.com/photoprism/photoprism/storage/sqlitetopostgresql.test.db"); err != nil { t.Fatal(err.Error()) } // Clear PostgreSQL target (migrate) if dumpName, err := filepath.Abs("./testdata/reset-migrate.postgresql.sql"); err != nil { t.Fatal(err) } else { if err = exec.Command("psql", "postgresql://photoprism:photoprism@postgres:5432/postgres", "--file="+dumpName).Run(); err != nil { t.Fatal(err) } } // Run command with test context. log = event.Log appArgs := []string{"photoprism", "--database-driver", "sqlite", "--database-dsn", "/go/src/github.com/photoprism/photoprism/storage/sqlitetopostgresql.test.db?_busy_timeout=5000&_foreign_keys=on", "--transfer-driver", "postgres", "--transfer-dsn", "postgresql://migrate:migrate@postgres:5432/migrate?TimeZone=UTC&connect_timeout=15&lock_timeout=5000&sslmode=disable"} cmdArgs := []string{"migrations", "transfer"} ctx := NewTestContextWithParse(appArgs, cmdArgs) s := event.Subscribe("log.info") defer event.Unsubscribe(s) var l string assert.IsType(t, hub.Subscription{}, s) go func() { for msg := range s.Receiver { l += msg.Fields["message"].(string) + "\n" } }() output, err := RunWithProvidedTestContext(ctx, MigrationsCommands, cmdArgs) // Check command output for plausibility. //t.Logf(output) if err != nil { assert.NoError(t, err) t.FailNow() } assert.NotContains(t, output, "Usage") time.Sleep(time.Second) // Check command output. if l == "" { t.Fatal("log output missing") } // t.Logf(l) assert.Contains(t, l, "migrate: number of albums transfered 31") assert.Contains(t, l, "migrate: number of albumusers transfered 0") assert.Contains(t, l, "migrate: number of cameras transfered 6") assert.Contains(t, l, "migrate: number of categories transfered 1") assert.Contains(t, l, "migrate: number of cells transfered 9") assert.Contains(t, l, "migrate: number of clients transfered 7") assert.Contains(t, l, "migrate: number of countries transfered 1") assert.Contains(t, l, "migrate: number of duplicates transfered 0") assert.Contains(t, l, "migrate: number of errors transfered 0") assert.Contains(t, l, "migrate: number of faces transfered 7") assert.Contains(t, l, "migrate: number of files transfered 71") assert.Contains(t, l, "migrate: number of fileshares transfered 2") assert.Contains(t, l, "migrate: number of filesyncs transfered 3") assert.Contains(t, l, "migrate: number of folders transfered 3") assert.Contains(t, l, "migrate: number of keywords transfered 26") assert.Contains(t, l, "migrate: number of labels transfered 32") assert.Contains(t, l, "migrate: number of lenses transfered 2") assert.Contains(t, l, "migrate: number of links transfered 5") assert.Contains(t, l, "migrate: number of markers transfered 18") assert.Contains(t, l, "migrate: number of passcodes transfered 3") assert.Contains(t, l, "migrate: number of passwords transfered 11") assert.Contains(t, l, "migrate: number of photos transfered 58") assert.Contains(t, l, "migrate: number of photousers transfered 0") assert.Contains(t, l, "migrate: number of places transfered 10") assert.Contains(t, l, "migrate: number of reactions transfered 3") assert.Contains(t, l, "migrate: number of sessions transfered 21") assert.Contains(t, l, "migrate: number of services transfered 2") assert.Contains(t, l, "migrate: number of subjects transfered 6") assert.Contains(t, l, "migrate: number of users transfered 11") assert.Contains(t, l, "migrate: number of userdetails transfered 9") assert.Contains(t, l, "migrate: number of usersettings transfered 13") assert.Contains(t, l, "migrate: number of usershares transfered 1") // Make sure that a sequence update has worked. testdb, err := gorm.Open(postgres.Open("postgresql://migrate:migrate@postgres:5432/migrate?TimeZone=UTC&connect_timeout=15&lock_timeout=5000&sslmode=disable"), &gorm.Config{}) if err != nil { assert.NoError(t, err) t.FailNow() } lens := entity.Lens{LensSlug: "PhotoPrismTest Data Slug For Lens", LensName: "PhotoPrism Biocular", LensMake: "PhotoPrism", LensModel: "Short", LensType: "Mono", LensDescription: "Special Test Lens"} if result := testdb.Create(&lens); result.Error != nil { assert.NoError(t, result.Error) t.FailNow() } // Remove target database file if !t.Failed() { os.Remove("/go/src/github.com/photoprism/photoprism/storage/sqlitetomysql.test.db") } }) } func copyFile(source, target string) error { if _, err := os.Stat(source); err != nil { return fmt.Errorf("copyFile: source file %s is required", source) } if _, err := os.Stat(target); err != nil { if err = os.Remove(target); err != nil { if !strings.Contains(err.Error(), "no such file or directory") { return fmt.Errorf("copyFile: target file %s can not be removed with error %s", target, err.Error()) } } } sourceFile, err := os.Open(source) if err != nil { return fmt.Errorf("copyFile: source file %s can not be opened with error %s", source, err.Error()) } defer sourceFile.Close() targetFile, err := os.Create(target) if err != nil { return fmt.Errorf("copyFile: target file %s can not be opened with error %s", target, err.Error()) } defer func() { closeErr := targetFile.Close() if err == nil { err = closeErr } }() if _, err = io.Copy(targetFile, sourceFile); err != nil { return fmt.Errorf("copyFile: copy failed with error %s", err.Error()) } if err = targetFile.Sync(); err != nil { return fmt.Errorf("copyFile: target sync failed with error %s", err.Error()) } return nil }