//go:build windows package backup import ( "errors" "os" "path/filepath" "testing" "github.com/aritmolab/thothii/tools/tht/internal/safeio" ) func TestReplaceRestoreFileWindowsCreatesAndReplacesOwnerOnlyRegular(t *testing.T) { parent := filepath.Join(t.TempDir(), "private") if err := os.Mkdir(parent, 0o700); err != nil { t.Fatal(err) } if err := safeio.ProtectPrivateDirectory(parent); err != nil { t.Fatal(err) } target := filepath.Join(parent, "auth.yaml") if err := replaceRestoreFile(target, []byte("created"), 0o600); err != nil { t.Fatal(err) } if err := safeio.ValidatePrivateRegular(target); err != nil { t.Fatalf("created restore file is not owner-only: %v", err) } if err := replaceRestoreFile(target, []byte("replacement"), 0o600); err != nil { t.Fatal(err) } if err := safeio.ValidatePrivateRegular(target); err != nil { t.Fatalf("replacement restore file is not owner-only: %v", err) } contents, err := os.ReadFile(target) if err != nil || string(contents) != "replacement" { t.Fatalf("replacement contents = %q, error = %v", contents, err) } } func TestReplaceRestoreFileWindowsPinsParentAndRejectsReparsePath(t *testing.T) { root := t.TempDir() parent := filepath.Join(root, "private") if err := os.Mkdir(parent, 0o700); err != nil { t.Fatal(err) } if err := safeio.ProtectPrivateDirectory(parent); err != nil { t.Fatal(err) } target := filepath.Join(parent, "users.yaml") if err := os.WriteFile(target, []byte("old"), 0o600); err != nil { t.Fatal(err) } if err := safeio.ProtectPrivateRegular(target); err != nil { t.Fatal(err) } renamed := parent + "-renamed" var hookCalled bool restoreHook := safeio.SetPrivateDirectoryTestHookForTest(func(stage string) { if stage != "after-restore-parent-open" { return } hookCalled = true if err := os.Rename(parent, renamed); err == nil { t.Fatal("parent rename succeeded while restore retained its directory handles") } }) defer restoreHook() if err := replaceRestoreFile(target, []byte("new"), 0o600); err != nil { t.Fatal(err) } if !hookCalled { t.Fatal("parent-pinning race hook was not reached") } link := filepath.Join(root, "reparse-parent") if err := os.Symlink(parent, link); err != nil { t.Skipf("Windows host does not permit symlink creation: %v", err) } if err := replaceRestoreFile(filepath.Join(link, "users.yaml"), []byte("unsafe"), 0o600); !errors.Is(err, safeio.ErrUnsafeFile) { t.Fatalf("reparse-parent restore error = %v, want ErrUnsafeFile", err) } }