mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-08 20:37:43 +00:00
4c382cea59
Fixes: https://github.com/go-gitea/gitea/issues/38818 Accepts GitHub's `$/` self-repository prefix in a reusable workflow `uses:`, alongside `./`. Gitea's `./` already resolves against the caller's own source repo and commit, which is what `$/` means, so the two are aliases here. Cycle detection folds both prefixes onto one key. Related PR for step-level support: https://gitea.com/gitea/runner/pulls/1150
196 lines
6.0 KiB
Go
196 lines
6.0 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package jobparser
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestParseUses(t *testing.T) {
|
|
t.Run("LocalSameRepo", func(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in string
|
|
want UsesRef
|
|
}{
|
|
{
|
|
name: "gitea dir, .yml",
|
|
in: "./.gitea/workflows/build.yml",
|
|
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/workflows/build.yml"},
|
|
},
|
|
{
|
|
name: "github dir, .yml",
|
|
in: "./.github/workflows/build.yml",
|
|
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".github/workflows/build.yml"},
|
|
},
|
|
{
|
|
name: "gitea dir, .yaml",
|
|
in: "./.gitea/workflows/build.yaml",
|
|
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/workflows/build.yaml"},
|
|
},
|
|
{
|
|
name: "filename containing dots is allowed",
|
|
in: "./.gitea/workflows/foo..bar.yml",
|
|
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/workflows/foo..bar.yml"},
|
|
},
|
|
{
|
|
name: "nested subdirectory",
|
|
in: "./.gitea/workflows/sub/build.yml",
|
|
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/workflows/sub/build.yml"},
|
|
},
|
|
{
|
|
// ParseUses is dir-agnostic; the allowed directories (WORKFLOW_DIRS / SCOPED_WORKFLOW_DIRS) are enforced by ResolveUses.
|
|
name: "scoped workflows dir parses",
|
|
in: "./.gitea/scoped_workflows/lib.yml",
|
|
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/scoped_workflows/lib.yml"},
|
|
},
|
|
{
|
|
name: "non-default dir parses (allowlist enforced downstream)",
|
|
in: "./.gitea/custom_workflows/x.yaml",
|
|
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/custom_workflows/x.yaml"},
|
|
},
|
|
{
|
|
name: "self-repo prefix",
|
|
in: "$/.gitea/workflows/build.yml",
|
|
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/workflows/build.yml"},
|
|
},
|
|
{
|
|
name: "leading/trailing whitespace is trimmed",
|
|
in: " ./.gitea/workflows/build.yml ",
|
|
want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/workflows/build.yml"},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
got, err := ParseUses(c.in)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, c.want, *got)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("LocalCrossRepo", func(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in string
|
|
want UsesRef
|
|
}{
|
|
{
|
|
name: "gitea dir, simple ref",
|
|
in: "owner/repo/.gitea/workflows/build.yml@v1",
|
|
want: UsesRef{
|
|
Kind: UsesKindLocalCrossRepo,
|
|
Owner: "owner",
|
|
Repo: "repo",
|
|
Path: ".gitea/workflows/build.yml",
|
|
Ref: "v1",
|
|
},
|
|
},
|
|
{
|
|
name: "github dir, branch ref",
|
|
in: "owner/repo/.github/workflows/build.yml@main",
|
|
want: UsesRef{
|
|
Kind: UsesKindLocalCrossRepo,
|
|
Owner: "owner",
|
|
Repo: "repo",
|
|
Path: ".github/workflows/build.yml",
|
|
Ref: "main",
|
|
},
|
|
},
|
|
{
|
|
name: ".yaml extension",
|
|
in: "owner/repo/.gitea/workflows/build.yaml@abc123",
|
|
want: UsesRef{
|
|
Kind: UsesKindLocalCrossRepo,
|
|
Owner: "owner",
|
|
Repo: "repo",
|
|
Path: ".gitea/workflows/build.yaml",
|
|
Ref: "abc123",
|
|
},
|
|
},
|
|
{
|
|
name: "ref with slashes (refs/heads/feature)",
|
|
in: "owner/repo/.gitea/workflows/build.yml@refs/heads/feature",
|
|
want: UsesRef{
|
|
Kind: UsesKindLocalCrossRepo,
|
|
Owner: "owner",
|
|
Repo: "repo",
|
|
Path: ".gitea/workflows/build.yml",
|
|
Ref: "refs/heads/feature",
|
|
},
|
|
},
|
|
{
|
|
name: "nested subdirectory under workflows",
|
|
in: "owner/repo/.gitea/workflows/sub/build.yml@v1",
|
|
want: UsesRef{
|
|
Kind: UsesKindLocalCrossRepo,
|
|
Owner: "owner",
|
|
Repo: "repo",
|
|
Path: ".gitea/workflows/sub/build.yml",
|
|
Ref: "v1",
|
|
},
|
|
},
|
|
{
|
|
name: "scoped workflows dir parses (allowlist enforced by ResolveUses)",
|
|
in: "owner/repo/.gitea/scoped_workflows/lib.yml@v1",
|
|
want: UsesRef{
|
|
Kind: UsesKindLocalCrossRepo,
|
|
Owner: "owner",
|
|
Repo: "repo",
|
|
Path: ".gitea/scoped_workflows/lib.yml",
|
|
Ref: "v1",
|
|
},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
got, err := ParseUses(c.in)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, c.want, *got)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("Errors", func(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in string
|
|
}{
|
|
{name: "empty string", in: ""},
|
|
{name: "whitespace only", in: " "},
|
|
|
|
// Same-repo malformed (note: a wrong *directory* parses and should be rejected by the caller)
|
|
{name: "same-repo with @ref", in: "./.gitea/workflows/build.yml@v1"},
|
|
{name: "self-repo with @ref", in: "$/.gitea/workflows/build.yml@v1"},
|
|
{name: "same-repo wrong extension", in: "./.gitea/workflows/build.txt"},
|
|
{name: "same-repo missing extension", in: "./.gitea/workflows/build"},
|
|
{name: "same-repo absolute path", in: "/.gitea/workflows/build.yml"},
|
|
{name: "same-repo path traversal", in: "./.gitea/workflows/../escape.yml"},
|
|
{name: "same-repo double slash", in: "./.gitea/workflows//build.yml"},
|
|
{name: "same-repo redundant ./", in: "./.gitea/workflows/./build.yml"},
|
|
|
|
// Cross-repo malformed
|
|
{name: "cross-repo missing @ref", in: "owner/repo/.gitea/workflows/build.yml"},
|
|
{name: "cross-repo empty ref", in: "owner/repo/.gitea/workflows/build.yml@"},
|
|
{name: "cross-repo missing owner", in: "/repo/.gitea/workflows/build.yml@v1"},
|
|
{name: "cross-repo missing repo", in: "owner//.gitea/workflows/build.yml@v1"},
|
|
{name: "cross-repo wrong extension", in: "owner/repo/.gitea/workflows/build.txt@v1"},
|
|
{name: "cross-repo path traversal", in: "owner/repo/.gitea/workflows/../escape.yml@v1"},
|
|
{name: "cross-repo double slash in path", in: "owner/repo/.gitea/workflows//build.yml@v1"},
|
|
// owner/repo with chars Gitea's name validators reject
|
|
{name: "cross-repo owner with space", in: "bad owner/repo/.gitea/workflows/build.yml@v1"},
|
|
{name: "cross-repo repo with @", in: "owner/re@po/.gitea/workflows/build.yml@v1"},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
_, err := ParseUses(c.in)
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
})
|
|
}
|