Files
move-data-nas/internal/syncengine/rsync_runner_test.go
T
darroyo 5b28b46e6b fix: simplify RunRemote to not re-prefix source or dest
RunRemote was adding user@host: prefixes on top of the paths already
pre-prefixed by engine.go in the remote-to-remote branch, causing
"both remote" rsync errors. The source path was also incorrectly
prefixed with srcUserHost:, making rsync reject the command entirely.

The fix: buildArgs already orders args correctly for rsync (source
then dest), so just use args[-2] and args[-1] as-is without any
additional prefixing.
2026-07-09 21:58:02 -04:00

238 lines
6.3 KiB
Go

package syncengine
import (
"strings"
"testing"
)
type remoteCmdTest struct {
srcHost string
srcUser string
srcPath string
dstHost string
dstUser string
dstPath string
direction string
}
func (tc remoteCmdTest) build() (srcArg, dstArg string) {
runner := NewRsyncRunner("/tmp/ssh", "")
args := runner.buildArgs(&SyncPairConfig{
Source: tc.srcPath,
Dest: tc.dstPath,
Direction: tc.direction,
})
sourcePath := args[len(args)-2]
destPath := args[len(args)-1]
return sourcePath, destPath
}
func TestBuildArgs_Push(t *testing.T) {
runner := NewRsyncRunner("/tmp/ssh", "")
pair := &SyncPairConfig{
Source: "/local/src",
Dest: "admin@10.5.0.144:/remote/dst",
Direction: "push",
RsyncFlags: "-aP",
ExcludePatterns: []string{},
}
args := runner.buildArgs(pair)
if args[0] != "-aP" {
t.Errorf("first flag = %q, want %q", args[0], "-aP")
}
if args[len(args)-2] != "/local/src" {
t.Errorf("source = %q, want %q", args[len(args)-2], "/local/src")
}
if args[len(args)-1] != "admin@10.5.0.144:/remote/dst" {
t.Errorf("dest = %q, want %q", args[len(args)-1], "admin@10.5.0.144:/remote/dst")
}
}
func TestBuildArgs_Pull(t *testing.T) {
runner := NewRsyncRunner("/tmp/ssh", "")
pair := &SyncPairConfig{
Source: "admin@10.5.0.144:/remote/src",
Dest: "/local/dst",
Direction: "pull",
RsyncFlags: "-aP",
ExcludePatterns: []string{},
}
args := runner.buildArgs(pair)
if args[len(args)-2] != "/local/dst" {
t.Errorf("pull: second-to-last (dest) = %q, want %q", args[len(args)-2], "/local/dst")
}
if args[len(args)-1] != "admin@10.5.0.144:/remote/src" {
t.Errorf("pull: last (source) = %q, want %q", args[len(args)-1], "admin@10.5.0.144:/remote/src")
}
}
func TestBuildArgs_Mirror(t *testing.T) {
runner := NewRsyncRunner("/tmp/ssh", "")
pair := &SyncPairConfig{
Source: "/local/src",
Dest: "admin@10.5.0.144:/remote/dst",
Direction: "mirror",
RsyncFlags: "-aP",
ExcludePatterns: []string{},
}
args := runner.buildArgs(pair)
foundDelete := false
for _, a := range args {
if a == "--delete" {
foundDelete = true
break
}
}
if !foundDelete {
t.Errorf("mirror args = %v, want --delete present", args)
}
}
func TestBuildArgs_MultiTokenFlags(t *testing.T) {
runner := NewRsyncRunner("/tmp/ssh", "")
pair := &SyncPairConfig{
Source: "/local/src",
Dest: "admin@10.5.0.144:/remote/dst",
Direction: "push",
RsyncFlags: "-aP --partial",
ExcludePatterns: []string{},
}
args := runner.buildArgs(pair)
if args[0] != "-aP" {
t.Errorf("first flag = %q, want %q", args[0], "-aP")
}
if args[1] != "--partial" {
t.Errorf("second flag = %q, want %q", args[1], "--partial")
}
}
func TestBuildArgs_ExcludePatterns(t *testing.T) {
runner := NewRsyncRunner("/tmp/ssh", "")
pair := &SyncPairConfig{
Source: "/local/src",
Dest: "admin@10.5.0.144:/remote/dst",
Direction: "push",
RsyncFlags: "-aP",
ExcludePatterns: []string{"*.tmp", ".DS_Store"},
}
args := runner.buildArgs(pair)
var excludes []string
for _, a := range args {
if strings.HasPrefix(a, "--exclude=") {
excludes = append(excludes, a)
}
}
if len(excludes) != 2 {
t.Errorf("excludes = %v, want 2 exclude entries", excludes)
}
if excludes[0] != "--exclude=*.tmp" {
t.Errorf("exclude[0] = %q, want %q", excludes[0], "--exclude=*.tmp")
}
}
func TestRunRemote_PushDestNoDoublePrefix(t *testing.T) {
tc := remoteCmdTest{
srcHost: "10.5.1.10",
srcUser: "root",
srcPath: "/share/homes/admin/media",
dstHost: "10.5.0.144",
dstUser: "admin",
dstPath: "admin@10.5.0.144:/share/media/peliculas",
direction: "push",
}
_, dstArg := tc.build()
if countOccurrences(dstArg, "@") > 1 {
t.Errorf("push dest %q has double SSH spec", dstArg)
}
if dstArg != "admin@10.5.0.144:/share/media/peliculas" {
t.Errorf("push dest = %q, want 'admin@10.5.0.144:/share/media/peliculas'", dstArg)
}
}
func TestRunRemote_PushSrcAndDestStayAsIs(t *testing.T) {
tc := remoteCmdTest{
srcHost: "10.5.1.10",
srcUser: "root",
srcPath: "/share/homes/admin/media",
dstHost: "10.5.0.144",
dstUser: "admin",
dstPath: "/share/media/peliculas",
direction: "push",
}
srcArg, dstArg := tc.build()
if srcArg != "/share/homes/admin/media" {
t.Errorf("push src = %q, want raw path '/share/homes/admin/media' (no user@host: prefix added)", srcArg)
}
if dstArg != "/share/media/peliculas" {
t.Errorf("push dst = %q, want raw path '/share/media/peliculas'", dstArg)
}
}
func TestRunRemote_PullSrcAndDestStayAsIs(t *testing.T) {
tc := remoteCmdTest{
srcHost: "10.5.0.144",
srcUser: "admin",
srcPath: "admin@10.5.0.144:/share/media/peliculas",
dstHost: "10.5.1.10",
dstUser: "root",
dstPath: "/share/data",
direction: "pull",
}
srcArg, dstArg := tc.build()
if countOccurrences(srcArg, "@") > 1 {
t.Errorf("pull src %q has double SSH spec", srcArg)
}
if srcArg != "/share/data" {
t.Errorf("pull src (rsync dest) = %q, want raw '/share/data'", srcArg)
}
if !strings.HasPrefix(dstArg, "admin@10.5.0.144:/") {
t.Errorf("pull dst (rsync src) = %q, want 'admin@10.5.0.144:/...' prefix", dstArg)
}
}
func countOccurrences(s, substr string) int {
return strings.Count(s, substr)
}
func TestRun_FlagsPreservedWithPrivKey(t *testing.T) {
runner := NewRsyncRunner("/tmp/ssh", "/tmp/ssh/id_ed25519")
pair := &SyncPairConfig{
Source: "/local/src",
Dest: "admin@10.5.0.144:/remote/dst",
Direction: "push",
RsyncFlags: "-aP --partial",
ExcludePatterns: []string{},
}
cmd := runner.buildRsyncCmd(pair)
if cmd.Args[0] != "rsync" {
t.Errorf("cmd.Args[0] = %q, want 'rsync'", cmd.Args[0])
}
if cmd.Args[1] != "-e" {
t.Errorf("cmd.Args[1] = %q, want '-e' (the -e flag for ssh)", cmd.Args[1])
}
if !strings.Contains(cmd.Args[2], "ssh -i") {
t.Errorf("cmd.Args[2] = %q, want ssh -i ...", cmd.Args[2])
}
hasAP := false
hasPartial := false
for i, a := range cmd.Args {
if a == "-aP" && i > 2 {
hasAP = true
}
if a == "--partial" && i > 2 {
hasPartial = true
}
}
if !hasAP {
t.Errorf("cmd.Args = %v, want -aP flag preserved (not dropped)", cmd.Args)
}
if !hasPartial {
t.Errorf("cmd.Args = %v, want --partial flag present", cmd.Args)
}
}