111 lines
3.2 KiB
Go
111 lines
3.2 KiB
Go
package cli
|
||
|
||
import (
|
||
"context"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
|
||
"git.pcux.cn/xuans/Scert/backend/internal/database"
|
||
)
|
||
|
||
func TestSQLiteRestoreRejectsActiveTransaction(t *testing.T) {
|
||
for _, write := range []bool{false, true} {
|
||
name := "reader"
|
||
if write {
|
||
name = "writer"
|
||
}
|
||
t.Run(name, func(t *testing.T) {
|
||
cfg, path, input := prepareRestoreCommandFixture(t, false)
|
||
before, err := os.ReadFile(path)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
db, err := database.Open(t.Context(), cfg.Database)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer db.Close()
|
||
tx, err := db.DB.BeginTx(t.Context(), nil)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer tx.Rollback()
|
||
if write {
|
||
_, err = tx.ExecContext(t.Context(), "UPDATE audit_logs SET message = '占用中的事务'")
|
||
} else {
|
||
var count int
|
||
err = tx.QueryRowContext(t.Context(), "SELECT COUNT(*) FROM audit_logs").Scan(&count)
|
||
}
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
previous := configPath
|
||
configPath = path
|
||
t.Cleanup(func() { configPath = previous })
|
||
command := newRestoreCommand()
|
||
var output strings.Builder
|
||
command.SetOut(&output)
|
||
command.SetErr(&output)
|
||
command.SetArgs([]string{"--input", input})
|
||
if err := command.ExecuteContext(t.Context()); err == nil {
|
||
t.Fatal("存在外部事务时仍执行了恢复")
|
||
}
|
||
after, err := os.ReadFile(path)
|
||
if err != nil || string(after) != string(before) {
|
||
t.Fatalf("预检失败修改了配置:%v", err)
|
||
}
|
||
if err := tx.Rollback(); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if count, err := auditActionCount(cfg.Database.DSN, "current"); err != nil || count != 1 {
|
||
t.Fatalf("预检失败修改了原有数据:数量=%d,错误=%v", count, err)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestSQLiteRestoreTargetSidecarSafety(t *testing.T) {
|
||
for _, suffix := range []string{"-wal", "-shm", "-journal"} {
|
||
t.Run("orphan"+suffix, func(t *testing.T) {
|
||
path := filepath.Join(t.TempDir(), "absent.db")
|
||
if err := os.WriteFile(path+suffix, []byte("保留的日志"), 0o600); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := prepareSQLiteRestoreTarget(t.Context(), path); err == nil {
|
||
t.Fatal("孤立日志未被拒绝")
|
||
}
|
||
if data, err := os.ReadFile(path + suffix); err != nil || string(data) != "保留的日志" {
|
||
t.Fatalf("孤立日志被修改:%v", err)
|
||
}
|
||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||
t.Fatalf("错误创建了数据库:%v", err)
|
||
}
|
||
})
|
||
t.Run("directory"+suffix, func(t *testing.T) {
|
||
path := filepath.Join(t.TempDir(), "current.db")
|
||
if err := createSQLiteTestDatabase(path, "current"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := os.Mkdir(path+suffix, 0o700); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := prepareSQLiteRestoreTarget(t.Context(), path); err == nil {
|
||
t.Fatal("非普通日志路径未被拒绝")
|
||
}
|
||
})
|
||
}
|
||
t.Run("canceled", func(t *testing.T) {
|
||
path := filepath.Join(t.TempDir(), "absent.db")
|
||
ctx, cancel := context.WithCancel(t.Context())
|
||
cancel()
|
||
if err := prepareSQLiteRestoreTarget(ctx, path); err != context.Canceled {
|
||
t.Fatalf("未保留取消错误:%v", err)
|
||
}
|
||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||
t.Fatalf("取消后创建了数据库:%v", err)
|
||
}
|
||
})
|
||
}
|