package updates import ( "fmt" "strings" "testing" "time" "git.golem15.com/golem15/summercms/modules/lagoon" "github.com/go-gormigrate/gormigrate/v2" "gorm.io/gorm" ) var journalTables = []string{ "golem15_journal_posts", "golem15_journal_categories", "golem15_journal_tags", "golem15_journal_posts_categories", "golem15_journal_posts_tags", "golem15_journal_settings", } func TestJournalMigrationsRollbackAndRemigrate(t *testing.T) { sqlDB := dedicatedDB(t, fmt.Sprintf("journal_mig_%d", time.Now().UnixNano())) gdb, err := lagoon.Use(t.Context(), sqlDB) if err != nil { t.Fatal(err) } backend := gormigrate.New(gdb, &gormigrate.Options{ TableName: "summer_migrations_summercms_cabana", UseTransaction: true, }, lagoon.BackendAdminMigrations) if err := backend.Migrate(); err != nil { t.Fatalf("backend_users migrations: %v", err) } m := journalMigrator(gdb) if err := m.Migrate(); err != nil { t.Fatalf("migrate: %v", err) } assertJournalSchema(t, gdb) for i := 0; i < len(All()); i++ { if err := m.RollbackLast(); err != nil { t.Fatalf("rollback %d: %v", i, err) } } for _, table := range journalTables { if gdb.Migrator().HasTable(table) { t.Fatalf("%s survived rollback", table) } } if gdb.Migrator().HasColumn("backend_users", "golem15_bloghub_author_slug") { t.Fatal("golem15_bloghub_author_slug survived rollback") } assertNoRainlabJournalTables(t, gdb) if err := m.Migrate(); err != nil { t.Fatalf("re-migrate: %v", err) } assertJournalSchema(t, gdb) } func journalMigrator(gdb *gorm.DB) *gormigrate.Gormigrate { return gormigrate.New(gdb, &gormigrate.Options{ TableName: "summer_migrations_golem15_journal", UseTransaction: true, }, All()) } func assertJournalSchema(t *testing.T, gdb *gorm.DB) { t.Helper() for _, name := range journalTables { if !gdb.Migrator().HasTable(name) { t.Fatalf("missing table %s", name) } } assertNoRainlabJournalTables(t, gdb) if !gdb.Migrator().HasColumn("backend_users", "golem15_bloghub_author_slug") { t.Fatal("backend_users.golem15_bloghub_author_slug missing") } wantPosts := []string{ "id", "user_id", "redactor_id", "title", "slug", "excerpt", "content", "content_html", "published_at", "published", "is_pinned", "metadata", "sources", "created_at", "updated_at", } for _, col := range wantPosts { if !gdb.Migrator().HasColumn("golem15_journal_posts", col) { t.Fatalf("golem15_journal_posts missing column %s", col) } } assertUdt(t, gdb, "golem15_journal_posts", "metadata", "jsonb") assertUdt(t, gdb, "golem15_journal_posts", "sources", "jsonb") assertIndex(t, gdb, "golem15_journal_posts_slug_unique") assertIndex(t, gdb, "golem15_journal_categories_slug_unique") assertIndex(t, gdb, "golem15_journal_tags_slug_unique") var typesense bool if err := gdb.WithContext(t.Context()). Raw(`SELECT search_use_typesense FROM golem15_journal_settings WHERE id = 1`). Scan(&typesense).Error; err != nil { t.Fatal(err) } if typesense { t.Fatal("search_use_typesense default must be false") } var showAll bool if err := gdb.WithContext(t.Context()). Raw(`SELECT show_all_posts FROM golem15_journal_settings WHERE id = 1`). Scan(&showAll).Error; err != nil { t.Fatal(err) } if !showAll { t.Fatal("show_all_posts default must be true") } } func assertNoRainlabJournalTables(t *testing.T, gdb *gorm.DB) { t.Helper() var leftover []string if err := gdb.WithContext(t.Context()). Raw(`SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND (tablename LIKE 'rainlab\_journal\_%' OR tablename LIKE 'winter\_journal\_%')`). Scan(&leftover).Error; err != nil { t.Fatal(err) } if len(leftover) != 0 { t.Fatalf("rainlab/winter-era journal tables present: %v", leftover) } } func assertUdt(t *testing.T, gdb *gorm.DB, table, column, want string) { t.Helper() var udt string if err := gdb.WithContext(t.Context()).Raw( `SELECT udt_name FROM information_schema.columns WHERE table_name = ? AND column_name = ?`, table, column, ).Scan(&udt).Error; err != nil { t.Fatal(err) } if udt != want { t.Fatalf("%s.%s udt = %q, want %q", table, column, udt, want) } } func assertIndex(t *testing.T, gdb *gorm.DB, name string) { t.Helper() var n int64 if err := gdb.WithContext(t.Context()).Raw( `SELECT COUNT(*) FROM pg_indexes WHERE schemaname = 'public' AND indexname = ?`, name, ).Scan(&n).Error; err != nil { t.Fatal(err) } if n != 1 { t.Fatalf("index %s count = %d, want 1", name, n) } } func assertUniqueSlug(t *testing.T, gdb *gorm.DB, table, slug string) { t.Helper() if err := gdb.Exec(`INSERT INTO `+table+` (slug) VALUES (?)`, slug).Error; err != nil { t.Fatalf("first insert %s.%s: %v", table, slug, err) } err := gdb.Exec(`INSERT INTO `+table+` (slug) VALUES (?)`, slug).Error if err == nil { t.Fatalf("duplicate slug on %s was accepted", table) } if !strings.Contains(strings.ToLower(err.Error()), "unique") && !strings.Contains(strings.ToLower(err.Error()), "duplicate") { t.Fatalf("duplicate slug error = %v", err) } }