@@ -12,6 +12,7 @@ import (
12
12
"path"
13
13
"strings"
14
14
"testing"
15
+ "time"
15
16
16
17
auth_model "code.gitea.io/gitea/models/auth"
17
18
"code.gitea.io/gitea/models/repo"
@@ -1058,6 +1059,10 @@ func Test_WebhookWorkflowRun(t *testing.T) {
1058
1059
name : "WorkflowRunDepthLimit" ,
1059
1060
callback : testWebhookWorkflowRunDepthLimit ,
1060
1061
},
1062
+ {
1063
+ name : "WorkflowRunDuplicateEvents" ,
1064
+ callback : testWorkflowRunDuplicateEvents ,
1065
+ },
1061
1066
}
1062
1067
for _ , test := range tests {
1063
1068
t .Run (test .name , func (t * testing.T ) {
@@ -1070,6 +1075,129 @@ func Test_WebhookWorkflowRun(t *testing.T) {
1070
1075
}
1071
1076
}
1072
1077
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
+
1073
1201
func testWebhookWorkflowRun (t * testing.T , webhookData * workflowRunWebhook ) {
1074
1202
// 1. create a new webhook with special webhook for repo1
1075
1203
user2 := unittest .AssertExistsAndLoadBean (t , & user_model.User {ID : 2 })
0 commit comments