loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[GITEA] Add support for shields.io-based badges

Adds a new `/{username}/{repo}/badges` family of routes, which redirect
to various shields.io badges. The goal is to not reimplement badge
generation, and delegate it to shields.io (or a similar service), which
are already used by many. This way, we get all the goodies that come
with it: different styles, colors, logos, you name it.

So these routes are just thin wrappers around shields.io that make it
easier to display the information we want. The URL is configurable via
`app.ini`, and is templatable, allowing to use alternative badge
generator services with slightly different URL patterns.

Additionally, for compatibility with GitHub, there's an
`/{username}/{repo}/actions/workflows/{workflow_file}/badge.svg` route
that works much the same way as on GitHub. Change the hostname in the
URL, and done.

Fixes gitea#5633, gitea#23688, and also fixes #126.

Work sponsored by Codeberg e.V.

Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
(cherry picked from commit fcd0f61212d8febd4bdfc27e61a4e13cbdd16d49)
(cherry picked from commit 20d14f784490a880c51ca0f0a6a5988a01887635)
(cherry picked from commit 4359741431bb39de4cf24de8b0cfb513f5233f55)
(cherry picked from commit 35cff45eb86177e750cd22e82a201880a5efe045)
(cherry picked from commit 2fc0d0b8a302d24177a00ab48b42ce083b52e506)

authored by

Gergely Nagy and committed by
Earl Warren
f90b8026 92f41d11

+471
+8
custom/conf/app.example.ini
··· 913 913 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 914 914 915 915 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 916 + ;[badges] 917 + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 918 + ;; Enable repository badges (via shields.io or a similar generator) 919 + ;ENABLED = true 920 + ;; Template for the badge generator. 921 + ;GENERATOR_URL_TEMPLATE = https://img.shields.io/badge/{{.label}}-{{.text}}-{{.color}} 922 + 923 + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 916 924 ;[repository] 917 925 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 918 926 ;; Root path for storing all repository data. By default, it is set to %(APP_DATA_PATH)s/gitea-repositories.
+15
models/actions/run.go
··· 323 323 return &run, nil 324 324 } 325 325 326 + func GetLatestRunForBranchAndWorkflow(ctx context.Context, repoID int64, branch, workflowFile, event string) (*ActionRun, error) { 327 + var run ActionRun 328 + q := db.GetEngine(ctx).Where("repo_id=?", repoID).And("ref=?", branch).And("workflow_id=?", workflowFile) 329 + if event != "" { 330 + q = q.And("event=?", event) 331 + } 332 + has, err := q.Desc("id").Get(&run) 333 + if err != nil { 334 + return nil, err 335 + } else if !has { 336 + return nil, util.NewNotExistErrorf("run with repo_id %d, ref %s, workflow_id %s", repoID, branch, workflowFile) 337 + } 338 + return &run, nil 339 + } 340 + 326 341 func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) { 327 342 var run ActionRun 328 343 has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
+24
modules/setting/badges.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package setting 5 + 6 + import ( 7 + "text/template" 8 + ) 9 + 10 + // Badges settings 11 + var Badges = struct { 12 + Enabled bool `ini:"ENABLED"` 13 + GeneratorURLTemplate string `ini:"GENERATOR_URL_TEMPLATE"` 14 + GeneratorURLTemplateTemplate *template.Template `ini:"-"` 15 + }{ 16 + Enabled: true, 17 + GeneratorURLTemplate: "https://img.shields.io/badge/{{.label}}-{{.text}}-{{.color}}", 18 + } 19 + 20 + func loadBadgesFrom(rootCfg ConfigProvider) { 21 + mustMapSetting(rootCfg, "badges", &Badges) 22 + 23 + Badges.GeneratorURLTemplateTemplate = template.Must(template.New("").Parse(Badges.GeneratorURLTemplate)) 24 + }
+1
modules/setting/setting.go
··· 147 147 loadUIFrom(cfg) 148 148 loadAdminFrom(cfg) 149 149 loadAPIFrom(cfg) 150 + loadBadgesFrom(cfg) 150 151 loadMetricsFrom(cfg) 151 152 loadCamoFrom(cfg) 152 153 loadI18nFrom(cfg)
+165
routers/web/repo/badges/badges.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package badges 5 + 6 + import ( 7 + "fmt" 8 + "net/url" 9 + "strings" 10 + 11 + actions_model "code.gitea.io/gitea/models/actions" 12 + repo_model "code.gitea.io/gitea/models/repo" 13 + "code.gitea.io/gitea/models/unit" 14 + context_module "code.gitea.io/gitea/modules/context" 15 + "code.gitea.io/gitea/modules/setting" 16 + ) 17 + 18 + func getBadgeURL(ctx *context_module.Context, label, text, color string) string { 19 + sb := &strings.Builder{} 20 + _ = setting.Badges.GeneratorURLTemplateTemplate.Execute(sb, map[string]string{ 21 + "label": url.PathEscape(label), 22 + "text": url.PathEscape(text), 23 + "color": url.PathEscape(color), 24 + }) 25 + 26 + badgeURL := sb.String() 27 + q := ctx.Req.URL.Query() 28 + // Remove any `branch` or `event` query parameters. They're used by the 29 + // workflow badge route, and do not need forwarding to the badge generator. 30 + delete(q, "branch") 31 + delete(q, "event") 32 + if len(q) > 0 { 33 + return fmt.Sprintf("%s?%s", badgeURL, q.Encode()) 34 + } 35 + return badgeURL 36 + } 37 + 38 + func redirectToBadge(ctx *context_module.Context, label, text, color string) { 39 + ctx.Redirect(getBadgeURL(ctx, label, text, color)) 40 + } 41 + 42 + func errorBadge(ctx *context_module.Context, label, text string) { 43 + ctx.Redirect(getBadgeURL(ctx, label, text, "crimson")) 44 + } 45 + 46 + func GetWorkflowBadge(ctx *context_module.Context) { 47 + branch := ctx.Req.URL.Query().Get("branch") 48 + if branch == "" { 49 + branch = ctx.Repo.Repository.DefaultBranch 50 + } 51 + branch = fmt.Sprintf("refs/heads/%s", branch) 52 + event := ctx.Req.URL.Query().Get("event") 53 + 54 + workflowFile := ctx.Params("workflow_name") 55 + run, err := actions_model.GetLatestRunForBranchAndWorkflow(ctx, ctx.Repo.Repository.ID, branch, workflowFile, event) 56 + if err != nil { 57 + errorBadge(ctx, workflowFile, "Not found") 58 + return 59 + } 60 + 61 + var color string 62 + switch run.Status { 63 + case actions_model.StatusUnknown: 64 + color = "lightgrey" 65 + case actions_model.StatusWaiting: 66 + color = "lightgrey" 67 + case actions_model.StatusRunning: 68 + color = "gold" 69 + case actions_model.StatusSuccess: 70 + color = "brightgreen" 71 + case actions_model.StatusFailure: 72 + color = "crimson" 73 + case actions_model.StatusCancelled: 74 + color = "orange" 75 + case actions_model.StatusSkipped: 76 + color = "blue" 77 + case actions_model.StatusBlocked: 78 + color = "yellow" 79 + default: 80 + color = "lightgrey" 81 + } 82 + 83 + redirectToBadge(ctx, workflowFile, run.Status.String(), color) 84 + } 85 + 86 + func getIssueOrPullBadge(ctx *context_module.Context, label, variant string, num int) { 87 + var text string 88 + if len(variant) > 0 { 89 + text = fmt.Sprintf("%d %s", num, variant) 90 + } else { 91 + text = fmt.Sprintf("%d", num) 92 + } 93 + redirectToBadge(ctx, label, text, "blue") 94 + } 95 + 96 + func getIssueBadge(ctx *context_module.Context, variant string, num int) { 97 + if !ctx.Repo.CanRead(unit.TypeIssues) && 98 + !ctx.Repo.CanRead(unit.TypeExternalTracker) { 99 + errorBadge(ctx, "issues", "Not found") 100 + return 101 + } 102 + 103 + _, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalTracker) 104 + if err == nil { 105 + errorBadge(ctx, "issues", "Not found") 106 + return 107 + } 108 + 109 + getIssueOrPullBadge(ctx, "issues", variant, num) 110 + } 111 + 112 + func getPullBadge(ctx *context_module.Context, variant string, num int) { 113 + if !ctx.Repo.Repository.CanEnablePulls() || !ctx.Repo.CanRead(unit.TypePullRequests) { 114 + errorBadge(ctx, "pulls", "Not found") 115 + return 116 + } 117 + 118 + getIssueOrPullBadge(ctx, "pulls", variant, num) 119 + } 120 + 121 + func GetOpenIssuesBadge(ctx *context_module.Context) { 122 + getIssueBadge(ctx, "open", ctx.Repo.Repository.NumOpenIssues) 123 + } 124 + 125 + func GetClosedIssuesBadge(ctx *context_module.Context) { 126 + getIssueBadge(ctx, "closed", ctx.Repo.Repository.NumClosedIssues) 127 + } 128 + 129 + func GetTotalIssuesBadge(ctx *context_module.Context) { 130 + getIssueBadge(ctx, "", ctx.Repo.Repository.NumIssues) 131 + } 132 + 133 + func GetOpenPullsBadge(ctx *context_module.Context) { 134 + getPullBadge(ctx, "open", ctx.Repo.Repository.NumOpenPulls) 135 + } 136 + 137 + func GetClosedPullsBadge(ctx *context_module.Context) { 138 + getPullBadge(ctx, "closed", ctx.Repo.Repository.NumClosedPulls) 139 + } 140 + 141 + func GetTotalPullsBadge(ctx *context_module.Context) { 142 + getPullBadge(ctx, "", ctx.Repo.Repository.NumPulls) 143 + } 144 + 145 + func GetStarsBadge(ctx *context_module.Context) { 146 + redirectToBadge(ctx, "stars", fmt.Sprintf("%d", ctx.Repo.Repository.NumStars), "blue") 147 + } 148 + 149 + func GetLatestReleaseBadge(ctx *context_module.Context) { 150 + release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID) 151 + if err != nil { 152 + if repo_model.IsErrReleaseNotExist(err) { 153 + errorBadge(ctx, "release", "Not found") 154 + return 155 + } 156 + ctx.ServerError("GetLatestReleaseByRepoID", err) 157 + } 158 + 159 + if err := release.LoadAttributes(ctx); err != nil { 160 + ctx.ServerError("LoadAttributes", err) 161 + return 162 + } 163 + 164 + redirectToBadge(ctx, "release", release.TagName, "blue") 165 + }
+21
routers/web/web.go
··· 37 37 org_setting "code.gitea.io/gitea/routers/web/org/setting" 38 38 "code.gitea.io/gitea/routers/web/repo" 39 39 "code.gitea.io/gitea/routers/web/repo/actions" 40 + "code.gitea.io/gitea/routers/web/repo/badges" 40 41 repo_setting "code.gitea.io/gitea/routers/web/repo/setting" 41 42 "code.gitea.io/gitea/routers/web/user" 42 43 user_setting "code.gitea.io/gitea/routers/web/user/setting" ··· 1316 1317 m.Get("/packages", repo.Packages) 1317 1318 } 1318 1319 1320 + if setting.Badges.Enabled { 1321 + m.Group("/badges", func() { 1322 + m.Get("/workflows/{workflow_name}/badge.svg", badges.GetWorkflowBadge) 1323 + m.Group("/issues", func() { 1324 + m.Get(".svg", badges.GetTotalIssuesBadge) 1325 + m.Get("/open.svg", badges.GetOpenIssuesBadge) 1326 + m.Get("/closed.svg", badges.GetClosedIssuesBadge) 1327 + }) 1328 + m.Group("/pulls", func() { 1329 + m.Get(".svg", badges.GetTotalPullsBadge) 1330 + m.Get("/open.svg", badges.GetOpenPullsBadge) 1331 + m.Get("/closed.svg", badges.GetClosedPullsBadge) 1332 + }) 1333 + m.Get("/stars.svg", badges.GetStarsBadge) 1334 + m.Get("/release.svg", badges.GetLatestReleaseBadge) 1335 + }) 1336 + } 1337 + 1319 1338 m.Group("/projects", func() { 1320 1339 m.Get("", repo.Projects) 1321 1340 m.Get("/{id}", repo.ViewProject) ··· 1367 1386 m.Post("/rerun", reqRepoActionsWriter, actions.Rerun) 1368 1387 }) 1369 1388 }) 1389 + 1390 + m.Get("/workflows/{workflow_name}/badge.svg", badges.GetWorkflowBadge) 1370 1391 }, reqRepoActionsReader, actions.MustEnableActions) 1371 1392 1372 1393 m.Group("/wiki", func() {
+237
tests/integration/repo_badges_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package integration 5 + 6 + import ( 7 + "fmt" 8 + "net/http" 9 + "net/http/httptest" 10 + "net/url" 11 + "strings" 12 + "testing" 13 + "time" 14 + 15 + actions_model "code.gitea.io/gitea/models/actions" 16 + "code.gitea.io/gitea/models/db" 17 + repo_model "code.gitea.io/gitea/models/repo" 18 + unit_model "code.gitea.io/gitea/models/unit" 19 + "code.gitea.io/gitea/models/unittest" 20 + user_model "code.gitea.io/gitea/models/user" 21 + "code.gitea.io/gitea/modules/git" 22 + "code.gitea.io/gitea/modules/test" 23 + repo_service "code.gitea.io/gitea/services/repository" 24 + files_service "code.gitea.io/gitea/services/repository/files" 25 + "code.gitea.io/gitea/tests" 26 + 27 + "github.com/stretchr/testify/assert" 28 + ) 29 + 30 + func assertBadge(t *testing.T, resp *httptest.ResponseRecorder, badge string) { 31 + assert.Equal(t, fmt.Sprintf("https://img.shields.io/badge/%s", badge), test.RedirectURL(resp)) 32 + } 33 + 34 + func createMinimalRepo(t *testing.T) func() { 35 + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 36 + 37 + // Create a new repository 38 + repo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{ 39 + Name: "minimal", 40 + Description: "minimal repo for badge testing", 41 + AutoInit: true, 42 + Gitignores: "Go", 43 + License: "MIT", 44 + Readme: "Default", 45 + DefaultBranch: "main", 46 + IsPrivate: false, 47 + }) 48 + assert.NoError(t, err) 49 + assert.NotEmpty(t, repo) 50 + 51 + // Enable Actions, and disable Issues, PRs and Releases 52 + err = repo_service.UpdateRepositoryUnits(db.DefaultContext, repo, []repo_model.RepoUnit{{ 53 + RepoID: repo.ID, 54 + Type: unit_model.TypeActions, 55 + }}, []unit_model.Type{unit_model.TypeIssues, unit_model.TypePullRequests, unit_model.TypeReleases}) 56 + assert.NoError(t, err) 57 + 58 + return func() { 59 + repo_service.DeleteRepository(db.DefaultContext, user2, repo, false) 60 + } 61 + } 62 + 63 + func addWorkflow(t *testing.T) { 64 + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 65 + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "minimal") 66 + assert.NoError(t, err) 67 + 68 + // Add a workflow file to the repo 69 + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ 70 + Files: []*files_service.ChangeRepoFile{ 71 + { 72 + Operation: "create", 73 + TreePath: ".gitea/workflows/pr.yml", 74 + ContentReader: strings.NewReader("name: test\non:\n push:\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: echo helloworld\n"), 75 + }, 76 + }, 77 + Message: "add workflow", 78 + OldBranch: "main", 79 + NewBranch: "main", 80 + Author: &files_service.IdentityOptions{ 81 + Name: user2.Name, 82 + Email: user2.Email, 83 + }, 84 + Committer: &files_service.IdentityOptions{ 85 + Name: user2.Name, 86 + Email: user2.Email, 87 + }, 88 + Dates: &files_service.CommitDateOptions{ 89 + Author: time.Now(), 90 + Committer: time.Now(), 91 + }, 92 + }) 93 + assert.NoError(t, err) 94 + assert.NotEmpty(t, addWorkflowToBaseResp) 95 + 96 + assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID})) 97 + } 98 + 99 + func TestWorkflowBadges(t *testing.T) { 100 + onGiteaRun(t, func(t *testing.T, u *url.URL) { 101 + defer tests.PrintCurrentTest(t)() 102 + defer createMinimalRepo(t)() 103 + 104 + addWorkflow(t) 105 + 106 + // Actions disabled 107 + req := NewRequest(t, "GET", "/user2/repo1/badges/workflows/test.yaml/badge.svg") 108 + resp := MakeRequest(t, req, http.StatusSeeOther) 109 + assertBadge(t, resp, "test.yaml-Not%20found-crimson") 110 + 111 + req = NewRequest(t, "GET", "/user2/repo1/badges/workflows/test.yaml/badge.svg?branch=no-such-branch") 112 + resp = MakeRequest(t, req, http.StatusSeeOther) 113 + assertBadge(t, resp, "test.yaml-Not%20found-crimson") 114 + 115 + // Actions enabled 116 + req = NewRequest(t, "GET", "/user2/minimal/badges/workflows/pr.yml/badge.svg") 117 + resp = MakeRequest(t, req, http.StatusSeeOther) 118 + assertBadge(t, resp, "pr.yml-waiting-lightgrey") 119 + 120 + req = NewRequest(t, "GET", "/user2/minimal/badges/workflows/pr.yml/badge.svg?branch=main") 121 + resp = MakeRequest(t, req, http.StatusSeeOther) 122 + assertBadge(t, resp, "pr.yml-waiting-lightgrey") 123 + 124 + req = NewRequest(t, "GET", "/user2/minimal/badges/workflows/pr.yml/badge.svg?branch=no-such-branch") 125 + resp = MakeRequest(t, req, http.StatusSeeOther) 126 + assertBadge(t, resp, "pr.yml-Not%20found-crimson") 127 + 128 + req = NewRequest(t, "GET", "/user2/minimal/badges/workflows/pr.yml/badge.svg?event=cron") 129 + resp = MakeRequest(t, req, http.StatusSeeOther) 130 + assertBadge(t, resp, "pr.yml-Not%20found-crimson") 131 + 132 + // GitHub compatibility 133 + req = NewRequest(t, "GET", "/user2/minimal/actions/workflows/pr.yml/badge.svg") 134 + resp = MakeRequest(t, req, http.StatusSeeOther) 135 + assertBadge(t, resp, "pr.yml-waiting-lightgrey") 136 + 137 + req = NewRequest(t, "GET", "/user2/minimal/actions/workflows/pr.yml/badge.svg?branch=main") 138 + resp = MakeRequest(t, req, http.StatusSeeOther) 139 + assertBadge(t, resp, "pr.yml-waiting-lightgrey") 140 + 141 + req = NewRequest(t, "GET", "/user2/minimal/actions/workflows/pr.yml/badge.svg?branch=no-such-branch") 142 + resp = MakeRequest(t, req, http.StatusSeeOther) 143 + assertBadge(t, resp, "pr.yml-Not%20found-crimson") 144 + 145 + req = NewRequest(t, "GET", "/user2/minimal/actions/workflows/pr.yml/badge.svg?event=cron") 146 + resp = MakeRequest(t, req, http.StatusSeeOther) 147 + assertBadge(t, resp, "pr.yml-Not%20found-crimson") 148 + }) 149 + } 150 + 151 + func TestBadges(t *testing.T) { 152 + defer tests.PrepareTestEnv(t)() 153 + 154 + t.Run("Stars", func(t *testing.T) { 155 + defer tests.PrintCurrentTest(t)() 156 + 157 + req := NewRequest(t, "GET", "/user2/repo1/badges/stars.svg") 158 + resp := MakeRequest(t, req, http.StatusSeeOther) 159 + 160 + assertBadge(t, resp, "stars-0-blue") 161 + }) 162 + 163 + t.Run("Issues", func(t *testing.T) { 164 + defer tests.PrintCurrentTest(t)() 165 + defer createMinimalRepo(t)() 166 + 167 + // Issues enabled 168 + req := NewRequest(t, "GET", "/user2/repo1/badges/issues.svg") 169 + resp := MakeRequest(t, req, http.StatusSeeOther) 170 + assertBadge(t, resp, "issues-2-blue") 171 + 172 + req = NewRequest(t, "GET", "/user2/repo1/badges/issues/open.svg") 173 + resp = MakeRequest(t, req, http.StatusSeeOther) 174 + assertBadge(t, resp, "issues-1%20open-blue") 175 + 176 + req = NewRequest(t, "GET", "/user2/repo1/badges/issues/closed.svg") 177 + resp = MakeRequest(t, req, http.StatusSeeOther) 178 + assertBadge(t, resp, "issues-1%20closed-blue") 179 + 180 + // Issues disabled 181 + req = NewRequest(t, "GET", "/user2/minimal/badges/issues.svg") 182 + resp = MakeRequest(t, req, http.StatusSeeOther) 183 + assertBadge(t, resp, "issues-Not%20found-crimson") 184 + 185 + req = NewRequest(t, "GET", "/user2/minimal/badges/issues/open.svg") 186 + resp = MakeRequest(t, req, http.StatusSeeOther) 187 + assertBadge(t, resp, "issues-Not%20found-crimson") 188 + 189 + req = NewRequest(t, "GET", "/user2/minimal/badges/issues/closed.svg") 190 + resp = MakeRequest(t, req, http.StatusSeeOther) 191 + assertBadge(t, resp, "issues-Not%20found-crimson") 192 + }) 193 + 194 + t.Run("Pulls", func(t *testing.T) { 195 + defer tests.PrintCurrentTest(t)() 196 + defer createMinimalRepo(t)() 197 + 198 + // Pull requests enabled 199 + req := NewRequest(t, "GET", "/user2/repo1/badges/pulls.svg") 200 + resp := MakeRequest(t, req, http.StatusSeeOther) 201 + assertBadge(t, resp, "pulls-3-blue") 202 + 203 + req = NewRequest(t, "GET", "/user2/repo1/badges/pulls/open.svg") 204 + resp = MakeRequest(t, req, http.StatusSeeOther) 205 + assertBadge(t, resp, "pulls-3%20open-blue") 206 + 207 + req = NewRequest(t, "GET", "/user2/repo1/badges/pulls/closed.svg") 208 + resp = MakeRequest(t, req, http.StatusSeeOther) 209 + assertBadge(t, resp, "pulls-0%20closed-blue") 210 + 211 + // Pull requests disabled 212 + req = NewRequest(t, "GET", "/user2/minimal/badges/pulls.svg") 213 + resp = MakeRequest(t, req, http.StatusSeeOther) 214 + assertBadge(t, resp, "pulls-Not%20found-crimson") 215 + 216 + req = NewRequest(t, "GET", "/user2/minimal/badges/pulls/open.svg") 217 + resp = MakeRequest(t, req, http.StatusSeeOther) 218 + assertBadge(t, resp, "pulls-Not%20found-crimson") 219 + 220 + req = NewRequest(t, "GET", "/user2/minimal/badges/pulls/closed.svg") 221 + resp = MakeRequest(t, req, http.StatusSeeOther) 222 + assertBadge(t, resp, "pulls-Not%20found-crimson") 223 + }) 224 + 225 + t.Run("Release", func(t *testing.T) { 226 + defer tests.PrintCurrentTest(t)() 227 + defer createMinimalRepo(t)() 228 + 229 + req := NewRequest(t, "GET", "/user2/repo1/badges/release.svg") 230 + resp := MakeRequest(t, req, http.StatusSeeOther) 231 + assertBadge(t, resp, "release-v1.1-blue") 232 + 233 + req = NewRequest(t, "GET", "/user2/minimal/badges/release.svg") 234 + resp = MakeRequest(t, req, http.StatusSeeOther) 235 + assertBadge(t, resp, "release-Not%20found-crimson") 236 + }) 237 + }