1
0
Fork 0
mirror of https://github.com/imjasonh/git-k8s synced 2026-07-10 15:41:54 +00:00
git-k8s/test/e2e/e2e_test.go
Claude 0075761012
Speed up e2e tests by removing Knative and parallelizing setup
- Remove Setup Knative step: the project only uses knative.dev/pkg as a
  Go library for the controller framework, not Knative Serving/Eventing.
  This eliminates the slowest setup step entirely.
- Create config-logging and config-observability ConfigMaps directly,
  which is all knative.dev/pkg/injection/sharedmain needs at runtime.
- Deploy Gitea in parallel with ko controller builds instead of
  sequentially, so both can start at the same time.
- Combine the "wait for controllers" and "wait for Gitea" steps into one.
- Reduce Gitea readiness probe initialDelaySeconds from 10s to 3s and
  periodSeconds from 5s to 3s.
- Reduce Gitea API wait loop sleep from 2s to 1s.
- Reduce e2e test poll interval from 2s to 1s for faster feedback.

https://claude.ai/code/session_018itccL4SXV3eD7iuPwLGYa
2026-02-28 04:35:38 +00:00

296 lines
8.5 KiB
Go

//go:build e2e
package e2e
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"testing"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
gitv1alpha1 "github.com/imjasonh/git-k8s/pkg/apis/git/v1alpha1"
gitclient "github.com/imjasonh/git-k8s/pkg/client"
)
const (
testNamespace = "default"
giteaUsername = "testadmin"
giteaPassword = "testpassword123"
pollInterval = 1 * time.Second
pollTimeout = 2 * time.Minute
)
var (
gitClient *gitclient.GitV1alpha1Client
kubeClient kubernetes.Interface
giteaURL string // localhost URL for test process (via port-forward)
giteaInURL string // in-cluster URL for controllers
)
func TestMain(m *testing.M) {
// Resolve Gitea URLs.
giteaURL = envOrDefault("GITEA_URL", "http://localhost:3000")
giteaInURL = envOrDefault("GITEA_INTERNAL_URL", "http://gitea.git-system.svc.cluster.local:3000")
// Build clients from KUBECONFIG.
kubeconfig := os.Getenv("KUBECONFIG")
if kubeconfig == "" {
kubeconfig = clientcmd.RecommendedHomeFile
}
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
fmt.Fprintf(os.Stderr, "building kubeconfig: %v\n", err)
os.Exit(1)
}
kubeClient, err = kubernetes.NewForConfig(config)
if err != nil {
fmt.Fprintf(os.Stderr, "creating kubernetes client: %v\n", err)
os.Exit(1)
}
dynClient, err := dynamic.NewForConfig(config)
if err != nil {
fmt.Fprintf(os.Stderr, "creating dynamic client: %v\n", err)
os.Exit(1)
}
gitClient = gitclient.NewFromDynamic(dynClient)
os.Exit(m.Run())
}
func envOrDefault(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
// createGiteaRepo creates a repository on Gitea via the API.
func createGiteaRepo(t *testing.T, name string) {
t.Helper()
body, _ := json.Marshal(map[string]interface{}{
"name": name,
"auto_init": true,
"default_branch": "main",
})
req, err := http.NewRequest("POST", giteaURL+"/api/v1/user/repos", bytes.NewReader(body))
if err != nil {
t.Fatalf("creating request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(giteaUsername, giteaPassword)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("creating Gitea repo %q: %v", name, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
t.Fatalf("creating Gitea repo %q: status %d", name, resp.StatusCode)
}
t.Logf("Created Gitea repo %q", name)
}
// getGiteaBranchCommit fetches the HEAD commit of a branch from Gitea.
func getGiteaBranchCommit(t *testing.T, repo, branch string) string {
t.Helper()
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/branches/%s", giteaURL, giteaUsername, repo, branch)
req, _ := http.NewRequest("GET", url, nil)
req.SetBasicAuth(giteaUsername, giteaPassword)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("getting branch %s/%s: %v", repo, branch, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("getting branch %s/%s: status %d", repo, branch, resp.StatusCode)
}
var result struct {
Commit struct {
ID string `json:"id"`
} `json:"commit"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
t.Fatalf("decoding branch response: %v", err)
}
return result.Commit.ID
}
// giteaBranchExists checks if a branch exists on Gitea.
func giteaBranchExists(t *testing.T, repo, branch string) bool {
t.Helper()
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/branches/%s", giteaURL, giteaUsername, repo, branch)
req, _ := http.NewRequest("GET", url, nil)
req.SetBasicAuth(giteaUsername, giteaPassword)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("checking branch %s/%s: %v", repo, branch, err)
}
resp.Body.Close()
return resp.StatusCode == http.StatusOK
}
// createCredentialsSecret creates a Secret with Gitea credentials.
// It returns the secret name. The secret is cleaned up when the test finishes.
func createCredentialsSecret(t *testing.T, name string) string {
t.Helper()
ctx := context.Background()
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: testNamespace,
},
StringData: map[string]string{
"username": giteaUsername,
"password": giteaPassword,
},
}
_, err := kubeClient.CoreV1().Secrets(testNamespace).Create(ctx, secret, metav1.CreateOptions{})
if err != nil {
t.Fatalf("creating secret %q: %v", name, err)
}
t.Cleanup(func() {
kubeClient.CoreV1().Secrets(testNamespace).Delete(ctx, name, metav1.DeleteOptions{})
})
return name
}
// createGitRepository creates a GitRepository CR pointing at a Gitea repo.
func createGitRepository(t *testing.T, name, repoName, secretName string) *gitv1alpha1.GitRepository {
t.Helper()
ctx := context.Background()
repo := &gitv1alpha1.GitRepository{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: testNamespace,
},
Spec: gitv1alpha1.GitRepositorySpec{
URL: fmt.Sprintf("%s/%s/%s.git", giteaInURL, giteaUsername, repoName),
DefaultBranch: "main",
Auth: &gitv1alpha1.GitAuth{
SecretRef: &gitv1alpha1.SecretRef{Name: secretName},
},
},
}
created, err := gitClient.GitRepositories(testNamespace).Create(ctx, repo, metav1.CreateOptions{})
if err != nil {
t.Fatalf("creating GitRepository %q: %v", name, err)
}
t.Cleanup(func() {
gitClient.GitRepositories(testNamespace).Delete(ctx, name, metav1.DeleteOptions{})
})
return created
}
// createGitBranch creates a GitBranch CR.
func createGitBranch(t *testing.T, name, repoRef, branchName string) *gitv1alpha1.GitBranch {
t.Helper()
ctx := context.Background()
branch := &gitv1alpha1.GitBranch{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: testNamespace,
},
Spec: gitv1alpha1.GitBranchSpec{
RepositoryRef: repoRef,
BranchName: branchName,
},
}
created, err := gitClient.GitBranches(testNamespace).Create(ctx, branch, metav1.CreateOptions{})
if err != nil {
t.Fatalf("creating GitBranch %q: %v", name, err)
}
t.Cleanup(func() {
gitClient.GitBranches(testNamespace).Delete(ctx, name, metav1.DeleteOptions{})
})
return created
}
// waitForPushTransaction polls until the GitPushTransaction reaches a terminal phase.
func waitForPushTransaction(t *testing.T, name string) *gitv1alpha1.GitPushTransaction {
t.Helper()
ctx := context.Background()
deadline := time.Now().Add(pollTimeout)
var lastTxn *gitv1alpha1.GitPushTransaction
for time.Now().Before(deadline) {
txn, err := gitClient.GitPushTransactions(testNamespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
t.Logf("getting push transaction %q: %v (retrying)", name, err)
time.Sleep(pollInterval)
continue
}
lastTxn = txn
t.Logf("GitPushTransaction %q phase: %s", name, txn.Status.Phase)
switch txn.Status.Phase {
case gitv1alpha1.TransactionPhaseSucceeded:
return txn
case gitv1alpha1.TransactionPhaseFailed:
t.Fatalf("GitPushTransaction %q failed: %s", name, txn.Status.Message)
}
time.Sleep(pollInterval)
}
if lastTxn != nil {
t.Logf("Last seen state: phase=%q message=%q resourceVersion=%s", lastTxn.Status.Phase, lastTxn.Status.Message, lastTxn.ResourceVersion)
}
t.Fatalf("timed out waiting for GitPushTransaction %q", name)
return nil
}
// waitForGitBranchCommit polls until the GitBranch has a non-empty headCommit.
func waitForGitBranchCommit(t *testing.T, name string) string {
t.Helper()
ctx := context.Background()
deadline := time.Now().Add(pollTimeout)
for time.Now().Before(deadline) {
branch, err := gitClient.GitBranches(testNamespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
time.Sleep(pollInterval)
continue
}
if branch.Status.HeadCommit != "" {
return branch.Status.HeadCommit
}
time.Sleep(pollInterval)
}
t.Fatalf("timed out waiting for GitBranch %q headCommit", name)
return ""
}
// waitForSyncPhase polls until the GitRepoSync reaches a non-empty phase.
func waitForSyncPhase(t *testing.T, name string) *gitv1alpha1.GitRepoSync {
t.Helper()
ctx := context.Background()
deadline := time.Now().Add(pollTimeout)
for time.Now().Before(deadline) {
syncObj, err := gitClient.GitRepoSyncs(testNamespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
time.Sleep(pollInterval)
continue
}
if syncObj.Status.Phase != "" {
t.Logf("GitRepoSync %q phase: %s", name, syncObj.Status.Phase)
return syncObj
}
time.Sleep(pollInterval)
}
t.Fatalf("timed out waiting for GitRepoSync %q phase", name)
return nil
}