Files
OpenFlare/internal/infra/persistence/migrator/migrator_test.go
T
ryan 3b010bb15e feat(log): disable user access log recording
- 移除全局用户访问日志采集中间件与批写入 writer(risk_control 包整包删除),
  不再写入 w_user_access_logs;存量数据与管理端访问日志统计页面保留
- 日志库迁移任务不再排空用户访问日志队列,状态接口不再展示其缓冲队列统计
- 迁移测试的系统配置 seed 计数断言更新为当前实际值(86 → 95),
  注释改为提示新增配置 seed 时同步更新
2026-08-09 10:35:39 +08:00

212 lines
7.4 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package migrator
import (
"context"
"strings"
"testing"
"github.com/Rain-kl/Wavelet/internal/infra/config"
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/alicebob/miniredis/v2"
"github.com/glebarez/sqlite"
"github.com/redis/go-redis/v9"
"github.com/redis/go-redis/v9/maintnotifications"
"gorm.io/gorm"
)
// expectedMigratedSystemConfigCount 为全新库执行全部迁移后 w_system_configs 的行数
// (初始系统配置 + 各期配置迁移/新增 seed:of_options 迁移、文件白名单、磁盘缓存、
// 登录会话 TTL、升级源、存储、FRPS Web UI、Pages、OpenResty 限流、单 IP 限频、
// 错误页、SW 离线、日志保留期、指标保留期等);新增配置 seed 迁移时需同步更新本常量。
const expectedMigratedSystemConfigCount = 95
func TestMigrateInitializesSQLiteDatabase(t *testing.T) {
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
if err != nil {
t.Fatalf("gorm.Open(sqlite) error = %v", err)
}
mr, err := miniredis.Run()
if err != nil {
t.Fatalf("miniredis.Run() error = %v", err)
}
redisClient := redis.NewClient(&redis.Options{
Addr: mr.Addr(),
MaintNotificationsConfig: &maintnotifications.Config{
Mode: maintnotifications.ModeDisabled,
},
})
previousDBEnabled := config.Config.Database.Enabled
config.Config.Database.Enabled = false
db.SetDB(sqliteDB)
t.Cleanup(func() {
config.Config.Database.Enabled = previousDBEnabled
db.SetDB(nil)
_ = redisClient.Close()
mr.Close()
})
Migrate()
var systemConfigCount int64
if err := sqliteDB.Table("w_system_configs").Count(&systemConfigCount).Error; err != nil {
t.Fatalf("Migrate() count w_system_configs error = %v", err)
}
if systemConfigCount != expectedMigratedSystemConfigCount {
t.Errorf("Migrate() w_system_configs count = %d, want %d", systemConfigCount, expectedMigratedSystemConfigCount)
}
var adminCount int64
if err := sqliteDB.Table("w_users").Where("username = ?", "admin").Count(&adminCount).Error; err != nil {
t.Fatalf("Migrate() count admin user error = %v", err)
}
if adminCount != 1 {
t.Errorf("Migrate() admin user count = %d, want %d", adminCount, 1)
}
var templateCount int64
if err := sqliteDB.Table("w_templates").Count(&templateCount).Error; err != nil {
t.Fatalf("Migrate() count templates error = %v", err)
}
if templateCount != 2 {
t.Errorf("Migrate() templates count = %d, want %d", templateCount, 2)
}
if !sqliteDB.Migrator().HasTable("of_zones") {
t.Error("Migrate() did not create of_zones")
}
if !sqliteDB.Migrator().HasTable("of_zone_domains") {
t.Error("Migrate() did not create of_zone_domains")
}
for _, table := range []string{
"of_cf_connections",
"of_cf_pointing_groups",
"of_cf_pointing_members",
} {
if !sqliteDB.Migrator().HasTable(table) {
t.Errorf("Migrate() did not create %s", table)
}
}
if !sqliteDB.Migrator().HasColumn("of_cf_connections", "authorization") {
t.Error("Migrate() did not create of_cf_connections.authorization")
}
if sqliteDB.Migrator().HasTable("of_managed_domains") {
t.Error("Migrate() should drop of_managed_domains after phase-2 cleanup")
}
if sqliteDB.Migrator().HasColumn(&model.ProxyRoute{}, "domain") {
t.Error("Migrate() should drop of_proxy_routes.domain after phase-2 cleanup")
}
if sqliteDB.Migrator().HasColumn(&model.ProxyRoute{}, "domains") {
t.Error("Migrate() should drop of_proxy_routes.domains after phase-2 cleanup")
}
if sqliteDB.Migrator().HasColumn(&model.ProxyRoute{}, "cert_id") {
t.Error("Migrate() should drop of_proxy_routes.cert_id after phase-2 cleanup")
}
if sqliteDB.Migrator().HasColumn(&model.ProxyRoute{}, "cert_ids") {
t.Error("Migrate() should drop of_proxy_routes.cert_ids after phase-2 cleanup")
}
if sqliteDB.Migrator().HasColumn(&model.ProxyRoute{}, "domain_cert_ids") {
t.Error("Migrate() should drop of_proxy_routes.domain_cert_ids after phase-2 cleanup")
}
zone := model.Zone{Domain: "example.com"}
if err := sqliteDB.Create(&zone).Error; err != nil {
t.Fatalf("Migrate() create Zone error = %v", err)
}
if err := sqliteDB.Create(&model.Zone{Domain: zone.Domain}).Error; err == nil {
t.Error("Migrate() allowed duplicate of_zones.domain")
}
domain := model.ZoneDomain{ZoneID: zone.ID, Domain: "api.example.com"}
if err := sqliteDB.Create(&domain).Error; err != nil {
t.Fatalf("Migrate() create ZoneDomain error = %v", err)
}
if err := sqliteDB.Create(&model.ZoneDomain{ZoneID: zone.ID, Domain: domain.Domain}).Error; err == nil {
t.Error("Migrate() allowed duplicate of_zone_domains.domain")
}
if err := sqliteDB.Exec(`INSERT INTO of_cf_pointing_members
(group_id, zone_domain_id, proxied, cf_zone_id, cf_record_id, desired_ip, sync_status, last_error)
VALUES (?, ?, ?, '', '', '', 'pending', '')`, 1, domain.ID, false).Error; err != nil {
t.Fatalf("Migrate() insert Cloudflare member error = %v", err)
}
if err := sqliteDB.Exec(`INSERT INTO of_cf_pointing_members
(group_id, zone_domain_id, proxied, cf_zone_id, cf_record_id, desired_ip, sync_status, last_error)
VALUES (?, ?, ?, '', '', '', 'pending', '')`, 2, domain.ID, false).Error; err == nil {
t.Error("Migrate() allowed duplicate of_cf_pointing_members.zone_domain_id")
}
}
func TestCloudflarePointingPostgresMigrationQuotesAuthorizationColumn(t *testing.T) {
content, err := migrationFS.ReadFile("goose/postgres/202608040001_create_cloudflare_pointing.sql")
if err != nil {
t.Fatalf("read Cloudflare pointing migration: %v", err)
}
if !strings.Contains(string(content), `"authorization" TEXT NOT NULL DEFAULT ''`) {
t.Error("Cloudflare pointing PostgreSQL migration must quote reserved column authorization")
}
}
func TestMigrateClearsStaleSystemConfigCache(t *testing.T) {
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
if err != nil {
t.Fatalf("gorm.Open(sqlite) error = %v", err)
}
mr, err := miniredis.Run()
if err != nil {
t.Fatalf("miniredis.Run() error = %v", err)
}
redisClient := redis.NewClient(&redis.Options{Addr: mr.Addr()})
previousDBEnabled := config.Config.Database.Enabled
previousRedis := db.Redis
config.Config.Database.Enabled = false
db.SetDB(sqliteDB)
db.Redis = redisClient
t.Cleanup(func() {
config.Config.Database.Enabled = previousDBEnabled
db.SetDB(nil)
db.Redis = previousRedis
_ = redisClient.Close()
mr.Close()
})
staleConfig := model.SystemConfig{
Key: model.ConfigKeyEmailLoginVerificationEnabled,
Value: "true",
Type: "system",
}
if err := db.HSetJSON(context.Background(), repository.SystemConfigRedisHashKey, model.ConfigKeyEmailLoginVerificationEnabled, &staleConfig); err != nil {
t.Fatalf("HSetJSON() error = %v", err)
}
Migrate()
exists, err := db.Redis.Exists(context.Background(), db.PrefixedKey(repository.SystemConfigRedisHashKey)).Result()
if err != nil {
t.Fatalf("Redis.Exists() error = %v", err)
}
if exists != 0 {
t.Fatalf("system config cache exists = %d, want 0", exists)
}
enabled, err := repository.GetBoolByKey(context.Background(), model.ConfigKeyEmailLoginVerificationEnabled)
if err != nil {
t.Fatalf("GetBoolByKey(%s) error = %v", model.ConfigKeyEmailLoginVerificationEnabled, err)
}
if enabled {
t.Fatalf("GetBoolByKey(%s) = true, want false", model.ConfigKeyEmailLoginVerificationEnabled)
}
}