Files
sm-journal-plugin/updates/migrations_test.go
Jakub Zych d09edde080 test(15-04): complete PHPUnit map, migrations, FormatHTML, and search-gate tests
Named JOURNAL-001/002 fillable, FormatHTML unsafe tags, rollback/remigrate, RSS/categories/tags/media, and TestSearchGateOff with zero Typesense HTTP.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 19:18:53 +02:00

171 lines
5.0 KiB
Go

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)
}
}