mirror of
https://github.com/taigrr/mg.git
synced 2026-04-02 03:28:42 -07:00
update env var expansion logic
This commit is contained in:
530
parse/mgconf_test.go
Normal file
530
parse/mgconf_test.go
Normal file
@@ -0,0 +1,530 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExpandPaths(t *testing.T) {
|
||||
// Set up test environment variables
|
||||
t.Setenv("HOME", "/home/testuser")
|
||||
t.Setenv("GOPATH", "/home/testuser/go")
|
||||
t.Setenv("CUSTOM_VAR", "/custom/path")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []Repo
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "expand $HOME",
|
||||
input: []Repo{
|
||||
{Path: "$HOME/code/project", Remote: "git@github.com:user/project.git"},
|
||||
},
|
||||
expected: []string{"/home/testuser/code/project"},
|
||||
},
|
||||
{
|
||||
name: "expand multiple variables",
|
||||
input: []Repo{
|
||||
{Path: "$HOME/code/project", Remote: "git@github.com:user/project.git"},
|
||||
{Path: "$GOPATH/src/github.com/user/repo", Remote: "git@github.com:user/repo.git"},
|
||||
},
|
||||
expected: []string{
|
||||
"/home/testuser/code/project",
|
||||
"/home/testuser/go/src/github.com/user/repo",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "expand custom variable",
|
||||
input: []Repo{
|
||||
{Path: "$CUSTOM_VAR/subdir", Remote: "git@github.com:user/project.git"},
|
||||
},
|
||||
expected: []string{"/custom/path/subdir"},
|
||||
},
|
||||
{
|
||||
name: "no expansion needed",
|
||||
input: []Repo{
|
||||
{Path: "/absolute/path/to/repo", Remote: "git@github.com:user/project.git"},
|
||||
},
|
||||
expected: []string{"/absolute/path/to/repo"},
|
||||
},
|
||||
{
|
||||
name: "empty repos",
|
||||
input: []Repo{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "undefined variable stays as-is",
|
||||
input: []Repo{
|
||||
{Path: "$UNDEFINED_VAR/code", Remote: "git@github.com:user/project.git"},
|
||||
},
|
||||
expected: []string{"/code"}, // undefined vars expand to empty string
|
||||
},
|
||||
{
|
||||
name: "braced variable syntax",
|
||||
input: []Repo{
|
||||
{Path: "${HOME}/code/project", Remote: "git@github.com:user/project.git"},
|
||||
},
|
||||
expected: []string{"/home/testuser/code/project"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
conf := MGConfig{Repos: tt.input}
|
||||
conf.ExpandPaths()
|
||||
|
||||
if len(conf.Repos) != len(tt.expected) {
|
||||
t.Fatalf("expected %d repos, got %d", len(tt.expected), len(conf.Repos))
|
||||
}
|
||||
|
||||
for i, repo := range conf.Repos {
|
||||
if repo.Path != tt.expected[i] {
|
||||
t.Errorf("repo %d: expected path %q, got %q", i, tt.expected[i], repo.Path)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollapsePaths(t *testing.T) {
|
||||
// Get the actual home directory for this test
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get home directory: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []Repo
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "collapse home directory",
|
||||
input: []Repo{
|
||||
{Path: filepath.Join(home, "code/project"), Remote: "git@github.com:user/project.git"},
|
||||
},
|
||||
expected: []string{"$HOME/code/project"},
|
||||
},
|
||||
{
|
||||
name: "collapse multiple paths",
|
||||
input: []Repo{
|
||||
{Path: filepath.Join(home, "code/project1"), Remote: "git@github.com:user/project1.git"},
|
||||
{Path: filepath.Join(home, "code/project2"), Remote: "git@github.com:user/project2.git"},
|
||||
},
|
||||
expected: []string{
|
||||
"$HOME/code/project1",
|
||||
"$HOME/code/project2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "path not under home",
|
||||
input: []Repo{
|
||||
{Path: "/opt/repos/project", Remote: "git@github.com:user/project.git"},
|
||||
},
|
||||
expected: []string{"/opt/repos/project"},
|
||||
},
|
||||
{
|
||||
name: "mixed paths",
|
||||
input: []Repo{
|
||||
{Path: filepath.Join(home, "code/project"), Remote: "git@github.com:user/project.git"},
|
||||
{Path: "/opt/repos/other", Remote: "git@github.com:user/other.git"},
|
||||
},
|
||||
expected: []string{
|
||||
"$HOME/code/project",
|
||||
"/opt/repos/other",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty repos",
|
||||
input: []Repo{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "home directory itself",
|
||||
input: []Repo{
|
||||
{Path: home, Remote: "git@github.com:user/home.git"},
|
||||
},
|
||||
expected: []string{"$HOME"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
conf := MGConfig{Repos: tt.input}
|
||||
conf.CollapsePaths()
|
||||
|
||||
if len(conf.Repos) != len(tt.expected) {
|
||||
t.Fatalf("expected %d repos, got %d", len(tt.expected), len(conf.Repos))
|
||||
}
|
||||
|
||||
for i, repo := range conf.Repos {
|
||||
if repo.Path != tt.expected[i] {
|
||||
t.Errorf("repo %d: expected path %q, got %q", i, tt.expected[i], repo.Path)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandAndCollapse_Roundtrip(t *testing.T) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get home directory: %v", err)
|
||||
}
|
||||
|
||||
// Start with $HOME-based paths (as stored in config)
|
||||
original := MGConfig{
|
||||
Repos: []Repo{
|
||||
{Path: "$HOME/code/project1", Remote: "git@github.com:user/project1.git"},
|
||||
{Path: "$HOME/go/src/github.com/user/repo", Remote: "git@github.com:user/repo.git"},
|
||||
{Path: "/opt/external/repo", Remote: "git@github.com:user/external.git"},
|
||||
},
|
||||
}
|
||||
|
||||
// Expand paths (as done when loading)
|
||||
conf := MGConfig{
|
||||
Repos: make([]Repo, len(original.Repos)),
|
||||
}
|
||||
copy(conf.Repos, original.Repos)
|
||||
conf.ExpandPaths()
|
||||
|
||||
// Verify expansion worked
|
||||
expectedExpanded := []string{
|
||||
filepath.Join(home, "code/project1"),
|
||||
filepath.Join(home, "go/src/github.com/user/repo"),
|
||||
"/opt/external/repo",
|
||||
}
|
||||
for i, repo := range conf.Repos {
|
||||
if repo.Path != expectedExpanded[i] {
|
||||
t.Errorf("after expand, repo %d: expected %q, got %q", i, expectedExpanded[i], repo.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// Collapse paths (as done when saving)
|
||||
conf.CollapsePaths()
|
||||
|
||||
// Verify we're back to the original
|
||||
for i, repo := range conf.Repos {
|
||||
if repo.Path != original.Repos[i].Path {
|
||||
t.Errorf("after roundtrip, repo %d: expected %q, got %q", i, original.Repos[i].Path, repo.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSave_CollapsesPaths(t *testing.T) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get home directory: %v", err)
|
||||
}
|
||||
|
||||
// Create a temp file for the config
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "mgconfig")
|
||||
t.Setenv("MGCONFIG", configPath)
|
||||
|
||||
// Create config with expanded (absolute) paths
|
||||
conf := MGConfig{
|
||||
Repos: []Repo{
|
||||
{Path: filepath.Join(home, "code/project"), Remote: "git@github.com:user/project.git"},
|
||||
{Path: "/opt/external/repo", Remote: "git@github.com:user/external.git"},
|
||||
},
|
||||
Aliases: map[string]string{"test": "echo test"},
|
||||
}
|
||||
|
||||
// Save should collapse paths
|
||||
err = conf.Save()
|
||||
if err != nil {
|
||||
t.Fatalf("Save() failed: %v", err)
|
||||
}
|
||||
|
||||
// Read back and verify paths are collapsed
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read saved config: %v", err)
|
||||
}
|
||||
|
||||
var saved MGConfig
|
||||
if err := json.Unmarshal(data, &saved); err != nil {
|
||||
t.Fatalf("failed to parse saved config: %v", err)
|
||||
}
|
||||
|
||||
expectedPaths := []string{
|
||||
"$HOME/code/project",
|
||||
"/opt/external/repo",
|
||||
}
|
||||
|
||||
for i, repo := range saved.Repos {
|
||||
if repo.Path != expectedPaths[i] {
|
||||
t.Errorf("saved repo %d: expected path %q, got %q", i, expectedPaths[i], repo.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify original config wasn't modified
|
||||
if conf.Repos[0].Path != filepath.Join(home, "code/project") {
|
||||
t.Errorf("original config was modified: expected %q, got %q",
|
||||
filepath.Join(home, "code/project"), conf.Repos[0].Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMGConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantRepos int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid config",
|
||||
input: `{
|
||||
"Repos": [
|
||||
{"Path": "$HOME/code/project", "Remote": "git@github.com:user/project.git"}
|
||||
],
|
||||
"Aliases": {"gc": "git gc"}
|
||||
}`,
|
||||
wantRepos: 1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty config",
|
||||
input: `{"Repos": [], "Aliases": {}}`,
|
||||
wantRepos: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid json",
|
||||
input: `{invalid}`,
|
||||
wantRepos: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "multiple repos",
|
||||
input: `{
|
||||
"Repos": [
|
||||
{"Path": "$HOME/code/project1", "Remote": "git@github.com:user/project1.git"},
|
||||
{"Path": "$HOME/code/project2", "Remote": "git@github.com:user/project2.git"}
|
||||
]
|
||||
}`,
|
||||
wantRepos: 2,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
conf, err := ParseMGConfig([]byte(tt.input))
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseMGConfig() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !tt.wantErr && len(conf.Repos) != tt.wantRepos {
|
||||
t.Errorf("ParseMGConfig() got %d repos, want %d", len(conf.Repos), tt.wantRepos)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRepo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
initial []Repo
|
||||
addPath string
|
||||
addRemote string
|
||||
wantErr bool
|
||||
wantCount int
|
||||
}{
|
||||
{
|
||||
name: "add to empty",
|
||||
initial: []Repo{},
|
||||
addPath: "$HOME/code/new",
|
||||
addRemote: "git@github.com:user/new.git",
|
||||
wantErr: false,
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "add to existing",
|
||||
initial: []Repo{
|
||||
{Path: "$HOME/code/existing", Remote: "git@github.com:user/existing.git"},
|
||||
},
|
||||
addPath: "$HOME/code/new",
|
||||
addRemote: "git@github.com:user/new.git",
|
||||
wantErr: false,
|
||||
wantCount: 2,
|
||||
},
|
||||
{
|
||||
name: "add duplicate",
|
||||
initial: []Repo{
|
||||
{Path: "$HOME/code/existing", Remote: "git@github.com:user/existing.git"},
|
||||
},
|
||||
addPath: "$HOME/code/existing",
|
||||
addRemote: "git@github.com:user/existing.git",
|
||||
wantErr: true,
|
||||
wantCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
conf := MGConfig{Repos: tt.initial}
|
||||
err := conf.AddRepo(tt.addPath, tt.addRemote)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("AddRepo() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if len(conf.Repos) != tt.wantCount {
|
||||
t.Errorf("AddRepo() repo count = %d, want %d", len(conf.Repos), tt.wantCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelRepo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
initial []Repo
|
||||
delPath string
|
||||
wantErr bool
|
||||
wantCount int
|
||||
}{
|
||||
{
|
||||
name: "delete existing",
|
||||
initial: []Repo{
|
||||
{Path: "$HOME/code/project", Remote: "git@github.com:user/project.git"},
|
||||
},
|
||||
delPath: "$HOME/code/project",
|
||||
wantErr: false,
|
||||
wantCount: 0,
|
||||
},
|
||||
{
|
||||
name: "delete from multiple",
|
||||
initial: []Repo{
|
||||
{Path: "$HOME/code/project1", Remote: "git@github.com:user/project1.git"},
|
||||
{Path: "$HOME/code/project2", Remote: "git@github.com:user/project2.git"},
|
||||
},
|
||||
delPath: "$HOME/code/project1",
|
||||
wantErr: false,
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "delete non-existent",
|
||||
initial: []Repo{
|
||||
{Path: "$HOME/code/project", Remote: "git@github.com:user/project.git"},
|
||||
},
|
||||
delPath: "$HOME/code/other",
|
||||
wantErr: true,
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "delete from empty",
|
||||
initial: []Repo{},
|
||||
delPath: "$HOME/code/project",
|
||||
wantErr: true,
|
||||
wantCount: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
conf := MGConfig{Repos: tt.initial}
|
||||
err := conf.DelRepo(tt.delPath)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DelRepo() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if len(conf.Repos) != tt.wantCount {
|
||||
t.Errorf("DelRepo() repo count = %d, want %d", len(conf.Repos), tt.wantCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
initial []Repo
|
||||
merge []Repo
|
||||
wantCount int
|
||||
wantDuplicates int
|
||||
wantNewPaths int
|
||||
}{
|
||||
{
|
||||
name: "merge into empty",
|
||||
initial: []Repo{},
|
||||
merge: []Repo{
|
||||
{Path: "$HOME/code/new", Remote: "git@github.com:user/new.git"},
|
||||
},
|
||||
wantCount: 1,
|
||||
wantDuplicates: 0,
|
||||
wantNewPaths: 1,
|
||||
},
|
||||
{
|
||||
name: "merge with duplicates",
|
||||
initial: []Repo{
|
||||
{Path: "$HOME/code/existing", Remote: "git@github.com:user/existing.git"},
|
||||
},
|
||||
merge: []Repo{
|
||||
{Path: "$HOME/code/existing", Remote: "git@github.com:user/existing.git"},
|
||||
{Path: "$HOME/code/new", Remote: "git@github.com:user/new.git"},
|
||||
},
|
||||
wantCount: 2,
|
||||
wantDuplicates: 1,
|
||||
wantNewPaths: 1,
|
||||
},
|
||||
{
|
||||
name: "merge all duplicates",
|
||||
initial: []Repo{
|
||||
{Path: "$HOME/code/project", Remote: "git@github.com:user/project.git"},
|
||||
},
|
||||
merge: []Repo{
|
||||
{Path: "$HOME/code/project", Remote: "git@github.com:user/project.git"},
|
||||
},
|
||||
wantCount: 1,
|
||||
wantDuplicates: 1,
|
||||
wantNewPaths: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
conf := MGConfig{Repos: tt.initial}
|
||||
mergeConf := MGConfig{Repos: tt.merge}
|
||||
|
||||
stats, err := conf.Merge(mergeConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Merge() unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(conf.Repos) != tt.wantCount {
|
||||
t.Errorf("Merge() repo count = %d, want %d", len(conf.Repos), tt.wantCount)
|
||||
}
|
||||
if stats.Duplicates != tt.wantDuplicates {
|
||||
t.Errorf("Merge() duplicates = %d, want %d", stats.Duplicates, tt.wantDuplicates)
|
||||
}
|
||||
if len(stats.NewPaths) != tt.wantNewPaths {
|
||||
t.Errorf("Merge() new paths = %d, want %d", len(stats.NewPaths), tt.wantNewPaths)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRepoPaths(t *testing.T) {
|
||||
conf := MGConfig{
|
||||
Repos: []Repo{
|
||||
{Path: "$HOME/code/project1", Remote: "git@github.com:user/project1.git"},
|
||||
{Path: "$HOME/code/project2", Remote: "git@github.com:user/project2.git"},
|
||||
},
|
||||
}
|
||||
|
||||
paths := conf.GetRepoPaths()
|
||||
|
||||
if len(paths) != 2 {
|
||||
t.Fatalf("GetRepoPaths() returned %d paths, want 2", len(paths))
|
||||
}
|
||||
|
||||
expected := []string{"$HOME/code/project1", "$HOME/code/project2"}
|
||||
for i, path := range paths {
|
||||
if path != expected[i] {
|
||||
t.Errorf("GetRepoPaths()[%d] = %q, want %q", i, path, expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user