You've already forked agentic-coding-workflow
124 lines
3.5 KiB
Go
124 lines
3.5 KiB
Go
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")
|
|
}
|