Files
sm-journal-plugin/updates/postgres_test.go
Jakub Zych 3d533d49f0 test(updates): expect category content in the Category.Fillable list pinned by TestJournalTables
TestJournalTables pins the exact Category.Fillable allow-list, like
models/fillable_test.go. The admin form needs content in Fillable (D-21).
2026-10-09 16:26:58 +02:00

196 lines
5.0 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) {}
// TestJournalEndToEnd keeps `go test ./... -run TestJournalEndToEnd`
// from reporting [no tests to run] in this package. The production-path
// proof lives in package journal_test.
func TestJournalEndToEnd(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)
}
assertJournalSchema(t, gdb)
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)
}
assertUniqueSlug(t, gdb, "golem15_journal_posts", "unique-post-slug")
assertUniqueSlug(t, gdb, "golem15_journal_tags", "unique-tag-slug")
assertUniqueSlug(t, gdb, "golem15_journal_categories", "unique-cat-slug")
if got := (models.Category{}).Fillable(); !reflect.DeepEqual(got, []string{"name", "slug", "code", "description", "content", "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)
}
}