fix(mcp): rename tests to AX-10 TestFilename_Function_{Good,Bad,Ugly} (AX-10)

Refactored pkg/mcp/mcp_test.go to match AX-10 naming: every test
function now ends in _Good (happy path), _Bad (error path / expected
failure), or _Ugly (edge case). Added missing variants across covered
groups: New, GetSupportedLanguages, DetectLanguageFromPath, Medium,
FileExists, ListDirectory, ResolveWorkspacePath.

Closes tasks.lthn.sh/view.php?id=199

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Codex 2026-04-24 21:31:09 +01:00 committed by Snider
parent 633f295244
commit 96fd169239

View file

@ -6,100 +6,152 @@ import (
"testing"
)
func TestNew_Good_DefaultWorkspace(t *testing.T) {
func TestMCP_New_Good(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get working directory: %v", err)
}
s, err := New(Options{})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
if s.workspaceRoot != cwd {
t.Errorf("Expected default workspace root %s, got %s", cwd, s.workspaceRoot)
}
if s.medium == nil {
t.Error("Expected medium to be set")
}
}
func TestNew_Good_CustomWorkspace(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
if s.workspaceRoot != tmpDir {
t.Errorf("Expected workspace root %s, got %s", tmpDir, s.workspaceRoot)
}
if s.medium == nil {
t.Error("Expected medium to be set")
}
}
func TestNew_Good_NoRestriction(t *testing.T) {
s, err := New(Options{Unrestricted: true})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
if s.workspaceRoot != "" {
t.Errorf("Expected empty workspace root, got %s", s.workspaceRoot)
}
if s.medium == nil {
t.Error("Expected medium to be set (unsandboxed)")
}
}
func TestNew_Good_RegistersBuiltInTools(t *testing.T) {
s, err := New(Options{})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
tools := map[string]bool{}
for _, rec := range s.Tools() {
tools[rec.Name] = true
}
for _, name := range []string{
"metrics_record",
"metrics_query",
"rag_query",
"rag_ingest",
"rag_collections",
"webview_connect",
"webview_disconnect",
"webview_navigate",
"webview_click",
"webview_type",
"webview_query",
"webview_console",
"webview_eval",
"webview_screenshot",
"webview_wait",
} {
if !tools[name] {
t.Fatalf("expected tool %q to be registered", name)
t.Run("default workspace", func(t *testing.T) {
s, err := New(Options{})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
}
for _, name := range []string{"process_start", "ws_start"} {
if tools[name] {
t.Fatalf("did not expect tool %q to be registered without dependencies", name)
if s.workspaceRoot != cwd {
t.Errorf("Expected default workspace root %s, got %s", cwd, s.workspaceRoot)
}
if s.medium == nil {
t.Error("Expected medium to be set")
}
})
t.Run("custom workspace", func(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
if s.workspaceRoot != tmpDir {
t.Errorf("Expected workspace root %s, got %s", tmpDir, s.workspaceRoot)
}
if s.medium == nil {
t.Error("Expected medium to be set")
}
})
t.Run("built in tools", func(t *testing.T) {
s, err := New(Options{})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
tools := map[string]bool{}
for _, rec := range s.Tools() {
tools[rec.Name] = true
}
for _, name := range []string{
"file_read",
"file_write",
"file_delete",
"file_rename",
"file_exists",
"file_edit",
"dir_list",
"dir_create",
"lang_detect",
"lang_list",
"metrics_record",
"metrics_query",
"rag_query",
"rag_ingest",
"rag_collections",
"webview_connect",
"webview_disconnect",
"webview_navigate",
"webview_click",
"webview_type",
"webview_query",
"webview_console",
"webview_eval",
"webview_screenshot",
"webview_wait",
} {
if !tools[name] {
t.Fatalf("expected tool %q to be registered", name)
}
}
for _, name := range []string{"process_start", "ws_start"} {
if tools[name] {
t.Fatalf("did not expect tool %q to be registered without dependencies", name)
}
}
})
}
func TestMCP_New_Bad(t *testing.T) {
s, err := New(Options{Subsystems: []Subsystem{nil}})
if err != nil {
t.Fatalf("Failed to create service with nil subsystem entry: %v", err)
}
if got := len(s.Subsystems()); got != 0 {
t.Fatalf("expected nil subsystem entry to be ignored, got %d subsystem(s)", got)
}
}
func TestGetSupportedLanguages_Good_IncludesAllDetectedLanguages(t *testing.T) {
s, err := New(Options{})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
func TestMCP_New_Ugly(t *testing.T) {
t.Run("unrestricted ignores workspace root", func(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir, Unrestricted: true})
if err != nil {
t.Fatalf("Failed to create unrestricted service: %v", err)
}
if s.workspaceRoot != "" {
t.Errorf("Expected empty workspace root, got %s", s.workspaceRoot)
}
if s.medium == nil {
t.Error("Expected medium to be set")
}
})
t.Run("relative workspace root is made absolute", func(t *testing.T) {
oldWD, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get working directory: %v", err)
}
tmpDir := t.TempDir()
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Failed to chdir: %v", err)
}
t.Cleanup(func() {
if err := os.Chdir(oldWD); err != nil {
t.Fatalf("Failed to restore working directory: %v", err)
}
})
s, err := New(Options{WorkspaceRoot: "."})
if err != nil {
t.Fatalf("Failed to create service with relative workspace: %v", err)
}
want, err := filepath.Abs(".")
if err != nil {
t.Fatalf("Failed to resolve expected workspace root: %v", err)
}
if s.workspaceRoot != want {
t.Fatalf("expected relative workspace root %q to resolve to %q", s.workspaceRoot, want)
}
})
}
func TestMCP_GetSupportedLanguages_Good(t *testing.T) {
s := newTestMCPService(t)
_, out, err := s.getSupportedLanguages(nil, nil, GetSupportedLanguagesInput{})
if err != nil {
@ -146,7 +198,69 @@ func TestGetSupportedLanguages_Good_IncludesAllDetectedLanguages(t *testing.T) {
}
}
func TestDetectLanguageFromPath_Good_KnownExtensions(t *testing.T) {
func TestMCP_GetSupportedLanguages_Bad(t *testing.T) {
s := newTestMCPService(t)
_, out, err := s.getSupportedLanguages(nil, nil, GetSupportedLanguagesInput{})
if err != nil {
t.Fatalf("getSupportedLanguages failed: %v", err)
}
ids := map[string]bool{}
extensions := map[string]string{}
for _, lang := range out.Languages {
if lang.ID == "" {
t.Fatal("supported language has empty ID")
}
if lang.Name == "" {
t.Fatalf("supported language %q has empty display name", lang.ID)
}
if ids[lang.ID] {
t.Fatalf("duplicate supported language ID %q", lang.ID)
}
ids[lang.ID] = true
for _, ext := range lang.Extensions {
if ext == "" {
t.Fatalf("language %q has empty extension", lang.ID)
}
if ext[0] != '.' {
t.Fatalf("language %q has extension %q without dot prefix", lang.ID, ext)
}
if got := languageByExtension[ext]; got != lang.ID {
t.Fatalf("extension %q maps to %q, want %q", ext, got, lang.ID)
}
if owner, ok := extensions[ext]; ok {
t.Fatalf("extension %q is registered for both %q and %q", ext, owner, lang.ID)
}
extensions[ext] = lang.ID
}
}
}
func TestMCP_GetSupportedLanguages_Ugly(t *testing.T) {
s := newTestMCPService(t)
_, out, err := s.getSupportedLanguages(nil, nil, GetSupportedLanguagesInput{})
if err != nil {
t.Fatalf("getSupportedLanguages failed: %v", err)
}
out.Languages[0].ID = "mutated"
out.Languages[0].Extensions[0] = ".mutated"
_, fresh, err := s.getSupportedLanguages(nil, nil, GetSupportedLanguagesInput{})
if err != nil {
t.Fatalf("getSupportedLanguages failed after caller mutation: %v", err)
}
if fresh.Languages[0].ID != "typescript" {
t.Fatalf("caller mutation leaked into fresh language list: %q", fresh.Languages[0].ID)
}
if fresh.Languages[0].Extensions[0] != ".ts" {
t.Fatalf("caller mutation leaked into fresh extension list: %q", fresh.Languages[0].Extensions[0])
}
}
func TestMCP_DetectLanguageFromPath_Good(t *testing.T) {
cases := map[string]string{
"main.go": "go",
"index.tsx": "typescript",
@ -163,66 +277,150 @@ func TestDetectLanguageFromPath_Good_KnownExtensions(t *testing.T) {
}
}
func TestMedium_Good_ReadWrite(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
// Write a file
testContent := "hello world"
err = s.medium.Write("test.txt", testContent)
if err != nil {
t.Fatalf("Failed to write file: %v", err)
}
// Read it back
content, err := s.medium.Read("test.txt")
if err != nil {
t.Fatalf("Failed to read file: %v", err)
}
if content != testContent {
t.Errorf("Expected content %q, got %q", testContent, content)
}
// Verify file exists on disk
diskPath := filepath.Join(tmpDir, "test.txt")
if _, err := os.Stat(diskPath); os.IsNotExist(err) {
t.Error("File should exist on disk")
func TestMCP_DetectLanguageFromPath_Bad(t *testing.T) {
for _, path := range []string{"notes.unknown", "Makefile", "dockerfile"} {
if got := detectLanguageFromPath(path); got != "plaintext" {
t.Fatalf("detectLanguageFromPath(%q) = %q, want plaintext", path, got)
}
}
}
func TestMedium_Good_EnsureDir(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
func TestMCP_DetectLanguageFromPath_Ugly(t *testing.T) {
cases := map[string]string{
"": "plaintext",
".gitignore": "plaintext",
"archive.tar.gz": "plaintext",
"nested/.config/app.yaml": "yaml",
}
err = s.medium.EnsureDir("subdir/nested")
if err != nil {
t.Fatalf("Failed to create directory: %v", err)
}
// Verify directory exists
diskPath := filepath.Join(tmpDir, "subdir", "nested")
info, err := os.Stat(diskPath)
if os.IsNotExist(err) {
t.Error("Directory should exist on disk")
}
if err == nil && !info.IsDir() {
t.Error("Path should be a directory")
for path, want := range cases {
if got := detectLanguageFromPath(path); got != want {
t.Fatalf("detectLanguageFromPath(%q) = %q, want %q", path, got, want)
}
}
}
func TestFileExists_Good_FileAndDirectory(t *testing.T) {
func TestMCP_Medium_Good(t *testing.T) {
t.Run("read write", func(t *testing.T) {
tmpDir := t.TempDir()
s := newTestMCPServiceWithRoot(t, tmpDir)
testContent := "hello world"
if err := s.medium.Write("test.txt", testContent); err != nil {
t.Fatalf("Failed to write file: %v", err)
}
content, err := s.medium.Read("test.txt")
if err != nil {
t.Fatalf("Failed to read file: %v", err)
}
if content != testContent {
t.Errorf("Expected content %q, got %q", testContent, content)
}
diskPath := filepath.Join(tmpDir, "test.txt")
if _, err := os.Stat(diskPath); os.IsNotExist(err) {
t.Error("File should exist on disk")
}
})
t.Run("ensure dir", func(t *testing.T) {
tmpDir := t.TempDir()
s := newTestMCPServiceWithRoot(t, tmpDir)
if err := s.medium.EnsureDir("subdir/nested"); err != nil {
t.Fatalf("Failed to create directory: %v", err)
}
diskPath := filepath.Join(tmpDir, "subdir", "nested")
info, err := os.Stat(diskPath)
if os.IsNotExist(err) {
t.Error("Directory should exist on disk")
}
if err == nil && !info.IsDir() {
t.Error("Path should be a directory")
}
})
t.Run("is file", func(t *testing.T) {
s := newTestMCPService(t)
if s.medium.IsFile("test.txt") {
t.Error("File should not exist yet")
}
if err := s.medium.Write("test.txt", "content"); err != nil {
t.Fatalf("Failed to write file: %v", err)
}
if !s.medium.IsFile("test.txt") {
t.Error("File should exist after write")
}
})
}
func TestMCP_Medium_Bad(t *testing.T) {
t.Run("missing read", func(t *testing.T) {
s := newTestMCPService(t)
if _, err := s.medium.Read("missing.txt"); err == nil {
t.Fatal("expected missing file read to fail")
}
})
t.Run("file blocks directory creation", func(t *testing.T) {
s := newTestMCPService(t)
if err := s.medium.Write("already-file", "content"); err != nil {
t.Fatalf("Failed to write file: %v", err)
}
if err := s.medium.EnsureDir("already-file"); err == nil {
t.Fatal("expected directory creation over a file to fail")
}
})
t.Run("symlink escape blocked", func(t *testing.T) {
tmpDir := t.TempDir()
outsideDir := t.TempDir()
targetFile := filepath.Join(outsideDir, "secret.txt")
if err := os.WriteFile(targetFile, []byte("secret"), 0644); err != nil {
t.Fatalf("Failed to create target file: %v", err)
}
symlinkPath := filepath.Join(tmpDir, "link")
if err := os.Symlink(targetFile, symlinkPath); err != nil {
t.Skipf("Symlinks not supported: %v", err)
}
s := newTestMCPServiceWithRoot(t, tmpDir)
if _, err := s.medium.Read("link"); err == nil {
t.Error("Expected permission denied for symlink escaping sandbox, but read succeeded")
}
})
}
func TestMCP_Medium_Ugly(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
s := newTestMCPServiceWithRoot(t, tmpDir)
if err := s.medium.Write("../notes.txt", "inside workspace"); err != nil {
t.Fatalf("Failed to write traversal path: %v", err)
}
content, err := os.ReadFile(filepath.Join(tmpDir, "notes.txt"))
if err != nil {
t.Fatalf("expected traversal path to be sanitized inside workspace: %v", err)
}
if string(content) != "inside workspace" {
t.Fatalf("expected sanitized traversal content, got %q", content)
}
}
func TestMCP_FileExists_Good(t *testing.T) {
s := newTestMCPService(t)
if err := s.medium.EnsureDir("nested"); err != nil {
t.Fatalf("Failed to create directory: %v", err)
}
@ -253,12 +451,38 @@ func TestFileExists_Good_FileAndDirectory(t *testing.T) {
}
}
func TestListDirectory_Good_ReturnsDocumentedEntryPaths(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir})
func TestMCP_FileExists_Bad(t *testing.T) {
s := newTestMCPService(t)
_, out, err := s.fileExists(nil, nil, FileExistsInput{Path: "missing.txt"})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
t.Fatalf("fileExists(missing) failed: %v", err)
}
if out.Exists {
t.Fatal("expected missing file to not exist")
}
if out.IsDir {
t.Fatal("expected missing file to not be reported as a directory")
}
}
func TestMCP_FileExists_Ugly(t *testing.T) {
s := newTestMCPService(t)
_, out, err := s.fileExists(nil, nil, FileExistsInput{Path: ""})
if err != nil {
t.Fatalf("fileExists(empty path) failed: %v", err)
}
if !out.Exists {
t.Fatal("expected empty path to resolve to existing workspace root")
}
if !out.IsDir {
t.Fatal("expected empty path to report workspace root as a directory")
}
}
func TestMCP_ListDirectory_Good(t *testing.T) {
s := newTestMCPService(t)
if err := s.medium.EnsureDir("nested"); err != nil {
t.Fatalf("Failed to create directory: %v", err)
@ -281,39 +505,54 @@ func TestListDirectory_Good_ReturnsDocumentedEntryPaths(t *testing.T) {
}
}
func TestMedium_Good_IsFile(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
func TestMCP_ListDirectory_Bad(t *testing.T) {
s := newTestMCPService(t)
// File doesn't exist yet
if s.medium.IsFile("test.txt") {
t.Error("File should not exist yet")
}
// Create the file
_ = s.medium.Write("test.txt", "content")
// Now it should exist
if !s.medium.IsFile("test.txt") {
t.Error("File should exist after write")
if _, _, err := s.listDirectory(nil, nil, ListDirectoryInput{Path: "missing"}); err == nil {
t.Fatal("expected missing directory list to fail")
}
}
func TestResolveWorkspacePath_Good(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
func TestMCP_ListDirectory_Ugly(t *testing.T) {
s := newTestMCPService(t)
if err := s.medium.Write("z.txt", "z"); err != nil {
t.Fatalf("Failed to write z.txt: %v", err)
}
if err := s.medium.Write("a.txt", "a"); err != nil {
t.Fatalf("Failed to write a.txt: %v", err)
}
if err := s.medium.EnsureDir("dir"); err != nil {
t.Fatalf("Failed to create directory: %v", err)
}
_, out, err := s.listDirectory(nil, nil, ListDirectoryInput{Path: ""})
if err != nil {
t.Fatalf("listDirectory(root) failed: %v", err)
}
got := make([]string, 0, len(out.Entries))
for _, entry := range out.Entries {
got = append(got, entry.Path)
}
want := []string{"a.txt", "dir", "z.txt"}
if len(got) != len(want) {
t.Fatalf("expected entries %v, got %v", want, got)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("expected entries %v, got %v", want, got)
}
}
}
func TestMCP_ResolveWorkspacePath_Good(t *testing.T) {
tmpDir := t.TempDir()
s := newTestMCPServiceWithRoot(t, tmpDir)
cases := map[string]string{
"docs/readme.md": filepath.Join(tmpDir, "docs", "readme.md"),
"/docs/readme.md": filepath.Join(tmpDir, "docs", "readme.md"),
"../escape/notes.md": filepath.Join(tmpDir, "escape", "notes.md"),
"": "",
"docs/readme.md": filepath.Join(tmpDir, "docs", "readme.md"),
"/docs/readme.md": filepath.Join(tmpDir, "docs", "readme.md"),
}
for input, want := range cases {
if got := s.resolveWorkspacePath(input); got != want {
@ -322,66 +561,52 @@ func TestResolveWorkspacePath_Good(t *testing.T) {
}
}
func TestResolveWorkspacePath_Good_Unrestricted(t *testing.T) {
s, err := New(Options{Unrestricted: true})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
if got, want := s.resolveWorkspacePath("docs/readme.md"), filepath.Clean("docs/readme.md"); got != want {
t.Fatalf("resolveWorkspacePath(relative) = %q, want %q", got, want)
}
if got, want := s.resolveWorkspacePath("/tmp/readme.md"), filepath.Clean("/tmp/readme.md"); got != want {
t.Fatalf("resolveWorkspacePath(absolute) = %q, want %q", got, want)
}
}
func TestSandboxing_Traversal_Sanitized(t *testing.T) {
func TestMCP_ResolveWorkspacePath_Bad(t *testing.T) {
tmpDir := t.TempDir()
s, err := New(Options{WorkspaceRoot: tmpDir})
s := newTestMCPServiceWithRoot(t, tmpDir)
got := s.resolveWorkspacePath("../escape/notes.md")
want := filepath.Join(tmpDir, "escape", "notes.md")
if got != want {
t.Fatalf("resolveWorkspacePath(traversal) = %q, want %q", got, want)
}
}
func TestMCP_ResolveWorkspacePath_Ugly(t *testing.T) {
t.Run("empty path", func(t *testing.T) {
s := newTestMCPService(t)
if got := s.resolveWorkspacePath(""); got != "" {
t.Fatalf("resolveWorkspacePath(empty) = %q, want empty", got)
}
})
t.Run("unrestricted", func(t *testing.T) {
s, err := New(Options{Unrestricted: true})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
if got, want := s.resolveWorkspacePath("docs/readme.md"), filepath.Clean("docs/readme.md"); got != want {
t.Fatalf("resolveWorkspacePath(relative) = %q, want %q", got, want)
}
if got, want := s.resolveWorkspacePath("/tmp/readme.md"), filepath.Clean("/tmp/readme.md"); got != want {
t.Fatalf("resolveWorkspacePath(absolute) = %q, want %q", got, want)
}
})
}
func newTestMCPService(t *testing.T) *Service {
t.Helper()
return newTestMCPServiceWithRoot(t, t.TempDir())
}
func newTestMCPServiceWithRoot(t *testing.T, root string) *Service {
t.Helper()
s, err := New(Options{WorkspaceRoot: root})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
// Path traversal is sanitized (.. becomes .), so ../secret.txt becomes
// ./secret.txt in the workspace. Since that file doesn't exist, we get
// a file not found error (not a traversal error).
_, err = s.medium.Read("../secret.txt")
if err == nil {
t.Error("Expected error (file not found)")
}
// Absolute paths are allowed through - they access the real filesystem.
// This is intentional for full filesystem access. Callers wanting sandboxing
// should validate inputs before calling Medium.
}
func TestSandboxing_Symlinks_Blocked(t *testing.T) {
tmpDir := t.TempDir()
outsideDir := t.TempDir()
// Create a target file outside workspace
targetFile := filepath.Join(outsideDir, "secret.txt")
if err := os.WriteFile(targetFile, []byte("secret"), 0644); err != nil {
t.Fatalf("Failed to create target file: %v", err)
}
// Create symlink inside workspace pointing outside
symlinkPath := filepath.Join(tmpDir, "link")
if err := os.Symlink(targetFile, symlinkPath); err != nil {
t.Skipf("Symlinks not supported: %v", err)
}
s, err := New(Options{WorkspaceRoot: tmpDir})
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
// Symlinks pointing outside the sandbox root are blocked (security feature).
// The sandbox resolves the symlink target and rejects it because it escapes
// the workspace boundary.
_, err = s.medium.Read("link")
if err == nil {
t.Error("Expected permission denied for symlink escaping sandbox, but read succeeded")
}
return s
}