chore: housekeeping and refactoring (bump to 4.7.1)

- Remove unused parameters, dead files, and inaccurate doc.go files
- Extract shared helpers, eliminate duplication
- Rename cheatpath.Cheatpath to cheatpath.Path
- Optimize filesystem walks (WalkDir, skip .git)
- Move sheet name validation to sheet.Validate
- Move integration tests to test/integration/
- Consolidate internal/mock into mocks/
- Move fuzz.sh to test/
- Inline loadSheets helper into command callers
- Extract config.New into its own file
- Fix stale references in HACKING.md and CLAUDE.md
- Restore plan9 build target
- Remove redundant and low-value tests
- Clean up project documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christopher Allen Lane
2026-02-15 15:09:30 -05:00
parent d4a8a79628
commit 5ad1a3c39f
68 changed files with 605 additions and 1578 deletions

View File

@@ -0,0 +1,129 @@
package integration
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
// TestBriefFlagIntegration exercises the -b/--brief flag end-to-end.
func TestBriefFlagIntegration(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("integration test uses Unix-specific env vars")
}
// Build the cheat binary once for all sub-tests.
binPath := filepath.Join(t.TempDir(), "cheat_test")
build := exec.Command("go", "build", "-o", binPath, "./cmd/cheat")
build.Dir = repoRoot(t)
if output, err := build.CombinedOutput(); err != nil {
t.Fatalf("failed to build cheat: %v\nOutput: %s", err, output)
}
// Set up a temp environment with some cheatsheets.
root := t.TempDir()
sheetsDir := filepath.Join(root, "sheets")
os.MkdirAll(sheetsDir, 0755)
os.WriteFile(
filepath.Join(sheetsDir, "tar"),
[]byte("---\nsyntax: bash\ntags: [ compression ]\n---\ntar xf archive.tar\n"),
0644,
)
os.WriteFile(
filepath.Join(sheetsDir, "curl"),
[]byte("---\nsyntax: bash\ntags: [ networking, http ]\n---\ncurl https://example.com\n"),
0644,
)
confPath := filepath.Join(root, "conf.yml")
conf := fmt.Sprintf("---\neditor: vi\ncolorize: false\ncheatpaths:\n - name: test\n path: %s\n readonly: true\n", sheetsDir)
os.WriteFile(confPath, []byte(conf), 0644)
env := []string{
"CHEAT_CONFIG_PATH=" + confPath,
"HOME=" + root,
"PATH=" + os.Getenv("PATH"),
"EDITOR=vi",
}
run := func(t *testing.T, args ...string) string {
t.Helper()
cmd := exec.Command(binPath, args...)
cmd.Dir = root
cmd.Env = env
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("cheat %v failed: %v\nOutput: %s", args, err, output)
}
return string(output)
}
t.Run("brief output omits file path column", func(t *testing.T) {
output := run(t, "-b")
lines := strings.Split(strings.TrimSpace(output), "\n")
// Header should have title and tags but not file
if !strings.Contains(lines[0], "title:") {
t.Errorf("expected title: in header, got: %s", lines[0])
}
if !strings.Contains(lines[0], "tags:") {
t.Errorf("expected tags: in header, got: %s", lines[0])
}
if strings.Contains(lines[0], "file:") {
t.Errorf("brief output should not contain file: column, got: %s", lines[0])
}
// Data lines should not contain the sheets directory path
for _, line := range lines[1:] {
if strings.Contains(line, sheetsDir) {
t.Errorf("brief output should not contain file paths, got: %s", line)
}
}
})
t.Run("list output still includes file path column", func(t *testing.T) {
output := run(t, "-l")
lines := strings.Split(strings.TrimSpace(output), "\n")
if !strings.Contains(lines[0], "file:") {
t.Errorf("list output should contain file: column, got: %s", lines[0])
}
})
t.Run("brief with filter works", func(t *testing.T) {
output := run(t, "-b", "tar")
if !strings.Contains(output, "tar") {
t.Errorf("expected tar in output, got: %s", output)
}
if strings.Contains(output, "curl") {
t.Errorf("filter should exclude curl, got: %s", output)
}
})
t.Run("combined -lb works identically to -b", func(t *testing.T) {
briefOnly := run(t, "-b", "tar")
combined := run(t, "-lb", "tar")
if briefOnly != combined {
t.Errorf("-b and -lb should produce identical output\n-b:\n%s\n-lb:\n%s", briefOnly, combined)
}
})
t.Run("brief with tag filter works", func(t *testing.T) {
output := run(t, "-b", "-t", "networking")
if !strings.Contains(output, "curl") {
t.Errorf("expected curl in tag-filtered output, got: %s", output)
}
if strings.Contains(output, "tar") {
// tar is tagged "compression", not "networking"
t.Errorf("tag filter should exclude tar, got: %s", output)
}
if strings.Contains(output, "file:") {
t.Errorf("brief output should not contain file: column, got: %s", output)
}
})
}

View File

@@ -0,0 +1,246 @@
package integration
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
// hasCwdCheatpath checks whether the --directories output contains a
// cheatpath named "cwd". The output format is "name: path\n" per line
// (tabwriter-aligned), so we look for a line beginning with "cwd".
func hasCwdCheatpath(output string) bool {
for _, line := range strings.Split(output, "\n") {
if strings.HasPrefix(line, "cwd") {
return true
}
}
return false
}
// TestLocalCheatpathIntegration exercises the recursive .cheat directory
// discovery end-to-end: it builds the real cheat binary, sets up filesystem
// layouts, and verifies behaviour from the user's perspective.
func TestLocalCheatpathIntegration(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("integration test uses Unix-specific env vars")
}
// Build the cheat binary once for all sub-tests.
binPath := filepath.Join(t.TempDir(), "cheat_test")
build := exec.Command("go", "build", "-o", binPath, "./cmd/cheat")
build.Dir = repoRoot(t)
if output, err := build.CombinedOutput(); err != nil {
t.Fatalf("failed to build cheat: %v\nOutput: %s", err, output)
}
// cheatEnv returns a minimal environment for the cheat binary.
cheatEnv := func(confPath, home string) []string {
return []string{
"CHEAT_CONFIG_PATH=" + confPath,
"HOME=" + home,
"PATH=" + os.Getenv("PATH"),
"EDITOR=vi",
}
}
// writeConfig writes a minimal valid config file referencing sheetsDir.
writeConfig := func(t *testing.T, dir, sheetsDir string) string {
t.Helper()
conf := fmt.Sprintf("---\neditor: vi\ncolorize: false\ncheatpaths:\n - name: base\n path: %s\n readonly: true\n", sheetsDir)
confPath := filepath.Join(dir, "conf.yml")
if err := os.WriteFile(confPath, []byte(conf), 0644); err != nil {
t.Fatalf("failed to write config: %v", err)
}
return confPath
}
t.Run("parent .cheat is discovered from subdirectory", func(t *testing.T) {
root := t.TempDir()
// Configured cheatpath (empty but must exist for validation)
sheetsDir := filepath.Join(root, "sheets")
os.MkdirAll(sheetsDir, 0755)
// .cheat at root with a cheatsheet
dotCheat := filepath.Join(root, ".cheat")
os.Mkdir(dotCheat, 0755)
os.WriteFile(
filepath.Join(dotCheat, "localsheet"),
[]byte("---\nsyntax: bash\n---\necho hello from local\n"),
0644,
)
confPath := writeConfig(t, root, sheetsDir)
// Work from a subdirectory
workDir := filepath.Join(root, "src", "pkg")
os.MkdirAll(workDir, 0755)
env := cheatEnv(confPath, root)
// --directories should list "cwd" cheatpath
cmd := exec.Command(binPath, "--directories")
cmd.Dir = workDir
cmd.Env = env
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("cheat --directories failed: %v\nOutput: %s", err, output)
}
if !hasCwdCheatpath(string(output)) {
t.Errorf("expected 'cwd' cheatpath in --directories output:\n%s", output)
}
// Viewing the cheatsheet should show its content
cmd2 := exec.Command(binPath, "localsheet")
cmd2.Dir = workDir
cmd2.Env = env
output2, err := cmd2.CombinedOutput()
if err != nil {
t.Fatalf("cheat localsheet failed: %v\nOutput: %s", err, output2)
}
if !strings.Contains(string(output2), "echo hello from local") {
t.Errorf("expected cheatsheet content, got:\n%s", output2)
}
})
t.Run("grandparent .cheat is discovered from deep subdirectory", func(t *testing.T) {
root := t.TempDir()
sheetsDir := filepath.Join(root, "sheets")
os.MkdirAll(sheetsDir, 0755)
dotCheat := filepath.Join(root, ".cheat")
os.Mkdir(dotCheat, 0755)
os.WriteFile(
filepath.Join(dotCheat, "deepsheet"),
[]byte("---\nsyntax: bash\n---\ndeep discovery works\n"),
0644,
)
confPath := writeConfig(t, root, sheetsDir)
deepDir := filepath.Join(root, "a", "b", "c", "d", "e")
os.MkdirAll(deepDir, 0755)
cmd := exec.Command(binPath, "deepsheet")
cmd.Dir = deepDir
cmd.Env = cheatEnv(confPath, root)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("cheat deepsheet failed: %v\nOutput: %s", err, output)
}
if !strings.Contains(string(output), "deep discovery works") {
t.Errorf("expected cheatsheet content, got:\n%s", output)
}
})
t.Run("nearest .cheat wins over ancestor .cheat", func(t *testing.T) {
root := t.TempDir()
sheetsDir := filepath.Join(root, "sheets")
os.MkdirAll(sheetsDir, 0755)
// .cheat at root
rootCheat := filepath.Join(root, ".cheat")
os.Mkdir(rootCheat, 0755)
os.WriteFile(
filepath.Join(rootCheat, "shared"),
[]byte("---\nsyntax: bash\n---\nfrom root\n"),
0644,
)
// .cheat at project/ (nearer)
projectDir := filepath.Join(root, "project")
os.MkdirAll(projectDir, 0755)
projectCheat := filepath.Join(projectDir, ".cheat")
os.Mkdir(projectCheat, 0755)
os.WriteFile(
filepath.Join(projectCheat, "shared"),
[]byte("---\nsyntax: bash\n---\nfrom project nearest\n"),
0644,
)
confPath := writeConfig(t, root, sheetsDir)
workDir := filepath.Join(projectDir, "src")
os.MkdirAll(workDir, 0755)
env := cheatEnv(confPath, root)
// --directories should list the nearer cheatpath
cmd := exec.Command(binPath, "--directories")
cmd.Dir = workDir
cmd.Env = env
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("cheat --directories failed: %v\nOutput: %s", err, output)
}
if !strings.Contains(string(output), projectCheat) {
t.Errorf("expected project .cheat path in output, got:\n%s", output)
}
// "shared" sheet should come from the nearer .cheat
cmd2 := exec.Command(binPath, "shared")
cmd2.Dir = workDir
cmd2.Env = env
output2, err := cmd2.CombinedOutput()
if err != nil {
t.Fatalf("cheat shared failed: %v\nOutput: %s", err, output2)
}
if !strings.Contains(string(output2), "from project nearest") {
t.Errorf("expected nearest .cheat content, got:\n%s", output2)
}
})
t.Run("no .cheat directory means no cwd cheatpath", func(t *testing.T) {
root := t.TempDir()
sheetsDir := filepath.Join(root, "sheets")
os.MkdirAll(sheetsDir, 0755)
// Need at least one sheet for --directories to work without error
os.WriteFile(filepath.Join(sheetsDir, "placeholder"),
[]byte("---\nsyntax: bash\n---\nplaceholder\n"), 0644)
confPath := writeConfig(t, root, sheetsDir)
// No .cheat anywhere under root
cmd := exec.Command(binPath, "--directories")
cmd.Dir = root
cmd.Env = cheatEnv(confPath, root)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("cheat --directories failed: %v\nOutput: %s", err, output)
}
if hasCwdCheatpath(string(output)) {
t.Errorf("'cwd' cheatpath should not appear when no .cheat exists:\n%s", output)
}
})
t.Run(".cheat file (not directory) is ignored", func(t *testing.T) {
root := t.TempDir()
sheetsDir := filepath.Join(root, "sheets")
os.MkdirAll(sheetsDir, 0755)
os.WriteFile(filepath.Join(sheetsDir, "placeholder"),
[]byte("---\nsyntax: bash\n---\nplaceholder\n"), 0644)
// Create .cheat as a regular file
os.WriteFile(filepath.Join(root, ".cheat"), []byte("not a dir"), 0644)
confPath := writeConfig(t, root, sheetsDir)
cmd := exec.Command(binPath, "--directories")
cmd.Dir = root
cmd.Env = cheatEnv(confPath, root)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("cheat --directories failed: %v\nOutput: %s", err, output)
}
if hasCwdCheatpath(string(output)) {
t.Errorf("'cwd' should not appear for a .cheat file:\n%s", output)
}
})
}

View File

@@ -0,0 +1,305 @@
package integration
import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
// TestFirstRunIntegration exercises the end-to-end first-run experience:
// no config exists, the binary creates one, and subsequent runs succeed.
// This is the regression test for issues #721, #771, and #730.
func TestFirstRunIntegration(t *testing.T) {
// Build the cheat binary
binName := "cheat_test"
if runtime.GOOS == "windows" {
binName += ".exe"
}
binPath := filepath.Join(t.TempDir(), binName)
build := exec.Command("go", "build", "-o", binPath, "./cmd/cheat")
build.Dir = repoRoot(t)
if output, err := build.CombinedOutput(); err != nil {
t.Fatalf("failed to build cheat: %v\nOutput: %s", err, output)
}
t.Run("init comments out community", func(t *testing.T) {
testHome := t.TempDir()
env := firstRunEnv(testHome)
cmd := exec.Command(binPath, "--init")
cmd.Env = env
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("--init failed: %v\nOutput: %s", err, output)
}
outStr := string(output)
// No placeholder strings should survive (regression for #721)
assertNoPlaceholders(t, outStr)
// Community cheatpath should be commented out
assertCommunityCommentedOut(t, outStr)
// Personal and work cheatpaths should be active (uncommented)
assertCheatpathActive(t, outStr, "personal")
assertCheatpathActive(t, outStr, "work")
// Should include clone instructions
if !strings.Contains(outStr, "git clone") {
t.Error("expected git clone instructions in --init output")
}
// Save the config and verify it loads without errors.
// --init only outputs config, it doesn't create directories,
// so we need to create the cheatpath dirs the config references.
confpath := filepath.Join(testHome, "conf.yml")
if err := os.WriteFile(confpath, output, 0644); err != nil {
t.Fatalf("failed to write config: %v", err)
}
// Determine the confdir that --init used (same logic as cmd_init.go)
initConfpaths := firstRunConfpaths(testHome)
initConfdir := filepath.Dir(initConfpaths[0])
for _, name := range []string{"personal", "work"} {
dir := filepath.Join(initConfdir, "cheatsheets", name)
if err := os.MkdirAll(dir, 0755); err != nil {
t.Fatalf("failed to create %s dir: %v", name, err)
}
}
cmd2 := exec.Command(binPath, "--directories")
cmd2.Env = append(append([]string{}, env...), "CHEAT_CONFIG_PATH="+confpath)
output2, err := cmd2.CombinedOutput()
if err != nil {
t.Fatalf("config from --init failed to load: %v\nOutput: %s", err, output2)
}
})
t.Run("decline config creation", func(t *testing.T) {
testHome := t.TempDir()
env := firstRunEnv(testHome)
cmd := exec.Command(binPath)
cmd.Env = env
cmd.Stdin = strings.NewReader("n\n")
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("cheat exited with error: %v\nOutput: %s", err, output)
}
// Verify no config was created
if firstRunConfigExists(testHome) {
t.Error("config file was created despite user declining")
}
})
t.Run("accept config decline community", func(t *testing.T) {
testHome := t.TempDir()
env := firstRunEnv(testHome)
// First run: yes to create config, no to community cheatsheets
cmd := exec.Command(binPath)
cmd.Env = env
cmd.Stdin = strings.NewReader("y\nn\n")
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("first run failed: %v\nOutput: %s", err, output)
}
outStr := string(output)
// Parse the config path from output
confpath := parseCreatedConfPath(t, outStr)
if confpath == "" {
t.Fatalf("could not find config path in output:\n%s", outStr)
}
// Verify config file exists
if _, err := os.Stat(confpath); os.IsNotExist(err) {
t.Fatalf("config file not found at %s", confpath)
}
// Verify config file contents
content, err := os.ReadFile(confpath)
if err != nil {
t.Fatalf("failed to read config: %v", err)
}
contentStr := string(content)
// No placeholder strings should survive (regression for #721)
assertNoPlaceholders(t, contentStr)
// Community cheatpath should be commented out
assertCommunityCommentedOut(t, contentStr)
// Personal and work cheatpaths should be active (uncommented)
assertCheatpathActive(t, contentStr, "personal")
assertCheatpathActive(t, contentStr, "work")
// Verify personal and work directories were created
confdir := filepath.Dir(confpath)
for _, name := range []string{"personal", "work"} {
dir := filepath.Join(confdir, "cheatsheets", name)
if _, err := os.Stat(dir); os.IsNotExist(err) {
t.Errorf("expected %s directory at %s", name, dir)
}
}
// Community directory should NOT exist
communityDir := filepath.Join(confdir, "cheatsheets", "community")
if _, err := os.Stat(communityDir); err == nil {
t.Error("community directory should not exist when declined")
}
// --- Second run: verify the config loads successfully ---
// This is the core regression test for #721/#771/#730:
// previously, the second run would fail because config.New()
// hard-errored on the missing community cheatpath directory.
// Use --directories (not --list, which exits 2 when no sheets exist).
cmd2 := exec.Command(binPath, "--directories")
cmd2.Env = append(append([]string{}, env...), "CHEAT_CONFIG_PATH="+confpath)
output2, err := cmd2.CombinedOutput()
if err != nil {
t.Fatalf(
"second run failed (regression for #721/#771/#730): %v\nOutput: %s",
err, output2,
)
}
// Verify the output lists the expected cheatpaths
outStr2 := string(output2)
if !strings.Contains(outStr2, "personal") {
t.Errorf("expected 'personal' cheatpath in --directories output:\n%s", outStr2)
}
if !strings.Contains(outStr2, "work") {
t.Errorf("expected 'work' cheatpath in --directories output:\n%s", outStr2)
}
})
}
// firstRunEnv returns a minimal environment for a clean first-run test.
func firstRunEnv(home string) []string {
env := []string{
"PATH=" + os.Getenv("PATH"),
}
switch runtime.GOOS {
case "windows":
env = append(env,
"APPDATA="+filepath.Join(home, "AppData", "Roaming"),
"USERPROFILE="+home,
"SystemRoot="+os.Getenv("SystemRoot"),
)
default:
env = append(env,
"HOME="+home,
"EDITOR=vi",
)
}
return env
}
// parseCreatedConfPath extracts the config file path from the installer's
// "Created config file: <path>" output. The message may appear mid-line
// (after prompt text), so we search for the substring anywhere in the output.
func parseCreatedConfPath(t *testing.T, output string) string {
t.Helper()
const marker = "Created config file: "
idx := strings.Index(output, marker)
if idx < 0 {
return ""
}
rest := output[idx+len(marker):]
// the path ends at the next newline
if nl := strings.IndexByte(rest, '\n'); nl >= 0 {
rest = rest[:nl]
}
return strings.TrimSpace(rest)
}
// firstRunConfpaths returns the config file paths that cheat would check
// for the given home directory, matching the logic in config.Paths().
func firstRunConfpaths(home string) []string {
switch runtime.GOOS {
case "windows":
return []string{
filepath.Join(home, "AppData", "Roaming", "cheat", "conf.yml"),
}
default:
return []string{
filepath.Join(home, ".config", "cheat", "conf.yml"),
}
}
}
// assertNoPlaceholders verifies that no template placeholder strings survived
// in the config output. This is the regression check for #721 (literal
// PAGER_PATH appearing in the config).
func assertNoPlaceholders(t *testing.T, content string) {
t.Helper()
placeholders := []string{
"PAGER_PATH",
"COMMUNITY_PATH",
"PERSONAL_PATH",
"WORK_PATH",
}
for _, p := range placeholders {
if strings.Contains(content, p) {
t.Errorf("placeholder %q was not replaced in config", p)
}
}
// EDITOR_PATH is special: it survives if no editor is found.
// In our test env EDITOR=vi is set, so it should be replaced.
if strings.Contains(content, "editor: EDITOR_PATH") {
t.Error("placeholder EDITOR_PATH was not replaced in config")
}
}
// assertCommunityCommentedOut verifies that the community cheatpath entry
// is commented out (not active) in the config.
func assertCommunityCommentedOut(t *testing.T, content string) {
t.Helper()
for _, line := range strings.Split(content, "\n") {
trimmed := strings.TrimSpace(line)
if trimmed == "- name: community" {
t.Error("community cheatpath should be commented out")
return
}
}
if !strings.Contains(content, "#- name: community") {
t.Error("expected commented-out community cheatpath")
}
}
// assertCheatpathActive verifies that a named cheatpath is present and
// uncommented in the config.
func assertCheatpathActive(t *testing.T, content string, name string) {
t.Helper()
marker := "- name: " + name
for _, line := range strings.Split(content, "\n") {
trimmed := strings.TrimSpace(line)
if trimmed == marker {
return
}
}
t.Errorf("expected active (uncommented) cheatpath %q", name)
}
// firstRunConfigExists checks whether a cheat config file exists under the
// given home directory at any of the standard locations.
func firstRunConfigExists(home string) bool {
candidates := []string{
filepath.Join(home, ".config", "cheat", "conf.yml"),
filepath.Join(home, ".cheat", "conf.yml"),
filepath.Join(home, "AppData", "Roaming", "cheat", "conf.yml"),
}
for _, p := range candidates {
if _, err := os.Stat(p); err == nil {
return true
}
}
return false
}

View File

@@ -0,0 +1,30 @@
package integration
import (
"path/filepath"
"runtime"
"testing"
)
// repoRoot returns the absolute path to the repository root.
// It derives this from the known location of this source file
// (test/integration/) relative to the repo root.
func repoRoot(t *testing.T) string {
t.Helper()
_, file, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("failed to determine repo root via runtime.Caller")
}
// file is <repo>/test/integration/helpers_test.go → go up two dirs
return filepath.Dir(filepath.Dir(filepath.Dir(file)))
}
// repoRootBench is the same as repoRoot but for use in benchmarks.
func repoRootBench(b *testing.B) string {
b.Helper()
_, file, _, ok := runtime.Caller(0)
if !ok {
b.Fatal("failed to determine repo root via runtime.Caller")
}
return filepath.Dir(filepath.Dir(filepath.Dir(file)))
}

View File

@@ -0,0 +1,229 @@
package integration
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
// TestPathTraversalIntegration tests that the cheat binary properly blocks
// path traversal attempts when invoked as a subprocess.
func TestPathTraversalIntegration(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("integration test uses Unix-specific env and tools")
}
// Build the cheat binary
binPath := filepath.Join(t.TempDir(), "cheat_test")
build := exec.Command("go", "build", "-o", binPath, "./cmd/cheat")
build.Dir = repoRoot(t)
if output, err := build.CombinedOutput(); err != nil {
t.Fatalf("Failed to build cheat: %v\nOutput: %s", err, output)
}
// Set up test environment
testDir := t.TempDir()
sheetsDir := filepath.Join(testDir, "sheets")
os.MkdirAll(sheetsDir, 0755)
// Create config
config := fmt.Sprintf(`---
editor: echo
colorize: false
pager: cat
cheatpaths:
- name: test
path: %s
readonly: false
`, sheetsDir)
configPath := filepath.Join(testDir, "config.yml")
if err := os.WriteFile(configPath, []byte(config), 0644); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
// Test table
tests := []struct {
name string
command []string
wantFail bool
wantMsg string
}{
// Blocked patterns
{
name: "block parent traversal edit",
command: []string{"--edit", "../evil"},
wantFail: true,
wantMsg: "cannot contain '..'",
},
{
name: "block absolute path edit",
command: []string{"--edit", "/etc/passwd"},
wantFail: true,
wantMsg: "cannot be an absolute path",
},
{
name: "block home dir edit",
command: []string{"--edit", "~/.ssh/config"},
wantFail: true,
wantMsg: "cannot start with '~'",
},
{
name: "block parent traversal remove",
command: []string{"--rm", "../evil"},
wantFail: true,
wantMsg: "cannot contain '..'",
},
{
name: "block complex traversal",
command: []string{"--edit", "foo/../../bar"},
wantFail: true,
wantMsg: "cannot contain '..'",
},
{
name: "block just dots",
command: []string{"--edit", ".."},
wantFail: true,
wantMsg: "cannot contain '..'",
},
{
name: "block empty name",
command: []string{"--edit", ""},
wantFail: true,
wantMsg: "cannot be empty",
},
// Allowed patterns
{
name: "allow simple name",
command: []string{"--edit", "docker"},
wantFail: false,
},
{
name: "allow nested name",
command: []string{"--edit", "lang/go"},
wantFail: false,
},
{
name: "block hidden file",
command: []string{"--edit", ".gitignore"},
wantFail: true,
wantMsg: "cannot start with '.'",
},
{
name: "allow current dir",
command: []string{"--edit", "./local"},
wantFail: false,
},
}
// Run tests
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
cmd := exec.Command(binPath, tc.command...)
cmd.Env = []string{
fmt.Sprintf("CHEAT_CONFIG_PATH=%s", configPath),
fmt.Sprintf("HOME=%s", testDir),
}
output, err := cmd.CombinedOutput()
if tc.wantFail {
if err == nil {
t.Errorf("Expected failure but command succeeded. Output: %s", output)
}
if !strings.Contains(string(output), "invalid cheatsheet name") {
t.Errorf("Expected 'invalid cheatsheet name' error, got: %s", output)
}
if tc.wantMsg != "" && !strings.Contains(string(output), tc.wantMsg) {
t.Errorf("Expected message %q in output, got: %s", tc.wantMsg, output)
}
} else {
// Command might fail for other reasons (e.g., editor not found)
// but should NOT fail with "invalid cheatsheet name"
if strings.Contains(string(output), "invalid cheatsheet name") {
t.Errorf("Command incorrectly blocked. Output: %s", output)
}
}
})
}
}
// TestPathTraversalRealWorld tests with more realistic scenarios
func TestPathTraversalRealWorld(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("integration test uses Unix-specific env and tools")
}
// This test ensures our protection works with actual file operations
// Build cheat
binPath := filepath.Join(t.TempDir(), "cheat_test")
build := exec.Command("go", "build", "-o", binPath, "./cmd/cheat")
build.Dir = repoRoot(t)
if output, err := build.CombinedOutput(); err != nil {
t.Fatalf("Failed to build: %v\n%s", err, output)
}
// Create test structure
testRoot := t.TempDir()
sheetsDir := filepath.Join(testRoot, "cheatsheets")
secretDir := filepath.Join(testRoot, "secrets")
os.MkdirAll(sheetsDir, 0755)
os.MkdirAll(secretDir, 0755)
// Create a "secret" file that should not be accessible
secretFile := filepath.Join(secretDir, "secret.txt")
os.WriteFile(secretFile, []byte("SECRET DATA"), 0644)
// Create config using vim in non-interactive mode
config := fmt.Sprintf(`---
editor: vim -u NONE -n --cmd "set noswapfile" --cmd "wq"
colorize: false
pager: cat
cheatpaths:
- name: personal
path: %s
readonly: false
`, sheetsDir)
configPath := filepath.Join(testRoot, "config.yml")
os.WriteFile(configPath, []byte(config), 0644)
// Test 1: Try to edit a file outside cheatsheets using traversal
cmd := exec.Command(binPath, "--edit", "../secrets/secret")
cmd.Env = []string{
fmt.Sprintf("CHEAT_CONFIG_PATH=%s", configPath),
fmt.Sprintf("HOME=%s", testRoot),
}
output, err := cmd.CombinedOutput()
if err == nil || !strings.Contains(string(output), "invalid cheatsheet name") {
t.Errorf("Path traversal was not blocked! Output: %s", output)
}
// Test 2: Verify the secret file is still intact
content, _ := os.ReadFile(secretFile)
if string(content) != "SECRET DATA" {
t.Errorf("Secret file was modified!")
}
// Test 3: Verify no files were created outside sheets directory
err = filepath.Walk(testRoot, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() &&
path != configPath &&
path != secretFile &&
!strings.HasPrefix(path, sheetsDir) {
t.Errorf("File created outside allowed directory: %s", path)
}
return nil
})
if err != nil {
t.Errorf("Walk error: %v", err)
}
}

View File

@@ -0,0 +1,196 @@
//go:build integration
package integration
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
// BenchmarkSearchCommand benchmarks the actual cheat search command
func BenchmarkSearchCommand(b *testing.B) {
root := repoRootBench(b)
// Build the cheat binary in .tmp (using absolute path)
tmpDir := filepath.Join(root, ".tmp", "bench-test")
if err := os.MkdirAll(tmpDir, 0755); err != nil {
b.Fatalf("Failed to create temp dir: %v", err)
}
cheatBin := filepath.Join(tmpDir, "cheat-bench")
// Clean up the binary when done
b.Cleanup(func() {
os.Remove(cheatBin)
})
cmd := exec.Command("go", "build", "-o", cheatBin, "./cmd/cheat")
cmd.Dir = root
if output, err := cmd.CombinedOutput(); err != nil {
b.Fatalf("Failed to build cheat: %v\nOutput: %s", err, output)
}
// Set up test environment in .tmp
configDir := filepath.Join(tmpDir, "config")
cheatsheetDir := filepath.Join(configDir, "cheatsheets", "community")
// Clone community cheatsheets (or reuse if already exists)
if _, err := os.Stat(cheatsheetDir); os.IsNotExist(err) {
b.Logf("Cloning community cheatsheets to %s...", cheatsheetDir)
_, err := git.PlainClone(cheatsheetDir, false, &git.CloneOptions{
URL: "https://github.com/cheat/cheatsheets.git",
Depth: 1,
SingleBranch: true,
ReferenceName: plumbing.ReferenceName("refs/heads/master"),
Progress: nil,
})
if err != nil {
b.Fatalf("Failed to clone cheatsheets: %v", err)
}
}
// Create a minimal config file
configFile := filepath.Join(configDir, "conf.yml")
configContent := fmt.Sprintf(`---
cheatpaths:
- name: community
path: %s
tags: [ community ]
readonly: true
`, cheatsheetDir)
if err := os.MkdirAll(configDir, 0755); err != nil {
b.Fatalf("Failed to create config dir: %v", err)
}
if err := os.WriteFile(configFile, []byte(configContent), 0644); err != nil {
b.Fatalf("Failed to write config: %v", err)
}
// Set environment to use our config
env := append(os.Environ(),
fmt.Sprintf("CHEAT_CONFIG_PATH=%s", configFile),
)
// Define test cases
testCases := []struct {
name string
args []string
}{
{"SimpleSearch", []string{"-s", "echo"}},
{"RegexSearch", []string{"-r", "-s", "^#.*example"}},
{"ColorizedSearch", []string{"-c", "-s", "grep"}},
{"ComplexRegex", []string{"-r", "-s", "(git|hg|svn)\\s+(add|commit|push)"}},
{"AllCheatpaths", []string{"-a", "-s", "list"}},
}
// Warm up - run once to ensure everything is loaded
warmupCmd := exec.Command(cheatBin, "-l")
warmupCmd.Env = env
warmupCmd.Run()
// Run benchmarks
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
// Reset timer to exclude setup
b.ResetTimer()
for i := 0; i < b.N; i++ {
cmd := exec.Command(cheatBin, tc.args...)
cmd.Env = env
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
b.Fatalf("Command failed: %v\nStderr: %s", err, stderr.String())
}
if stdout.Len() == 0 {
b.Fatal("No output from search")
}
}
})
}
}
// BenchmarkListCommand benchmarks the list command for comparison
func BenchmarkListCommand(b *testing.B) {
root := repoRootBench(b)
// Build the cheat binary in .tmp (using absolute path)
tmpDir := filepath.Join(root, ".tmp", "bench-test")
if err := os.MkdirAll(tmpDir, 0755); err != nil {
b.Fatalf("Failed to create temp dir: %v", err)
}
cheatBin := filepath.Join(tmpDir, "cheat-bench")
// Clean up the binary when done
b.Cleanup(func() {
os.Remove(cheatBin)
})
cmd := exec.Command("go", "build", "-o", cheatBin, "./cmd/cheat")
cmd.Dir = root
if output, err := cmd.CombinedOutput(); err != nil {
b.Fatalf("Failed to build cheat: %v\nOutput: %s", err, output)
}
// Set up test environment (simplified - reuse if possible)
configDir := filepath.Join(tmpDir, "config")
cheatsheetDir := filepath.Join(configDir, "cheatsheets", "community")
// Check if we need to clone
if _, err := os.Stat(cheatsheetDir); os.IsNotExist(err) {
_, err := git.PlainClone(cheatsheetDir, false, &git.CloneOptions{
URL: "https://github.com/cheat/cheatsheets.git",
Depth: 1,
SingleBranch: true,
ReferenceName: plumbing.ReferenceName("refs/heads/master"),
Progress: nil,
})
if err != nil {
b.Fatalf("Failed to clone cheatsheets: %v", err)
}
}
// Create config
configFile := filepath.Join(configDir, "conf.yml")
configContent := fmt.Sprintf(`---
cheatpaths:
- name: community
path: %s
tags: [ community ]
readonly: true
`, cheatsheetDir)
os.MkdirAll(configDir, 0755)
os.WriteFile(configFile, []byte(configContent), 0644)
env := append(os.Environ(),
fmt.Sprintf("CHEAT_CONFIG_PATH=%s", configFile),
)
b.ResetTimer()
for i := 0; i < b.N; i++ {
cmd := exec.Command(cheatBin, "-l")
cmd.Env = env
var stdout bytes.Buffer
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
b.Fatalf("Command failed: %v", err)
}
}
}