mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-24 05:39:46 +02:00
Closes https://github.com/go-gitea/gitea/issues/33579. Adds browser previews for Actions artifacts. Selecting an artifact opens its file browser; selecting a file renders it in the same tab. The ZIP download remains available separately. Previews require sign-in and read access to the run. Text, image and PDF files are supported; rendered HTML and JavaScript run in a sandboxed frame and are labeled as automatically generated. The frame loads files from a signed link that expires after an hour, because its requests carry no session cookie. `[actions] ARTIFACT_PREVIEW_MAX_SIZE` limits total previewable artifact size (`0` disables previews; `-1` removes the limit); individual files also follow `[ui] MAX_DISPLAY_FILE_SIZE`. <img width="1803" height="913" alt="image" src="https://github.com/user-attachments/assets/a38fd704-2244-44fa-9181-c695ecbe0276" /> Docs: https://gitea.com/gitea/docs/pulls/533 --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Zettat123 <zettat123@gmail.com>
103 lines
3.4 KiB
Go
103 lines
3.4 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package actions
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dev/modules/setting"
|
|
"gitea.dev/modules/test"
|
|
"gitea.dev/modules/typesniffer"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestNewArtifactPreviewList(t *testing.T) {
|
|
paths := []string{"b.txt", "a.txt", "b.txt"}
|
|
assert.Equal(t, artifactPreviewList{paths: []string{"a.txt", "b.txt"}}, newArtifactPreviewList(paths))
|
|
|
|
paths = make([]string, artifactPreviewMaxFiles+10)
|
|
for i := range paths {
|
|
paths[i] = "file-" + strconv.Itoa(i)
|
|
}
|
|
list := newArtifactPreviewList(paths)
|
|
assert.True(t, list.truncated)
|
|
assert.Len(t, list.paths, artifactPreviewMaxFiles)
|
|
paths[0] = "changed"
|
|
assert.Equal(t, "file-0", list.paths[0])
|
|
}
|
|
|
|
func TestBuildArtifactPreviewFiles(t *testing.T) {
|
|
files := buildArtifactPreviewFiles([]string{"README.md", "report/assets/chart.svg", "report/index.html"}, "report/index.html", "/preview/")
|
|
assert.Equal(t, []ArtifactPreviewFile{
|
|
{Path: "README.md", Name: "README.md", Link: "/preview/README.md"},
|
|
{Path: "report", Name: "report"},
|
|
{Path: "report/assets", Name: "assets", Depth: 1},
|
|
{Path: "report/assets/chart.svg", Name: "chart.svg", Link: "/preview/report/assets/chart.svg", Depth: 2},
|
|
{Path: "report/index.html", Name: "index.html", Link: "/preview/report/index.html", Depth: 1, Selected: true},
|
|
}, files)
|
|
}
|
|
|
|
func TestArtifactPreviewContentType(t *testing.T) {
|
|
sniffedText := typesniffer.FromContentType("text/plain; charset=utf-8")
|
|
assert.Equal(t, "text/html; charset=utf-8", artifactPreviewContentType("index.HTM", sniffedText))
|
|
assert.Equal(t, "text/css; charset=utf-8", artifactPreviewContentType("style.css", sniffedText))
|
|
assert.Equal(t, "text/javascript; charset=utf-8", artifactPreviewContentType("script.mjs", sniffedText))
|
|
assert.Equal(t, "text/plain; charset=utf-8", artifactPreviewContentType("output.txt", sniffedText))
|
|
assert.Equal(t, "image/png", artifactPreviewContentType("image.txt", typesniffer.FromContentType("image/png")))
|
|
}
|
|
|
|
func TestIsArtifactPreviewSizeAllowed(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.Actions.ArtifactPreviewMaxSize, int64(-1))()
|
|
assert.True(t, isArtifactPreviewSizeAllowed(1<<40))
|
|
|
|
setting.Actions.ArtifactPreviewMaxSize = 0
|
|
assert.False(t, isArtifactPreviewSizeAllowed(0))
|
|
|
|
setting.Actions.ArtifactPreviewMaxSize = 10
|
|
assert.True(t, isArtifactPreviewSizeAllowed(10))
|
|
assert.False(t, isArtifactPreviewSizeAllowed(11))
|
|
}
|
|
|
|
func TestInsertArtifactPreviewHelperScript(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
before, after string
|
|
}{
|
|
{
|
|
in: "any",
|
|
before: "",
|
|
after: "any",
|
|
},
|
|
{
|
|
in: "<Head >any</head>",
|
|
before: "<Head >",
|
|
after: "any</head>",
|
|
},
|
|
{
|
|
in: "any<Body >",
|
|
before: "any<head>",
|
|
after: "</head><Body >",
|
|
},
|
|
{
|
|
in: "<Html >any</html>",
|
|
before: "<Html ><head>",
|
|
after: "</head>any</html>",
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.in, func(t *testing.T) {
|
|
out := string(insertArtifactPreviewHelperScript([]byte(c.in)))
|
|
out, ok := strings.CutPrefix(out, c.before)
|
|
assert.True(t, ok, "expect prefix %q for input %q", c.before, c.in)
|
|
out, ok = strings.CutSuffix(out, c.after)
|
|
assert.True(t, ok, "expect suffix %q for input %q", c.after, c.in)
|
|
assert.True(t, strings.HasPrefix(out, "<script "))
|
|
assert.True(t, strings.HasSuffix(out, "</script>"))
|
|
})
|
|
}
|
|
}
|