@@ -12,6 +12,7 @@ import (
1212 "path"
1313 "strings"
1414 "testing"
15+ "time"
1516
1617 auth_model "code.gitea.io/gitea/models/auth"
1718 "code.gitea.io/gitea/models/repo"
@@ -1058,6 +1059,10 @@ func Test_WebhookWorkflowRun(t *testing.T) {
10581059 name : "WorkflowRunDepthLimit" ,
10591060 callback : testWebhookWorkflowRunDepthLimit ,
10601061 },
1062+ {
1063+ name : "WorkflowRunDuplicateEvents" ,
1064+ callback : testWorkflowRunDuplicateEvents ,
1065+ },
10611066 }
10621067 for _ , test := range tests {
10631068 t .Run (test .name , func (t * testing.T ) {
@@ -1070,6 +1075,129 @@ func Test_WebhookWorkflowRun(t *testing.T) {
10701075 }
10711076}
10721077
1078+ func testWorkflowRunDuplicateEvents (t * testing.T , webhookData * workflowRunWebhook ) {
1079+ // 1. create a new webhook with special webhook for repo1
1080+ user2 := unittest .AssertExistsAndLoadBean (t , & user_model.User {ID : 2 })
1081+ session := loginUser (t , "user2" )
1082+ token := getTokenForLoggedInUser (t , session , auth_model .AccessTokenScopeWriteRepository , auth_model .AccessTokenScopeWriteUser )
1083+
1084+ testAPICreateWebhookForRepo (t , session , "user2" , "repo1" , webhookData .URL , "workflow_run" )
1085+
1086+ repo1 := unittest .AssertExistsAndLoadBean (t , & repo.Repository {ID : 1 })
1087+
1088+ gitRepo1 , err := gitrepo .OpenRepository (t .Context (), repo1 )
1089+ assert .NoError (t , err )
1090+
1091+ // 2.2 trigger the webhooks
1092+
1093+ // add workflow file to the repo
1094+ // init the workflow
1095+ wfTreePath := ".gitea/workflows/push.yml"
1096+ wfFileContent := `on:
1097+ push:
1098+ workflow_dispatch:
1099+
1100+ jobs:
1101+ test:
1102+ runs-on: ubuntu-latest
1103+ steps:
1104+ - run: exit 0
1105+
1106+ test2:
1107+ needs: [test]
1108+ runs-on: ubuntu-latest
1109+ steps:
1110+ - run: exit 0
1111+
1112+ test3:
1113+ needs: [test, test2]
1114+ runs-on: ubuntu-latest
1115+ steps:
1116+ - run: exit 0
1117+
1118+ test4:
1119+ needs: [test, test2, test3]
1120+ runs-on: ubuntu-latest
1121+ steps:
1122+ - run: exit 0
1123+
1124+ test5:
1125+ needs: [test, test2, test4]
1126+ runs-on: ubuntu-latest
1127+ steps:
1128+ - run: exit 0
1129+
1130+ test6:
1131+ strategy:
1132+ matrix:
1133+ os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04]
1134+ needs: [test, test2, test3]
1135+ runs-on: ${{ matrix.os }}
1136+ steps:
1137+ - run: exit 0
1138+
1139+ test7:
1140+ needs: test6
1141+ runs-on: ubuntu-latest
1142+ steps:
1143+ - run: exit 0
1144+
1145+ test8:
1146+ runs-on: ubuntu-latest
1147+ steps:
1148+ - run: exit 0
1149+
1150+ test9:
1151+ strategy:
1152+ matrix:
1153+ os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, ubuntu-25.04, windows-2022, windows-2025, macos-13, macos-14, macos-15]
1154+ runs-on: ${{ matrix.os }}
1155+ steps:
1156+ - run: exit 0
1157+
1158+ test10:
1159+ runs-on: ubuntu-latest
1160+ steps:
1161+ - run: exit 0`
1162+ opts := getWorkflowCreateFileOptions (user2 , repo1 .DefaultBranch , "create " + wfTreePath , wfFileContent )
1163+ createWorkflowFile (t , token , "user2" , "repo1" , wfTreePath , opts )
1164+
1165+ commitID , err := gitRepo1 .GetBranchCommitID (repo1 .DefaultBranch )
1166+ assert .NoError (t , err )
1167+
1168+ // 3. validate the webhook is triggered
1169+ assert .Equal (t , "workflow_run" , webhookData .triggeredEvent )
1170+ assert .Len (t , webhookData .payloads , 1 )
1171+ assert .Equal (t , "requested" , webhookData .payloads [0 ].Action )
1172+ assert .Equal (t , "queued" , webhookData .payloads [0 ].WorkflowRun .Status )
1173+ assert .Equal (t , repo1 .DefaultBranch , webhookData .payloads [0 ].WorkflowRun .HeadBranch )
1174+ assert .Equal (t , commitID , webhookData .payloads [0 ].WorkflowRun .HeadSha )
1175+ assert .Equal (t , "repo1" , webhookData .payloads [0 ].Repo .Name )
1176+ assert .Equal (t , "user2/repo1" , webhookData .payloads [0 ].Repo .FullName )
1177+
1178+ time .Sleep (15 * time .Second ) // wait for the workflow to be processed
1179+
1180+ // Call cancel ui api
1181+ // Only a web UI API exists for cancelling workflow runs, so use the UI endpoint.
1182+ cancelURL := fmt .Sprintf ("/user2/repo1/actions/runs/%d/cancel" , webhookData .payloads [0 ].WorkflowRun .RunNumber )
1183+ req := NewRequestWithValues (t , "POST" , cancelURL , map [string ]string {
1184+ "_csrf" : GetUserCSRFToken (t , session ),
1185+ })
1186+ session .MakeRequest (t , req , http .StatusOK )
1187+
1188+ assert .Len (t , webhookData .payloads , 2 )
1189+
1190+ // 4. Validate the second webhook payload
1191+ assert .Equal (t , "workflow_run" , webhookData .triggeredEvent )
1192+ assert .Equal (t , "completed" , webhookData .payloads [1 ].Action )
1193+ assert .Equal (t , "push" , webhookData .payloads [1 ].WorkflowRun .Event )
1194+ assert .Equal (t , "completed" , webhookData .payloads [1 ].WorkflowRun .Status )
1195+ assert .Equal (t , repo1 .DefaultBranch , webhookData .payloads [1 ].WorkflowRun .HeadBranch )
1196+ assert .Equal (t , commitID , webhookData .payloads [1 ].WorkflowRun .HeadSha )
1197+ assert .Equal (t , "repo1" , webhookData .payloads [1 ].Repo .Name )
1198+ assert .Equal (t , "user2/repo1" , webhookData .payloads [1 ].Repo .FullName )
1199+ }
1200+
10731201func testWebhookWorkflowRun (t * testing.T , webhookData * workflowRunWebhook ) {
10741202 // 1. create a new webhook with special webhook for repo1
10751203 user2 := unittest .AssertExistsAndLoadBean (t , & user_model.User {ID : 2 })
0 commit comments