mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
522 lines
12 KiB
Go
522 lines
12 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
)
|
|
|
|
func TestGetUserConfigDir(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
xdgConfig string
|
|
goos string
|
|
expectXDG bool
|
|
expectMacOS bool
|
|
}{
|
|
{
|
|
name: "XDG_CONFIG_HOME set",
|
|
xdgConfig: "/custom/config",
|
|
expectXDG: true,
|
|
},
|
|
{
|
|
name: "macOS without XDG",
|
|
xdgConfig: "",
|
|
goos: "darwin",
|
|
expectMacOS: true,
|
|
},
|
|
{
|
|
name: "Linux without XDG",
|
|
xdgConfig: "",
|
|
goos: "linux",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Save and restore environment
|
|
origXDG := os.Getenv("XDG_CONFIG_HOME")
|
|
defer func() {
|
|
if origXDG != "" {
|
|
_ = os.Setenv("XDG_CONFIG_HOME", origXDG)
|
|
} else {
|
|
_ = os.Unsetenv("XDG_CONFIG_HOME")
|
|
}
|
|
}()
|
|
|
|
if tt.xdgConfig != "" {
|
|
_ = os.Setenv("XDG_CONFIG_HOME", tt.xdgConfig)
|
|
} else {
|
|
_ = os.Unsetenv("XDG_CONFIG_HOME")
|
|
}
|
|
|
|
dir, err := getUserConfigDir()
|
|
if err != nil {
|
|
t.Fatalf("getUserConfigDir() error = %v", err)
|
|
}
|
|
|
|
if tt.expectXDG {
|
|
expected := filepath.Join(tt.xdgConfig, "tiki")
|
|
if dir != expected {
|
|
t.Errorf("getUserConfigDir() = %q, want %q", dir, expected)
|
|
}
|
|
} else if tt.expectMacOS && runtime.GOOS == "darwin" {
|
|
// On macOS, should contain "Library/Application Support/tiki" or ".config/tiki"
|
|
if !filepath.IsAbs(dir) {
|
|
t.Errorf("getUserConfigDir() returned non-absolute path: %q", dir)
|
|
}
|
|
if filepath.Base(dir) != "tiki" {
|
|
t.Errorf("getUserConfigDir() = %q, want basename 'tiki'", dir)
|
|
}
|
|
} else {
|
|
// Should be absolute and end with /tiki
|
|
if !filepath.IsAbs(dir) {
|
|
t.Errorf("getUserConfigDir() returned non-absolute path: %q", dir)
|
|
}
|
|
if filepath.Base(dir) != "tiki" {
|
|
t.Errorf("getUserConfigDir() = %q, want basename 'tiki'", dir)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetUserCacheDir(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
xdgCache string
|
|
expectXDG bool
|
|
}{
|
|
{
|
|
name: "XDG_CACHE_HOME set",
|
|
xdgCache: "/custom/cache",
|
|
expectXDG: true,
|
|
},
|
|
{
|
|
name: "without XDG",
|
|
xdgCache: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Save and restore environment
|
|
origXDG := os.Getenv("XDG_CACHE_HOME")
|
|
defer func() {
|
|
if origXDG != "" {
|
|
_ = os.Setenv("XDG_CACHE_HOME", origXDG)
|
|
} else {
|
|
_ = os.Unsetenv("XDG_CACHE_HOME")
|
|
}
|
|
}()
|
|
|
|
if tt.xdgCache != "" {
|
|
_ = os.Setenv("XDG_CACHE_HOME", tt.xdgCache)
|
|
} else {
|
|
_ = os.Unsetenv("XDG_CACHE_HOME")
|
|
}
|
|
|
|
dir, err := getUserCacheDir()
|
|
if err != nil {
|
|
t.Fatalf("getUserCacheDir() error = %v", err)
|
|
}
|
|
|
|
if tt.expectXDG {
|
|
expected := filepath.Join(tt.xdgCache, "tiki")
|
|
if dir != expected {
|
|
t.Errorf("getUserCacheDir() = %q, want %q", dir, expected)
|
|
}
|
|
} else {
|
|
// Should be absolute and end with /tiki
|
|
if !filepath.IsAbs(dir) {
|
|
t.Errorf("getUserCacheDir() returned non-absolute path: %q", dir)
|
|
}
|
|
if filepath.Base(dir) != "tiki" {
|
|
t.Errorf("getUserCacheDir() = %q, want basename 'tiki'", dir)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetProjectRoot(t *testing.T) {
|
|
root, err := getProjectRoot()
|
|
if err != nil {
|
|
t.Fatalf("getProjectRoot() error = %v", err)
|
|
}
|
|
|
|
if !filepath.IsAbs(root) {
|
|
t.Errorf("getProjectRoot() = %q, want absolute path", root)
|
|
}
|
|
|
|
// Verify the directory exists
|
|
if _, err := os.Stat(root); err != nil {
|
|
t.Errorf("getProjectRoot() returned path that doesn't exist: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPathManagerPaths(t *testing.T) {
|
|
pm, err := newPathManager()
|
|
if err != nil {
|
|
t.Fatalf("newPathManager() error = %v", err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
getter func() string
|
|
want string
|
|
}{
|
|
{
|
|
name: "ConfigDir",
|
|
getter: pm.ConfigDir,
|
|
},
|
|
{
|
|
name: "CacheDir",
|
|
getter: pm.CacheDir,
|
|
},
|
|
{
|
|
name: "ConfigFile",
|
|
getter: pm.ConfigFile,
|
|
},
|
|
{
|
|
name: "TaskDir",
|
|
getter: pm.TaskDir,
|
|
},
|
|
{
|
|
name: "DokiDir",
|
|
getter: pm.DokiDir,
|
|
},
|
|
{
|
|
name: "ProjectConfigFile",
|
|
getter: pm.ProjectConfigFile,
|
|
},
|
|
{
|
|
name: "TemplateFile",
|
|
getter: pm.TemplateFile,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.getter()
|
|
if result == "" {
|
|
t.Errorf("%s() returned empty string", tt.name)
|
|
}
|
|
if !filepath.IsAbs(result) {
|
|
t.Errorf("%s() = %q, want absolute path", tt.name, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPathManagerPluginSearchPaths(t *testing.T) {
|
|
pm, err := newPathManager()
|
|
if err != nil {
|
|
t.Fatalf("newPathManager() error = %v", err)
|
|
}
|
|
|
|
paths := pm.PluginSearchPaths()
|
|
if len(paths) != 2 {
|
|
t.Errorf("PluginSearchPaths() returned %d paths, want 2", len(paths))
|
|
}
|
|
|
|
// First should be project config dir (.doc/)
|
|
if paths[0] != pm.ProjectConfigDir() {
|
|
t.Errorf("PluginSearchPaths()[0] = %q, want %q", paths[0], pm.ProjectConfigDir())
|
|
}
|
|
|
|
// Second should be user config dir
|
|
if paths[1] != pm.ConfigDir() {
|
|
t.Errorf("PluginSearchPaths()[1] = %q, want %q", paths[1], pm.ConfigDir())
|
|
}
|
|
|
|
// All paths should be absolute
|
|
for i, path := range paths {
|
|
if !filepath.IsAbs(path) {
|
|
t.Errorf("PluginSearchPaths()[%d] = %q, want absolute path", i, path)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPathManagerEnsureDirs(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tmpDir := t.TempDir()
|
|
|
|
// Create a PathManager with temporary paths
|
|
pm := &PathManager{
|
|
configDir: filepath.Join(tmpDir, "config"),
|
|
cacheDir: filepath.Join(tmpDir, "cache"),
|
|
projectRoot: tmpDir,
|
|
}
|
|
|
|
// Call EnsureDirs
|
|
if err := pm.EnsureDirs(); err != nil {
|
|
t.Fatalf("EnsureDirs() error = %v", err)
|
|
}
|
|
|
|
// Verify directories were created
|
|
dirs := []string{
|
|
pm.ConfigDir(),
|
|
pm.CacheDir(),
|
|
pm.TaskDir(),
|
|
pm.DokiDir(),
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
info, err := os.Stat(dir)
|
|
if err != nil {
|
|
t.Errorf("directory %q was not created: %v", dir, err)
|
|
continue
|
|
}
|
|
if !info.IsDir() {
|
|
t.Errorf("%q is not a directory", dir)
|
|
}
|
|
// Check permissions (should be 0755) - skip on Windows as it uses ACL-based permissions
|
|
if runtime.GOOS != "windows" {
|
|
if info.Mode().Perm() != 0755 {
|
|
t.Errorf("directory %q has permissions %o, want 0755", dir, info.Mode().Perm())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGlobalAccessorFunctions(t *testing.T) {
|
|
// Test that all global accessor functions return non-empty absolute paths
|
|
tests := []struct {
|
|
name string
|
|
getter func() string
|
|
}{
|
|
{"GetConfigDir", GetConfigDir},
|
|
{"GetCacheDir", GetCacheDir},
|
|
{"GetConfigFile", GetConfigFile},
|
|
{"GetTaskDir", GetTaskDir},
|
|
{"GetDokiDir", GetDokiDir},
|
|
{"GetProjectConfigFile", GetProjectConfigFile},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.getter()
|
|
if result == "" {
|
|
t.Errorf("%s() returned empty string", tt.name)
|
|
}
|
|
if !filepath.IsAbs(result) {
|
|
t.Errorf("%s() = %q, want absolute path", tt.name, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPluginSearchPaths(t *testing.T) {
|
|
paths := GetPluginSearchPaths()
|
|
if len(paths) != 2 {
|
|
t.Errorf("GetPluginSearchPaths() returned %d paths, want 2", len(paths))
|
|
}
|
|
|
|
for i, path := range paths {
|
|
if path == "" {
|
|
t.Errorf("GetPluginSearchPaths()[%d] is empty", i)
|
|
}
|
|
if !filepath.IsAbs(path) {
|
|
t.Errorf("GetPluginSearchPaths()[%d] = %q, want absolute path", i, path)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInitPaths(t *testing.T) {
|
|
// Reset to test initialization
|
|
ResetPathManager()
|
|
defer ResetPathManager() // Clean up after test
|
|
|
|
err := InitPaths()
|
|
if err != nil {
|
|
t.Fatalf("InitPaths() error = %v", err)
|
|
}
|
|
|
|
// After InitPaths, all accessors should work
|
|
if GetConfigDir() == "" {
|
|
t.Error("GetConfigDir() returned empty after InitPaths()")
|
|
}
|
|
if GetTaskDir() == "" {
|
|
t.Error("GetTaskDir() returned empty after InitPaths()")
|
|
}
|
|
}
|
|
|
|
func TestFindTemplateFile_CwdOverridesProject(t *testing.T) {
|
|
// user config with new.md
|
|
userDir := t.TempDir()
|
|
t.Setenv("XDG_CONFIG_HOME", userDir)
|
|
userTikiDir := filepath.Join(userDir, "tiki")
|
|
if err := os.MkdirAll(userTikiDir, 0750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(userTikiDir, "new.md"), []byte("---\npriority: 1\n---"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// project .doc/ with new.md
|
|
projectDir := t.TempDir()
|
|
docDir := filepath.Join(projectDir, ".doc")
|
|
if err := os.MkdirAll(docDir, 0750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(docDir, "new.md"), []byte("---\npriority: 2\n---"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// cwd with new.md (highest priority)
|
|
cwdDir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(cwdDir, "new.md"), []byte("---\npriority: 3\n---"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
originalDir, _ := os.Getwd()
|
|
defer func() { _ = os.Chdir(originalDir) }()
|
|
_ = os.Chdir(cwdDir)
|
|
|
|
ResetPathManager()
|
|
pm := mustGetPathManager()
|
|
pm.projectRoot = projectDir
|
|
|
|
got := FindTemplateFile()
|
|
gotAbs, _ := filepath.Abs(got)
|
|
wantAbs, _ := filepath.Abs("new.md")
|
|
if gotAbs != wantAbs {
|
|
t.Errorf("FindTemplateFile() = %q, want cwd file %q", gotAbs, wantAbs)
|
|
}
|
|
}
|
|
|
|
func TestFindTemplateFile_ProjectOverridesUser(t *testing.T) {
|
|
// user config with new.md
|
|
userDir := t.TempDir()
|
|
t.Setenv("XDG_CONFIG_HOME", userDir)
|
|
userTikiDir := filepath.Join(userDir, "tiki")
|
|
if err := os.MkdirAll(userTikiDir, 0750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(userTikiDir, "new.md"), []byte("---\npriority: 1\n---"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// project .doc/ with new.md
|
|
projectDir := t.TempDir()
|
|
docDir := filepath.Join(projectDir, ".doc")
|
|
if err := os.MkdirAll(docDir, 0750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(docDir, "new.md"), []byte("---\npriority: 2\n---"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// cwd with NO new.md
|
|
cwdDir := t.TempDir()
|
|
originalDir, _ := os.Getwd()
|
|
defer func() { _ = os.Chdir(originalDir) }()
|
|
_ = os.Chdir(cwdDir)
|
|
|
|
ResetPathManager()
|
|
pm := mustGetPathManager()
|
|
pm.projectRoot = projectDir
|
|
|
|
got := FindTemplateFile()
|
|
want := filepath.Join(docDir, "new.md")
|
|
if got != want {
|
|
t.Errorf("FindTemplateFile() = %q, want project file %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestFindTemplateFile_UserOnlyFallback(t *testing.T) {
|
|
// user config with new.md
|
|
userDir := t.TempDir()
|
|
t.Setenv("XDG_CONFIG_HOME", userDir)
|
|
userTikiDir := filepath.Join(userDir, "tiki")
|
|
if err := os.MkdirAll(userTikiDir, 0750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(userTikiDir, "new.md"), []byte("---\npriority: 1\n---"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// no project .doc/, no cwd new.md
|
|
cwdDir := t.TempDir()
|
|
originalDir, _ := os.Getwd()
|
|
defer func() { _ = os.Chdir(originalDir) }()
|
|
_ = os.Chdir(cwdDir)
|
|
|
|
ResetPathManager()
|
|
pm := mustGetPathManager()
|
|
pm.projectRoot = cwdDir
|
|
|
|
got := FindTemplateFile()
|
|
want := filepath.Join(userTikiDir, "new.md")
|
|
if got != want {
|
|
t.Errorf("FindTemplateFile() = %q, want user config file %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestFindTemplateFile_NoneFound(t *testing.T) {
|
|
// empty user config dir (no new.md)
|
|
userDir := t.TempDir()
|
|
t.Setenv("XDG_CONFIG_HOME", userDir)
|
|
if err := os.MkdirAll(filepath.Join(userDir, "tiki"), 0750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// empty cwd
|
|
cwdDir := t.TempDir()
|
|
originalDir, _ := os.Getwd()
|
|
defer func() { _ = os.Chdir(originalDir) }()
|
|
_ = os.Chdir(cwdDir)
|
|
|
|
ResetPathManager()
|
|
pm := mustGetPathManager()
|
|
pm.projectRoot = cwdDir
|
|
|
|
got := FindTemplateFile()
|
|
if got != "" {
|
|
t.Errorf("FindTemplateFile() = %q, want empty string when no file exists", got)
|
|
}
|
|
}
|
|
|
|
func TestResetPathManager(t *testing.T) {
|
|
// Save original XDG
|
|
origXDG := os.Getenv("XDG_CONFIG_HOME")
|
|
defer func() {
|
|
if origXDG != "" {
|
|
_ = os.Setenv("XDG_CONFIG_HOME", origXDG)
|
|
} else {
|
|
_ = os.Unsetenv("XDG_CONFIG_HOME")
|
|
}
|
|
ResetPathManager() // Clean up
|
|
}()
|
|
|
|
// First initialization
|
|
ResetPathManager()
|
|
_ = os.Setenv("XDG_CONFIG_HOME", "/first/config")
|
|
if err := InitPaths(); err != nil {
|
|
t.Fatalf("first InitPaths() error = %v", err)
|
|
}
|
|
first := GetConfigDir()
|
|
expected1 := filepath.Join("/first/config", "tiki")
|
|
if first != expected1 {
|
|
t.Errorf("first GetConfigDir() = %q, want %q", first, expected1)
|
|
}
|
|
|
|
// Reset and reinitialize with different env
|
|
ResetPathManager()
|
|
_ = os.Setenv("XDG_CONFIG_HOME", "/second/config")
|
|
if err := InitPaths(); err != nil {
|
|
t.Fatalf("second InitPaths() error = %v", err)
|
|
}
|
|
second := GetConfigDir()
|
|
expected2 := filepath.Join("/second/config", "tiki")
|
|
if second != expected2 {
|
|
t.Errorf("second GetConfigDir() = %q, want %q", second, expected2)
|
|
}
|
|
|
|
// Verify they're different (reset worked)
|
|
if first == second {
|
|
t.Error("ResetPathManager() did not allow re-initialization with different config")
|
|
}
|
|
}
|