Files
5a56e118e4 feat(actions): Add artifact preview in Actions run view (#36754)
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>
2026-09-26 07:42:36 +00:00

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>"))
})
}
}