Files
gickup/main_github_test.go
Danny Sauer 55882ef63e fix(github): use App installation token for destination pushes (#433)
Pass the resolved destination credential through GetOrCreate to the push helper. Cover App and configured tokens on github.com and Enterprise Server, API failures, and the backup call chain.
2026-09-09 18:34:52 +02:00

98 lines
2.8 KiB
Go

package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"sync"
"testing"
"time"
"github.com/cooperspencer/gickup/types"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
)
//nolint:paralleltest // backup calls providers that assign package-level loggers.
func TestBackupGitHubAppPushCredential(t *testing.T) {
const wantToken = "installation-token-for-test"
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
keyFile := filepath.Join(t.TempDir(), "app.pem")
if err := os.WriteFile(keyFile, pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key),
}), 0o600); err != nil {
t.Fatal(err)
}
sourceDir := t.TempDir()
source, err := git.PlainInit(sourceDir, false)
if err != nil {
t.Fatal(err)
}
worktree, err := source.Worktree()
if err != nil {
t.Fatal(err)
}
if _, err := worktree.Commit("seed", &git.CommitOptions{
AllowEmptyCommits: true,
Author: &object.Signature{Name: "test", Email: "test@example.com", When: time.Now()},
}); err != nil {
t.Fatal(err)
}
var passwordsMu sync.Mutex
var passwords []string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method + " " + r.URL.Path {
case "POST /api/v3/app/installations/42/access_tokens":
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(map[string]string{
"token": wantToken, "expires_at": time.Now().Add(time.Hour).UTC().Format(time.RFC3339),
})
case "GET /api/v3/orgs/example":
_, _ = w.Write([]byte(`{"login":"example"}`))
case "GET /api/v3/repos/example/repo":
_ = json.NewEncoder(w).Encode(map[string]string{"clone_url": "http://" + r.Host + "/example/repo.git"})
case "GET /example/repo.git/info/refs":
_, password, _ := r.BasicAuth()
passwordsMu.Lock()
passwords = append(passwords, password)
passwordsMu.Unlock()
w.WriteHeader(http.StatusUnauthorized)
default:
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
w.WriteHeader(http.StatusNotFound)
}
}))
defer server.Close()
backup([]types.Repo{{Name: "repo", URL: sourceDir}}, &types.Conf{
Destination: types.Destination{Github: []types.GenRepo{{
URL: server.URL, Organization: "example",
AppID: 1, AppInstallationID: 42, AppPrivateKeyFile: keyFile,
}}},
})
passwordsMu.Lock()
gotPasswords := passwords
passwords = nil
passwordsMu.Unlock()
if len(gotPasswords) == 0 {
t.Fatal("backup did not attempt a push")
}
for _, got := range gotPasswords {
if got != wantToken {
t.Fatalf("backup push password = %q, want resolved installation token", got)
}
}
}