package app import ( "encoding/json" "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" "strings" "testing" ) func requireGitForApp(t *testing.T) { t.Helper() if _, err := exec.LookPath("git"); err != nil { t.Skip("git not installed") } } func seedRepoWithFeatureBranch(t *testing.T, bare string) { t.Helper() if err := gitInitBare(bare); err != nil { t.Fatal(err) } work := filepath.Join(t.TempDir(), "work") if err := runGit("", "clone", bare, work); err != nil { t.Fatal(err) } for _, args := range [][]string{{"config", "user.name", "Test User"}, {"config", "user.email", "test@example.com"}} { if err := runGit(work, args...); err != nil { t.Fatal(err) } } if err := os.WriteFile(filepath.Join(work, "README.md"), []byte("hello\n"), 0644); err != nil { t.Fatal(err) } if err := runGit(work, "add", "README.md"); err != nil { t.Fatal(err) } if err := runGit(work, "commit", "-m", "initial"); err != nil { t.Fatal(err) } if err := runGit(work, "push", "origin", "HEAD:main"); err != nil { t.Fatal(err) } if err := runGit(work, "checkout", "-b", "feature"); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(work, "feature.txt"), []byte("feature\n"), 0644); err != nil { t.Fatal(err) } if err := runGit(work, "add", "feature.txt"); err != nil { t.Fatal(err) } if err := runGit(work, "commit", "-m", "feature"); err != nil { t.Fatal(err) } if err := runGit(work, "push", "origin", "HEAD:feature"); err != nil { t.Fatal(err) } if err := runGit(work, "tag", "v1.0.0"); err != nil { t.Fatal(err) } if err := runGit(work, "push", "origin", "v1.0.0"); err != nil { t.Fatal(err) } } func TestAppGitRefsDiffAndMerge(t *testing.T) { requireGitForApp(t) s, mock, cleanup := newMockServer(t) defer cleanup() bare := s.repoPath("alice", "demo") if err := os.MkdirAll(filepath.Dir(bare), 0755); err != nil { t.Fatal(err) } seedRepoWithFeatureBranch(t, bare) repo := Repository{ID: 10, OwnerUserID: 1, Owner: "alice", Name: "demo", Visibility: "public", DefaultBranch: "main"} expectLoadRepo(mock, "alice", "demo", repo) rr := httptest.NewRecorder() s.handleRepoBranches(rr, httptest.NewRequest(http.MethodGet, "/api/repos/alice/demo/branches", nil), "alice", "demo") if rr.Code != http.StatusOK { t.Fatalf("branches status=%d body=%s", rr.Code, rr.Body.String()) } var refs []RefInfo if err := json.Unmarshal(rr.Body.Bytes(), &refs); err != nil { t.Fatal(err) } if len(refs) < 2 { t.Fatalf("expected main and feature refs, got %#v", refs) } expectLoadRepo(mock, "alice", "demo", repo) rr = httptest.NewRecorder() s.handleRepoTags(rr, httptest.NewRequest(http.MethodGet, "/api/repos/alice/demo/tags", nil), "alice", "demo") if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "v1.0.0") { t.Fatalf("tags status=%d body=%s", rr.Code, rr.Body.String()) } pr := PullRequest{Number: 1, TargetOwner: "alice", TargetRepo: "demo", TargetBranch: "main", SourceOwner: "alice", SourceRepo: "demo", SourceBranch: "feature"} diff, err := s.prDiff(pr) if err != nil { t.Fatal(err) } if !strings.Contains(diff, "feature.txt") { t.Fatalf("diff missing feature file: %s", diff) } if err := s.mergePR(pr); err != nil { t.Fatal(err) } if !gitBranchExists(bare, "main") { t.Fatal("main branch should exist after merge") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } }