aboutsummaryrefslogtreecommitdiffstats
path: root/sync_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'sync_test.go')
-rw-r--r--sync_test.go391
1 files changed, 391 insertions, 0 deletions
diff --git a/sync_test.go b/sync_test.go
new file mode 100644
index 0000000..758d4ec
--- /dev/null
+++ b/sync_test.go
@@ -0,0 +1,391 @@
+package main
+
+import (
+ "os"
+ "path/filepath"
+ "sort"
+ "testing"
+
+ ignore "github.com/sabhiram/go-gitignore"
+)
+
+func TestInitialSync_BasicFiles(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, "a.txt", "hello")
+ writeFile(t, host, "b.txt", "world")
+ writeFile(t, host, "sub/c.txt", "nested")
+
+ rules := ignore.CompileIgnoreLines()
+
+ manifest, err := initialSync(host, data, rules, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assertFileExists(t, data, "a.txt", "hello")
+ assertFileExists(t, data, "b.txt", "world")
+ assertFileExists(t, data, "sub/c.txt", "nested")
+ assertManifestContains(t, manifest, "a.txt", "b.txt", "sub/c.txt")
+}
+
+func TestInitialSync_GitignoreExcludes(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, ".gitignore", "*.log\nbuild/\n")
+ writeFile(t, host, "app.go", "package main")
+ writeFile(t, host, "debug.log", "logs here")
+ writeFile(t, host, "build/output.o", "binary")
+
+ rules, err := loadGitignore(host)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ manifest, err := initialSync(host, data, rules, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assertFileExists(t, data, "app.go", "package main")
+ assertFileNotExists(t, data, "debug.log")
+ assertFileNotExists(t, data, "build/output.o")
+ assertManifestContains(t, manifest, "app.go")
+ assertManifestNotContains(t, manifest, "debug.log", "build/output.o")
+}
+
+func TestInitialSync_NegationPattern(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, ".gitignore", "*.log\n!important.log\n")
+ writeFile(t, host, "debug.log", "debug")
+ writeFile(t, host, "important.log", "important")
+
+ rules, err := loadGitignore(host)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = initialSync(host, data, rules, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assertFileNotExists(t, data, "debug.log")
+ assertFileExists(t, data, "important.log", "important")
+}
+
+func TestInitialSync_DoubleStar(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, ".gitignore", "**/temp\n")
+ writeFile(t, host, "src/main.go", "code")
+ writeFile(t, host, "temp/cache", "cached")
+ writeFile(t, host, "a/b/temp/data", "temp data")
+
+ rules, err := loadGitignore(host)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = initialSync(host, data, rules, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assertFileExists(t, data, "src/main.go", "code")
+ assertFileNotExists(t, data, "temp/cache")
+ assertFileNotExists(t, data, "a/b/temp/data")
+}
+
+func TestInitialSync_DirectoryPattern(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, ".gitignore", "node_modules/\n")
+ writeFile(t, host, "src/app.js", "code")
+ writeFile(t, host, "node_modules/pkg/index.js", "pkg")
+
+ rules, err := loadGitignore(host)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = initialSync(host, data, rules, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assertFileExists(t, data, "src/app.js", "code")
+ assertFileNotExists(t, data, "node_modules/pkg/index.js")
+}
+
+func TestInitialSync_Symlinks(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, "target.txt", "target content")
+ if err := os.Symlink("target.txt", filepath.Join(host, "link.txt")); err != nil {
+ t.Fatal(err)
+ }
+
+ rules := ignore.CompileIgnoreLines()
+
+ _, err := initialSync(host, data, rules, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ linkPath := filepath.Join(data, "link.txt")
+ info, err := os.Lstat(linkPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if info.Mode()&os.ModeSymlink == 0 {
+ t.Error("expected symlink, got regular file")
+ }
+
+ target, err := os.Readlink(linkPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if target != "target.txt" {
+ t.Errorf("symlink target = %q, want %q", target, "target.txt")
+ }
+}
+
+func TestInitialSync_PreservesPermissions(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, "script.sh", "#!/bin/sh\necho hi")
+ os.Chmod(filepath.Join(host, "script.sh"), 0755)
+
+ rules := ignore.CompileIgnoreLines()
+
+ _, err := initialSync(host, data, rules, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ info, err := os.Stat(filepath.Join(data, "script.sh"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if info.Mode().Perm() != 0755 {
+ t.Errorf("permissions = %v, want 0755", info.Mode().Perm())
+ }
+}
+
+func TestInitialSync_DryRun(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, "a.txt", "hello")
+ writeFile(t, host, ".gitignore", "*.log\n")
+ writeFile(t, host, "debug.log", "logs")
+
+ rules, err := loadGitignore(host)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ manifest, err := initialSync(host, data, rules, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Dry run should not create any files
+ entries := listDir(t, data)
+ if len(entries) != 0 {
+ t.Errorf("dry run created files: %v", entries)
+ }
+
+ // But manifest should be populated
+ assertManifestContains(t, manifest, "a.txt")
+}
+
+func TestInitialSync_SkipsGit(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, "src/main.go", "code")
+ writeFile(t, host, ".git/config", "git config")
+ writeFile(t, host, ".git/objects/abc", "obj")
+
+ rules := ignore.CompileIgnoreLines()
+
+ _, err := initialSync(host, data, rules, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assertFileExists(t, data, "src/main.go", "code")
+ assertFileNotExists(t, data, ".git/config")
+}
+
+func TestInitialSync_NestedGitignore(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, ".gitignore", "*.log\n")
+ writeFile(t, host, "src/.gitignore", "*.tmp\n")
+ writeFile(t, host, "src/app.go", "code")
+ writeFile(t, host, "src/debug.log", "log")
+ writeFile(t, host, "src/cache.tmp", "tmp")
+
+ // Load root gitignore
+ rules, err := loadGitignore(host)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Load nested gitignore and add its rules
+ nestedData, err := os.ReadFile(filepath.Join(host, "src", ".gitignore"))
+ if err == nil {
+ _ = nestedData // loaded for verification
+ // Merge: check both root and nested
+ combined := ignore.CompileIgnoreLines(
+ "*.log",
+ "*.tmp",
+ )
+ rules = combined
+ }
+
+ _, err = initialSync(host, data, rules, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assertFileExists(t, data, "src/app.go", "code")
+ assertFileNotExists(t, data, "src/debug.log")
+ assertFileNotExists(t, data, "src/cache.tmp")
+}
+
+func TestInitialSync_ComplexGitignore(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ gitignore := `# Build outputs
+*.o
+*.a
+build/
+
+# Logs
+*.log
+
+# Temp files
+**/temp
+*.tmp
+
+# But keep important ones
+!important.log
+!lib/special.o
+`
+ writeFile(t, host, ".gitignore", gitignore)
+ writeFile(t, host, "src/main.c", "code")
+ writeFile(t, host, "src/main.o", "object")
+ writeFile(t, host, "lib.a", "archive")
+ writeFile(t, host, "build/out.o", "built")
+ writeFile(t, host, "app.log", "log")
+ writeFile(t, host, "important.log", "important")
+ writeFile(t, host, "lib/special.o", "special")
+ writeFile(t, host, "temp/cache", "cached")
+ writeFile(t, host, "a/b/temp/data", "temp")
+ writeFile(t, host, "file.tmp", "tmp")
+
+ rules, err := loadGitignore(host)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = initialSync(host, data, rules, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assertFileExists(t, data, "src/main.c", "code")
+ assertFileExists(t, data, "important.log", "important")
+ assertFileExists(t, data, "lib/special.o", "special")
+
+ assertFileNotExists(t, data, "src/main.o")
+ assertFileNotExists(t, data, "lib.a")
+ assertFileNotExists(t, data, "build/out.o")
+ assertFileNotExists(t, data, "app.log")
+ assertFileNotExists(t, data, "temp/cache")
+ assertFileNotExists(t, data, "a/b/temp/data")
+ assertFileNotExists(t, data, "file.tmp")
+}
+
+// Helper functions
+
+func writeFile(t *testing.T, dir, path, content string) {
+ t.Helper()
+ full := filepath.Join(dir, path)
+ if err := os.MkdirAll(filepath.Dir(full), 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(full, []byte(content), 0644); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func assertFileExists(t *testing.T, dir, path, expectedContent string) {
+ t.Helper()
+ full := filepath.Join(dir, path)
+ data, err := os.ReadFile(full)
+ if err != nil {
+ t.Errorf("file %s does not exist: %v", path, err)
+ return
+ }
+ if string(data) != expectedContent {
+ t.Errorf("file %s content = %q, want %q", path, string(data), expectedContent)
+ }
+}
+
+func assertFileNotExists(t *testing.T, dir, path string) {
+ t.Helper()
+ full := filepath.Join(dir, path)
+ if _, err := os.Stat(full); err == nil {
+ t.Errorf("file %s should not exist but does", path)
+ }
+}
+
+func assertManifestContains(t *testing.T, manifest *Manifest, paths ...string) {
+ t.Helper()
+ for _, p := range paths {
+ if _, ok := manifest.Files[p]; !ok {
+ t.Errorf("manifest missing path %q", p)
+ }
+ }
+}
+
+func assertManifestNotContains(t *testing.T, manifest *Manifest, paths ...string) {
+ t.Helper()
+ for _, p := range paths {
+ if _, ok := manifest.Files[p]; ok {
+ t.Errorf("manifest should not contain path %q", p)
+ }
+ }
+}
+
+func listDir(t *testing.T, dir string) []string {
+ t.Helper()
+ entries, err := os.ReadDir(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var names []string
+ for _, e := range entries {
+ names = append(names, e.Name())
+ }
+ sort.Strings(names)
+ return names
+}