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) } }) } }