gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package actions
  4. import (
  5. "testing"
  6. "code.gitea.io/gitea/modules/git"
  7. api "code.gitea.io/gitea/modules/structs"
  8. webhook_module "code.gitea.io/gitea/modules/webhook"
  9. "github.com/stretchr/testify/assert"
  10. )
  11. func TestDetectMatched(t *testing.T) {
  12. testCases := []struct {
  13. desc string
  14. commit *git.Commit
  15. triggedEvent webhook_module.HookEventType
  16. payload api.Payloader
  17. yamlOn string
  18. expected bool
  19. }{
  20. {
  21. desc: "HookEventCreate(create) matches GithubEventCreate(create)",
  22. triggedEvent: webhook_module.HookEventCreate,
  23. payload: nil,
  24. yamlOn: "on: create",
  25. expected: true,
  26. },
  27. {
  28. desc: "HookEventIssues(issues) `opened` action matches GithubEventIssues(issues)",
  29. triggedEvent: webhook_module.HookEventIssues,
  30. payload: &api.IssuePayload{Action: api.HookIssueOpened},
  31. yamlOn: "on: issues",
  32. expected: true,
  33. },
  34. {
  35. desc: "HookEventIssues(issues) `milestoned` action matches GithubEventIssues(issues)",
  36. triggedEvent: webhook_module.HookEventIssues,
  37. payload: &api.IssuePayload{Action: api.HookIssueMilestoned},
  38. yamlOn: "on: issues",
  39. expected: true,
  40. },
  41. {
  42. desc: "HookEventPullRequestSync(pull_request_sync) matches GithubEventPullRequest(pull_request)",
  43. triggedEvent: webhook_module.HookEventPullRequestSync,
  44. payload: &api.PullRequestPayload{Action: api.HookIssueSynchronized},
  45. yamlOn: "on: pull_request",
  46. expected: true,
  47. },
  48. {
  49. desc: "HookEventPullRequest(pull_request) `label_updated` action doesn't match GithubEventPullRequest(pull_request) with no activity type",
  50. triggedEvent: webhook_module.HookEventPullRequest,
  51. payload: &api.PullRequestPayload{Action: api.HookIssueLabelUpdated},
  52. yamlOn: "on: pull_request",
  53. expected: false,
  54. },
  55. {
  56. desc: "HookEventPullRequest(pull_request) `closed` action doesn't match GithubEventPullRequest(pull_request) with no activity type",
  57. triggedEvent: webhook_module.HookEventPullRequest,
  58. payload: &api.PullRequestPayload{Action: api.HookIssueClosed},
  59. yamlOn: "on: pull_request",
  60. expected: false,
  61. },
  62. {
  63. desc: "HookEventPullRequest(pull_request) `closed` action doesn't match GithubEventPullRequest(pull_request) with branches",
  64. triggedEvent: webhook_module.HookEventPullRequest,
  65. payload: &api.PullRequestPayload{
  66. Action: api.HookIssueClosed,
  67. PullRequest: &api.PullRequest{
  68. Base: &api.PRBranchInfo{},
  69. },
  70. },
  71. yamlOn: "on:\n pull_request:\n branches: [main]",
  72. expected: false,
  73. },
  74. {
  75. desc: "HookEventPullRequest(pull_request) `label_updated` action matches GithubEventPullRequest(pull_request) with `label` activity type",
  76. triggedEvent: webhook_module.HookEventPullRequest,
  77. payload: &api.PullRequestPayload{Action: api.HookIssueLabelUpdated},
  78. yamlOn: "on:\n pull_request:\n types: [labeled]",
  79. expected: true,
  80. },
  81. {
  82. desc: "HookEventPullRequestReviewComment(pull_request_review_comment) matches GithubEventPullRequestReviewComment(pull_request_review_comment)",
  83. triggedEvent: webhook_module.HookEventPullRequestReviewComment,
  84. payload: &api.PullRequestPayload{Action: api.HookIssueReviewed},
  85. yamlOn: "on:\n pull_request_review_comment:\n types: [created]",
  86. expected: true,
  87. },
  88. {
  89. desc: "HookEventPullRequestReviewRejected(pull_request_review_rejected) doesn't match GithubEventPullRequestReview(pull_request_review) with `dismissed` activity type (we don't support `dismissed` at present)",
  90. triggedEvent: webhook_module.HookEventPullRequestReviewRejected,
  91. payload: &api.PullRequestPayload{Action: api.HookIssueReviewed},
  92. yamlOn: "on:\n pull_request_review:\n types: [dismissed]",
  93. expected: false,
  94. },
  95. {
  96. desc: "HookEventRelease(release) `published` action matches GithubEventRelease(release) with `published` activity type",
  97. triggedEvent: webhook_module.HookEventRelease,
  98. payload: &api.ReleasePayload{Action: api.HookReleasePublished},
  99. yamlOn: "on:\n release:\n types: [published]",
  100. expected: true,
  101. },
  102. {
  103. desc: "HookEventPackage(package) `created` action doesn't match GithubEventRegistryPackage(registry_package) with `updated` activity type",
  104. triggedEvent: webhook_module.HookEventPackage,
  105. payload: &api.PackagePayload{Action: api.HookPackageCreated},
  106. yamlOn: "on:\n registry_package:\n types: [updated]",
  107. expected: false,
  108. },
  109. {
  110. desc: "HookEventWiki(wiki) matches GithubEventGollum(gollum)",
  111. triggedEvent: webhook_module.HookEventWiki,
  112. payload: nil,
  113. yamlOn: "on: gollum",
  114. expected: true,
  115. },
  116. {
  117. desc: "HookEventSchedue(schedule) matches GithubEventSchedule(schedule)",
  118. triggedEvent: webhook_module.HookEventSchedule,
  119. payload: nil,
  120. yamlOn: "on: schedule",
  121. expected: true,
  122. },
  123. {
  124. desc: "push to tag matches workflow with paths condition (should skip paths check)",
  125. triggedEvent: webhook_module.HookEventPush,
  126. payload: &api.PushPayload{
  127. Ref: "refs/tags/v1.0.0",
  128. Before: "0000000",
  129. Commits: []*api.PayloadCommit{
  130. {
  131. ID: "abcdef123456",
  132. Added: []string{"src/main.go"},
  133. Message: "Release v1.0.0",
  134. },
  135. },
  136. },
  137. commit: nil,
  138. yamlOn: "on:\n push:\n paths:\n - src/**",
  139. expected: true,
  140. },
  141. }
  142. for _, tc := range testCases {
  143. t.Run(tc.desc, func(t *testing.T) {
  144. evts, err := GetEventsFromContent([]byte(tc.yamlOn))
  145. assert.NoError(t, err)
  146. assert.Len(t, evts, 1)
  147. assert.Equal(t, tc.expected, detectMatched(nil, tc.commit, tc.triggedEvent, tc.payload, evts[0]))
  148. })
  149. }
  150. }
  151. func TestMatchIssuesEvent(t *testing.T) {
  152. testCases := []struct {
  153. desc string
  154. payload *api.IssuePayload
  155. yamlOn string
  156. expected bool
  157. eventType string
  158. }{
  159. {
  160. desc: "Label deletion should trigger unlabeled event",
  161. payload: &api.IssuePayload{
  162. Action: api.HookIssueLabelUpdated,
  163. Issue: &api.Issue{
  164. Labels: []*api.Label{},
  165. },
  166. Changes: &api.ChangesPayload{
  167. RemovedLabels: []*api.Label{
  168. {ID: 123, Name: "deleted-label"},
  169. },
  170. },
  171. },
  172. yamlOn: "on:\n issues:\n types: [unlabeled]",
  173. expected: true,
  174. eventType: "unlabeled",
  175. },
  176. {
  177. desc: "Label deletion with existing labels should trigger unlabeled event",
  178. payload: &api.IssuePayload{
  179. Action: api.HookIssueLabelUpdated,
  180. Issue: &api.Issue{
  181. Labels: []*api.Label{
  182. {ID: 456, Name: "existing-label"},
  183. },
  184. },
  185. Changes: &api.ChangesPayload{
  186. AddedLabels: nil,
  187. RemovedLabels: []*api.Label{
  188. {ID: 123, Name: "deleted-label"},
  189. },
  190. },
  191. },
  192. yamlOn: "on:\n issues:\n types: [unlabeled]",
  193. expected: true,
  194. eventType: "unlabeled",
  195. },
  196. {
  197. desc: "Label addition should trigger labeled event",
  198. payload: &api.IssuePayload{
  199. Action: api.HookIssueLabelUpdated,
  200. Issue: &api.Issue{
  201. Labels: []*api.Label{
  202. {ID: 123, Name: "new-label"},
  203. },
  204. },
  205. Changes: &api.ChangesPayload{
  206. AddedLabels: []*api.Label{
  207. {ID: 123, Name: "new-label"},
  208. },
  209. RemovedLabels: []*api.Label{}, // Empty array, no labels removed
  210. },
  211. },
  212. yamlOn: "on:\n issues:\n types: [labeled]",
  213. expected: true,
  214. eventType: "labeled",
  215. },
  216. {
  217. desc: "Label clear should trigger unlabeled event",
  218. payload: &api.IssuePayload{
  219. Action: api.HookIssueLabelCleared,
  220. Issue: &api.Issue{
  221. Labels: []*api.Label{},
  222. },
  223. },
  224. yamlOn: "on:\n issues:\n types: [unlabeled]",
  225. expected: true,
  226. eventType: "unlabeled",
  227. },
  228. {
  229. desc: "Both adding and removing labels should trigger labeled event",
  230. payload: &api.IssuePayload{
  231. Action: api.HookIssueLabelUpdated,
  232. Issue: &api.Issue{
  233. Labels: []*api.Label{
  234. {ID: 789, Name: "new-label"},
  235. },
  236. },
  237. Changes: &api.ChangesPayload{
  238. AddedLabels: []*api.Label{
  239. {ID: 789, Name: "new-label"},
  240. },
  241. RemovedLabels: []*api.Label{
  242. {ID: 123, Name: "deleted-label"},
  243. },
  244. },
  245. },
  246. yamlOn: "on:\n issues:\n types: [labeled]",
  247. expected: true,
  248. eventType: "labeled",
  249. },
  250. {
  251. desc: "Both adding and removing labels should trigger unlabeled event",
  252. payload: &api.IssuePayload{
  253. Action: api.HookIssueLabelUpdated,
  254. Issue: &api.Issue{
  255. Labels: []*api.Label{
  256. {ID: 789, Name: "new-label"},
  257. },
  258. },
  259. Changes: &api.ChangesPayload{
  260. AddedLabels: []*api.Label{
  261. {ID: 789, Name: "new-label"},
  262. },
  263. RemovedLabels: []*api.Label{
  264. {ID: 123, Name: "deleted-label"},
  265. },
  266. },
  267. },
  268. yamlOn: "on:\n issues:\n types: [unlabeled]",
  269. expected: true,
  270. eventType: "unlabeled",
  271. },
  272. {
  273. desc: "Both adding and removing labels should trigger both events",
  274. payload: &api.IssuePayload{
  275. Action: api.HookIssueLabelUpdated,
  276. Issue: &api.Issue{
  277. Labels: []*api.Label{
  278. {ID: 789, Name: "new-label"},
  279. },
  280. },
  281. Changes: &api.ChangesPayload{
  282. AddedLabels: []*api.Label{
  283. {ID: 789, Name: "new-label"},
  284. },
  285. RemovedLabels: []*api.Label{
  286. {ID: 123, Name: "deleted-label"},
  287. },
  288. },
  289. },
  290. yamlOn: "on:\n issues:\n types: [labeled, unlabeled]",
  291. expected: true,
  292. eventType: "multiple",
  293. },
  294. }
  295. for _, tc := range testCases {
  296. t.Run(tc.desc, func(t *testing.T) {
  297. evts, err := GetEventsFromContent([]byte(tc.yamlOn))
  298. assert.NoError(t, err)
  299. assert.Len(t, evts, 1)
  300. // Test if the event matches as expected
  301. assert.Equal(t, tc.expected, matchIssuesEvent(tc.payload, evts[0]))
  302. // For extra validation, check that action mapping works correctly
  303. if tc.eventType == "multiple" {
  304. // Skip direct action mapping validation for multiple events case
  305. // as one action can map to multiple event types
  306. return
  307. }
  308. // Determine expected action for single event case
  309. var expectedAction string
  310. switch tc.payload.Action {
  311. case api.HookIssueLabelUpdated:
  312. if tc.eventType == "labeled" {
  313. expectedAction = "labeled"
  314. } else if tc.eventType == "unlabeled" {
  315. expectedAction = "unlabeled"
  316. }
  317. case api.HookIssueLabelCleared:
  318. expectedAction = "unlabeled"
  319. default:
  320. expectedAction = string(tc.payload.Action)
  321. }
  322. assert.Equal(t, expectedAction, tc.eventType, "Event type should match expected")
  323. })
  324. }
  325. }