package models_test import ( "context" "database/sql" "encoding/json" "flag" "fmt" "os" "testing" "time" "git.golem15.com/golem15/sm-journal-plugin" "git.golem15.com/golem15/sm-journal-plugin/models" "git.golem15.com/golem15/sm-translate-plugin" "git.golem15.com/golem15/sm-translate-plugin/classes" translatemodels "git.golem15.com/golem15/sm-translate-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" "gorm.io/gorm" ) var ( journalPG *postgres.PostgresContainer journalSQL *sql.DB 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) } // testShort reports -short. go test passes -test.short=true, so the flags // are parsed and read through testing.Short, never by scanning os.Args. func testShort() bool { flag.Parse() return testing.Short() } 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 return nil } func stopJournalPostgres() { if journalSQL != nil { _ = journalSQL.Close() } if journalPG != nil { _ = testcontainers.TerminateContainer(journalPG) } } func journalDB(t *testing.T) *gorm.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") } gdb, err := lagoon.Use(t.Context(), journalSQL) if err != nil { t.Fatal(err) } return gdb } func migrateTranslateThenJournal(t *testing.T, gdb *gorm.DB) { t.Helper() 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) } translateMig := gormigrate.New(gdb, &gormigrate.Options{ TableName: "summer_migrations_golem15_translate", UseTransaction: true, }, (&translate.Plugin{}).Migrations()) if err := translateMig.Migrate(); err != nil { t.Fatalf("translate migrations: %v", err) } journalMig := gormigrate.New(gdb, &gormigrate.Options{ TableName: "summer_migrations_golem15_journal", UseTransaction: true, }, (&journal.Plugin{}).Migrations()) if err := journalMig.Migrate(); err != nil { t.Fatalf("journal migrations: %v", err) } } // 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 TestPostTranslatableSmoke(t *testing.T) { gdb := journalDB(t) migrateTranslateThenJournal(t, gdb) if gdb.Migrator().HasTable("rainlab_journal_posts") { t.Fatal("rainlab-era journal table must not be created") } if !gdb.Migrator().HasTable("golem15_journal_posts") { t.Fatal("golem15_journal_posts was not created") } post := &models.Post{} if post.MorphName() != `Golem15\Journal\Models\Post` { t.Fatalf("MorphName = %q", post.MorphName()) } indexes := post.TranslatableIndexes() if len(indexes) != 1 || indexes[0] != "slug" { t.Fatalf("TranslatableIndexes = %#v, want [slug]", indexes) } for _, name := range post.Fillable() { if name == "redactor_id" || name == "content_html" || name == "user_id" { t.Fatalf("Fillable includes forbidden %q", name) } } ctx := t.Context() post = &models.Post{ Title: "Hello", Slug: "hello", Excerpt: "English excerpt", Content: "English content", } if err := gdb.WithContext(ctx).Create(post).Error; err != nil { t.Fatal(err) } if post.ID == 0 { t.Fatal("expected persisted post id") } if err := classes.SetTranslated(ctx, gdb, post, "title", "en", "Hello"); err != nil { t.Fatalf("set en title: %v", err) } if err := classes.SetTranslated(ctx, gdb, post, "content", "en", "English content"); err != nil { t.Fatalf("set en content: %v", err) } if err := classes.SetTranslated(ctx, gdb, post, "content_html", "en", "
English content
"); err != nil { t.Fatalf("set en content_html: %v", err) } if err := classes.SetTranslated(ctx, gdb, post, "excerpt", "en", "English excerpt"); err != nil { t.Fatalf("set en excerpt: %v", err) } if err := classes.SetTranslated(ctx, gdb, post, "title", "pl", "Witaj"); err != nil { t.Fatalf("set pl title: %v", err) } if err := classes.SetTranslated(ctx, gdb, post, "slug", "pl", "witaj"); err != nil { t.Fatalf("set pl slug: %v", err) } got, err := classes.Translated(ctx, gdb, post, "title", "pl") if err != nil { t.Fatalf("read pl title: %v", err) } if got != "Witaj" { t.Fatalf("pl title = %#v, want Witaj", got) } var stored models.Post if err := gdb.WithContext(ctx).First(&stored, post.ID).Error; err != nil { t.Fatal(err) } if stored.Title != "Hello" { t.Fatalf("host title = %q, want Hello", stored.Title) } if stored.Slug != "hello" { t.Fatalf("host slug = %q, want hello", stored.Slug) } var attrs []translatemodels.Attribute if err := gdb.WithContext(ctx). Where("model_id = ? AND model_type = ?", fmt.Sprint(post.ID), post.MorphName()). Find(&attrs).Error; err != nil { t.Fatal(err) } if len(attrs) != 1 { t.Fatalf("attribute rows = %d, want 1 (Polish only)", len(attrs)) } if attrs[0].Locale != "pl" { t.Fatalf("attribute locale = %q, want pl", attrs[0].Locale) } if attrs[0].AttributeData == nil { t.Fatal("attribute_data is nil") } var data map[string]any if err := json.Unmarshal([]byte(*attrs[0].AttributeData), &data); err != nil { t.Fatal(err) } if data["title"] != "Witaj" { t.Fatalf("attribute_data.title = %#v", data["title"]) } if data["slug"] != "witaj" { t.Fatalf("attribute_data.slug = %#v", data["slug"]) } var enCount int64 if err := gdb.WithContext(ctx).Model(&translatemodels.Attribute{}). Where("locale = ? AND model_id = ?", "en", fmt.Sprint(post.ID)). Count(&enCount).Error; err != nil { t.Fatal(err) } if enCount != 0 { t.Fatalf("en attribute rows = %d, want 0 (English lives on the host row)", enCount) } }