mirror of
https://github.com/cooperspencer/gickup.git
synced 2026-10-20 08:18:38 +00:00
git lfs adds http.extraHeader to object downloads, which already carry an Authorization header from the LFS batch response. gitlab.com rejects the duplicate header with 400, so no LFS objects were downloaded since #425.
426 lines
10 KiB
Go
426 lines
10 KiB
Go
package local
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/cooperspencer/gickup/types"
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/config"
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
|
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
|
"github.com/go-git/go-git/v5/storage/memory"
|
|
)
|
|
|
|
func runPrune(t *testing.T, backups []string, keep int) []string {
|
|
t.Helper()
|
|
|
|
parentdir := t.TempDir()
|
|
for _, d := range backups {
|
|
if err := os.Mkdir(filepath.Join(parentdir, d), 0o777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if err := pruneOldBackups(parentdir, "test/repo", types.Local{Keep: keep}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
files, err := os.ReadDir(parentdir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
remaining := []string{}
|
|
for _, f := range files {
|
|
remaining = append(remaining, f.Name())
|
|
}
|
|
|
|
return remaining
|
|
}
|
|
|
|
func TestPruneKeepsNewestBackup(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
remaining := runPrune(t, []string{"1785061240", "1785115104"}, 1)
|
|
|
|
want := []string{"1785115104"}
|
|
if !reflect.DeepEqual(remaining, want) {
|
|
t.Fatalf("remaining = %v, want %v", remaining, want)
|
|
}
|
|
}
|
|
|
|
func TestPruneKeepsIssuesDirWithItsBackup(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
remaining := runPrune(t,
|
|
[]string{"1785061241", "1785061241.issues", "1785115111", "1785115111.issues"}, 1)
|
|
|
|
want := []string{"1785115111", "1785115111.issues"}
|
|
if !reflect.DeepEqual(remaining, want) {
|
|
t.Fatalf("remaining = %v, want %v", remaining, want)
|
|
}
|
|
}
|
|
|
|
func TestPruneKeepsOtherDirectories(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
remaining := runPrune(t,
|
|
[]string{"1785061241", "1785061241.issues", "dummy", "dummy.issue", "1785061241x.dummy", "1785115111", "1785115111.issues"}, 1)
|
|
|
|
want := []string{"1785061241x.dummy", "1785115111", "1785115111.issues", "dummy", "dummy.issue"}
|
|
if !reflect.DeepEqual(remaining, want) {
|
|
t.Fatalf("remaining = %v, want %v", remaining, want)
|
|
}
|
|
}
|
|
|
|
func TestPruneDeletesOtherTimestampedDirectories(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
remaining := runPrune(t,
|
|
[]string{"1785061241", "1785061241.issues", "1785061241.dummy", "1785115111", "1785115111.issues"}, 1)
|
|
|
|
want := []string{"1785115111", "1785115111.issues"}
|
|
if !reflect.DeepEqual(remaining, want) {
|
|
t.Fatalf("remaining = %v, want %v", remaining, want)
|
|
}
|
|
}
|
|
|
|
func TestPruneKeepsMultipleGenerations(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
remaining := runPrune(t,
|
|
[]string{"1785000000", "1785000000.issues", "1785061241", "1785061241.issues", "1785115111", "1785115111.issues"}, 2)
|
|
|
|
want := []string{"1785061241", "1785061241.issues", "1785115111", "1785115111.issues"}
|
|
if !reflect.DeepEqual(remaining, want) {
|
|
t.Fatalf("remaining = %v, want %v", remaining, want)
|
|
}
|
|
}
|
|
|
|
func TestPruneKeepsEverythingBelowKeepCount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
remaining := runPrune(t, []string{"1785115111", "1785115111.issues"}, 2)
|
|
|
|
want := []string{"1785115111", "1785115111.issues"}
|
|
if !reflect.DeepEqual(remaining, want) {
|
|
t.Fatalf("remaining = %v, want %v", remaining, want)
|
|
}
|
|
}
|
|
|
|
func TestRandomStringLengthAndCharset(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
|
|
got := RandomString(64)
|
|
if len(got) != 64 {
|
|
t.Fatalf("len(RandomString()) = %d, want 64", len(got))
|
|
}
|
|
|
|
for _, r := range got {
|
|
if !strings.ContainsRune(charset, r) {
|
|
t.Fatalf("unexpected rune %q in %q", r, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRandomStringZeroLength(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if got := RandomString(0); got != "" {
|
|
t.Fatalf("RandomString(0) = %q, want empty string", got)
|
|
}
|
|
}
|
|
|
|
func TestTokenAuth_NoToken(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repo := types.Repo{Token: ""}
|
|
if got := tokenAuth(repo); got != nil {
|
|
t.Fatalf("tokenAuth with empty token = %v, want nil", got)
|
|
}
|
|
}
|
|
|
|
func TestTokenAuth_NoTokenUser(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repo := types.Repo{
|
|
Token: "mytoken",
|
|
NoTokenUser: true,
|
|
}
|
|
got := tokenAuth(repo)
|
|
if got == nil {
|
|
t.Fatal("tokenAuth returned nil, want *BasicAuth")
|
|
}
|
|
if got.Username != "x-access-token" {
|
|
t.Errorf("Username = %q, want %q", got.Username, "x-access-token")
|
|
}
|
|
if got.Password != "mytoken" {
|
|
t.Errorf("Password = %q, want %q", got.Password, "mytoken")
|
|
}
|
|
}
|
|
|
|
func TestTokenAuth_WithTokenUser(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repo := types.Repo{
|
|
Token: "mytoken",
|
|
NoTokenUser: false,
|
|
Origin: types.GenRepo{
|
|
User: "octocat",
|
|
},
|
|
}
|
|
got := tokenAuth(repo)
|
|
if got == nil {
|
|
t.Fatal("tokenAuth returned nil, want *BasicAuth")
|
|
}
|
|
if got.Username != "octocat" {
|
|
t.Errorf("Username = %q, want %q", got.Username, "octocat")
|
|
}
|
|
if got.Password != "mytoken" {
|
|
t.Errorf("Password = %q, want %q", got.Password, "mytoken")
|
|
}
|
|
}
|
|
|
|
func TestToGitCmdAuth_Nil(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if got := toGitCmdAuth(nil); got != nil {
|
|
t.Fatalf("toGitCmdAuth(nil) = %v, want nil", got)
|
|
}
|
|
}
|
|
|
|
func TestToGitCmdAuth_FromTokenAuth_GitHubApp(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repo := types.Repo{
|
|
Token: "ghs_testtoken123",
|
|
NoTokenUser: true,
|
|
}
|
|
auth := tokenAuth(repo)
|
|
gitAuth := toGitCmdAuth(auth)
|
|
if gitAuth == nil {
|
|
t.Fatal("toGitCmdAuth returned nil, want *gitcmd.Auth")
|
|
}
|
|
if gitAuth.Username != "x-access-token" {
|
|
t.Errorf("Username = %q, want %q", gitAuth.Username, "x-access-token")
|
|
}
|
|
if gitAuth.Password != "ghs_testtoken123" {
|
|
t.Errorf("Password = %q, want %q", gitAuth.Password, "ghs_testtoken123")
|
|
}
|
|
|
|
env := gitAuth.Env()
|
|
want := map[string]bool{
|
|
"GICKUP_GIT_USERNAME=x-access-token": false,
|
|
"GICKUP_GIT_PASSWORD=ghs_testtoken123": false,
|
|
"GIT_CONFIG_KEY_1=credential.helper": false,
|
|
}
|
|
for _, e := range env {
|
|
if _, ok := want[e]; ok {
|
|
want[e] = true
|
|
}
|
|
if strings.Contains(e, "http.extraHeader") {
|
|
t.Errorf("unexpected http.extraHeader in env: %s", e)
|
|
}
|
|
}
|
|
for e, found := range want {
|
|
if !found {
|
|
t.Errorf("env missing %q: %v", e, env)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestToGitCmdAuth_FromUsernamePassword(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
auth := &http.BasicAuth{
|
|
Username: "myuser",
|
|
Password: "mypassword",
|
|
}
|
|
gitAuth := toGitCmdAuth(auth)
|
|
if gitAuth == nil {
|
|
t.Fatal("toGitCmdAuth returned nil, want *gitcmd.Auth")
|
|
}
|
|
if gitAuth.Username != "myuser" {
|
|
t.Errorf("Username = %q, want %q", gitAuth.Username, "myuser")
|
|
}
|
|
if gitAuth.Password != "mypassword" {
|
|
t.Errorf("Password = %q, want %q", gitAuth.Password, "mypassword")
|
|
}
|
|
}
|
|
|
|
func TestToGitCmdAuth_SSHAuth(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
auth := &ssh.PublicKeys{}
|
|
if got := toGitCmdAuth(auth); got != nil {
|
|
t.Fatalf("toGitCmdAuth(ssh) = %v, want nil", got)
|
|
}
|
|
}
|
|
|
|
func TestSanitizeRemote_RewritesCredentialURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir := t.TempDir()
|
|
r, err := git.PlainInit(dir, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to init repo: %v", err)
|
|
}
|
|
|
|
_, err = r.CreateRemote(&config.RemoteConfig{
|
|
Name: "origin",
|
|
URLs: []string{"https://xyz:expired-token-123@github.com/owner/repo.git"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to create remote: %v", err)
|
|
}
|
|
|
|
err = sanitizeRemote(r, "https://github.com/owner/repo.git")
|
|
if err != nil {
|
|
t.Fatalf("sanitizeRemote returned error: %v", err)
|
|
}
|
|
|
|
// Reopen the repository from disk to verify persistence
|
|
reopened, err := git.PlainOpen(dir)
|
|
if err != nil {
|
|
t.Fatalf("failed to reopen repo: %v", err)
|
|
}
|
|
|
|
cfg, err := reopened.Config()
|
|
if err != nil {
|
|
t.Fatalf("failed to get config: %v", err)
|
|
}
|
|
|
|
rem, ok := cfg.Remotes["origin"]
|
|
if !ok {
|
|
t.Fatal("remote origin not found")
|
|
}
|
|
|
|
wantURLs := []string{"https://github.com/owner/repo.git"}
|
|
if !reflect.DeepEqual(rem.URLs, wantURLs) {
|
|
t.Errorf("remote URLs = %v, want %v", rem.URLs, wantURLs)
|
|
}
|
|
|
|
for _, u := range rem.URLs {
|
|
if strings.Contains(u, "expired-token-123") || strings.Contains(u, "xyz:") || strings.Contains(u, "@") {
|
|
t.Errorf("token or credentials leaked in remote URL: %s", u)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSanitizeRemote_PreservesSSH(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir := t.TempDir()
|
|
r, err := git.PlainInit(dir, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to init repo: %v", err)
|
|
}
|
|
|
|
_, err = r.CreateRemote(&config.RemoteConfig{
|
|
Name: "origin",
|
|
URLs: []string{"git@github.com:owner/repo.git"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to create remote: %v", err)
|
|
}
|
|
|
|
err = sanitizeRemote(r, "https://github.com/owner/repo.git")
|
|
if err != nil {
|
|
t.Fatalf("sanitizeRemote returned error: %v", err)
|
|
}
|
|
|
|
cfg, err := r.Config()
|
|
if err != nil {
|
|
t.Fatalf("failed to get config: %v", err)
|
|
}
|
|
|
|
rem := cfg.Remotes["origin"]
|
|
wantURLs := []string{"git@github.com:owner/repo.git"}
|
|
if !reflect.DeepEqual(rem.URLs, wantURLs) {
|
|
t.Errorf("SSH remote URLs = %v, want %v", rem.URLs, wantURLs)
|
|
}
|
|
}
|
|
|
|
func TestSanitizeRemote_PreservesCleanURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir := t.TempDir()
|
|
r, err := git.PlainInit(dir, true)
|
|
if err != nil {
|
|
t.Fatalf("failed to init repo: %v", err)
|
|
}
|
|
|
|
_, err = r.CreateRemote(&config.RemoteConfig{
|
|
Name: "origin",
|
|
URLs: []string{"https://github.com/owner/repo.git"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to create remote: %v", err)
|
|
}
|
|
|
|
err = sanitizeRemote(r, "https://github.com/owner/repo.git")
|
|
if err != nil {
|
|
t.Fatalf("sanitizeRemote returned error: %v", err)
|
|
}
|
|
|
|
cfg, err := r.Config()
|
|
if err != nil {
|
|
t.Fatalf("failed to get config: %v", err)
|
|
}
|
|
|
|
rem := cfg.Remotes["origin"]
|
|
wantURLs := []string{"https://github.com/owner/repo.git"}
|
|
if !reflect.DeepEqual(rem.URLs, wantURLs) {
|
|
t.Errorf("clean remote URLs = %v, want %v", rem.URLs, wantURLs)
|
|
}
|
|
}
|
|
|
|
func TestGetPushRefSpecs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repo, err := git.Init(memory.NewStorage(), nil)
|
|
if err != nil {
|
|
t.Fatalf("init repository: %v", err)
|
|
}
|
|
|
|
hash := plumbing.NewHash("1111111111111111111111111111111111111111")
|
|
for _, name := range []plumbing.ReferenceName{
|
|
plumbing.NewBranchReferenceName("master"),
|
|
plumbing.NewBranchReferenceName("feature"),
|
|
plumbing.NewTagReferenceName("v1.0.0"),
|
|
plumbing.NewRemoteReferenceName("origin", "feature"),
|
|
} {
|
|
if err := repo.Storer.SetReference(plumbing.NewHashReference(name, hash)); err != nil {
|
|
t.Fatalf("set reference %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
refspecs, err := getPushRefSpecs(repo, plumbing.NewBranchReferenceName("master"))
|
|
if err != nil {
|
|
t.Fatalf("getPushRefSpecs() error: %v", err)
|
|
}
|
|
|
|
want := map[string]bool{
|
|
"refs/heads/feature:refs/heads/feature": false,
|
|
"refs/tags/v1.0.0:refs/tags/v1.0.0": false,
|
|
}
|
|
for _, refspec := range refspecs {
|
|
key := refspec.String()
|
|
if _, ok := want[key]; !ok {
|
|
t.Fatalf("unexpected refspec %q", key)
|
|
}
|
|
want[key] = true
|
|
}
|
|
for refspec, found := range want {
|
|
if !found {
|
|
t.Errorf("missing refspec %q", refspec)
|
|
}
|
|
}
|
|
}
|