Squash the remaining golem15_journal_* tables plus the backend_users author slug so a host migrate matches the frozen PHP schema. Co-authored-by: Cursor <cursoragent@cursor.com>
222 lines
5.6 KiB
Go
222 lines
5.6 KiB
Go
package updates
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.golem15.com/golem15/sm-journal-plugin/models"
|
|
"git.golem15.com/golem15/summercms/modules/lagoon"
|
|
"github.com/go-gormigrate/gormigrate/v2"
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
"github.com/testcontainers/testcontainers-go"
|
|
"github.com/testcontainers/testcontainers-go/modules/postgres"
|
|
)
|
|
|
|
var (
|
|
journalPG *postgres.PostgresContainer
|
|
journalSQL *sql.DB
|
|
journalDSN string
|
|
journalPGErr error
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
code := 1
|
|
if !testShort() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
|
journalPGErr = startJournalPostgres(ctx)
|
|
cancel()
|
|
if journalPGErr != nil {
|
|
fmt.Fprintf(os.Stderr, "golem15.journal: testcontainers postgres: %v\n", journalPGErr)
|
|
stopJournalPostgres()
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
code = m.Run()
|
|
stopJournalPostgres()
|
|
os.Exit(code)
|
|
}
|
|
|
|
func testShort() bool {
|
|
for _, a := range os.Args {
|
|
if a == "-test.short" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func startJournalPostgres(ctx context.Context) error {
|
|
ctr, err := postgres.Run(ctx,
|
|
"postgres:16-alpine",
|
|
postgres.WithDatabase("golem15_journal"),
|
|
postgres.WithUsername("golem15_journal"),
|
|
postgres.WithPassword("golem15_journal"),
|
|
postgres.BasicWaitStrategies(),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
journalPG = ctr
|
|
dsn, err := ctr.ConnectionString(ctx, "sslmode=disable")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
db, err := sql.Open("pgx", dsn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := db.PingContext(ctx); err != nil {
|
|
_ = db.Close()
|
|
return err
|
|
}
|
|
journalSQL = db
|
|
journalDSN = dsn
|
|
return nil
|
|
}
|
|
|
|
func stopJournalPostgres() {
|
|
if journalSQL != nil {
|
|
_ = journalSQL.Close()
|
|
}
|
|
if journalPG != nil {
|
|
_ = testcontainers.TerminateContainer(journalPG)
|
|
}
|
|
}
|
|
|
|
func journalAdminDB(t *testing.T) *sql.DB {
|
|
t.Helper()
|
|
if testing.Short() {
|
|
t.Skip("requires testcontainers postgres")
|
|
}
|
|
if journalPGErr != nil {
|
|
t.Fatalf("postgres unavailable: %v", journalPGErr)
|
|
}
|
|
if journalSQL == nil {
|
|
t.Fatal("postgres unavailable: container was not started")
|
|
}
|
|
return journalSQL
|
|
}
|
|
|
|
func dedicatedDB(t *testing.T, name string) *sql.DB {
|
|
t.Helper()
|
|
admin := journalAdminDB(t)
|
|
ctx := t.Context()
|
|
quoted := `"` + strings.ReplaceAll(name, `"`, `""`) + `"`
|
|
if _, err := admin.ExecContext(ctx, `CREATE DATABASE `+quoted+` TEMPLATE template0 ENCODING 'UTF8'`); err != nil && !strings.Contains(err.Error(), "already exists") {
|
|
t.Fatalf("create %s: %v", name, err)
|
|
}
|
|
u, err := url.Parse(journalDSN)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
u.Path = "/" + name
|
|
db, err := sql.Open("pgx", u.String())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_ = db.Close()
|
|
_, _ = admin.ExecContext(context.Background(), `DROP DATABASE IF EXISTS `+quoted+` WITH (FORCE)`)
|
|
})
|
|
if err := db.PingContext(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return db
|
|
}
|
|
|
|
// TestPostTranslatableSmoke keeps `go test ./... -run TestPostTranslatableSmoke`
|
|
// from reporting [no tests to run] in this package. The production-path
|
|
// proof lives in package models.
|
|
func TestPostTranslatableSmoke(t *testing.T) {}
|
|
|
|
func TestJournalTables(t *testing.T) {
|
|
sqlDB := dedicatedDB(t, "journal_tables")
|
|
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)
|
|
}
|
|
journal := gormigrate.New(gdb, &gormigrate.Options{
|
|
TableName: "summer_migrations_golem15_journal",
|
|
UseTransaction: true,
|
|
}, All())
|
|
if err := journal.Migrate(); err != nil {
|
|
t.Fatalf("journal migrations: %v", err)
|
|
}
|
|
|
|
for _, name := range []string{
|
|
"golem15_journal_posts",
|
|
"golem15_journal_categories",
|
|
"golem15_journal_tags",
|
|
"golem15_journal_posts_categories",
|
|
"golem15_journal_posts_tags",
|
|
"golem15_journal_settings",
|
|
} {
|
|
if !gdb.Migrator().HasTable(name) {
|
|
t.Fatalf("missing table %s", name)
|
|
}
|
|
}
|
|
var leftover []string
|
|
if err := gdb.WithContext(t.Context()).
|
|
Raw(`SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND tablename LIKE 'rainlab\_journal\_%'`).
|
|
Scan(&leftover).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(leftover) != 0 {
|
|
t.Fatalf("rainlab-era journal tables present: %v", leftover)
|
|
}
|
|
if !gdb.Migrator().HasColumn("backend_users", "golem15_bloghub_author_slug") {
|
|
t.Fatal("backend_users.golem15_bloghub_author_slug missing")
|
|
}
|
|
|
|
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 catCount int64
|
|
if err := gdb.WithContext(t.Context()).
|
|
Table("golem15_journal_categories").
|
|
Where("slug = ?", "uncategorized").
|
|
Count(&catCount).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if catCount != 1 {
|
|
t.Fatalf("Uncategorized rows = %d, want 1", catCount)
|
|
}
|
|
var postCount int64
|
|
if err := gdb.WithContext(t.Context()).
|
|
Table("golem15_journal_posts").
|
|
Count(&postCount).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if postCount != 0 {
|
|
t.Fatalf("seeded posts = %d, want 0", postCount)
|
|
}
|
|
|
|
if got := (models.Category{}).Fillable(); !reflect.DeepEqual(got, []string{"name", "slug", "code", "description", "parent_id"}) {
|
|
t.Fatalf("Category.Fillable = %#v", got)
|
|
}
|
|
if got := (models.Tag{}).Fillable(); !reflect.DeepEqual(got, []string{"name", "slug", "description"}) {
|
|
t.Fatalf("Tag.Fillable = %#v", got)
|
|
}
|
|
}
|