feat(ops): проверка бэкапа и уборка файлов без записи
- glchat verify-backup: расшифровка age, integrity_check, foreign_key_check, схема и состав данных, сверка с манифестом; живые данные не трогает - чистка удаляет файлы на диске, у которых не осталось записи в базе (так оставались вложения удалённых серверов), и пустые подкаталоги; работает через os.Root, чтобы символическая ссылка не вывела за каталог файлов - удаление сервера теперь чистит и содержимое файлов, а не только записи - install.sh пишет значения .env с пробелами в кавычках: файл подключается через `set -a && . .env`, иначе имя главного сервера выполнялось как команда - тесты: Go на уборку файлов без записи (свежие не трогаются, пустые каталоги удаляются)
This commit is contained in:
@@ -209,3 +209,49 @@ func TestCleanupKeepsFreshOrphansAndRecentAudit(t *testing.T) {
|
||||
t.Fatalf("свежий файл удалён: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanupRemovesFilesWithoutRecords(t *testing.T) {
|
||||
st, _, dataDir := newRetentionStore(t)
|
||||
ctx := context.Background()
|
||||
filesDir := filepath.Join(dataDir, "files")
|
||||
nested := filepath.Join(filesDir, "ab")
|
||||
if err := os.MkdirAll(nested, 0o700); err != nil {
|
||||
t.Fatalf("prepare files dir: %v", err)
|
||||
}
|
||||
|
||||
// Файл на диске, записи о котором нет: так остаются вложения удалённых
|
||||
// серверов (запись уходит каскадом, содержимое — нет).
|
||||
orphan := filepath.Join(nested, "orphan.bin")
|
||||
if err := os.WriteFile(orphan, []byte("мусор"), 0o600); err != nil {
|
||||
t.Fatalf("write orphan: %v", err)
|
||||
}
|
||||
old := time.Now().Add(-48 * time.Hour)
|
||||
if err := os.Chtimes(orphan, old, old); err != nil {
|
||||
t.Fatalf("touch orphan: %v", err)
|
||||
}
|
||||
|
||||
// Свежий файл без записи не трогаем: он может ещё не получить запись.
|
||||
fresh := filepath.Join(filesDir, "fresh.bin")
|
||||
if err := os.WriteFile(fresh, []byte("новый"), 0o600); err != nil {
|
||||
t.Fatalf("write fresh: %v", err)
|
||||
}
|
||||
|
||||
runner := New(st, Options{DataDir: dataDir, AuditDays: 30, OrphanHours: 24})
|
||||
stats, err := runner.RunOnce(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("cleanup: %v", err)
|
||||
}
|
||||
if stats.Unknown != 1 {
|
||||
t.Fatalf("удалено файлов без записи: %d, ожидался 1", stats.Unknown)
|
||||
}
|
||||
if _, err := os.Stat(orphan); !os.IsNotExist(err) {
|
||||
t.Fatalf("файл без записи остался: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(fresh); err != nil {
|
||||
t.Fatalf("свежий файл удалён: %v", err)
|
||||
}
|
||||
// Пустой подкаталог после уборки удалён.
|
||||
if _, err := os.Stat(nested); !os.IsNotExist(err) {
|
||||
t.Fatalf("пустой каталог остался: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user