Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 sftp `mockClosableClient` type with mockery.

## [v7.13.0] - 2025-01-26
### Added
Expand Down
52 changes: 7 additions & 45 deletions backend/sftp/concurrency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ package sftp
import (
"fmt"
"io"
"os"
"reflect"
"sync"
"testing"
"time"

_sftp "github.com/pkg/sftp"
"github.com/stretchr/testify/suite"
"golang.org/x/crypto/ssh"

"github.com/c2fo/vfs/v7/backend/sftp/mocks"
"github.com/c2fo/vfs/v7/utils/authority"
)

Expand Down Expand Up @@ -272,9 +271,9 @@ func (s *SFTPConcurrencyTestSuite) TestConnectTimeout() {
func (s *SFTPConcurrencyTestSuite) TestTimerLogicValidation() {
s.Run("Timer closes valid client", func() {
// Create a mock client that tracks Close() calls
mockClient := &mockClosableClient{
closeCalled: make(chan bool, 1),
}
mockClient := mocks.NewClient(s.T())
closeCalled := make(chan bool, 1)
mockClient.EXPECT().Close().Run(func() { closeCalled <- true }).Return(nil).Once()
mockConn := &mockCloser{closeCalled: make(chan bool, 1)}

fs := &FileSystem{
Expand All @@ -294,7 +293,7 @@ func (s *SFTPConcurrencyTestSuite) TestTimerLogicValidation() {

// Wait for timer to fire and Close to be called
select {
case <-mockClient.closeCalled:
case <-closeCalled:
// Success - Close was called on valid client
case <-time.After(2 * time.Second):
s.Fail("Timer should have closed the valid client")
Expand All @@ -317,7 +316,7 @@ func (s *SFTPConcurrencyTestSuite) TestTimerLogicValidation() {

s.Run("Timer handles typed-nil client safely", func() {
// Create a typed-nil client (this is what happens when client creation fails)
var typedNilClient Client = (*mockClosableClient)(nil)
var typedNilClient Client = (*mocks.Client)(nil)

fs := &FileSystem{
sftpclient: typedNilClient,
Expand Down Expand Up @@ -352,10 +351,7 @@ func (s *SFTPConcurrencyTestSuite) TestTimerLogicValidation() {
s.Run("Timer does not call Close on typed-nil client", func() {
// This test validates that Close() is NOT called on typed-nil
// (which would panic since the receiver is nil)
closableNil := &mockClosableClient{
closeCalled: make(chan bool, 1),
}
var typedNilClient Client = (*mockClosableClient)(nil)
var typedNilClient Client = (*mocks.Client)(nil)

fs := &FileSystem{
sftpclient: typedNilClient,
Expand Down Expand Up @@ -390,43 +386,9 @@ func (s *SFTPConcurrencyTestSuite) TestTimerLogicValidation() {

// The key validation: no panic should have occurred
s.False(panicOccurred, "Timer should handle typed-nil without panic")

// Verify Close was never called (channel should be empty)
select {
case <-closableNil.closeCalled:
s.Fail("Close should NOT be called on typed-nil client")
default:
// Success - Close was not called
}
})
}

// mockClosableClient implements Client interface for testing timer behavior
type mockClosableClient struct {
closeCalled chan bool
}

func (m *mockClosableClient) Close() error {
if m.closeCalled != nil {
m.closeCalled <- true
}
return nil
}

func (m *mockClosableClient) Chmod(path string, mode os.FileMode) error { return nil }
func (m *mockClosableClient) Chtimes(path string, atime, mtime time.Time) error {
return nil
}
func (m *mockClosableClient) Create(path string) (*_sftp.File, error) { return nil, nil }
func (m *mockClosableClient) MkdirAll(path string) error { return nil }
func (m *mockClosableClient) OpenFile(path string, f int) (*_sftp.File, error) {
return nil, nil
}
func (m *mockClosableClient) ReadDir(p string) ([]os.FileInfo, error) { return nil, nil }
func (m *mockClosableClient) Remove(path string) error { return nil }
func (m *mockClosableClient) Rename(oldname, newname string) error { return nil }
func (m *mockClosableClient) Stat(p string) (os.FileInfo, error) { return nil, nil }

// mockCloser implements io.Closer for testing
type mockCloser struct {
closeCalled chan bool
Expand Down
Loading