Files
bsncubed 6b52e83bdc 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>
2026-07-29 23:21:21 +10:00

69 lines
1.8 KiB
Go

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