Files
coder/agent/agentfiles/ls_internal_test.go
Asher ff9ed91811 chore: move agent's file API into separate package (#21531)
This makes it so we can test it directly without having to go through
Tailnet, which appears to be causing flakes in CI where the requests
time out and never make it to the agent.

Takes inspiration from the container-related API endpoints.

Would probably make sense to refactor the ls tests to also go through
the API (rather than be internal tests like they are currently) but I
left those alone for now to keep the diff minimal.
2026-01-16 17:03:17 -09:00

247 lines
5.6 KiB
Go

package agentfiles
import (
"os"
"path/filepath"
"runtime"
"testing"
"github.com/spf13/afero"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/codersdk/workspacesdk"
)
type testFs struct {
afero.Fs
}
func newTestFs(base afero.Fs) *testFs {
return &testFs{
Fs: base,
}
}
func (*testFs) Open(name string) (afero.File, error) {
return nil, os.ErrPermission
}
func TestListFilesWithQueryParam(t *testing.T) {
t.Parallel()
fs := afero.NewMemMapFs()
query := workspacesdk.LSRequest{}
_, err := listFiles(fs, "not-relative", query)
require.Error(t, err)
require.Contains(t, err.Error(), "must be absolute")
tmpDir := t.TempDir()
err = fs.MkdirAll(tmpDir, 0o755)
require.NoError(t, err)
res, err := listFiles(fs, tmpDir, query)
require.NoError(t, err)
require.Len(t, res.Contents, 0)
}
func TestListFilesNonExistentDirectory(t *testing.T) {
t.Parallel()
fs := afero.NewMemMapFs()
query := workspacesdk.LSRequest{
Path: []string{"idontexist"},
Relativity: workspacesdk.LSRelativityHome,
}
_, err := listFiles(fs, "", query)
require.ErrorIs(t, err, os.ErrNotExist)
}
func TestListFilesPermissionDenied(t *testing.T) {
t.Parallel()
fs := newTestFs(afero.NewMemMapFs())
home, err := os.UserHomeDir()
require.NoError(t, err)
tmpDir := t.TempDir()
reposDir := filepath.Join(tmpDir, "repos")
err = fs.MkdirAll(reposDir, 0o000)
require.NoError(t, err)
rel, err := filepath.Rel(home, reposDir)
require.NoError(t, err)
query := workspacesdk.LSRequest{
Path: pathToArray(rel),
Relativity: workspacesdk.LSRelativityHome,
}
_, err = listFiles(fs, "", query)
require.ErrorIs(t, err, os.ErrPermission)
}
func TestListFilesNotADirectory(t *testing.T) {
t.Parallel()
fs := afero.NewMemMapFs()
home, err := os.UserHomeDir()
require.NoError(t, err)
tmpDir := t.TempDir()
err = fs.MkdirAll(tmpDir, 0o755)
require.NoError(t, err)
filePath := filepath.Join(tmpDir, "file.txt")
err = afero.WriteFile(fs, filePath, []byte("content"), 0o600)
require.NoError(t, err)
rel, err := filepath.Rel(home, filePath)
require.NoError(t, err)
query := workspacesdk.LSRequest{
Path: pathToArray(rel),
Relativity: workspacesdk.LSRelativityHome,
}
_, err = listFiles(fs, "", query)
require.ErrorContains(t, err, "is not a directory")
}
func TestListFilesSuccess(t *testing.T) {
t.Parallel()
tc := []struct {
name string
baseFunc func(t *testing.T) string
relativity workspacesdk.LSRelativity
}{
{
name: "home",
baseFunc: func(t *testing.T) string {
home, err := os.UserHomeDir()
require.NoError(t, err)
return home
},
relativity: workspacesdk.LSRelativityHome,
},
{
name: "root",
baseFunc: func(*testing.T) string {
if runtime.GOOS == "windows" {
return ""
}
return "/"
},
relativity: workspacesdk.LSRelativityRoot,
},
}
// nolint:paralleltest // Not since Go v1.22.
for _, tc := range tc {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
fs := afero.NewMemMapFs()
base := tc.baseFunc(t)
tmpDir := t.TempDir()
reposDir := filepath.Join(tmpDir, "repos")
err := fs.MkdirAll(reposDir, 0o755)
require.NoError(t, err)
downloadsDir := filepath.Join(tmpDir, "Downloads")
err = fs.MkdirAll(downloadsDir, 0o755)
require.NoError(t, err)
textFile := filepath.Join(tmpDir, "file.txt")
err = afero.WriteFile(fs, textFile, []byte("content"), 0o600)
require.NoError(t, err)
var queryComponents []string
// We can't get an absolute path relative to empty string on Windows.
if runtime.GOOS == "windows" && base == "" {
queryComponents = pathToArray(tmpDir)
} else {
rel, err := filepath.Rel(base, tmpDir)
require.NoError(t, err)
queryComponents = pathToArray(rel)
}
query := workspacesdk.LSRequest{
Path: queryComponents,
Relativity: tc.relativity,
}
resp, err := listFiles(fs, "", query)
require.NoError(t, err)
require.Equal(t, tmpDir, resp.AbsolutePathString)
// Output is sorted
require.Equal(t, []workspacesdk.LSFile{
{
Name: "Downloads",
AbsolutePathString: downloadsDir,
IsDir: true,
},
{
Name: "repos",
AbsolutePathString: reposDir,
IsDir: true,
},
{
Name: "file.txt",
AbsolutePathString: textFile,
IsDir: false,
},
}, resp.Contents)
})
}
}
func TestListFilesListDrives(t *testing.T) {
t.Parallel()
if runtime.GOOS != "windows" {
t.Skip("skipping test on non-Windows OS")
}
fs := afero.NewOsFs()
query := workspacesdk.LSRequest{
Path: []string{},
Relativity: workspacesdk.LSRelativityRoot,
}
resp, err := listFiles(fs, "", query)
require.NoError(t, err)
require.Contains(t, resp.Contents, workspacesdk.LSFile{
Name: "C:\\",
AbsolutePathString: "C:\\",
IsDir: true,
})
query = workspacesdk.LSRequest{
Path: []string{"C:\\"},
Relativity: workspacesdk.LSRelativityRoot,
}
resp, err = listFiles(fs, "", query)
require.NoError(t, err)
query = workspacesdk.LSRequest{
Path: resp.AbsolutePath,
Relativity: workspacesdk.LSRelativityRoot,
}
resp, err = listFiles(fs, "", query)
require.NoError(t, err)
// System directory should always exist
require.Contains(t, resp.Contents, workspacesdk.LSFile{
Name: "Windows",
AbsolutePathString: "C:\\Windows",
IsDir: true,
})
query = workspacesdk.LSRequest{
// Network drives are not supported.
Path: []string{"\\sshfs\\work"},
Relativity: workspacesdk.LSRelativityRoot,
}
resp, err = listFiles(fs, "", query)
require.ErrorContains(t, err, "drive")
}