mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-03-25 17:03:05 -04:00
Some checks are pending
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
Make the variable `GITHUB_WORKFLOW_REF` available in Forgejo Action workflows. It is the ref path to the workflow and looks like `testowner/testrepo/.forgejo/workflows/test-workflow.yaml@refs/heads/main` ([GitHub documentation](https://docs.github.com/en/actions/reference/workflows-and-actions/variables)). GitHub Actions like [gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) rely on its presence. See https://code.forgejo.org/forgejo/forgejo-actions-feature-requests/issues/56 for additional details. `GITHUB_WORKFLOW_REF` cannot be generated easily during an action run. Either the path to workflow file has to be hardcoded or inferred by replicating the logic Forgejo uses to determine it. That is further complicated by the fact that Forgejo supports multiple search paths, namely `.forgejo/workflows`, `.gitea/workflows`, and `.github/workflows`. It is also the reason that the workflow directory is now stored in the database alongside the name of the workflow file. Partial implementation is required in Forgejo Runner, see https://code.forgejo.org/forgejo/runner/pulls/1197. Example workflow: ```yaml on: push: workflow_dispatch: schedule: - cron: "* * * * *" jobs: test: runs-on: ubuntu-latest steps: - run: | echo "FORGEJO_WORKFLOW_REF=$FORGEJO_WORKFLOW_REF" echo "GITHUB_WORKFLOW_REF=$GITHUB_WORKFLOW_REF" echo "forgejo.workflow_ref=${{ forgejo.workflow_ref }}" echo "github.workflow_ref=${{ github.workflow_ref }}" ``` ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [ ] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [ ] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10276 Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org> Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch> Co-committed-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
398 lines
14 KiB
Go
398 lines
14 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package actions
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"forgejo.org/modules/git"
|
|
"forgejo.org/modules/setting"
|
|
api "forgejo.org/modules/structs"
|
|
"forgejo.org/modules/test"
|
|
webhook_module "forgejo.org/modules/webhook"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestActionsWorkflowsDetectMatched(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
commit *git.Commit
|
|
triggeredEvent webhook_module.HookEventType
|
|
payload api.Payloader
|
|
yamlOn string
|
|
expected bool
|
|
}{
|
|
{
|
|
desc: "HookEventCreate(create) matches GithubEventCreate(create)",
|
|
triggeredEvent: webhook_module.HookEventCreate,
|
|
payload: nil,
|
|
yamlOn: "on: create",
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "HookEventIssues(issues) `opened` action matches GithubEventIssues(issues)",
|
|
triggeredEvent: webhook_module.HookEventIssues,
|
|
payload: &api.IssuePayload{Action: api.HookIssueOpened},
|
|
yamlOn: "on: issues",
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "HookEventIssueComment(issue_comment) `created` action matches GithubEventIssueComment(issue_comment)",
|
|
triggeredEvent: webhook_module.HookEventIssueComment,
|
|
payload: &api.IssueCommentPayload{Action: api.HookIssueCommentCreated},
|
|
yamlOn: "on:\n issue_comment:\n types: [created]",
|
|
expected: true,
|
|
},
|
|
|
|
{
|
|
desc: "HookEventIssues(issues) `milestoned` action matches GithubEventIssues(issues)",
|
|
triggeredEvent: webhook_module.HookEventIssues,
|
|
payload: &api.IssuePayload{Action: api.HookIssueMilestoned},
|
|
yamlOn: "on: issues",
|
|
expected: true,
|
|
},
|
|
|
|
{
|
|
desc: "HookEventPullRequestSync(pull_request_sync) matches GithubEventPullRequest(pull_request)",
|
|
triggeredEvent: webhook_module.HookEventPullRequestSync,
|
|
payload: &api.PullRequestPayload{Action: api.HookIssueSynchronized},
|
|
yamlOn: "on: pull_request",
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "HookEventPullRequest(pull_request) `label_updated` action doesn't match GithubEventPullRequest(pull_request) with no activity type",
|
|
triggeredEvent: webhook_module.HookEventPullRequest,
|
|
payload: &api.PullRequestPayload{Action: api.HookIssueLabelUpdated},
|
|
yamlOn: "on: pull_request",
|
|
expected: false,
|
|
},
|
|
{
|
|
desc: "HookEventPullRequest(pull_request) `closed` action doesn't match GithubEventPullRequest(pull_request) with no activity type",
|
|
triggeredEvent: webhook_module.HookEventPullRequest,
|
|
payload: &api.PullRequestPayload{Action: api.HookIssueClosed},
|
|
yamlOn: "on: pull_request",
|
|
expected: false,
|
|
},
|
|
{
|
|
desc: "HookEventPullRequest(pull_request) `closed` action doesn't match GithubEventPullRequest(pull_request) with branches",
|
|
triggeredEvent: webhook_module.HookEventPullRequest,
|
|
payload: &api.PullRequestPayload{
|
|
Action: api.HookIssueClosed,
|
|
PullRequest: &api.PullRequest{
|
|
Base: &api.PRBranchInfo{},
|
|
},
|
|
},
|
|
yamlOn: "on:\n pull_request:\n branches: [main]",
|
|
expected: false,
|
|
},
|
|
{
|
|
desc: "HookEventPullRequest(pull_request) `label_updated` action matches GithubEventPullRequest(pull_request) with `label` activity type",
|
|
triggeredEvent: webhook_module.HookEventPullRequest,
|
|
payload: &api.PullRequestPayload{Action: api.HookIssueLabelUpdated},
|
|
yamlOn: "on:\n pull_request:\n types: [labeled]",
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "HookEventPullRequestReviewComment(pull_request_review_comment) matches GithubEventPullRequestReviewComment(pull_request_review_comment)",
|
|
triggeredEvent: webhook_module.HookEventPullRequestReviewComment,
|
|
payload: &api.PullRequestPayload{Action: api.HookIssueReviewed},
|
|
yamlOn: "on:\n pull_request_review_comment:\n types: [created]",
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "HookEventPullRequestReviewRejected(pull_request_review_rejected) doesn't match GithubEventPullRequestReview(pull_request_review) with `dismissed` activity type (we don't support `dismissed` at present)",
|
|
triggeredEvent: webhook_module.HookEventPullRequestReviewRejected,
|
|
payload: &api.PullRequestPayload{Action: api.HookIssueReviewed},
|
|
yamlOn: "on:\n pull_request_review:\n types: [dismissed]",
|
|
expected: false,
|
|
},
|
|
{
|
|
desc: "HookEventRelease(release) `published` action matches GithubEventRelease(release) with `published` activity type",
|
|
triggeredEvent: webhook_module.HookEventRelease,
|
|
payload: &api.ReleasePayload{Action: api.HookReleasePublished},
|
|
yamlOn: "on:\n release:\n types: [published]",
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "HookEventRelease(updated) `updated` action matches GithubEventRelease(edited) with `edited` activity type",
|
|
triggeredEvent: webhook_module.HookEventRelease,
|
|
payload: &api.ReleasePayload{Action: api.HookReleaseUpdated},
|
|
yamlOn: "on:\n release:\n types: [edited]",
|
|
expected: true,
|
|
},
|
|
|
|
{
|
|
desc: "HookEventPackage(package) `created` action doesn't match GithubEventRegistryPackage(registry_package) with `updated` activity type",
|
|
triggeredEvent: webhook_module.HookEventPackage,
|
|
payload: &api.PackagePayload{Action: api.HookPackageCreated},
|
|
yamlOn: "on:\n registry_package:\n types: [updated]",
|
|
expected: false,
|
|
},
|
|
{
|
|
desc: "HookEventWiki(wiki) matches GithubEventGollum(gollum)",
|
|
triggeredEvent: webhook_module.HookEventWiki,
|
|
payload: nil,
|
|
yamlOn: "on: gollum",
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "HookEventSchedule(schedule) matches GithubEventSchedule(schedule)",
|
|
triggeredEvent: webhook_module.HookEventSchedule,
|
|
payload: nil,
|
|
yamlOn: "on: schedule",
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "HookEventWorkflowDispatch(workflow_dispatch) matches GithubEventWorkflowDispatch(workflow_dispatch)",
|
|
triggeredEvent: webhook_module.HookEventWorkflowDispatch,
|
|
payload: nil,
|
|
yamlOn: "on: workflow_dispatch",
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "push to tag matches workflow with paths condition (should skip paths check)",
|
|
triggeredEvent: webhook_module.HookEventPush,
|
|
payload: &api.PushPayload{
|
|
Ref: "refs/tags/v1.0.0",
|
|
Before: "0000000",
|
|
Commits: []*api.PayloadCommit{
|
|
{
|
|
ID: "abcdef123456",
|
|
Added: []string{"src/main.go"},
|
|
Message: "Release v1.0.0",
|
|
},
|
|
},
|
|
},
|
|
commit: nil,
|
|
yamlOn: "on:\n push:\n paths:\n - src/**",
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
evts, err := GetEventsFromContent([]byte(tc.yamlOn))
|
|
require.NoError(t, err)
|
|
assert.Len(t, evts, 1)
|
|
assert.Equal(t, tc.expected, detectMatched(nil, tc.commit, tc.triggeredEvent, tc.payload, evts[0]))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestActionsWorkflowsListWorkflowsReturnsNoWorkflowsIfThereAreNone(t *testing.T) {
|
|
t.Cleanup(test.MockVariableValue(&setting.Git.HomePath, t.TempDir()))
|
|
require.NoError(t, git.InitSimple(t.Context()))
|
|
|
|
committer := git.Signature{
|
|
Email: "jane@example.com",
|
|
Name: "Jane",
|
|
When: time.Now(),
|
|
}
|
|
repoHome := t.TempDir()
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(repoHome, "README.md"), []byte("My project"), 0o644))
|
|
|
|
require.NoError(t, git.InitRepository(t.Context(), repoHome, false, git.Sha1ObjectFormat.Name()))
|
|
require.NoError(t, git.AddChanges(repoHome, true))
|
|
require.NoError(t, git.CommitChanges(repoHome, git.CommitChangesOptions{Message: "Import", Committer: &committer}))
|
|
|
|
gitRepo, err := git.OpenRepository(t.Context(), repoHome)
|
|
require.NoError(t, err)
|
|
defer gitRepo.Close()
|
|
|
|
headBranch, err := gitRepo.GetHEADBranch()
|
|
require.NoError(t, err)
|
|
|
|
lastCommitID, err := gitRepo.GetBranchCommitID(headBranch.Name)
|
|
require.NoError(t, err)
|
|
|
|
lastCommit, err := gitRepo.GetCommit(lastCommitID)
|
|
require.NoError(t, err)
|
|
|
|
source, workflows, err := ListWorkflows(lastCommit)
|
|
require.NoError(t, err)
|
|
|
|
assert.Empty(t, source)
|
|
assert.Empty(t, workflows)
|
|
}
|
|
|
|
func TestActionsWorkflowsListWorkflowsIgnoresNonWorkflowFiles(t *testing.T) {
|
|
t.Cleanup(test.MockVariableValue(&setting.Git.HomePath, t.TempDir()))
|
|
require.NoError(t, git.InitSimple(t.Context()))
|
|
|
|
committer := git.Signature{
|
|
Email: "jane@example.com",
|
|
Name: "Jane",
|
|
When: time.Now(),
|
|
}
|
|
githubWorkflow := []byte(`
|
|
name: GitHub Workflow
|
|
on:
|
|
push:
|
|
jobs:
|
|
do-something:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- run: echo 'Hello GitHub'
|
|
`)
|
|
repoHome := t.TempDir()
|
|
|
|
require.NoError(t, os.MkdirAll(filepath.Join(repoHome, ".forgejo/workflows"), os.ModePerm))
|
|
require.NoError(t, os.WriteFile(filepath.Join(repoHome, ".forgejo/workflows", "README.md"), []byte("My project"), 0o644))
|
|
|
|
// Prepare a valid workflow in .github/workflows to verify that it is ignored because .forgejo/workflows is present.
|
|
require.NoError(t, os.MkdirAll(filepath.Join(repoHome, ".github/workflows"), os.ModePerm))
|
|
require.NoError(t, os.WriteFile(filepath.Join(repoHome, ".github/workflows", "github.yaml"), githubWorkflow, 0o644))
|
|
|
|
require.NoError(t, git.InitRepository(t.Context(), repoHome, false, git.Sha1ObjectFormat.Name()))
|
|
require.NoError(t, git.AddChanges(repoHome, true))
|
|
require.NoError(t, git.CommitChanges(repoHome, git.CommitChangesOptions{Message: "Import", Committer: &committer}))
|
|
|
|
gitRepo, err := git.OpenRepository(t.Context(), repoHome)
|
|
require.NoError(t, err)
|
|
defer gitRepo.Close()
|
|
|
|
headBranch, err := gitRepo.GetHEADBranch()
|
|
require.NoError(t, err)
|
|
|
|
lastCommitID, err := gitRepo.GetBranchCommitID(headBranch.Name)
|
|
require.NoError(t, err)
|
|
|
|
lastCommit, err := gitRepo.GetCommit(lastCommitID)
|
|
require.NoError(t, err)
|
|
|
|
source, workflows, err := ListWorkflows(lastCommit)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, ".forgejo/workflows", source)
|
|
assert.Empty(t, workflows)
|
|
}
|
|
|
|
func TestActionsWorkflowsListWorkflowsReturnsForgejoWorkflowsOnly(t *testing.T) {
|
|
t.Cleanup(test.MockVariableValue(&setting.Git.HomePath, t.TempDir()))
|
|
require.NoError(t, git.InitSimple(t.Context()))
|
|
|
|
committer := git.Signature{
|
|
Email: "jane@example.com",
|
|
Name: "Jane",
|
|
When: time.Now(),
|
|
}
|
|
forgejoWorkflow := []byte(`
|
|
name: Forgejo Workflow
|
|
on:
|
|
push:
|
|
jobs:
|
|
do-something:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- run: echo 'Hello Forgejo'
|
|
`)
|
|
githubWorkflow := []byte(`
|
|
name: GitHub Workflow
|
|
on:
|
|
push:
|
|
jobs:
|
|
do-something:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- run: echo 'Hello GitHub'
|
|
`)
|
|
repoHome := t.TempDir()
|
|
|
|
require.NoError(t, os.MkdirAll(filepath.Join(repoHome, ".forgejo/workflows"), os.ModePerm))
|
|
require.NoError(t, os.WriteFile(filepath.Join(repoHome, ".forgejo/workflows", "forgejo.yaml"), forgejoWorkflow, 0o644))
|
|
|
|
require.NoError(t, os.MkdirAll(filepath.Join(repoHome, ".github/workflows"), os.ModePerm))
|
|
require.NoError(t, os.WriteFile(filepath.Join(repoHome, ".github/workflows", "github.yaml"), githubWorkflow, 0o644))
|
|
|
|
require.NoError(t, git.InitRepository(t.Context(), repoHome, false, git.Sha1ObjectFormat.Name()))
|
|
require.NoError(t, git.AddChanges(repoHome, true))
|
|
require.NoError(t, git.CommitChanges(repoHome, git.CommitChangesOptions{Message: "Import", Committer: &committer}))
|
|
|
|
gitRepo, err := git.OpenRepository(t.Context(), repoHome)
|
|
require.NoError(t, err)
|
|
defer gitRepo.Close()
|
|
|
|
headBranch, err := gitRepo.GetHEADBranch()
|
|
require.NoError(t, err)
|
|
|
|
lastCommitID, err := gitRepo.GetBranchCommitID(headBranch.Name)
|
|
require.NoError(t, err)
|
|
|
|
lastCommit, err := gitRepo.GetCommit(lastCommitID)
|
|
require.NoError(t, err)
|
|
|
|
source, workflows, err := ListWorkflows(lastCommit)
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, workflows, 1)
|
|
assert.Equal(t, ".forgejo/workflows", source)
|
|
assert.Equal(t, "forgejo.yaml", workflows[0].Name())
|
|
}
|
|
|
|
func TestActionsWorkflowsListWorkflowsReturnsGitHubWorkflowsIfForgejoWorkflowsAbsent(t *testing.T) {
|
|
t.Cleanup(test.MockVariableValue(&setting.Git.HomePath, t.TempDir()))
|
|
require.NoError(t, git.InitSimple(t.Context()))
|
|
|
|
committer := git.Signature{
|
|
Email: "jane@example.com",
|
|
Name: "Jane",
|
|
When: time.Now(),
|
|
}
|
|
buildWorkflow := []byte(`
|
|
name: Build
|
|
on:
|
|
push:
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- run: echo 'We are building'
|
|
`)
|
|
testWorkflow := []byte(`
|
|
name: Test
|
|
on:
|
|
push:
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- run: echo 'We are testing'
|
|
`)
|
|
repoHome := t.TempDir()
|
|
|
|
require.NoError(t, os.MkdirAll(filepath.Join(repoHome, ".github/workflows"), os.ModePerm))
|
|
require.NoError(t, os.WriteFile(filepath.Join(repoHome, ".github/workflows", "build.yaml"), buildWorkflow, 0o644))
|
|
require.NoError(t, os.WriteFile(filepath.Join(repoHome, ".github/workflows", "test.yml"), testWorkflow, 0o644))
|
|
|
|
require.NoError(t, git.InitRepository(t.Context(), repoHome, false, git.Sha1ObjectFormat.Name()))
|
|
require.NoError(t, git.AddChanges(repoHome, true))
|
|
require.NoError(t, git.CommitChanges(repoHome, git.CommitChangesOptions{Message: "Import", Committer: &committer}))
|
|
|
|
gitRepo, err := git.OpenRepository(t.Context(), repoHome)
|
|
require.NoError(t, err)
|
|
defer gitRepo.Close()
|
|
|
|
headBranch, err := gitRepo.GetHEADBranch()
|
|
require.NoError(t, err)
|
|
|
|
lastCommitID, err := gitRepo.GetBranchCommitID(headBranch.Name)
|
|
require.NoError(t, err)
|
|
|
|
lastCommit, err := gitRepo.GetCommit(lastCommitID)
|
|
require.NoError(t, err)
|
|
|
|
source, workflows, err := ListWorkflows(lastCommit)
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, workflows, 2)
|
|
assert.Equal(t, ".github/workflows", source)
|
|
assert.Equal(t, "build.yaml", workflows[0].Name())
|
|
assert.Equal(t, "test.yml", workflows[1].Name())
|
|
}
|