package web import ( "context" "net/http" "net/http/httptest" "path/filepath" "strconv" "strings" "testing" "github.com/go-chi/chi/v5" "hearthforge/internal/config" "hearthforge/internal/db" "hearthforge/internal/gitcmd" ) // ciTestServer builds a server on a temp database with one repository and one // finished run. Every request runs as the admin. func ciTestServer(t *testing.T) (http.Handler, *db.DB, int64) { t.Helper() ctx := context.Background() dir := t.TempDir() database, err := db.Open(filepath.Join(dir, "hearthforge.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { database.Close() }) if _, err := database.InitAdmin(ctx, "hunter2"); err != nil { t.Fatal(err) } admin, err := database.UserByName(ctx, db.AdminUsername) if err != nil || admin == nil { t.Fatalf("admin missing: %v", err) } repo, err := database.CreateRepo(ctx, "ci-repo", nil, false, "main", db.NowISO()) if err != nil { t.Fatal(err) } runID := insertCiRun(t, database, repo.ID, admin.ID, "pipeline setup", "hello") cfg := &config.Config{ DataDir: dir, OwnerDisplayName: "Admin", BaseURL: "http://localhost:3000", CIMaxConcurrent: 1, MaxConcurrentArchives: 1, } s := &Server{Cfg: cfg, DB: database, Git: gitcmd.New(cfg)} if err := s.Git.Init(ctx, "ci-repo", "main"); err != nil { t.Fatal(err) } user := &db.SessionUser{ID: admin.ID, Username: admin.Username, IsAdmin: true} r := chi.NewRouter() r.Use(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), userKey, user))) }) }) s.ciRoutes(r) return r, database, runID } func ciGet(t *testing.T, h http.Handler, path string) string { t.Helper() rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, path, nil)) if rec.Code != http.StatusOK { t.Fatalf("GET %s = %d, want 200", path, rec.Code) } return rec.Body.String() } func TestCiHistoryPage(t *testing.T) { h, _, runID := ciTestServer(t) body := ciGet(t, h, "/ci-repo/ci") for _, want := range []string{ `class="ci-status-pill ci-status-success"`, `href="/ci-repo/ci/` + strconv.FormatInt(runID, 10) + `"`, "Run pipeline", "Purge caches", "How to define a pipeline file", } { if !strings.Contains(body, want) { t.Errorf("history page misses %q", want) } } } func TestCiRunDetailPage(t *testing.T) { h, _, runID := ciTestServer(t) body := ciGet(t, h, "/ci-repo/ci/"+strconv.FormatInt(runID, 10)) for _, want := range []string{ `class="ci-step-name">pipeline setup<`, `class="ci-step-name">hello<`, "Pipeline #1", "Retry", } { if !strings.Contains(body, want) { t.Errorf("run detail page misses %q", want) } } } func TestCiBadge(t *testing.T) { h, _, _ := ciTestServer(t) rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/ci-repo/ci/badge.svg", nil)) if rec.Code != http.StatusOK { t.Fatalf("badge status = %d", rec.Code) } if ct := rec.Header().Get("Content-Type"); ct != "image/svg+xml" { t.Errorf("badge content type = %q", ct) } if body := rec.Body.String(); !strings.Contains(body, "