Files
OpenFlare/internal/db/migrator/bridge_test.go
T
2026-06-19 22:18:52 +08:00

271 lines
8.3 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package migrator
import (
"testing"
"time"
"github.com/Rain-kl/Wavelet/internal/config"
"github.com/Rain-kl/Wavelet/internal/db"
"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"
)
func TestMigrateUpgradesLegacyDatabaseAndPreservesData(t *testing.T) {
// 1. Initialize an empty in-memory SQLite DB
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
if err != nil {
t.Fatalf("gorm.Open(sqlite) error = %v", err)
}
rawDB, err := sqliteDB.DB()
if err != nil {
t.Fatalf("sqliteDB.DB() error = %v", err)
}
// 2. Set up the legacy schema manually
setupLegacySchema := []string{
`CREATE TABLE users (
id integer(64) NOT NULL,
username text,
password text NOT NULL,
display_name text,
role integer(64),
status integer(64),
token text,
email text,
github_id text,
wechat_id text,
CONSTRAINT users_pkey PRIMARY KEY (id)
);`,
`CREATE TABLE options (
key text NOT NULL,
value text,
CONSTRAINT options_pkey PRIMARY KEY (key)
);`,
`CREATE TABLE origins (
id integer(64) NOT NULL,
name text(255) NOT NULL,
address text(255) NOT NULL,
remark text(255),
created_at text(6),
updated_at text(6),
CONSTRAINT origins_pkey PRIMARY KEY (id)
);`,
`CREATE TABLE apply_logs (
id integer(64) NOT NULL,
node_id text(64) NOT NULL,
version text(32) NOT NULL,
result text(32) NOT NULL,
message text,
checksum text(64) NOT NULL,
main_config_checksum text(64) NOT NULL,
route_config_checksum text(64) NOT NULL,
support_file_count integer(64) NOT NULL,
created_at text(6),
CONSTRAINT apply_logs_pkey PRIMARY KEY (id)
);`,
// Legacy log tables (which should be dropped)
`CREATE TABLE node_access_logs_00 (
id integer(64) NOT NULL,
node_id text(64) NOT NULL,
logged_at text(6) NOT NULL
);`,
`CREATE TABLE node_system_profiles (
id integer(64) NOT NULL,
node_id text(64) NOT NULL,
hostname text(255)
);`,
}
for _, query := range setupLegacySchema {
if _, err := rawDB.Exec(query); err != nil {
t.Fatalf("Exec setup query failed: %v", err)
}
}
// 3. Populate with legacy mock data
insertMockData := []string{
// Legacy admin user
`INSERT INTO users (id, username, password, display_name, role, status, email)
VALUES (1, 'ryan', 'hashed_pass_123', 'Root User ryan', 100, 1, 'ryan@example.com');`,
// Legacy normal user
`INSERT INTO users (id, username, password, display_name, role, status, email)
VALUES (2, 'jack', 'hashed_pass_456', 'Normal User jack', 10, 1, 'jack@example.com');`,
// Legacy config options
`INSERT INTO options (key, value) VALUES ('GitHubClientId', 'Ov23lixMKW');`,
`INSERT INTO options (key, value) VALUES ('GitHubClientSecret', 'secret_abc_123');`,
// Legacy origin
`INSERT INTO origins (id, name, address, remark, created_at, updated_at)
VALUES (10, 'my_origin', '127.0.0.1:8080', 'original origin', '2026-06-01 12:00:00', '2026-06-01 12:00:00');`,
// Legacy apply log
`INSERT INTO apply_logs (id, node_id, version, result, message, checksum, main_config_checksum, route_config_checksum, support_file_count, created_at)
VALUES (100, 'node_1', 'v1.0.0', 'success', 'applied successfully', 'hash1', 'hash2', 'hash3', 2, '2026-06-01 12:00:00');`,
}
for _, query := range insertMockData {
if _, err := rawDB.Exec(query); err != nil {
t.Fatalf("Exec mock data query failed: %v", err)
}
}
// 4. Set up mock services (Redis & Config)
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
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()
})
// 5. Run the Goose migrations
Migrate()
// 6. Assertions on migrated data schema & records
// Check that the w_users table exists and contains correct columns/records
var usersCount int64
if err := sqliteDB.Table("w_users").Count(&usersCount).Error; err != nil {
t.Fatalf("count w_users error: %v", err)
}
if usersCount < 2 {
t.Errorf("expected at least 2 users, got %d", usersCount)
}
// Verify user 1 (ryan) details
type userStruct struct {
ID int64
Username string
Nickname string
Email string
IsActive bool
IsAdmin bool
}
var ryanUser userStruct
if err := sqliteDB.Table("w_users").Where("id = ?", 1).First(&ryanUser).Error; err != nil {
t.Fatalf("query ryan user failed: %v", err)
}
if ryanUser.Username != "ryan" || ryanUser.Nickname != "Root User ryan" || !ryanUser.IsActive || !ryanUser.IsAdmin {
t.Errorf("ryan user migrated incorrectly: %+v", ryanUser)
}
// Verify user 2 (jack) details
var jackUser userStruct
if err := sqliteDB.Table("w_users").Where("id = ?", 2).First(&jackUser).Error; err != nil {
t.Fatalf("query jack user failed: %v", err)
}
if jackUser.Username != "jack" || jackUser.Nickname != "Normal User jack" || !jackUser.IsActive || jackUser.IsAdmin {
t.Errorf("jack user migrated incorrectly: %+v", jackUser)
}
// Verify options migrated to of_options
var githubClientIDVal string
if err := sqliteDB.Table("of_options").Where("key = ?", "GitHubClientId").Select("value").Scan(&githubClientIDVal).Error; err != nil {
t.Fatalf("query GitHubClientId failed: %v", err)
}
if githubClientIDVal != "Ov23lixMKW" {
t.Errorf("GitHubClientId value incorrect: got %s, want Ov23lixMKW", githubClientIDVal)
}
var githubClientSecretVal string
if err := sqliteDB.Table("of_options").Where("key = ?", "GitHubClientSecret").Select("value").Scan(&githubClientSecretVal).Error; err != nil {
t.Fatalf("query GitHubClientSecret failed: %v", err)
}
if githubClientSecretVal != "secret_abc_123" {
t.Errorf("GitHubClientSecret value incorrect: got %s, want secret_abc_123", githubClientSecretVal)
}
// Verify origin migrated to of_origins
type originStruct struct {
ID int64
Name string
Address string
Remark string
}
var testOrigin originStruct
if err := sqliteDB.Table("of_origins").Where("id = ?", 10).First(&testOrigin).Error; err != nil {
t.Fatalf("query my_origin failed: %v", err)
}
if testOrigin.Name != "my_origin" || testOrigin.Address != "127.0.0.1:8080" || testOrigin.Remark != "original origin" {
t.Errorf("origin migrated incorrectly: %+v", testOrigin)
}
// Verify apply log migrated to of_apply_logs
type applyLogStruct struct {
ID int64
NodeID string
Version string
Result string
Message string
CreatedAt time.Time
}
var testApplyLog applyLogStruct
if err := sqliteDB.Table("of_apply_logs").Where("id = ?", 100).First(&testApplyLog).Error; err != nil {
t.Fatalf("query apply_logs failed: %v", err)
}
if testApplyLog.NodeID != "node_1" || testApplyLog.Version != "v1.0.0" || testApplyLog.Result != "success" || testApplyLog.Message != "applied successfully" {
t.Errorf("apply log migrated incorrectly: %+v", testApplyLog)
}
// 7. Verify that all legacy_ prefix tables have been dropped
legacyTablesToCheck := []string{
"legacy_users",
"legacy_options",
"legacy_origins",
"legacy_apply_logs",
"legacy_proxy_routes",
"legacy_nodes",
"legacy_waf_rule_groups",
"legacy_waf_rule_group_bindings",
"legacy_waf_ip_groups",
"legacy_tls_certificates",
"legacy_managed_domains",
"legacy_dns_accounts",
"legacy_acme_accounts",
"legacy_config_versions",
"legacy_pages_projects",
"legacy_pages_deployments",
"legacy_pages_deployment_files",
"users",
"options",
"origins",
"apply_logs",
"node_access_logs_00",
"node_system_profiles",
}
for _, table := range legacyTablesToCheck {
var count int
if err := sqliteDB.Raw("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?", table).Scan(&count).Error; err != nil {
t.Fatalf("check table %s existence failed: %v", table, err)
}
if count > 0 {
t.Errorf("expected table %s to be dropped, but it still exists", table)
}
}
}