Fix a bug in the SFTP backend that would cause an error when overwriting existing files

This commit is contained in:
2026-06-13 13:57:50 -05:00
parent fc33bbca54
commit c84d8868d1
2 changed files with 155 additions and 1 deletions

View File

@@ -0,0 +1,125 @@
package ssh
import (
"errors"
"os"
"testing"
"github.com/pkg/sftp"
)
func TestRenamePromotedFileUsesPlainRenameWithoutOverwrite(t *testing.T) {
client := &recordingRenamer{}
if err := renamePromotedFile(client, "temp", "index.html", false); err != nil {
t.Fatalf("renamePromotedFile() error = %v", err)
}
if got, want := client.calls, []string{"rename temp index.html"}; !equalStrings(got, want) {
t.Fatalf("calls = %q, want %q", got, want)
}
}
func TestRenamePromotedFileUsesPosixRenameForOverwrite(t *testing.T) {
client := &recordingRenamer{}
if err := renamePromotedFile(client, "temp", "index.html", true); err != nil {
t.Fatalf("renamePromotedFile() error = %v", err)
}
if got, want := client.calls, []string{"posix temp index.html"}; !equalStrings(got, want) {
t.Fatalf("calls = %q, want %q", got, want)
}
}
func TestRenamePromotedFileFallsBackWhenReplaceRenameUnsupported(t *testing.T) {
for _, err := range []error{
sftp.ErrSSHFxOpUnsupported,
sftp.ErrSSHFxFailure,
&sftp.StatusError{Code: uint32(sftp.ErrSSHFxOpUnsupported)},
&sftp.StatusError{Code: uint32(sftp.ErrSSHFxFailure)},
} {
t.Run(err.Error(), func(t *testing.T) {
client := &recordingRenamer{posixErr: err}
if err := renamePromotedFile(client, "temp", "index.html", true); err != nil {
t.Fatalf("renamePromotedFile() error = %v", err)
}
want := []string{"posix temp index.html", "remove index.html", "rename temp index.html"}
if got := client.calls; !equalStrings(got, want) {
t.Fatalf("calls = %q, want %q", got, want)
}
})
}
}
func TestRenamePromotedFileIgnoresMissingTargetDuringFallback(t *testing.T) {
client := &recordingRenamer{
posixErr: sftp.ErrSSHFxOpUnsupported,
removeErr: &os.PathError{
Op: "remove",
Path: "index.html",
Err: os.ErrNotExist,
},
}
if err := renamePromotedFile(client, "temp", "index.html", true); err != nil {
t.Fatalf("renamePromotedFile() error = %v", err)
}
want := []string{"posix temp index.html", "remove index.html", "rename temp index.html"}
if got := client.calls; !equalStrings(got, want) {
t.Fatalf("calls = %q, want %q", got, want)
}
}
func TestRenamePromotedFileDoesNotFallbackForPermissionError(t *testing.T) {
client := &recordingRenamer{posixErr: sftp.ErrSSHFxPermissionDenied}
if err := renamePromotedFile(client, "temp", "index.html", true); !errors.Is(err, sftp.ErrSSHFxPermissionDenied) {
t.Fatalf("renamePromotedFile() error = %v, want permission denied", err)
}
if got, want := client.calls, []string{"posix temp index.html"}; !equalStrings(got, want) {
t.Fatalf("calls = %q, want %q", got, want)
}
}
func TestRenamePromotedFileReturnsRemoveFallbackError(t *testing.T) {
client := &recordingRenamer{
posixErr: sftp.ErrSSHFxOpUnsupported,
removeErr: sftp.ErrSSHFxPermissionDenied,
}
if err := renamePromotedFile(client, "temp", "index.html", true); !errors.Is(err, sftp.ErrSSHFxPermissionDenied) {
t.Fatalf("renamePromotedFile() error = %v, want permission denied", err)
}
want := []string{"posix temp index.html", "remove index.html"}
if got := client.calls; !equalStrings(got, want) {
t.Fatalf("calls = %q, want %q", got, want)
}
}
type recordingRenamer struct {
calls []string
posixErr error
renameErr error
removeErr error
}
func (r *recordingRenamer) PosixRename(oldname, newname string) error {
r.calls = append(r.calls, "posix "+oldname+" "+newname)
return r.posixErr
}
func (r *recordingRenamer) Rename(oldname, newname string) error {
r.calls = append(r.calls, "rename "+oldname+" "+newname)
return r.renameErr
}
func (r *recordingRenamer) Remove(path string) error {
r.calls = append(r.calls, "remove "+path)
return r.removeErr
}
func equalStrings(a, b []string) bool {
if len(a) != len(b) {
return false
}
for index := range a {
if a[index] != b[index] {
return false
}
}
return true
}