package web import ( "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "git-browser/internal/repository" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) func TestServerRendersCorePages(t *testing.T) { root := t.TempDir() if err := initRepo(root, "demo"); err != nil { t.Fatal(err) } store, err := repository.Discover(root) if err != nil { t.Fatal(err) } server, err := NewServer(store, Config{ CloneUser: "git", CloneRoot: "repos", }) if err != nil { t.Fatal(err) } repo, err := store.Open("demo") if err != nil { t.Fatal(err) } rev, err := repo.DefaultRevision() if err != nil { t.Fatal(err) } tests := []struct { path string want string }{ {path: "/", want: "Repositories"}, {path: "/demo", want: "git@localhost:repos/demo"}, {path: "/demo", want: "data-copy-text=\"git@localhost:repos/demo\""}, {path: "/demo", want: "revision: master"}, {path: "/demo", want: "README.md"}, {path: "/demo/blob/" + rev.Name + "/README.md", want: "aria-label=\"Breadcrumb\""}, {path: "/demo/log/" + rev.Name, want: "/demo/tree/"}, {path: "/demo/log/" + rev.Name + "?path=README.md", want: "back to file"}, {path: "/demo/branches", want: "feature"}, {path: "/demo/tree/feature/", want: "README.md"}, {path: "/demo/tree/feature/", want: "/demo/branches?rev=feature"}, } for _, tc := range tests { req := httptest.NewRequest(http.MethodGet, tc.path, nil) req.Host = "localhost:8080" rec := httptest.NewRecorder() server.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("%s: got status %d, want %d", tc.path, rec.Code, http.StatusOK) } if !strings.Contains(rec.Body.String(), tc.want) { t.Fatalf("%s: response did not contain %q\nbody:\n%s", tc.path, tc.want, rec.Body.String()) } } } func TestServerUsesConfiguredCloneHost(t *testing.T) { root := t.TempDir() if err := initRepo(root, "demo"); err != nil { t.Fatal(err) } store, err := repository.Discover(root) if err != nil { t.Fatal(err) } server, err := NewServer(store, Config{ CloneUser: "git", CloneHost: "git.example.com", CloneRoot: "repos", }) if err != nil { t.Fatal(err) } req := httptest.NewRequest(http.MethodGet, "/demo", nil) req.Host = "localhost:8080" rec := httptest.NewRecorder() server.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("got status %d, want %d", rec.Code, http.StatusOK) } if !strings.Contains(rec.Body.String(), "git@git.example.com:repos/demo") { t.Fatalf("response did not contain overridden clone URL\nbody:\n%s", rec.Body.String()) } } func TestServerRefreshesRepositoryListBetweenRequests(t *testing.T) { root := t.TempDir() if err := initRepo(root, "alpha"); err != nil { t.Fatal(err) } store, err := repository.Discover(root) if err != nil { t.Fatal(err) } server, err := NewServer(store, Config{ CloneUser: "git", CloneRoot: "repos", }) if err != nil { t.Fatal(err) } first := httptest.NewRequest(http.MethodGet, "/", nil) first.Host = "localhost:8080" firstRec := httptest.NewRecorder() server.Handler().ServeHTTP(firstRec, first) if firstRec.Code != http.StatusOK { t.Fatalf("got status %d, want %d", firstRec.Code, http.StatusOK) } if strings.Contains(firstRec.Body.String(), "beta") { t.Fatalf("unexpected repository in initial response\nbody:\n%s", firstRec.Body.String()) } if err := initRepo(root, "beta"); err != nil { t.Fatal(err) } waitForCondition(t, 2*time.Second, func() bool { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Host = "localhost:8080" rec := httptest.NewRecorder() server.Handler().ServeHTTP(rec, req) return rec.Code == http.StatusOK && strings.Contains(rec.Body.String(), "beta") }) } func TestServerServesRawBlob(t *testing.T) { root := t.TempDir() if err := initRepo(root, "demo"); err != nil { t.Fatal(err) } store, err := repository.Discover(root) if err != nil { t.Fatal(err) } server, err := NewServer(store, Config{ CloneUser: "git", CloneRoot: "repos", }) if err != nil { t.Fatal(err) } repo, err := store.Open("demo") if err != nil { t.Fatal(err) } rev, err := repo.DefaultRevision() if err != nil { t.Fatal(err) } req := httptest.NewRequest(http.MethodGet, "/demo/blob/"+rev.Name+"/README.md?raw=1", nil) req.Host = "localhost:8080" rec := httptest.NewRecorder() server.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("got status %d, want %d", rec.Code, http.StatusOK) } if body := rec.Body.String(); body != "hello\n" { t.Fatalf("got raw body %q, want %q", body, "hello\n") } if contentType := rec.Header().Get("Content-Type"); !strings.Contains(contentType, "text/plain") { t.Fatalf("got content type %q, want text/plain", contentType) } } func TestServerRendersEmptyRepositoryPage(t *testing.T) { root := t.TempDir() if _, err := git.PlainInit(filepath.Join(root, "empty"), false); err != nil { t.Fatal(err) } store, err := repository.Discover(root) if err != nil { t.Fatal(err) } server, err := NewServer(store, Config{ CloneUser: "git", CloneRoot: "", }) if err != nil { t.Fatal(err) } tests := []struct { path string want string }{ {path: "/empty", want: "This repository has no commits yet."}, {path: "/empty/branches", want: "No branches found."}, } for _, tc := range tests { req := httptest.NewRequest(http.MethodGet, tc.path, nil) req.Host = "localhost:8080" rec := httptest.NewRecorder() server.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("%s: got status %d, want %d", tc.path, rec.Code, http.StatusOK) } if !strings.Contains(rec.Body.String(), tc.want) { t.Fatalf("%s: response did not contain %q\nbody:\n%s", tc.path, tc.want, rec.Body.String()) } } } func TestServerStripsDotGitSuffixOnlyInUI(t *testing.T) { root := t.TempDir() if err := initRepo(root, "demo.git"); err != nil { t.Fatal(err) } store, err := repository.Discover(root) if err != nil { t.Fatal(err) } server, err := NewServer(store, Config{ CloneUser: "git", CloneRoot: "repos", }) if err != nil { t.Fatal(err) } tests := []struct { path string want []string wantNot []string }{ { path: "/", want: []string{">demo"}, wantNot: []string{">demo.git"}, }, { path: "/demo.git", want: []string{"