package git
import (
"context"
"errors"
"os"
"path/filepath"
"slices"
"strings"
"testing"
)
func TestExtractHostFromPrompt(t *testing.T) {
tests := []struct {
prompt string
wantHost string
}{
{"Username for 'https://github.com': ", "github.com"},
{"Password for 'https://x-access-token@github.com': ", "github.com"},
{"Password for 'https://github.com.evil/repo.git': ", "github.com.evil"},
{"Password for 'https://x-tangled-token@knot.example.com:443': ", "knot.example.com"},
{"Password for 'https://knot.example.com': ", "knot.example.com"},
{"random string without url", ""},
}
for _, tt := range tests {
got := ExtractHostFromPrompt(tt.prompt)
if got != tt.wantHost {
t.Errorf("ExtractHostFromPrompt(%q) = %q, want %q", tt.prompt, got, tt.wantHost)
}
}
}
func TestHandleAskpassExactHostMatching(t *testing.T) {
u, err := HandleAskpass("Username for 'https://github.com': ", "github.com", "mytoken")
if err != nil || u != "x-access-token" {
t.Fatalf("source username failed: %v, %q", err, u)
}
p, err := HandleAskpass("Password for 'https://x-access-token@github.com': ", "github.com", "mytoken")
if err != nil || p != "mytoken" {
t.Fatalf("source password failed: %v, %q", err, p)
}
_, err = HandleAskpass("Password for 'https://github.com.evil': ", "github.com", "mytoken")
if err == nil {
t.Fatal("expected error for github.com.evil subdomain mismatch")
}
_, err = HandleAskpass("Password for 'https://attacker.com': ", "github.com", "mytoken")
if err == nil {
t.Fatal("expected error for attacker.com mismatch")
}
}
func TestCreateAskpassScriptExcludesSecrets(t *testing.T) {
dir := t.TempDir()
scriptPath, err := CreateAskpassScript(dir)
if err != nil {
t.Fatal(err)
}
content, err := os.ReadFile(scriptPath)
if err != nil {
t.Fatal(err)
}
secretToken := "supersecret12345"
if strings.Contains(string(content), secretToken) {
t.Fatal("askpass script contains secret token!")
}
if strings.Contains(string(content), "Bearer") {
t.Fatal("askpass script contains Bearer string!")
}
if !strings.Contains(string(content), "askpass") {
t.Fatal("askpass script does not invoke askpass command")
}
}
func TestRedactSecrets(t *testing.T) {
secret1 := "ghp_tokensecret98765"
secret2 := "capability-secret-token"
logMsg := "clone failed with ghp_tokensecret98765 and serving failed with capability-secret-token"
redacted := RedactSecrets(logMsg, secret1, secret2)
if strings.Contains(redacted, secret1) || strings.Contains(redacted, secret2) {
t.Fatalf("secret was not redacted: %s", redacted)
}
if !strings.Contains(redacted, "[REDACTED]") {
t.Fatalf("missing [REDACTED] in %s", redacted)
}
}
func TestDiskLimitEnforcement(t *testing.T) {
dir := t.TempDir()
watchDir := filepath.Join(dir, "scratch")
_ = os.MkdirAll(watchDir, 0700)
runner := RealCommandRunner{
MaxDiskBytes: 100,
WatchDir: watchDir,
}
testFile := filepath.Join(watchDir, "bigfile.txt")
_, err := runner.Run(context.Background(), dir, nil, "sh", "-c", "head -c 10000 /dev/zero > '"+testFile+"' && sleep 1")
if err == nil {
t.Fatal("expected disk limit error, got nil")
}
if !strings.Contains(err.Error(), "scratch disk limit exceeded") {
t.Fatalf("expected ErrDiskLimitExceeded, got: %v", err)
}
}
func TestClassifyGitErrorKeepsRepoNotFoundRetryable(t *testing.T) {
// github answers a repo the token cannot see with both phrases at once
output := "remote: Repository not found.\nfatal: Authentication failed for 'https://github.com/alice/nope.git/'"
err := ClassifyGitError(output, errors.New("exit status 128"))
if !errors.Is(err, ErrRepoNotFound) {
t.Fatalf("error = %v, want ErrRepoNotFound", err)
}
if errors.Is(err, ErrAuthRequired) {
t.Fatal("a missing repository masqueraded as authorization required; the batch token would be scrubbed")
}
err = ClassifyGitError("fatal: Authentication failed for 'https://github.com/alice/repo.git/'", nil)
if !errors.Is(err, ErrAuthRequired) || errors.Is(err, ErrRepoNotFound) {
t.Fatalf("error = %v, want ErrAuthRequired only", err)
}
}
func TestHardenedGitEnvTrustsTheProcessCertFile(t *testing.T) {
t.Setenv("SSL_CERT_FILE", "")
for _, kv := range HardenedGitEnv("", "", t.TempDir(), "", "") {
if strings.HasPrefix(kv, "GIT_SSL_CAINFO=") {
t.Fatalf("set %s with no SSL_CERT_FILE", kv)
}
}
t.Setenv("SSL_CERT_FILE", "/etc/local-root.crt")
env := HardenedGitEnv("", "", t.TempDir(), "", "")
if !slices.Contains(env, "GIT_SSL_CAINFO=/etc/local-root.crt") {
t.Fatalf("env %q doesn't point git at SSL_CERT_FILE", env)
}
}