diff options
| author | Paul Buetow <paul@buetow.org> | 2026-07-04 22:29:49 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-07-04 22:29:49 +0300 |
| commit | 49e4dbf6ee07b13790231091ef8f73f025841a19 (patch) | |
| tree | 1be5171ca564c1f651886a59391caedfee7253b6 /internal/file/file_test.go | |
| parent | 2381c8712ebafa8060f0d2feacadd85bef280eb0 (diff) | |
add more file types
Diffstat (limited to 'internal/file/file_test.go')
| -rw-r--r-- | internal/file/file_test.go | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/internal/file/file_test.go b/internal/file/file_test.go index a9397ba..d9e09ac 100644 --- a/internal/file/file_test.go +++ b/internal/file/file_test.go @@ -171,3 +171,271 @@ func TestHaveTemplateFile(t *testing.T) { t.Errorf("expected content to contain Param %q, got %q", expectedParam, string(got)) } } + +func TestHaveDirectoryCreate(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "sub", "nested") + + if err := Have(path, IsDirectory()); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + info, err := os.Stat(path) + if err != nil { + t.Fatalf("stat: %v", err) + } + if !info.IsDir() { + t.Errorf("expected a directory at %s", path) + } + if info.Mode().Perm() != 0o750 { + t.Errorf("expected default dir mode 0750, got %v", info.Mode().Perm()) + } +} + +func TestHaveDirectoryIdempotentWithMode(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "d") + + // Call the resource logic directly to exercise idempotency without the + // one-per-process resource registry rejecting a duplicate registration. + f1 := &File{path: path, mode: 0o755} + if err := f1.haveDirectory(); err != nil { + t.Fatalf("first apply: %v", err) + } + f2 := &File{path: path, mode: 0o700} + if err := f2.haveDirectory(); err != nil { + t.Fatalf("second apply: %v", err) + } + + info, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if info.Mode().Perm() != 0o700 { + t.Errorf("expected mode 0700 enforced, got %v", info.Mode().Perm()) + } +} + +func TestHaveDirectoryFailsWhenFileExists(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "afile") + if err := os.WriteFile(path, []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + + if err := Have(path, IsDirectory()); err == nil { + t.Error("expected error when a regular file is in the way of a directory") + } +} + +func TestHaveSymlinkCreateAndIdempotent(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "target.txt") + link := filepath.Join(dir, "link.txt") + if err := os.WriteFile(target, []byte("t"), 0o644); err != nil { + t.Fatal(err) + } + + if err := Have(link, IsSymlink(target)); err != nil { + t.Fatalf("create: %v", err) + } + got, err := os.Readlink(link) + if err != nil { + t.Fatalf("readlink: %v", err) + } + if got != target { + t.Errorf("expected link -> %s, got %s", target, got) + } + + // Re-applying the same link should be a no-op (tested directly to avoid the + // one-per-process resource registry rejecting a duplicate registration). + f := &File{path: link, symlink: true, symlinkTarget: target} + if err := f.haveSymlink(); err != nil { + t.Fatalf("idempotent apply: %v", err) + } +} + +func TestHaveSymlinkRepoints(t *testing.T) { + dir := t.TempDir() + old := filepath.Join(dir, "old.txt") + newT := filepath.Join(dir, "new.txt") + link := filepath.Join(dir, "link") + for _, p := range []string{old, newT} { + if err := os.WriteFile(p, []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + } + if err := os.Symlink(old, link); err != nil { + t.Fatal(err) + } + + if err := Have(link, IsSymlink(newT)); err != nil { + t.Fatalf("repoint: %v", err) + } + got, err := os.Readlink(link) + if err != nil { + t.Fatal(err) + } + if got != newT { + t.Errorf("expected repoint to %s, got %s", newT, got) + } +} + +func TestHaveSymlinkMovesRealFileAside(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "target.txt") + link := filepath.Join(dir, "real") + if err := os.WriteFile(target, []byte("t"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(link, []byte("original"), 0o644); err != nil { + t.Fatal(err) + } + + if err := Have(link, IsSymlink(target)); err != nil { + t.Fatalf("symlink over real file: %v", err) + } + + got, err := os.Readlink(link) + if err != nil { + t.Fatalf("expected %s to be a symlink: %v", link, err) + } + if got != target { + t.Errorf("expected link -> %s, got %s", target, got) + } + if data, err := os.ReadFile(link + ".old"); err != nil || string(data) != "original" { + t.Errorf("expected original content preserved in %s.old, got %q err %v", link, string(data), err) + } +} + +func TestHaveAbsent(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "gone.txt") + if err := os.WriteFile(path, []byte("bye"), 0o644); err != nil { + t.Fatal(err) + } + + if err := Have(path, IsAbsent()); err != nil { + t.Fatalf("absent: %v", err) + } + if _, err := os.Stat(path); !os.IsNotExist(err) { + t.Errorf("expected %s to be removed", path) + } + + // Idempotent: removing a missing file is not an error (direct call to avoid + // duplicate registration in the one-per-process registry). + f := &File{path: path} + if err := f.haveAbsent(); err != nil { + t.Fatalf("absent on missing file: %v", err) + } +} + +func TestHaveHardlinkCreateAndIdempotent(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "target.txt") + link := filepath.Join(dir, "link.txt") + if err := os.WriteFile(target, []byte("payload"), 0o644); err != nil { + t.Fatal(err) + } + + if err := Have(link, Hardlink(target)); err != nil { + t.Fatalf("create: %v", err) + } + + ti, err := os.Stat(target) + if err != nil { + t.Fatal(err) + } + li, err := os.Stat(link) + if err != nil { + t.Fatal(err) + } + if !sameInode(ti, li) { + t.Errorf("expected %s and %s to share an inode", link, target) + } + + // Re-applying the same link should be a no-op (direct call to avoid the + // one-per-process resource registry rejecting a duplicate registration). + f := &File{path: link, hardlink: true, hardlinkTarget: target} + if err := f.haveHardlink(); err != nil { + t.Fatalf("idempotent apply: %v", err) + } +} + +func TestHaveHardlinkMovesRealFileAside(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "target.txt") + link := filepath.Join(dir, "real") + if err := os.WriteFile(target, []byte("payload"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(link, []byte("original"), 0o644); err != nil { + t.Fatal(err) + } + + if err := Have(link, Hardlink(target)); err != nil { + t.Fatalf("hardlink over real file: %v", err) + } + + ti, err := os.Stat(target) + if err != nil { + t.Fatal(err) + } + li, err := os.Stat(link) + if err != nil { + t.Fatal(err) + } + if !sameInode(ti, li) { + t.Errorf("expected %s to be hardlinked to %s", link, target) + } + if data, err := os.ReadFile(link + ".old"); err != nil || string(data) != "original" { + t.Errorf("expected original content preserved in %s.old, got %q err %v", link, string(data), err) + } +} + +func TestHaveHardlinkMissingTarget(t *testing.T) { + dir := t.TempDir() + link := filepath.Join(dir, "link") + if err := Have(link, Hardlink(filepath.Join(dir, "nope"))); err == nil { + t.Error("expected error when hardlink target does not exist") + } +} + +func TestHaveAbsentNonEmptyDirWithoutPruneFails(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "d") + if err := os.MkdirAll(filepath.Join(target, "sub"), 0o755); err != nil { + t.Fatal(err) + } + + if err := Have(target, IsAbsent()); err == nil { + t.Error("expected error removing a non-empty directory without PruneDirectory()") + } + if _, err := os.Stat(target); err != nil { + t.Errorf("expected %s to still exist, got %v", target, err) + } +} + +func TestHaveAbsentPruneDirectoryRecursive(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "d") + if err := os.MkdirAll(filepath.Join(target, "sub", "deep"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(target, "sub", "f.txt"), []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + + if err := Have(target, IsAbsent(), PruneDirectory()); err != nil { + t.Fatalf("prune remove: %v", err) + } + if _, err := os.Stat(target); !os.IsNotExist(err) { + t.Errorf("expected %s to be removed recursively", target) + } + + // Idempotent: removing a missing tree is not an error. + f := &File{path: target, absent: true, pruneDirectory: true} + if err := f.haveAbsent(); err != nil { + t.Fatalf("prune on missing tree: %v", err) + } +} |
