1
0
Fork 0
mirror of https://github.com/imjasonh/git-k8s synced 2026-07-06 22:12:25 +00:00
git-k8s/test/e2e/repowatcher_test.go
Claude 6f803bf10e
Add comprehensive e2e tests for repo-watcher-controller
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
2026-02-28 14:15:28 +00:00

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{})
})
}