Skip to content

Commit 59ee906

Browse files
committed
Improve test coverage to exceed 90%
- Add tests for error handling in FTP container - Add more comprehensive tests for file operations - Enhance testing for stdout/stderr capture - Add tests for large outputs and binary content - Add tests for directory operations Coverage: 90.1% for main package, 80.6% for containers
1 parent bd62c53 commit 59ee906

File tree

3 files changed

+312
-0
lines changed

3 files changed

+312
-0
lines changed

capture_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,79 @@ func TestCaptureFunctionErrors(t *testing.T) {
210210
panic("test panic")
211211
})
212212
})
213+
214+
t.Run("stdout and stderr with panic", func(t *testing.T) {
215+
defer func() {
216+
if r := recover(); r == nil {
217+
t.Errorf("Expected panic")
218+
}
219+
}()
220+
221+
CaptureStdoutAndStderr(t, func() {
222+
panic("test panic")
223+
})
224+
})
225+
}
226+
227+
// TestCaptureWithOutput tests capturing output when the function has early returns
228+
func TestCaptureWithOutput(t *testing.T) {
229+
t.Run("stdout with basic output", func(t *testing.T) {
230+
output := CaptureStdout(t, func() {
231+
fmt.Fprintln(os.Stdout, "before early return")
232+
})
233+
require.Equal(t, "before early return\n", output)
234+
require.NotContains(t, output, "after early return")
235+
})
236+
237+
t.Run("stderr with basic output", func(t *testing.T) {
238+
output := CaptureStderr(t, func() {
239+
fmt.Fprintln(os.Stderr, "before early return")
240+
})
241+
require.Equal(t, "before early return\n", output)
242+
require.NotContains(t, output, "after early return")
243+
})
244+
245+
t.Run("stdout and stderr with basic output", func(t *testing.T) {
246+
stdout, stderr := CaptureStdoutAndStderr(t, func() {
247+
fmt.Fprintln(os.Stdout, "stdout before early return")
248+
fmt.Fprintln(os.Stderr, "stderr before early return")
249+
})
250+
require.Equal(t, "stdout before early return\n", stdout)
251+
require.Equal(t, "stderr before early return\n", stderr)
252+
require.NotContains(t, stdout, "after early return")
253+
require.NotContains(t, stderr, "after early return")
254+
})
255+
}
256+
257+
// TestCaptureWithLargeOutput tests capturing large amounts of output
258+
func TestCaptureWithLargeOutput(t *testing.T) {
259+
const largeSize = 1000
260+
largeData := strings.Repeat("a", largeSize)
261+
262+
t.Run("large stdout", func(t *testing.T) {
263+
output := CaptureStdout(t, func() {
264+
fmt.Fprint(os.Stdout, largeData)
265+
})
266+
require.Len(t, output, largeSize)
267+
require.Equal(t, largeData, output)
268+
})
269+
270+
t.Run("large stderr", func(t *testing.T) {
271+
output := CaptureStderr(t, func() {
272+
fmt.Fprint(os.Stderr, largeData)
273+
})
274+
require.Len(t, output, largeSize)
275+
require.Equal(t, largeData, output)
276+
})
277+
278+
t.Run("large stdout and stderr", func(t *testing.T) {
279+
stdout, stderr := CaptureStdoutAndStderr(t, func() {
280+
fmt.Fprint(os.Stdout, largeData)
281+
fmt.Fprint(os.Stderr, largeData)
282+
})
283+
require.Len(t, stdout, largeSize)
284+
require.Len(t, stderr, largeSize)
285+
require.Equal(t, largeData, stdout)
286+
require.Equal(t, largeData, stderr)
287+
})
213288
}

containers/ftp_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,79 @@ func TestFTPContainer(t *testing.T) {
6363
require.NoError(t, os.WriteFile(localPath, content, 0o600))
6464
}
6565

66+
t.Run("Connect", func(t *testing.T) {
67+
// test the connect function directly
68+
conn, err := ftpContainer.connect(ctx)
69+
require.NoError(t, err)
70+
require.NotNil(t, conn)
71+
72+
// test connection state
73+
pwd, err := conn.CurrentDir()
74+
require.NoError(t, err)
75+
require.NotEmpty(t, pwd)
76+
77+
// close properly
78+
err = conn.Quit()
79+
require.NoError(t, err)
80+
})
81+
82+
t.Run("SaveAndRestoreCurrentDirectory", func(t *testing.T) {
83+
// first we need to make sure the testdir exists
84+
testdirPath := filepath.Join(tempDir, "testdir")
85+
moreFilePath := filepath.Join(testdirPath, "more.txt")
86+
require.NoError(t, os.MkdirAll(testdirPath, 0o750))
87+
require.NoError(t, os.WriteFile(moreFilePath, []byte("Test content"), 0o600))
88+
err := ftpContainer.SaveFile(ctx, moreFilePath, "testdir/more.txt")
89+
require.NoError(t, err, "Failed to create directory for test")
90+
91+
conn, err := ftpContainer.connect(ctx)
92+
require.NoError(t, err)
93+
defer func() {
94+
err := conn.Quit()
95+
require.NoError(t, err)
96+
}()
97+
98+
// test saving current directory
99+
originalDir, err := ftpContainer.saveCurrentDirectory(conn)
100+
require.NoError(t, err)
101+
require.NotEmpty(t, originalDir)
102+
103+
// change directory - now testdir should exist
104+
err = conn.ChangeDir("testdir")
105+
require.NoError(t, err)
106+
107+
// verify we're in a different directory
108+
currentDir, err := conn.CurrentDir()
109+
require.NoError(t, err)
110+
require.NotEqual(t, originalDir, currentDir)
111+
112+
// restore to original directory
113+
ftpContainer.restoreWorkingDirectory(conn, originalDir)
114+
115+
// verify we're back in the original directory
116+
restoredDir, err := conn.CurrentDir()
117+
require.NoError(t, err)
118+
require.Equal(t, originalDir, restoredDir)
119+
120+
// test with empty original directory (should be a no-op)
121+
ftpContainer.restoreWorkingDirectory(conn, "")
122+
})
123+
66124
t.Run("Upload", func(t *testing.T) {
67125
for filename := range testFiles {
68126
localPath := filepath.Join(tempDir, filename)
69127
remotePath := filename
70128
err := ftpContainer.SaveFile(ctx, localPath, remotePath)
71129
require.NoError(t, err, "Failed to upload file %s", filename)
72130
}
131+
132+
// test with invalid local path
133+
err := ftpContainer.SaveFile(ctx, "/path/does/not/exist.txt", "remote.txt")
134+
require.Error(t, err)
135+
136+
// test with invalid path traversal attempt
137+
err = ftpContainer.SaveFile(ctx, "../../../etc/passwd", "remote.txt")
138+
require.Error(t, err)
73139
})
74140

75141
t.Run("ListHome", func(t *testing.T) {
@@ -94,6 +160,15 @@ func TestFTPContainer(t *testing.T) {
94160
assert.True(t, foundFiles["test1.txt"], "test1.txt should be in home directory")
95161
assert.True(t, foundFiles["test2.txt"], "test2.txt should be in home directory")
96162
assert.True(t, foundFiles["testdir"], "testdir should be in home directory")
163+
164+
// test empty path and "." path
165+
emptyEntries, err := ftpContainer.ListFiles(ctx, "")
166+
require.NoError(t, err)
167+
require.NotEmpty(t, emptyEntries)
168+
169+
dotEntries, err := ftpContainer.ListFiles(ctx, ".")
170+
require.NoError(t, err)
171+
require.NotEmpty(t, dotEntries)
97172
})
98173

99174
t.Run("ListSubdir", func(t *testing.T) {
@@ -135,5 +210,121 @@ func TestFTPContainer(t *testing.T) {
135210
require.Equal(t, originalContent, downloadedContent, "Content mismatch for file %s", filename)
136211
t.Logf("Verified content for downloaded file %s", filename)
137212
}
213+
214+
// test with non-existent remote file
215+
err := ftpContainer.GetFile(ctx, "non-existent-file.txt", filepath.Join(downloadDir, "non-existent.txt"))
216+
require.Error(t, err)
217+
218+
// test with path traversal attempt
219+
err = ftpContainer.GetFile(ctx, "file.txt", "../../../etc/malicious.txt")
220+
require.Error(t, err)
221+
})
222+
223+
t.Run("SplitPath", func(t *testing.T) {
224+
testCases := []struct {
225+
path string
226+
expected []string
227+
}{
228+
{path: "foo/bar/baz", expected: []string{"foo", "bar", "baz"}},
229+
{path: "/foo/bar/baz", expected: []string{"foo", "bar", "baz"}},
230+
{path: "foo/bar/baz/", expected: []string{"foo", "bar", "baz"}},
231+
{path: "/foo/bar/baz/", expected: []string{"foo", "bar", "baz"}},
232+
{path: "", expected: []string{}},
233+
{path: "/", expected: []string{}},
234+
}
235+
236+
for _, tc := range testCases {
237+
t.Run(tc.path, func(t *testing.T) {
238+
result := splitPath(tc.path)
239+
require.Equal(t, tc.expected, result)
240+
})
241+
}
138242
})
243+
244+
t.Run("CreateDirRecursive", func(t *testing.T) {
245+
conn, err := ftpContainer.connect(ctx)
246+
require.NoError(t, err)
247+
defer func() {
248+
err := conn.Quit()
249+
require.NoError(t, err)
250+
}()
251+
252+
// create a deep directory structure
253+
err = ftpContainer.createDirRecursive(conn, "deep/nested/directory/structure")
254+
require.NoError(t, err)
255+
256+
// verify it exists by listing files
257+
entries, err := ftpContainer.ListFiles(ctx, "deep/nested/directory")
258+
require.NoError(t, err)
259+
260+
foundStructure := false
261+
for _, entry := range entries {
262+
if entry.Name == "structure" && entry.Type == 1 {
263+
foundStructure = true
264+
break
265+
}
266+
}
267+
assert.True(t, foundStructure, "deep/nested/directory/structure should exist")
268+
269+
// test with empty path (should be a no-op)
270+
err = ftpContainer.createDirRecursive(conn, "")
271+
require.NoError(t, err)
272+
})
273+
}
274+
275+
// TestFTPContainerErrorHandling tests error handling in FTP container
276+
func TestFTPContainerErrorHandling(t *testing.T) {
277+
t.Run("TestHandleMakeDirFailure", func(t *testing.T) {
278+
if testing.Short() {
279+
t.Skip("skipping FTP error handling test in short mode")
280+
}
281+
282+
// create a test container
283+
ctx := context.Background()
284+
ftpContainer := NewFTPTestContainer(ctx, t)
285+
defer func() {
286+
err := ftpContainer.Close(context.Background())
287+
require.NoError(t, err)
288+
}()
289+
290+
// connect to the container
291+
conn, err := ftpContainer.connect(ctx)
292+
require.NoError(t, err)
293+
defer conn.Quit()
294+
295+
// test the handleMakeDirFailure function with a directory that already exists
296+
// first create the directory normally
297+
err = conn.MakeDir("testdir2")
298+
require.NoError(t, err)
299+
300+
// now simulate a failure but where the directory actually exists
301+
err = ftpContainer.handleMakeDirFailure(conn, "testdir2", fmt.Errorf("simulated error"))
302+
require.NoError(t, err, "Should handle the case where directory exists but MakeDir failed")
303+
304+
// test with a non-existent directory
305+
err = ftpContainer.handleMakeDirFailure(conn, "definitely_not_exists_dir", fmt.Errorf("simulated error"))
306+
require.Error(t, err, "Should fail when directory doesn't exist")
307+
})
308+
}
309+
310+
// Test utility methods separately
311+
func TestSplitPath(t *testing.T) {
312+
testCases := []struct {
313+
path string
314+
expected []string
315+
}{
316+
{path: "foo/bar/baz", expected: []string{"foo", "bar", "baz"}},
317+
{path: "/foo/bar/baz", expected: []string{"foo", "bar", "baz"}},
318+
{path: "foo/bar/baz/", expected: []string{"foo", "bar", "baz"}},
319+
{path: "/foo/bar/baz/", expected: []string{"foo", "bar", "baz"}},
320+
{path: "", expected: []string{}},
321+
{path: "/", expected: []string{}},
322+
}
323+
324+
for _, tc := range testCases {
325+
t.Run(tc.path, func(t *testing.T) {
326+
result := splitPath(tc.path)
327+
require.Equal(t, tc.expected, result)
328+
})
329+
}
139330
}

file_utils_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package testutils
33
import (
44
"os"
55
"path/filepath"
6+
"runtime"
7+
"strings"
68
"testing"
79

810
"github.com/stretchr/testify/require"
@@ -74,4 +76,48 @@ func TestWriteTestFile(t *testing.T) {
7476
_, err = os.Stat(filePath)
7577
require.True(t, os.IsNotExist(err), "File should be removed after cleanup")
7678
})
79+
80+
t.Run("with large content", func(t *testing.T) {
81+
// create a large string (100KB)
82+
largeContent := strings.Repeat("abcdefghij", 10*1024) // 10 chars * 10K = 100KB
83+
filePath := WriteTestFile(t, largeContent)
84+
85+
// verify the file exists and has correct size
86+
info, err := os.Stat(filePath)
87+
require.NoError(t, err)
88+
require.Equal(t, int64(len(largeContent)), info.Size())
89+
90+
// verify the content
91+
data, err := os.ReadFile(filePath) // #nosec G304 -- safe file access in test
92+
require.NoError(t, err)
93+
require.Equal(t, largeContent, string(data))
94+
})
95+
96+
t.Run("with binary content", func(t *testing.T) {
97+
// create some binary data
98+
binaryContent := string([]byte{0, 1, 2, 3, 4, 5, 255, 254, 253})
99+
filePath := WriteTestFile(t, binaryContent)
100+
101+
// verify the content
102+
data, err := os.ReadFile(filePath) // #nosec G304 -- safe file access in test
103+
require.NoError(t, err)
104+
require.Equal(t, binaryContent, string(data))
105+
})
106+
107+
t.Run("file permissions", func(t *testing.T) {
108+
content := "permission test"
109+
filePath := WriteTestFile(t, content)
110+
111+
// check that the file has the expected permissions (0o600)
112+
info, err := os.Stat(filePath)
113+
require.NoError(t, err)
114+
115+
// on Unix systems, we can check the exact permission bits
116+
if runtime.GOOS != "windows" {
117+
// 0o600 = rw------- (read/write for owner only)
118+
expectedPerm := os.FileMode(0o600)
119+
actualPerm := info.Mode().Perm()
120+
require.Equal(t, expectedPerm, actualPerm, "File should have 0600 permissions")
121+
}
122+
})
77123
}

0 commit comments

Comments
 (0)