- 配置版本:fsutil 版本化备份(内容相同自动去重),主 nginx.conf 与 VPS vhost 的版本列表/逐行差异/一键回滚,回滚先校验语法与 nginx -t 再写回 - agent 0.12.2:写 vhost 前留版本;nginx -t 校验失败改为恢复上一个可用版本(此前直接删文件),新增域名 vhost 版本端点,双架构二进制已重编 - 通知:补企业微信 / 钉钉 / 飞书机器人,切换预设时服务地址跟随该预设默认值 - 阈值告警:新增 internal/monitor 巡检(CPU/内存/磁盘持续超阈值、隧道连续 N 次未注册),默认关闭,带静默期与恢复通知 - API Token:api_tokens 表 + Authorization Bearer 认证,只读/读写两种范围;令牌管理与改密/退出仅限登录会话
This commit is contained in:
@@ -69,3 +69,132 @@ func TestBackupCopiesContent(t *testing.T) {
|
||||
t.Fatalf("备份内容不符:%q", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupVersionedStoresContent(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "nginx.conf")
|
||||
if err := os.WriteFile(path, []byte("worker_processes 1;"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
version, err := BackupVersioned(path, 5)
|
||||
if err != nil || version == "" {
|
||||
t.Fatalf("应返回版本路径:%q %v", version, err)
|
||||
}
|
||||
if !strings.Contains(version, VersionsDir(path)) {
|
||||
t.Fatalf("版本应落在版本目录内:%s", version)
|
||||
}
|
||||
data, err := os.ReadFile(version)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(data) != "worker_processes 1;" {
|
||||
t.Fatalf("版本内容不符:%q", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupVersionedPrunesToKeep(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "nginx.conf")
|
||||
// 每次写入不同内容:内容相同会被去重,剪枝也就无从检验
|
||||
for _, content := range []string{"v1", "v2", "v3", "v4", "v5"} {
|
||||
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := BackupVersioned(path, 2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
entries, err := os.ReadDir(VersionsDir(path))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(entries) != 2 {
|
||||
t.Fatalf("应只保留 2 份版本,实际 %d", len(entries))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupVersionedSkipsMissingFile(t *testing.T) {
|
||||
version, err := BackupVersioned(filepath.Join(t.TempDir(), "nope.conf"), 5)
|
||||
if err != nil || version != "" {
|
||||
t.Fatalf("文件不存在时应返回空路径且无错误:%q %v", version, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListVersionsNewestFirst(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "nginx.conf")
|
||||
if err := os.WriteFile(path, []byte("v1"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := BackupVersioned(path, 5); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte("v2"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := BackupVersioned(path, 5); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
versions, err := ListVersions(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(versions) != 2 || versions[0].Name <= versions[1].Name {
|
||||
t.Fatalf("版本应按名称倒序(新的在前)列出:%v", versions)
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, version := range versions {
|
||||
data, err := ReadVersion(path, version.Name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version.Size != int64(len(data)) {
|
||||
t.Fatalf("版本大小不符:%d vs %d", version.Size, len(data))
|
||||
}
|
||||
seen[string(data)] = true
|
||||
}
|
||||
if !seen["v1"] || !seen["v2"] {
|
||||
t.Fatalf("两个版本的内容都应在:%v", seen)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupVersionedSkipsIdenticalContent(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "nginx.conf")
|
||||
if err := os.WriteFile(path, []byte("same"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
first, err := BackupVersioned(path, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
second, err := BackupVersioned(path, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if first != second {
|
||||
t.Fatalf("内容未变时不应新增版本:%q vs %q", first, second)
|
||||
}
|
||||
versions, err := ListVersions(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(versions) != 1 {
|
||||
t.Fatalf("内容未变时应只有 1 个版本,实际 %d", len(versions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadVersionRejectsTraversal(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "nginx.conf")
|
||||
if _, err := ReadVersion(path, "../nginx.conf"); err == nil {
|
||||
t.Fatal("带路径上跳的版本名应被拒绝")
|
||||
}
|
||||
if _, err := ReadVersion(path, "missing.conf"); err == nil {
|
||||
t.Fatal("不存在的版本应报错")
|
||||
}
|
||||
versions, err := ListVersions(path)
|
||||
if err != nil || len(versions) != 0 {
|
||||
t.Fatalf("版本目录不存在时应返回空列表且无错误:%v %v", versions, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user