refac: migration crash, real-database tests, site deleted during create, ruels out of the repo, lists
This commit is contained in:
1 parent
680b9a1308
commit
9d284e65a9
62 files changed
+1858
-987
No files matched your search
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user