gitea源码

repo_webhook_test.go 61KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "net/http/httptest"
  9. "net/url"
  10. "path"
  11. "strings"
  12. "testing"
  13. "time"
  14. auth_model "code.gitea.io/gitea/models/auth"
  15. "code.gitea.io/gitea/models/repo"
  16. "code.gitea.io/gitea/models/unittest"
  17. user_model "code.gitea.io/gitea/models/user"
  18. "code.gitea.io/gitea/models/webhook"
  19. "code.gitea.io/gitea/modules/commitstatus"
  20. "code.gitea.io/gitea/modules/git"
  21. "code.gitea.io/gitea/modules/gitrepo"
  22. "code.gitea.io/gitea/modules/json"
  23. "code.gitea.io/gitea/modules/setting"
  24. api "code.gitea.io/gitea/modules/structs"
  25. "code.gitea.io/gitea/modules/test"
  26. webhook_module "code.gitea.io/gitea/modules/webhook"
  27. "code.gitea.io/gitea/services/actions"
  28. "code.gitea.io/gitea/tests"
  29. runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
  30. "github.com/PuerkitoBio/goquery"
  31. "github.com/stretchr/testify/assert"
  32. "github.com/stretchr/testify/require"
  33. )
  34. func TestNewWebHookLink(t *testing.T) {
  35. defer tests.PrepareTestEnv(t)()
  36. session := loginUser(t, "user2")
  37. baseurl := "/user2/repo1/settings/hooks"
  38. tests := []string{
  39. // webhook list page
  40. baseurl,
  41. // new webhook page
  42. baseurl + "/gitea/new",
  43. // edit webhook page
  44. baseurl + "/1",
  45. }
  46. for _, url := range tests {
  47. resp := session.MakeRequest(t, NewRequest(t, "GET", url), http.StatusOK)
  48. htmlDoc := NewHTMLParser(t, resp.Body)
  49. menus := htmlDoc.doc.Find(".ui.top.attached.header .ui.dropdown .menu a")
  50. menus.Each(func(i int, menu *goquery.Selection) {
  51. url, exist := menu.Attr("href")
  52. assert.True(t, exist)
  53. assert.True(t, strings.HasPrefix(url, baseurl))
  54. })
  55. }
  56. }
  57. func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, repoName, url, event string, branchFilter ...string) {
  58. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
  59. var branchFilterString string
  60. if len(branchFilter) > 0 {
  61. branchFilterString = branchFilter[0]
  62. }
  63. req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+userName+"/"+repoName+"/hooks", api.CreateHookOption{
  64. Type: "gitea",
  65. Config: api.CreateHookOptionConfig{
  66. "content_type": "json",
  67. "url": url,
  68. },
  69. Events: []string{event},
  70. Active: true,
  71. BranchFilter: branchFilterString,
  72. }).AddTokenAuth(token)
  73. MakeRequest(t, req, http.StatusCreated)
  74. }
  75. func testCreateWebhookForRepo(t *testing.T, session *TestSession, webhookType, userName, repoName, url, eventKind string) {
  76. csrf := GetUserCSRFToken(t, session)
  77. req := NewRequestWithValues(t, "POST", "/"+userName+"/"+repoName+"/settings/hooks/"+webhookType+"/new", map[string]string{
  78. "_csrf": csrf,
  79. "payload_url": url,
  80. "events": eventKind,
  81. "active": "true",
  82. "content_type": fmt.Sprintf("%d", webhook.ContentTypeJSON),
  83. "http_method": "POST",
  84. })
  85. session.MakeRequest(t, req, http.StatusSeeOther)
  86. }
  87. func testAPICreateWebhookForOrg(t *testing.T, session *TestSession, userName, url, event string) {
  88. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
  89. req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/"+userName+"/hooks", api.CreateHookOption{
  90. Type: "gitea",
  91. Config: api.CreateHookOptionConfig{
  92. "content_type": "json",
  93. "url": url,
  94. },
  95. Events: []string{event},
  96. Active: true,
  97. }).AddTokenAuth(token)
  98. MakeRequest(t, req, http.StatusCreated)
  99. }
  100. type mockWebhookProvider struct {
  101. server *httptest.Server
  102. }
  103. func newMockWebhookProvider(callback func(r *http.Request), status int) *mockWebhookProvider {
  104. m := &mockWebhookProvider{}
  105. m.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  106. callback(r)
  107. w.WriteHeader(status)
  108. }))
  109. return m
  110. }
  111. func (m *mockWebhookProvider) URL() string {
  112. if m.server == nil {
  113. return ""
  114. }
  115. return m.server.URL
  116. }
  117. // Close closes the mock webhook http server
  118. func (m *mockWebhookProvider) Close() {
  119. if m.server != nil {
  120. m.server.Close()
  121. m.server = nil
  122. }
  123. }
  124. func Test_WebhookCreate(t *testing.T) {
  125. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  126. var payloads []api.CreatePayload
  127. var triggeredEvent string
  128. provider := newMockWebhookProvider(func(r *http.Request) {
  129. content, _ := io.ReadAll(r.Body)
  130. var payload api.CreatePayload
  131. err := json.Unmarshal(content, &payload)
  132. assert.NoError(t, err)
  133. payloads = append(payloads, payload)
  134. triggeredEvent = string(webhook_module.HookEventCreate)
  135. }, http.StatusOK)
  136. defer provider.Close()
  137. // 1. create a new webhook with special webhook for repo1
  138. session := loginUser(t, "user2")
  139. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "create")
  140. // 2. trigger the webhook
  141. testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
  142. // 3. validate the webhook is triggered
  143. assert.Len(t, payloads, 1)
  144. assert.Equal(t, string(webhook_module.HookEventCreate), triggeredEvent)
  145. assert.Equal(t, "repo1", payloads[0].Repo.Name)
  146. assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName)
  147. assert.Equal(t, "master2", payloads[0].Ref)
  148. assert.Equal(t, "branch", payloads[0].RefType)
  149. })
  150. }
  151. func Test_WebhookDelete(t *testing.T) {
  152. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  153. var payloads []api.DeletePayload
  154. var triggeredEvent string
  155. provider := newMockWebhookProvider(func(r *http.Request) {
  156. content, _ := io.ReadAll(r.Body)
  157. var payload api.DeletePayload
  158. err := json.Unmarshal(content, &payload)
  159. assert.NoError(t, err)
  160. payloads = append(payloads, payload)
  161. triggeredEvent = "delete"
  162. }, http.StatusOK)
  163. defer provider.Close()
  164. // 1. create a new webhook with special webhook for repo1
  165. session := loginUser(t, "user2")
  166. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "delete")
  167. // 2. trigger the webhook
  168. testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
  169. testAPIDeleteBranch(t, "master2", http.StatusNoContent)
  170. // 3. validate the webhook is triggered
  171. assert.Equal(t, "delete", triggeredEvent)
  172. assert.Len(t, payloads, 1)
  173. assert.Equal(t, "repo1", payloads[0].Repo.Name)
  174. assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName)
  175. assert.Equal(t, "master2", payloads[0].Ref)
  176. assert.Equal(t, "branch", payloads[0].RefType)
  177. })
  178. }
  179. func Test_WebhookFork(t *testing.T) {
  180. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  181. var payloads []api.ForkPayload
  182. var triggeredEvent string
  183. provider := newMockWebhookProvider(func(r *http.Request) {
  184. content, _ := io.ReadAll(r.Body)
  185. var payload api.ForkPayload
  186. err := json.Unmarshal(content, &payload)
  187. assert.NoError(t, err)
  188. payloads = append(payloads, payload)
  189. triggeredEvent = "fork"
  190. }, http.StatusOK)
  191. defer provider.Close()
  192. // 1. create a new webhook with special webhook for repo1
  193. session := loginUser(t, "user1")
  194. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "fork")
  195. // 2. trigger the webhook
  196. testRepoFork(t, session, "user2", "repo1", "user1", "repo1-fork", "master")
  197. // 3. validate the webhook is triggered
  198. assert.Equal(t, "fork", triggeredEvent)
  199. assert.Len(t, payloads, 1)
  200. assert.Equal(t, "repo1-fork", payloads[0].Repo.Name)
  201. assert.Equal(t, "user1/repo1-fork", payloads[0].Repo.FullName)
  202. assert.Equal(t, "repo1", payloads[0].Forkee.Name)
  203. assert.Equal(t, "user2/repo1", payloads[0].Forkee.FullName)
  204. })
  205. }
  206. func Test_WebhookIssueComment(t *testing.T) {
  207. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  208. var payloads []api.IssueCommentPayload
  209. var triggeredEvent string
  210. provider := newMockWebhookProvider(func(r *http.Request) {
  211. content, _ := io.ReadAll(r.Body)
  212. var payload api.IssueCommentPayload
  213. err := json.Unmarshal(content, &payload)
  214. assert.NoError(t, err)
  215. payloads = append(payloads, payload)
  216. triggeredEvent = "issue_comment"
  217. }, http.StatusOK)
  218. defer provider.Close()
  219. // 1. create a new webhook with special webhook for repo1
  220. session := loginUser(t, "user2")
  221. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_comment")
  222. t.Run("create comment", func(t *testing.T) {
  223. // 2. trigger the webhook
  224. issueURL := testNewIssue(t, session, "user2", "repo1", "Title2", "Description2")
  225. testIssueAddComment(t, session, issueURL, "issue title2 comment1", "")
  226. // 3. validate the webhook is triggered
  227. assert.Equal(t, "issue_comment", triggeredEvent)
  228. assert.Len(t, payloads, 1)
  229. assert.EqualValues(t, "created", payloads[0].Action)
  230. assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
  231. assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
  232. assert.Equal(t, "Title2", payloads[0].Issue.Title)
  233. assert.Equal(t, "Description2", payloads[0].Issue.Body)
  234. assert.Equal(t, "issue title2 comment1", payloads[0].Comment.Body)
  235. })
  236. t.Run("update comment", func(t *testing.T) {
  237. payloads = make([]api.IssueCommentPayload, 0, 2)
  238. triggeredEvent = ""
  239. // 2. trigger the webhook
  240. issueURL := testNewIssue(t, session, "user2", "repo1", "Title3", "Description3")
  241. commentID := testIssueAddComment(t, session, issueURL, "issue title3 comment1", "")
  242. modifiedContent := "issue title2 comment1 - modified"
  243. req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
  244. "_csrf": GetUserCSRFToken(t, session),
  245. "content": modifiedContent,
  246. })
  247. session.MakeRequest(t, req, http.StatusOK)
  248. // 3. validate the webhook is triggered
  249. assert.Equal(t, "issue_comment", triggeredEvent)
  250. assert.Len(t, payloads, 2)
  251. assert.EqualValues(t, "edited", payloads[1].Action)
  252. assert.Equal(t, "repo1", payloads[1].Issue.Repo.Name)
  253. assert.Equal(t, "user2/repo1", payloads[1].Issue.Repo.FullName)
  254. assert.Equal(t, "Title3", payloads[1].Issue.Title)
  255. assert.Equal(t, "Description3", payloads[1].Issue.Body)
  256. assert.Equal(t, modifiedContent, payloads[1].Comment.Body)
  257. })
  258. t.Run("Update comment with no content change", func(t *testing.T) {
  259. payloads = make([]api.IssueCommentPayload, 0, 2)
  260. triggeredEvent = ""
  261. commentContent := "issue title3 comment1"
  262. // 2. trigger the webhook
  263. issueURL := testNewIssue(t, session, "user2", "repo1", "Title3", "Description3")
  264. commentID := testIssueAddComment(t, session, issueURL, commentContent, "")
  265. payloads = make([]api.IssueCommentPayload, 0, 2)
  266. triggeredEvent = ""
  267. req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
  268. "_csrf": GetUserCSRFToken(t, session),
  269. "content": commentContent,
  270. })
  271. session.MakeRequest(t, req, http.StatusOK)
  272. // 3. validate the webhook is not triggered because no content change
  273. assert.Empty(t, triggeredEvent)
  274. assert.Empty(t, payloads)
  275. })
  276. })
  277. }
  278. func Test_WebhookRelease(t *testing.T) {
  279. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  280. var payloads []api.ReleasePayload
  281. var triggeredEvent string
  282. provider := newMockWebhookProvider(func(r *http.Request) {
  283. content, _ := io.ReadAll(r.Body)
  284. var payload api.ReleasePayload
  285. err := json.Unmarshal(content, &payload)
  286. assert.NoError(t, err)
  287. payloads = append(payloads, payload)
  288. triggeredEvent = "release"
  289. }, http.StatusOK)
  290. defer provider.Close()
  291. // 1. create a new webhook with special webhook for repo1
  292. session := loginUser(t, "user2")
  293. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "release")
  294. // 2. trigger the webhook
  295. createNewRelease(t, session, "/user2/repo1", "v0.0.99", "v0.0.99", false, false)
  296. // 3. validate the webhook is triggered
  297. assert.Equal(t, "release", triggeredEvent)
  298. assert.Len(t, payloads, 1)
  299. assert.Equal(t, "repo1", payloads[0].Repository.Name)
  300. assert.Equal(t, "user2/repo1", payloads[0].Repository.FullName)
  301. assert.Equal(t, "v0.0.99", payloads[0].Release.TagName)
  302. assert.False(t, payloads[0].Release.IsDraft)
  303. assert.False(t, payloads[0].Release.IsPrerelease)
  304. })
  305. }
  306. func Test_WebhookPush(t *testing.T) {
  307. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  308. var payloads []api.PushPayload
  309. var triggeredEvent string
  310. provider := newMockWebhookProvider(func(r *http.Request) {
  311. content, _ := io.ReadAll(r.Body)
  312. var payload api.PushPayload
  313. err := json.Unmarshal(content, &payload)
  314. assert.NoError(t, err)
  315. payloads = append(payloads, payload)
  316. triggeredEvent = "push"
  317. }, http.StatusOK)
  318. defer provider.Close()
  319. // 1. create a new webhook with special webhook for repo1
  320. session := loginUser(t, "user2")
  321. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push")
  322. // 2. trigger the webhook
  323. testCreateFile(t, session, "user2", "repo1", "master", "", "test_webhook_push.md", "# a test file for webhook push")
  324. // 3. validate the webhook is triggered
  325. assert.Equal(t, "push", triggeredEvent)
  326. assert.Len(t, payloads, 1)
  327. assert.Equal(t, "repo1", payloads[0].Repo.Name)
  328. assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName)
  329. assert.Len(t, payloads[0].Commits, 1)
  330. assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added)
  331. })
  332. }
  333. func Test_WebhookPushDevBranch(t *testing.T) {
  334. var payloads []api.PushPayload
  335. var triggeredEvent string
  336. provider := newMockWebhookProvider(func(r *http.Request) {
  337. content, _ := io.ReadAll(r.Body)
  338. var payload api.PushPayload
  339. err := json.Unmarshal(content, &payload)
  340. assert.NoError(t, err)
  341. payloads = append(payloads, payload)
  342. triggeredEvent = "push"
  343. }, http.StatusOK)
  344. defer provider.Close()
  345. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  346. // 1. create a new webhook with special webhook for repo1
  347. session := loginUser(t, "user2")
  348. // only for dev branch
  349. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push", "develop")
  350. // 2. this should not trigger the webhook
  351. testCreateFile(t, session, "user2", "repo1", "master", "", "test_webhook_push.md", "# a test file for webhook push")
  352. assert.Empty(t, triggeredEvent)
  353. assert.Empty(t, payloads)
  354. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  355. gitRepo, err := gitrepo.OpenRepository(t.Context(), repo1)
  356. assert.NoError(t, err)
  357. defer gitRepo.Close()
  358. beforeCommitID, err := gitRepo.GetBranchCommitID("develop")
  359. assert.NoError(t, err)
  360. // 3. trigger the webhook
  361. testCreateFile(t, session, "user2", "repo1", "develop", "", "test_webhook_push.md", "# a test file for webhook push")
  362. afterCommitID, err := gitRepo.GetBranchCommitID("develop")
  363. assert.NoError(t, err)
  364. // 4. validate the webhook is triggered
  365. assert.Equal(t, "push", triggeredEvent)
  366. assert.Len(t, payloads, 1)
  367. assert.Equal(t, "refs/heads/develop", payloads[0].Ref)
  368. assert.Equal(t, beforeCommitID, payloads[0].Before)
  369. assert.Equal(t, afterCommitID, payloads[0].After)
  370. assert.Equal(t, "repo1", payloads[0].Repo.Name)
  371. assert.Equal(t, "develop", payloads[0].Branch())
  372. assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName)
  373. assert.Len(t, payloads[0].Commits, 1)
  374. assert.Equal(t, afterCommitID, payloads[0].Commits[0].ID)
  375. assert.Equal(t, setting.AppURL+"user2/repo1/compare/"+beforeCommitID+"..."+afterCommitID, payloads[0].CompareURL)
  376. assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added)
  377. assert.Empty(t, payloads[0].Commits[0].Removed)
  378. })
  379. }
  380. func Test_WebhookPushToNewBranch(t *testing.T) {
  381. var payloads []api.PushPayload
  382. var triggeredEvent string
  383. provider := newMockWebhookProvider(func(r *http.Request) {
  384. content, _ := io.ReadAll(r.Body)
  385. var payload api.PushPayload
  386. err := json.Unmarshal(content, &payload)
  387. assert.NoError(t, err)
  388. payloads = append(payloads, payload)
  389. triggeredEvent = "push"
  390. }, http.StatusOK)
  391. defer provider.Close()
  392. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  393. // 1. create a new webhook with special webhook for repo1
  394. session := loginUser(t, "user2")
  395. // only for dev branch
  396. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push", "new_branch")
  397. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  398. gitRepo, err := gitrepo.OpenRepository(t.Context(), repo1)
  399. assert.NoError(t, err)
  400. defer gitRepo.Close()
  401. beforeCommitID, err := gitRepo.GetBranchCommitID("master")
  402. assert.NoError(t, err)
  403. // 2. trigger the webhook
  404. testCreateFile(t, session, "user2", "repo1", "master", "new_branch", "test_webhook_push.md", "# a new push from new branch")
  405. afterCommitID, err := gitRepo.GetBranchCommitID("new_branch")
  406. assert.NoError(t, err)
  407. emptyCommitID := git.Sha1ObjectFormat.EmptyObjectID().String()
  408. // 4. validate the webhook is triggered
  409. assert.Equal(t, "push", triggeredEvent)
  410. assert.Len(t, payloads, 1)
  411. assert.Equal(t, "refs/heads/new_branch", payloads[0].Ref)
  412. assert.Equal(t, emptyCommitID, payloads[0].Before)
  413. assert.Equal(t, afterCommitID, payloads[0].After)
  414. assert.Equal(t, "repo1", payloads[0].Repo.Name)
  415. assert.Equal(t, "new_branch", payloads[0].Branch())
  416. assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName)
  417. assert.Len(t, payloads[0].Commits, 1)
  418. assert.Equal(t, afterCommitID, payloads[0].Commits[0].ID)
  419. assert.Equal(t, setting.AppURL+"user2/repo1/compare/"+beforeCommitID+"..."+afterCommitID, payloads[0].CompareURL)
  420. assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added)
  421. assert.Empty(t, payloads[0].Commits[0].Removed)
  422. })
  423. }
  424. func Test_WebhookIssue(t *testing.T) {
  425. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  426. var payloads []api.IssuePayload
  427. var triggeredEvent string
  428. provider := newMockWebhookProvider(func(r *http.Request) {
  429. content, _ := io.ReadAll(r.Body)
  430. var payload api.IssuePayload
  431. err := json.Unmarshal(content, &payload)
  432. assert.NoError(t, err)
  433. payloads = append(payloads, payload)
  434. triggeredEvent = "issues"
  435. }, http.StatusOK)
  436. defer provider.Close()
  437. // 1. create a new webhook with special webhook for repo1
  438. session := loginUser(t, "user2")
  439. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issues")
  440. // 2. trigger the webhook
  441. testNewIssue(t, session, "user2", "repo1", "Title1", "Description1")
  442. // 3. validate the webhook is triggered
  443. assert.Equal(t, "issues", triggeredEvent)
  444. assert.Len(t, payloads, 1)
  445. assert.EqualValues(t, "opened", payloads[0].Action)
  446. assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
  447. assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
  448. assert.Equal(t, "Title1", payloads[0].Issue.Title)
  449. assert.Equal(t, "Description1", payloads[0].Issue.Body)
  450. assert.Positive(t, payloads[0].Issue.Created.Unix())
  451. assert.Positive(t, payloads[0].Issue.Updated.Unix())
  452. })
  453. }
  454. func Test_WebhookIssueDelete(t *testing.T) {
  455. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  456. var payloads []api.IssuePayload
  457. var triggeredEvent string
  458. provider := newMockWebhookProvider(func(r *http.Request) {
  459. content, _ := io.ReadAll(r.Body)
  460. var payload api.IssuePayload
  461. err := json.Unmarshal(content, &payload)
  462. assert.NoError(t, err)
  463. payloads = append(payloads, payload)
  464. triggeredEvent = "issue"
  465. }, http.StatusOK)
  466. defer provider.Close()
  467. // 1. create a new webhook with special webhook for repo1
  468. session := loginUser(t, "user2")
  469. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issues")
  470. issueURL := testNewIssue(t, session, "user2", "repo1", "Title1", "Description1")
  471. // 2. trigger the webhook
  472. testIssueDelete(t, session, issueURL)
  473. // 3. validate the webhook is triggered
  474. assert.Equal(t, "issue", triggeredEvent)
  475. require.Len(t, payloads, 2)
  476. assert.EqualValues(t, "deleted", payloads[1].Action)
  477. assert.Equal(t, "repo1", payloads[1].Issue.Repo.Name)
  478. assert.Equal(t, "user2/repo1", payloads[1].Issue.Repo.FullName)
  479. assert.Equal(t, "Title1", payloads[1].Issue.Title)
  480. assert.Equal(t, "Description1", payloads[1].Issue.Body)
  481. })
  482. }
  483. func Test_WebhookIssueAssign(t *testing.T) {
  484. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  485. var payloads []api.PullRequestPayload
  486. var triggeredEvent string
  487. provider := newMockWebhookProvider(func(r *http.Request) {
  488. content, _ := io.ReadAll(r.Body)
  489. var payload api.PullRequestPayload
  490. err := json.Unmarshal(content, &payload)
  491. assert.NoError(t, err)
  492. payloads = append(payloads, payload)
  493. triggeredEvent = "pull_request_assign"
  494. }, http.StatusOK)
  495. defer provider.Close()
  496. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  497. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  498. // 1. create a new webhook with special webhook for repo1
  499. session := loginUser(t, "user2")
  500. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request_assign")
  501. // 2. trigger the webhook, issue 2 is a pull request
  502. testIssueAssign(t, session, repo1.Link(), 2, user2.ID)
  503. // 3. validate the webhook is triggered
  504. assert.Equal(t, "pull_request_assign", triggeredEvent)
  505. assert.Len(t, payloads, 1)
  506. assert.EqualValues(t, "assigned", payloads[0].Action)
  507. assert.Equal(t, "repo1", payloads[0].PullRequest.Base.Repository.Name)
  508. assert.Equal(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName)
  509. assert.Equal(t, "issue2", payloads[0].PullRequest.Title)
  510. assert.Equal(t, "content for the second issue", payloads[0].PullRequest.Body)
  511. assert.Equal(t, user2.ID, payloads[0].PullRequest.Assignee.ID)
  512. })
  513. }
  514. func Test_WebhookIssueMilestone(t *testing.T) {
  515. var payloads []api.IssuePayload
  516. var triggeredEvent string
  517. provider := newMockWebhookProvider(func(r *http.Request) {
  518. content, _ := io.ReadAll(r.Body)
  519. var payload api.IssuePayload
  520. err := json.Unmarshal(content, &payload)
  521. assert.NoError(t, err)
  522. payloads = append(payloads, payload)
  523. triggeredEvent = "issues"
  524. }, http.StatusOK)
  525. defer provider.Close()
  526. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  527. // create a new webhook with special webhook for repo1
  528. session := loginUser(t, "user2")
  529. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  530. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_milestone")
  531. t.Run("assign a milestone", func(t *testing.T) {
  532. // trigger the webhook
  533. testIssueChangeMilestone(t, session, repo1.Link(), 1, 1)
  534. // validate the webhook is triggered
  535. assert.Equal(t, "issues", triggeredEvent)
  536. assert.Len(t, payloads, 1)
  537. assert.Equal(t, "milestoned", string(payloads[0].Action))
  538. assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
  539. assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
  540. assert.Equal(t, "issue1", payloads[0].Issue.Title)
  541. assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
  542. assert.EqualValues(t, 1, payloads[0].Issue.Milestone.ID)
  543. })
  544. t.Run("change a milestong", func(t *testing.T) {
  545. // trigger the webhook again
  546. triggeredEvent = ""
  547. payloads = make([]api.IssuePayload, 0, 1)
  548. // change milestone to 2
  549. testIssueChangeMilestone(t, session, repo1.Link(), 1, 2)
  550. // validate the webhook is triggered
  551. assert.Equal(t, "issues", triggeredEvent)
  552. assert.Len(t, payloads, 1)
  553. assert.Equal(t, "milestoned", string(payloads[0].Action))
  554. assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
  555. assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
  556. assert.Equal(t, "issue1", payloads[0].Issue.Title)
  557. assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
  558. assert.EqualValues(t, 2, payloads[0].Issue.Milestone.ID)
  559. })
  560. t.Run("remove a milestone", func(t *testing.T) {
  561. // trigger the webhook again
  562. triggeredEvent = ""
  563. payloads = make([]api.IssuePayload, 0, 1)
  564. // change milestone to 0
  565. testIssueChangeMilestone(t, session, repo1.Link(), 1, 0)
  566. // validate the webhook is triggered
  567. assert.Equal(t, "issues", triggeredEvent)
  568. assert.Len(t, payloads, 1)
  569. assert.Equal(t, "demilestoned", string(payloads[0].Action))
  570. assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
  571. assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
  572. assert.Equal(t, "issue1", payloads[0].Issue.Title)
  573. assert.Equal(t, "content for the first issue", payloads[0].Issue.Body)
  574. assert.Nil(t, payloads[0].Issue.Milestone)
  575. })
  576. })
  577. }
  578. func Test_WebhookPullRequest(t *testing.T) {
  579. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  580. var payloads []api.PullRequestPayload
  581. var triggeredEvent string
  582. provider := newMockWebhookProvider(func(r *http.Request) {
  583. content, _ := io.ReadAll(r.Body)
  584. var payload api.PullRequestPayload
  585. err := json.Unmarshal(content, &payload)
  586. assert.NoError(t, err)
  587. payloads = append(payloads, payload)
  588. triggeredEvent = "pull_request"
  589. }, http.StatusOK)
  590. defer provider.Close()
  591. // 1. create a new webhook with special webhook for repo1
  592. session := loginUser(t, "user2")
  593. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request")
  594. testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
  595. // 2. trigger the webhook
  596. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  597. testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request")
  598. // 3. validate the webhook is triggered
  599. assert.Equal(t, "pull_request", triggeredEvent)
  600. require.Len(t, payloads, 1)
  601. assert.Equal(t, "repo1", payloads[0].PullRequest.Base.Repository.Name)
  602. assert.Equal(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName)
  603. assert.Equal(t, "repo1", payloads[0].PullRequest.Head.Repository.Name)
  604. assert.Equal(t, "user2/repo1", payloads[0].PullRequest.Head.Repository.FullName)
  605. assert.Equal(t, 0, *payloads[0].PullRequest.Additions)
  606. assert.Equal(t, 0, *payloads[0].PullRequest.ChangedFiles)
  607. assert.Equal(t, 0, *payloads[0].PullRequest.Deletions)
  608. })
  609. }
  610. func Test_WebhookPullRequestDelete(t *testing.T) {
  611. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  612. var payloads []api.PullRequestPayload
  613. var triggeredEvent string
  614. provider := newMockWebhookProvider(func(r *http.Request) {
  615. content, _ := io.ReadAll(r.Body)
  616. var payload api.PullRequestPayload
  617. err := json.Unmarshal(content, &payload)
  618. assert.NoError(t, err)
  619. payloads = append(payloads, payload)
  620. triggeredEvent = "pull_request"
  621. }, http.StatusOK)
  622. defer provider.Close()
  623. // 1. create a new webhook with special webhook for repo1
  624. session := loginUser(t, "user2")
  625. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request")
  626. testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
  627. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  628. issueURL := testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request")
  629. // 2. trigger the webhook
  630. testIssueDelete(t, session, path.Join(repo1.Link(), "pulls", issueURL))
  631. // 3. validate the webhook is triggered
  632. assert.Equal(t, "pull_request", triggeredEvent)
  633. require.Len(t, payloads, 2)
  634. assert.EqualValues(t, "deleted", payloads[1].Action)
  635. assert.Equal(t, "repo1", payloads[1].PullRequest.Base.Repository.Name)
  636. assert.Equal(t, "user2/repo1", payloads[1].PullRequest.Base.Repository.FullName)
  637. assert.Equal(t, 0, *payloads[1].PullRequest.Additions)
  638. assert.Equal(t, 0, *payloads[1].PullRequest.ChangedFiles)
  639. assert.Equal(t, 0, *payloads[1].PullRequest.Deletions)
  640. })
  641. }
  642. func Test_WebhookPullRequestComment(t *testing.T) {
  643. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  644. var payloads []api.IssueCommentPayload
  645. var triggeredEvent string
  646. provider := newMockWebhookProvider(func(r *http.Request) {
  647. content, _ := io.ReadAll(r.Body)
  648. var payload api.IssueCommentPayload
  649. err := json.Unmarshal(content, &payload)
  650. assert.NoError(t, err)
  651. payloads = append(payloads, payload)
  652. triggeredEvent = "pull_request_comment"
  653. }, http.StatusOK)
  654. defer provider.Close()
  655. // 1. create a new webhook with special webhook for repo1
  656. session := loginUser(t, "user2")
  657. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request_comment")
  658. // 2. trigger the webhook
  659. testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
  660. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  661. prID := testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request")
  662. testIssueAddComment(t, session, "/user2/repo1/pulls/"+prID, "pull title2 comment1", "")
  663. // 3. validate the webhook is triggered
  664. assert.Equal(t, "pull_request_comment", triggeredEvent)
  665. assert.Len(t, payloads, 1)
  666. assert.EqualValues(t, "created", payloads[0].Action)
  667. assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name)
  668. assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
  669. assert.Equal(t, "first pull request", payloads[0].Issue.Title)
  670. assert.Empty(t, payloads[0].Issue.Body)
  671. assert.Equal(t, "pull title2 comment1", payloads[0].Comment.Body)
  672. })
  673. }
  674. func Test_WebhookWiki(t *testing.T) {
  675. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  676. var payloads []api.WikiPayload
  677. var triggeredEvent string
  678. provider := newMockWebhookProvider(func(r *http.Request) {
  679. content, _ := io.ReadAll(r.Body)
  680. var payload api.WikiPayload
  681. err := json.Unmarshal(content, &payload)
  682. assert.NoError(t, err)
  683. payloads = append(payloads, payload)
  684. triggeredEvent = "wiki"
  685. }, http.StatusOK)
  686. defer provider.Close()
  687. // 1. create a new webhook with special webhook for repo1
  688. session := loginUser(t, "user2")
  689. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "wiki")
  690. // 2. trigger the webhook
  691. testAPICreateWikiPage(t, session, "user2", "repo1", "Test Wiki Page", http.StatusCreated)
  692. // 3. validate the webhook is triggered
  693. assert.Equal(t, "wiki", triggeredEvent)
  694. assert.Len(t, payloads, 1)
  695. assert.EqualValues(t, "created", payloads[0].Action)
  696. assert.Equal(t, "repo1", payloads[0].Repository.Name)
  697. assert.Equal(t, "user2/repo1", payloads[0].Repository.FullName)
  698. assert.Equal(t, "Test-Wiki-Page", payloads[0].Page)
  699. })
  700. }
  701. func Test_WebhookRepository(t *testing.T) {
  702. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  703. var payloads []api.RepositoryPayload
  704. var triggeredEvent string
  705. provider := newMockWebhookProvider(func(r *http.Request) {
  706. content, _ := io.ReadAll(r.Body)
  707. var payload api.RepositoryPayload
  708. err := json.Unmarshal(content, &payload)
  709. assert.NoError(t, err)
  710. payloads = append(payloads, payload)
  711. triggeredEvent = "repository"
  712. }, http.StatusOK)
  713. defer provider.Close()
  714. // 1. create a new webhook with special webhook for repo1
  715. session := loginUser(t, "user1")
  716. testAPICreateWebhookForOrg(t, session, "org3", provider.URL(), "repository")
  717. // 2. trigger the webhook
  718. testAPIOrgCreateRepo(t, session, "org3", "repo_new", http.StatusCreated)
  719. // 3. validate the webhook is triggered
  720. assert.Equal(t, "repository", triggeredEvent)
  721. assert.Len(t, payloads, 1)
  722. assert.EqualValues(t, "created", payloads[0].Action)
  723. assert.Equal(t, "org3", payloads[0].Organization.UserName)
  724. assert.Equal(t, "repo_new", payloads[0].Repository.Name)
  725. assert.Equal(t, "org3/repo_new", payloads[0].Repository.FullName)
  726. })
  727. }
  728. func Test_WebhookPackage(t *testing.T) {
  729. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  730. var payloads []api.PackagePayload
  731. var triggeredEvent string
  732. provider := newMockWebhookProvider(func(r *http.Request) {
  733. content, _ := io.ReadAll(r.Body)
  734. var payload api.PackagePayload
  735. err := json.Unmarshal(content, &payload)
  736. assert.NoError(t, err)
  737. payloads = append(payloads, payload)
  738. triggeredEvent = "package"
  739. }, http.StatusOK)
  740. defer provider.Close()
  741. // 1. create a new webhook with special webhook for repo1
  742. session := loginUser(t, "user1")
  743. testAPICreateWebhookForOrg(t, session, "org3", provider.URL(), "package")
  744. // 2. trigger the webhook
  745. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
  746. url := fmt.Sprintf("/api/packages/%s/generic/%s/%s", "org3", "gitea", "v1.24.0")
  747. req := NewRequestWithBody(t, "PUT", url+"/gitea", strings.NewReader("This is a dummy file")).
  748. AddTokenAuth(token)
  749. MakeRequest(t, req, http.StatusCreated)
  750. // 3. validate the webhook is triggered
  751. assert.Equal(t, "package", triggeredEvent)
  752. assert.Len(t, payloads, 1)
  753. assert.EqualValues(t, "created", payloads[0].Action)
  754. assert.Equal(t, "gitea", payloads[0].Package.Name)
  755. assert.Equal(t, "generic", payloads[0].Package.Type)
  756. assert.Equal(t, "org3", payloads[0].Organization.UserName)
  757. assert.Equal(t, "v1.24.0", payloads[0].Package.Version)
  758. })
  759. }
  760. func Test_WebhookStatus(t *testing.T) {
  761. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  762. var payloads []api.CommitStatusPayload
  763. var triggeredEvent string
  764. provider := newMockWebhookProvider(func(r *http.Request) {
  765. assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status")
  766. assert.Contains(t, r.Header["X-Github-Hook-Installation-Target-Type"], "repository", "X-GitHub-Hook-Installation-Target-Type should contain repository")
  767. assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status")
  768. assert.Contains(t, r.Header["X-Gitea-Hook-Installation-Target-Type"], "repository", "X-Gitea-Hook-Installation-Target-Type should contain repository")
  769. assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status")
  770. content, _ := io.ReadAll(r.Body)
  771. var payload api.CommitStatusPayload
  772. err := json.Unmarshal(content, &payload)
  773. assert.NoError(t, err)
  774. payloads = append(payloads, payload)
  775. triggeredEvent = "status"
  776. }, http.StatusOK)
  777. defer provider.Close()
  778. // 1. create a new webhook with special webhook for repo1
  779. session := loginUser(t, "user2")
  780. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "status")
  781. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  782. gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1)
  783. assert.NoError(t, err)
  784. commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch)
  785. assert.NoError(t, err)
  786. // 2. trigger the webhook
  787. testCtx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeAll)
  788. // update a status for a commit via API
  789. doAPICreateCommitStatus(testCtx, commitID, api.CreateStatusOption{
  790. State: commitstatus.CommitStatusSuccess,
  791. TargetURL: "http://test.ci/",
  792. Description: "",
  793. Context: "testci",
  794. })(t)
  795. // 3. validate the webhook is triggered
  796. assert.Equal(t, "status", triggeredEvent)
  797. assert.Len(t, payloads, 1)
  798. assert.Equal(t, commitID, payloads[0].Commit.ID)
  799. assert.Equal(t, "repo1", payloads[0].Repo.Name)
  800. assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName)
  801. assert.Equal(t, "testci", payloads[0].Context)
  802. assert.Equal(t, commitID, payloads[0].SHA)
  803. })
  804. }
  805. func Test_WebhookStatus_NoWrongTrigger(t *testing.T) {
  806. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  807. var trigger string
  808. provider := newMockWebhookProvider(func(r *http.Request) {
  809. assert.NotContains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should not contain status")
  810. assert.NotContains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should not contain status")
  811. assert.NotContains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should not contain status")
  812. trigger = "push"
  813. }, http.StatusOK)
  814. defer provider.Close()
  815. // 1. create a new webhook with special webhook for repo1
  816. session := loginUser(t, "user2")
  817. // create a push_only webhook from web UI
  818. testCreateWebhookForRepo(t, session, "gitea", "user2", "repo1", provider.URL(), "push_only")
  819. // 2. trigger the webhook with a push action
  820. testCreateFile(t, session, "user2", "repo1", "master", "", "test_webhook_push.md", "# a test file for webhook push")
  821. // 3. validate the webhook is triggered with right event
  822. assert.Equal(t, "push", trigger)
  823. })
  824. }
  825. func Test_WebhookWorkflowJob(t *testing.T) {
  826. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  827. var payloads []api.WorkflowJobPayload
  828. var triggeredEvent string
  829. provider := newMockWebhookProvider(func(r *http.Request) {
  830. assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_job", "X-GitHub-Event-Type should contain workflow_job")
  831. assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_job", "X-Gitea-Event-Type should contain workflow_job")
  832. assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_job", "X-Gogs-Event-Type should contain workflow_job")
  833. content, _ := io.ReadAll(r.Body)
  834. var payload api.WorkflowJobPayload
  835. err := json.Unmarshal(content, &payload)
  836. assert.NoError(t, err)
  837. payloads = append(payloads, payload)
  838. triggeredEvent = "workflow_job"
  839. }, http.StatusOK)
  840. defer provider.Close()
  841. // 1. create a new webhook with special webhook for repo1
  842. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  843. session := loginUser(t, "user2")
  844. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  845. testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "workflow_job")
  846. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  847. gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1)
  848. assert.NoError(t, err)
  849. runner := newMockRunner()
  850. runner.registerAsRepoRunner(t, "user2", "repo1", "mock-runner", []string{"ubuntu-latest"}, false)
  851. // 2. trigger the webhooks
  852. // add workflow file to the repo
  853. // init the workflow
  854. wfTreePath := ".gitea/workflows/push.yml"
  855. wfFileContent := `name: Push
  856. on: push
  857. jobs:
  858. wf1-job:
  859. runs-on: ubuntu-latest
  860. steps:
  861. - run: echo 'test the webhook'
  862. wf2-job:
  863. runs-on: ubuntu-latest
  864. needs: wf1-job
  865. steps:
  866. - run: echo 'cmd 1'
  867. - run: echo 'cmd 2'
  868. `
  869. opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent)
  870. createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts)
  871. commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch)
  872. assert.NoError(t, err)
  873. // 3. validate the webhook is triggered
  874. assert.Equal(t, "workflow_job", triggeredEvent)
  875. assert.Len(t, payloads, 2)
  876. assert.Equal(t, "queued", payloads[0].Action)
  877. assert.Equal(t, "queued", payloads[0].WorkflowJob.Status)
  878. assert.Equal(t, []string{"ubuntu-latest"}, payloads[0].WorkflowJob.Labels)
  879. assert.Equal(t, commitID, payloads[0].WorkflowJob.HeadSha)
  880. assert.Equal(t, "repo1", payloads[0].Repo.Name)
  881. assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName)
  882. assert.Equal(t, "waiting", payloads[1].Action)
  883. assert.Equal(t, "waiting", payloads[1].WorkflowJob.Status)
  884. assert.Equal(t, commitID, payloads[1].WorkflowJob.HeadSha)
  885. assert.Equal(t, "repo1", payloads[1].Repo.Name)
  886. assert.Equal(t, "user2/repo1", payloads[1].Repo.FullName)
  887. // 4. Execute a single Job
  888. task := runner.fetchTask(t)
  889. outcome := &mockTaskOutcome{
  890. result: runnerv1.Result_RESULT_SUCCESS,
  891. }
  892. runner.execTask(t, task, outcome)
  893. // 5. validate the webhook is triggered
  894. assert.Equal(t, "workflow_job", triggeredEvent)
  895. assert.Len(t, payloads, 5)
  896. assert.Equal(t, "in_progress", payloads[2].Action)
  897. assert.Equal(t, "in_progress", payloads[2].WorkflowJob.Status)
  898. assert.Equal(t, "mock-runner", payloads[2].WorkflowJob.RunnerName)
  899. assert.Equal(t, commitID, payloads[2].WorkflowJob.HeadSha)
  900. assert.Equal(t, "repo1", payloads[2].Repo.Name)
  901. assert.Equal(t, "user2/repo1", payloads[2].Repo.FullName)
  902. assert.Equal(t, "completed", payloads[3].Action)
  903. assert.Equal(t, "completed", payloads[3].WorkflowJob.Status)
  904. assert.Equal(t, "mock-runner", payloads[3].WorkflowJob.RunnerName)
  905. assert.Equal(t, "success", payloads[3].WorkflowJob.Conclusion)
  906. assert.Equal(t, commitID, payloads[3].WorkflowJob.HeadSha)
  907. assert.Equal(t, "repo1", payloads[3].Repo.Name)
  908. assert.Equal(t, "user2/repo1", payloads[3].Repo.FullName)
  909. assert.Contains(t, payloads[3].WorkflowJob.URL, fmt.Sprintf("/actions/jobs/%d", payloads[3].WorkflowJob.ID))
  910. assert.Contains(t, payloads[3].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 0))
  911. assert.Len(t, payloads[3].WorkflowJob.Steps, 1)
  912. assert.Equal(t, "queued", payloads[4].Action)
  913. assert.Equal(t, "queued", payloads[4].WorkflowJob.Status)
  914. assert.Equal(t, []string{"ubuntu-latest"}, payloads[4].WorkflowJob.Labels)
  915. assert.Equal(t, commitID, payloads[4].WorkflowJob.HeadSha)
  916. assert.Equal(t, "repo1", payloads[4].Repo.Name)
  917. assert.Equal(t, "user2/repo1", payloads[4].Repo.FullName)
  918. // 6. Execute a single Job
  919. task = runner.fetchTask(t)
  920. outcome = &mockTaskOutcome{
  921. result: runnerv1.Result_RESULT_FAILURE,
  922. }
  923. runner.execTask(t, task, outcome)
  924. // 7. validate the webhook is triggered
  925. assert.Equal(t, "workflow_job", triggeredEvent)
  926. assert.Len(t, payloads, 7)
  927. assert.Equal(t, "in_progress", payloads[5].Action)
  928. assert.Equal(t, "in_progress", payloads[5].WorkflowJob.Status)
  929. assert.Equal(t, "mock-runner", payloads[5].WorkflowJob.RunnerName)
  930. assert.Equal(t, commitID, payloads[5].WorkflowJob.HeadSha)
  931. assert.Equal(t, "repo1", payloads[5].Repo.Name)
  932. assert.Equal(t, "user2/repo1", payloads[5].Repo.FullName)
  933. assert.Equal(t, "completed", payloads[6].Action)
  934. assert.Equal(t, "completed", payloads[6].WorkflowJob.Status)
  935. assert.Equal(t, "failure", payloads[6].WorkflowJob.Conclusion)
  936. assert.Equal(t, "mock-runner", payloads[6].WorkflowJob.RunnerName)
  937. assert.Equal(t, commitID, payloads[6].WorkflowJob.HeadSha)
  938. assert.Equal(t, "repo1", payloads[6].Repo.Name)
  939. assert.Equal(t, "user2/repo1", payloads[6].Repo.FullName)
  940. assert.Contains(t, payloads[6].WorkflowJob.URL, fmt.Sprintf("/actions/jobs/%d", payloads[6].WorkflowJob.ID))
  941. assert.Contains(t, payloads[6].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 1))
  942. assert.Len(t, payloads[6].WorkflowJob.Steps, 2)
  943. })
  944. }
  945. type workflowRunWebhook struct {
  946. URL string
  947. payloads []api.WorkflowRunPayload
  948. triggeredEvent string
  949. }
  950. func Test_WebhookWorkflowRun(t *testing.T) {
  951. testCases := []struct {
  952. name string
  953. testFunc func(t *testing.T, webhookData *workflowRunWebhook)
  954. }{
  955. {
  956. name: "WorkflowRun",
  957. testFunc: testWebhookWorkflowRun,
  958. },
  959. {
  960. name: "WorkflowRunDepthLimit",
  961. testFunc: testWebhookWorkflowRunDepthLimit,
  962. },
  963. {
  964. name: "WorkflowRunEvents",
  965. testFunc: testWorkflowRunEvents,
  966. },
  967. {
  968. name: "WorkflowRunEventsOnRerun",
  969. testFunc: testWorkflowRunEventsOnRerun,
  970. },
  971. {
  972. name: "WorkflowRunEventsOnCancellingAbandonedRunAllJobsAbandoned",
  973. testFunc: func(t *testing.T, webhookData *workflowRunWebhook) {
  974. testWorkflowRunEventsOnCancellingAbandonedRun(t, webhookData, true)
  975. },
  976. },
  977. {
  978. name: "WorkflowRunEventsOnCancellingAbandonedRunPartiallyAbandoned",
  979. testFunc: func(t *testing.T, webhookData *workflowRunWebhook) {
  980. testWorkflowRunEventsOnCancellingAbandonedRun(t, webhookData, false)
  981. },
  982. },
  983. }
  984. for _, obj := range testCases {
  985. t.Run(obj.name, func(t *testing.T) {
  986. onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
  987. webhookData := &workflowRunWebhook{}
  988. provider := newMockWebhookProvider(func(r *http.Request) {
  989. assert.Contains(t, r.Header["X-Github-Event-Type"], "workflow_run", "X-GitHub-Event-Type should contain workflow_run")
  990. assert.Contains(t, r.Header["X-Gitea-Event-Type"], "workflow_run", "X-Gitea-Event-Type should contain workflow_run")
  991. assert.Contains(t, r.Header["X-Gogs-Event-Type"], "workflow_run", "X-Gogs-Event-Type should contain workflow_run")
  992. content, _ := io.ReadAll(r.Body)
  993. var payload api.WorkflowRunPayload
  994. err := json.Unmarshal(content, &payload)
  995. assert.NoError(t, err)
  996. webhookData.payloads = append(webhookData.payloads, payload)
  997. webhookData.triggeredEvent = "workflow_run"
  998. }, http.StatusOK)
  999. defer provider.Close()
  1000. webhookData.URL = provider.URL()
  1001. webhookData.payloads = nil
  1002. webhookData.triggeredEvent = ""
  1003. obj.testFunc(t, webhookData)
  1004. })
  1005. })
  1006. }
  1007. }
  1008. func testWorkflowRunEvents(t *testing.T, webhookData *workflowRunWebhook) {
  1009. // 1. create a new webhook with special webhook for repo1
  1010. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  1011. session := loginUser(t, "user2")
  1012. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  1013. testAPICreateWebhookForRepo(t, session, "user2", "repo1", webhookData.URL, "workflow_run")
  1014. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  1015. gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1)
  1016. assert.NoError(t, err)
  1017. // 2.2 trigger the webhooks
  1018. // add workflow file to the repo
  1019. // init the workflow
  1020. wfTreePath := ".gitea/workflows/push.yml"
  1021. wfFileContent := `on:
  1022. push:
  1023. workflow_dispatch:
  1024. jobs:
  1025. test:
  1026. runs-on: ubuntu-latest
  1027. steps:
  1028. - run: exit 0
  1029. test2:
  1030. needs: [test]
  1031. runs-on: ubuntu-latest
  1032. steps:
  1033. - run: exit 0
  1034. test3:
  1035. needs: [test, test2]
  1036. runs-on: ubuntu-latest
  1037. steps:
  1038. - run: exit 0
  1039. test4:
  1040. needs: [test, test2, test3]
  1041. runs-on: ubuntu-latest
  1042. steps:
  1043. - run: exit 0
  1044. test5:
  1045. needs: [test, test2, test4]
  1046. runs-on: ubuntu-latest
  1047. steps:
  1048. - run: exit 0
  1049. test6:
  1050. strategy:
  1051. matrix:
  1052. os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04]
  1053. needs: [test, test2, test3]
  1054. runs-on: ${{ matrix.os }}
  1055. steps:
  1056. - run: exit 0
  1057. test7:
  1058. needs: test6
  1059. runs-on: ubuntu-latest
  1060. steps:
  1061. - run: exit 0
  1062. test8:
  1063. runs-on: ubuntu-latest
  1064. steps:
  1065. - run: exit 0
  1066. test9:
  1067. strategy:
  1068. matrix:
  1069. os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, ubuntu-25.04, windows-2022, windows-2025, macos-13, macos-14, macos-15]
  1070. runs-on: ${{ matrix.os }}
  1071. steps:
  1072. - run: exit 0
  1073. test10:
  1074. runs-on: ubuntu-latest
  1075. steps:
  1076. - run: exit 0`
  1077. opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent)
  1078. createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts)
  1079. commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch)
  1080. assert.NoError(t, err)
  1081. // 3. validate the webhook is triggered
  1082. assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
  1083. assert.Len(t, webhookData.payloads, 1)
  1084. assert.Equal(t, "requested", webhookData.payloads[0].Action)
  1085. assert.Equal(t, "queued", webhookData.payloads[0].WorkflowRun.Status)
  1086. assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[0].WorkflowRun.HeadBranch)
  1087. assert.Equal(t, commitID, webhookData.payloads[0].WorkflowRun.HeadSha)
  1088. assert.Equal(t, "repo1", webhookData.payloads[0].Repo.Name)
  1089. assert.Equal(t, "user2/repo1", webhookData.payloads[0].Repo.FullName)
  1090. // Call cancel ui api
  1091. // Only a web UI API exists for cancelling workflow runs, so use the UI endpoint.
  1092. cancelURL := fmt.Sprintf("/user2/repo1/actions/runs/%d/cancel", webhookData.payloads[0].WorkflowRun.RunNumber)
  1093. req := NewRequestWithValues(t, "POST", cancelURL, map[string]string{
  1094. "_csrf": GetUserCSRFToken(t, session),
  1095. })
  1096. session.MakeRequest(t, req, http.StatusOK)
  1097. assert.Len(t, webhookData.payloads, 2)
  1098. // 4. Validate the second webhook payload
  1099. assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
  1100. assert.Equal(t, "completed", webhookData.payloads[1].Action)
  1101. assert.Equal(t, "push", webhookData.payloads[1].WorkflowRun.Event)
  1102. assert.Equal(t, "completed", webhookData.payloads[1].WorkflowRun.Status)
  1103. assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[1].WorkflowRun.HeadBranch)
  1104. assert.Equal(t, commitID, webhookData.payloads[1].WorkflowRun.HeadSha)
  1105. assert.Equal(t, "repo1", webhookData.payloads[1].Repo.Name)
  1106. assert.Equal(t, "user2/repo1", webhookData.payloads[1].Repo.FullName)
  1107. }
  1108. func testWorkflowRunEventsOnRerun(t *testing.T, webhookData *workflowRunWebhook) {
  1109. // 1. create a new webhook with special webhook for repo1
  1110. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  1111. session := loginUser(t, "user2")
  1112. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  1113. runners := make([]*mockRunner, 2)
  1114. for i := range runners {
  1115. runners[i] = newMockRunner()
  1116. runners[i].registerAsRepoRunner(t, "user2", "repo1", fmt.Sprintf("mock-runner-%d", i), []string{"ubuntu-latest"}, false)
  1117. }
  1118. testAPICreateWebhookForRepo(t, session, "user2", "repo1", webhookData.URL, "workflow_run")
  1119. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  1120. gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1)
  1121. assert.NoError(t, err)
  1122. // 2.2 trigger the webhooks
  1123. // add workflow file to the repo
  1124. // init the workflow
  1125. wfTreePath := ".gitea/workflows/push.yml"
  1126. wfFileContent := `on:
  1127. push:
  1128. workflow_dispatch:
  1129. jobs:
  1130. test:
  1131. runs-on: ubuntu-latest
  1132. steps:
  1133. - run: exit 0
  1134. test2:
  1135. needs: [test]
  1136. runs-on: ubuntu-latest
  1137. steps:
  1138. - run: exit 0
  1139. test3:
  1140. needs: [test, test2]
  1141. runs-on: ubuntu-latest
  1142. steps:
  1143. - run: exit 0
  1144. test4:
  1145. needs: [test, test2, test3]
  1146. runs-on: ubuntu-latest
  1147. steps:
  1148. - run: exit 0
  1149. test5:
  1150. needs: [test, test2, test4]
  1151. runs-on: ubuntu-latest
  1152. steps:
  1153. - run: exit 0
  1154. test6:
  1155. strategy:
  1156. matrix:
  1157. os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04]
  1158. needs: [test, test2, test3]
  1159. runs-on: ${{ matrix.os }}
  1160. steps:
  1161. - run: exit 0
  1162. test7:
  1163. needs: test6
  1164. runs-on: ubuntu-latest
  1165. steps:
  1166. - run: exit 0
  1167. test8:
  1168. runs-on: ubuntu-latest
  1169. steps:
  1170. - run: exit 0
  1171. test9:
  1172. strategy:
  1173. matrix:
  1174. os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, ubuntu-25.04, windows-2022, windows-2025, macos-13, macos-14, macos-15]
  1175. runs-on: ${{ matrix.os }}
  1176. steps:
  1177. - run: exit 0
  1178. test10:
  1179. runs-on: ubuntu-latest
  1180. steps:
  1181. - run: exit 0`
  1182. opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent)
  1183. createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts)
  1184. commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch)
  1185. assert.NoError(t, err)
  1186. // 3. validate the webhook is triggered
  1187. assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
  1188. assert.Len(t, webhookData.payloads, 1)
  1189. assert.Equal(t, "requested", webhookData.payloads[0].Action)
  1190. assert.Equal(t, "queued", webhookData.payloads[0].WorkflowRun.Status)
  1191. assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[0].WorkflowRun.HeadBranch)
  1192. assert.Equal(t, commitID, webhookData.payloads[0].WorkflowRun.HeadSha)
  1193. assert.Equal(t, "repo1", webhookData.payloads[0].Repo.Name)
  1194. assert.Equal(t, "user2/repo1", webhookData.payloads[0].Repo.FullName)
  1195. for _, runner := range runners {
  1196. task := runner.fetchTask(t)
  1197. runner.execTask(t, task, &mockTaskOutcome{
  1198. result: runnerv1.Result_RESULT_SUCCESS,
  1199. })
  1200. }
  1201. // Call cancel ui api
  1202. // Only a web UI API exists for cancelling workflow runs, so use the UI endpoint.
  1203. cancelURL := fmt.Sprintf("/user2/repo1/actions/runs/%d/cancel", webhookData.payloads[0].WorkflowRun.RunNumber)
  1204. req := NewRequestWithValues(t, "POST", cancelURL, map[string]string{
  1205. "_csrf": GetUserCSRFToken(t, session),
  1206. })
  1207. session.MakeRequest(t, req, http.StatusOK)
  1208. assert.Len(t, webhookData.payloads, 2)
  1209. // 4. Validate the second webhook payload
  1210. assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
  1211. assert.Equal(t, "completed", webhookData.payloads[1].Action)
  1212. assert.Equal(t, "push", webhookData.payloads[1].WorkflowRun.Event)
  1213. assert.Equal(t, "completed", webhookData.payloads[1].WorkflowRun.Status)
  1214. assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[1].WorkflowRun.HeadBranch)
  1215. assert.Equal(t, commitID, webhookData.payloads[1].WorkflowRun.HeadSha)
  1216. assert.Equal(t, "repo1", webhookData.payloads[1].Repo.Name)
  1217. assert.Equal(t, "user2/repo1", webhookData.payloads[1].Repo.FullName)
  1218. // Call rerun ui api
  1219. // Only a web UI API exists for cancelling workflow runs, so use the UI endpoint.
  1220. rerunURL := fmt.Sprintf("/user2/repo1/actions/runs/%d/rerun", webhookData.payloads[0].WorkflowRun.RunNumber)
  1221. req = NewRequestWithValues(t, "POST", rerunURL, map[string]string{
  1222. "_csrf": GetUserCSRFToken(t, session),
  1223. })
  1224. session.MakeRequest(t, req, http.StatusOK)
  1225. assert.Len(t, webhookData.payloads, 3)
  1226. }
  1227. func testWorkflowRunEventsOnCancellingAbandonedRun(t *testing.T, webhookData *workflowRunWebhook, allJobsAbandoned bool) {
  1228. defer test.MockVariableValue(&setting.Actions.AbandonedJobTimeout, 0*time.Nanosecond)()
  1229. // 1. create a new webhook with special webhook for repo1
  1230. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  1231. session := loginUser(t, "user2")
  1232. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  1233. repoName := "test-workflow-run-cancelling-abandoned-run"
  1234. testRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: createActionsTestRepo(t, token, repoName, false).ID})
  1235. runners := make([]*mockRunner, 2)
  1236. for i := range runners {
  1237. runners[i] = newMockRunner()
  1238. runners[i].registerAsRepoRunner(t, "user2", repoName,
  1239. fmt.Sprintf("mock-runner-%d", i), []string{"ubuntu-latest"}, false)
  1240. }
  1241. testAPICreateWebhookForRepo(t, session, "user2", repoName, webhookData.URL, "workflow_run")
  1242. ctx := t.Context()
  1243. gitRepo, err := gitrepo.OpenRepository(ctx, testRepo)
  1244. assert.NoError(t, err)
  1245. // 2.2 trigger the webhooks
  1246. // add workflow file to the repo
  1247. // init the workflow
  1248. wfilename := "push.yml"
  1249. wfTreePath := ".gitea/workflows/" + wfilename
  1250. wfFileContent := `on:
  1251. push:
  1252. workflow_dispatch:
  1253. jobs:
  1254. test:
  1255. runs-on: ubuntu-latest
  1256. steps:
  1257. - run: exit 0
  1258. test2:
  1259. needs: [test]
  1260. runs-on: ubuntu-latest
  1261. steps:
  1262. - run: exit 0
  1263. test3:
  1264. needs: [test, test2]
  1265. runs-on: ubuntu-latest
  1266. steps:
  1267. - run: exit 0
  1268. test4:
  1269. needs: [test, test2, test3]
  1270. runs-on: ubuntu-latest
  1271. steps:
  1272. - run: exit 0
  1273. test5:
  1274. needs: [test, test2, test4]
  1275. runs-on: ubuntu-latest
  1276. steps:
  1277. - run: exit 0
  1278. test6:
  1279. strategy:
  1280. matrix:
  1281. os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04]
  1282. needs: [test, test2, test3]
  1283. runs-on: ${{ matrix.os }}
  1284. steps:
  1285. - run: exit 0
  1286. test7:
  1287. needs: test6
  1288. runs-on: ubuntu-latest
  1289. steps:
  1290. - run: exit 0
  1291. test8:
  1292. runs-on: ubuntu-latest
  1293. steps:
  1294. - run: exit 0
  1295. test9:
  1296. strategy:
  1297. matrix:
  1298. os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, ubuntu-25.04, windows-2022, windows-2025, macos-13, macos-14, macos-15]
  1299. runs-on: ${{ matrix.os }}
  1300. steps:
  1301. - run: exit 0
  1302. test10:
  1303. runs-on: ubuntu-latest
  1304. steps:
  1305. - run: exit 0`
  1306. opts := getWorkflowCreateFileOptions(user2, testRepo.DefaultBranch, "create "+wfTreePath, wfFileContent)
  1307. createWorkflowFile(t, token, "user2", repoName, wfTreePath, opts)
  1308. commitID, err := gitRepo.GetBranchCommitID(testRepo.DefaultBranch)
  1309. assert.NoError(t, err)
  1310. // 3. validate the webhook is triggered
  1311. assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
  1312. assert.Len(t, webhookData.payloads, 1)
  1313. assert.Equal(t, "requested", webhookData.payloads[0].Action)
  1314. assert.Equal(t, "queued", webhookData.payloads[0].WorkflowRun.Status)
  1315. assert.Equal(t, testRepo.DefaultBranch, webhookData.payloads[0].WorkflowRun.HeadBranch)
  1316. assert.Equal(t, commitID, webhookData.payloads[0].WorkflowRun.HeadSha)
  1317. assert.Equal(t, repoName, webhookData.payloads[0].Repo.Name)
  1318. assert.Equal(t, "user2/"+repoName, webhookData.payloads[0].Repo.FullName)
  1319. if !allJobsAbandoned {
  1320. for _, runner := range runners {
  1321. task := runner.fetchTask(t)
  1322. runner.execTask(t, task, &mockTaskOutcome{
  1323. result: runnerv1.Result_RESULT_SUCCESS,
  1324. })
  1325. }
  1326. }
  1327. // Add this sleep to ensure the func can find the tasks by timestamp.
  1328. time.Sleep(time.Second)
  1329. err = actions.CancelAbandonedJobs(ctx)
  1330. assert.NoError(t, err)
  1331. assert.Len(t, webhookData.payloads, 2)
  1332. assert.Equal(t, "completed", webhookData.payloads[1].Action)
  1333. assert.Equal(t, "completed", webhookData.payloads[1].WorkflowRun.Status)
  1334. assert.Equal(t, testRepo.DefaultBranch, webhookData.payloads[1].WorkflowRun.HeadBranch)
  1335. assert.Equal(t, commitID, webhookData.payloads[1].WorkflowRun.HeadSha)
  1336. assert.Equal(t, repoName, webhookData.payloads[1].Repo.Name)
  1337. assert.Equal(t, "user2/"+repoName, webhookData.payloads[1].Repo.FullName)
  1338. }
  1339. func testWebhookWorkflowRun(t *testing.T, webhookData *workflowRunWebhook) {
  1340. // 1. create a new webhook with special webhook for repo1
  1341. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  1342. session := loginUser(t, "user2")
  1343. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  1344. testAPICreateWebhookForRepo(t, session, "user2", "repo1", webhookData.URL, "workflow_run")
  1345. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  1346. gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1)
  1347. assert.NoError(t, err)
  1348. runner := newMockRunner()
  1349. runner.registerAsRepoRunner(t, "user2", "repo1", "mock-runner", []string{"ubuntu-latest"}, false)
  1350. // 2.1 add workflow_run workflow file to the repo
  1351. opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+"dispatch.yml", `
  1352. on:
  1353. workflow_run:
  1354. workflows: ["Push"]
  1355. types:
  1356. - completed
  1357. jobs:
  1358. dispatch:
  1359. runs-on: ubuntu-latest
  1360. steps:
  1361. - run: echo 'test the webhook'
  1362. `)
  1363. createWorkflowFile(t, token, "user2", "repo1", ".gitea/workflows/dispatch.yml", opts)
  1364. // 2.2 trigger the webhooks
  1365. // add workflow file to the repo
  1366. // init the workflow
  1367. wfTreePath := ".gitea/workflows/push.yml"
  1368. wfFileContent := `name: Push
  1369. on: push
  1370. jobs:
  1371. wf1-job:
  1372. runs-on: ubuntu-latest
  1373. steps:
  1374. - run: echo 'test the webhook'
  1375. wf2-job:
  1376. runs-on: ubuntu-latest
  1377. needs: wf1-job
  1378. steps:
  1379. - run: echo 'cmd 1'
  1380. - run: echo 'cmd 2'
  1381. `
  1382. opts = getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent)
  1383. createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts)
  1384. commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch)
  1385. assert.NoError(t, err)
  1386. // 3. validate the webhook is triggered
  1387. assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
  1388. assert.Len(t, webhookData.payloads, 1)
  1389. assert.Equal(t, "requested", webhookData.payloads[0].Action)
  1390. assert.Equal(t, "queued", webhookData.payloads[0].WorkflowRun.Status)
  1391. assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[0].WorkflowRun.HeadBranch)
  1392. assert.Equal(t, commitID, webhookData.payloads[0].WorkflowRun.HeadSha)
  1393. assert.Equal(t, "repo1", webhookData.payloads[0].Repo.Name)
  1394. assert.Equal(t, "user2/repo1", webhookData.payloads[0].Repo.FullName)
  1395. // 4. Execute two Jobs
  1396. task := runner.fetchTask(t)
  1397. outcome := &mockTaskOutcome{
  1398. result: runnerv1.Result_RESULT_SUCCESS,
  1399. }
  1400. runner.execTask(t, task, outcome)
  1401. task = runner.fetchTask(t)
  1402. outcome = &mockTaskOutcome{
  1403. result: runnerv1.Result_RESULT_FAILURE,
  1404. }
  1405. runner.execTask(t, task, outcome)
  1406. // 7. validate the webhook is triggered
  1407. assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
  1408. assert.Len(t, webhookData.payloads, 3)
  1409. assert.Equal(t, "completed", webhookData.payloads[1].Action)
  1410. assert.Equal(t, "push", webhookData.payloads[1].WorkflowRun.Event)
  1411. // 3. validate the webhook is triggered
  1412. assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
  1413. assert.Len(t, webhookData.payloads, 3)
  1414. assert.Equal(t, "requested", webhookData.payloads[2].Action)
  1415. assert.Equal(t, "queued", webhookData.payloads[2].WorkflowRun.Status)
  1416. assert.Equal(t, "workflow_run", webhookData.payloads[2].WorkflowRun.Event)
  1417. assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[2].WorkflowRun.HeadBranch)
  1418. assert.Equal(t, commitID, webhookData.payloads[2].WorkflowRun.HeadSha)
  1419. assert.Equal(t, "repo1", webhookData.payloads[2].Repo.Name)
  1420. assert.Equal(t, "user2/repo1", webhookData.payloads[2].Repo.FullName)
  1421. }
  1422. func testWebhookWorkflowRunDepthLimit(t *testing.T, webhookData *workflowRunWebhook) {
  1423. // 1. create a new webhook with special webhook for repo1
  1424. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  1425. session := loginUser(t, "user2")
  1426. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  1427. testAPICreateWebhookForRepo(t, session, "user2", "repo1", webhookData.URL, "workflow_run")
  1428. repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
  1429. gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1)
  1430. assert.NoError(t, err)
  1431. // 2. trigger the webhooks
  1432. // add workflow file to the repo
  1433. // init the workflow
  1434. wfTreePath := ".gitea/workflows/push.yml"
  1435. wfFileContent := `name: Endless Loop
  1436. on:
  1437. push:
  1438. workflow_run:
  1439. types:
  1440. - requested
  1441. jobs:
  1442. dispatch:
  1443. runs-on: ubuntu-latest
  1444. steps:
  1445. - run: echo 'test the webhook'
  1446. `
  1447. opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent)
  1448. createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts)
  1449. commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch)
  1450. assert.NoError(t, err)
  1451. // 3. validate the webhook is triggered
  1452. assert.Equal(t, "workflow_run", webhookData.triggeredEvent)
  1453. // 1x push + 5x workflow_run requested chain
  1454. assert.Len(t, webhookData.payloads, 6)
  1455. for i := range 6 {
  1456. assert.Equal(t, "requested", webhookData.payloads[i].Action)
  1457. assert.Equal(t, "queued", webhookData.payloads[i].WorkflowRun.Status)
  1458. assert.Equal(t, repo1.DefaultBranch, webhookData.payloads[i].WorkflowRun.HeadBranch)
  1459. assert.Equal(t, commitID, webhookData.payloads[i].WorkflowRun.HeadSha)
  1460. if i == 0 {
  1461. assert.Equal(t, "push", webhookData.payloads[i].WorkflowRun.Event)
  1462. } else {
  1463. assert.Equal(t, "workflow_run", webhookData.payloads[i].WorkflowRun.Event)
  1464. }
  1465. assert.Equal(t, "repo1", webhookData.payloads[i].Repo.Name)
  1466. assert.Equal(t, "user2/repo1", webhookData.payloads[i].Repo.FullName)
  1467. }
  1468. }