package archive import ( "bytes" "os" "path/filepath" "runtime" "strings" "testing" ) func TestMatcher(t *testing.T) { m := NewMatcher([]string{"/opt/stoat/data/db", "/etc/x-ui/x-ui.db*", "*.swp", "/opt/p/pgdata/"}) cases := map[string]bool{ "/opt/stoat/data/db": true, "/opt/stoat/data/db/WiredTiger": true, "/opt/stoat/data/dbx": false, "/etc/x-ui/x-ui.db": true, "/etc/x-ui/x-ui.db-wal": true, "/etc/x-ui/config.json": false, "/var/scripts/.a.sh.swp": true, "/opt/p/pgdata/base/1": true, "/opt/p/pgdata2": false, } for p, want := range cases { if got := m.Match(p); got != want { t.Errorf("Match(%q) = %v, want %v", p, got, want) } } } func TestRoundtrip(t *testing.T) { src := t.TempDir() must(t, os.MkdirAll(filepath.Join(src, "a", "b"), 0o750)) must(t, os.WriteFile(filepath.Join(src, "a", "b", "f.txt"), []byte("hello"), 0o640)) must(t, os.WriteFile(filepath.Join(src, "a", "skip.swp"), []byte("x"), 0o644)) must(t, os.MkdirAll(filepath.Join(src, "a", "pgdata"), 0o700)) must(t, os.WriteFile(filepath.Join(src, "a", "pgdata", "PG_VERSION"), []byte("17"), 0o600)) symlinks := runtime.GOOS != "windows" if symlinks { must(t, os.Symlink("b/f.txt", filepath.Join(src, "a", "link"))) } var buf bytes.Buffer w, err := NewWriter(&buf) must(t, err) must(t, w.AddBytes("manifest.json", []byte(`{"domain":"x.example"}`), 0o644)) skip := NewMatcher([]string{"*.swp", filepath.ToSlash(filepath.Join(src, "a", "pgdata"))}) must(t, w.AddTree(filepath.Join(src, "a"), func(p string) string { rel, _ := filepath.Rel(src, p) return "cat/files/" + filepath.ToSlash(rel) }, skip)) must(t, w.Close()) if len(w.Warnings) > 0 { t.Fatalf("warnings: %v", w.Warnings) } var m struct{ Domain string } must(t, ReadManifest(bytes.NewReader(buf.Bytes()), &m)) if m.Domain != "x.example" { t.Fatalf("manifest domain = %q", m.Domain) } dst := t.TempDir() e := &Extractor{Route: func(name string) (string, bool) { rest, ok := strings.CutPrefix(name, "cat/files/") if !ok { return "", false } return filepath.Join(dst, filepath.FromSlash(rest)), true }} must(t, e.Extract(bytes.NewReader(buf.Bytes()))) data, err := os.ReadFile(filepath.Join(dst, "a", "b", "f.txt")) must(t, err) if string(data) != "hello" { t.Fatalf("content = %q", data) } if _, err := os.Stat(filepath.Join(dst, "a", "skip.swp")); !os.IsNotExist(err) { t.Fatalf("skip.swp должен быть исключён") } if _, err := os.Stat(filepath.Join(dst, "a", "pgdata")); !os.IsNotExist(err) { t.Fatalf("pgdata должен быть исключён") } if runtime.GOOS != "windows" { fi, err := os.Stat(filepath.Join(dst, "a", "b", "f.txt")) must(t, err) if fi.Mode().Perm() != 0o640 { t.Fatalf("mode = %v", fi.Mode().Perm()) } fi, err = os.Stat(filepath.Join(dst, "a", "b")) must(t, err) if fi.Mode().Perm() != 0o750 { t.Fatalf("dir mode = %v", fi.Mode().Perm()) } } if symlinks { target, err := os.Readlink(filepath.Join(dst, "a", "link")) must(t, err) if target != "b/f.txt" { t.Fatalf("symlink = %q", target) } } // повторная распаковка поверх существующих файлов e2 := &Extractor{Route: e.Route} must(t, e2.Extract(bytes.NewReader(buf.Bytes()))) } func must(t *testing.T, err error) { t.Helper() if err != nil { t.Fatal(err) } }