You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
606 lines
17 KiB
606 lines
17 KiB
package sftp
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"path"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestHomeDirPathTranslation tests that SFTP operations correctly translate
|
|
// paths relative to the user's HomeDir.
|
|
// This is the fix for https://github.com/seaweedfs/seaweedfs/issues/7470
|
|
func TestHomeDirPathTranslation(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
config := DefaultTestConfig()
|
|
config.EnableDebug = testing.Verbose()
|
|
|
|
fw := NewSftpTestFramework(t, config)
|
|
err := fw.Setup(config)
|
|
require.NoError(t, err, "failed to setup test framework")
|
|
defer fw.Cleanup()
|
|
|
|
// Test with user "testuser" who has HomeDir="/sftp/testuser"
|
|
// When they upload to "/", it should actually go to "/sftp/testuser"
|
|
sftpClient, sshConn, err := fw.ConnectSFTP("testuser", "testuserpassword")
|
|
require.NoError(t, err, "failed to connect as testuser")
|
|
defer sshConn.Close()
|
|
defer sftpClient.Close()
|
|
|
|
// Test 1: Upload file to "/" (should map to /sftp/testuser/)
|
|
t.Run("UploadToRoot", func(t *testing.T) {
|
|
testContent := []byte("Hello from SFTP test!")
|
|
filename := "test_upload.txt"
|
|
|
|
// Create file at "/" from user's perspective
|
|
file, err := sftpClient.Create("/" + filename)
|
|
require.NoError(t, err, "should be able to create file at /")
|
|
|
|
_, err = file.Write(testContent)
|
|
require.NoError(t, err, "should be able to write to file")
|
|
err = file.Close()
|
|
require.NoError(t, err, "should be able to close file")
|
|
|
|
// Verify file exists and has correct content
|
|
readFile, err := sftpClient.Open("/" + filename)
|
|
require.NoError(t, err, "should be able to open file")
|
|
defer readFile.Close()
|
|
|
|
content, err := io.ReadAll(readFile)
|
|
require.NoError(t, err, "should be able to read file")
|
|
require.Equal(t, testContent, content, "file content should match")
|
|
|
|
// Clean up
|
|
err = sftpClient.Remove("/" + filename)
|
|
require.NoError(t, err, "should be able to remove file")
|
|
})
|
|
|
|
// Test 2: Create directory at "/" (should map to /sftp/testuser/)
|
|
t.Run("CreateDirAtRoot", func(t *testing.T) {
|
|
dirname := "test_dir"
|
|
|
|
err := sftpClient.Mkdir("/" + dirname)
|
|
require.NoError(t, err, "should be able to create directory at /")
|
|
|
|
// Verify directory exists
|
|
info, err := sftpClient.Stat("/" + dirname)
|
|
require.NoError(t, err, "should be able to stat directory")
|
|
require.True(t, info.IsDir(), "should be a directory")
|
|
|
|
// Clean up
|
|
err = sftpClient.RemoveDirectory("/" + dirname)
|
|
require.NoError(t, err, "should be able to remove directory")
|
|
})
|
|
|
|
// Test 3: List directory at "/" (should list /sftp/testuser/)
|
|
t.Run("ListRoot", func(t *testing.T) {
|
|
// Create a test file first
|
|
testContent := []byte("list test content")
|
|
filename := "list_test.txt"
|
|
|
|
file, err := sftpClient.Create("/" + filename)
|
|
require.NoError(t, err)
|
|
_, err = file.Write(testContent)
|
|
require.NoError(t, err)
|
|
file.Close()
|
|
|
|
// List root directory
|
|
files, err := sftpClient.ReadDir("/")
|
|
require.NoError(t, err, "should be able to list root directory")
|
|
|
|
// Should find our test file
|
|
found := false
|
|
for _, f := range files {
|
|
if f.Name() == filename {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
require.True(t, found, "should find test file in listing")
|
|
|
|
// Clean up
|
|
err = sftpClient.Remove("/" + filename)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
// Test 4: Nested directory operations
|
|
t.Run("NestedOperations", func(t *testing.T) {
|
|
// Create nested directory structure
|
|
err := sftpClient.MkdirAll("/nested/dir/structure")
|
|
require.NoError(t, err, "should be able to create nested directories")
|
|
|
|
// Create file in nested directory
|
|
testContent := []byte("nested file content")
|
|
file, err := sftpClient.Create("/nested/dir/structure/file.txt")
|
|
require.NoError(t, err)
|
|
_, err = file.Write(testContent)
|
|
require.NoError(t, err)
|
|
file.Close()
|
|
|
|
// Verify file exists
|
|
readFile, err := sftpClient.Open("/nested/dir/structure/file.txt")
|
|
require.NoError(t, err)
|
|
content, err := io.ReadAll(readFile)
|
|
require.NoError(t, err)
|
|
readFile.Close()
|
|
require.Equal(t, testContent, content)
|
|
|
|
// Clean up
|
|
err = sftpClient.Remove("/nested/dir/structure/file.txt")
|
|
require.NoError(t, err)
|
|
err = sftpClient.RemoveDirectory("/nested/dir/structure")
|
|
require.NoError(t, err)
|
|
err = sftpClient.RemoveDirectory("/nested/dir")
|
|
require.NoError(t, err)
|
|
err = sftpClient.RemoveDirectory("/nested")
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
// Test 5: Rename operation
|
|
t.Run("RenameFile", func(t *testing.T) {
|
|
testContent := []byte("rename test content")
|
|
|
|
file, err := sftpClient.Create("/original.txt")
|
|
require.NoError(t, err)
|
|
_, err = file.Write(testContent)
|
|
require.NoError(t, err)
|
|
file.Close()
|
|
|
|
// Rename file
|
|
err = sftpClient.Rename("/original.txt", "/renamed.txt")
|
|
require.NoError(t, err, "should be able to rename file")
|
|
|
|
// Verify old file doesn't exist
|
|
_, err = sftpClient.Stat("/original.txt")
|
|
require.Error(t, err, "original file should not exist")
|
|
|
|
// Verify new file exists with correct content
|
|
readFile, err := sftpClient.Open("/renamed.txt")
|
|
require.NoError(t, err, "renamed file should exist")
|
|
content, err := io.ReadAll(readFile)
|
|
require.NoError(t, err)
|
|
readFile.Close()
|
|
require.Equal(t, testContent, content)
|
|
|
|
// Clean up
|
|
err = sftpClient.Remove("/renamed.txt")
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
// TestAdminRootAccess tests that admin user with HomeDir="/" can access everything
|
|
func TestAdminRootAccess(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
config := DefaultTestConfig()
|
|
config.EnableDebug = testing.Verbose()
|
|
|
|
fw := NewSftpTestFramework(t, config)
|
|
err := fw.Setup(config)
|
|
require.NoError(t, err, "failed to setup test framework")
|
|
defer fw.Cleanup()
|
|
|
|
// Connect as admin with HomeDir="/"
|
|
sftpClient, sshConn, err := fw.ConnectSFTP("admin", "adminpassword")
|
|
require.NoError(t, err, "failed to connect as admin")
|
|
defer sshConn.Close()
|
|
defer sftpClient.Close()
|
|
|
|
// Admin should be able to create directories anywhere
|
|
t.Run("CreateAnyDirectory", func(t *testing.T) {
|
|
// Create the user's home directory structure
|
|
err := sftpClient.MkdirAll("/sftp/testuser")
|
|
require.NoError(t, err, "admin should be able to create any directory")
|
|
|
|
// Create file in that directory
|
|
testContent := []byte("admin created this")
|
|
file, err := sftpClient.Create("/sftp/testuser/admin_file.txt")
|
|
require.NoError(t, err)
|
|
_, err = file.Write(testContent)
|
|
require.NoError(t, err)
|
|
file.Close()
|
|
|
|
// Verify file exists
|
|
info, err := sftpClient.Stat("/sftp/testuser/admin_file.txt")
|
|
require.NoError(t, err)
|
|
require.False(t, info.IsDir())
|
|
|
|
// Clean up
|
|
err = sftpClient.Remove("/sftp/testuser/admin_file.txt")
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
// TestLargeFileUpload tests uploading larger files through SFTP
|
|
func TestLargeFileUpload(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
config := DefaultTestConfig()
|
|
config.EnableDebug = testing.Verbose()
|
|
|
|
fw := NewSftpTestFramework(t, config)
|
|
err := fw.Setup(config)
|
|
require.NoError(t, err, "failed to setup test framework")
|
|
defer fw.Cleanup()
|
|
|
|
sftpClient, sshConn, err := fw.ConnectSFTP("testuser", "testuserpassword")
|
|
require.NoError(t, err, "failed to connect as testuser")
|
|
defer sshConn.Close()
|
|
defer sftpClient.Close()
|
|
|
|
// Create a 1MB file
|
|
t.Run("Upload1MB", func(t *testing.T) {
|
|
size := 1024 * 1024 // 1MB
|
|
testData := bytes.Repeat([]byte("A"), size)
|
|
|
|
file, err := sftpClient.Create("/large_file.bin")
|
|
require.NoError(t, err)
|
|
n, err := file.Write(testData)
|
|
require.NoError(t, err)
|
|
require.Equal(t, size, n)
|
|
file.Close()
|
|
|
|
// Verify file size
|
|
info, err := sftpClient.Stat("/large_file.bin")
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(size), info.Size())
|
|
|
|
// Verify content
|
|
readFile, err := sftpClient.Open("/large_file.bin")
|
|
require.NoError(t, err)
|
|
content, err := io.ReadAll(readFile)
|
|
require.NoError(t, err)
|
|
readFile.Close()
|
|
require.Equal(t, testData, content)
|
|
|
|
// Clean up
|
|
err = sftpClient.Remove("/large_file.bin")
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
// TestStatOperations tests Stat and Lstat operations
|
|
func TestStatOperations(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
config := DefaultTestConfig()
|
|
config.EnableDebug = testing.Verbose()
|
|
|
|
fw := NewSftpTestFramework(t, config)
|
|
err := fw.Setup(config)
|
|
require.NoError(t, err, "failed to setup test framework")
|
|
defer fw.Cleanup()
|
|
|
|
sftpClient, sshConn, err := fw.ConnectSFTP("testuser", "testuserpassword")
|
|
require.NoError(t, err, "failed to connect as testuser")
|
|
defer sshConn.Close()
|
|
defer sftpClient.Close()
|
|
|
|
// Create a test file
|
|
testContent := []byte("stat test content")
|
|
file, err := sftpClient.Create("/stat_test.txt")
|
|
require.NoError(t, err)
|
|
_, err = file.Write(testContent)
|
|
require.NoError(t, err)
|
|
file.Close()
|
|
|
|
t.Run("StatFile", func(t *testing.T) {
|
|
info, err := sftpClient.Stat("/stat_test.txt")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "stat_test.txt", info.Name())
|
|
require.Equal(t, int64(len(testContent)), info.Size())
|
|
require.False(t, info.IsDir())
|
|
})
|
|
|
|
t.Run("StatDirectory", func(t *testing.T) {
|
|
err := sftpClient.Mkdir("/stat_dir")
|
|
require.NoError(t, err)
|
|
|
|
info, err := sftpClient.Stat("/stat_dir")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "stat_dir", info.Name())
|
|
require.True(t, info.IsDir())
|
|
|
|
// Clean up
|
|
err = sftpClient.RemoveDirectory("/stat_dir")
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("StatRoot", func(t *testing.T) {
|
|
// Should be able to stat "/" which maps to user's home directory
|
|
info, err := sftpClient.Stat("/")
|
|
require.NoError(t, err, "should be able to stat root (home) directory")
|
|
require.True(t, info.IsDir(), "root should be a directory")
|
|
})
|
|
|
|
// Clean up
|
|
err = sftpClient.Remove("/stat_test.txt")
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// TestWalk tests walking directory trees
|
|
func TestWalk(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
config := DefaultTestConfig()
|
|
config.EnableDebug = testing.Verbose()
|
|
|
|
fw := NewSftpTestFramework(t, config)
|
|
err := fw.Setup(config)
|
|
require.NoError(t, err, "failed to setup test framework")
|
|
defer fw.Cleanup()
|
|
|
|
sftpClient, sshConn, err := fw.ConnectSFTP("testuser", "testuserpassword")
|
|
require.NoError(t, err, "failed to connect as testuser")
|
|
defer sshConn.Close()
|
|
defer sftpClient.Close()
|
|
|
|
// Create directory structure
|
|
err = sftpClient.MkdirAll("/walk/a/b")
|
|
require.NoError(t, err)
|
|
err = sftpClient.MkdirAll("/walk/c")
|
|
require.NoError(t, err)
|
|
|
|
// Create files
|
|
for _, p := range []string{"/walk/file1.txt", "/walk/a/file2.txt", "/walk/a/b/file3.txt", "/walk/c/file4.txt"} {
|
|
file, err := sftpClient.Create(p)
|
|
require.NoError(t, err)
|
|
file.Write([]byte("test"))
|
|
file.Close()
|
|
}
|
|
|
|
t.Run("WalkEntireTree", func(t *testing.T) {
|
|
var paths []string
|
|
walker := sftpClient.Walk("/walk")
|
|
for walker.Step() {
|
|
if walker.Err() != nil {
|
|
continue
|
|
}
|
|
paths = append(paths, walker.Path())
|
|
}
|
|
|
|
// Should find all directories and files
|
|
require.Contains(t, paths, "/walk")
|
|
require.Contains(t, paths, "/walk/a")
|
|
require.Contains(t, paths, "/walk/a/b")
|
|
require.Contains(t, paths, "/walk/c")
|
|
})
|
|
|
|
// Clean up
|
|
for _, p := range []string{"/walk/file1.txt", "/walk/a/file2.txt", "/walk/a/b/file3.txt", "/walk/c/file4.txt"} {
|
|
sftpClient.Remove(p)
|
|
}
|
|
for _, p := range []string{"/walk/a/b", "/walk/a", "/walk/c", "/walk"} {
|
|
sftpClient.RemoveDirectory(p)
|
|
}
|
|
}
|
|
|
|
// TestCurrentWorkingDirectory tests that Getwd and Chdir work correctly
|
|
func TestCurrentWorkingDirectory(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
config := DefaultTestConfig()
|
|
config.EnableDebug = testing.Verbose()
|
|
|
|
fw := NewSftpTestFramework(t, config)
|
|
err := fw.Setup(config)
|
|
require.NoError(t, err, "failed to setup test framework")
|
|
defer fw.Cleanup()
|
|
|
|
sftpClient, sshConn, err := fw.ConnectSFTP("testuser", "testuserpassword")
|
|
require.NoError(t, err, "failed to connect as testuser")
|
|
defer sshConn.Close()
|
|
defer sftpClient.Close()
|
|
|
|
// Create test directory
|
|
err = sftpClient.Mkdir("/cwd_test")
|
|
require.NoError(t, err)
|
|
|
|
t.Run("GetCurrentDir", func(t *testing.T) {
|
|
cwd, err := sftpClient.Getwd()
|
|
require.NoError(t, err)
|
|
// The initial working directory should be the user's home directory
|
|
// which from the user's perspective is "/"
|
|
require.NotEmpty(t, cwd)
|
|
})
|
|
|
|
t.Run("ChangeAndCreate", func(t *testing.T) {
|
|
// Create file in subdirectory using relative path after chdir
|
|
// Note: pkg/sftp doesn't support Chdir, so we test using absolute paths
|
|
file, err := sftpClient.Create("/cwd_test/relative_file.txt")
|
|
require.NoError(t, err)
|
|
file.Write([]byte("test"))
|
|
file.Close()
|
|
|
|
// Verify using absolute path
|
|
_, err = sftpClient.Stat("/cwd_test/relative_file.txt")
|
|
require.NoError(t, err)
|
|
|
|
// Clean up
|
|
sftpClient.Remove("/cwd_test/relative_file.txt")
|
|
})
|
|
|
|
// Clean up
|
|
err = sftpClient.RemoveDirectory("/cwd_test")
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// TestPathEdgeCases tests various edge cases in path handling
|
|
func TestPathEdgeCases(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
config := DefaultTestConfig()
|
|
config.EnableDebug = testing.Verbose()
|
|
|
|
fw := NewSftpTestFramework(t, config)
|
|
err := fw.Setup(config)
|
|
require.NoError(t, err, "failed to setup test framework")
|
|
defer fw.Cleanup()
|
|
|
|
sftpClient, sshConn, err := fw.ConnectSFTP("testuser", "testuserpassword")
|
|
require.NoError(t, err, "failed to connect as testuser")
|
|
defer sshConn.Close()
|
|
defer sftpClient.Close()
|
|
|
|
t.Run("PathWithDotDot", func(t *testing.T) {
|
|
// Create directory structure
|
|
err := sftpClient.MkdirAll("/edge/subdir")
|
|
require.NoError(t, err)
|
|
|
|
// Create file using path with ..
|
|
file, err := sftpClient.Create("/edge/subdir/../file.txt")
|
|
require.NoError(t, err)
|
|
file.Write([]byte("test"))
|
|
file.Close()
|
|
|
|
// Verify file was created in /edge
|
|
_, err = sftpClient.Stat("/edge/file.txt")
|
|
require.NoError(t, err, "file should be created in parent directory")
|
|
|
|
// Clean up
|
|
sftpClient.Remove("/edge/file.txt")
|
|
sftpClient.RemoveDirectory("/edge/subdir")
|
|
sftpClient.RemoveDirectory("/edge")
|
|
})
|
|
|
|
t.Run("PathWithTrailingSlash", func(t *testing.T) {
|
|
err := sftpClient.Mkdir("/trailing")
|
|
require.NoError(t, err)
|
|
|
|
// Stat with trailing slash
|
|
info, err := sftpClient.Stat("/trailing/")
|
|
require.NoError(t, err)
|
|
require.True(t, info.IsDir())
|
|
|
|
// Clean up
|
|
sftpClient.RemoveDirectory("/trailing")
|
|
})
|
|
|
|
t.Run("CreateFileAtRootPath", func(t *testing.T) {
|
|
// This is the exact scenario from issue #7470
|
|
// User with HomeDir="/sftp/testuser" uploads to "/"
|
|
file, err := sftpClient.Create("/issue7470.txt")
|
|
require.NoError(t, err, "should be able to create file at / (issue #7470)")
|
|
file.Write([]byte("This tests the fix for issue #7470"))
|
|
file.Close()
|
|
|
|
// Verify
|
|
_, err = sftpClient.Stat("/issue7470.txt")
|
|
require.NoError(t, err)
|
|
|
|
// Clean up
|
|
sftpClient.Remove("/issue7470.txt")
|
|
})
|
|
}
|
|
|
|
// TestFileContent tests reading and writing file content correctly
|
|
func TestFileContent(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
config := DefaultTestConfig()
|
|
config.EnableDebug = testing.Verbose()
|
|
|
|
fw := NewSftpTestFramework(t, config)
|
|
err := fw.Setup(config)
|
|
require.NoError(t, err, "failed to setup test framework")
|
|
defer fw.Cleanup()
|
|
|
|
sftpClient, sshConn, err := fw.ConnectSFTP("testuser", "testuserpassword")
|
|
require.NoError(t, err, "failed to connect as testuser")
|
|
defer sshConn.Close()
|
|
defer sftpClient.Close()
|
|
|
|
t.Run("BinaryContent", func(t *testing.T) {
|
|
// Create binary data with all byte values
|
|
data := make([]byte, 256)
|
|
for i := 0; i < 256; i++ {
|
|
data[i] = byte(i)
|
|
}
|
|
|
|
file, err := sftpClient.Create("/binary.bin")
|
|
require.NoError(t, err)
|
|
n, err := file.Write(data)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 256, n)
|
|
file.Close()
|
|
|
|
// Read back
|
|
readFile, err := sftpClient.Open("/binary.bin")
|
|
require.NoError(t, err)
|
|
content, err := io.ReadAll(readFile)
|
|
require.NoError(t, err)
|
|
readFile.Close()
|
|
|
|
require.Equal(t, data, content, "binary content should match")
|
|
|
|
// Clean up
|
|
sftpClient.Remove("/binary.bin")
|
|
})
|
|
|
|
t.Run("EmptyFile", func(t *testing.T) {
|
|
file, err := sftpClient.Create("/empty.txt")
|
|
require.NoError(t, err)
|
|
file.Close()
|
|
|
|
info, err := sftpClient.Stat("/empty.txt")
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), info.Size())
|
|
|
|
// Clean up
|
|
sftpClient.Remove("/empty.txt")
|
|
})
|
|
|
|
t.Run("UnicodeFilename", func(t *testing.T) {
|
|
filename := "/文件名.txt"
|
|
content := []byte("Unicode content: 你好世界")
|
|
|
|
file, err := sftpClient.Create(filename)
|
|
require.NoError(t, err)
|
|
file.Write(content)
|
|
file.Close()
|
|
|
|
// Read back
|
|
readFile, err := sftpClient.Open(filename)
|
|
require.NoError(t, err)
|
|
readContent, err := io.ReadAll(readFile)
|
|
require.NoError(t, err)
|
|
readFile.Close()
|
|
|
|
require.Equal(t, content, readContent)
|
|
|
|
// Verify in listing
|
|
files, err := sftpClient.ReadDir("/")
|
|
require.NoError(t, err)
|
|
found := false
|
|
for _, f := range files {
|
|
if f.Name() == path.Base(filename) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
require.True(t, found, "should find unicode filename in listing")
|
|
|
|
// Clean up
|
|
sftpClient.Remove(filename)
|
|
})
|
|
}
|
|
|