From 297440b04713544d64f90d04ae9df416016b1ad1 Mon Sep 17 00:00:00 2001 From: Nathan Baulch Date: Tue, 9 Dec 2025 13:06:02 +1100 Subject: [PATCH] ftp: Replace fakeDataConn with mockery --- CHANGELOG.md | 1 + backend/ftp/dataconn.go | 6 +- backend/ftp/dataconn_test.go | 5 +- backend/ftp/file.go | 6 +- backend/ftp/fileSystem.go | 1 - backend/ftp/file_test.go | 600 +++++++++++++++-------------------- backend/ftp/location_test.go | 8 +- go.mod | 3 - go.sum | 41 --- 9 files changed, 265 insertions(+), 406 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f148d36..34ed8c3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ 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] +- Replace ftp `fakeDataConn` type with mockery. ## [v7.13.0] - 2025-01-26 ### Added diff --git a/backend/ftp/dataconn.go b/backend/ftp/dataconn.go index 373f235f..191e3c9b 100644 --- a/backend/ftp/dataconn.go +++ b/backend/ftp/dataconn.go @@ -147,7 +147,7 @@ func getDataConn(ctx context.Context, a authority.Authority, fs *FileSystem, f * fs.dataconn = nil } - if fs.dataconn == nil || fs.resetConn { + if fs.dataconn == nil { client, err := fs.Client(ctx, a) if err != nil { return nil, err @@ -176,10 +176,6 @@ func getDataConn(ctx context.Context, a authority.Authority, fs *FileSystem, f * c: client, } } - // ensure resetConn is false since we've opened/reopened the file - if f != nil { - fs.resetConn = false - } } return fs.dataconn, nil diff --git a/backend/ftp/dataconn_test.go b/backend/ftp/dataconn_test.go index 6d47b79a..0abeb0c1 100644 --- a/backend/ftp/dataconn_test.go +++ b/backend/ftp/dataconn_test.go @@ -230,9 +230,10 @@ func (s *dataConnSuite) TestGetDataConn_writeSuccess() { func (s *dataConnSuite) TestGetDataConn_readAfterWriteError() { // open dataconn for read after dataconn for write exists - error on dataconn.Close - fakedconn := NewFakeDataConn(types.OpenWrite) + fakedconn := mocks.NewDataConn(s.T()) + fakedconn.EXPECT().Mode().Return(types.OpenWrite) closeErr := errors.New("some close err") - fakedconn.AssertCloseErr(closeErr) + fakedconn.EXPECT().Close().Return(closeErr).Once() s.ftpFile.location.fileSystem.dataconn = fakedconn dc, err := getDataConn( s.T().Context(), diff --git a/backend/ftp/file.go b/backend/ftp/file.go index 53d9fe2a..879f4173 100644 --- a/backend/ftp/file.go +++ b/backend/ftp/file.go @@ -310,7 +310,7 @@ func (f *File) Close() error { if err != nil { return utils.WrapCloseError(err) } - f.location.fileSystem.resetConn = true + f.location.fileSystem.dataconn = nil } // no op for unopened file f.offset = 0 @@ -372,7 +372,7 @@ func (f *File) Seek(offset int64, whence int) (int64, error) { if err != nil { return 0, utils.WrapSeekError(err) } - f.location.fileSystem.resetConn = true + f.location.fileSystem.dataconn = nil case 2: // offset from end of the file sz, err := f.Size() if err != nil { @@ -393,7 +393,7 @@ func (f *File) Seek(offset int64, whence int) (int64, error) { if err != nil { return 0, utils.WrapSeekError(err) } - f.location.fileSystem.resetConn = true + f.location.fileSystem.dataconn = nil } } diff --git a/backend/ftp/fileSystem.go b/backend/ftp/fileSystem.go index 4bc97dd4..36555048 100644 --- a/backend/ftp/fileSystem.go +++ b/backend/ftp/fileSystem.go @@ -30,7 +30,6 @@ type FileSystem struct { options Options ftpclient types.Client dataconn types.DataConn - resetConn bool } // NewFileSystem initializer for fileSystem struct. diff --git a/backend/ftp/file_test.go b/backend/ftp/file_test.go index dd0abfec..47a2ea8c 100644 --- a/backend/ftp/file_test.go +++ b/backend/ftp/file_test.go @@ -11,7 +11,6 @@ import ( "testing" "time" - fs "github.com/dsoprea/go-utility/v2/filesystem" _ftp "github.com/jlaffaye/ftp" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" @@ -40,6 +39,7 @@ func (ts *fileTestSuite) SetupTest() { ts.fs = FileSystem{ftpclient: ts.ftpClientMock, options: Options{}} ts.testFile, err = ts.fs.NewFile("user@host.com:22", "/some/path/to/file.txt") ts.Require().NoError(err, "Shouldn't return error creating test ftp.File instance.") + dataConnGetterFunc = getDataConn } var errClientGetter = errors.New("some dataconn getter error") @@ -55,8 +55,8 @@ func (ts *fileTestSuite) TestRead() { contents := "hello world!" - dc := NewFakeDataConn(types.OpenRead) - ts.Require().NoError(dc.AssertReadContents(contents)) + dc := mocks.NewDataConn(ts.T()) + dc.EXPECT().Mode().Return(types.OpenRead) auth, err := authority.NewAuthority("user@host1.com:22") ts.Require().NoError(err) @@ -74,6 +74,10 @@ func (ts *fileTestSuite) TestRead() { } // test successful read localFile := bytes.NewBuffer([]byte{}) + dc.EXPECT().Read(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + copy(p, contents) + return len(contents), io.EOF + }).Once() b, copyErr := io.Copy(localFile, ftpfile) ts.Require().NoError(copyErr, "no error expected") ts.Len(contents, int(b), "byte count after copy") @@ -81,7 +85,7 @@ func (ts *fileTestSuite) TestRead() { // test read error myReadErr := errors.New("some read error") - dc.AssertReadErr(myReadErr) + dc.EXPECT().Read(mock.Anything).Return(0, myReadErr).Once() cnt, rErr := ftpfile.Read(make([]byte, 1)) ts.Require().Error(rErr, "no error expected") ts.Require().ErrorIs(rErr, myReadErr, "error is a read error") @@ -102,10 +106,7 @@ func (ts *fileTestSuite) TestClose() { fp := "/some/path.txt" client := mocks.NewClient(ts.T()) - contents := "hello world!" - - dc := NewFakeDataConn(types.OpenRead) - ts.Require().NoError(dc.AssertReadContents(contents)) + dc := mocks.NewDataConn(ts.T()) auth, err := authority.NewAuthority("user@host1.com:22") ts.Require().NoError(err) @@ -129,24 +130,23 @@ func (ts *fileTestSuite) TestClose() { // error closing ftpfile myCloseErr := errors.New("some close error") - dc.AssertCloseErr(myCloseErr) + dc.EXPECT().Close().Return(myCloseErr).Once() err = ftpfile.Close() ts.Require().Error(err, "close error expected") - ts.Equal(1, dc.GetCloseCalledCount(), "dataconn.Close() called once") // success closing ftpfile - dc.AssertCloseErr(nil) + dc.EXPECT().Close().Return(nil).Once() err = ftpfile.Close() ts.Require().NoError(err, "no close error expected") // values zeroed after successful Close() - ts.True(ftpfile.location.fileSystem.resetConn, "resetConn should be true") + ts.Nil(ftpfile.location.fileSystem.dataconn, "dataconn should be nil") ts.Zero(ftpfile.offset, "offset should be zero") - ts.Equal(2, dc.GetCloseCalledCount(), "dataconn.Close() called a second time") } func (ts *fileTestSuite) TestWrite() { - fakeDataConn := NewFakeDataConn(types.OpenWrite) + fakeDataConn := mocks.NewDataConn(ts.T()) + fakeDataConn.EXPECT().Mode().Return(types.OpenWrite) auth, err := authority.NewAuthority("user@host.com:22") ts.Require().NoError(err) @@ -163,14 +163,19 @@ func (ts *fileTestSuite) TestWrite() { contents := "hello world!" // test write success + var written string + fakeDataConn.EXPECT().Write(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + written = string(p) + return len(p), nil + }).Once() count, err := file.Write([]byte(contents)) ts.Len(contents, count, "Returned count of bytes written should match number of bytes passed to Write.") - ts.Equal(fakeDataConn.GetWriteContents(), contents, "expected contents written") + ts.Equal(contents, written, "expected contents written") ts.Require().NoError(err, "Error should be nil when calling Write") // test write failure myWriteErr := errors.New("some write error") - fakeDataConn.AssertWriteErr(myWriteErr) + fakeDataConn.EXPECT().Write(mock.Anything).Return(0, myWriteErr).Once() count, wErr := file.Write([]byte(contents)) ts.Require().Error(wErr, "no error expected") ts.Require().ErrorIs(wErr, myWriteErr, "error is a write error") @@ -187,10 +192,13 @@ func (ts *fileTestSuite) TestWrite() { } func (ts *fileTestSuite) TestSeek() { - dataConnGetterFunc = getFakeDataConn - defer func() { - dataConnGetterFunc = getDataConn - }() + mockDataConn := mocks.NewDataConn(ts.T()) + mockDataConn.EXPECT().Mode().Return(types.OpenRead) + mockDataConn.EXPECT().IsTimePreciseInList().Return(true) + dataConnGetterFunc = func(_ context.Context, _ authority.Authority, fs *FileSystem, _ *File, _ types.OpenType) (types.DataConn, error) { + fs.dataconn = mockDataConn + return mockDataConn, nil + } // set up ftpfile fp := "/some/path.txt" @@ -200,14 +208,11 @@ func (ts *fileTestSuite) TestSeek() { auth, err := authority.NewAuthority("user@host1.com:22") ts.Require().NoError(err) - fakeDataConn := NewFakeDataConn(types.OpenRead) - ts.Require().NoError(fakeDataConn.AssertReadContents(contents)) ftpfile := &File{ location: &Location{ fileSystem: &FileSystem{ ftpclient: client, options: Options{}, - dataconn: fakeDataConn, }, authority: auth, }, @@ -215,52 +220,76 @@ func (ts *fileTestSuite) TestSeek() { } // seek to position 6, whence 0 + mockDataConn.EXPECT().GetEntry(fp).Return(&_ftp.Entry{}, nil).Once() _, err = ftpfile.Seek(6, 0) ts.Require().NoError(err, "no error expected") localFile := bytes.NewBuffer([]byte{}) + mockDataConn.EXPECT().Read(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + copy(p, contents[6:]) + return len(contents) - 6, io.EOF + }).Once() _, err = io.Copy(localFile, ftpfile) ts.Require().NoError(err, "no error expected") ts.Equal("world!", localFile.String(), "Seeking should move the ftp file cursor as expected") localFile = bytes.NewBuffer([]byte{}) // seek back to start + mockDataConn.EXPECT().GetEntry(fp).Return(&_ftp.Entry{}, nil).Once() _, err = ftpfile.Seek(0, 0) ts.Require().NoError(err, "no error expected") + mockDataConn.EXPECT().Read(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + copy(p, contents) + return len(contents), io.EOF + }).Once() _, err = io.Copy(localFile, ftpfile) ts.Require().NoError(err, "no error expected") ts.Equal(contents, localFile.String(), "Subsequent calls to seek work on temp ftp file as expected") // whence = 1 (seek relative position), seek 2 + mockDataConn.EXPECT().GetEntry(fp).Return(&_ftp.Entry{}, nil).Once() pos, err := ftpfile.Seek(6, 0) // seek to some mid point ts.Require().NoError(err, "no error expected") ts.Equal(int64(6), pos, "position check") + mockDataConn.EXPECT().GetEntry(fp).Return(&_ftp.Entry{}, nil).Once() + mockDataConn.EXPECT().Close().Return(nil).Once() pos, err = ftpfile.Seek(2, 1) // now seek to relative position ts.Require().NoError(err, "no error expected") ts.Equal(int64(8), pos, "position check") localFile.Reset() + mockDataConn.EXPECT().Read(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + copy(p, contents[8:]) + return len(contents) - 8, io.EOF + }).Once() _, err = io.Copy(localFile, ftpfile) ts.Require().NoError(err, "no error expected") ts.Equal("rld!", localFile.String(), "seek should be position 8, 2 relative to 6") // whence = 1, original file offset < 0 (not even sure if this is possible) ftpfile.offset = -2 // this SHOULD not be possible + mockDataConn.EXPECT().GetEntry(fp).Return(&_ftp.Entry{}, nil).Once() + mockDataConn.EXPECT().Close().Return(nil).Once() pos, err = ftpfile.Seek(5, 1) ts.Require().NoError(err, "no error expected") ts.Equal(int64(5), pos, "new offset should be 5") // whence = 2 (seek from end) - ftpfile.location.fileSystem.dataconn.(*FakeDataConn).AssertSize(uint64(len(contents))) + mockDataConn.EXPECT().GetEntry(fp).Return(&_ftp.Entry{Size: uint64(len(contents))}, nil).Twice() + mockDataConn.EXPECT().Close().Return(nil).Once() pos, err = ftpfile.Seek(8, 2) // seek to some mid point ts.Require().NoError(err, "no error expected") ts.Equal(int64(4), pos, "position check") localFile.Reset() + mockDataConn.EXPECT().Read(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + copy(p, contents[4:]) + return len(contents) - 4, io.EOF + }).Once() _, err = io.Copy(localFile, ftpfile) ts.Require().NoError(err, "no error expected") 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.location.fileSystem.resetConn = true // make dataconn nil + ftpfile.offset = 8 // set it to some offset + mockDataConn.EXPECT().GetEntry(fp).Return(&_ftp.Entry{}, nil).Once() offset, err := ftpfile.Seek(6, 0) ts.Require().NoError(err, "error not expected") ts.Equal(int64(6), offset, "returned offset should be 6") @@ -268,33 +297,35 @@ func (ts *fileTestSuite) TestSeek() { ts.NotNil(ftpfile.location.fileSystem.dataconn, "dataconn should no longer be nil") // whence = 2, correction of offset to 0 when whence 2 and seek offset > len(contents) + mockDataConn.EXPECT().GetEntry(fp).Return(&_ftp.Entry{}, nil).Twice() + mockDataConn.EXPECT().Close().Return(nil).Once() pos, err = ftpfile.Seek(15, 2) ts.Require().NoError(err, "no error expected") ts.Zero(pos, "new offset should be 5") // whence = 2, file doesn't exist yet - ftpfile.location.fileSystem.dataconn.(*FakeDataConn).AssertExists(false) + mockDataConn.EXPECT().GetEntry(fp).Return(nil, os.ErrNotExist).Once() _, err = ftpfile.Seek(15, 2) ts.Require().Error(err, "error expected") ts.Require().ErrorIs(err, os.ErrNotExist, "os error not exist expected") } func (ts *fileTestSuite) TestSeekError() { - dataConnGetterFunc = getFakeDataConn - defer func() { - dataConnGetterFunc = getDataConn - }() + mockDataConn := mocks.NewDataConn(ts.T()) + mockDataConn.EXPECT().Mode().Return(types.OpenRead) + mockDataConn.EXPECT().IsTimePreciseInList().Return(true) + dataConnGetterFunc = func(_ context.Context, _ authority.Authority, fs *FileSystem, _ *File, _ types.OpenType) (types.DataConn, error) { + fs.dataconn = mockDataConn + return mockDataConn, nil + } // set up ftpfile fp := "/some/path.txt" client := mocks.NewClient(ts.T()) - - contents := "hello world!" + client.EXPECT().IsTimePreciseInList().Return(true) auth, err := authority.NewAuthority("user@host1.com:22") ts.Require().NoError(err) - fakeDataConn := NewFakeDataConn(types.OpenRead) - ts.Require().NoError(fakeDataConn.AssertReadContents(contents)) ftpfile := &File{ location: &Location{ fileSystem: &FileSystem{ @@ -315,27 +346,25 @@ func (ts *fileTestSuite) TestSeekError() { ts.Require().Error(err, "should return an error") ts.Require().ErrorIs(err, dconnErr, "should be right kind of error") + dataConnGetterFunc = func(_ context.Context, _ authority.Authority, fs *FileSystem, _ *File, _ types.OpenType) (types.DataConn, error) { + fs.dataconn = mockDataConn + return mockDataConn, nil + } + // whence = 1, f.dataconn.Close() error - dataConnGetterFunc = getFakeDataConn - fakedconn := NewFakeDataConn(types.OpenRead) - ftpfile.location.fileSystem.dataconn = fakedconn + mockDataConn.EXPECT().GetEntry(fp).Return(&_ftp.Entry{}, nil).Once() closeErr := errors.New("some close error") - fakedconn.AssertCloseErr(closeErr) + mockDataConn.EXPECT().Close().Return(closeErr).Once() pos, err := ftpfile.Seek(3, 1) ts.Require().Error(err, "should return an error") ts.Require().ErrorIs(err, closeErr, "should be right kind of error") ts.Zero(pos, "position should be 0 on error") - fakedconn.AssertCloseErr(nil) // whence = 2, f.Size() error (client.GetEntry error) dataConnGetterFunc = getDataConn - ftpfile.location.fileSystem.resetConn = true + ftpfile.Location().FileSystem().(*FileSystem).dataconn = nil sizeErr := errors.New("some Size error") - client.EXPECT(). - IsTimePreciseInList(). - Return(true). - Once() client.EXPECT(). GetEntry(ftpfile.Path()). Return(nil, sizeErr). // return non-ErrNotFound error when calling Size() @@ -346,8 +375,8 @@ func (ts *fileTestSuite) TestSeekError() { ts.Zero(pos, "position should be 0 on error") // whence = 2, f.dataconn.Close() error - ftpfile.location.fileSystem.dataconn = fakedconn - fakedconn.AssertCloseErr(closeErr) + ftpfile.Location().FileSystem().(*FileSystem).dataconn = mockDataConn + mockDataConn.EXPECT().Close().Return(closeErr).Once() pos, err = ftpfile.Seek(3, 2) ts.Require().Error(err, "should return an error") ts.Require().ErrorIs(err, closeErr, "should be right kind of error") @@ -451,109 +480,139 @@ func (ts *fileTestSuite) TestNotExists_MLST() { } func (ts *fileTestSuite) TestCopyToFile() { - dataConnGetterFunc = getFakeDataConn - defer func() { - dataConnGetterFunc = getDataConn - }() - // set up source contents := "hello world!" - fakeReadDataConn := NewFakeDataConn(types.OpenRead) - ts.Require().NoError(fakeReadDataConn.AssertReadContents(contents)) + fakeReadDataConn := mocks.NewDataConn(ts.T()) + fakeReadDataConn.EXPECT().IsTimePreciseInList().Return(true) auth2, err := authority.NewAuthority("123@xyz.com:3022") ts.Require().NoError(err) + sourceFS := NewFileSystem(WithClient(ts.ftpClientMock)) sourceFile := &File{ location: &Location{ - fileSystem: NewFileSystem(WithClient(ts.ftpClientMock)), + fileSystem: sourceFS, authority: auth2, }, path: "/src/hello.txt", } - sourceFile.location.fileSystem.dataconn = fakeReadDataConn // set up target - fakeWriteDataConn := NewFakeDataConn(types.OpenWrite) + fakeWriteDataConn := mocks.NewDataConn(ts.T()) auth, err := authority.NewAuthority("user@host.com:22") ts.Require().NoError(err) + targetFS := NewFileSystem(WithClient(ts.ftpClientMock)) targetFile := &File{ location: &Location{ - fileSystem: NewFileSystem(WithClient(ts.ftpClientMock)), + fileSystem: targetFS, authority: auth, }, path: "/targ/hello.txt", } - targetFile.location.fileSystem.dataconn = fakeWriteDataConn + + dataConnGetterFunc = func(_ context.Context, _ authority.Authority, fs *FileSystem, _ *File, _ types.OpenType) (types.DataConn, error) { + if fs == sourceFS { + return fakeReadDataConn, nil + } + if fs == targetFS { + return fakeWriteDataConn, nil + } + return fs.dataconn, nil + } // successful copy + fakeReadDataConn.EXPECT().GetEntry("/src/hello.txt").Return(&_ftp.Entry{}, nil).Once() + fakeReadDataConn.EXPECT().Read(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + copy(p, contents) + return len(contents), io.EOF + }).Once() + var written string + fakeWriteDataConn.EXPECT().Write(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + written = string(p) + return len(contents), nil + }).Once() err = sourceFile.CopyToFile(targetFile) ts.Require().NoError(err, "Error shouldn't be returned from successful call to CopyToFile") - ts.Equal(contents, targetFile.location.fileSystem.dataconn.(*FakeDataConn).GetWriteContents(), "contents match") + ts.Equal(contents, written, "contents match") // file doesn't exist error while copying - fakeSingleOpDataConn := NewFakeDataConn(types.SingleOp) - fakeSingleOpDataConn.AssertExists(false) - sourceFile.location.fileSystem.resetConn = false - sourceFile.location.fileSystem.dataconn = fakeSingleOpDataConn + fakeReadDataConn.EXPECT().GetEntry("/src/hello.txt").Return(&_ftp.Entry{}, nil).Once() + fakeReadDataConn.EXPECT().Read(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + copy(p, contents) + return len(contents), io.EOF + }).Once() + fakeWriteDataConn.EXPECT().Write(mock.Anything).Return(len(contents), os.ErrNotExist).Once() err = sourceFile.CopyToFile(targetFile) ts.Require().Error(err, "error is expected") ts.Require().ErrorIs(err, os.ErrNotExist, "error is expected kind of error") // writer close error while copying - fakeReadDataConn = NewFakeDataConn(types.OpenRead) - sourceFile.location.fileSystem.dataconn = fakeReadDataConn - sourceFile.location.fileSystem.resetConn = false - ts.Require().NoError(fakeReadDataConn.AssertReadContents(contents)) - fakeWriteDataConn = NewFakeDataConn(types.OpenWrite) + fakeReadDataConn.EXPECT().GetEntry("/src/hello.txt").Return(&_ftp.Entry{}, nil).Once() + fakeReadDataConn.EXPECT().Read(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + copy(p, contents) + return len(contents), io.EOF + }).Once() targetFile.location.fileSystem.dataconn = fakeWriteDataConn - targetFile.location.fileSystem.resetConn = false closeErr := errors.New("some close error") - fakeWriteDataConn.AssertCloseErr(closeErr) // assert writer close error + fakeWriteDataConn.EXPECT().Write(mock.Anything).Return(len(contents), nil).Once() + fakeWriteDataConn.EXPECT().Close().Return(closeErr).Twice() err = sourceFile.CopyToFile(targetFile) ts.Require().Error(err, "error is expected") ts.Require().ErrorIs(err, closeErr, "error is expected kind of error") } func (ts *fileTestSuite) TestCopyToLocation() { - dataConnGetterFunc = getFakeDataConn - defer func() { - dataConnGetterFunc = getDataConn - }() - // set up source contents := "hello world!" - fakeReadDataConn := NewFakeDataConn(types.OpenRead) - ts.Require().NoError(fakeReadDataConn.AssertReadContents(contents)) + fakeReadDataConn := mocks.NewDataConn(ts.T()) + fakeReadDataConn.EXPECT().IsTimePreciseInList().Return(true) auth2, err := authority.NewAuthority("123@xyz.com:3022") ts.Require().NoError(err) + sourceFS := NewFileSystem(WithClient(ts.ftpClientMock)) sourceFile := &File{ location: &Location{ - fileSystem: NewFileSystem(WithClient(ts.ftpClientMock)), + fileSystem: sourceFS, authority: auth2, }, path: "/src/hello.txt", } - sourceFile.location.fileSystem.dataconn = fakeReadDataConn // set up target + fakeWriteDataConn := mocks.NewDataConn(ts.T()) auth, err := authority.NewAuthority("user@host.com:22") ts.Require().NoError(err) + targetFS := NewFileSystem(WithClient(ts.ftpClientMock)) targetLocation := &Location{ - fileSystem: &FileSystem{ - ftpclient: ts.ftpClientMock, - }, - authority: auth, - path: "/targ/", + fileSystem: targetFS, + authority: auth, + path: "/targ/", + } + + dataConnGetterFunc = func(_ context.Context, _ authority.Authority, fs *FileSystem, _ *File, _ types.OpenType) (types.DataConn, error) { + if fs == sourceFS { + return fakeReadDataConn, nil + } + if fs == targetFS { + return fakeWriteDataConn, nil + } + return fs.dataconn, nil } // copy to location success + fakeReadDataConn.EXPECT().GetEntry("/src/hello.txt").Return(&_ftp.Entry{}, nil).Once() + fakeReadDataConn.EXPECT().Read(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + copy(p, contents) + return len(contents), io.EOF + }).Once() + var written string + fakeWriteDataConn.EXPECT().Write(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + written = string(p) + return len(p), nil + }).Once() newFile, err := sourceFile.CopyToLocation(targetLocation) ts.Require().NoError(err, "Error shouldn't be returned from successful call to CopyToFile") ts.Equal("ftp://user@host.com:22/targ/hello.txt", newFile.URI(), "new file uri check") - ts.Equal(contents, newFile.(*File).location.fileSystem.dataconn.(*FakeDataConn).GetWriteContents(), "contents match") + ts.Equal(contents, written, "contents match") // copy to location newfile failure - fakeReadDataConn = NewFakeDataConn(types.OpenRead) - sourceFile.location.fileSystem.dataconn = fakeReadDataConn sourceFile.path = "" newFile, err = sourceFile.CopyToLocation(targetLocation) ts.Require().Error(err, "error is expected") @@ -562,92 +621,107 @@ func (ts *fileTestSuite) TestCopyToLocation() { } func (ts *fileTestSuite) TestMoveToFile_differentAuthority() { - dataConnGetterFunc = getFakeDataConn - defer func() { - dataConnGetterFunc = getDataConn - }() - // set up source contents := "hello world!" - fakeReadDataConn := NewFakeDataConn(types.OpenRead) - ts.Require().NoError(fakeReadDataConn.AssertReadContents(contents)) + fakeReadDataConn := mocks.NewDataConn(ts.T()) + fakeReadDataConn.EXPECT().IsTimePreciseInList().Return(true) auth2, err := authority.NewAuthority("123@xyz.com:3022") ts.Require().NoError(err) + sourceFS := NewFileSystem(WithClient(ts.ftpClientMock)) sourceFile := &File{ location: &Location{ - fileSystem: NewFileSystem(WithClient(ts.ftpClientMock)), + fileSystem: sourceFS, authority: auth2, }, path: "/src/hello.txt", } - sourceFile.location.fileSystem.dataconn = fakeReadDataConn // set up target - fakeWriteDataConn := NewFakeDataConn(types.OpenWrite) + fakeWriteDataConn := mocks.NewDataConn(ts.T()) auth, err := authority.NewAuthority("user@host.com:22") ts.Require().NoError(err) + targetFS := NewFileSystem(WithClient(ts.ftpClientMock)) targetFile := &File{ location: &Location{ - fileSystem: NewFileSystem(WithClient(ts.ftpClientMock)), + fileSystem: targetFS, authority: auth, }, path: "/targ/hello.txt", } - targetFile.location.fileSystem.dataconn = fakeWriteDataConn + + dataConnGetterFunc = func(_ context.Context, _ authority.Authority, fs *FileSystem, _ *File, _ types.OpenType) (types.DataConn, error) { + if fs == sourceFS { + return fakeReadDataConn, nil + } + if fs == targetFS { + return fakeWriteDataConn, nil + } + return fs.dataconn, nil + } // successfully MoveToFile for different authorities (copy-delete) + fakeReadDataConn.EXPECT().GetEntry("/src/hello.txt").Return(&_ftp.Entry{}, nil).Once() + fakeReadDataConn.EXPECT().Read(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + copy(p, contents) + return len(contents), io.EOF + }).Once() + fakeReadDataConn.EXPECT().Delete("/src/hello.txt").Return(nil).Once() + var written string + fakeWriteDataConn.EXPECT().Write(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + written = string(p) + return len(p), nil + }).Once() err = sourceFile.MoveToFile(targetFile) ts.Require().NoError(err, "Error shouldn't be returned from successful call to MoveToFile") - ts.Equal(contents, targetFile.location.fileSystem.dataconn.(*FakeDataConn).GetWriteContents(), "contents match") + ts.Equal(contents, written, "contents match") ts.Equal("ftp://user@host.com:22/targ/hello.txt", targetFile.URI(), "expected uri") // CopyToFile failure on MoveToFile - fakeReadDataConn = NewFakeDataConn(types.SingleOp) - sourceFile.location.fileSystem.dataconn = fakeReadDataConn - sourceFile.location.fileSystem.resetConn = false readErr := errors.New("some read error") - fakeReadDataConn.AssertExists(true) - fakeReadDataConn.AssertSingleOpErr(readErr) + fakeReadDataConn.EXPECT().GetEntry("/src/hello.txt").Return(nil, readErr).Once() err = sourceFile.MoveToFile(targetFile) ts.Require().Error(err, "error should be returned from successful call to MoveToFile") ts.Require().ErrorIs(err, readErr, "correct kind of error") } func (ts *fileTestSuite) TestMoveToFile_sameAuthority() { - dataConnGetterFunc = getFakeDataConn - defer func() { - dataConnGetterFunc = getDataConn - }() - // set up source - contents := "hello world!" - fakeReadDataConn := NewFakeDataConn(types.OpenRead) - ts.Require().NoError(fakeReadDataConn.AssertReadContents(contents)) + fakeReadDataConn := mocks.NewDataConn(ts.T()) auth2, err := authority.NewAuthority("123@xyz.com:3022") ts.Require().NoError(err) srcMockFTPClient := mocks.NewClient(ts.T()) + sourceFS := NewFileSystem(WithClient(srcMockFTPClient)) sourceFile := &File{ location: &Location{ - fileSystem: NewFileSystem(WithClient(srcMockFTPClient)), + fileSystem: sourceFS, authority: auth2, }, path: "/src/hello.txt", } - sourceFile.location.fileSystem.dataconn = fakeReadDataConn // set up target tgtMockFTPClient := mocks.NewClient(ts.T()) - fakeWriteDataConn := NewFakeDataConn(types.OpenWrite) + fakeWriteDataConn := mocks.NewDataConn(ts.T()) auth, err := authority.NewAuthority("123@xyz.com:3022") ts.Require().NoError(err) + targetFS := NewFileSystem(WithClient(tgtMockFTPClient)) targetFile := &File{ location: &Location{ - fileSystem: NewFileSystem(WithClient(tgtMockFTPClient)), + fileSystem: targetFS, authority: auth, }, path: "/targ/hello.txt", } - targetFile.location.fileSystem.dataconn = fakeWriteDataConn + + dataConnGetterFunc = func(_ context.Context, _ authority.Authority, fs *FileSystem, _ *File, _ types.OpenType) (types.DataConn, error) { + if fs == sourceFS { + return fakeReadDataConn, nil + } + if fs == targetFS { + return fakeWriteDataConn, nil + } + return fs.dataconn, nil + } // successfully MoveToFile for same authorities (rename) - dir exists entries := []*_ftp.Entry{ @@ -656,15 +730,15 @@ func (ts *fileTestSuite) TestMoveToFile_sameAuthority() { Type: _ftp.EntryTypeFolder, }, } - tgtMockFTPClient.EXPECT(). - List("/"). - Return(entries, nil). - Once() + fakeReadDataConn.EXPECT().Rename("/src/hello.txt", "/targ/hello.txt").Return(nil).Once() + fakeWriteDataConn.EXPECT().List("/").Return(entries, nil).Once() err = sourceFile.MoveToFile(targetFile) ts.Require().NoError(err, "Error shouldn't be returned from successful call to MoveToFile") ts.Equal("ftp://123@xyz.com:3022/targ/hello.txt", targetFile.URI(), "expected uri") // successfully MoveToFile for same authorities (rename) - dir doesn't exist + fakeReadDataConn.EXPECT().Rename("/src/hello.txt", "/targ/hello.txt").Return(nil).Once() + fakeWriteDataConn.EXPECT().List("/").Return(entries, nil).Once() err = sourceFile.MoveToFile(targetFile) ts.Require().NoError(err, "Error shouldn't be returned from successful call to MoveToFile") ts.Equal("ftp://123@xyz.com:3022/targ/hello.txt", targetFile.URI(), "expected uri") @@ -672,71 +746,82 @@ func (ts *fileTestSuite) TestMoveToFile_sameAuthority() { // get client failure defaultClientGetter = clientGetterReturnsError dataConnGetterFunc = getDataConn - sourceFile.location.fileSystem.ftpclient = nil - sourceFile.location.fileSystem.resetConn = true + sourceFile.Location().FileSystem().(*FileSystem).ftpclient = nil + tgtMockFTPClient.EXPECT().List("/").Return(entries, nil).Once() err = sourceFile.MoveToFile(targetFile) ts.Require().Error(err, "error is expected") ts.Require().ErrorIs(err, errClientGetter, "error is the right kind of error") defaultClientGetter = func(ctx context.Context, auth authority.Authority, opts Options) (client types.Client, err error) { return GetClient(ctx, auth, opts) } - targetFile.location.fileSystem.ftpclient = tgtMockFTPClient - dataConnGetterFunc = getFakeDataConn + dataConnGetterFunc = func(_ context.Context, _ authority.Authority, fs *FileSystem, _ *File, _ types.OpenType) (types.DataConn, error) { + return fs.dataconn, nil + } + sourceFile.location.fileSystem.dataconn = fakeReadDataConn // Exists failure existsErr := errors.New("some exists error") - targetFile.location.fileSystem.dataconn = NewFakeDataConn(types.SingleOp) - targetFile.location.fileSystem.dataconn.(*FakeDataConn).AssertSingleOpErr(existsErr) - targetFile.location.fileSystem.dataconn.(*FakeDataConn).AssertExists(true) + fakeReadDataConn.EXPECT().Rename("/src/hello.txt", "/targ/hello.txt").Return(existsErr).Once() + tgtMockFTPClient.EXPECT().List("/").Return(entries, nil).Once() err = sourceFile.MoveToFile(targetFile) ts.Require().Error(err, "error is expected") ts.Require().ErrorIs(err, existsErr, "error is the right kind of error") - targetFile.location.fileSystem.dataconn = NewFakeDataConn(types.SingleOp) // Mkdir failure mkdirErr := errors.New("some mkdir error") - sourceFile.location.fileSystem.dataconn = NewFakeDataConn(types.SingleOp) - sourceFile.location.fileSystem.dataconn.(*FakeDataConn).AssertSingleOpErr(mkdirErr) - sourceFile.location.fileSystem.resetConn = false + fakeReadDataConn.EXPECT().Rename("/src/hello.txt", "/targ/hello.txt").Return(mkdirErr).Once() + tgtMockFTPClient.EXPECT().List("/").Return(entries, nil).Once() err = sourceFile.MoveToFile(targetFile) ts.Require().Error(err, "error is expected") ts.Require().ErrorIs(err, mkdirErr, "error is the right kind of error") } func (ts *fileTestSuite) TestMoveToLocation() { - dataConnGetterFunc = getFakeDataConn - defer func() { - dataConnGetterFunc = getDataConn - }() - // set up source contents := "hello world!" - fakeReadDataConn := NewFakeDataConn(types.OpenRead) - ts.Require().NoError(fakeReadDataConn.AssertReadContents(contents)) + fakeReadDataConn := mocks.NewDataConn(ts.T()) + fakeReadDataConn.EXPECT().IsTimePreciseInList().Return(true) auth, err := authority.NewAuthority("123@xyz.com:3022") ts.Require().NoError(err) srcMockFTPClient := mocks.NewClient(ts.T()) + sourceFS := NewFileSystem(WithClient(srcMockFTPClient)) sourceFile := &File{ location: &Location{ - fileSystem: NewFileSystem(WithClient(srcMockFTPClient)), + fileSystem: sourceFS, authority: auth, }, path: "/src/hello.txt", } - sourceFile.location.fileSystem.dataconn = fakeReadDataConn // set up target + fakeWriteDataConn := mocks.NewDataConn(ts.T()) auth2, err := authority.NewAuthority("user@host.com:22") ts.Require().NoError(err) + targetFS := NewFileSystem(WithClient(srcMockFTPClient)) targetLocation := &Location{ - fileSystem: &FileSystem{ - ftpclient: ts.ftpClientMock, - }, - authority: auth2, - path: "/targ/", + fileSystem: targetFS, + authority: auth2, + path: "/targ/", + } + + dataConnGetterFunc = func(_ context.Context, _ authority.Authority, fs *FileSystem, _ *File, _ types.OpenType) (types.DataConn, error) { + if fs == sourceFS { + return fakeReadDataConn, nil + } + if fs == targetFS { + return fakeWriteDataConn, nil + } + return fs.dataconn, nil } // successful MoveToLocation + fakeReadDataConn.EXPECT().GetEntry("/src/hello.txt").Return(&_ftp.Entry{}, nil).Once() + fakeReadDataConn.EXPECT().Read(mock.Anything).RunAndReturn(func(p []byte) (int, error) { + copy(p, contents) + return len(contents), io.EOF + }).Once() + fakeReadDataConn.EXPECT().Delete("/src/hello.txt").Return(nil).Once() + fakeWriteDataConn.EXPECT().Write(mock.Anything).Return(len(contents), nil).Once() newFile, err := sourceFile.MoveToLocation(targetLocation) ts.Require().NoError(err, "error shouldn't be returned from successful call to MoveToFile") ts.Equal("ftp://user@host.com:22/targ/hello.txt", newFile.URI(), "new file uri check") @@ -753,7 +838,8 @@ func (ts *fileTestSuite) TestTouch_exists() { filepath := "/some/path.txt" // set up source client := mocks.NewClient(ts.T()) - dconn := NewFakeDataConn(types.OpenRead) + dconn := mocks.NewDataConn(ts.T()) + dconn.EXPECT().Mode().Return(types.OpenRead) auth, err := authority.NewAuthority("123@xyz.com:3022") ts.Require().NoError(err) file := &File{ @@ -826,6 +912,7 @@ func (ts *fileTestSuite) TestTouch_exists() { Rename(file.Location().Path()+tempFileName, file.Path()). Return(nil). Once() + dconn.EXPECT().Close().Return(nil) ts.Require().NoError(file.Touch()) // success calling Touch when SetTime is supported @@ -910,15 +997,17 @@ func (ts *fileTestSuite) TestTouch_exists() { } func (ts *fileTestSuite) TestTouch_notExists() { - dataConnGetterFunc = getFakeDataConn - defer func() { - dataConnGetterFunc = getDataConn - }() + mockDataConn := mocks.NewDataConn(ts.T()) + mockDataConn.EXPECT().IsTimePreciseInList().Return(true) + dataConnGetterFunc = func(_ context.Context, _ authority.Authority, fs *FileSystem, _ *File, _ types.OpenType) (types.DataConn, error) { + fs.dataconn = mockDataConn + return mockDataConn, nil + } filepath := "/some/path.txt" // set up source client := mocks.NewClient(ts.T()) - dconn := NewFakeDataConn(types.SingleOp) + client.EXPECT().IsTimePreciseInList().Return(true) auth, err := authority.NewAuthority("123@xyz.com:3022") ts.Require().NoError(err) file := &File{ @@ -926,7 +1015,6 @@ func (ts *fileTestSuite) TestTouch_notExists() { fileSystem: &FileSystem{ ftpclient: client, options: Options{}, - dataconn: dconn, }, authority: auth, path: "/some/", @@ -935,7 +1023,9 @@ func (ts *fileTestSuite) TestTouch_notExists() { } // success calling Touch when file does not exist - dconn.AssertExists(false) + mockDataConn.EXPECT().GetEntry(filepath).Return(nil, errors.New("550")).Once() + mockDataConn.EXPECT().Write(mock.Anything).Return(0, nil).Once() + mockDataConn.EXPECT().Close().Return(nil).Once() err = file.Touch() ts.Require().NoError(err, "no error expected") @@ -950,10 +1040,6 @@ func (ts *fileTestSuite) TestTouch_notExists() { }, }, nil). Once() - client.EXPECT(). - IsTimePreciseInList(). - Return(true). - Once() client.EXPECT(). GetEntry(file.Path()). // initial exists check Return(&_ftp.Entry{}, errors.New("550")). @@ -964,7 +1050,6 @@ func (ts *fileTestSuite) TestTouch_notExists() { StorFrom(file.Path(), mock.Anything, uint64(0)). Return(wErr). Once() - file.location.fileSystem.resetConn = false err = file.Touch() ts.Require().Error(err, "expected error") @@ -1005,8 +1090,8 @@ func (ts *fileTestSuite) TestDelete() { // failure getting client defaultClientGetter = clientGetterReturnsError - testFile.location.fileSystem.ftpclient = nil - testFile.location.fileSystem.resetConn = true + testFile.Location().FileSystem().(*FileSystem).ftpclient = nil + testFile.Location().FileSystem().(*FileSystem).dataconn = nil err = testFile.Delete() ts.Require().Error(err, "failed delete should return an error") ts.Require().ErrorIs(err, errClientGetter, "should be right kind of error") @@ -1049,8 +1134,8 @@ func (ts *fileTestSuite) TestLastModified() { // stat client error defaultClientGetter = clientGetterReturnsError - ts.testFile.(*File).location.fileSystem.ftpclient = nil - ts.testFile.(*File).location.fileSystem.resetConn = true + ts.testFile.(*File).Location().FileSystem().(*FileSystem).ftpclient = nil + ts.testFile.(*File).Location().FileSystem().(*FileSystem).dataconn = nil modTime, err = ts.testFile.LastModified() ts.Require().Error(err, "error expected") ts.Require().ErrorIs(err, errClientGetter, "err should be correct type") @@ -1153,188 +1238,3 @@ func (ts *fileTestSuite) TestNewFile() { ts.Equal(authorityStr, ftpFile.Location().Authority().String()) ts.Equal(key, ftpFile.Path()) } - -// FakeDataConn implements a types.DataConn -type FakeDataConn struct { - rw *fs.SeekableBuffer - mode types.OpenType - closeErr error - writeErr error - readErr error - singleOpErr error - exists bool - mlst bool - size uint64 - closeCalledCount int -} - -func (f *FakeDataConn) Delete(p string) error { - return f.singleOpErr -} - -func (f *FakeDataConn) GetEntry(p string) (*_ftp.Entry, error) { - if f.exists { - return &_ftp.Entry{ - Size: f.size, - }, f.singleOpErr - } else { - return nil, errors.New("550") - } -} - -func (f *FakeDataConn) List(p string) ([]*_ftp.Entry, error) { - if f.exists { - return []*_ftp.Entry{ - { - Type: _ftp.EntryTypeFolder, - }, - }, f.singleOpErr - } - return nil, errors.New("550") -} - -func (f *FakeDataConn) MakeDir(p string) error { - return f.singleOpErr -} - -func (f *FakeDataConn) Rename(from, to string) error { - return f.singleOpErr -} - -func (f *FakeDataConn) IsSetTimeSupported() bool { - return false -} - -func (f *FakeDataConn) SetTime(p string, t time.Time) error { - return f.singleOpErr -} - -func (f *FakeDataConn) IsTimePreciseInList() bool { - return f.mlst -} - -func (f *FakeDataConn) Read(p []byte) (int, error) { - if f.readErr != nil { - return 0, f.readErr - } - return f.rw.Read(p) -} - -func (f *FakeDataConn) Write(p []byte) (int, error) { - if f.writeErr != nil { - return 0, f.writeErr - } - return f.rw.Write(p) -} - -func (f *FakeDataConn) Close() error { - f.closeCalledCount++ - return f.closeErr -} - -func (f *FakeDataConn) Mode() types.OpenType { - return f.mode -} - -func NewFakeDataConn(mode types.OpenType) *FakeDataConn { - buf := fs.NewSeekableBuffer() - return &FakeDataConn{ - mode: mode, - rw: buf, - } -} - -func (f *FakeDataConn) AssertReadErr(err error) { - f.readErr = err -} - -func (f *FakeDataConn) AssertWriteErr(err error) { - f.writeErr = err -} - -func (f *FakeDataConn) AssertCloseErr(err error) { - f.closeErr = err -} - -func (f *FakeDataConn) AssertExists(exists bool) { - f.exists = exists -} - -func (f *FakeDataConn) AssertSingleOpErr(err error) { - f.singleOpErr = err -} - -func (f *FakeDataConn) AssertMLST(mlst bool) { - f.mlst = mlst -} - -func (f *FakeDataConn) AssertSize(size uint64) { - f.size = size -} - -func (f *FakeDataConn) AssertReadContents(contents string) error { - // write contents to buffer - _, err := f.rw.Write([]byte(contents)) - if err != nil { - return err - } - - // reset cursor after writing contents - _, err = f.rw.Seek(0, 0) - - return err -} - -func (f *FakeDataConn) GetWriteContents() string { - return string(f.rw.Bytes()) -} - -func (f *FakeDataConn) GetCloseCalledCount() int { - return f.closeCalledCount -} - -func getFakeDataConn(_ context.Context, a authority.Authority, fileSystem *FileSystem, f *File, t types.OpenType) (types.DataConn, error) { - if fileSystem.dataconn != nil { - if fileSystem.dataconn.Mode() != t { - // wrong session type ... close current session and unset it (so we can set a new one after) - err := fileSystem.dataconn.Close() - if err != nil { - return fileSystem.dataconn, err - } - if f != nil { - f.location.fileSystem.resetConn = true - } - } - } - - if f != nil && f.location.fileSystem.resetConn { - f.location.fileSystem.resetConn = false - - contents := fileSystem.dataconn.(*FakeDataConn).rw.Bytes() - fileSystem.dataconn = NewFakeDataConn(t) - _, err := fileSystem.dataconn.Write(contents) - if err != nil { - return nil, err - } - _, err = fileSystem.dataconn.(*FakeDataConn).rw.Seek(0, 0) - if err != nil { - return nil, err - } - fileSystem.dataconn.(*FakeDataConn).exists = true - fileSystem.dataconn.(*FakeDataConn).mlst = true - } - - if fileSystem.dataconn == nil { - fileSystem.dataconn = NewFakeDataConn(t) - } - - // Seek to offset (whence is always zero because of the way file.Seek calculates it for you) - if f != nil { - _, err := fileSystem.dataconn.(*FakeDataConn).rw.Seek(f.offset, 0) - if err != nil { - return nil, err - } - } - - return fileSystem.dataconn, nil -} diff --git a/backend/ftp/location_test.go b/backend/ftp/location_test.go index 4bd3ae6c..7b8bc712 100644 --- a/backend/ftp/location_test.go +++ b/backend/ftp/location_test.go @@ -1,6 +1,7 @@ package ftp import ( + "context" "errors" "os" "regexp" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/c2fo/vfs/v7/backend/ftp/mocks" + "github.com/c2fo/vfs/v7/backend/ftp/types" "github.com/c2fo/vfs/v7/utils" "github.com/c2fo/vfs/v7/utils/authority" ) @@ -535,7 +537,11 @@ func (lt *locationTestSuite) TestNewLocation() { } func (lt *locationTestSuite) TestDeleteFile() { - dataConnGetterFunc = getFakeDataConn + mockDataConn := mocks.NewDataConn(lt.T()) + mockDataConn.EXPECT().Delete("/old/filename.txt").Return(nil).Once() + dataConnGetterFunc = func(context.Context, authority.Authority, *FileSystem, *File, types.OpenType) (types.DataConn, error) { + return mockDataConn, nil + } loc, err := lt.ftpfs.NewLocation("ftp.host.com:21", "/old/") lt.Require().NoError(err) diff --git a/go.mod b/go.mod index 23bb20d0..e387e9df 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.21.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.95.1 github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 - github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 github.com/fatih/color v1.18.0 github.com/fsouza/fake-gcs-server v1.52.3 github.com/jlaffaye/ftp v0.2.1-0.20240214224549-4edb16bfcd0f @@ -56,11 +55,9 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20260121142036-a486691bba94 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-errors/errors v1.5.1 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/go.sum b/go.sum index 26bc688c..d1219ae2 100644 --- a/go.sum +++ b/go.sum @@ -99,21 +99,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E= -github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8= -github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= -github.com/dsoprea/go-exif/v3 v3.0.0-20221003160559-cf5cd88aa559/go.mod h1:rW6DMEv25U9zCtE5ukC7ttBRllXj7g7TAHl7tQrT5No= -github.com/dsoprea/go-exif/v3 v3.0.0-20221003171958-de6cb6e380a8/go.mod h1:akyZEJZ/k5bmbC9gA612ZLQkcED8enS9vuTiuAkENr0= -github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= -github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= -github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg= -github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= -github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8= -github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU= -github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LVjRU0RNUuMDqkPTxcALio0LWPFPXxxFCvVGVAwEpFc= -github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU= -github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw= -github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -134,12 +119,6 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsouza/fake-gcs-server v1.52.3 h1:hXddOPMGDKq5ENmttw6xkodVJy0uVhf7HhWvQgAOH6g= github.com/fsouza/fake-gcs-server v1.52.3/go.mod h1:A0XtSRX+zz5pLRAt88j9+Of0omQQW+RMqipFbvdNclQ= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= -github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= -github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= @@ -153,9 +132,6 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= @@ -202,8 +178,6 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jlaffaye/ftp v0.2.1-0.20240214224549-4edb16bfcd0f h1:u9Rqt4DbfQ1xc7syxtnWFNU1OjcXJeVYGsiU1q3QAI4= github.com/jlaffaye/ftp v0.2.1-0.20240214224549-4edb16bfcd0f/go.mod h1:4p8lUl4vQ80L598CygL+3IFtm+3nggvvW/palOlViwE= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= @@ -301,12 +275,7 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -320,23 +289,16 @@ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= @@ -383,9 +345,6 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=