165 lines
7.0 KiB
Go
165 lines
7.0 KiB
Go
package database
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.pcux.cn/xuans/Scert/backend/internal/config"
|
|
"git.pcux.cn/xuans/Scert/backend/internal/testsupport/mysqltest"
|
|
"git.pcux.cn/xuans/Scert/backend/internal/testsupport/postgrestest"
|
|
)
|
|
|
|
func TestSQLiteDNSAccountProjectGuards(t *testing.T) {
|
|
for _, foreignKeys := range []int{0, 1} {
|
|
for _, recursive := range []int{0, 1} {
|
|
t.Run(fmt.Sprintf("foreign_keys_%d_recursive_%d", foreignKeys, recursive), func(t *testing.T) {
|
|
cfg := sqliteBusinessUpgradeConfig(t)
|
|
cfg.MaxOpenConns, cfg.MaxIdleConns = 1, 1
|
|
db, err := Open(t.Context(), cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
for _, statement := range []string{fmt.Sprintf("PRAGMA foreign_keys=%d", foreignKeys), fmt.Sprintf("PRAGMA recursive_triggers=%d", recursive)} {
|
|
if _, err := db.DB.ExecContext(t.Context(), statement); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := Migrate(t.Context(), db.DB, "sqlite"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := db.DB.ExecContext(t.Context(), `INSERT INTO projects (id,name,description) VALUES ('dns-only','账户项目','')`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := db.DB.ExecContext(t.Context(), `INSERT INTO dns_accounts VALUES ('kept','账户','dns-only','retired-owner','aliyun','secret://dns/kept','active','unverified',1,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, statement := range []string{
|
|
`INSERT INTO dns_accounts VALUES ('orphan','账户','missing','owner','aliyun','secret://dns/orphan','active','unverified',1,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)`,
|
|
`UPDATE dns_accounts SET project_id='missing' WHERE id='kept'`,
|
|
`DELETE FROM projects WHERE id='dns-only'`,
|
|
`UPDATE projects SET id='moved' WHERE id='dns-only'`,
|
|
`INSERT OR REPLACE INTO projects (id,name,description) VALUES ('dns-only','覆盖','')`,
|
|
} {
|
|
if _, err := db.DB.ExecContext(t.Context(), statement); err == nil {
|
|
t.Fatal("项目保护没有拒绝破坏引用的操作")
|
|
}
|
|
}
|
|
for name, want := range map[string]int{"foreign_keys": foreignKeys, "recursive_triggers": recursive} {
|
|
var value int
|
|
if err := db.DB.QueryRowContext(t.Context(), "PRAGMA "+name).Scan(&value); err != nil || value != want {
|
|
t.Fatal("迁移改变用户连接参数", name, value, err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDNSAccountMigration(t *testing.T) {
|
|
for _, driver := range []string{"sqlite", "mysql", "postgres"} {
|
|
t.Run(driver, func(t *testing.T) {
|
|
var cfg config.DatabaseConfig
|
|
switch driver {
|
|
case "sqlite":
|
|
cfg = sqliteBusinessUpgradeConfig(t)
|
|
case "mysql":
|
|
cfg = mysqltest.New(t).Database(t)
|
|
case "postgres":
|
|
cfg = postgrestest.New(t).Database(t)
|
|
}
|
|
db, err := Open(t.Context(), cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
if _, err := db.DB.ExecContext(t.Context(), migrationTableSQL(driver)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for version := 1; version <= 26; version++ {
|
|
if err := applyMigration(t.Context(), db.DB, driver, version); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if _, err := db.DB.ExecContext(t.Context(), `INSERT INTO domains (id,name,certificate_type,sans_json,status,challenge_type)
|
|
VALUES ('legacy-dns','legacy.example.com','single','[]','valid','http-01')`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := Migrate(t.Context(), db.DB, driver); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
query := `INSERT INTO dns_accounts (id,name,project_id,owner_user_id,provider,credential_ref,status,verification_state,revision,created_at,updated_at)
|
|
VALUES ('kept','保留账户','default','missing-owner','aliyun','secret://dns/kept','disabled','unverified',9,?,?)`
|
|
if driver == "postgres" {
|
|
query = `INSERT INTO dns_accounts (id,name,project_id,owner_user_id,provider,credential_ref,status,verification_state,revision,created_at,updated_at)
|
|
VALUES ('kept','保留账户','default','missing-owner','aliyun','secret://dns/kept','disabled','unverified',9,$1,$2)`
|
|
}
|
|
if _, err := db.DB.ExecContext(t.Context(), query, time.Now().UTC(), time.Now().UTC()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := Migrate(t.Context(), db.DB, driver); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var status, challenge string
|
|
if err := db.DB.QueryRowContext(t.Context(), `SELECT status,challenge_type FROM domains WHERE id='legacy-dns'`).Scan(&status, &challenge); err != nil || status != "valid" || challenge != "http-01" {
|
|
t.Fatal("升级改变历史域名", err)
|
|
}
|
|
var revision int
|
|
if err := db.DB.QueryRowContext(t.Context(), `SELECT status,revision FROM dns_accounts WHERE id='kept'`).Scan(&status, &revision); err != nil || status != "disabled" || revision != 9 {
|
|
t.Fatal("重入覆盖已有账户", err)
|
|
}
|
|
if _, err := db.DB.ExecContext(t.Context(), `UPDATE dns_accounts SET project_id='missing' WHERE id='kept'`); err == nil {
|
|
t.Fatal("项目外键没有生效")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMySQLDNSAccountInterruptedMigration(t *testing.T) {
|
|
f := startRealMySQL(t)
|
|
for _, boundary := range []string{"before_create", "after_create", "after_constraint", "with_record", "bad_column", "bad_index", "bad_foreign_key", "bad_collation"} {
|
|
t.Run(boundary, func(t *testing.T) {
|
|
db := f.database(t)
|
|
mysqlPrepareVersion(t, db.DB, 26)
|
|
statements := mysqlDeploymentStatements(t, 27)
|
|
if boundary != "before_create" {
|
|
mysqlDeploymentExec(t, db.DB, statements[0])
|
|
}
|
|
if boundary == "after_constraint" || boundary == "with_record" {
|
|
mysqlDeploymentExec(t, db.DB, statements[1])
|
|
}
|
|
conflict := true
|
|
switch boundary {
|
|
case "bad_column":
|
|
mysqlDeploymentExec(t, db.DB, "ALTER TABLE dns_accounts MODIFY COLUMN revision INT NULL")
|
|
case "bad_index":
|
|
mysqlDeploymentExec(t, db.DB, "ALTER TABLE dns_accounts DROP INDEX uq_dns_accounts_credential")
|
|
case "bad_foreign_key":
|
|
mysqlDeploymentExec(t, db.DB, "ALTER TABLE dns_accounts ADD CONSTRAINT fk_dns_accounts_project FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE")
|
|
case "bad_collation":
|
|
mysqlDeploymentExec(t, db.DB, "ALTER TABLE dns_accounts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
|
|
default:
|
|
conflict = false
|
|
}
|
|
if boundary == "with_record" {
|
|
mysqlDeploymentExec(t, db.DB, `INSERT INTO dns_accounts VALUES ('kept','保留账户','default','owner','aliyun','secret://dns/kept','disabled','unverified',7,NOW(),NOW())`)
|
|
}
|
|
err := Migrate(t.Context(), db.DB, "mysql")
|
|
if (err != nil) != conflict {
|
|
t.Fatal("中断恢复或冲突拒绝错误", err)
|
|
}
|
|
version, err := currentMigrationVersion(t.Context(), db.DB)
|
|
if err != nil || (conflict && version != 26) || (!conflict && version != latestMigrationVersion) {
|
|
t.Fatal("迁移版本错误", version, err)
|
|
}
|
|
if boundary == "with_record" {
|
|
var revision int
|
|
if err := db.DB.QueryRowContext(t.Context(), `SELECT revision FROM dns_accounts WHERE id='kept'`).Scan(&revision); err != nil || revision != 7 {
|
|
t.Fatal("中断恢复丢失原有账户", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|