package git import ( "context" "errors" "os" "os/exec" "strings" "testing" "time" "github.com/stretchr/testify/require" ) // envValue 从 "K=V" 形式的 env 列表中取出 key 对应的 value。 func envValue(env []string, key string) string { prefix := key + "=" for _, e := range env { if strings.HasPrefix(e, prefix) { return strings.TrimPrefix(e, prefix) } } return "" } func TestDefaultRunner_exec_BinaryNotFound(t *testing.T) { t.Parallel() r := NewDefaultRunner(Config{Binary: "this-is-not-a-real-binary-xyz"}) _, _, err := r.exec(context.Background(), "", nil, "--version") require.Error(t, err) var ge *GitError require.True(t, errors.As(err, &ge)) } func TestDefaultRunner_exec_NonZeroExit(t *testing.T) { t.Parallel() gitAvailable(t) r := newTestRunner(t) _, _, err := r.exec(context.Background(), t.TempDir(), nil, "rev-parse", "HEAD") require.Error(t, err) var ge *GitError require.True(t, errors.As(err, &ge)) require.NotZero(t, ge.ExitCode) } func TestDefaultRunner_exec_Timeout(t *testing.T) { t.Parallel() gitAvailable(t) r := newTestRunner(t) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) defer cancel() // 任意命令都会因 ctx 已过期而立刻被 kill _, _, err := r.exec(ctx, "", nil, "version") require.Error(t, err) } func TestIsAuthError(t *testing.T) { t.Parallel() cases := []struct { stderr string want bool }{ {"Authentication failed for https://...", true}, {"fatal: could not read Username for 'https://...'", true}, {"Permission denied (publickey).", true}, {"random other failure", false}, } for _, c := range cases { got := IsAuthError(&GitError{Stderr: c.stderr}) require.Equal(t, c.want, got, "stderr=%q", c.stderr) } } func TestCredentialEnv_None(t *testing.T) { t.Parallel() env, cleanup, err := credentialEnv(t.TempDir(), nil) require.NoError(t, err) require.Nil(t, env) cleanup() // 不应 panic } func TestCredentialEnv_PAT(t *testing.T) { t.Parallel() tmp := t.TempDir() env, cleanup, err := credentialEnv(tmp, &Credential{Kind: CredentialPAT, Username: "alice", Secret: []byte("tok")}) require.NoError(t, err) defer cleanup() joined := strings.Join(env, "\n") require.Contains(t, joined, "GIT_ASKPASS=") require.Contains(t, joined, "GIT_TERMINAL_PROMPT=0") // 凭据通过 0600 临时文件下发(避免出现在进程 env 中),env 仅暴露文件路径, // 文件内容须等于用户名/令牌原文。 require.Contains(t, joined, "GIT_PAT_USERNAME_FILE=") require.Contains(t, joined, "GIT_PAT_TOKEN_FILE=") userFile := envValue(env, "GIT_PAT_USERNAME_FILE") tokenFile := envValue(env, "GIT_PAT_TOKEN_FILE") require.NotEmpty(t, userFile) require.NotEmpty(t, tokenFile) userData, err := os.ReadFile(userFile) require.NoError(t, err) require.Equal(t, "alice", string(userData)) tokenData, err := os.ReadFile(tokenFile) require.NoError(t, err) require.Equal(t, "tok", string(tokenData)) } func TestCredentialEnv_SSHKey(t *testing.T) { t.Parallel() if _, err := exec.LookPath("ssh"); err != nil { t.Skip("ssh not available") } tmp := t.TempDir() env, cleanup, err := credentialEnv(tmp, &Credential{ Kind: CredentialSSHKey, Secret: []byte("-----BEGIN OPENSSH PRIVATE KEY-----\nstub\n-----END OPENSSH PRIVATE KEY-----\n"), }) require.NoError(t, err) defer cleanup() require.Len(t, env, 1) require.True(t, strings.HasPrefix(env[0], "GIT_SSH_COMMAND=")) require.Contains(t, env[0], "IdentitiesOnly=yes") require.Contains(t, env[0], "StrictHostKeyChecking=accept-new") }