Add SSH SFTP backend support
This commit is contained in:
118
internal/adapters/ssh/options_test.go
Normal file
118
internal/adapters/ssh/options_test.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/distributor/internal/storage"
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
func TestOptionsNormalizeDefaultsUserPortAndHostKeyPolicy(t *testing.T) {
|
||||
options, err := (Options{
|
||||
Host: "example.com",
|
||||
Root: "/reports",
|
||||
}).normalized()
|
||||
if err != nil {
|
||||
t.Fatalf("normalized() error = %v", err)
|
||||
}
|
||||
if options.User == "" {
|
||||
t.Fatal("normalized user is empty")
|
||||
}
|
||||
if options.Port != 22 {
|
||||
t.Fatalf("port = %d, want 22", options.Port)
|
||||
}
|
||||
if options.HostKeyPolicy != HostKeyPolicyAcceptNew {
|
||||
t.Fatalf("host key policy = %q, want accept-new", options.HostKeyPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionsNormalizeRejectsInvalidFields(t *testing.T) {
|
||||
tests := map[string]Options{
|
||||
"host": {Root: "/reports"},
|
||||
"port": {
|
||||
Host: "example.com",
|
||||
Port: 70000,
|
||||
Root: "/reports",
|
||||
},
|
||||
"path": {
|
||||
Host: "example.com",
|
||||
},
|
||||
"host key policy": {
|
||||
Host: "example.com",
|
||||
Root: "/reports",
|
||||
HostKeyPolicy: "prompt",
|
||||
},
|
||||
}
|
||||
for name, options := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if _, err := options.normalized(); err == nil {
|
||||
t.Fatal("normalized() error = nil, want error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativePathEnforcesLogicalPathRules(t *testing.T) {
|
||||
backend := &Backend{root: "/srv/reports"}
|
||||
tests := map[string]string{
|
||||
"bundle/report.md": "/srv/reports/bundle/report.md",
|
||||
"": "/srv/reports",
|
||||
}
|
||||
for logicalPath, want := range tests {
|
||||
t.Run(logicalPath, func(t *testing.T) {
|
||||
got, err := backend.nativePath(logicalPath, true)
|
||||
if err != nil {
|
||||
t.Fatalf("nativePath() error = %v", err)
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("nativePath() = %q, want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for _, logicalPath := range []string{"/absolute", "../escape", "a/../b", `a\b`} {
|
||||
t.Run("reject "+logicalPath, func(t *testing.T) {
|
||||
_, err := backend.nativePath(logicalPath, true)
|
||||
if err == nil || !storage.IsInvalidPath(err) {
|
||||
t.Fatalf("nativePath() error = %v, want invalid path", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRejectsMissingAuthBeforeDial(t *testing.T) {
|
||||
t.Setenv("SSH_AUTH_SOCK", "")
|
||||
_, err := New(context.Background(), Options{
|
||||
Host: "example.com",
|
||||
User: "reports",
|
||||
Root: "/reports",
|
||||
HostKeyPolicy: HostKeyPolicyOff,
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "no SSH auth methods configured") {
|
||||
t.Fatalf("New() error = %v, want missing auth", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateErrorMapsSFTPStatusCodes(t *testing.T) {
|
||||
backend := &Backend{}
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
want storage.ErrorKind
|
||||
}{
|
||||
{name: "not found", err: sftp.ErrSSHFxNoSuchFile, want: storage.ErrNotFound},
|
||||
{name: "permission", err: sftp.ErrSSHFxPermissionDenied, want: storage.ErrPermission},
|
||||
{name: "unsupported", err: sftp.ErrSSHFxOpUnsupported, want: storage.ErrUnsupported},
|
||||
{name: "temporary", err: sftp.ErrSSHFxConnectionLost, want: storage.ErrTemporary},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := backend.translateError(storage.OpStat, "report.md", tt.err)
|
||||
if !storage.IsKind(err, tt.want) {
|
||||
t.Fatalf("translateError() = %v, want kind %s", err, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user