mirror of
https://github.com/imjasonh/git-k8s
synced 2026-07-06 22:12:25 +00:00
Tests cover the key behaviors of the repo-watcher: - Branch discovery: auto-creates GitBranch CRDs from remote refs - Owner references: created branches correctly reference GitRepository - New branch detection: branches added on Gitea are picked up - Commit update detection: new pushes to Gitea update headCommit - Branch deletion: branches removed from Gitea trigger CRD deletion - Multiple branches: all branches on a repo are discovered - LastFetchTime: status is updated and advances on each poll - Labels: repository label is set on created branches - Slash branch names: feature/foo naming handled correctly Also adds shared e2e helpers for Gitea branch/file operations and updates CI workflow to deploy and monitor the repo-watcher-controller. https://claude.ai/code/session_01P7xfBqARU5DFS8QJ4uDPVj
377 lines
14 KiB
Go
377 lines
14 KiB
Go
//go:build e2e
|
|
|
|
package e2e
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
// TestRepoWatcherDiscoversBranches verifies that when a GitRepository is created
|
|
// pointing at a Gitea repo with a "main" branch, the repo-watcher controller
|
|
// auto-creates a GitBranch CRD with the correct headCommit.
|
|
func TestRepoWatcherDiscoversBranches(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repoName := "e2e-watcher-discover"
|
|
createGiteaRepo(t, repoName)
|
|
|
|
secretName := createCredentialsSecret(t, "e2e-watcher-discover-creds")
|
|
|
|
// Use a short poll interval so we don't wait long.
|
|
interval := metav1.Duration{Duration: 2 * time.Second}
|
|
repo := createGitRepositoryWithPollInterval(t, "e2e-watcher-discover-repo", repoName, secretName, interval)
|
|
_ = repo
|
|
|
|
// The repo-watcher should auto-create a GitBranch for "main".
|
|
branchCRDName := "e2e-watcher-discover-repo-main"
|
|
branch := waitForGitBranchExists(t, branchCRDName)
|
|
|
|
// Verify the branch spec.
|
|
if branch.Spec.RepositoryRef != "e2e-watcher-discover-repo" {
|
|
t.Errorf("GitBranch repositoryRef = %q, want %q", branch.Spec.RepositoryRef, "e2e-watcher-discover-repo")
|
|
}
|
|
if branch.Spec.BranchName != "main" {
|
|
t.Errorf("GitBranch branchName = %q, want %q", branch.Spec.BranchName, "main")
|
|
}
|
|
|
|
// Verify headCommit matches what Gitea reports.
|
|
expectedCommit := getGiteaBranchCommit(t, repoName, "main")
|
|
commit := waitForGitBranchCommit(t, branchCRDName)
|
|
if commit != expectedCommit {
|
|
t.Errorf("GitBranch headCommit = %q, want %q", commit, expectedCommit)
|
|
}
|
|
|
|
// Cleanup: the watcher-created branch won't be cleaned up by our test helpers
|
|
// since we didn't create it. Delete it explicitly.
|
|
t.Cleanup(func() {
|
|
gitClient.GitBranches(testNamespace).Delete(context.Background(), branchCRDName, metav1.DeleteOptions{})
|
|
})
|
|
}
|
|
|
|
// TestRepoWatcherOwnerReferences verifies that auto-created GitBranch CRDs have
|
|
// correct owner references pointing back to the GitRepository.
|
|
func TestRepoWatcherOwnerReferences(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repoName := "e2e-watcher-ownerref"
|
|
createGiteaRepo(t, repoName)
|
|
|
|
secretName := createCredentialsSecret(t, "e2e-watcher-ownerref-creds")
|
|
interval := metav1.Duration{Duration: 2 * time.Second}
|
|
repo := createGitRepositoryWithPollInterval(t, "e2e-watcher-ownerref-repo", repoName, secretName, interval)
|
|
|
|
branchCRDName := "e2e-watcher-ownerref-repo-main"
|
|
branch := waitForGitBranchExists(t, branchCRDName)
|
|
|
|
// Verify owner reference.
|
|
if len(branch.OwnerReferences) == 0 {
|
|
t.Fatal("GitBranch has no owner references")
|
|
}
|
|
ownerRef := branch.OwnerReferences[0]
|
|
if ownerRef.Kind != "GitRepository" {
|
|
t.Errorf("owner reference kind = %q, want %q", ownerRef.Kind, "GitRepository")
|
|
}
|
|
if ownerRef.Name != "e2e-watcher-ownerref-repo" {
|
|
t.Errorf("owner reference name = %q, want %q", ownerRef.Name, "e2e-watcher-ownerref-repo")
|
|
}
|
|
if ownerRef.UID != repo.UID {
|
|
t.Errorf("owner reference UID = %q, want %q", ownerRef.UID, repo.UID)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
gitClient.GitBranches(testNamespace).Delete(context.Background(), branchCRDName, metav1.DeleteOptions{})
|
|
})
|
|
}
|
|
|
|
// TestRepoWatcherDetectsNewBranch verifies that when a new branch is created on
|
|
// Gitea, the repo-watcher creates a corresponding GitBranch CRD.
|
|
func TestRepoWatcherDetectsNewBranch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repoName := "e2e-watcher-newbranch"
|
|
createGiteaRepo(t, repoName)
|
|
|
|
secretName := createCredentialsSecret(t, "e2e-watcher-newbranch-creds")
|
|
interval := metav1.Duration{Duration: 2 * time.Second}
|
|
createGitRepositoryWithPollInterval(t, "e2e-watcher-newbranch-repo", repoName, secretName, interval)
|
|
|
|
// Wait for the initial "main" branch to be discovered.
|
|
mainBranchCRD := "e2e-watcher-newbranch-repo-main"
|
|
waitForGitBranchExists(t, mainBranchCRD)
|
|
t.Cleanup(func() {
|
|
gitClient.GitBranches(testNamespace).Delete(context.Background(), mainBranchCRD, metav1.DeleteOptions{})
|
|
})
|
|
|
|
// Create a new branch on Gitea.
|
|
createGiteaBranch(t, repoName, "feature-new", "main")
|
|
|
|
// The watcher should detect the new branch.
|
|
featureBranchCRD := "e2e-watcher-newbranch-repo-feature-new"
|
|
branch := waitForGitBranchExists(t, featureBranchCRD)
|
|
|
|
if branch.Spec.BranchName != "feature-new" {
|
|
t.Errorf("GitBranch branchName = %q, want %q", branch.Spec.BranchName, "feature-new")
|
|
}
|
|
|
|
// The feature branch should have the same commit as main (branched from it).
|
|
expectedCommit := getGiteaBranchCommit(t, repoName, "feature-new")
|
|
commit := waitForGitBranchCommit(t, featureBranchCRD)
|
|
if commit != expectedCommit {
|
|
t.Errorf("GitBranch headCommit = %q, want %q", commit, expectedCommit)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
gitClient.GitBranches(testNamespace).Delete(context.Background(), featureBranchCRD, metav1.DeleteOptions{})
|
|
})
|
|
}
|
|
|
|
// TestRepoWatcherDetectsCommitUpdate verifies that when a new commit is pushed to
|
|
// a branch on Gitea, the repo-watcher updates the GitBranch headCommit.
|
|
func TestRepoWatcherDetectsCommitUpdate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repoName := "e2e-watcher-commit"
|
|
createGiteaRepo(t, repoName)
|
|
|
|
secretName := createCredentialsSecret(t, "e2e-watcher-commit-creds")
|
|
interval := metav1.Duration{Duration: 2 * time.Second}
|
|
createGitRepositoryWithPollInterval(t, "e2e-watcher-commit-repo", repoName, secretName, interval)
|
|
|
|
// Wait for initial discovery.
|
|
branchCRDName := "e2e-watcher-commit-repo-main"
|
|
waitForGitBranchExists(t, branchCRDName)
|
|
oldCommit := waitForGitBranchCommit(t, branchCRDName)
|
|
t.Logf("Initial headCommit: %s", oldCommit)
|
|
|
|
// Push a new commit by creating a file on main via Gitea API.
|
|
fileContent := base64.StdEncoding.EncodeToString([]byte("hello from e2e test"))
|
|
newCommit := createGiteaFile(t, repoName, "main", "test-file.txt", fileContent)
|
|
t.Logf("New commit after file creation: %s", newCommit)
|
|
|
|
// Verify the commit changed on Gitea.
|
|
giteaCommit := getGiteaBranchCommit(t, repoName, "main")
|
|
if giteaCommit == oldCommit {
|
|
t.Fatal("Gitea commit did not change after creating file")
|
|
}
|
|
|
|
// Wait for the watcher to pick up the new commit.
|
|
waitForGitBranchHeadCommit(t, branchCRDName, giteaCommit)
|
|
|
|
t.Cleanup(func() {
|
|
gitClient.GitBranches(testNamespace).Delete(context.Background(), branchCRDName, metav1.DeleteOptions{})
|
|
})
|
|
}
|
|
|
|
// TestRepoWatcherDeletesBranch verifies that when a branch is deleted on Gitea,
|
|
// the repo-watcher deletes the corresponding GitBranch CRD.
|
|
func TestRepoWatcherDeletesBranch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repoName := "e2e-watcher-delete"
|
|
createGiteaRepo(t, repoName)
|
|
|
|
secretName := createCredentialsSecret(t, "e2e-watcher-delete-creds")
|
|
interval := metav1.Duration{Duration: 2 * time.Second}
|
|
createGitRepositoryWithPollInterval(t, "e2e-watcher-delete-repo", repoName, secretName, interval)
|
|
|
|
// Create a feature branch on Gitea.
|
|
createGiteaBranch(t, repoName, "to-delete", "main")
|
|
|
|
// Wait for both branches to be discovered.
|
|
mainCRD := "e2e-watcher-delete-repo-main"
|
|
featureCRD := "e2e-watcher-delete-repo-to-delete"
|
|
waitForGitBranchExists(t, mainCRD)
|
|
waitForGitBranchExists(t, featureCRD)
|
|
t.Logf("Both branches discovered")
|
|
|
|
// Delete the feature branch on Gitea.
|
|
deleteGiteaBranch(t, repoName, "to-delete")
|
|
|
|
// Wait for the watcher to delete the GitBranch CRD.
|
|
waitForGitBranchDeleted(t, featureCRD)
|
|
|
|
// Main should still exist.
|
|
ctx := context.Background()
|
|
_, err := gitClient.GitBranches(testNamespace).Get(ctx, mainCRD, metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Errorf("main GitBranch should still exist, but got error: %v", err)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
gitClient.GitBranches(testNamespace).Delete(ctx, mainCRD, metav1.DeleteOptions{})
|
|
})
|
|
}
|
|
|
|
// TestRepoWatcherMultipleBranches verifies that the watcher discovers all branches
|
|
// on a repository, not just the default one.
|
|
func TestRepoWatcherMultipleBranches(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repoName := "e2e-watcher-multi"
|
|
createGiteaRepo(t, repoName)
|
|
|
|
// Create several branches on Gitea before creating the GitRepository CR.
|
|
createGiteaBranch(t, repoName, "develop", "main")
|
|
createGiteaBranch(t, repoName, "release-v1", "main")
|
|
createGiteaBranch(t, repoName, "hotfix", "main")
|
|
|
|
secretName := createCredentialsSecret(t, "e2e-watcher-multi-creds")
|
|
interval := metav1.Duration{Duration: 2 * time.Second}
|
|
createGitRepositoryWithPollInterval(t, "e2e-watcher-multi-repo", repoName, secretName, interval)
|
|
|
|
// All four branches should be discovered.
|
|
expectedBranches := map[string]string{
|
|
"e2e-watcher-multi-repo-main": "main",
|
|
"e2e-watcher-multi-repo-develop": "develop",
|
|
"e2e-watcher-multi-repo-release-v1": "release-v1",
|
|
"e2e-watcher-multi-repo-hotfix": "hotfix",
|
|
}
|
|
|
|
ctx := context.Background()
|
|
for crdName, branchName := range expectedBranches {
|
|
branch := waitForGitBranchExists(t, crdName)
|
|
if branch.Spec.BranchName != branchName {
|
|
t.Errorf("GitBranch %q has branchName %q, want %q", crdName, branch.Spec.BranchName, branchName)
|
|
}
|
|
commit := waitForGitBranchCommit(t, crdName)
|
|
if commit == "" {
|
|
t.Errorf("GitBranch %q has empty headCommit", crdName)
|
|
}
|
|
t.Cleanup(func() {
|
|
gitClient.GitBranches(testNamespace).Delete(ctx, crdName, metav1.DeleteOptions{})
|
|
})
|
|
}
|
|
|
|
t.Logf("All %d branches discovered", len(expectedBranches))
|
|
}
|
|
|
|
// TestRepoWatcherLastFetchTime verifies that the controller updates
|
|
// GitRepository.status.lastFetchTime after each poll.
|
|
func TestRepoWatcherLastFetchTime(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repoName := "e2e-watcher-fetchtime"
|
|
createGiteaRepo(t, repoName)
|
|
|
|
secretName := createCredentialsSecret(t, "e2e-watcher-fetchtime-creds")
|
|
interval := metav1.Duration{Duration: 2 * time.Second}
|
|
createGitRepositoryWithPollInterval(t, "e2e-watcher-fetchtime-repo", repoName, secretName, interval)
|
|
|
|
// Wait for the branch to be discovered (proves at least one poll happened).
|
|
mainCRD := "e2e-watcher-fetchtime-repo-main"
|
|
waitForGitBranchExists(t, mainCRD)
|
|
|
|
// Check that lastFetchTime is set on the GitRepository.
|
|
ctx := context.Background()
|
|
deadline := time.Now().Add(pollTimeout)
|
|
for time.Now().Before(deadline) {
|
|
repo, err := gitClient.GitRepositories(testNamespace).Get(ctx, "e2e-watcher-fetchtime-repo", metav1.GetOptions{})
|
|
if err != nil {
|
|
time.Sleep(pollInterval)
|
|
continue
|
|
}
|
|
if repo.Status.LastFetchTime != nil {
|
|
t.Logf("GitRepository lastFetchTime: %v", repo.Status.LastFetchTime.Time)
|
|
|
|
// Wait a few polls and check that it advances.
|
|
firstFetch := repo.Status.LastFetchTime.Time
|
|
time.Sleep(5 * time.Second)
|
|
|
|
repo2, err := gitClient.GitRepositories(testNamespace).Get(ctx, "e2e-watcher-fetchtime-repo", metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("getting GitRepository: %v", err)
|
|
}
|
|
if repo2.Status.LastFetchTime == nil {
|
|
t.Fatal("lastFetchTime is nil after second check")
|
|
}
|
|
if !repo2.Status.LastFetchTime.Time.After(firstFetch) {
|
|
t.Errorf("lastFetchTime did not advance: first=%v second=%v", firstFetch, repo2.Status.LastFetchTime.Time)
|
|
}
|
|
t.Logf("lastFetchTime advanced from %v to %v", firstFetch, repo2.Status.LastFetchTime.Time)
|
|
|
|
t.Cleanup(func() {
|
|
gitClient.GitBranches(testNamespace).Delete(context.Background(), mainCRD, metav1.DeleteOptions{})
|
|
})
|
|
return
|
|
}
|
|
time.Sleep(pollInterval)
|
|
}
|
|
t.Fatal("timed out waiting for lastFetchTime to be set")
|
|
}
|
|
|
|
// TestRepoWatcherLabels verifies that auto-created GitBranch CRDs have the
|
|
// repository label set correctly.
|
|
func TestRepoWatcherLabels(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repoName := "e2e-watcher-labels"
|
|
createGiteaRepo(t, repoName)
|
|
|
|
secretName := createCredentialsSecret(t, "e2e-watcher-labels-creds")
|
|
interval := metav1.Duration{Duration: 2 * time.Second}
|
|
createGitRepositoryWithPollInterval(t, "e2e-watcher-labels-repo", repoName, secretName, interval)
|
|
|
|
branchCRDName := "e2e-watcher-labels-repo-main"
|
|
branch := waitForGitBranchExists(t, branchCRDName)
|
|
|
|
// Verify label.
|
|
expectedLabel := "git-k8s.imjasonh.com/repository"
|
|
labelValue, ok := branch.Labels[expectedLabel]
|
|
if !ok {
|
|
t.Fatalf("GitBranch missing label %q, labels: %v", expectedLabel, branch.Labels)
|
|
}
|
|
if labelValue != "e2e-watcher-labels-repo" {
|
|
t.Errorf("label %q = %q, want %q", expectedLabel, labelValue, "e2e-watcher-labels-repo")
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
gitClient.GitBranches(testNamespace).Delete(context.Background(), branchCRDName, metav1.DeleteOptions{})
|
|
})
|
|
}
|
|
|
|
// TestRepoWatcherSlashBranchName verifies that branches with slashes in the name
|
|
// (e.g., "feature/foo") are handled correctly, with slashes replaced by dashes
|
|
// in the CRD name.
|
|
func TestRepoWatcherSlashBranchName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
repoName := "e2e-watcher-slash"
|
|
createGiteaRepo(t, repoName)
|
|
|
|
// Create a branch with a slash in the name.
|
|
createGiteaBranch(t, repoName, "feature/my-thing", "main")
|
|
|
|
secretName := createCredentialsSecret(t, "e2e-watcher-slash-creds")
|
|
interval := metav1.Duration{Duration: 2 * time.Second}
|
|
createGitRepositoryWithPollInterval(t, "e2e-watcher-slash-repo", repoName, secretName, interval)
|
|
|
|
// The CRD name should have slashes replaced with dashes.
|
|
mainCRD := "e2e-watcher-slash-repo-main"
|
|
featureCRD := "e2e-watcher-slash-repo-feature-my-thing"
|
|
|
|
waitForGitBranchExists(t, mainCRD)
|
|
branch := waitForGitBranchExists(t, featureCRD)
|
|
|
|
// But the spec.branchName should preserve the original slash.
|
|
if branch.Spec.BranchName != "feature/my-thing" {
|
|
t.Errorf("GitBranch branchName = %q, want %q", branch.Spec.BranchName, "feature/my-thing")
|
|
}
|
|
|
|
expectedCommit := getGiteaBranchCommit(t, repoName, fmt.Sprintf("feature/my-thing"))
|
|
commit := waitForGitBranchCommit(t, featureCRD)
|
|
if commit != expectedCommit {
|
|
t.Errorf("headCommit = %q, want %q", commit, expectedCommit)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
t.Cleanup(func() {
|
|
gitClient.GitBranches(testNamespace).Delete(ctx, mainCRD, metav1.DeleteOptions{})
|
|
gitClient.GitBranches(testNamespace).Delete(ctx, featureCRD, metav1.DeleteOptions{})
|
|
})
|
|
}
|