修正SQLite正式命令行验收版本并覆盖部署历史恢复

This commit is contained in:
xuans 2026-10-01 17:38:13 +08:00
parent 877e7393aa
commit dbcdf05b3e
2 changed files with 87 additions and 1 deletions

View File

@ -85,6 +85,7 @@ func TestSQLiteBusinessUpgradeStandaloneCLI(t *testing.T) {
runSQLiteProductCLI(t, binary, fixture, "certificate", "list")
assertSQLiteCLIUpgradeUsable(t, fixture)
createSQLiteProjectForBackup(t, fixture)
seedSQLiteCLIDeploymentHistory(t, fixture)
upgraded := snapshotSQLiteCLIState(t, *cfg)
currentArchive := filepath.Join(t.TempDir(), "upgraded-backup")
runSQLiteProductCLI(t, binary, fixture, "backup", "--output", currentArchive)
@ -110,6 +111,7 @@ func TestSQLiteBusinessUpgradeStandaloneCLI(t *testing.T) {
_ = current.Close()
t.Fatal(err)
}
mutateSQLiteCLIDeploymentHistory(t, current.DB)
if err := current.Close(); err != nil {
t.Fatal(err)
}
@ -289,7 +291,7 @@ func assertSQLiteCLIUpgradeUsable(t *testing.T, fixture sqliteBusinessFixture) {
}
defer db.Close()
var version, bindings int
if err := db.DB.QueryRowContext(t.Context(), "SELECT MAX(version) FROM schema_migrations").Scan(&version); err != nil || version != 14 {
if err := db.DB.QueryRowContext(t.Context(), "SELECT MAX(version) FROM schema_migrations").Scan(&version); err != nil || version != expectedSQLiteCLIMigrationVersion(t, db.DB) {
t.Fatalf("正式应用入口未完成历史升级:%d,%v", version, err)
}
project, err := repository.NewSQLProjectRepository(db.DB).GetProject(t.Context(), domain.DefaultProjectID)

View File

@ -0,0 +1,84 @@
package cli
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"git.pcux.cn/xuans/Scert/backend/internal/database"
)
// 以当批源码目录为预期,同时校验正式程序写入的迁移历史连续且完整。
func expectedSQLiteCLIMigrationVersion(t *testing.T, db *sql.DB) int {
t.Helper()
entries, err := os.ReadDir(filepath.Join("..", "database", "migrations", "sqlite"))
if err != nil || len(entries) == 0 {
t.Fatalf("无法读取本批 SQLite 迁移目录:%v", err)
}
for index, entry := range entries {
if entry.IsDir() || !strings.HasPrefix(entry.Name(), fmt.Sprintf("%04d_", index+1)) || !strings.HasSuffix(entry.Name(), ".sql") {
t.Fatalf("SQLite 迁移文件序列无效:%s", entry.Name())
}
}
rows, err := db.QueryContext(t.Context(), "SELECT version FROM schema_migrations ORDER BY version")
if err != nil {
t.Fatal(err)
}
defer rows.Close()
count := 0
for rows.Next() {
var version int
if err := rows.Scan(&version); err != nil {
t.Fatal(err)
}
count++
if version != count {
t.Fatalf("正式程序迁移历史不连续:期望 %d,实际 %d", count, version)
}
}
if err := rows.Err(); err != nil {
t.Fatal(err)
}
if count != len(entries) {
t.Fatalf("正式程序迁移不完整:源码 %d 项,数据库 %d 项", len(entries), count)
}
return len(entries)
}
// 仅在隔离样本中写入终态事务,用于验证存储恢复,不作为远端恢复证据。
func seedSQLiteCLIDeploymentHistory(t *testing.T, fixture sqliteBusinessFixture) {
t.Helper()
db, err := database.Open(t.Context(), fixture.config.Database)
if err != nil {
t.Fatal(err)
}
defer db.Close()
for _, statement := range []string{
`INSERT INTO task_idempotency (domain_id, idempotency_key, task_id) VALUES ('domain', 'backup-deploy-key', 'task')`,
`INSERT INTO task_attempts (id, task_id, attempt_number, stage, status, message) VALUES ('backup-attempt', 'task', 1, 'certificate_deploy', 'success', '历史部署尝试')`,
`INSERT INTO task_deployment_results (task_id, attempt_id, attempt_number, result_index, server_id, server_name, status, message, backup_id) VALUES ('task', 'backup-attempt', 1, 0, 'server', '历史服务器', 'success', '历史部署结果', 'backup-transaction')`,
`INSERT INTO deployment_transactions (id, task_id, attempt_id, attempt_number, result_index, server_id, server_name, certificate_path, certificate_chain_path, private_key_path, temporary_paths_json, installed_paths_json, backups_json, lock_paths_json, reload_attempted, phase, status, message, recovery_evidence_json) VALUES ('backup-transaction', 'task', 'backup-attempt', 1, 0, 'server', '历史服务器', '/test/cert.pem', '/test/chain.pem', '/test/key.pem', '[]', '["/test/cert.pem"]', '[]', '[]', 1, 'committed', 'committed', '历史事务', '{"version":1,"lock_token":"fixture-lock","address":"192.0.2.10","port":22}')`,
} {
if _, err := db.DB.ExecContext(t.Context(), statement); err != nil {
t.Fatal(err)
}
}
}
// 主动改变新增表,避免用空表或未变化的数据宣称恢复成功。
func mutateSQLiteCLIDeploymentHistory(t *testing.T, db *sql.DB) {
t.Helper()
for _, statement := range []string{
`UPDATE task_idempotency SET idempotency_key = 'changed-key'`,
`UPDATE task_attempts SET message = '待恢复尝试'`,
`UPDATE task_deployment_results SET message = '待恢复结果'`,
`UPDATE deployment_transactions SET recovery_evidence_json = '{}', message = '待恢复证据'`,
} {
if _, err := db.ExecContext(t.Context(), statement); err != nil {
t.Fatal(err)
}
}
}