package application import ( "context" "errors" "strings" "testing" "github.com/mmmy/snapgo/internal/domain" ) type fakeFTPUploader struct { path string data []byte err error } func (f *fakeFTPUploader) Upload(_ context.Context, remoteRelPath string, data []byte) error { f.path = remoteRelPath f.data = append([]byte(nil), data...) return f.err } func TestCaptureAndFTPServiceUploadsAndCopiesFTPPath(t *testing.T) { uploader := &fakeFTPUploader{} clip := &fakeClipboard{} notifier := &fakeNotifier{} svc := &CaptureAndFTPService{ Uploader: uploader, Clipboard: clip, Notifier: notifier, Cfg: domain.FTPConfig{ Protocol: domain.FTPProtocolFTP, PathPrefix: "snapgo/", }, } if err := svc.ExecuteWithBytes(context.Background(), []byte("png")); err != nil { t.Fatalf("upload FTP screenshot: %v", err) } if string(uploader.data) != "png" { t.Fatalf("expected PNG bytes, got %q", string(uploader.data)) } if !strings.HasPrefix(uploader.path, "snapgo/") || !strings.HasSuffix(uploader.path, ".png") { t.Fatalf("unexpected remote path %q", uploader.path) } if clip.text != "/"+uploader.path { t.Fatalf("expected FTP root path copied, got %q", clip.text) } if notifier.success != clip.text { t.Fatalf("expected success notification %q, got %q", clip.text, notifier.success) } } func TestCaptureAndFTPServiceCopiesSFTPHomePath(t *testing.T) { uploader := &fakeFTPUploader{} clip := &fakeClipboard{} svc := &CaptureAndFTPService{ Uploader: uploader, Clipboard: clip, Cfg: domain.FTPConfig{ Protocol: domain.FTPProtocolSFTP, PathPrefix: "images", }, } if err := svc.ExecuteWithBytes(context.Background(), []byte("png")); err != nil { t.Fatalf("upload SFTP screenshot: %v", err) } if clip.text != "~/"+uploader.path { t.Fatalf("expected SFTP home path copied, got %q", clip.text) } } func TestCaptureAndFTPServiceRejectsTraversalBeforeUpload(t *testing.T) { uploader := &fakeFTPUploader{} notifier := &fakeNotifier{} svc := &CaptureAndFTPService{ Uploader: uploader, Notifier: notifier, Cfg: domain.FTPConfig{ Protocol: domain.FTPProtocolSFTP, PathPrefix: "../../outside", }, } err := svc.ExecuteWithBytes(context.Background(), []byte("png")) if err == nil { t.Fatal("expected traversal prefix to fail") } if uploader.path != "" { t.Fatalf("uploader should not run, got path %q", uploader.path) } if notifier.failure == "" { t.Fatal("expected failure notification") } } func TestCaptureAndFTPServiceSurfacesUploadFailure(t *testing.T) { want := errors.New("server unavailable") uploader := &fakeFTPUploader{err: want} clip := &fakeClipboard{} notifier := &fakeNotifier{} svc := &CaptureAndFTPService{ Uploader: uploader, Clipboard: clip, Notifier: notifier, Cfg: domain.FTPConfig{Protocol: domain.FTPProtocolFTP}, } err := svc.ExecuteWithBytes(context.Background(), []byte("png")) if !errors.Is(err, want) { t.Fatalf("expected %v, got %v", want, err) } if clip.text != "" { t.Fatalf("clipboard should remain empty, got %q", clip.text) } if !strings.Contains(notifier.failure, "server unavailable") { t.Fatalf("unexpected failure notification %q", notifier.failure) } } func TestBuildFTPRemotePathNormalizesLoginRootMarkers(t *testing.T) { remotePath, err := buildFTPRemotePath(" ~////snapgo/ ") if err != nil { t.Fatalf("build remote path: %v", err) } if strings.HasPrefix(remotePath, "/") || !strings.HasPrefix(remotePath, "snapgo/") { t.Fatalf("expected relative snapgo path, got %q", remotePath) } }