Repository: C2FO/vfs
Pull request #183 (2024-05-15 20:34:18)

Location exists bug 182

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/C2FO/vfs/pull/183?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 21.60%. Comparing base [(`cbcf632`)](https://app.codecov.io/gh/C2FO/vfs/commit/cbcf632be25b3a40f6f377ab3eb644d41c80c292?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO) to head [(`d84a78d`)](https://app.codecov.io/gh/C2FO/vfs/pull/183?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO).
> Report is 1 commits behind head on main.


:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #183      +/-   ##
==========================================
- Coverage   21.62%   21.60%   -0.03%     
==========================================
  Files          59       59              
  Lines       19246    19265      +19     
==========================================
  Hits         4162     4162              
- Misses      14623    14641      +18     
- Partials      461      462       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/C2FO/vfs/pull/183?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e7b5a5..b7e4ff0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 ## [Unreleased]
+## [6.13.2] - 2024-05-15
+### Fixed
+- Fixed #182 - location.Exists was checking the wrong dir for existence.  Now it checks the file's parent dir for the subdir's existence.
+- Corrected where file.Close() occurs then file.Location().NewFile("somefile.txt") but was not repsecting the connection reset. Now it does.
+
 ## [6.13.1] - 2024-04-24
 ### Fixed
 - Fixed #180 - SFTP backend now only opens files for W (instead of RW) except after read/seek. This should fix issues where server permission are write only.
diff --git a/backend/ftp/dataconn.go b/backend/ftp/dataconn.go
index 8473f5d..0b3e5c4 100644
--- a/backend/ftp/dataconn.go
+++ b/backend/ftp/dataconn.go
@@ -40,9 +40,7 @@ func (dc *dataConn) GetEntry(p string) (*_ftp.Entry, error) {
 	return dc.c.GetEntry(p)
 }
 
-// GetEntry conducts an FTP list for
-// the given path. Only allowed
-// in a single op connection.
+// List conducts an FTP list for the given path. Only allowed in a single op connection.
 func (dc *dataConn) List(p string) ([]*_ftp.Entry, error) {
 	if dc.mode != types.SingleOp {
 		return nil, singleOpInvalidDataconnType
@@ -84,8 +82,7 @@ func (dc *dataConn) SetTime(path string, t time.Time) error {
 	return dc.c.SetTime(path, t)
 }
 
-// IsTimPreciseInList will return true if MLST is
-// an available FTP action in the given dataconn.
+// IsTimePreciseInList will return true if MLST is an available FTP action in the given dataconn.
 func (dc *dataConn) IsTimePreciseInList() bool {
 	return dc.c.IsTimePreciseInList()
 }
@@ -95,9 +92,7 @@ func (dc *dataConn) Mode() types.OpenType {
 	return dc.mode
 }
 
-// Read will read bytes from the DataConn open file
-// to the given buffer. Only allowed in an open read
-// DataConn
+// Read will read bytes from the DataConn open file to the given buffer. Only allowed in an open read DataConn
 func (dc *dataConn) Read(buf []byte) (int, error) {
 	if dc.mode != types.OpenRead {
 		return 0, readInvalidDataconnType
@@ -152,7 +147,7 @@ func getDataConn(ctx context.Context, authority utils.Authority, fs *FileSystem,
 		fs.dataconn = nil
 	}
 
-	if fs.dataconn == nil || (f != nil && f.resetConn) {
+	if fs.dataconn == nil || fs.resetConn {
 		client, err := fs.Client(ctx, authority)
 		if err != nil {
 			return nil, err
@@ -183,7 +178,7 @@ func getDataConn(ctx context.Context, authority utils.Authority, fs *FileSystem,
 		}
 		// ensure resetConn is false since we've opened/reopened the file
 		if f != nil {
-			f.resetConn = false
+			fs.resetConn = false
 		}
 	}
 
diff --git a/backend/ftp/file.go b/backend/ftp/file.go
index d4c2664..cfc0647 100644
--- a/backend/ftp/file.go
+++ b/backend/ftp/file.go
@@ -34,7 +34,6 @@ type File struct {
 	authority  utils.Authority
 	path       string
 	offset     int64
-	resetConn  bool
 }
 
 // Info Functions
@@ -320,7 +319,7 @@ func (f *File) Close() error {
 		if err != nil {
 			return utils.WrapCloseError(err)
 		}
-		f.resetConn = true
+		f.fileSystem.resetConn = true
 	}
 	// no op for unopened file
 	f.offset = 0
@@ -382,7 +381,7 @@ func (f *File) Seek(offset int64, whence int) (int64, error) {
 			if err != nil {
 				return 0, utils.WrapSeekError(err)
 			}
-			f.resetConn = true
+			f.fileSystem.resetConn = true
 		case 2: // offset from end of the file
 			sz, err := f.Size()
 			if err != nil {
@@ -403,7 +402,7 @@ func (f *File) Seek(offset int64, whence int) (int64, error) {
 			if err != nil {
 				return 0, utils.WrapSeekError(err)
 			}
-			f.resetConn = true
+			f.fileSystem.resetConn = true
 		}
 	}
 
diff --git a/backend/ftp/fileSystem.go b/backend/ftp/fileSystem.go
index 0e673e0..2511725 100644
--- a/backend/ftp/fileSystem.go
+++ b/backend/ftp/fileSystem.go
@@ -24,6 +24,7 @@ type FileSystem struct {
 	options   vfs.Options
 	ftpclient types.Client
 	dataconn  types.DataConn
+	resetConn bool
 }
 
 // Retry will return the default no-op retrier. The FTP client provides its own retryer interface, and is available
@@ -87,7 +88,7 @@ func (fs *FileSystem) Scheme() string {
 	return Scheme
 }
 
-// Client returns the underlying ftp data connection, creating it, if necessary
+// DataConn returns the underlying ftp data connection, creating it, if necessary
 // See Overview for authentication resolution
 func (fs *FileSystem) DataConn(ctx context.Context, authority utils.Authority, t types.OpenType, f *File) (types.DataConn, error) {
 	if t != types.SingleOp && f == nil {
diff --git a/backend/ftp/location.go b/backend/ftp/location.go
index 4cfe7b3..130ff3e 100644
--- a/backend/ftp/location.go
+++ b/backend/ftp/location.go
@@ -143,7 +143,11 @@ func (l *Location) Exists() (bool, error) {
 	if err != nil {
 		return false, err
 	}
-	entries, err := dc.List(l.Path())
+
+	// get parent directory by removing the last part of the path
+	parentDir := strings.TrimSuffix(l.Path(), path.Base(l.Path())+"/")
+
+	entries, err := dc.List(parentDir)
 	if err != nil {
 		if strings.HasPrefix(err.Error(), fmt.Sprintf("%d", _ftp.StatusFileUnavailable)) {
 			// in this case the directory does not exist
@@ -152,15 +156,13 @@ func (l *Location) Exists() (bool, error) {
 		return false, err
 	}
 
-	if len(entries) == 0 {
-		return false, nil
-	}
-
-	if entries[0].Type != _ftp.EntryTypeFolder {
-		return false, err
+	for _, entry := range entries {
+		if entry.Name == path.Base(l.Path()) && entries[0].Type == _ftp.EntryTypeFolder {
+			return true, nil
+		}
 	}
 
-	return true, nil
+	return false, nil
 }
 
 // NewLocation makes a copy of the underlying Location, then modifies its path by calling ChangeDir with the
diff --git a/backend/sftp/file.go b/backend/sftp/file.go
index 8e13763..11b61a3 100644
--- a/backend/sftp/file.go
+++ b/backend/sftp/file.go
@@ -377,7 +377,7 @@ func (f *File) String() string {
 */
 
 // openFile wrapper allows us to inject a file opener (for mocking) vs the defaultOpenFile.
-func (f *File) openFile(flags int) (ReadWriteSeekCloser, error) {
+func (f *File) openFile(flags int) (ReadWriteSeekCloser, error) { //nolint:gocyclo // this function is complex by nature
 	if f.sftpfile != nil {
 
 		// this case shouldn't normally exist except when we've set our own ReadWriteSeekCloser in tests


Test patch:
diff --git a/backend/ftp/dataconn_test.go b/backend/ftp/dataconn_test.go
index 726351c..bea5a4a 100644
--- a/backend/ftp/dataconn_test.go
+++ b/backend/ftp/dataconn_test.go
@@ -19,145 +19,183 @@ import (
 
 type dataConnSuite struct {
 	suite.Suite
+	ftpFile *File
+	client  *mocks.Client
 }
 
 func TestDataConn(t *testing.T) {
 	suite.Run(t, new(dataConnSuite))
 }
 
-func (s *dataConnSuite) TestGetDataConn() {
+// test setup
+func (s *dataConnSuite) SetupTest() {
 	// set up ftpfile
-	fp := "/some/path.txt"
-	client := mocks.NewClient(s.T())
-	ftpfile := &File{
+	filepath := "/some/path.txt"
+	s.client = mocks.NewClient(s.T())
+	s.ftpFile = &File{
 		fileSystem: &FileSystem{
-			ftpclient: client,
+			ftpclient: s.client,
 			options:   Options{},
-			dataconn: &dataConn{
-				mode: types.OpenRead,
-			},
 		},
-		path: fp,
+		path: filepath,
 	}
 
+}
+
+func (s *dataConnSuite) TestGetDataConn_alreadyExists() {
+
 	// dataconn already exists
-	dc, err := getDataConn(context.Background(), utils.Authority{}, ftpfile.fileSystem, ftpfile, types.OpenRead)
+	s.ftpFile.fileSystem.dataconn = &dataConn{
+		mode: types.OpenRead,
+	}
+	dc, err := getDataConn(context.Background(), utils.Authority{}, s.ftpFile.fileSystem, s.ftpFile, types.OpenRead)
 	s.NoError(err, "no error expected")
 	s.IsTypef(&dataConn{}, dc, "dataconn returned")
+}
+
+func (s *dataConnSuite) TestGetDataConn_openForRead() {
 
 	// dataconn is nil - open for read
-	ftpfile.fileSystem.dataconn = nil
-	client.EXPECT().
-		RetrFrom(ftpfile.Path(), uint64(0)).
+	s.client.EXPECT().
+		RetrFrom(s.ftpFile.Path(), uint64(0)).
 		Return(&_ftp.Response{}, nil).
 		Once()
-	dc, err = getDataConn(context.Background(), utils.Authority{}, ftpfile.fileSystem, ftpfile, types.OpenRead)
+	dc, err := getDataConn(context.Background(), utils.Authority{}, s.ftpFile.fileSystem, s.ftpFile, types.OpenRead)
 	s.NoError(err, "no error expected")
 	s.IsTypef(&dataConn{}, dc, "dataconn returned")
+}
 
+func (s *dataConnSuite) TestGetDataConn_errorClientSetup() {
 	// dataconn is nil - error getting client
-	ftpfile.fileSystem.dataconn = nil
 	defaultClientGetter = clientGetterReturnsError
-	ftpfile.fileSystem.ftpclient = nil
-	dc, err = getDataConn(context.Background(), utils.Authority{}, ftpfile.fileSystem, ftpfile, types.OpenRead)
+	s.ftpFile.fileSystem.ftpclient = nil
+	dc, err := getDataConn(context.Background(), utils.Authority{}, s.ftpFile.fileSystem, s.ftpFile, types.OpenRead)
 	s.Error(err, "error is expected")
 	s.ErrorIs(err, errClientGetter, "error is right kind of error")
 	s.Nil(dc, "dataconn should be nil on error")
 	defaultClientGetter = getClient
-	ftpfile.fileSystem.ftpclient = client
+}
+
+func (s *dataConnSuite) TestGetDataConn_ReadError() {
 
 	// dataconn is nil - error calling client.RetrFrom
 	someErr := errors.New("some error")
-	client.EXPECT().
-		RetrFrom(ftpfile.Path(), uint64(0)).
+
+	s.client.EXPECT().
+		RetrFrom(s.ftpFile.Path(), uint64(0)).
 		Return(nil, someErr).
 		Once()
-	dc, err = getDataConn(context.Background(), utils.Authority{}, ftpfile.fileSystem, ftpfile, types.OpenRead)
+	dc, err := getDataConn(context.Background(), utils.Authority{}, s.ftpFile.fileSystem, s.ftpFile, types.OpenRead)
 	s.Error(err, "error is expected")
 	s.ErrorIs(err, someErr, "error is right kind of error")
 	s.Nil(dc, "dataconn should be nil on error")
+}
 
-	// dataconn is nil - open for write - location doesnt exist - success
-	entries := []*_ftp.Entry{{
-		Type: _ftp.EntryTypeFolder,
-	}}
-	ftpfile.fileSystem.dataconn = nil
-	client.EXPECT().
-		List(ftpfile.Location().Path()).
+func (s *dataConnSuite) TestGetDataConn_WriteLocationNotExists() {
+
+	// dataconn is nil - opebrewn for write - location doesnt exist - success
+	s.client.EXPECT().
+		List("/").
 		Return(nil, errors.New("550")).
 		Once()
-	client.EXPECT().
-		MakeDir(ftpfile.Location().Path()).
+	s.client.EXPECT().
+		MakeDir(s.ftpFile.Location().Path()).
 		Return(nil).
 		Once()
-	client.EXPECT().
-		StorFrom(ftpfile.Path(), mock.Anything, uint64(0)).
+	s.client.EXPECT().
+		StorFrom(s.ftpFile.Path(), mock.Anything, uint64(0)).
 		Return(nil).
 		Once()
-	_, err = getDataConn(context.Background(), utils.Authority{}, ftpfile.fileSystem, ftpfile, types.OpenWrite)
+	_, err := getDataConn(context.Background(), utils.Authority{}, s.ftpFile.fileSystem, s.ftpFile, types.OpenWrite)
 	s.NoError(err, "no error expected")
 
+	// brief sleep to ensure goroutines running StorFrom can all complete
+	time.Sleep(50 * time.Millisecond)
+}
+
+func (s *dataConnSuite) TestGetDataConn_errorWriting() {
+	entries := []*_ftp.Entry{{
+		Name: "some",
+		Type: _ftp.EntryTypeFolder,
+	}}
+	someErr := errors.New("some error")
+
 	// dataconn is nil - open for write - error calling client.StorFrom
-	ftpfile.fileSystem.dataconn = nil
-	client.EXPECT().
-		List(ftpfile.Location().Path()).
+	s.client.EXPECT().
+		List("/").
 		Return(entries, nil).
 		Once()
-	client.EXPECT().
-		StorFrom(ftpfile.Path(), mock.Anything, uint64(0)).
+	s.client.EXPECT().
+		StorFrom(s.ftpFile.Path(), mock.Anything, uint64(0)).
 		Return(someErr).
 		Once()
-	dc, err = getDataConn(context.Background(), utils.Authority{}, ftpfile.fileSystem, ftpfile, types.OpenWrite)
+	dc, err := getDataConn(context.Background(), utils.Authority{}, s.ftpFile.fileSystem, s.ftpFile, types.OpenWrite)
 	s.NoError(err, "no error expected")
 	// error in getDataConn should close the PipeReader meaning Write errors
 	_, err = dc.Write([]byte{})
 	s.Error(err, "error is expected")
+}
+
+func (s *dataConnSuite) TestGetDataConn_writeSuccess() {
+	entries := []*_ftp.Entry{{
+		Name: "some",
+		Type: _ftp.EntryTypeFolder,
+	}}
 
 	// dataconn is nil - open for write - success
-	ftpfile.fileSystem.dataconn = nil
-	client.EXPECT().
-		List(ftpfile.Location().Path()).
+	s.client.EXPECT().
+		List("/").
 		Return(entries, nil).
 		Once()
-	client.EXPECT().
-		StorFrom(ftpfile.Path(), mock.Anything, uint64(0)).
+	s.client.EXPECT().
+		StorFrom(s.ftpFile.Path(), mock.Anything, uint64(0)).
 		Return(nil).
 		Once()
-	dc, err = getDataConn(context.Background(), utils.Authority{}, ftpfile.fileSystem, ftpfile, types.OpenWrite)
+	dc, err := getDataConn(context.Background(), utils.Authority{}, s.ftpFile.fileSystem, s.ftpFile, types.OpenWrite)
 	s.NoError(err, "no error expected")
 	s.IsTypef(&dataConn{}, dc, "dataconn returned")
 
+	// brief sleep to ensure goroutines running StorFrom can all complete
+	time.Sleep(50 * time.Millisecond)
+}
+
+func (s *dataConnSuite) TestGetDataConn_reaedAfterWriteError() {
+
 	// open dataconn for read after dataconn for write exists - error on dataconn.Close
 	fakedconn := NewFakeDataConn(types.OpenWrite)
 	closeErr := errors.New("some close err")
 	fakedconn.AssertCloseErr(closeErr)
-	ftpfile.fileSystem.dataconn = fakedconn
-	dc, err = getDataConn(context.Background(), utils.Authority{}, ftpfile.fileSystem, ftpfile, types.OpenRead)
+	s.ftpFile.fileSystem.dataconn = fakedconn
+	dc, err := getDataConn(context.Background(), utils.Authority{}, s.ftpFile.fileSystem, s.ftpFile, types.OpenRead)
 	s.Error(err, "error is expected")
 	s.ErrorIs(err, closeErr, "error is right kind of error")
 	s.Nil(dc, "dataconn should be nil on error")
+}
 
+func (s *dataConnSuite) TestGetDataConn_writeAfterReadSuccess() {
 	// open dataconn for write after dataconn for read exists
-	ftpfile.fileSystem.dataconn = &dataConn{
+	entries := []*_ftp.Entry{{
+		Name: "some",
+		Type: _ftp.EntryTypeFolder,
+	}}
+	s.ftpFile.fileSystem.dataconn = &dataConn{
 		mode: types.OpenRead,
 		R:    io.NopCloser(strings.NewReader("")),
 	}
-	client.EXPECT().
-		List(ftpfile.Location().Path()).
+	s.client.EXPECT().
+		List("/").
 		Return(entries, nil).
 		Once()
-	client.EXPECT().
-		StorFrom(ftpfile.Path(), mock.Anything, uint64(0)).
+	s.client.EXPECT().
+		StorFrom(s.ftpFile.Path(), mock.Anything, uint64(0)).
 		Return(nil).
 		Once()
-	dc, err = getDataConn(context.Background(), utils.Authority{}, ftpfile.fileSystem, ftpfile, types.OpenWrite)
+	dc, err := getDataConn(context.Background(), utils.Authority{}, s.ftpFile.fileSystem, s.ftpFile, types.OpenWrite)
 	s.NoError(err, "no error expected")
 	s.IsTypef(&dataConn{}, dc, "dataconn returned")
 
 	// brief sleep to ensure goroutines running StorFrom can all complete
 	time.Sleep(50 * time.Millisecond)
-
-	client.AssertExpectations(s.T())
 }
 
 func (s *dataConnSuite) TestMode() {
diff --git a/backend/ftp/file_test.go b/backend/ftp/file_test.go
index 37e68d3..bc82230 100644
--- a/backend/ftp/file_test.go
+++ b/backend/ftp/file_test.go
@@ -141,7 +141,7 @@ func (ts *fileTestSuite) TestClose() {
 	ts.NoError(err, "no close error expected")
 
 	// values zeroed after successful Close()
-	ts.True(ftpfile.resetConn, "resetConn should be true")
+	ts.True(ftpfile.fileSystem.resetConn, "resetConn should be true")
 	ts.EqualValues(0, ftpfile.offset, "offset should be zero")
 	ts.Equal(2, dc.GetCloseCalledCount(), "dataconn.Close() called a second time")
 }
@@ -256,8 +256,8 @@ func (ts *fileTestSuite) TestSeek() {
 	ts.Equal("o world!", localFile.String(), "seek should be position 8, 2 relative to 6")
 
 	// dataconn != nil, so set file offset and get new dataconn
-	ftpfile.offset = 8       // set it to some offset
-	ftpfile.resetConn = true // make dataconn nil
+	ftpfile.offset = 8                  // set it to some offset
+	ftpfile.fileSystem.resetConn = true // make dataconn nil
 	offset, err := ftpfile.Seek(6, 0)
 	ts.NoError(err, "error not expected")
 	ts.EqualValues(6, offset, "returned offset should be 6")
@@ -326,7 +326,7 @@ func (ts *fileTestSuite) TestSeekError() {
 
 	// whence = 2, f.Size() error (client.GetEntry error)
 	dataConnGetterFunc = getDataConn
-	ftpfile.resetConn = true
+	ftpfile.fileSystem.resetConn = true
 	sizeErr := errors.New("some Size error")
 
 	client.EXPECT().
@@ -486,7 +486,7 @@ func (ts *fileTestSuite) TestCopyToFile() {
 	// file doesnt exist error while copying
 	fakeSingleOpDataConn := NewFakeDataConn(types.SingleOp)
 	fakeSingleOpDataConn.AssertExists(false)
-	sourceFile.resetConn = false
+	sourceFile.fileSystem.resetConn = false
 	sourceFile.fileSystem.dataconn = fakeSingleOpDataConn
 	err = sourceFile.CopyToFile(targetFile)
 	ts.Error(err, "error is expected")
@@ -495,11 +495,11 @@ func (ts *fileTestSuite) TestCopyToFile() {
 	// writer close error while copying
 	fakeReadDataConn = NewFakeDataConn(types.OpenRead)
 	sourceFile.fileSystem.dataconn = fakeReadDataConn
-	sourceFile.resetConn = false
+	sourceFile.fileSystem.resetConn = false
 	ts.NoError(fakeReadDataConn.AssertReadContents(contents))
 	fakeWriteDataConn = NewFakeDataConn(types.OpenWrite)
 	targetFile.fileSystem.dataconn = fakeWriteDataConn
-	targetFile.resetConn = false
+	targetFile.fileSystem.resetConn = false
 	closeErr := errors.New("some close error")
 	fakeWriteDataConn.AssertCloseErr(closeErr) // assert writer close error
 	err = sourceFile.CopyToFile(targetFile)
@@ -593,7 +593,7 @@ func (ts *fileTestSuite) TestMoveToFile_differentAuthority() {
 	// CopyToFile failure on MoveToFile
 	fakeReadDataConn = NewFakeDataConn(types.SingleOp)
 	sourceFile.fileSystem.dataconn = fakeReadDataConn
-	sourceFile.resetConn = false
+	sourceFile.fileSystem.resetConn = false
 	readErr := errors.New("some read error")
 	fakeReadDataConn.AssertExists(true)
 	fakeReadDataConn.AssertSingleOpErr(readErr)
@@ -644,7 +644,7 @@ func (ts *fileTestSuite) TestMoveToFile_sameAuthority() {
 		},
 	}
 	tgtMockFTPClient.EXPECT().
-		List(targetFile.Location().Path()).
+		List("/").
 		Return(entries, nil).
 		Once()
 	srcMockFTPClient.EXPECT().
@@ -676,7 +676,7 @@ func (ts *fileTestSuite) TestMoveToFile_sameAuthority() {
 	defaultClientGetter = clientGetterReturnsError
 	dataConnGetterFunc = getDataConn
 	sourceFile.fileSystem.WithClient(nil)
-	sourceFile.resetConn = true
+	sourceFile.fileSystem.resetConn = true
 	err = sourceFile.MoveToFile(targetFile)
 	ts.Error(err, "error is expected")
 	ts.ErrorIs(err, errClientGetter, "error is the right kind of error")
@@ -702,7 +702,7 @@ func (ts *fileTestSuite) TestMoveToFile_sameAuthority() {
 		Once()
 	sourceFile.fileSystem.dataconn = NewFakeDataConn(types.SingleOp)
 	sourceFile.fileSystem.dataconn.(*FakeDataConn).AssertSingleOpErr(mkdirErr)
-	sourceFile.resetConn = false
+	sourceFile.fileSystem.resetConn = false
 	err = sourceFile.MoveToFile(targetFile)
 	ts.Error(err, "error is expected")
 	ts.ErrorIs(err, mkdirErr, "error is the right kind of error")
@@ -781,27 +781,35 @@ func (ts *fileTestSuite) TestTouch_exists() {
 			Type: _ftp.EntryTypeFolder,
 		},
 	}
+	parentEntries := []*_ftp.Entry{
+		{
+			Name: "some",
+			Type: _ftp.EntryTypeFolder,
+		},
+	}
 
 	// success calling Touch
 	p := file.Path()
-	client.EXPECT().
-		List(file.Location().Path()).
-		Return(entries, nil).
-		Twice() // location should always exist for this test - no mkdir call
+	// time precision check in Touch() -> file.Exists() -> stat()
 	client.EXPECT().
 		IsTimePreciseInList().
 		Return(false).
 		Once()
+	// file exists check in Touch() -> file.Exists() -> stat()
 	client.EXPECT().
-		List(p). // initial exists check
+		List(p).
 		Return(entries, nil).
 		Once()
-
-	// reject set time method each time Touch is called
+	// reject set time method each time Touch() is called
 	client.EXPECT().
 		IsSetTimeSupported().
 		Return(false).
 		Once()
+	// location exists check in Touch() -> MoveToFile() -> location.Exists()
+	client.EXPECT().
+		List("/").
+		Return(parentEntries, nil).
+		Twice() // location should always exist for this test - no mkdir call
 
 	// Move to temp file (to update last modified)
 	n := time.Now()
@@ -894,7 +902,7 @@ func (ts *fileTestSuite) TestTouch_exists() {
 		Return(false).
 		Once()
 	client.EXPECT().
-		List(file.Location().Path()).
+		List("/").
 		Return(nil, listErr).
 		Once()
 
@@ -935,9 +943,10 @@ func (ts *fileTestSuite) TestTouch_notExists() {
 	// failure to Write in Touch func
 	dataConnGetterFunc = getDataConn
 	client.EXPECT().
-		List(file.Location().Path()). // initial exists check
+		List("/"). // initial exists check
 		Return([]*_ftp.Entry{
 			{
+				Name: "some",
 				Type: _ftp.EntryTypeFolder,
 			},
 		}, nil).
@@ -956,7 +965,7 @@ func (ts *fileTestSuite) TestTouch_notExists() {
 		StorFrom(file.Path(), mock.Anything, uint64(0)).
 		Return(wErr).
 		Once()
-	file.resetConn = false
+	file.fileSystem.resetConn = false
 
 	err = file.Touch()
 	ts.Error(err, "expected error")
@@ -998,7 +1007,7 @@ func (ts *fileTestSuite) TestDelete() {
 	// failure getting client
 	defaultClientGetter = clientGetterReturnsError
 	testFile.fileSystem.WithClient(nil)
-	testFile.resetConn = true
+	testFile.fileSystem.resetConn = true
 	err = testFile.Delete()
 	ts.Error(err, "failed delete should return an error")
 	ts.ErrorIs(err, errClientGetter, "should be right kind of error")
@@ -1045,7 +1054,7 @@ func (ts *fileTestSuite) TestLastModified() {
 	// stat client error
 	defaultClientGetter = clientGetterReturnsError
 	ts.testFile.(*File).fileSystem.WithClient(nil)
-	ts.testFile.(*File).resetConn = true
+	ts.testFile.(*File).fileSystem.resetConn = true
 	modTime, err = ts.testFile.LastModified()
 	ts.Error(err, "error expected")
 	ts.ErrorIs(err, errClientGetter, "err should be correct type")
@@ -1300,14 +1309,14 @@ func getFakeDataConn(_ context.Context, a utils.Authority, fileSystem *FileSyste
 				return fileSystem.dataconn, err
 			}
 			if f != nil {
-				f.resetConn = true
+				f.fileSystem.resetConn = true
 			}
 		}
 	}
 
-	if f != nil && f.resetConn {
+	if f != nil && f.fileSystem.resetConn {
 		if f != nil {
-			f.resetConn = false
+			f.fileSystem.resetConn = false
 		}
 		contents := fileSystem.dataconn.(*FakeDataConn).rw.Bytes()
 		fileSystem.dataconn = NewFakeDataConn(t)
diff --git a/backend/ftp/location_test.go b/backend/ftp/location_test.go
index 5992af8..50730a6 100644
--- a/backend/ftp/location_test.go
+++ b/backend/ftp/location_test.go
@@ -406,7 +406,7 @@ func (lt *locationTestSuite) TestExists() {
 	// locations does not exist
 	locPath = "/my/dir/"
 	dirs = []*_ftp.Entry{}
-	lt.client.On("List", locPath).Return(dirs, nil).Once()
+	lt.client.On("List", "/my/").Return(dirs, nil).Once()
 	loc, err = lt.ftpfs.NewLocation(authority, locPath)
 	lt.NoError(err)
 	exists, err = loc.Exists()
@@ -414,7 +414,7 @@ func (lt *locationTestSuite) TestExists() {
 	lt.True(!exists, "Call to Exists expected to return false.")
 
 	// some error calling list
-	lt.client.On("List", locPath).Return(dirs, errors.New("some error")).Once()
+	lt.client.On("List", "/my/").Return(dirs, errors.New("some error")).Once()
 	loc, err = lt.ftpfs.NewLocation(authority, locPath)
 	lt.NoError(err)
 	exists, err = loc.Exists()
@@ -430,7 +430,7 @@ func (lt *locationTestSuite) TestExists() {
 			Time:   time.Now().UTC(),
 		},
 	}
-	lt.client.On("List", locPath).Return(dirs, nil).Once()
+	lt.client.On("List", "/my/").Return(dirs, nil).Once()
 	loc, err = lt.ftpfs.NewLocation(authority, locPath)
 	lt.NoError(err)
 	exists, err = loc.Exists()