Files
OpenFlare/internal/db/migrator/pages_source_migration_test.go
T
deqiying 38b0516937 feat(pages): 支持 Remote 部署源同步
新增部署源配置与运行态模型、安全下载、租约续期、原子激活和失败补偿。

接入内部任务与脱敏前端交互,并阻止数据库 Trace 和日志展开敏感查询参数。
2026-07-19 17:36:51 +08:00

323 lines
11 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package migrator
import (
"database/sql"
"fmt"
"os"
"strings"
"testing"
"time"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/glebarez/sqlite"
"github.com/pressly/goose/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
const (
pagesSourcePreviousMigration = int64(202607180001)
pagesSourceMigration = int64(202607190001)
pagesMigrationProjectID = uint(900001)
pagesMigrationDeploymentID = uint(900001)
)
func TestPagesSourceMigrationSQLiteUpDownUp(t *testing.T) {
dbPath := t.TempDir() + "/pages-source-migration.db"
gormDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
require.NoError(t, err)
sqlDB, err := gormDB.DB()
require.NoError(t, err)
sqlDB.SetMaxOpenConns(1)
t.Cleanup(func() { require.NoError(t, sqlDB.Close()) })
runPagesSourceMigrationUpDownUp(t, gormDB, sqlDB, dialectSqlite, "goose/sqlite")
var indexSQL string
require.NoError(t, gormDB.Raw(
"SELECT sql FROM sqlite_master WHERE type = 'index' AND name = ?",
"idx_of_pages_deployments_source_revision",
).Scan(&indexSQL).Error)
assert.Contains(t, strings.ToUpper(indexSQL), "WHERE SOURCE_IDENTITY IS NOT NULL AND SOURCE_REVISION IS NOT NULL")
}
func TestPagesSourceMigrationPostgresUpDownUp(t *testing.T) {
dsn := strings.TrimSpace(os.Getenv("OPENFLARE_TEST_POSTGRES_DSN"))
if dsn == "" {
t.Skip("OPENFLARE_TEST_POSTGRES_DSN is not set")
}
gormDB, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
require.NoError(t, err)
sqlDB, err := gormDB.DB()
require.NoError(t, err)
sqlDB.SetMaxOpenConns(1)
schema := fmt.Sprintf("pages_source_migration_%d", time.Now().UnixNano())
require.Regexp(t, `^[a-z0-9_]+$`, schema)
require.NoError(t, gormDB.Exec(`CREATE SCHEMA "`+schema+`"`).Error)
require.NoError(t, gormDB.Exec(`SET search_path TO "`+schema+`"`).Error)
t.Cleanup(func() {
assert.NoError(t, gormDB.Exec("SET search_path TO public").Error)
assert.NoError(t, gormDB.Exec(`DROP SCHEMA IF EXISTS "`+schema+`" CASCADE`).Error)
assert.NoError(t, sqlDB.Close())
})
runPagesSourceMigrationUpDownUp(t, gormDB, sqlDB, dialectPostgres, "goose/postgres")
}
func runPagesSourceMigrationUpDownUp(
t *testing.T,
gormDB *gorm.DB,
sqlDB *sql.DB,
dialect string,
dir string,
) {
t.Helper()
goose.SetBaseFS(migrationFS)
require.NoError(t, goose.SetDialect(dialect))
require.NoError(t, goose.UpTo(sqlDB, dir, pagesSourcePreviousMigration))
seedPrePagesSourceMigrationData(t, gormDB)
require.NoError(t, goose.UpTo(sqlDB, dir, pagesSourceMigration))
assertPagesSourceMigrationUp(t, gormDB)
require.NoError(t, goose.DownTo(sqlDB, dir, pagesSourcePreviousMigration))
assertPagesSourceMigrationDown(t, gormDB)
require.NoError(t, goose.UpTo(sqlDB, dir, pagesSourceMigration))
assertPagesSourceMigrationUpAgain(t, gormDB)
}
func seedPrePagesSourceMigrationData(t *testing.T, gormDB *gorm.DB) {
t.Helper()
require.NoError(t, gormDB.Exec(`
INSERT INTO of_pages_projects (
id, name, slug, description, enabled, active_deployment_id, root_dir, entry_file
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`,
pagesMigrationProjectID,
"Migration Site",
"migration-site",
"keep-project-data",
true,
pagesMigrationDeploymentID,
"public",
"home.html",
).Error)
require.NoError(t, gormDB.Exec(`
INSERT INTO of_pages_deployments (
id, project_id, deployment_number, checksum, status, upload_id, artifact_path,
file_count, total_size, created_by
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`,
pagesMigrationDeploymentID,
pagesMigrationProjectID,
1,
strings.Repeat("a", 64),
model.PagesDeploymentStatusActive,
uint64(700001),
"legacy/package.zip",
2,
int64(128),
"user:1",
).Error)
}
func assertPagesSourceMigrationUp(t *testing.T, gormDB *gorm.DB) {
t.Helper()
migrator := gormDB.Migrator()
assert.True(t, migrator.HasTable(&model.PagesProjectSource{}))
assert.True(t, migrator.HasTable(&model.PagesProjectSourceRuntime{}))
assert.True(t, migrator.HasColumn(&model.PagesProject{}, "ContentConfigVersion"))
assert.True(t, migrator.HasColumn(&model.PagesDeployment{}, "SourceType"))
assert.True(t, migrator.HasColumn(&model.PagesDeployment{}, "SourceIdentity"))
assert.True(t, migrator.HasColumn(&model.PagesDeployment{}, "SourceRevision"))
assert.True(t, migrator.HasIndex(&model.PagesProjectSource{}, "idx_of_pages_project_sources_project_id"))
assert.True(t, migrator.HasIndex(&model.PagesProjectSourceRuntime{}, "idx_of_pages_project_source_runtime_next_check_at"))
assert.True(t, migrator.HasIndex(&model.PagesDeployment{}, "idx_of_pages_deployments_project_number"))
assert.True(t, migrator.HasIndex(&model.PagesDeployment{}, "idx_of_pages_deployments_source_revision"))
var project model.PagesProject
require.NoError(t, gormDB.First(&project, pagesMigrationProjectID).Error)
assert.Equal(t, 0, project.ContentConfigVersion)
assert.Equal(t, "keep-project-data", project.Description)
assert.Equal(t, "public", project.RootDir)
assert.Equal(t, "home.html", project.EntryFile)
var deployment model.PagesDeployment
require.NoError(t, gormDB.First(&deployment, pagesMigrationDeploymentID).Error)
assert.Equal(t, "manual_upload", deployment.SourceType)
assert.Equal(t, "manual_upload", deployment.TriggerType)
assert.Nil(t, deployment.SourceIdentity)
assert.Nil(t, deployment.SourceRevision)
assert.Equal(t, uint64(700001), deployment.UploadID)
sourceID := createMigrationSourceRuntime(t, gormDB)
assertPagesSourceConstraints(t, gormDB, sourceID)
}
func createMigrationSourceRuntime(t *testing.T, gormDB *gorm.DB) uint {
t.Helper()
source := model.PagesProjectSource{
ProjectID: pagesMigrationProjectID,
SourceType: "remote_url",
RemoteURL: "https://example.com/site.zip?token=secret",
RemoteNetworkPolicy: "public",
CheckIntervalMinutes: 0,
ConfigVersion: 1,
SourceIdentity: strings.Repeat("b", 64),
}
require.NoError(t, gormDB.Create(&source).Error)
require.NotZero(t, source.ID)
require.NoError(t, gormDB.Create(&model.PagesProjectSourceRuntime{
SourceID: source.ID,
SyncStatus: "idle",
}).Error)
return source.ID
}
func assertPagesSourceConstraints(t *testing.T, gormDB *gorm.DB, sourceID uint) {
t.Helper()
duplicateSource := model.PagesProjectSource{
ProjectID: pagesMigrationProjectID,
SourceType: "remote_url",
ConfigVersion: 1,
SourceIdentity: strings.Repeat("c", 64),
}
assert.Error(t, gormDB.Create(&duplicateSource).Error)
for number := 2; number <= 3; number++ {
require.NoError(t, createMigrationDeployment(
gormDB,
number,
strings.Repeat(string(rune('a'+number)), 64),
nil,
nil,
))
}
identity := strings.Repeat("d", 64)
revision := strings.Repeat("e", 64)
require.NoError(t, createMigrationDeployment(
gormDB,
4,
strings.Repeat("f", 64),
&identity,
&revision,
))
assert.Error(t, createMigrationDeployment(
gormDB,
5,
strings.Repeat("0", 64),
&identity,
&revision,
))
assert.Error(t, createMigrationDeployment(
gormDB,
1,
strings.Repeat("1", 64),
nil,
nil,
))
var runtime model.PagesProjectSourceRuntime
require.NoError(t, gormDB.First(&runtime, sourceID).Error)
assert.Equal(t, "idle", runtime.SyncStatus)
}
func createMigrationDeployment(
gormDB *gorm.DB,
deploymentNumber int,
checksum string,
identity *string,
revision *string,
) error {
return gormDB.Create(&model.PagesDeployment{
ProjectID: pagesMigrationProjectID,
DeploymentNumber: deploymentNumber,
Checksum: checksum,
Status: model.PagesDeploymentStatusUploaded,
UploadID: uint64(710000 + deploymentNumber),
ArtifactPath: fmt.Sprintf("legacy/%d.zip", deploymentNumber),
SourceType: "manual_upload",
SourceIdentity: identity,
SourceRevision: revision,
TriggerType: "manual_upload",
}).Error
}
func assertPagesSourceMigrationDown(t *testing.T, gormDB *gorm.DB) {
t.Helper()
migrator := gormDB.Migrator()
assert.False(t, migrator.HasTable(&model.PagesProjectSource{}))
assert.False(t, migrator.HasTable(&model.PagesProjectSourceRuntime{}))
assert.False(t, migrator.HasColumn(&model.PagesProject{}, "ContentConfigVersion"))
assert.False(t, migrator.HasColumn(&model.PagesDeployment{}, "SourceType"))
assert.False(t, migrator.HasColumn(&model.PagesDeployment{}, "SourceIdentity"))
assert.False(t, migrator.HasColumn(&model.PagesDeployment{}, "SourceRevision"))
assert.False(t, migrator.HasIndex(&model.PagesDeployment{}, "idx_of_pages_deployments_project_number"))
assert.False(t, migrator.HasIndex(&model.PagesDeployment{}, "idx_of_pages_deployments_source_revision"))
assert.True(t, migrator.HasIndex(&model.PagesDeployment{}, "idx_of_pages_deployments_project_id"))
assert.True(t, migrator.HasIndex(&model.PagesDeployment{}, "idx_of_pages_deployments_upload_id"))
var project struct {
Description string
RootDir string
EntryFile string
ActiveDeploymentID *uint
}
require.NoError(t, gormDB.Table("of_pages_projects").Where("id = ?", pagesMigrationProjectID).Take(&project).Error)
assert.Equal(t, "keep-project-data", project.Description)
assert.Equal(t, "public", project.RootDir)
assert.Equal(t, "home.html", project.EntryFile)
require.NotNil(t, project.ActiveDeploymentID)
assert.Equal(t, pagesMigrationDeploymentID, *project.ActiveDeploymentID)
var deployment struct {
UploadID uint64
ArtifactPath string
FileCount int
TotalSize int64
}
require.NoError(t, gormDB.Table("of_pages_deployments").Where("id = ?", pagesMigrationDeploymentID).Take(&deployment).Error)
assert.Equal(t, uint64(700001), deployment.UploadID)
assert.Equal(t, "legacy/package.zip", deployment.ArtifactPath)
assert.Equal(t, 2, deployment.FileCount)
assert.Equal(t, int64(128), deployment.TotalSize)
var count int64
require.NoError(t, gormDB.Table("of_pages_deployments").Where("project_id = ?", pagesMigrationProjectID).Count(&count).Error)
assert.Equal(t, int64(4), count)
}
func assertPagesSourceMigrationUpAgain(t *testing.T, gormDB *gorm.DB) {
t.Helper()
assert.True(t, gormDB.Migrator().HasTable(&model.PagesProjectSource{}))
assert.True(t, gormDB.Migrator().HasTable(&model.PagesProjectSourceRuntime{}))
assert.True(t, gormDB.Migrator().HasColumn(&model.PagesProject{}, "ContentConfigVersion"))
assert.True(t, gormDB.Migrator().HasColumn(&model.PagesDeployment{}, "SourceRevision"))
var count int64
require.NoError(t, gormDB.Table("of_pages_deployments").
Where("project_id = ? AND source_type = ? AND trigger_type = ?", pagesMigrationProjectID, "manual_upload", "manual_upload").
Count(&count).Error)
assert.Equal(t, int64(4), count)
require.NoError(t, gormDB.Table("of_pages_project_sources").Count(&count).Error)
assert.Zero(t, count, "source config is intentionally removed by Down and is not reconstructable")
}