Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

1496 lines
46 KiB
Go

package workspace
import (
"context"
"errors"
"os"
"path/filepath"
"slices"
"testing"
workspacefiles "github.com/tutti-os/tutti/packages/workspace/files"
)
func TestLocalFilesAdapterListsLogicalChildren(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := os.Mkdir(filepath.Join(rootDir, "src"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(rootDir, "src", "main.go"), []byte("package main\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(rootDir, "README.md"), []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(0)
listing, err := adapter.ListDirectory(context.Background(), localFilesRoot(rootDir), "/workspace", false)
if err != nil {
t.Fatalf("ListDirectory() error = %v", err)
}
if listing.WorkspaceID != "ws-1" || listing.DirectoryPath != "/workspace" {
t.Fatalf("listing = %#v", listing)
}
if len(listing.Entries) != 2 {
t.Fatalf("entries = %#v, want 2 entries", listing.Entries)
}
if listing.Entries[0].Path != "/workspace/src" || listing.Entries[0].Kind != workspacefiles.EntryKindDirectory {
t.Fatalf("first entry = %#v, want src directory", listing.Entries[0])
}
if listing.Entries[0].SizeBytes != nil || !listing.Entries[0].HasChildren {
t.Fatalf("directory metadata = %#v", listing.Entries[0])
}
if listing.Entries[1].Path != "/workspace/README.md" || listing.Entries[1].SizeBytes == nil {
t.Fatalf("second entry = %#v, want readme file with size", listing.Entries[1])
}
}
func TestLocalFilesAdapterReadsExternalAbsolutePathsFromFilesystemRoot(t *testing.T) {
t.Parallel()
targetDir := t.TempDir()
targetPath := filepath.Join(targetDir, "test-note.md")
if err := os.WriteFile(targetPath, []byte("# Test note\n"), 0o644); err != nil {
t.Fatal(err)
}
root := workspacefiles.WorkspaceRoot{
WorkspaceID: "ws-1",
LogicalRoot: filepath.ToSlash(string(filepath.Separator)),
PhysicalRoot: string(filepath.Separator),
}
adapter := LocalFilesAdapter{}
listing, err := adapter.ListDirectory(
context.Background(),
root,
workspacefiles.LogicalPath(filepath.ToSlash(targetDir)),
false,
)
if err != nil {
t.Fatalf("ListDirectory() error = %v", err)
}
if listing.DirectoryPath.String() != filepath.ToSlash(targetDir) {
t.Fatalf("directoryPath = %q, want %q", listing.DirectoryPath, filepath.ToSlash(targetDir))
}
if len(listing.Entries) != 1 || listing.Entries[0].Path.String() != filepath.ToSlash(targetPath) {
t.Fatalf("entries = %#v, want test note", listing.Entries)
}
content, err := adapter.ReadFile(
context.Background(),
root,
workspacefiles.LogicalPath(filepath.ToSlash(targetPath)),
workspacefiles.DefaultReadFileMaxBytes,
)
if err != nil {
t.Fatalf("ReadFile() error = %v", err)
}
if string(content.Bytes) != "# Test note\n" {
t.Fatalf("content = %q", string(content.Bytes))
}
}
func TestLocalFilesAdapterRejectsSymlinkEscape(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
outsideDir := t.TempDir()
if err := os.WriteFile(filepath.Join(outsideDir, "secret.txt"), []byte("nope"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.Symlink(outsideDir, filepath.Join(rootDir, "outside")); err != nil {
t.Skipf("symlink unavailable: %v", err)
}
adapter := LocalFilesAdapter{}
listing, err := adapter.ListDirectory(context.Background(), localFilesRoot(rootDir), "/workspace", false)
if err != nil {
t.Fatalf("ListDirectory() error = %v", err)
}
if len(listing.Entries) != 0 {
t.Fatalf("entries = %#v, want symlink escape filtered", listing.Entries)
}
}
func TestLocalFilesAdapterListDirectorySkipsHiddenEntriesByDefault(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := os.Mkdir(filepath.Join(rootDir, ".agents"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(rootDir, ".env"), []byte("SECRET=1\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(filepath.Join(rootDir, "docs"), 0o755); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
listing, err := adapter.ListDirectory(context.Background(), localFilesRoot(rootDir), "/workspace", false)
if err != nil {
t.Fatalf("ListDirectory() error = %v", err)
}
if len(listing.Entries) != 1 || listing.Entries[0].Path != "/workspace/docs" {
t.Fatalf("entries = %#v, want only visible docs directory", listing.Entries)
}
}
func TestLocalFilesAdapterHidesKnownTransientFilesUnlessRequested(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
for _, name := range []string{
"~$draft.docx",
"download.crdownload",
"ordinary.tmp",
"~notes.md",
"report.docx",
} {
if err := os.WriteFile(filepath.Join(rootDir, name), []byte(name), 0o644); err != nil {
t.Fatal(err)
}
}
adapter := LocalFilesAdapter{}
listing, err := adapter.ListDirectory(context.Background(), localFilesRoot(rootDir), "/workspace", false)
if err != nil {
t.Fatalf("ListDirectory() error = %v", err)
}
paths := make([]workspacefiles.LogicalPath, 0, len(listing.Entries))
for _, entry := range listing.Entries {
paths = append(paths, entry.Path)
}
want := []workspacefiles.LogicalPath{
"/workspace/ordinary.tmp",
"/workspace/report.docx",
"/workspace/~notes.md",
}
if !slices.Equal(paths, want) {
t.Fatalf("entries = %#v, want %v", listing.Entries, want)
}
includingHidden, err := adapter.ListDirectory(context.Background(), localFilesRoot(rootDir), "/workspace", true)
if err != nil {
t.Fatalf("ListDirectory(includeHidden) error = %v", err)
}
if len(includingHidden.Entries) != 5 {
t.Fatalf("includeHidden entries = %#v, want all five files", includingHidden.Entries)
}
}
func TestLocalFilesAdapterListDirectoryKeepsNonHiddenSystemDirectories(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
for _, name := range []string{
"Applications",
"Library",
"System",
"Volumes",
"Desktop",
"Documents",
"Downloads",
"demo",
} {
if err := os.Mkdir(filepath.Join(rootDir, name), 0o755); err != nil {
t.Fatal(err)
}
}
adapter := LocalFilesAdapter{}
listing, err := adapter.ListDirectory(context.Background(), localFilesRoot(rootDir), "/workspace", false)
if err != nil {
t.Fatalf("ListDirectory() error = %v", err)
}
paths := make([]workspacefiles.LogicalPath, 0, len(listing.Entries))
for _, entry := range listing.Entries {
paths = append(paths, entry.Path)
}
want := []workspacefiles.LogicalPath{
"/workspace/Applications",
"/workspace/demo",
"/workspace/Desktop",
"/workspace/Documents",
"/workspace/Downloads",
"/workspace/Library",
"/workspace/System",
"/workspace/Volumes",
}
if !slices.Equal(paths, want) {
t.Fatalf("entries = %#v, want %v", listing.Entries, want)
}
}
func TestLocalFilesAdapterUsesPhysicalPathForMacOSRootProtectedDirectoryPolicy(t *testing.T) {
withLocalFilesRuntimeGOOS(t, "darwin")
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
if (LocalFilesAdapter{}).ShouldPrefetchDirectory(workspacefiles.WorkspaceRoot{
WorkspaceID: "ws-1",
LogicalRoot: "/",
PhysicalRoot: string(filepath.Separator),
}, "/System") {
t.Fatal("physical /System was not treated as protected")
}
if !(LocalFilesAdapter{}).ShouldPrefetchDirectory(workspacefiles.WorkspaceRoot{
WorkspaceID: "ws-1",
LogicalRoot: "/",
PhysicalRoot: t.TempDir(),
}, "/System") {
t.Fatal("logical /System inside an ordinary physical root was treated as protected")
}
if (LocalFilesAdapter{}).ShouldPrefetchDirectory(workspacefiles.WorkspaceRoot{
WorkspaceID: "ws-1",
LogicalRoot: "/workspace",
PhysicalRoot: homeDir,
}, "/workspace/Desktop") {
t.Fatal("logical /workspace/Desktop mapped to physical home Desktop was not treated as protected")
}
}
func TestLocalFilesAdapterMarksMacOSHomeTCCDirectoriesExpandableWithoutReadingChildren(t *testing.T) {
withLocalFilesRuntimeGOOS(t, "darwin")
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
desktopDir := filepath.Join(homeDir, "Desktop")
if err := os.Mkdir(desktopDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.Chmod(desktopDir, 0o000); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
_ = os.Chmod(desktopDir, 0o755)
})
root := workspacefiles.WorkspaceRoot{
WorkspaceID: "ws-1",
LogicalRoot: filepath.ToSlash(homeDir),
PhysicalRoot: homeDir,
}
listing, err := (LocalFilesAdapter{}).ListDirectory(
context.Background(),
root,
workspacefiles.NormalizeLogicalRoot(homeDir),
false,
)
if err != nil {
t.Fatalf("ListDirectory() error = %v", err)
}
if len(listing.Entries) != 1 {
t.Fatalf("entries = %#v, want one Desktop directory", listing.Entries)
}
entry := listing.Entries[0]
if entry.Path.String() != filepath.ToSlash(filepath.Join(homeDir, "Desktop")) ||
entry.Kind != workspacefiles.EntryKindDirectory {
t.Fatalf("entry = %#v, want Desktop directory", entry)
}
if !entry.HasChildren {
t.Fatalf("entry hasChildren = false, want true without reading children")
}
if (LocalFilesAdapter{}).ShouldPrefetchDirectory(root, entry.Path) {
t.Fatal("ShouldPrefetchDirectory(Desktop) = true, want false")
}
}
func TestLocalFilesAdapterListDirectoryIncludesHiddenEntriesWhenRequested(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := os.Mkdir(filepath.Join(rootDir, ".agents"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(rootDir, ".env"), []byte("SECRET=1\n"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
listing, err := adapter.ListDirectory(context.Background(), localFilesRoot(rootDir), "/workspace", true)
if err != nil {
t.Fatalf("ListDirectory() error = %v", err)
}
if len(listing.Entries) != 2 {
t.Fatalf("entries = %#v, want hidden file and directory", listing.Entries)
}
}
func TestLocalFilesAdapterCreatesAndDeletesEntries(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
adapter := LocalFilesAdapter{}
root := localFilesRoot(rootDir)
dirEntry, err := adapter.CreateDirectory(context.Background(), root, "/workspace/docs")
if err != nil {
t.Fatalf("CreateDirectory() error = %v", err)
}
if dirEntry.Kind != workspacefiles.EntryKindDirectory {
t.Fatalf("dir entry = %#v", dirEntry)
}
fileEntry, err := adapter.CreateFile(context.Background(), root, "/workspace/docs/readme.md")
if err != nil {
t.Fatalf("CreateFile() error = %v", err)
}
if fileEntry.Kind != workspacefiles.EntryKindFile || fileEntry.SizeBytes == nil {
t.Fatalf("file entry = %#v", fileEntry)
}
if err := adapter.DeleteEntry(context.Background(), root, "/workspace/docs", workspacefiles.EntryKindDirectory); err != nil {
t.Fatalf("DeleteEntry() error = %v", err)
}
if _, err := os.Stat(filepath.Join(rootDir, "docs")); !os.IsNotExist(err) {
t.Fatalf("deleted directory stat error = %v, want not exist", err)
}
}
func TestLocalFilesAdapterWritesTextFiles(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := os.MkdirAll(filepath.Join(rootDir, "docs"), 0o755); err != nil {
t.Fatal(err)
}
targetPath := filepath.Join(rootDir, "docs", "readme.md")
if err := os.WriteFile(targetPath, []byte("old"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
entry, err := adapter.WriteTextFile(
context.Background(),
localFilesRoot(rootDir),
"/workspace/docs/readme.md",
"updated",
)
if err != nil {
t.Fatalf("WriteTextFile() error = %v", err)
}
content, err := os.ReadFile(targetPath)
if err != nil {
t.Fatal(err)
}
if string(content) != "updated" {
t.Fatalf("file content = %q", content)
}
if entry.Path != "/workspace/docs/readme.md" {
t.Fatalf("entry path = %q", entry.Path)
}
}
func TestLocalFilesAdapterReadsFilesWithinBudget(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := os.MkdirAll(filepath.Join(rootDir, "docs"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(rootDir, "docs", "readme.md"), []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
content, err := adapter.ReadFile(
context.Background(),
localFilesRoot(rootDir),
"/workspace/docs/readme.md",
10,
)
if err != nil {
t.Fatalf("ReadFile() error = %v", err)
}
if string(content.Bytes) != "hello" {
t.Fatalf("content = %q", content.Bytes)
}
if content.Path != "/workspace/docs/readme.md" {
t.Fatalf("content path = %q", content.Path)
}
}
func TestLocalFilesAdapterRejectsReadFilesAboveBudget(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := os.WriteFile(filepath.Join(rootDir, "large.txt"), []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
_, err := adapter.ReadFile(
context.Background(),
localFilesRoot(rootDir),
"/workspace/large.txt",
4,
)
if !errors.Is(err, workspacefiles.ErrFileTooLarge) {
t.Fatalf("ReadFile() error = %v, want %v", err, workspacefiles.ErrFileTooLarge)
}
}
func TestLocalFilesAdapterMovesEntries(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := os.MkdirAll(filepath.Join(rootDir, "src"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(rootDir, "docs"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(rootDir, "src", "readme.md"), []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
entry, err := adapter.MoveEntry(
context.Background(),
localFilesRoot(rootDir),
"/workspace/src/readme.md",
"/workspace/docs",
)
if err != nil {
t.Fatalf("MoveEntry() error = %v", err)
}
if entry.Path != "/workspace/docs/readme.md" || entry.Kind != workspacefiles.EntryKindFile {
t.Fatalf("moved entry = %#v", entry)
}
if _, err := os.Stat(filepath.Join(rootDir, "src", "readme.md")); !os.IsNotExist(err) {
t.Fatalf("source stat error = %v, want not exist", err)
}
if _, err := os.Stat(filepath.Join(rootDir, "docs", "readme.md")); err != nil {
t.Fatalf("target stat error = %v", err)
}
}
func TestLocalFilesAdapterRenamesEntries(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := os.WriteFile(filepath.Join(rootDir, "notes.txt"), []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
entry, err := adapter.RenameEntry(
context.Background(),
localFilesRoot(rootDir),
"/workspace/notes.txt",
"renamed.txt",
)
if err != nil {
t.Fatalf("RenameEntry() error = %v", err)
}
if entry.Path != "/workspace/renamed.txt" || entry.Name != "renamed.txt" {
t.Fatalf("renamed entry = %#v", entry)
}
if _, err := os.Stat(filepath.Join(rootDir, "notes.txt")); !os.IsNotExist(err) {
t.Fatalf("source stat error = %v, want not exist", err)
}
if content, err := os.ReadFile(filepath.Join(rootDir, "renamed.txt")); err != nil || string(content) != "hello" {
t.Fatalf("renamed content = %q, err = %v", content, err)
}
}
func TestLocalFilesAdapterCopiesEntries(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := os.WriteFile(filepath.Join(rootDir, "notes.txt"), []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
entry, err := adapter.CopyEntry(
context.Background(),
localFilesRoot(rootDir),
"/workspace/notes.txt",
)
if err != nil {
t.Fatalf("CopyEntry() error = %v", err)
}
if entry.Path != "/workspace/notes copy.txt" || entry.Name != "notes copy.txt" {
t.Fatalf("copied entry = %#v", entry)
}
if content, err := os.ReadFile(filepath.Join(rootDir, "notes copy.txt")); err != nil || string(content) != "hello" {
t.Fatalf("copied content = %q, err = %v", content, err)
}
if content, err := os.ReadFile(filepath.Join(rootDir, "notes.txt")); err != nil || string(content) != "hello" {
t.Fatalf("source content = %q, err = %v", content, err)
}
}
func TestLocalFilesAdapterUploadsFilesIntoDirectory(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
sourceDir := t.TempDir()
if err := os.Mkdir(filepath.Join(rootDir, "docs"), 0o755); err != nil {
t.Fatal(err)
}
sourcePath := filepath.Join(sourceDir, "notes.txt")
if err := os.WriteFile(sourcePath, []byte("hello upload"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
entries, err := adapter.UploadFiles(
context.Background(),
localFilesRoot(rootDir),
"/workspace/docs",
[]string{sourcePath},
false,
)
if err != nil {
t.Fatalf("UploadFiles() error = %v", err)
}
if len(entries) != 1 || entries[0].Path != "/workspace/docs/notes.txt" {
t.Fatalf("entries = %#v", entries)
}
uploadedContent, err := os.ReadFile(filepath.Join(rootDir, "docs", "notes.txt"))
if err != nil {
t.Fatalf("ReadFile() error = %v", err)
}
if string(uploadedContent) != "hello upload" {
t.Fatalf("uploaded content = %q", uploadedContent)
}
}
func TestLocalFilesAdapterUploadsDirectorySourcesRecursively(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
sourceParentDir := t.TempDir()
sourceDir := filepath.Join(sourceParentDir, "nested")
if err := os.Mkdir(filepath.Join(rootDir, "docs"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(sourceDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(sourceDir, "deeper"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(sourceDir, "deeper", "notes.txt"), []byte("nested upload"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
entries, err := adapter.UploadFiles(
context.Background(),
localFilesRoot(rootDir),
"/workspace/docs",
[]string{sourceDir},
false,
)
if err != nil {
t.Fatalf("UploadFiles() error = %v", err)
}
if len(entries) != 1 || entries[0].Path != "/workspace/docs/nested" || entries[0].Kind != workspacefiles.EntryKindDirectory {
t.Fatalf("entries = %#v", entries)
}
uploadedContent, err := os.ReadFile(filepath.Join(rootDir, "docs", "nested", "deeper", "notes.txt"))
if err != nil {
t.Fatalf("ReadFile() error = %v", err)
}
if string(uploadedContent) != "nested upload" {
t.Fatalf("uploaded content = %q", uploadedContent)
}
}
func TestLocalFilesAdapterPreflightUploadFilesDetectsRecursiveConflicts(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
sourceParentDir := t.TempDir()
sourceDir := filepath.Join(sourceParentDir, "nested")
if err := os.MkdirAll(filepath.Join(rootDir, "docs", "nested", "deeper"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(sourceDir, "deeper"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(rootDir, "docs", "nested", "deeper", "notes.txt"), []byte("existing"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(sourceDir, "deeper", "notes.txt"), []byte("incoming"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
conflicts, err := adapter.PreflightUploadFiles(
context.Background(),
localFilesRoot(rootDir),
"/workspace/docs",
[]string{sourceDir},
)
if err != nil {
t.Fatalf("PreflightUploadFiles() error = %v", err)
}
if len(conflicts) != 1 {
t.Fatalf("conflicts = %#v, want 1 conflict", conflicts)
}
if conflicts[0].DestinationPath != "/workspace/docs/nested/deeper/notes.txt" {
t.Fatalf("destinationPath = %q, want /workspace/docs/nested/deeper/notes.txt", conflicts[0].DestinationPath)
}
if conflicts[0].Kind != workspacefiles.UploadConflictKindReplaceable {
t.Fatalf("kind = %q, want %q", conflicts[0].Kind, workspacefiles.UploadConflictKindReplaceable)
}
}
func TestLocalFilesAdapterOverwritesExistingUploadedFiles(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
sourceDir := t.TempDir()
if err := os.Mkdir(filepath.Join(rootDir, "docs"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(rootDir, "docs", "notes.txt"), []byte("old"), 0o644); err != nil {
t.Fatal(err)
}
sourcePath := filepath.Join(sourceDir, "notes.txt")
if err := os.WriteFile(sourcePath, []byte("new"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
_, err := adapter.UploadFiles(
context.Background(),
localFilesRoot(rootDir),
"/workspace/docs",
[]string{sourcePath},
true,
)
if err != nil {
t.Fatalf("UploadFiles() error = %v", err)
}
uploadedContent, err := os.ReadFile(filepath.Join(rootDir, "docs", "notes.txt"))
if err != nil {
t.Fatalf("ReadFile() error = %v", err)
}
if string(uploadedContent) != "new" {
t.Fatalf("uploaded content = %q", uploadedContent)
}
}
func TestLocalFilesAdapterPreflightUploadFilesDetectsTypeMismatchConflicts(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
sourceParentDir := t.TempDir()
sourceDir := filepath.Join(sourceParentDir, "nested")
if err := os.Mkdir(filepath.Join(rootDir, "docs"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(rootDir, "docs", "nested"), []byte("existing file"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(sourceDir, "deeper"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(sourceDir, "deeper", "notes.txt"), []byte("incoming"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{}
conflicts, err := adapter.PreflightUploadFiles(
context.Background(),
localFilesRoot(rootDir),
"/workspace/docs",
[]string{sourceDir},
)
if err != nil {
t.Fatalf("PreflightUploadFiles() error = %v", err)
}
if len(conflicts) != 1 {
t.Fatalf("conflicts = %#v, want 1 conflict", conflicts)
}
if conflicts[0].DestinationPath != "/workspace/docs/nested" {
t.Fatalf("destinationPath = %q, want /workspace/docs/nested", conflicts[0].DestinationPath)
}
if conflicts[0].Kind != workspacefiles.UploadConflictKindTypeMismatch {
t.Fatalf("kind = %q, want %q", conflicts[0].Kind, workspacefiles.UploadConflictKindTypeMismatch)
}
}
func TestLocalFilesAdapterSearchSkipsHiddenNoiseDirectoriesForNormalQueries(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
hiddenMatchPath := filepath.Join(
rootDir,
".cache",
"codex-runtimes",
"codex-primary-runtime",
"dependencies",
"python",
"lib",
"python3.12",
"site-packages",
"artifact_tool_v2-2.8.0.dist-info",
"direct_url.json",
)
if err := os.MkdirAll(filepath.Dir(hiddenMatchPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(hiddenMatchPath, []byte("{}"), 0o644); err != nil {
t.Fatal(err)
}
applicationsMatchPath := filepath.Join(rootDir, "Applications", "Nexight.app", "package.json")
if err := os.MkdirAll(filepath.Dir(applicationsMatchPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(applicationsMatchPath, []byte(`{"name":"app"}`), 0o644); err != nil {
t.Fatal(err)
}
systemMatchPath := filepath.Join(rootDir, "System", "Library", "package.json")
if err := os.MkdirAll(filepath.Dir(systemMatchPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(systemMatchPath, []byte(`{"name":"system"}`), 0o644); err != nil {
t.Fatal(err)
}
projectPackagePath := filepath.Join(rootDir, "project", "package.json")
if err := os.MkdirAll(filepath.Dir(projectPackagePath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(projectPackagePath, []byte(`{"name":"project"}`), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(1)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "package.json",
Limit: 5,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 1 {
t.Fatalf("entries = %#v, want 1 result", result.Entries)
}
if result.Entries[0].Path != "/workspace/project/package.json" {
t.Fatalf("first result = %#v, want /workspace/project/package.json", result.Entries[0])
}
}
func TestLocalFilesAdapterSearchKeepsShallowMatchesBeforeDeepCandidateCap(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
deepDir := filepath.Join(rootDir, "a-deep-project")
if err := os.MkdirAll(deepDir, 0o755); err != nil {
t.Fatal(err)
}
for _, name := range []string{"filler-a.txt", "filler-b.txt", "filler-c.txt"} {
if err := os.WriteFile(
filepath.Join(deepDir, name),
[]byte("filler"),
0o644,
); err != nil {
t.Fatal(err)
}
}
if err := os.WriteFile(filepath.Join(rootDir, "郑伟斌.csv"), []byte("a,b\n"), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(2)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "郑伟斌",
Limit: 5,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 1 {
t.Fatalf("entries = %#v, want shallow csv result", result.Entries)
}
if result.Entries[0].Path != "/workspace/郑伟斌.csv" {
t.Fatalf("first result = %#v, want /workspace/郑伟斌.csv", result.Entries[0])
}
}
func TestLocalFilesAdapterSearchTypeFilterKeepsFilenameAndParentPathMatches(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
// 一个目录名含 "22" 的文档文件:普通名称查询仍可以低优先级命中相对路径段。
docInFolder := filepath.Join(rootDir, "reports22", "q3.csv")
if err := os.MkdirAll(filepath.Dir(docInFolder), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(docInFolder, []byte("a,b\n"), 0o644); err != nil {
t.Fatal(err)
}
// 含 "22" 的文档文件(csv 归入 document):类型与关键词都命中,应保留。
docMatch := filepath.Join(rootDir, "data22.csv")
if err := os.WriteFile(docMatch, []byte("a,b\n"), 0o644); err != nil {
t.Fatal(err)
}
// 含 "22" 但非文档的文件(图片):关键词命中、类型不命中,应被筛选掉。
imageMatch := filepath.Join(rootDir, "shot22.png")
if err := os.WriteFile(imageMatch, []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(0)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "22",
Limit: 20,
Filters: []string{"document"},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
gotPaths := make(map[string]bool, len(result.Entries))
for _, entry := range result.Entries {
gotPaths[entry.Path.String()] = true
if entry.Kind == workspacefiles.EntryKindDirectory {
t.Fatalf("entry = %#v, directories must be excluded when a type filter is active", entry)
}
}
// 交集:(文件名或相对路径关键词 "22") ∩ 类型 document。
wantPaths := []string{"/workspace/data22.csv", "/workspace/reports22/q3.csv"}
for _, want := range wantPaths {
if !gotPaths[want] {
t.Fatalf("entries = %#v, want to include %s", result.Entries, want)
}
}
if gotPaths["/workspace/shot22.png"] {
t.Fatalf("entries = %#v, must exclude non-document shot22.png", result.Entries)
}
if len(result.Entries) != len(wantPaths) {
t.Fatalf("entries = %#v, want exactly %d results", result.Entries, len(wantPaths))
}
}
func TestLocalFilesAdapterSearchScopesToWithinSubdirectory(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
// 工作区根下两处同名匹配:仅「文稿」范围内的应被返回,根下另一处不应出现。
inDocuments := filepath.Join(rootDir, "Documents", "report-notes.md")
if err := os.MkdirAll(filepath.Dir(inDocuments), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(inDocuments, []byte("hi"), 0o644); err != nil {
t.Fatal(err)
}
outsideDocuments := filepath.Join(rootDir, "Downloads", "report-notes.md")
if err := os.MkdirAll(filepath.Dir(outsideDocuments), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(outsideDocuments, []byte("hi"), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(0)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "report",
Limit: 20,
Within: "Documents",
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 1 {
t.Fatalf("entries = %#v, want exactly 1 scoped result", result.Entries)
}
// 结果逻辑路径仍以工作区根为基准(含 /Documents 前缀),保持可定位。
if got := result.Entries[0].Path.String(); got != "/workspace/Documents/report-notes.md" {
t.Fatalf("result path = %q, want /workspace/Documents/report-notes.md", got)
}
}
func TestLocalFilesAdapterSearchWithoutWithinSpansWholeRoot(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
for _, rel := range []string{
filepath.Join("Documents", "report-notes.md"),
filepath.Join("Downloads", "report-notes.md"),
} {
full := filepath.Join(rootDir, rel)
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(full, []byte("hi"), 0o644); err != nil {
t.Fatal(err)
}
}
adapter := testLocalFilesAdapter(0)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "report",
Limit: 20,
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 2 {
t.Fatalf("entries = %#v, want both matches across the whole root", result.Entries)
}
}
func TestLocalFilesAdapterSearchNormalizesPhysicalAbsolutePathQuery(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
target := filepath.Join(rootDir, "src", "user")
if err := os.MkdirAll(target, 0o755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(rootDir, "archive", "src", "user"), 0o755); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(0)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: target,
Limit: 20,
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) == 0 || result.Entries[0].Path != "/workspace/src/user" {
t.Fatalf("entries = %#v, want physical absolute-path target first", result.Entries)
}
}
func TestLocalFilesAdapterSearchNormalizesPhysicalAbsolutePathWithRelativeRoot(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
target := filepath.Join(rootDir, "src", "user")
if err := os.MkdirAll(target, 0o755); err != nil {
t.Fatal(err)
}
workingDirectory, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
relativeRoot, err := filepath.Rel(workingDirectory, rootDir)
if err != nil {
t.Fatal(err)
}
root := localFilesRoot(relativeRoot)
result, err := testLocalFilesAdapter(0).Search(context.Background(), root, workspacefiles.SearchInput{
Query: target,
Limit: 20,
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) == 0 || result.Entries[0].Path != "/workspace/src/user" {
t.Fatalf("entries = %#v, want physical absolute-path target first", result.Entries)
}
}
func TestLocalFilesAdapterSearchFallsBackWhenIndexQueryExpires(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
readmePath := filepath.Join(rootDir, "project", "README.md")
if err := os.MkdirAll(filepath.Dir(readmePath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(readmePath, []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{searchProvider: failingLocalFileSearchProvider{err: context.DeadlineExceeded}}
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "README",
Limit: 5,
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 1 || result.Entries[0].Path != "/workspace/project/README.md" {
t.Fatalf("entries = %#v, want filesystem fallback result", result.Entries)
}
}
func TestLocalFilesAdapterSearchFallsBackWhenIndexQueryIsCanceled(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
if err := os.WriteFile(filepath.Join(rootDir, "report.md"), []byte("report"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{searchProvider: failingLocalFileSearchProvider{err: context.Canceled}}
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "report",
Limit: 5,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 1 || result.Entries[0].Path != "/workspace/report.md" {
t.Fatalf("entries = %#v, want filesystem fallback result", result.Entries)
}
}
func TestLocalFilesAdapterSearchFallsBackFromEmptyIndexWithinSelectedDirectory(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
selectedDir := filepath.Join(rootDir, "tutti-file-change-repro-clean")
for _, relativePath := range []string{"report.md", ".hidden.md", "node_modules/ignored.md"} {
filePath := filepath.Join(selectedDir, relativePath)
if err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filePath, []byte("content"), 0o644); err != nil {
t.Fatal(err)
}
}
outsidePath := filepath.Join(rootDir, "outside", "report.md")
if err := os.MkdirAll(filepath.Dir(outsidePath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(outsidePath, []byte("outside"), 0o644); err != nil {
t.Fatal(err)
}
adapter := LocalFilesAdapter{searchProvider: emptyLocalFileSearchProvider{}}
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "re",
Limit: 5,
Within: "tutti-file-change-repro-clean",
Filters: []string{"document"},
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 1 || result.Entries[0].Path != "/workspace/tutti-file-change-repro-clean/report.md" {
t.Fatalf("entries = %#v, want only scoped visible document", result.Entries)
}
}
func TestLocalFilesAdapterSearchDoesNotMatchExplicitHiddenPathWhenFilenameDiffers(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
hiddenConfigPath := filepath.Join(rootDir, ".git", "config")
if err := os.MkdirAll(filepath.Dir(hiddenConfigPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(hiddenConfigPath, []byte("[core]\n"), 0o644); err != nil {
t.Fatal(err)
}
projectConfigPath := filepath.Join(rootDir, "project", "config")
if err := os.MkdirAll(filepath.Dir(projectConfigPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(projectConfigPath, []byte("project"), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(1)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: ".git/config",
Limit: 5,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 0 {
t.Fatalf("entries = %#v, want no path-only matches", result.Entries)
}
}
func TestLocalFilesAdapterSearchSkipsHiddenFilesForNormalQueries(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
hiddenEnvPath := filepath.Join(rootDir, ".env")
if err := os.WriteFile(hiddenEnvPath, []byte("SECRET=1\n"), 0o644); err != nil {
t.Fatal(err)
}
visibleEnvPath := filepath.Join(rootDir, "docs", "env.md")
if err := os.MkdirAll(filepath.Dir(visibleEnvPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(visibleEnvPath, []byte("env docs"), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(0)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "env",
Limit: 5,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
for _, entry := range result.Entries {
if entry.Path == "/workspace/.env" {
t.Fatalf("unexpected hidden file result %#v", entry)
}
}
if len(result.Entries) == 0 || result.Entries[0].Path != "/workspace/docs/env.md" {
t.Fatalf("entries = %#v, want visible env.md result first", result.Entries)
}
}
func TestLocalFilesAdapterSearchSkipsKnownTransientFiles(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
for name, content := range map[string]string{
"~$draft.docx": "office lock",
"draft.crdownload": "partial download",
"draft.docx": "visible document",
"~notes.md": "visible tilde file",
} {
if err := os.WriteFile(filepath.Join(rootDir, name), []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}
adapter := testLocalFilesAdapter(0)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "draft",
Limit: 10,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 1 || result.Entries[0].Path != "/workspace/draft.docx" {
t.Fatalf("entries = %#v, want only visible draft.docx", result.Entries)
}
tildeResult, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "notes",
Limit: 10,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() tilde file error = %v", err)
}
if len(tildeResult.Entries) != 1 || tildeResult.Entries[0].Path != "/workspace/~notes.md" {
t.Fatalf("entries = %#v, want ordinary tilde file", tildeResult.Entries)
}
}
func TestLocalFilesAdapterSearchSkipsHiddenFilesWhenQueryExplicitlyTargetsThemWithoutOptIn(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
hiddenEnvPath := filepath.Join(rootDir, ".env")
if err := os.WriteFile(hiddenEnvPath, []byte("SECRET=1\n"), 0o644); err != nil {
t.Fatal(err)
}
visibleEnvPath := filepath.Join(rootDir, "docs", "runtime.env")
if err := os.MkdirAll(filepath.Dir(visibleEnvPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(visibleEnvPath, []byte("VISIBLE=1\n"), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(0)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: ".env",
Limit: 5,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
for _, entry := range result.Entries {
if entry.Path == "/workspace/.env" {
t.Fatalf("unexpected hidden file result %#v", entry)
}
}
if len(result.Entries) != 1 || result.Entries[0].Path != "/workspace/docs/runtime.env" {
t.Fatalf("entries = %#v, want visible runtime.env only", result.Entries)
}
}
func TestLocalFilesAdapterSearchDoesNotDescendHiddenDirsForDotLiteralQuery(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
hiddenDmgPath := filepath.Join(rootDir, ".cache", "downloads", "googlechrome.dmg")
if err := os.MkdirAll(filepath.Dir(hiddenDmgPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(hiddenDmgPath, []byte("hidden dmg"), 0o644); err != nil {
t.Fatal(err)
}
visibleDmgPath := filepath.Join(rootDir, "Downloads", "googlechrome.dmg")
if err := os.MkdirAll(filepath.Dir(visibleDmgPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(visibleDmgPath, []byte("visible dmg"), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(0)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: ".dmg",
Limit: 5,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 1 {
t.Fatalf("entries = %#v, want one visible dmg result", result.Entries)
}
if result.Entries[0].Path != "/workspace/Downloads/googlechrome.dmg" {
t.Fatalf("first result = %#v, want visible dmg", result.Entries[0])
}
}
func TestLocalFilesAdapterSearchDoesNotDescendHiddenDirsForMultiTokenDotLiteralQuery(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
hiddenMatchPath := filepath.Join(rootDir, ".agents", "skills", "baoyu-slide-deck", "scripts", "merge-to-pdf.ts")
if err := os.MkdirAll(filepath.Dir(hiddenMatchPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(hiddenMatchPath, []byte("merge"), 0o644); err != nil {
t.Fatal(err)
}
visibleDmgPath := filepath.Join(rootDir, "Downloads", "googlechrome.dmg")
if err := os.MkdirAll(filepath.Dir(visibleDmgPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(visibleDmgPath, []byte("visible dmg"), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(1)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "chrome .dmg",
Limit: 5,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 1 {
t.Fatalf("entries = %#v, want one visible dmg result", result.Entries)
}
if result.Entries[0].Path != "/workspace/Downloads/googlechrome.dmg" {
t.Fatalf("first result = %#v, want visible dmg", result.Entries[0])
}
}
func TestLocalFilesAdapterSearchDoesNotDescendHiddenDirsForPathExtensionQuery(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
hiddenDmgPath := filepath.Join(rootDir, ".cache", "Downloads", "googlechrome.dmg")
if err := os.MkdirAll(filepath.Dir(hiddenDmgPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(hiddenDmgPath, []byte("hidden dmg"), 0o644); err != nil {
t.Fatal(err)
}
visibleDmgPath := filepath.Join(rootDir, "Downloads", "googlechrome.dmg")
if err := os.MkdirAll(filepath.Dir(visibleDmgPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(visibleDmgPath, []byte("visible dmg"), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(1)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: ".dmg",
Limit: 5,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) != 1 {
t.Fatalf("entries = %#v, want one visible dmg result", result.Entries)
}
if result.Entries[0].Path != "/workspace/Downloads/googlechrome.dmg" {
t.Fatalf("first result = %#v, want visible dmg", result.Entries[0])
}
}
func TestLocalFilesAdapterSearchIncludesHiddenFilesWhenIncludeHiddenIsTrue(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
hiddenEnvPath := filepath.Join(rootDir, ".env")
if err := os.WriteFile(hiddenEnvPath, []byte("SECRET=1\n"), 0o644); err != nil {
t.Fatal(err)
}
visibleEnvPath := filepath.Join(rootDir, "docs", "env.md")
if err := os.MkdirAll(filepath.Dir(visibleEnvPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(visibleEnvPath, []byte("env docs"), 0o644); err != nil {
t.Fatal(err)
}
adapter := testLocalFilesAdapter(0)
result, err := adapter.Search(context.Background(), localFilesRoot(rootDir), workspacefiles.SearchInput{
Query: "env",
Limit: 5,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
IncludeHidden: true,
})
if err != nil {
t.Fatalf("Search() error = %v", err)
}
if len(result.Entries) == 0 {
t.Fatal("expected search results when includeHidden is enabled")
}
paths := make([]workspacefiles.LogicalPath, 0, len(result.Entries))
for _, entry := range result.Entries {
paths = append(paths, entry.Path)
}
foundHidden := false
for _, resultPath := range paths {
if resultPath == "/workspace/.env" {
foundHidden = true
break
}
}
if !foundHidden {
t.Fatalf("paths = %#v, want hidden /workspace/.env result", paths)
}
}
func TestLocalFileSearchCandidatesFilterIndexedNoiseAsFallback(t *testing.T) {
t.Parallel()
rootDir := t.TempDir()
noisePath := filepath.Join(rootDir, "node_modules", "package", "index.ts")
buildPath := filepath.Join(rootDir, "build", "generated", "index.ts")
visiblePath := filepath.Join(rootDir, "src", "index.ts")
for _, candidatePath := range []string{noisePath, buildPath, visiblePath} {
if err := os.MkdirAll(filepath.Dir(candidatePath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(candidatePath, []byte("index"), 0o644); err != nil {
t.Fatal(err)
}
}
candidates, stats := localFileSearchCandidates(
rootDir,
rootDir,
[]string{noisePath, buildPath, visiblePath},
workspacefiles.SearchInput{IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile}},
)
if len(candidates) != 1 || candidates[0].RelativePath != "src/index.ts" {
t.Fatalf("candidates = %#v, want visible path only", candidates)
}
if stats.skippedIgnoredCount != 2 {
t.Fatalf("skippedIgnoredCount = %d, want 2", stats.skippedIgnoredCount)
}
candidates, _ = localFileSearchCandidates(
rootDir,
rootDir,
[]string{noisePath},
workspacefiles.SearchInput{
IncludeHidden: true,
IncludeKinds: []workspacefiles.EntryKind{workspacefiles.EntryKindFile},
},
)
if len(candidates) != 1 || candidates[0].RelativePath != "node_modules/package/index.ts" {
t.Fatalf("candidates = %#v, want noise path with IncludeHidden", candidates)
}
}
func localFilesRoot(rootDir string) workspacefiles.WorkspaceRoot {
return workspacefiles.WorkspaceRoot{
WorkspaceID: "ws-1",
LogicalRoot: "/workspace",
PhysicalRoot: rootDir,
}
}
func TestWorkspacePathVisibilityCacheReusesSharedAncestors(t *testing.T) {
rootDir := t.TempDir()
sharedDir := filepath.Join(rootDir, "shared")
if err := os.MkdirAll(sharedDir, 0o755); err != nil {
t.Fatal(err)
}
first := filepath.Join(sharedDir, "first.txt")
second := filepath.Join(sharedDir, "second.txt")
for _, path := range []string{first, second} {
if err := os.WriteFile(path, []byte("visible"), 0o644); err != nil {
t.Fatal(err)
}
}
cache := make(map[string]bool)
if shouldHideWorkspacePathCached(rootDir, first, cache) {
t.Fatal("first path unexpectedly hidden")
}
if got := len(cache); got != 2 {
t.Fatalf("cache entries after first candidate = %d, want shared ancestor and leaf", got)
}
if shouldHideWorkspacePathCached(rootDir, second, cache) {
t.Fatal("second path unexpectedly hidden")
}
if got := len(cache); got != 3 {
t.Fatalf("cache entries after sibling = %d, want one reused ancestor and two leaves", got)
}
}
func testLocalFilesAdapter(maxCandidates int) LocalFilesAdapter {
return LocalFilesAdapter{
MaxSearchCandidates: maxCandidates,
searchProvider: testFilesystemSearchProvider{},
}
}
func withLocalFilesRuntimeGOOS(t *testing.T, goos string) {
t.Helper()
previous := localFilesRuntimeGOOS
localFilesRuntimeGOOS = goos
t.Cleanup(func() {
localFilesRuntimeGOOS = previous
})
}