2023-04-13 00:16:47 +08:00
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"testing"
"code.gitea.io/gitea/modules/git"
2026-02-19 01:31:01 +01:00
"code.gitea.io/gitea/modules/setting"
2023-04-13 00:16:47 +08:00
api "code.gitea.io/gitea/modules/structs"
2026-04-10 04:57:04 +08:00
"code.gitea.io/gitea/modules/test"
2023-04-13 00:16:47 +08:00
webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/stretchr/testify/assert"
)
2026-04-10 04:57:04 +08:00
func fullWorkflowContent ( part string ) [ ] byte {
return [ ] byte ( `
name: test
` + part + `
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo hello
` )
}
2026-02-19 01:31:01 +01:00
func TestIsWorkflow ( t * testing . T ) {
2026-04-10 04:57:04 +08:00
defer test . MockVariableValue ( & setting . Actions . WorkflowDirs ) ( )
2026-02-19 01:31:01 +01:00
tests := [ ] struct {
name string
dirs [ ] string
path string
expected bool
} {
{
name : "default with yml extension" ,
dirs : [ ] string { ".gitea/workflows" , ".github/workflows" } ,
path : ".gitea/workflows/test.yml" ,
expected : true ,
} ,
{
name : "default with yaml extension" ,
dirs : [ ] string { ".gitea/workflows" , ".github/workflows" } ,
path : ".github/workflows/test.yaml" ,
expected : true ,
} ,
{
name : "only gitea configured, github path rejected" ,
dirs : [ ] string { ".gitea/workflows" } ,
path : ".github/workflows/test.yml" ,
expected : false ,
} ,
{
name : "only github configured, gitea path rejected" ,
dirs : [ ] string { ".github/workflows" } ,
path : ".gitea/workflows/test.yml" ,
expected : false ,
} ,
{
name : "custom workflow dir" ,
dirs : [ ] string { ".custom/workflows" } ,
path : ".custom/workflows/deploy.yml" ,
expected : true ,
} ,
{
name : "non-workflow file" ,
dirs : [ ] string { ".gitea/workflows" , ".github/workflows" } ,
path : ".gitea/workflows/readme.md" ,
expected : false ,
} ,
{
name : "directory boundary" ,
dirs : [ ] string { ".gitea/workflows" } ,
path : ".gitea/workflows2/test.yml" ,
expected : false ,
} ,
{
name : "unrelated path" ,
dirs : [ ] string { ".gitea/workflows" , ".github/workflows" } ,
path : "src/main.go" ,
expected : false ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
setting . Actions . WorkflowDirs = tt . dirs
assert . Equal ( t , tt . expected , IsWorkflow ( tt . path ) )
} )
}
}
2023-04-13 00:16:47 +08:00
func TestDetectMatched ( t * testing . T ) {
testCases := [ ] struct {
desc string
commit * git . Commit
triggedEvent webhook_module . HookEventType
payload api . Payloader
yamlOn string
expected bool
} {
{
2023-06-26 14:33:18 +08:00
desc : "HookEventCreate(create) matches GithubEventCreate(create)" ,
2023-04-13 00:16:47 +08:00
triggedEvent : webhook_module . HookEventCreate ,
payload : nil ,
yamlOn : "on: create" ,
expected : true ,
} ,
{
2023-06-26 14:33:18 +08:00
desc : "HookEventIssues(issues) `opened` action matches GithubEventIssues(issues)" ,
2023-04-13 00:16:47 +08:00
triggedEvent : webhook_module . HookEventIssues ,
payload : & api . IssuePayload { Action : api . HookIssueOpened } ,
yamlOn : "on: issues" ,
expected : true ,
} ,
{
2023-06-26 14:33:18 +08:00
desc : "HookEventIssues(issues) `milestoned` action matches GithubEventIssues(issues)" ,
2023-04-13 00:16:47 +08:00
triggedEvent : webhook_module . HookEventIssues ,
payload : & api . IssuePayload { Action : api . HookIssueMilestoned } ,
yamlOn : "on: issues" ,
expected : true ,
} ,
{
2023-06-26 14:33:18 +08:00
desc : "HookEventPullRequestSync(pull_request_sync) matches GithubEventPullRequest(pull_request)" ,
2023-04-13 00:16:47 +08:00
triggedEvent : webhook_module . HookEventPullRequestSync ,
payload : & api . PullRequestPayload { Action : api . HookIssueSynchronized } ,
yamlOn : "on: pull_request" ,
expected : true ,
} ,
{
2023-06-26 14:33:18 +08:00
desc : "HookEventPullRequest(pull_request) `label_updated` action doesn't match GithubEventPullRequest(pull_request) with no activity type" ,
2023-04-13 00:16:47 +08:00
triggedEvent : webhook_module . HookEventPullRequest ,
payload : & api . PullRequestPayload { Action : api . HookIssueLabelUpdated } ,
yamlOn : "on: pull_request" ,
expected : false ,
} ,
2023-07-08 00:30:07 +08:00
{
desc : "HookEventPullRequest(pull_request) `closed` action doesn't match GithubEventPullRequest(pull_request) with no activity type" ,
triggedEvent : 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" ,
triggedEvent : 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 ,
} ,
2023-04-13 00:16:47 +08:00
{
2023-06-26 14:33:18 +08:00
desc : "HookEventPullRequest(pull_request) `label_updated` action matches GithubEventPullRequest(pull_request) with `label` activity type" ,
2023-04-13 00:16:47 +08:00
triggedEvent : webhook_module . HookEventPullRequest ,
payload : & api . PullRequestPayload { Action : api . HookIssueLabelUpdated } ,
yamlOn : "on:\n pull_request:\n types: [labeled]" ,
expected : true ,
} ,
{
2023-06-26 14:33:18 +08:00
desc : "HookEventPullRequestReviewComment(pull_request_review_comment) matches GithubEventPullRequestReviewComment(pull_request_review_comment)" ,
2023-04-13 00:16:47 +08:00
triggedEvent : webhook_module . HookEventPullRequestReviewComment ,
payload : & api . PullRequestPayload { Action : api . HookIssueReviewed } ,
yamlOn : "on:\n pull_request_review_comment:\n types: [created]" ,
expected : true ,
} ,
{
2023-06-26 14:33:18 +08:00
desc : "HookEventPullRequestReviewRejected(pull_request_review_rejected) doesn't match GithubEventPullRequestReview(pull_request_review) with `dismissed` activity type (we don't support `dismissed` at present)" ,
2023-04-13 00:16:47 +08:00
triggedEvent : webhook_module . HookEventPullRequestReviewRejected ,
payload : & api . PullRequestPayload { Action : api . HookIssueReviewed } ,
yamlOn : "on:\n pull_request_review:\n types: [dismissed]" ,
expected : false ,
} ,
{
2023-06-26 14:33:18 +08:00
desc : "HookEventRelease(release) `published` action matches GithubEventRelease(release) with `published` activity type" ,
2023-04-13 00:16:47 +08:00
triggedEvent : webhook_module . HookEventRelease ,
payload : & api . ReleasePayload { Action : api . HookReleasePublished } ,
yamlOn : "on:\n release:\n types: [published]" ,
expected : true ,
} ,
{
2023-06-26 14:33:18 +08:00
desc : "HookEventPackage(package) `created` action doesn't match GithubEventRegistryPackage(registry_package) with `updated` activity type" ,
2023-04-13 00:16:47 +08:00
triggedEvent : webhook_module . HookEventPackage ,
payload : & api . PackagePayload { Action : api . HookPackageCreated } ,
yamlOn : "on:\n registry_package:\n types: [updated]" ,
expected : false ,
} ,
2023-04-18 01:49:47 +08:00
{
2023-06-26 14:33:18 +08:00
desc : "HookEventWiki(wiki) matches GithubEventGollum(gollum)" ,
2023-04-18 01:49:47 +08:00
triggedEvent : webhook_module . HookEventWiki ,
payload : nil ,
yamlOn : "on: gollum" ,
expected : true ,
} ,
2024-01-13 05:50:38 +08:00
{
2026-02-28 20:23:20 +01:00
desc : "HookEventSchedule(schedule) matches GithubEventSchedule(schedule)" ,
2024-01-13 05:50:38 +08:00
triggedEvent : webhook_module . HookEventSchedule ,
payload : nil ,
yamlOn : "on: schedule" ,
expected : true ,
} ,
2025-06-10 02:44:45 +09:00
{
desc : "push to tag matches workflow with paths condition (should skip paths check)" ,
triggedEvent : 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 ,
} ,
2023-04-13 00:16:47 +08:00
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
2026-04-10 04:57:04 +08:00
evts , err := GetEventsFromContent ( fullWorkflowContent ( tc . yamlOn ) )
2023-04-13 00:16:47 +08:00
assert . NoError ( t , err )
assert . Len ( t , evts , 1 )
2023-08-05 14:26:06 +08:00
assert . Equal ( t , tc . expected , detectMatched ( nil , tc . commit , tc . triggedEvent , tc . payload , evts [ 0 ] ) )
2023-04-13 00:16:47 +08:00
} )
}
}
2025-09-24 22:15:38 +05:30
func TestMatchIssuesEvent ( t * testing . T ) {
testCases := [ ] struct {
desc string
payload * api . IssuePayload
yamlOn string
expected bool
eventType string
} {
{
desc : "Label deletion should trigger unlabeled event" ,
payload : & api . IssuePayload {
Action : api . HookIssueLabelUpdated ,
Issue : & api . Issue {
Labels : [ ] * api . Label { } ,
} ,
Changes : & api . ChangesPayload {
RemovedLabels : [ ] * api . Label {
{ ID : 123 , Name : "deleted-label" } ,
} ,
} ,
} ,
yamlOn : "on:\n issues:\n types: [unlabeled]" ,
expected : true ,
eventType : "unlabeled" ,
} ,
{
desc : "Label deletion with existing labels should trigger unlabeled event" ,
payload : & api . IssuePayload {
Action : api . HookIssueLabelUpdated ,
Issue : & api . Issue {
Labels : [ ] * api . Label {
{ ID : 456 , Name : "existing-label" } ,
} ,
} ,
Changes : & api . ChangesPayload {
AddedLabels : nil ,
RemovedLabels : [ ] * api . Label {
{ ID : 123 , Name : "deleted-label" } ,
} ,
} ,
} ,
yamlOn : "on:\n issues:\n types: [unlabeled]" ,
expected : true ,
eventType : "unlabeled" ,
} ,
{
desc : "Label addition should trigger labeled event" ,
payload : & api . IssuePayload {
Action : api . HookIssueLabelUpdated ,
Issue : & api . Issue {
Labels : [ ] * api . Label {
{ ID : 123 , Name : "new-label" } ,
} ,
} ,
Changes : & api . ChangesPayload {
AddedLabels : [ ] * api . Label {
{ ID : 123 , Name : "new-label" } ,
} ,
RemovedLabels : [ ] * api . Label { } , // Empty array, no labels removed
} ,
} ,
yamlOn : "on:\n issues:\n types: [labeled]" ,
expected : true ,
eventType : "labeled" ,
} ,
{
desc : "Label clear should trigger unlabeled event" ,
payload : & api . IssuePayload {
Action : api . HookIssueLabelCleared ,
Issue : & api . Issue {
Labels : [ ] * api . Label { } ,
} ,
} ,
yamlOn : "on:\n issues:\n types: [unlabeled]" ,
expected : true ,
eventType : "unlabeled" ,
} ,
{
desc : "Both adding and removing labels should trigger labeled event" ,
payload : & api . IssuePayload {
Action : api . HookIssueLabelUpdated ,
Issue : & api . Issue {
Labels : [ ] * api . Label {
{ ID : 789 , Name : "new-label" } ,
} ,
} ,
Changes : & api . ChangesPayload {
AddedLabels : [ ] * api . Label {
{ ID : 789 , Name : "new-label" } ,
} ,
RemovedLabels : [ ] * api . Label {
{ ID : 123 , Name : "deleted-label" } ,
} ,
} ,
} ,
yamlOn : "on:\n issues:\n types: [labeled]" ,
expected : true ,
eventType : "labeled" ,
} ,
{
desc : "Both adding and removing labels should trigger unlabeled event" ,
payload : & api . IssuePayload {
Action : api . HookIssueLabelUpdated ,
Issue : & api . Issue {
Labels : [ ] * api . Label {
{ ID : 789 , Name : "new-label" } ,
} ,
} ,
Changes : & api . ChangesPayload {
AddedLabels : [ ] * api . Label {
{ ID : 789 , Name : "new-label" } ,
} ,
RemovedLabels : [ ] * api . Label {
{ ID : 123 , Name : "deleted-label" } ,
} ,
} ,
} ,
yamlOn : "on:\n issues:\n types: [unlabeled]" ,
expected : true ,
eventType : "unlabeled" ,
} ,
{
desc : "Both adding and removing labels should trigger both events" ,
payload : & api . IssuePayload {
Action : api . HookIssueLabelUpdated ,
Issue : & api . Issue {
Labels : [ ] * api . Label {
{ ID : 789 , Name : "new-label" } ,
} ,
} ,
Changes : & api . ChangesPayload {
AddedLabels : [ ] * api . Label {
{ ID : 789 , Name : "new-label" } ,
} ,
RemovedLabels : [ ] * api . Label {
{ ID : 123 , Name : "deleted-label" } ,
} ,
} ,
} ,
yamlOn : "on:\n issues:\n types: [labeled, unlabeled]" ,
expected : true ,
eventType : "multiple" ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
2026-04-10 04:57:04 +08:00
evts , err := GetEventsFromContent ( fullWorkflowContent ( tc . yamlOn ) )
2025-09-24 22:15:38 +05:30
assert . NoError ( t , err )
assert . Len ( t , evts , 1 )
// Test if the event matches as expected
assert . Equal ( t , tc . expected , matchIssuesEvent ( tc . payload , evts [ 0 ] ) )
// For extra validation, check that action mapping works correctly
if tc . eventType == "multiple" {
// Skip direct action mapping validation for multiple events case
// as one action can map to multiple event types
return
}
// Determine expected action for single event case
var expectedAction string
switch tc . payload . Action {
case api . HookIssueLabelUpdated :
if tc . eventType == "labeled" {
expectedAction = "labeled"
} else if tc . eventType == "unlabeled" {
expectedAction = "unlabeled"
}
case api . HookIssueLabelCleared :
expectedAction = "unlabeled"
default :
expectedAction = string ( tc . payload . Action )
}
assert . Equal ( t , expectedAction , tc . eventType , "Event type should match expected" )
} )
}
}