gitea source for verification 2026-05-22
Some checks are pending
release-nightly / nightly-binary (push) Waiting to run
release-nightly / nightly-docker-rootful (push) Waiting to run
release-nightly / nightly-docker-rootless (push) Waiting to run

This commit is contained in:
2026-05-22 16:44:59 +08:00
commit 7a61cd3abc
5650 changed files with 690128 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package devtest
import (
"fmt"
"html/template"
"net/http"
"path"
"strconv"
"strings"
"time"
"unicode"
"code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/badge"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
)
// List all devtest templates, they will be used for e2e tests for the UI components
func List(ctx *context.Context) {
templateNames, err := templates.AssetFS().ListFiles("devtest", true)
if err != nil {
ctx.ServerError("AssetFS().ListFiles", err)
return
}
var subNames []string
for _, tmplName := range templateNames {
subName := strings.TrimSuffix(tmplName, ".tmpl")
if !strings.HasPrefix(subName, "devtest-") {
subNames = append(subNames, subName)
}
}
ctx.Data["SubNames"] = subNames
ctx.HTML(http.StatusOK, "devtest/devtest-list")
}
func FetchActionTest(ctx *context.Context) {
_ = ctx.Req.ParseForm()
ctx.Flash.Info("fetch-action: " + ctx.Req.Method + " " + ctx.Req.RequestURI + "<br>" +
"Form: " + ctx.Req.Form.Encode() + "<br>" +
"PostForm: " + ctx.Req.PostForm.Encode(),
)
time.Sleep(2 * time.Second)
ctx.JSONRedirect("")
}
func prepareMockDataGiteaUI(ctx *context.Context) {
now := time.Now()
ctx.Data["TimeNow"] = now
ctx.Data["TimePast5s"] = now.Add(-5 * time.Second)
ctx.Data["TimeFuture5s"] = now.Add(5 * time.Second)
ctx.Data["TimePast2m"] = now.Add(-2 * time.Minute)
ctx.Data["TimeFuture2m"] = now.Add(2 * time.Minute)
ctx.Data["TimePast1y"] = now.Add(-1 * 366 * 86400 * time.Second)
ctx.Data["TimeFuture1y"] = now.Add(1 * 366 * 86400 * time.Second)
}
func prepareMockDataBadgeCommitSign(ctx *context.Context) {
var commits []*asymkey.SignCommit
mockUsers, _ := db.Find[user_model.User](ctx, user_model.SearchUserOptions{ListOptions: db.ListOptions{PageSize: 1}})
mockUser := mockUsers[0]
commits = append(commits, &asymkey.SignCommit{
Verification: &asymkey.CommitVerification{},
UserCommit: &user_model.UserCommit{
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
},
})
commits = append(commits, &asymkey.SignCommit{
Verification: &asymkey.CommitVerification{
Verified: true,
Reason: "name / key-id",
SigningUser: mockUser,
SigningKey: &asymkey.GPGKey{KeyID: "12345678"},
TrustStatus: "trusted",
},
UserCommit: &user_model.UserCommit{
User: mockUser,
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
},
})
commits = append(commits, &asymkey.SignCommit{
Verification: &asymkey.CommitVerification{
Verified: true,
Reason: "name / key-id",
SigningUser: mockUser,
SigningSSHKey: &asymkey.PublicKey{Fingerprint: "aa:bb:cc:dd:ee"},
TrustStatus: "untrusted",
},
UserCommit: &user_model.UserCommit{
User: mockUser,
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
},
})
commits = append(commits, &asymkey.SignCommit{
Verification: &asymkey.CommitVerification{
Verified: true,
Reason: "name / key-id",
SigningUser: mockUser,
SigningSSHKey: &asymkey.PublicKey{Fingerprint: "aa:bb:cc:dd:ee"},
TrustStatus: "other(unmatch)",
},
UserCommit: &user_model.UserCommit{
User: mockUser,
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
},
})
commits = append(commits, &asymkey.SignCommit{
Verification: &asymkey.CommitVerification{
Warning: true,
Reason: "gpg.error",
SigningEmail: "test@example.com",
},
UserCommit: &user_model.UserCommit{
User: mockUser,
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
},
})
ctx.Data["MockCommits"] = commits
}
func prepareMockDataBadgeActionsSvg(ctx *context.Context) {
fontFamilyNames := strings.Split(badge.DefaultFontFamily, ",")
selectedFontFamilyName := ctx.FormString("font", fontFamilyNames[0])
selectedStyle := ctx.FormString("style", badge.DefaultStyle)
var badges []badge.Badge
badges = append(badges, badge.GenerateBadge("啊啊啊啊啊啊啊啊啊啊啊啊", "🌞🌞🌞🌞🌞", "green"))
for r := range rune(256) {
if unicode.IsPrint(r) {
s := strings.Repeat(string(r), 15)
badges = append(badges, badge.GenerateBadge(s, util.TruncateRunes(s, 7), "green"))
}
}
var badgeSVGs []template.HTML
for i, b := range badges {
b.IDPrefix = "devtest-" + strconv.FormatInt(int64(i), 10) + "-"
b.FontFamily = selectedFontFamilyName
var h template.HTML
var err error
switch selectedStyle {
case badge.StyleFlat:
h, err = ctx.RenderToHTML("shared/actions/runner_badge_flat", map[string]any{"Badge": b})
case badge.StyleFlatSquare:
h, err = ctx.RenderToHTML("shared/actions/runner_badge_flat-square", map[string]any{"Badge": b})
default:
err = fmt.Errorf("unknown badge style: %s", selectedStyle)
}
if err != nil {
ctx.ServerError("RenderToHTML", err)
return
}
badgeSVGs = append(badgeSVGs, h)
}
ctx.Data["BadgeSVGs"] = badgeSVGs
ctx.Data["BadgeFontFamilyNames"] = fontFamilyNames
ctx.Data["SelectedFontFamilyName"] = selectedFontFamilyName
ctx.Data["BadgeStyles"] = badge.GlobalVars().AllStyles
ctx.Data["SelectedStyle"] = selectedStyle
}
func prepareMockData(ctx *context.Context) {
switch ctx.Req.URL.Path {
case "/devtest/gitea-ui":
prepareMockDataGiteaUI(ctx)
case "/devtest/badge-commit-sign":
prepareMockDataBadgeCommitSign(ctx)
case "/devtest/badge-actions-svg":
prepareMockDataBadgeActionsSvg(ctx)
}
}
func TmplCommon(ctx *context.Context) {
prepareMockData(ctx)
if ctx.Req.Method == http.MethodPost {
_ = ctx.Req.ParseForm()
ctx.Flash.Info("form: "+ctx.Req.Method+" "+ctx.Req.RequestURI+"<br>"+
"Form: "+ctx.Req.Form.Encode()+"<br>"+
"PostForm: "+ctx.Req.PostForm.Encode(),
true,
)
time.Sleep(2 * time.Second)
}
ctx.HTML(http.StatusOK, templates.TplName("devtest"+path.Clean("/"+ctx.PathParam("sub"))))
}

View File

@@ -0,0 +1,58 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package devtest
import (
"net/http"
"strings"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/mailer"
"gopkg.in/yaml.v3"
)
func MailPreviewRender(ctx *context.Context) {
tmplName := ctx.PathParam("*")
mockDataContent, err := templates.AssetFS().ReadFile("mail/" + tmplName + ".devtest.yml")
mockData := map[string]any{}
if err == nil {
err = yaml.Unmarshal(mockDataContent, &mockData)
if err != nil {
http.Error(ctx.Resp, "Failed to parse mock data: "+err.Error(), http.StatusInternalServerError)
return
}
}
mockData["locale"] = ctx.Locale
err = mailer.LoadedTemplates().BodyTemplates.ExecuteTemplate(ctx.Resp, tmplName, mockData)
if err != nil {
_, _ = ctx.Resp.Write([]byte(err.Error()))
}
}
func prepareMailPreviewRender(ctx *context.Context, tmplName string) {
tmplSubject := mailer.LoadedTemplates().SubjectTemplates.Lookup(tmplName)
if tmplSubject == nil {
ctx.Data["RenderMailSubject"] = "default subject"
} else {
var buf strings.Builder
err := tmplSubject.Execute(&buf, nil)
if err != nil {
ctx.Data["RenderMailSubject"] = err.Error()
} else {
ctx.Data["RenderMailSubject"] = buf.String()
}
}
ctx.Data["RenderMailTemplateName"] = tmplName
}
func MailPreview(ctx *context.Context) {
ctx.Data["MailTemplateNames"] = mailer.LoadedTemplates().TemplateNames
tmplName := ctx.FormString("tmpl")
if tmplName != "" {
prepareMailPreviewRender(ctx, tmplName)
}
ctx.HTML(http.StatusOK, "devtest/mail-preview")
}

View File

@@ -0,0 +1,185 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package devtest
import (
mathRand "math/rand/v2"
"net/http"
"slices"
"strconv"
"strings"
"time"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/repo/actions"
"code.gitea.io/gitea/services/context"
)
type generateMockStepsLogOptions struct {
mockCountFirst int
mockCountGeneral int
groupRepeat int
}
func generateMockStepsLog(logCur actions.LogCursor, opts generateMockStepsLogOptions) (stepsLog []*actions.ViewStepLog) {
var mockedLogs []string
mockedLogs = append(mockedLogs, "::group::test group for: step={step}, cursor={cursor}")
mockedLogs = append(mockedLogs, slices.Repeat([]string{"in group msg for: step={step}, cursor={cursor}"}, opts.groupRepeat)...)
mockedLogs = append(mockedLogs, "::endgroup::")
mockedLogs = append(mockedLogs,
"message for: step={step}, cursor={cursor}",
"message for: step={step}, cursor={cursor}",
"##[group]test group for: step={step}, cursor={cursor}",
"in group msg for: step={step}, cursor={cursor}",
"##[endgroup]",
)
// usually the cursor is the "file offset", but here we abuse it as "line number" to make the mock easier, intentionally
cur := logCur.Cursor
// for the first batch, return as many as possible to test the auto-expand and auto-scroll
mockCount := util.Iif(logCur.Cursor == 0, opts.mockCountFirst, opts.mockCountGeneral)
for range mockCount {
logStr := mockedLogs[int(cur)%len(mockedLogs)]
cur++
logStr = strings.ReplaceAll(logStr, "{step}", strconv.Itoa(logCur.Step))
logStr = strings.ReplaceAll(logStr, "{cursor}", strconv.FormatInt(cur, 10))
stepsLog = append(stepsLog, &actions.ViewStepLog{
Step: logCur.Step,
Cursor: cur,
Started: time.Now().Unix() - 1,
Lines: []*actions.ViewStepLogLine{
{Index: cur, Message: logStr, Timestamp: float64(time.Now().UnixNano()) / float64(time.Second)},
},
})
}
return stepsLog
}
func MockActionsView(ctx *context.Context) {
ctx.Data["RunID"] = ctx.PathParam("run")
ctx.Data["JobID"] = ctx.PathParam("job")
ctx.HTML(http.StatusOK, "devtest/repo-action-view")
}
func MockActionsRunsJobs(ctx *context.Context) {
runID := ctx.PathParamInt64("run")
req := web.GetForm(ctx).(*actions.ViewRequest)
resp := &actions.ViewResponse{}
resp.State.Run.TitleHTML = `mock run title <a href="/">link</a>`
resp.State.Run.Status = actions_model.StatusRunning.String()
resp.State.Run.CanCancel = runID == 10
resp.State.Run.CanApprove = runID == 20
resp.State.Run.CanRerun = runID == 30
resp.State.Run.CanDeleteArtifact = true
resp.State.Run.WorkflowID = "workflow-id"
resp.State.Run.WorkflowLink = "./workflow-link"
resp.State.Run.Commit = actions.ViewCommit{
ShortSha: "ccccdddd",
Link: "./commit-link",
Pusher: actions.ViewUser{
DisplayName: "pusher user",
Link: "./pusher-link",
},
Branch: actions.ViewBranch{
Name: "commit-branch",
Link: "./branch-link",
IsDeleted: false,
},
}
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-a",
Size: 100 * 1024,
Status: "expired",
})
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-b",
Size: 1024 * 1024,
Status: "completed",
})
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-very-loooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
Size: 100 * 1024,
Status: "expired",
})
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-really-loooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
Size: 1024 * 1024,
Status: "completed",
})
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
ID: runID * 10,
Name: "job 100",
Status: actions_model.StatusRunning.String(),
CanRerun: true,
Duration: "1h",
})
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
ID: runID*10 + 1,
Name: "job 101",
Status: actions_model.StatusWaiting.String(),
CanRerun: false,
Duration: "2h",
})
resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{
ID: runID*10 + 2,
Name: "job 102",
Status: actions_model.StatusFailure.String(),
CanRerun: false,
Duration: "3h",
})
var mockLogOptions []generateMockStepsLogOptions
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
Summary: "step 0 (mock slow)",
Duration: time.Hour.String(),
Status: actions_model.StatusRunning.String(),
})
mockLogOptions = append(mockLogOptions, generateMockStepsLogOptions{mockCountFirst: 30, mockCountGeneral: 1, groupRepeat: 3})
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
Summary: "step 1 (mock fast)",
Duration: time.Hour.String(),
Status: actions_model.StatusRunning.String(),
})
mockLogOptions = append(mockLogOptions, generateMockStepsLogOptions{mockCountFirst: 30, mockCountGeneral: 3, groupRepeat: 20})
resp.State.CurrentJob.Steps = append(resp.State.CurrentJob.Steps, &actions.ViewJobStep{
Summary: "step 2 (mock error)",
Duration: time.Hour.String(),
Status: actions_model.StatusRunning.String(),
})
mockLogOptions = append(mockLogOptions, generateMockStepsLogOptions{mockCountFirst: 30, mockCountGeneral: 3, groupRepeat: 3})
if len(req.LogCursors) == 0 {
ctx.JSON(http.StatusOK, resp)
return
}
resp.Logs.StepsLog = []*actions.ViewStepLog{}
doSlowResponse := false
doErrorResponse := false
for _, logCur := range req.LogCursors {
if !logCur.Expanded {
continue
}
doSlowResponse = doSlowResponse || logCur.Step == 0
doErrorResponse = doErrorResponse || logCur.Step == 2
resp.Logs.StepsLog = append(resp.Logs.StepsLog, generateMockStepsLog(logCur, mockLogOptions[logCur.Step])...)
}
if doErrorResponse {
if mathRand.Float64() > 0.5 {
ctx.HTTPError(http.StatusInternalServerError, "devtest mock error response")
return
}
}
if doSlowResponse {
time.Sleep(time.Duration(3000) * time.Millisecond)
} else {
time.Sleep(time.Duration(100) * time.Millisecond) // actually, frontend reload every 1 second, any smaller delay is fine
}
ctx.JSON(http.StatusOK, resp)
}