mirror of
https://github.com/cheat/cheat.git
synced 2026-03-07 03:03:32 +01:00
chore: bump version to 4.5.0
Bug fixes: - Fix inverted pager detection logic (returned error instead of path) - Fix repo.Clone ignoring destination directory parameter - Fix sheet loading using append on pre-sized slices - Clean up partial files on copy failure - Trim whitespace from editor config Security: - Add path traversal protection for cheatsheet names Performance: - Move regex compilation outside search loop - Replace string concatenation with strings.Join in search Build: - Remove go:generate; embed config and usage as string literals - Parallelize release builds - Add fuzz testing infrastructure Testing: - Improve test coverage from 38.9% to 50.2% - Add fuzz tests for search, filter, tags, and validation Documentation: - Fix inaccurate code examples in HACKING.md - Add missing --conf and --all options to man page - Add ADRs for path traversal, env parsing, and search parallelization - Update CONTRIBUTING.md to reflect project policy Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -96,6 +96,9 @@ func New(_ map[string]interface{}, confPath string, resolve bool) (Config, error
|
||||
conf.Cheatpaths[i].Path = expanded
|
||||
}
|
||||
|
||||
// trim editor whitespace
|
||||
conf.Editor = strings.TrimSpace(conf.Editor)
|
||||
|
||||
// if an editor was not provided in the configs, attempt to choose one
|
||||
// that's appropriate for the environment
|
||||
if conf.Editor == "" {
|
||||
|
||||
247
internal/config/config_extended_test.go
Normal file
247
internal/config/config_extended_test.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/cheat/cheat/internal/mock"
|
||||
)
|
||||
|
||||
// TestConfigYAMLErrors tests YAML parsing errors
|
||||
func TestConfigYAMLErrors(t *testing.T) {
|
||||
// Create a temporary file with invalid YAML
|
||||
tempDir, err := os.MkdirTemp("", "cheat-config-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
invalidYAML := filepath.Join(tempDir, "invalid.yml")
|
||||
err = os.WriteFile(invalidYAML, []byte("invalid: yaml: content:\n - no closing"), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write invalid yaml: %v", err)
|
||||
}
|
||||
|
||||
// Attempt to load invalid YAML
|
||||
_, err = New(map[string]interface{}{}, invalidYAML, false)
|
||||
if err == nil {
|
||||
t.Error("expected error for invalid YAML, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigLocalCheatpath tests local .cheat directory detection
|
||||
func TestConfigLocalCheatpath(t *testing.T) {
|
||||
// Create a temporary directory to act as working directory
|
||||
tempDir, err := os.MkdirTemp("", "cheat-config-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Save current working directory
|
||||
oldCwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get cwd: %v", err)
|
||||
}
|
||||
defer os.Chdir(oldCwd)
|
||||
|
||||
// Change to temp directory
|
||||
err = os.Chdir(tempDir)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to change dir: %v", err)
|
||||
}
|
||||
|
||||
// Create .cheat directory
|
||||
localCheat := filepath.Join(tempDir, ".cheat")
|
||||
err = os.Mkdir(localCheat, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create .cheat dir: %v", err)
|
||||
}
|
||||
|
||||
// Load config
|
||||
conf, err := New(map[string]interface{}{}, mock.Path("conf/empty.yml"), false)
|
||||
if err != nil {
|
||||
t.Errorf("failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// Check that local cheatpath was added
|
||||
found := false
|
||||
for _, cp := range conf.Cheatpaths {
|
||||
if cp.Name == "cwd" && cp.Path == localCheat {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Error("local .cheat directory was not added to cheatpaths")
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigDefaults tests default values
|
||||
func TestConfigDefaults(t *testing.T) {
|
||||
// Load empty config
|
||||
conf, err := New(map[string]interface{}{}, mock.Path("conf/empty.yml"), false)
|
||||
if err != nil {
|
||||
t.Errorf("failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// Check defaults
|
||||
if conf.Style != "bw" {
|
||||
t.Errorf("expected default style 'bw', got %s", conf.Style)
|
||||
}
|
||||
|
||||
if conf.Formatter != "terminal" {
|
||||
t.Errorf("expected default formatter 'terminal', got %s", conf.Formatter)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigSymlinkResolution tests symlink resolution
|
||||
func TestConfigSymlinkResolution(t *testing.T) {
|
||||
// Create temp directory structure
|
||||
tempDir, err := os.MkdirTemp("", "cheat-config-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create target directory
|
||||
targetDir := filepath.Join(tempDir, "target")
|
||||
err = os.Mkdir(targetDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create target dir: %v", err)
|
||||
}
|
||||
|
||||
// Create symlink
|
||||
linkPath := filepath.Join(tempDir, "link")
|
||||
err = os.Symlink(targetDir, linkPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create symlink: %v", err)
|
||||
}
|
||||
|
||||
// Create config with symlink path
|
||||
configContent := `---
|
||||
editor: vim
|
||||
cheatpaths:
|
||||
- name: test
|
||||
path: ` + linkPath + `
|
||||
readonly: true
|
||||
`
|
||||
configFile := filepath.Join(tempDir, "config.yml")
|
||||
err = os.WriteFile(configFile, []byte(configContent), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
// Load config with symlink resolution
|
||||
conf, err := New(map[string]interface{}{}, configFile, true)
|
||||
if err != nil {
|
||||
t.Errorf("failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// Verify symlink was resolved
|
||||
if len(conf.Cheatpaths) > 0 && conf.Cheatpaths[0].Path != targetDir {
|
||||
t.Errorf("expected symlink to be resolved to %s, got %s", targetDir, conf.Cheatpaths[0].Path)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigBrokenSymlink tests broken symlink handling
|
||||
func TestConfigBrokenSymlink(t *testing.T) {
|
||||
// Create temp directory
|
||||
tempDir, err := os.MkdirTemp("", "cheat-config-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create broken symlink
|
||||
linkPath := filepath.Join(tempDir, "broken-link")
|
||||
err = os.Symlink("/nonexistent/path", linkPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create symlink: %v", err)
|
||||
}
|
||||
|
||||
// Create config with broken symlink
|
||||
configContent := `---
|
||||
editor: vim
|
||||
cheatpaths:
|
||||
- name: test
|
||||
path: ` + linkPath + `
|
||||
readonly: true
|
||||
`
|
||||
configFile := filepath.Join(tempDir, "config.yml")
|
||||
err = os.WriteFile(configFile, []byte(configContent), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
// Load config with symlink resolution should fail
|
||||
_, err = New(map[string]interface{}{}, configFile, true)
|
||||
if err == nil {
|
||||
t.Error("expected error for broken symlink, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigTildeExpansionError tests tilde expansion error handling
|
||||
func TestConfigTildeExpansionError(t *testing.T) {
|
||||
// This is tricky to test without mocking homedir.Expand
|
||||
// We'll create a config with an invalid home reference
|
||||
tempDir, err := os.MkdirTemp("", "cheat-config-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create config with user that likely doesn't exist
|
||||
configContent := `---
|
||||
editor: vim
|
||||
cheatpaths:
|
||||
- name: test
|
||||
path: ~nonexistentuser12345/cheat
|
||||
readonly: true
|
||||
`
|
||||
configFile := filepath.Join(tempDir, "config.yml")
|
||||
err = os.WriteFile(configFile, []byte(configContent), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
// Load config - this may or may not fail depending on the system
|
||||
// but we're testing that it doesn't panic
|
||||
_, _ = New(map[string]interface{}{}, configFile, false)
|
||||
}
|
||||
|
||||
// TestConfigGetCwdError tests error handling when os.Getwd fails
|
||||
func TestConfigGetCwdError(t *testing.T) {
|
||||
// This is difficult to test without being able to break os.Getwd
|
||||
// We'll create a scenario where the current directory is removed
|
||||
|
||||
// Create and enter a temp directory
|
||||
tempDir, err := os.MkdirTemp("", "cheat-config-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
|
||||
oldCwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get cwd: %v", err)
|
||||
}
|
||||
defer os.Chdir(oldCwd)
|
||||
|
||||
err = os.Chdir(tempDir)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to change dir: %v", err)
|
||||
}
|
||||
|
||||
// Remove the directory we're in
|
||||
err = os.RemoveAll(tempDir)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to remove temp dir: %v", err)
|
||||
}
|
||||
|
||||
// Now os.Getwd should fail
|
||||
_, err = New(map[string]interface{}{}, mock.Path("conf/empty.yml"), false)
|
||||
// This might not fail on all systems, so we just ensure no panic
|
||||
_ = err
|
||||
}
|
||||
52
internal/config/doc.go
Normal file
52
internal/config/doc.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Package config manages application configuration and settings.
|
||||
//
|
||||
// The config package provides functionality to:
|
||||
// - Load configuration from YAML files
|
||||
// - Validate configuration values
|
||||
// - Manage platform-specific configuration paths
|
||||
// - Handle editor and pager settings
|
||||
// - Configure colorization and formatting options
|
||||
//
|
||||
// # Configuration Structure
|
||||
//
|
||||
// The main configuration file (conf.yml) contains:
|
||||
// - Editor preferences
|
||||
// - Pager settings
|
||||
// - Colorization options
|
||||
// - Cheatpath definitions
|
||||
// - Formatting preferences
|
||||
//
|
||||
// Example configuration:
|
||||
//
|
||||
// ---
|
||||
// editor: vim
|
||||
// colorize: true
|
||||
// style: monokai
|
||||
// formatter: terminal256
|
||||
// pager: less -FRX
|
||||
// cheatpaths:
|
||||
// - name: personal
|
||||
// path: ~/cheat
|
||||
// tags: []
|
||||
// readonly: false
|
||||
// - name: community
|
||||
// path: ~/cheat/.cheat
|
||||
// tags: [community]
|
||||
// readonly: true
|
||||
//
|
||||
// # Platform-Specific Paths
|
||||
//
|
||||
// The package automatically detects configuration paths based on the operating system:
|
||||
// - Linux/Unix: $XDG_CONFIG_HOME/cheat/conf.yml or ~/.config/cheat/conf.yml
|
||||
// - macOS: ~/Library/Application Support/cheat/conf.yml
|
||||
// - Windows: %APPDATA%\cheat\conf.yml
|
||||
//
|
||||
// # Environment Variables
|
||||
//
|
||||
// The following environment variables are respected:
|
||||
// - CHEAT_CONFIG_PATH: Override the configuration file location
|
||||
// - CHEAT_USE_FZF: Enable fzf integration when set to "true"
|
||||
// - EDITOR: Default editor if not specified in config
|
||||
// - VISUAL: Fallback editor if EDITOR is not set
|
||||
// - PAGER: Default pager if not specified in config
|
||||
package config
|
||||
95
internal/config/editor_test.go
Normal file
95
internal/config/editor_test.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestEditor tests the Editor function
|
||||
func TestEditor(t *testing.T) {
|
||||
// Save original env vars
|
||||
oldVisual := os.Getenv("VISUAL")
|
||||
oldEditor := os.Getenv("EDITOR")
|
||||
defer func() {
|
||||
os.Setenv("VISUAL", oldVisual)
|
||||
os.Setenv("EDITOR", oldEditor)
|
||||
}()
|
||||
|
||||
t.Run("windows default", func(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("skipping windows test on non-windows platform")
|
||||
}
|
||||
|
||||
// Clear env vars
|
||||
os.Setenv("VISUAL", "")
|
||||
os.Setenv("EDITOR", "")
|
||||
|
||||
editor, err := Editor()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if editor != "notepad" {
|
||||
t.Errorf("expected 'notepad' on windows, got %s", editor)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("VISUAL takes precedence", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping non-windows test on windows platform")
|
||||
}
|
||||
|
||||
os.Setenv("VISUAL", "emacs")
|
||||
os.Setenv("EDITOR", "nano")
|
||||
|
||||
editor, err := Editor()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if editor != "emacs" {
|
||||
t.Errorf("expected VISUAL to take precedence, got %s", editor)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("EDITOR when no VISUAL", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping non-windows test on windows platform")
|
||||
}
|
||||
|
||||
os.Setenv("VISUAL", "")
|
||||
os.Setenv("EDITOR", "vim")
|
||||
|
||||
editor, err := Editor()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if editor != "vim" {
|
||||
t.Errorf("expected EDITOR value, got %s", editor)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no editor found error", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping non-windows test on windows platform")
|
||||
}
|
||||
|
||||
// Clear all environment variables
|
||||
os.Setenv("VISUAL", "")
|
||||
os.Setenv("EDITOR", "")
|
||||
|
||||
// Create a custom PATH that doesn't include common editors
|
||||
oldPath := os.Getenv("PATH")
|
||||
defer os.Setenv("PATH", oldPath)
|
||||
|
||||
// Set a very limited PATH that won't have editors
|
||||
os.Setenv("PATH", "/nonexistent")
|
||||
|
||||
editor, err := Editor()
|
||||
|
||||
// If we found an editor, it's likely in the system
|
||||
// This test might not always produce an error on systems with editors
|
||||
if editor == "" && err == nil {
|
||||
t.Error("expected error when no editor found")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -35,3 +37,84 @@ func TestInit(t *testing.T) {
|
||||
t.Errorf("failed to write configs: want: %s, got: %s", conf, got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitCreateDirectory tests that Init creates the directory if it doesn't exist
|
||||
func TestInitCreateDirectory(t *testing.T) {
|
||||
// Create a temp directory
|
||||
tempDir, err := os.MkdirTemp("", "cheat-init-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Path to a config file in a non-existent subdirectory
|
||||
confPath := filepath.Join(tempDir, "subdir", "conf.yml")
|
||||
|
||||
// Initialize the config file
|
||||
conf := "test config"
|
||||
if err = Init(confPath, conf); err != nil {
|
||||
t.Errorf("failed to init config file: %v", err)
|
||||
}
|
||||
|
||||
// Verify the directory was created
|
||||
if _, err := os.Stat(filepath.Dir(confPath)); os.IsNotExist(err) {
|
||||
t.Error("Init did not create the directory")
|
||||
}
|
||||
|
||||
// Verify the file was created with correct content
|
||||
bytes, err := os.ReadFile(confPath)
|
||||
if err != nil {
|
||||
t.Errorf("failed to read config file: %v", err)
|
||||
}
|
||||
if string(bytes) != conf {
|
||||
t.Errorf("config content mismatch: got %q, want %q", string(bytes), conf)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitWriteError tests error handling when file write fails
|
||||
func TestInitWriteError(t *testing.T) {
|
||||
// Skip this test if running as root (can write anywhere)
|
||||
if os.Getuid() == 0 {
|
||||
t.Skip("Cannot test write errors as root")
|
||||
}
|
||||
|
||||
// Try to write to a read-only directory
|
||||
err := Init("/dev/null/impossible/path/conf.yml", "test")
|
||||
if err == nil {
|
||||
t.Error("expected error when writing to invalid path, got nil")
|
||||
}
|
||||
if err != nil && !strings.Contains(err.Error(), "failed to create") {
|
||||
t.Errorf("expected 'failed to create' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitExistingFile tests that Init overwrites existing files
|
||||
func TestInitExistingFile(t *testing.T) {
|
||||
// Create a temp file
|
||||
tempFile, err := os.CreateTemp("", "cheat-init-existing-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
// Write initial content
|
||||
initialContent := "initial content"
|
||||
if err := os.WriteFile(tempFile.Name(), []byte(initialContent), 0644); err != nil {
|
||||
t.Fatalf("failed to write initial content: %v", err)
|
||||
}
|
||||
|
||||
// Initialize with new content
|
||||
newContent := "new config content"
|
||||
if err = Init(tempFile.Name(), newContent); err != nil {
|
||||
t.Errorf("failed to init over existing file: %v", err)
|
||||
}
|
||||
|
||||
// Verify the file was overwritten
|
||||
bytes, err := os.ReadFile(tempFile.Name())
|
||||
if err != nil {
|
||||
t.Errorf("failed to read config file: %v", err)
|
||||
}
|
||||
if string(bytes) != newContent {
|
||||
t.Errorf("config not overwritten: got %q, want %q", string(bytes), newContent)
|
||||
}
|
||||
}
|
||||
|
||||
125
internal/config/new_test.go
Normal file
125
internal/config/new_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewTrimsWhitespace(t *testing.T) {
|
||||
// Create a temporary config file with whitespace in editor and pager
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "config.yml")
|
||||
|
||||
configContent := `---
|
||||
editor: " vim -c 'set number' "
|
||||
pager: " less -R "
|
||||
style: monokai
|
||||
formatter: terminal
|
||||
cheatpaths:
|
||||
- name: personal
|
||||
path: ~/cheat
|
||||
tags: []
|
||||
readonly: false
|
||||
`
|
||||
|
||||
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
|
||||
t.Fatalf("failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
// Load the config
|
||||
conf, err := New(map[string]interface{}{}, configPath, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// Verify editor is trimmed
|
||||
expectedEditor := "vim -c 'set number'"
|
||||
if conf.Editor != expectedEditor {
|
||||
t.Errorf("editor not properly trimmed: got %q, want %q", conf.Editor, expectedEditor)
|
||||
}
|
||||
|
||||
// Verify pager is trimmed
|
||||
expectedPager := "less -R"
|
||||
if conf.Pager != expectedPager {
|
||||
t.Errorf("pager not properly trimmed: got %q, want %q", conf.Pager, expectedPager)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewEmptyEditorFallback(t *testing.T) {
|
||||
// Skip if required environment variables would interfere
|
||||
oldVisual := os.Getenv("VISUAL")
|
||||
oldEditor := os.Getenv("EDITOR")
|
||||
os.Unsetenv("VISUAL")
|
||||
os.Unsetenv("EDITOR")
|
||||
defer func() {
|
||||
os.Setenv("VISUAL", oldVisual)
|
||||
os.Setenv("EDITOR", oldEditor)
|
||||
}()
|
||||
|
||||
// Create a config with whitespace-only editor
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "config.yml")
|
||||
|
||||
configContent := `---
|
||||
editor: " "
|
||||
pager: less
|
||||
style: monokai
|
||||
formatter: terminal
|
||||
cheatpaths:
|
||||
- name: personal
|
||||
path: ~/cheat
|
||||
tags: []
|
||||
readonly: false
|
||||
`
|
||||
|
||||
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
|
||||
t.Fatalf("failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
// Load the config
|
||||
conf, err := New(map[string]interface{}{}, configPath, false)
|
||||
if err != nil {
|
||||
// It's OK if this fails due to no editor being found
|
||||
// The important thing is it doesn't panic
|
||||
return
|
||||
}
|
||||
|
||||
// If it succeeded, editor should not be empty (fallback was used)
|
||||
if conf.Editor == "" {
|
||||
t.Error("editor should not be empty after fallback")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWhitespaceOnlyPager(t *testing.T) {
|
||||
// Create a config with whitespace-only pager
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "config.yml")
|
||||
|
||||
configContent := `---
|
||||
editor: vim
|
||||
pager: " "
|
||||
style: monokai
|
||||
formatter: terminal
|
||||
cheatpaths:
|
||||
- name: personal
|
||||
path: ~/cheat
|
||||
tags: []
|
||||
readonly: false
|
||||
`
|
||||
|
||||
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
|
||||
t.Fatalf("failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
// Load the config
|
||||
conf, err := New(map[string]interface{}{}, configPath, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// Pager should be empty after trimming
|
||||
if conf.Pager != "" {
|
||||
t.Errorf("pager should be empty after trimming whitespace: got %q", conf.Pager)
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func Pager() string {
|
||||
// Otherwise, search for `pager`, `less`, and `more` on the `$PATH`. If
|
||||
// none are found, return an empty pager.
|
||||
for _, pager := range []string{"pager", "less", "more"} {
|
||||
if path, err := exec.LookPath(pager); err != nil {
|
||||
if path, err := exec.LookPath(pager); err == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
90
internal/config/pager_test.go
Normal file
90
internal/config/pager_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestPager tests the Pager function
|
||||
func TestPager(t *testing.T) {
|
||||
// Save original env var
|
||||
oldPager := os.Getenv("PAGER")
|
||||
defer os.Setenv("PAGER", oldPager)
|
||||
|
||||
t.Run("windows default", func(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("skipping windows test on non-windows platform")
|
||||
}
|
||||
|
||||
os.Setenv("PAGER", "")
|
||||
pager := Pager()
|
||||
if pager != "more" {
|
||||
t.Errorf("expected 'more' on windows, got %s", pager)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("PAGER env var", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping non-windows test on windows platform")
|
||||
}
|
||||
|
||||
os.Setenv("PAGER", "bat")
|
||||
pager := Pager()
|
||||
if pager != "bat" {
|
||||
t.Errorf("expected PAGER env var value, got %s", pager)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fallback to system pager", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping non-windows test on windows platform")
|
||||
}
|
||||
|
||||
os.Setenv("PAGER", "")
|
||||
pager := Pager()
|
||||
|
||||
// Should find one of the fallback pagers or return empty string
|
||||
validPagers := map[string]bool{
|
||||
"": true, // no pager found
|
||||
"pager": true,
|
||||
"less": true,
|
||||
"more": true,
|
||||
}
|
||||
|
||||
// Check if it's a path to one of these
|
||||
found := false
|
||||
for p := range validPagers {
|
||||
if p == "" && pager == "" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
if p != "" && (pager == p || len(pager) >= len(p) && pager[len(pager)-len(p):] == p) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("unexpected pager value: %s", pager)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no pager available", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping non-windows test on windows platform")
|
||||
}
|
||||
|
||||
os.Setenv("PAGER", "")
|
||||
|
||||
// Save and modify PATH to ensure no pagers are found
|
||||
oldPath := os.Getenv("PATH")
|
||||
defer os.Setenv("PATH", oldPath)
|
||||
os.Setenv("PATH", "/nonexistent")
|
||||
|
||||
pager := Pager()
|
||||
if pager != "" {
|
||||
t.Errorf("expected empty string when no pager found, got %s", pager)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user