133 lines
4.1 KiB
Go
133 lines
4.1 KiB
Go
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)
|
|
}
|