432 lines
9.9 KiB
Go
432 lines
9.9 KiB
Go
package utils
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestResolvePath(t *testing.T) {
|
|
// Save original working directory
|
|
origDir, _ := os.Getwd()
|
|
defer os.Chdir(origDir)
|
|
|
|
// Create a temporary directory for testing
|
|
tmpDir, err := os.MkdirTemp("", "path_test")
|
|
assert.NoError(t, err)
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
setup func() // Optional setup function
|
|
}{
|
|
{
|
|
name: "Empty path",
|
|
input: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "Already absolute path",
|
|
input: func() string {
|
|
if runtime.GOOS == "windows" {
|
|
return "C:/absolute/path/file.txt"
|
|
}
|
|
return "/absolute/path/file.txt"
|
|
}(),
|
|
expected: func() string {
|
|
if runtime.GOOS == "windows" {
|
|
return "C:/absolute/path/file.txt"
|
|
}
|
|
return "/absolute/path/file.txt"
|
|
}(),
|
|
},
|
|
{
|
|
name: "Relative path",
|
|
input: "relative/file.txt",
|
|
expected: func() string {
|
|
abs, _ := filepath.Abs("relative/file.txt")
|
|
return strings.ReplaceAll(abs, "\\", "/")
|
|
}(),
|
|
},
|
|
{
|
|
name: "Tilde expansion - home only",
|
|
input: "~",
|
|
expected: func() string {
|
|
home := os.Getenv("HOME")
|
|
if home == "" && runtime.GOOS == "windows" {
|
|
home = os.Getenv("USERPROFILE")
|
|
}
|
|
return strings.ReplaceAll(filepath.Clean(home), "\\", "/")
|
|
}(),
|
|
},
|
|
{
|
|
name: "Tilde expansion - with subpath",
|
|
input: "~/Documents/file.txt",
|
|
expected: func() string {
|
|
home := os.Getenv("HOME")
|
|
if home == "" && runtime.GOOS == "windows" {
|
|
home = os.Getenv("USERPROFILE")
|
|
}
|
|
expected := filepath.Join(home, "Documents", "file.txt")
|
|
return strings.ReplaceAll(filepath.Clean(expected), "\\", "/")
|
|
}(),
|
|
},
|
|
{
|
|
name: "Path normalization - double slashes",
|
|
input: "path//to//file.txt",
|
|
expected: func() string {
|
|
abs, _ := filepath.Abs("path/to/file.txt")
|
|
return strings.ReplaceAll(abs, "\\", "/")
|
|
}(),
|
|
},
|
|
{
|
|
name: "Path normalization - . and ..",
|
|
input: "path/./to/../file.txt",
|
|
expected: func() string {
|
|
abs, _ := filepath.Abs("path/file.txt")
|
|
return strings.ReplaceAll(abs, "\\", "/")
|
|
}(),
|
|
},
|
|
{
|
|
name: "Windows backslash normalization",
|
|
input: "path\\to\\file.txt",
|
|
expected: func() string {
|
|
abs, _ := filepath.Abs("path/to/file.txt")
|
|
return strings.ReplaceAll(abs, "\\", "/")
|
|
}(),
|
|
},
|
|
{
|
|
name: "Mixed separators with tilde",
|
|
input: "~/Documents\\file.txt",
|
|
expected: func() string {
|
|
home := os.Getenv("HOME")
|
|
if home == "" && runtime.GOOS == "windows" {
|
|
home = os.Getenv("USERPROFILE")
|
|
}
|
|
expected := filepath.Join(home, "Documents", "file.txt")
|
|
return strings.ReplaceAll(filepath.Clean(expected), "\\", "/")
|
|
}(),
|
|
},
|
|
{
|
|
name: "Relative path from current directory",
|
|
input: "./file.txt",
|
|
expected: func() string {
|
|
abs, _ := filepath.Abs("file.txt")
|
|
return strings.ReplaceAll(abs, "\\", "/")
|
|
}(),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.setup != nil {
|
|
tt.setup()
|
|
}
|
|
|
|
result := ResolvePath(tt.input)
|
|
assert.Equal(t, tt.expected, result, "ResolvePath(%q) = %q, want %q", tt.input, result, tt.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolvePathWithWorkingDirectoryChange(t *testing.T) {
|
|
// Save original working directory
|
|
origDir, _ := os.Getwd()
|
|
defer os.Chdir(origDir)
|
|
|
|
// Create temporary directories
|
|
tmpDir, err := os.MkdirTemp("", "path_test")
|
|
assert.NoError(t, err)
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
subDir := filepath.Join(tmpDir, "subdir")
|
|
err = os.MkdirAll(subDir, 0755)
|
|
assert.NoError(t, err)
|
|
|
|
// Change to subdirectory
|
|
err = os.Chdir(subDir)
|
|
assert.NoError(t, err)
|
|
|
|
// Test relative path resolution from new working directory
|
|
result := ResolvePath("../test.txt")
|
|
expected := filepath.Join(tmpDir, "test.txt")
|
|
expected = strings.ReplaceAll(filepath.Clean(expected), "\\", "/")
|
|
|
|
assert.Equal(t, expected, result)
|
|
}
|
|
|
|
func TestResolvePathComplexTilde(t *testing.T) {
|
|
// Test complex tilde patterns
|
|
home := os.Getenv("HOME")
|
|
if home == "" && runtime.GOOS == "windows" {
|
|
home = os.Getenv("USERPROFILE")
|
|
}
|
|
|
|
if home == "" {
|
|
t.Skip("Cannot determine home directory for tilde expansion tests")
|
|
}
|
|
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{
|
|
input: "~",
|
|
expected: strings.ReplaceAll(filepath.Clean(home), "\\", "/"),
|
|
},
|
|
{
|
|
input: "~/",
|
|
expected: strings.ReplaceAll(filepath.Clean(home), "\\", "/"),
|
|
},
|
|
{
|
|
input: "~~",
|
|
expected: func() string {
|
|
// ~~ should be treated as ~ followed by ~ (tilde expansion)
|
|
home := os.Getenv("HOME")
|
|
if home == "" && runtime.GOOS == "windows" {
|
|
home = os.Getenv("USERPROFILE")
|
|
}
|
|
if home != "" {
|
|
// First ~ gets expanded, second ~ remains
|
|
return strings.ReplaceAll(filepath.Clean(home+"~"), "\\", "/")
|
|
}
|
|
abs, _ := filepath.Abs("~~")
|
|
return strings.ReplaceAll(abs, "\\", "/")
|
|
}(),
|
|
},
|
|
{
|
|
input: func() string {
|
|
if runtime.GOOS == "windows" {
|
|
return "C:/not/tilde/path"
|
|
}
|
|
return "/not/tilde/path"
|
|
}(),
|
|
expected: func() string {
|
|
if runtime.GOOS == "windows" {
|
|
return "C:/not/tilde/path"
|
|
}
|
|
return "/not/tilde/path"
|
|
}(),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run("Complex tilde: "+tt.input, func(t *testing.T) {
|
|
result := ResolvePath(tt.input)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsAbsolutePath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Empty path",
|
|
input: "",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Absolute Unix path",
|
|
input: "/absolute/path",
|
|
expected: func() bool {
|
|
if runtime.GOOS == "windows" {
|
|
// On Windows, paths starting with / are not considered absolute
|
|
return false
|
|
}
|
|
return true
|
|
}(),
|
|
},
|
|
{
|
|
name: "Relative path",
|
|
input: "relative/path",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Tilde expansion (becomes absolute)",
|
|
input: "~/path",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Windows absolute path",
|
|
input: "C:\\Windows\\System32",
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := IsAbsolutePath(tt.input)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetRelativePath(t *testing.T) {
|
|
// Create temporary directories for testing
|
|
tmpDir, err := os.MkdirTemp("", "relative_path_test")
|
|
assert.NoError(t, err)
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
baseDir := filepath.Join(tmpDir, "base")
|
|
targetDir := filepath.Join(tmpDir, "target")
|
|
subDir := filepath.Join(targetDir, "subdir")
|
|
|
|
err = os.MkdirAll(baseDir, 0755)
|
|
assert.NoError(t, err)
|
|
err = os.MkdirAll(subDir, 0755)
|
|
assert.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
base string
|
|
target string
|
|
expected string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Target is subdirectory of base",
|
|
base: baseDir,
|
|
target: filepath.Join(baseDir, "subdir"),
|
|
expected: "subdir",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Target is parent of base",
|
|
base: filepath.Join(baseDir, "subdir"),
|
|
target: baseDir,
|
|
expected: "..",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Target is sibling directory",
|
|
base: baseDir,
|
|
target: targetDir,
|
|
expected: "../target",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Same directory",
|
|
base: baseDir,
|
|
target: baseDir,
|
|
expected: ".",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "With tilde expansion",
|
|
base: baseDir,
|
|
target: filepath.Join(baseDir, "file.txt"),
|
|
expected: "file.txt",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := GetRelativePath(tt.base, tt.target)
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolvePathRegression(t *testing.T) {
|
|
// This test specifically addresses the original bug:
|
|
// "~ is NOT BEING FUCKING RESOLVED"
|
|
|
|
home := os.Getenv("HOME")
|
|
if home == "" && runtime.GOOS == "windows" {
|
|
home = os.Getenv("USERPROFILE")
|
|
}
|
|
|
|
if home == "" {
|
|
t.Skip("Cannot determine home directory for regression test")
|
|
}
|
|
|
|
// Test the exact pattern from the bug report
|
|
testPath := "~/Seafile/activitywatch/sync.yml"
|
|
result := ResolvePath(testPath)
|
|
expected := filepath.Join(home, "Seafile", "activitywatch", "sync.yml")
|
|
expected = strings.ReplaceAll(filepath.Clean(expected), "\\", "/")
|
|
|
|
assert.Equal(t, expected, result, "Tilde expansion bug not fixed!")
|
|
assert.NotContains(t, result, "~", "Tilde still present in resolved path!")
|
|
// Convert both to forward slashes for comparison
|
|
homeForwardSlash := strings.ReplaceAll(home, "\\", "/")
|
|
assert.Contains(t, result, homeForwardSlash, "Home directory not found in resolved path!")
|
|
}
|
|
|
|
func TestResolvePathEdgeCases(t *testing.T) {
|
|
// Save original working directory
|
|
origDir, _ := os.Getwd()
|
|
defer os.Chdir(origDir)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
setup func()
|
|
shouldPanic bool
|
|
}{
|
|
{
|
|
name: "Just dot",
|
|
input: ".",
|
|
},
|
|
{
|
|
name: "Just double dot",
|
|
input: "..",
|
|
},
|
|
{
|
|
name: "Triple dot",
|
|
input: "...",
|
|
},
|
|
{
|
|
name: "Multiple leading dots",
|
|
input: "./.././../file.txt",
|
|
},
|
|
{
|
|
name: "Path with spaces",
|
|
input: "path with spaces/file.txt",
|
|
},
|
|
{
|
|
name: "Very long relative path",
|
|
input: strings.Repeat("../", 10) + "file.txt",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.setup != nil {
|
|
tt.setup()
|
|
}
|
|
|
|
if tt.shouldPanic {
|
|
assert.Panics(t, func() {
|
|
ResolvePath(tt.input)
|
|
})
|
|
} else {
|
|
// Should not panic
|
|
assert.NotPanics(t, func() {
|
|
ResolvePath(tt.input)
|
|
})
|
|
// Result should be a valid absolute path
|
|
result := ResolvePath(tt.input)
|
|
if tt.input != "" {
|
|
assert.True(t, filepath.IsAbs(result) || result == "", "Result should be absolute or empty")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
} |