1 package ssh
2
3 import (
4 "errors"
5 "fmt"
6 "net/url"
7 "os"
8 "os/exec"
9 "testing"
10
11 "github.com/MakeNowJust/heredoc"
12 "github.com/cli/safeexec"
13 )
14
15 func TestTranslator(t *testing.T) {
16 if _, err := safeexec.LookPath("ssh"); err != nil {
17 t.Skip("no ssh found on system")
18 }
19
20 tests := []struct {
21 name string
22 sshConfig string
23 arg string
24 want string
25 }{
26 {
27 name: "translate SSH URL",
28 sshConfig: heredoc.Doc(`
29 Host github-*
30 Hostname github.com
31 `),
32 arg: "ssh://git@github-foo/owner/repo.git",
33 want: "ssh://git@github.com/owner/repo.git",
34 },
35 {
36 name: "does not translate HTTPS URL",
37 sshConfig: heredoc.Doc(`
38 Host github-*
39 Hostname github.com
40 `),
41 arg: "https://github-foo/owner/repo.git",
42 want: "https://github-foo/owner/repo.git",
43 },
44 {
45 name: "treats ssh.github.com as github.com",
46 sshConfig: heredoc.Doc(`
47 Host github.com
48 Hostname ssh.github.com
49 `),
50 arg: "ssh://git@github.com/owner/repo.git",
51 want: "ssh://git@github.com/owner/repo.git",
52 },
53 }
54 for _, tt := range tests {
55 t.Run(tt.name, func(t *testing.T) {
56 f, err := os.CreateTemp("", "ssh-config.*")
57 if err != nil {
58 t.Fatalf("error creating file: %v", err)
59 }
60 _, err = f.WriteString(tt.sshConfig)
61 _ = f.Close()
62 if err != nil {
63 t.Fatalf("error writing ssh config: %v", err)
64 }
65
66 tr := &Translator{
67 newCommand: func(exe string, args ...string) *exec.Cmd {
68 args = append([]string{"-F", f.Name()}, args...)
69 return exec.Command(exe, args...)
70 },
71 }
72 u, err := url.Parse(tt.arg)
73 if err != nil {
74 t.Fatalf("error parsing URL: %v", err)
75 }
76 res := tr.Translate(u)
77 if got := res.String(); got != tt.want {
78 t.Errorf("expected %q, got %q", tt.want, got)
79 }
80 })
81 }
82 }
83
84 func TestHelperProcess(t *testing.T) {
85 if os.Getenv("GH_WANT_HELPER_PROCESS") != "1" {
86 return
87 }
88 if err := func(args []string) error {
89 if len(args) < 3 || args[2] == "error" {
90 return errors.New("fatal")
91 }
92 if args[2] == "empty.io" {
93 return nil
94 }
95 fmt.Fprintf(os.Stdout, "hostname %s\n", args[2])
96 return nil
97 }(os.Args[3:]); err != nil {
98 fmt.Fprintln(os.Stderr, err)
99 os.Exit(1)
100 }
101 os.Exit(0)
102 }
103
104 func TestTranslator_caching(t *testing.T) {
105 countLookPath := 0
106 countNewCommand := 0
107 tr := &Translator{
108 lookPath: func(s string) (string, error) {
109 countLookPath++
110 return "/path/to/ssh", nil
111 },
112 newCommand: func(exe string, args ...string) *exec.Cmd {
113 args = append([]string{"-test.run=TestHelperProcess", "--", exe}, args...)
114 c := exec.Command(os.Args[0], args...)
115 c.Env = []string{"GH_WANT_HELPER_PROCESS=1"}
116 countNewCommand++
117 return c
118 },
119 }
120
121 tests := []struct {
122 input string
123 result string
124 }{
125 {
126 input: "ssh://github1.com/owner/repo.git",
127 result: "github1.com",
128 },
129 {
130 input: "ssh://github2.com/owner/repo.git",
131 result: "github2.com",
132 },
133 {
134 input: "ssh://empty.io/owner/repo.git",
135 result: "empty.io",
136 },
137 {
138 input: "ssh://error/owner/repo.git",
139 result: "error",
140 },
141 }
142 for _, tt := range tests {
143 t.Run(tt.input, func(t *testing.T) {
144 u, err := url.Parse(tt.input)
145 if err != nil {
146 t.Fatalf("error parsing URL: %v", err)
147 }
148 if res := tr.Translate(u); res.Host != tt.result {
149 t.Errorf("expected github.com, got: %q", res.Host)
150 }
151 if res := tr.Translate(u); res.Host != tt.result {
152 t.Errorf("expected github.com, got: %q (second call)", res.Host)
153 }
154 })
155 }
156
157 if countLookPath != 1 {
158 t.Errorf("expected lookPath to happen 1 time; actual: %d", countLookPath)
159 }
160 if countNewCommand != len(tests) {
161 t.Errorf("expected ssh command to shell out %d times; actual: %d", len(tests), countNewCommand)
162 }
163 }
164
View as plain text