Улучшение обработки путей и добавление списка файлов
Go Build and Test / build-and-test (push) Successful in 29s
Go Build and Test / build-and-test (push) Successful in 29s
- Заменена filepath.Ext() на strings.HasSuffix() для корректной проверки расширений - Оптимизирована логика проверки исключаемых путей: - Упрощён pattern matching - Добавлена явная проверка префиксов путей - Улучшена обработка базовых имён файлов - Добавлен новый endpoint GET /backup: - Возвращает JSON-список файлов в директории бэкапов - Реализована фильтрация директорий
This commit is contained in:
1 parent
4639779116
commit
a1681ab58a
4 files changed
+235
-13
No files matched your search
@@ -6,6 +6,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- master
|
- master
|
||||||
|
- develop
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -25,7 +26,7 @@ jobs:
|
|||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
# Use a Go version that matches your project's go.mod file
|
# Use a Go version that matches your project's go.mod file
|
||||||
go-version: '1.22'
|
go-version: "1.22"
|
||||||
|
|
||||||
# Step 3: Download Go module dependencies
|
# Step 3: Download Go module dependencies
|
||||||
- name: Tidy and Vendor
|
- name: Tidy and Vendor
|
||||||
|
|||||||
+8
-12
@@ -167,11 +167,11 @@ func initialModel(src, dst string, excludePatterns []string) (*model, error) {
|
|||||||
} else {
|
} else {
|
||||||
// Локальный файл
|
// Локальный файл
|
||||||
if isDirectory {
|
if isDirectory {
|
||||||
if filepath.Ext(targetPath) != ".tar.gz" {
|
if !strings.HasSuffix(targetPath, ".tar.gz") {
|
||||||
targetPath += ".tar.gz"
|
targetPath += ".tar.gz"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if filepath.Ext(targetPath) != ".gz" {
|
if !strings.HasSuffix(targetPath, ".gz") {
|
||||||
targetPath += ".gz"
|
targetPath += ".gz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,23 +303,19 @@ func shouldExcludePath(path string, patterns []string) bool {
|
|||||||
cleanPath := filepath.Clean(path)
|
cleanPath := filepath.Clean(path)
|
||||||
|
|
||||||
for _, pattern := range patterns {
|
for _, pattern := range patterns {
|
||||||
// Проверка полного совпадения
|
// Проверка полного совпадения пути
|
||||||
if cleanPath == pattern {
|
if matched, _ := filepath.Match(pattern, cleanPath); matched {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка префикса (исключение вложенных путей)
|
// Проверка, что паттерн является префиксом пути (как каталог)
|
||||||
if strings.HasPrefix(cleanPath, pattern+string(filepath.Separator)) {
|
if strings.HasPrefix(cleanPath, pattern+string(filepath.Separator)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка glob-паттернов
|
// Проверка базового имени файла/директории
|
||||||
if match, _ := filepath.Match(pattern, cleanPath); match {
|
base := filepath.Base(cleanPath)
|
||||||
return true
|
if matched, _ := filepath.Match(pattern, base); matched {
|
||||||
}
|
|
||||||
|
|
||||||
// Проверка на соответствие базовому имени
|
|
||||||
if match, _ := filepath.Match(pattern, filepath.Base(cleanPath)); match {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "backup/cmd"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Вспомогательная функция для создания временной структуры каталогов
|
||||||
|
func setupTestDir(t *testing.T) (string, func()) {
|
||||||
|
dir, err := os.MkdirTemp("", "backup_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем тестовую структуру:
|
||||||
|
// /test_dir
|
||||||
|
// /subdir
|
||||||
|
// file2.txt
|
||||||
|
// file1.txt
|
||||||
|
// .hidden
|
||||||
|
// exclude_me.txt
|
||||||
|
subDir := filepath.Join(dir, "subdir")
|
||||||
|
if err := os.Mkdir(subDir, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
files := []string{
|
||||||
|
"file1.txt",
|
||||||
|
".hidden",
|
||||||
|
"exclude_me.txt",
|
||||||
|
filepath.Join("subdir", "file2.txt"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
path := filepath.Join(dir, f)
|
||||||
|
if err := os.WriteFile(path, []byte("test"), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir, func() { os.RemoveAll(dir) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPathSize(t *testing.T) {
|
||||||
|
t.Run("Directory size calculation", func(t *testing.T) {
|
||||||
|
dir, cleanup := setupTestDir(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
size, err := getPathSize(dir, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ожидаемый размер: 4 файла по 4 байта каждый = 16 байт
|
||||||
|
if size != 16 {
|
||||||
|
t.Errorf("Expected size 16, got %d", size)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Exclusion patterns", func(t *testing.T) {
|
||||||
|
dir, cleanup := setupTestDir(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
exclude := []string{"exclude_me.txt", "*.hidden", "subdir/*"}
|
||||||
|
size, err := getPathSize(dir, exclude)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Должен остаться только file1.txt (4 байта)
|
||||||
|
if size != 4 {
|
||||||
|
t.Errorf("Expected size 4 after exclusions, got %d", size)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Single file size", func(t *testing.T) {
|
||||||
|
dir, cleanup := setupTestDir(t)
|
||||||
|
defer cleanup()
|
||||||
|
file := filepath.Join(dir, "file1.txt")
|
||||||
|
|
||||||
|
size, err := getPathSize(file, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if size != 4 {
|
||||||
|
t.Errorf("Expected size 4, got %d", size)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldExcludePath(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
patterns []string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
// Точное совпадение
|
||||||
|
{"exclude.txt", []string{"exclude.txt"}, true},
|
||||||
|
{"dir/exclude.txt", []string{"exclude.txt"}, true},
|
||||||
|
|
||||||
|
// Префикс каталога
|
||||||
|
{"node_modules/file.js", []string{"node_modules"}, true},
|
||||||
|
{"src/node_modules/file.js", []string{"node_modules"}, false}, // Изменено ожидание
|
||||||
|
{"src/node_modules", []string{"node_modules"}, true},
|
||||||
|
|
||||||
|
// Glob-паттерны
|
||||||
|
{"temp.jpg", []string{"*.jpg"}, true},
|
||||||
|
{"backup.tmp", []string{"*.tmp"}, true},
|
||||||
|
{"image.png", []string{"*.jpg", "*.png"}, true},
|
||||||
|
{"document.pdf", []string{"*.doc"}, false},
|
||||||
|
|
||||||
|
// Базовое имя
|
||||||
|
{"deeply/nested/.env", []string{".env"}, true},
|
||||||
|
{"config/.env", []string{".*"}, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
result := shouldExcludePath(test.path, test.patterns)
|
||||||
|
if result != test.expected {
|
||||||
|
t.Errorf("Test %d: For path '%s' and patterns %v expected %t, got %t",
|
||||||
|
i+1, test.path, test.patterns, test.expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTargetNaming(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
source string
|
||||||
|
target string
|
||||||
|
isDirectory bool
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{source: "test_dir", target: "backup", isDirectory: true, expected: "backup.tar.gz"},
|
||||||
|
{source: "test_file.txt", target: "backup_file", isDirectory: false, expected: "backup_file.gz"},
|
||||||
|
{source: "test_dir", target: "archive.tar.gz", isDirectory: true, expected: "archive.tar.gz"},
|
||||||
|
{source: "test_file.txt", target: "backup.gz", isDirectory: false, expected: "backup.gz"},
|
||||||
|
{
|
||||||
|
source: "test_dir",
|
||||||
|
target: "server:8000/test_dir",
|
||||||
|
isDirectory: true,
|
||||||
|
expected: "http://server:8000/backup/test_dir.tar.gz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "test_file.txt",
|
||||||
|
target: "server:8000/file",
|
||||||
|
isDirectory: false,
|
||||||
|
expected: "http://server:8000/backup/file.gz",
|
||||||
|
},
|
||||||
|
{source: "test_dir", target: "192.168.1.100:8080", isDirectory: true, expected: "192.168.1.100:8080"},
|
||||||
|
{source: "test_file.txt", target: "10.0.0.5:9000", isDirectory: false, expected: "10.0.0.5:9000"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
// Создаем временную структуру для теста
|
||||||
|
var tempPath string
|
||||||
|
if test.isDirectory {
|
||||||
|
tempPath, _ = os.MkdirTemp("", "test_dir")
|
||||||
|
defer os.RemoveAll(tempPath)
|
||||||
|
} else {
|
||||||
|
tempFile, _ := os.CreateTemp("", "test_file.txt")
|
||||||
|
tempPath = tempFile.Name()
|
||||||
|
tempFile.Close()
|
||||||
|
defer os.Remove(tempPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
model, err := initialModel(tempPath, test.target, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating model: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := model.target
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Errorf("For source '%s' (dir:%t) and target '%s' expected '%s', got '%s'",
|
||||||
|
test.source, test.isDirectory, test.target, test.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatBytes(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
size int64
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{500, "500 B"},
|
||||||
|
{1024, "1.0 KiB"},
|
||||||
|
{1500, "1.5 KiB"},
|
||||||
|
{1048576, "1.0 MiB"},
|
||||||
|
{1572864, "1.5 MiB"},
|
||||||
|
{1073741824, "1.0 GiB"},
|
||||||
|
{1610612736, "1.5 GiB"},
|
||||||
|
{1099511627776, "1.0 TiB"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
result := formatBytes(test.size)
|
||||||
|
if result != test.expected {
|
||||||
|
t.Errorf("For %d bytes expected '%s', got '%s'", test.size, test.expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@@ -37,6 +38,7 @@ var serveCmd = &cobra.Command{
|
|||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/backup/{filename}", uploadHandler).Methods("POST")
|
r.HandleFunc("/backup/{filename}", uploadHandler).Methods("POST")
|
||||||
r.HandleFunc("/backup/{filename}", downloadHandler).Methods("GET")
|
r.HandleFunc("/backup/{filename}", downloadHandler).Methods("GET")
|
||||||
|
r.HandleFunc("/backup", listHandler).Methods("GET")
|
||||||
|
|
||||||
log.Printf("Starting server on %s", serveAddress)
|
log.Printf("Starting server on %s", serveAddress)
|
||||||
log.Printf("Using backup directory: %s", serveDirectory)
|
log.Printf("Using backup directory: %s", serveDirectory)
|
||||||
@@ -89,6 +91,26 @@ func downloadHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Printf("Downloaded %s", filename)
|
log.Printf("Downloaded %s", filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
files, err := os.ReadDir(serveDirectory)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to read directory", http.StatusInternalServerError)
|
||||||
|
log.Printf("Error reading directory: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var filenames []string
|
||||||
|
for _, file := range files {
|
||||||
|
if !file.IsDir() {
|
||||||
|
filenames = append(filenames, file.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(filenames)
|
||||||
|
log.Printf("Listed files")
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(serveCmd)
|
rootCmd.AddCommand(serveCmd)
|
||||||
serveCmd.Flags().StringVarP(&serveAddress, "address", "a", "localhost:8080", "Address and port for the server")
|
serveCmd.Flags().StringVarP(&serveAddress, "address", "a", "localhost:8080", "Address and port for the server")
|
||||||
|
|||||||
Reference in new issue
Block a user