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