Skip to content

Commit 37c3c8b

Browse files
committed
test: added test cases and ci fix
1 parent add9af4 commit 37c3c8b

4 files changed

Lines changed: 412 additions & 6 deletions

File tree

.goreleaser.yaml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ builds:
3333

3434
archives:
3535
- id: default
36-
format: tar.gz
36+
formats: [tar.gz]
3737
name_template: >-
3838
{{ .ProjectName }}_
3939
{{- .Os }}_
@@ -92,10 +92,9 @@ brews:
9292
system "#{bin}/git-profile", "version"
9393
install: |
9494
bin.install "git-profile"
95-
# Shell completions
96-
bash_completion.install Utils.safe_popen_read(bin/"git-profile", "completion", "bash") => "git-profile"
97-
zsh_completion.install Utils.safe_popen_read(bin/"git-profile", "completion", "zsh") => "_git-profile"
98-
fish_completion.install Utils.safe_popen_read(bin/"git-profile", "completion", "fish") => "git-profile.fish"
95+
# generate_completions_from_executable runs `git-profile completion <shell>`
96+
# and writes the output to the correct Homebrew completion directory.
97+
generate_completions_from_executable(bin/"git-profile", "completion")
9998
10099
# ── Arch Linux: AUR ──────────────────────────────────────────────────────
101100
aurs:

cmd/cmd_test.go

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,41 @@
44
package cmd_test
55

66
import (
7+
"os"
8+
"os/exec"
79
"path/filepath"
810
"testing"
911

1012
"github.com/hapiio/git-profile/cmd"
1113
"github.com/hapiio/git-profile/internal/config"
1214
)
1315

16+
// initGitRepo creates a temporary directory, runs git init inside it, and
17+
// sets a minimal local git identity so git operations succeed.
18+
func initGitRepo(t *testing.T) string {
19+
t.Helper()
20+
dir := t.TempDir()
21+
if err := exec.Command("git", "init", dir).Run(); err != nil {
22+
t.Skipf("git not available: %v", err)
23+
}
24+
exec.Command("git", "-C", dir, "config", "user.name", "Test User").Run() //nolint:errcheck
25+
exec.Command("git", "-C", dir, "config", "user.email", "test@example.com").Run() //nolint:errcheck
26+
return dir
27+
}
28+
29+
// chdir changes the working directory for the test and restores it via t.Cleanup.
30+
func chdir(t *testing.T, dir string) {
31+
t.Helper()
32+
orig, err := os.Getwd()
33+
if err != nil {
34+
t.Fatal(err)
35+
}
36+
if err := os.Chdir(dir); err != nil {
37+
t.Fatal(err)
38+
}
39+
t.Cleanup(func() { os.Chdir(orig) }) //nolint:errcheck
40+
}
41+
1442
// run executes git-profile with --config <cfgPath> prepended to args.
1543
func run(t *testing.T, cfgPath string, args ...string) error {
1644
t.Helper()
@@ -282,3 +310,212 @@ func TestVersion_NoError(t *testing.T) {
282310
cfg := tmpCfg(t)
283311
mustRun(t, cfg, "version")
284312
}
313+
314+
// ---- current ----
315+
316+
func TestCurrent_NotInRepo_Errors(t *testing.T) {
317+
cfg := tmpCfg(t)
318+
chdir(t, t.TempDir()) // plain directory, not a git repo
319+
err := run(t, cfg, "current")
320+
if err == nil {
321+
t.Fatal("current outside git repo should error, got nil")
322+
}
323+
}
324+
325+
func TestCurrent_InRepo_NoError(t *testing.T) {
326+
cfg := tmpCfg(t)
327+
dir := initGitRepo(t)
328+
chdir(t, dir)
329+
mustRun(t, cfg, "current")
330+
}
331+
332+
func TestCurrent_InRepo_WithMatchedProfile_NoError(t *testing.T) {
333+
cfg := tmpCfg(t)
334+
dir := initGitRepo(t)
335+
// Add a profile that matches the git identity set by initGitRepo.
336+
mustRun(t, cfg, "add", "--id", "test", "--name", "Test User", "--email", "test@example.com")
337+
chdir(t, dir)
338+
mustRun(t, cfg, "current")
339+
}
340+
341+
// ---- use ----
342+
343+
func TestUse_NotFound_Errors(t *testing.T) {
344+
cfg := tmpCfg(t)
345+
err := run(t, cfg, "use", "ghost")
346+
if err == nil {
347+
t.Fatal("use of nonexistent profile should error, got nil")
348+
}
349+
}
350+
351+
func TestUse_NotInRepo_Errors(t *testing.T) {
352+
cfg := tmpCfg(t)
353+
chdir(t, t.TempDir())
354+
mustRun(t, cfg, "add", "--id", "work", "--name", "Jane", "--email", "jane@co.com")
355+
err := run(t, cfg, "use", "work")
356+
if err == nil {
357+
t.Fatal("use without --global outside git repo should error, got nil")
358+
}
359+
}
360+
361+
func TestUse_InRepo_Success(t *testing.T) {
362+
cfg := tmpCfg(t)
363+
dir := initGitRepo(t)
364+
mustRun(t, cfg, "add", "--id", "work", "--name", "Jane Dev", "--email", "jane@co.com")
365+
chdir(t, dir)
366+
mustRun(t, cfg, "use", "work")
367+
}
368+
369+
func TestUse_InRepo_WithSSHKey_Success(t *testing.T) {
370+
cfg := tmpCfg(t)
371+
dir := initGitRepo(t)
372+
mustRun(t, cfg, "add", "--id", "oss", "--name", "Dev", "--email", "dev@oss.org", "--ssh-key", "~/.ssh/id_oss")
373+
chdir(t, dir)
374+
mustRun(t, cfg, "use", "oss")
375+
}
376+
377+
// ---- set-default ----
378+
379+
func TestSetDefault_NotFound_Errors(t *testing.T) {
380+
cfg := tmpCfg(t)
381+
err := run(t, cfg, "set-default", "ghost")
382+
if err == nil {
383+
t.Fatal("set-default of nonexistent profile should error, got nil")
384+
}
385+
}
386+
387+
func TestSetDefault_NotInRepo_Errors(t *testing.T) {
388+
cfg := tmpCfg(t)
389+
chdir(t, t.TempDir())
390+
mustRun(t, cfg, "add", "--id", "work", "--name", "Jane", "--email", "jane@co.com")
391+
err := run(t, cfg, "set-default", "work")
392+
if err == nil {
393+
t.Fatal("set-default without --global outside git repo should error, got nil")
394+
}
395+
}
396+
397+
func TestSetDefault_InRepo_Success(t *testing.T) {
398+
cfg := tmpCfg(t)
399+
dir := initGitRepo(t)
400+
mustRun(t, cfg, "add", "--id", "work", "--name", "Jane", "--email", "jane@co.com")
401+
chdir(t, dir)
402+
mustRun(t, cfg, "set-default", "work")
403+
}
404+
405+
// ---- ensure ----
406+
407+
func TestEnsure_NoProfiles_Errors(t *testing.T) {
408+
cfg := tmpCfg(t)
409+
// empty config — no profiles at all
410+
err := run(t, cfg, "ensure")
411+
if err == nil {
412+
t.Fatal("ensure with no profiles should error, got nil")
413+
}
414+
}
415+
416+
func TestEnsure_NoDefault_NoTTY_Errors(t *testing.T) {
417+
cfg := tmpCfg(t)
418+
// Use a unique ID that won't match any real gitprofile.default in the user's
419+
// git config, so both local and global default lookups fall through.
420+
mustRun(t, cfg, "add", "--id", "norealdeffound", "--name", "Jane", "--email", "jane@co.com")
421+
// Chdir to a plain dir (not a git repo) so git.GetConfig returns an error
422+
// and cannot pick up a local gitprofile.default from the project repo.
423+
chdir(t, t.TempDir())
424+
err := run(t, cfg, "ensure")
425+
if err == nil {
426+
t.Fatal("ensure with no matching default and no TTY should error, got nil")
427+
}
428+
}
429+
430+
func TestEnsure_WithLocalDefault_InRepo_Success(t *testing.T) {
431+
cfg := tmpCfg(t)
432+
dir := initGitRepo(t)
433+
mustRun(t, cfg, "add", "--id", "work", "--name", "Jane", "--email", "jane@co.com")
434+
chdir(t, dir)
435+
mustRun(t, cfg, "set-default", "work")
436+
mustRun(t, cfg, "ensure")
437+
}
438+
439+
// ---- install-hooks ----
440+
441+
func TestInstallHooks_NotInRepo_Errors(t *testing.T) {
442+
cfg := tmpCfg(t)
443+
chdir(t, t.TempDir())
444+
err := run(t, cfg, "install-hooks")
445+
if err == nil {
446+
t.Fatal("install-hooks outside git repo should error, got nil")
447+
}
448+
}
449+
450+
func TestInstallHooks_InRepo_CreatesHooks(t *testing.T) {
451+
cfg := tmpCfg(t)
452+
dir := initGitRepo(t)
453+
chdir(t, dir)
454+
mustRun(t, cfg, "install-hooks")
455+
456+
for _, hookName := range []string{"prepare-commit-msg", "pre-push"} {
457+
hookPath := filepath.Join(dir, ".git", "hooks", hookName)
458+
if _, err := os.Stat(hookPath); os.IsNotExist(err) {
459+
t.Errorf("hook %q was not created at %s", hookName, hookPath)
460+
}
461+
}
462+
}
463+
464+
func TestInstallHooks_Idempotent(t *testing.T) {
465+
cfg := tmpCfg(t)
466+
dir := initGitRepo(t)
467+
chdir(t, dir)
468+
// Running twice should not error (second run updates existing hooks).
469+
mustRun(t, cfg, "install-hooks")
470+
mustRun(t, cfg, "install-hooks")
471+
}
472+
473+
// ---- import ----
474+
475+
func TestImport_InRepo_WithID_Success(t *testing.T) {
476+
cfg := tmpCfg(t)
477+
dir := initGitRepo(t)
478+
chdir(t, dir)
479+
mustRun(t, cfg, "import", "--id", "imported")
480+
481+
p := loadCfg(t, cfg).Profiles["imported"]
482+
if p.GitUser != "Test User" {
483+
t.Errorf("GitUser = %q, want %q", p.GitUser, "Test User")
484+
}
485+
if p.GitEmail != "test@example.com" {
486+
t.Errorf("GitEmail = %q, want %q", p.GitEmail, "test@example.com")
487+
}
488+
}
489+
490+
func TestImport_NoID_NonTTY_Errors(t *testing.T) {
491+
cfg := tmpCfg(t)
492+
dir := initGitRepo(t)
493+
chdir(t, dir)
494+
// Cobra reuses the command struct between RunArgs calls and pflag does not
495+
// reset flag variables to their defaults between executions. Pass --id ""
496+
// explicitly so the id variable is written as empty regardless of prior state.
497+
err := run(t, cfg, "import", "--id", "")
498+
if err == nil {
499+
t.Fatal("import with empty --id in non-TTY should error, got nil")
500+
}
501+
}
502+
503+
func TestImport_DuplicateID_Errors(t *testing.T) {
504+
cfg := tmpCfg(t)
505+
dir := initGitRepo(t)
506+
chdir(t, dir)
507+
mustRun(t, cfg, "import", "--id", "imported")
508+
err := run(t, cfg, "import", "--id", "imported")
509+
if err == nil {
510+
t.Fatal("import with duplicate profile ID should error, got nil")
511+
}
512+
}
513+
514+
func TestImport_NotInRepo_Errors(t *testing.T) {
515+
cfg := tmpCfg(t)
516+
chdir(t, t.TempDir())
517+
err := run(t, cfg, "import", "--id", "x")
518+
if err == nil {
519+
t.Fatal("import outside git repo (without --global) should error, got nil")
520+
}
521+
}

cmd/internal_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Package cmd internal tests cover unexported helper functions that are not
2+
// reachable from the external cmd_test package.
3+
package cmd
4+
5+
import (
6+
"os"
7+
"strings"
8+
"testing"
9+
)
10+
11+
func TestExpandHome_WithTilde(t *testing.T) {
12+
home, err := os.UserHomeDir()
13+
if err != nil {
14+
t.Skip("no home dir:", err)
15+
}
16+
got := expandHome("~/foo/bar")
17+
want := home + "/foo/bar"
18+
if got != want {
19+
t.Errorf("expandHome(%q) = %q, want %q", "~/foo/bar", got, want)
20+
}
21+
}
22+
23+
func TestExpandHome_AbsolutePath(t *testing.T) {
24+
in := "/absolute/path/key"
25+
if got := expandHome(in); got != in {
26+
t.Errorf("expandHome(%q) = %q, want unchanged", in, got)
27+
}
28+
}
29+
30+
func TestExpandHome_EmptyString(t *testing.T) {
31+
if got := expandHome(""); got != "" {
32+
t.Errorf("expandHome(%q) = %q, want %q", "", got, "")
33+
}
34+
}
35+
36+
func TestExpandHome_TildeOnly(t *testing.T) {
37+
// "~" without a slash should be returned unchanged.
38+
if got := expandHome("~"); got != "~" {
39+
t.Errorf("expandHome(%q) = %q, want unchanged", "~", got)
40+
}
41+
}
42+
43+
// ---- extractSSHKey ----
44+
45+
func TestExtractSSHKey_StandardFormat(t *testing.T) {
46+
got := extractSSHKey("ssh -i /home/user/.ssh/id_rsa -F /dev/null")
47+
if got != "/home/user/.ssh/id_rsa" {
48+
t.Errorf("extractSSHKey = %q, want %q", got, "/home/user/.ssh/id_rsa")
49+
}
50+
}
51+
52+
func TestExtractSSHKey_Empty(t *testing.T) {
53+
if got := extractSSHKey(""); got != "" {
54+
t.Errorf("extractSSHKey(%q) = %q, want empty", "", got)
55+
}
56+
}
57+
58+
func TestExtractSSHKey_NoFlag(t *testing.T) {
59+
if got := extractSSHKey("ssh -F /dev/null"); got != "" {
60+
t.Errorf("extractSSHKey = %q, want empty", got)
61+
}
62+
}
63+
64+
func TestExtractSSHKey_TildeKey(t *testing.T) {
65+
got := extractSSHKey("ssh -i ~/.ssh/id_ed25519 -F /dev/null")
66+
if got != "~/.ssh/id_ed25519" {
67+
t.Errorf("extractSSHKey = %q, want %q", got, "~/.ssh/id_ed25519")
68+
}
69+
}
70+
71+
// ---- quotedList ----
72+
73+
func TestQuotedList_Single(t *testing.T) {
74+
got := quotedList([]string{"work"})
75+
if got != `"work"` {
76+
t.Errorf("quotedList = %q, want %q", got, `"work"`)
77+
}
78+
}
79+
80+
func TestQuotedList_Multiple(t *testing.T) {
81+
got := quotedList([]string{"work", "personal"})
82+
if !strings.Contains(got, `"work"`) || !strings.Contains(got, `"personal"`) {
83+
t.Errorf("quotedList = %q, want both entries quoted", got)
84+
}
85+
}
86+
87+
func TestQuotedList_Empty(t *testing.T) {
88+
got := quotedList([]string{})
89+
if got != "" {
90+
t.Errorf("quotedList([]) = %q, want empty string", got)
91+
}
92+
}

0 commit comments

Comments
 (0)