Initial commit: haul — desktop SFTP client
A two-pane local/remote file browser over SSH, split out of dial so file transfer lives in its own tool. The two share ~/.ssh/config as their base. - internal/sshconf: ~/.ssh/config parsing, for the saved-host list. - internal/sshx: dialling and auth — agent, keys, password, host-key verification, ProxyJump. - internal/vfs: the FS interface both sides are driven through, implemented over the local disk and over SFTP. - internal/xfer: recursive copy engine with progress reporting, cancellation, conflict policy, and resume of partial transfers. - internal/ui: the Fyne GUI — connect window (saved hosts plus quick connect) and the two-pane browser session. Build with `make build`; the Makefile locates the Go toolchain at ~/.local/go, which is deliberately kept off PATH. Cross-compiling needs a per-target C toolchain because Fyne is cgo, so `make dist` builds the native binary only — the alternatives are documented in the Makefile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
package sshx
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
// TestLiveConnect exercises the real auth + SFTP path against a live host.
|
||||
// Gated on DIAL_LIVE_HOST so the normal test suite never needs a server.
|
||||
// DIAL_LIVE_HOST=192.168.192.201 go test ./internal/sshx -run TestLiveConnect -v
|
||||
func TestLiveConnect(t *testing.T) {
|
||||
alias := os.Getenv("DIAL_LIVE_HOST")
|
||||
if alias == "" {
|
||||
t.Skip("set DIAL_LIVE_HOST to run the live connection test")
|
||||
}
|
||||
opts := Options{Logf: func(format string, args ...any) { t.Logf(format, args...) }}
|
||||
client, err := Connect(alias, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("Connect(%q): %v", alias, err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
sf, err := sftp.NewClient(client)
|
||||
if err != nil {
|
||||
t.Fatalf("sftp.NewClient: %v", err)
|
||||
}
|
||||
defer sf.Close()
|
||||
|
||||
entries, err := sf.ReadDir(".")
|
||||
if err != nil {
|
||||
t.Fatalf("ReadDir(.): %v", err)
|
||||
}
|
||||
for _, e := range entries {
|
||||
t.Logf(" %s\t%d bytes", e.Name(), e.Size())
|
||||
}
|
||||
t.Logf("SFTP OK: %d entries in remote home", len(entries))
|
||||
|
||||
// Round-trip: write a file, read it back, verify contents, clean up.
|
||||
const remote = "dial-live-test.txt"
|
||||
want := []byte("dial round-trip @ live test\n")
|
||||
w, err := sf.Create(remote)
|
||||
if err != nil {
|
||||
t.Fatalf("Create(%s): %v", remote, err)
|
||||
}
|
||||
if _, err := w.Write(want); err != nil {
|
||||
t.Fatalf("Write: %v", err)
|
||||
}
|
||||
w.Close()
|
||||
|
||||
r, err := sf.Open(remote)
|
||||
if err != nil {
|
||||
t.Fatalf("Open(%s): %v", remote, err)
|
||||
}
|
||||
got := make([]byte, len(want))
|
||||
if _, err := r.Read(got); err != nil {
|
||||
t.Fatalf("Read: %v", err)
|
||||
}
|
||||
r.Close()
|
||||
if string(got) != string(want) {
|
||||
t.Fatalf("round-trip mismatch: got %q want %q", got, want)
|
||||
}
|
||||
if err := sf.Remove(remote); err != nil {
|
||||
t.Fatalf("Remove(%s): %v", remote, err)
|
||||
}
|
||||
t.Logf("round-trip OK: wrote %d bytes, read back identical, removed", len(want))
|
||||
}
|
||||
Reference in New Issue
Block a user