refac: migration crash, real-database tests, site deleted during create, ruels out of the repo, lists

This commit is contained in:
Florian Sylvain committed 2026-11-03 05:06:04 +01:00
1 parent 680b9a1308
commit 9d284e65a9
62 files changed
+1858 -987

No files matched your search

+132
View File
@@ -0,0 +1,132 @@
package test
import (
"RenewCMS/internal/infrastructure/persistence/database"
entity "RenewCMS/internal/infrastructure/persistence/models"
"path/filepath"
"testing"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
const (
nullableSourceRevisionColumn = "ALTER TABLE articles ADD COLUMN source_revision BIGINT"
legacyRevisionArticles = 3
)
func reopenableSQLite(t *testing.T) (*gorm.DB, func() *gorm.DB) {
t.Helper()
path := filepath.ToSlash(filepath.Join(t.TempDir(), "revisions.db"))
connect := func() *gorm.DB {
connection, err := database.Connect(database.Config{Driver: database.DriverSQLite, Source: path})
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
connection.DB.Logger = logger.Discard
t.Cleanup(func() { _ = connection.Close() })
return connection.DB
}
return connect(), connect
}
func reopenableMySQL(t *testing.T) (*gorm.DB, func() *gorm.DB) {
t.Helper()
server := connectMySQLTestServer(t)
config := server.createDatabase(t)
connect := func() *gorm.DB { return server.connect(t, config).DB }
return connect(), connect
}
func reopenablePostgres(t *testing.T) (*gorm.DB, func() *gorm.DB) {
t.Helper()
server := connectPostgresTestServer(t)
config := server.createDatabase(t)
connect := func() *gorm.DB { return server.connect(t, config).DB }
return connect(), connect
}
func regressToNullableSourceRevision(t *testing.T, db *gorm.DB) {
t.Helper()
if err := db.Migrator().DropColumn(&entity.Article{}, "source_revision"); err != nil {
t.Fatalf("drop source_revision: %v", err)
}
if err := db.Exec(nullableSourceRevisionColumn).Error; err != nil {
t.Fatalf("add nullable source_revision: %v", err)
}
}
func nullSourceRevisions(t *testing.T, db *gorm.DB) int64 {
t.Helper()
var total int64
if err := db.Unscoped().Model(&entity.Article{}).Where("source_revision IS NULL").Count(&total).Error; err != nil {
t.Fatal(err)
}
return total
}
func runSourceRevisionBackfill(t *testing.T, open func(t *testing.T) (*gorm.DB, func() *gorm.DB)) {
t.Run("Articles stored before source revisions existed migrate twice", func(t *testing.T) {
db, reconnect := open(t)
if err := database.Migrate(db, migrationPublicURL); err != nil {
t.Fatalf("first migrate: %v", err)
}
for index := 0; index < legacyRevisionArticles; index++ {
row := entity.Article{Title: "Legacy", Body: "Body", StoryID: fixtureStory(), Slug: uniqueTestSlug(), SourceRevision: 7}
if err := db.Create(&row).Error; err != nil {
t.Fatal(err)
}
}
regressToNullableSourceRevision(t, db)
if nulls := nullSourceRevisions(t, db); nulls != legacyRevisionArticles {
t.Fatalf("expected %d NULL source revisions, got %d", legacyRevisionArticles, nulls)
}
db = reconnect()
if err := database.Migrate(db, migrationPublicURL); err != nil {
t.Fatalf("migrate over NULL source revisions: %v", err)
}
if nulls := nullSourceRevisions(t, db); nulls != 0 {
t.Errorf("%d NULL source revisions survived", nulls)
}
var revisions []uint32
if err := db.Unscoped().Model(&entity.Article{}).Order("id ASC").Pluck("source_revision", &revisions).Error; err != nil {
t.Fatal(err)
}
if len(revisions) != legacyRevisionArticles {
t.Fatalf("revisions = %v", revisions)
}
for _, revision := range revisions {
if revision != 0 {
t.Errorf("backfilled source revision = %d, want 0", revision)
}
}
if columns, err := db.Migrator().ColumnTypes(&entity.Article{}); err == nil {
for _, column := range columns {
if nullable, known := column.Nullable(); column.Name() == "source_revision" && known && nullable {
t.Errorf("source_revision is still nullable")
}
}
}
db = reconnect()
if err := database.Migrate(db, migrationPublicURL); err != nil {
t.Fatalf("second migrate: %v", err)
}
if nulls := nullSourceRevisions(t, db); nulls != 0 {
t.Errorf("second migrate left %d NULL source revisions", nulls)
}
})
}
func TestSourceRevisionBackfillSQLite(t *testing.T) {
runSourceRevisionBackfill(t, reopenableSQLite)
}
func TestSourceRevisionBackfillMySQL(t *testing.T) {
runSourceRevisionBackfill(t, reopenableMySQL)
}
func TestSourceRevisionBackfillPostgres(t *testing.T) {
runSourceRevisionBackfill(t, reopenablePostgres)
}