- 自动备份:每日一份(按最新备份是否在 24h 内判定,重启不重复)、保留份数可配、列表/下载/恢复/删除,恢复前先校验归档;顺手修 writeArchive 逐个检查 tar/gzip/文件 Close()(审查报告 §二) - 健康持续化:只存状态变更(up/down/unknown)并推导可用率与不可用区间;公开状态页默认关闭、访问码存 SHA-256、按 IP 限流、只暴露域名与可用率 - 隧道判定修复:RouteHealthCandidate 新增 tunnel_known,frps 管理接口不可用时不再把「判不出来」当成「不通」(此前会误报「隧道连续不通」);汇总与徽标同样区分「无法判定」 - 指标历史:metrics_samples 纵向点表(本机 CPU/内存/磁盘 + 每规则上下行,30 天保留、查询按桶平均、不做降采样表);仪表盘趋势卡支持 1h/24h/7d - 仪表盘布局:运行状态并入顶部统计区(运行时长补进「服务」卡);性能并入系统资源那一行,整行 4 列 - 测试:backup/metrics/frp/api/app 新增用例;gofmt、go build/vet/test、vue-tsc、vite build 全绿 - agent 无改动,AgentVersion 保持 0.12.2
131 lines
3.6 KiB
Go
131 lines
3.6 KiB
Go
package backup
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDueForBackup(t *testing.T) {
|
|
now := time.Date(2026, 9, 25, 10, 0, 0, 0, time.UTC)
|
|
|
|
// 还没有备份 → 立刻做一份
|
|
if !dueForBackup(time.Time{}, now, AutoInterval) {
|
|
t.Fatal("没有备份时应立即备份")
|
|
}
|
|
// 刚备份过 → 跳过:这正是重启不会重复备份的原因
|
|
if dueForBackup(now.Add(-time.Hour), now, AutoInterval) {
|
|
t.Fatal("间隔内的备份不应重复执行")
|
|
}
|
|
// 正好到间隔 → 该做了
|
|
if !dueForBackup(now.Add(-AutoInterval), now, AutoInterval) {
|
|
t.Fatal("到达间隔应再备份一次")
|
|
}
|
|
// 超过间隔 → 该做
|
|
if !dueForBackup(now.Add(-25*time.Hour), now, AutoInterval) {
|
|
t.Fatal("超过间隔应再备份一次")
|
|
}
|
|
}
|
|
|
|
func TestExportVerifyRoundTrip(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(dir, "fonu.db"), []byte("db"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
svc := New(dir)
|
|
|
|
path, err := svc.Export(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("export: %v", err)
|
|
}
|
|
entries, err := svc.Verify(context.Background(), path)
|
|
if err != nil {
|
|
t.Fatalf("verify: %v", err)
|
|
}
|
|
if entries < 1 {
|
|
t.Fatalf("条目数应至少为 1,实际 %d", entries)
|
|
}
|
|
|
|
archives, err := svc.List()
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(archives) != 1 {
|
|
t.Fatalf("应列出 1 份备份,实际 %d", len(archives))
|
|
}
|
|
if archives[0].Size == 0 || !archiveNamePattern.MatchString(archives[0].Name) {
|
|
t.Fatalf("备份条目异常:%+v", archives[0])
|
|
}
|
|
}
|
|
|
|
func TestVerifyRejectsGarbage(t *testing.T) {
|
|
svc := New(t.TempDir())
|
|
path := filepath.Join(svc.backupsDir(), "fonu-backup-20260925-000000-1.tar.gz")
|
|
if err := os.MkdirAll(svc.backupsDir(), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte("not a gzip stream"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := svc.Verify(context.Background(), path); err == nil {
|
|
t.Fatal("非 gzip 内容应校验失败")
|
|
}
|
|
}
|
|
|
|
func TestListPruneAndPath(t *testing.T) {
|
|
svc := New(t.TempDir())
|
|
if err := os.MkdirAll(svc.backupsDir(), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, name := range []string{
|
|
"fonu-backup-20260923-100000-1.tar.gz",
|
|
"fonu-backup-20260924-100000-2.tar.gz",
|
|
"fonu-backup-20260925-100000-3.tar.gz",
|
|
} {
|
|
if err := os.WriteFile(filepath.Join(svc.backupsDir(), name), []byte("x"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// 不符合命名格式的文件不参与:备份目录里可能混着手工放进来的东西
|
|
if err := os.WriteFile(filepath.Join(svc.backupsDir(), "notes.txt"), []byte("x"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
archives, err := svc.List()
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(archives) != 3 {
|
|
t.Fatalf("应只列出 3 份备份,实际 %d", len(archives))
|
|
}
|
|
if archives[0].Name != "fonu-backup-20260925-100000-3.tar.gz" {
|
|
t.Fatalf("应按时间倒序,实际首条 %s", archives[0].Name)
|
|
}
|
|
|
|
removed, err := svc.Prune(2)
|
|
if err != nil {
|
|
t.Fatalf("prune: %v", err)
|
|
}
|
|
if removed != 1 {
|
|
t.Fatalf("应删除 1 份,实际 %d", removed)
|
|
}
|
|
if archives, _ = svc.List(); len(archives) != 2 {
|
|
t.Fatalf("剪枝后应剩 2 份,实际 %d", len(archives))
|
|
}
|
|
|
|
// 名称校验:不能让备份名把读写带出备份目录
|
|
for _, name := range []string{"../fonu.db", "notes.txt", "", "fonu-backup-x.tar.gz"} {
|
|
if _, err := svc.Path(name); err == nil {
|
|
t.Fatalf("非法备份名应拒绝:%q", name)
|
|
}
|
|
}
|
|
if err := svc.Remove("fonu-backup-20260920-100000-9.tar.gz"); err == nil {
|
|
t.Fatal("删除不存在的备份应报错")
|
|
}
|
|
if _, err := svc.Prune(0); err != nil {
|
|
t.Fatalf("keep<=0 应直接返回:%v", err)
|
|
}
|
|
}
|