@@ -5,12 +5,14 @@ package integration
55
66import (
77 "fmt"
8+ "net/http"
89 "net/url"
910 "strings"
1011 "testing"
1112 "time"
1213
1314 actions_model "code.gitea.io/gitea/models/actions"
15+ auth_model "code.gitea.io/gitea/models/auth"
1416 "code.gitea.io/gitea/models/db"
1517 git_model "code.gitea.io/gitea/models/git"
1618 issues_model "code.gitea.io/gitea/models/issues"
@@ -22,9 +24,11 @@ import (
2224 "code.gitea.io/gitea/modules/git"
2325 "code.gitea.io/gitea/modules/gitrepo"
2426 "code.gitea.io/gitea/modules/setting"
27+ api "code.gitea.io/gitea/modules/structs"
2528 "code.gitea.io/gitea/modules/test"
2629 webhook_module "code.gitea.io/gitea/modules/webhook"
2730 actions_service "code.gitea.io/gitea/services/actions"
31+ issue_service "code.gitea.io/gitea/services/issue"
2832 pull_service "code.gitea.io/gitea/services/pull"
2933 release_service "code.gitea.io/gitea/services/release"
3034 repo_service "code.gitea.io/gitea/services/repository"
@@ -35,6 +39,238 @@ import (
3539 "github.com/stretchr/testify/require"
3640)
3741
42+ func TestPullRequestCommitStatus (t * testing.T ) {
43+ onGiteaRun (t , func (t * testing.T , u * url.URL ) {
44+ user2 := unittest .AssertExistsAndLoadBean (t , & user_model.User {ID : 2 }) // owner of the base repo
45+ session := loginUser (t , "user2" )
46+ token := getTokenForLoggedInUser (t , session , auth_model .AccessTokenScopeWriteIssue )
47+
48+ // prepare the repository
49+ files := make ([]* files_service.ChangeRepoFile , 0 , 10 )
50+ for _ , onType := range []string {
51+ "opened" ,
52+ "synchronize" ,
53+ "labeled" ,
54+ "unlabeled" ,
55+ "assigned" ,
56+ "unassigned" ,
57+ "milestoned" ,
58+ "demilestoned" ,
59+ "closed" ,
60+ "reopened" ,
61+ } {
62+ files = append (files , & files_service.ChangeRepoFile {
63+ Operation : "create" ,
64+ TreePath : fmt .Sprintf (".forgejo/workflows/%s.yml" , onType ),
65+ ContentReader : strings .NewReader (fmt .Sprintf (`name: %[1]s
66+ on:
67+ pull_request:
68+ types:
69+ - %[1]s
70+ jobs:
71+ %[1]s:
72+ runs-on: docker
73+ steps:
74+ - run: true
75+ ` , onType )),
76+ })
77+ }
78+ baseRepo , _ , f := tests .CreateDeclarativeRepo (t , user2 , "repo-pull-request" ,
79+ []unit_model.Type {unit_model .TypeActions }, nil , files )
80+ defer f ()
81+ baseGitRepo , err := gitrepo .OpenRepository (db .DefaultContext , baseRepo )
82+ require .NoError (t , err )
83+ defer func () {
84+ baseGitRepo .Close ()
85+ }()
86+
87+ // prepare the pull request
88+ testEditFileToNewBranch (t , session , "user2" , "repo-pull-request" , "main" , "wip-something" , "README.md" , "Hello, world 1" )
89+ testPullCreate (t , session , "user2" , "repo-pull-request" , true , "main" , "wip-something" , "Commit status PR" )
90+ pr := unittest .AssertExistsAndLoadBean (t , & issues_model.PullRequest {BaseRepoID : baseRepo .ID })
91+ require .NoError (t , pr .LoadIssue (db .DefaultContext ))
92+
93+ // prepare the assignees
94+ issueURL := fmt .Sprintf ("/api/v1/repos/%s/%s/issues/%s" , "user2" , "repo-pull-request" , fmt .Sprintf ("%d" , pr .Issue .Index ))
95+
96+ // prepare the labels
97+ labelStr := "/api/v1/repos/user2/repo-pull-request/labels"
98+ req := NewRequestWithJSON (t , "POST" , labelStr , & api.CreateLabelOption {
99+ Name : "mylabel" ,
100+ Color : "abcdef" ,
101+ Description : "description mylabel" ,
102+ }).AddTokenAuth (token )
103+ resp := MakeRequest (t , req , http .StatusCreated )
104+ label := new (api.Label )
105+ DecodeJSON (t , resp , & label )
106+ labelURL := fmt .Sprintf ("%s/labels" , issueURL )
107+
108+ // prepare the milestone
109+ milestoneStr := "/api/v1/repos/user2/repo-pull-request/milestones"
110+ req = NewRequestWithJSON (t , "POST" , milestoneStr , & api.CreateMilestoneOption {
111+ Title : "mymilestone" ,
112+ State : "open" ,
113+ }).AddTokenAuth (token )
114+ resp = MakeRequest (t , req , http .StatusCreated )
115+ milestone := new (api.Milestone )
116+ DecodeJSON (t , resp , & milestone )
117+
118+ // check that one of the status associated with the commit sha matches both
119+ // context & state
120+ checkCommitStatus := func (sha , context string , state api.CommitStatusState ) bool {
121+ commitStatuses , _ , err := git_model .GetLatestCommitStatus (db .DefaultContext , pr .BaseRepoID , sha , db .ListOptionsAll )
122+ require .NoError (t , err )
123+ for _ , commitStatus := range commitStatuses {
124+ if state == commitStatus .State && context == commitStatus .Context {
125+ return true
126+ }
127+ }
128+ return false
129+ }
130+
131+ count := 0
132+ for _ , testCase := range []struct {
133+ onType string
134+ jobID string
135+ doSomething func ()
136+ action api.HookIssueAction
137+ hasLabel bool
138+ }{
139+ {
140+ onType : "opened" ,
141+ doSomething : func () {},
142+ action : api .HookIssueOpened ,
143+ },
144+ {
145+ onType : "synchronize" ,
146+ doSomething : func () {
147+ testEditFile (t , session , "user2" , "repo-pull-request" , "wip-something" , "README.md" , "Hello, world 2" )
148+ },
149+ action : api .HookIssueSynchronized ,
150+ },
151+ {
152+ onType : "labeled" ,
153+ doSomething : func () {
154+ req := NewRequestWithJSON (t , "POST" , labelURL , & api.IssueLabelsOption {
155+ Labels : []any {label .ID },
156+ }).AddTokenAuth (token )
157+ MakeRequest (t , req , http .StatusOK )
158+ },
159+ action : api .HookIssueLabelUpdated ,
160+ hasLabel : true ,
161+ },
162+ {
163+ onType : "unlabeled" ,
164+ doSomething : func () {
165+ req := NewRequestWithJSON (t , "PUT" , labelURL , & api.IssueLabelsOption {
166+ Labels : []any {},
167+ }).AddTokenAuth (token )
168+ MakeRequest (t , req , http .StatusOK )
169+ },
170+ action : api .HookIssueLabelCleared ,
171+ hasLabel : true ,
172+ },
173+ {
174+ onType : "assigned" ,
175+ doSomething : func () {
176+ req := NewRequestWithJSON (t , "PATCH" , issueURL , & api.EditIssueOption {
177+ Assignees : []string {"user2" },
178+ }).AddTokenAuth (token )
179+ MakeRequest (t , req , http .StatusCreated )
180+ },
181+ action : api .HookIssueAssigned ,
182+ },
183+ {
184+ onType : "unassigned" ,
185+ doSomething : func () {
186+ req := NewRequestWithJSON (t , "PATCH" , issueURL , & api.EditIssueOption {
187+ Assignees : []string {},
188+ }).AddTokenAuth (token )
189+ MakeRequest (t , req , http .StatusCreated )
190+ },
191+ action : api .HookIssueUnassigned ,
192+ },
193+ {
194+ onType : "milestoned" ,
195+ doSomething : func () {
196+ req := NewRequestWithJSON (t , "PATCH" , issueURL , & api.EditIssueOption {
197+ Milestone : & milestone .ID ,
198+ }).AddTokenAuth (token )
199+ MakeRequest (t , req , http .StatusCreated )
200+ },
201+ action : api .HookIssueMilestoned ,
202+ },
203+ {
204+ onType : "demilestoned" ,
205+ doSomething : func () {
206+ var zero int64
207+ req := NewRequestWithJSON (t , "PATCH" , issueURL , & api.EditIssueOption {
208+ Milestone : & zero ,
209+ }).AddTokenAuth (token )
210+ MakeRequest (t , req , http .StatusCreated )
211+ },
212+ action : api .HookIssueDemilestoned ,
213+ },
214+ {
215+ onType : "closed" ,
216+ doSomething : func () {
217+ sha , err := baseGitRepo .GetRefCommitID (pr .GetGitRefName ())
218+ require .NoError (t , err )
219+ err = issue_service .ChangeStatus (db .DefaultContext , pr .Issue , user2 , sha , true )
220+ require .NoError (t , err )
221+ },
222+ action : api .HookIssueClosed ,
223+ },
224+ {
225+ onType : "reopened" ,
226+ doSomething : func () {
227+ sha , err := baseGitRepo .GetRefCommitID (pr .GetGitRefName ())
228+ require .NoError (t , err )
229+ err = issue_service .ChangeStatus (db .DefaultContext , pr .Issue , user2 , sha , false )
230+ require .NoError (t , err )
231+ },
232+ action : api .HookIssueReOpened ,
233+ },
234+ } {
235+ t .Run (testCase .onType , func (t * testing.T ) {
236+ // trigger the onType event
237+ testCase .doSomething ()
238+ count ++
239+ context := fmt .Sprintf ("%[1]s / %[1]s (pull_request)" , testCase .onType )
240+
241+ // wait for a new ActionRun to be created
242+ assert .Eventually (t , func () bool {
243+ return count == unittest .GetCount (t , & actions_model.ActionRun {RepoID : baseRepo .ID })
244+ }, 30 * time .Second , 1 * time .Second )
245+
246+ // verify the expected ActionRun was created
247+ sha , err := baseGitRepo .GetRefCommitID (pr .GetGitRefName ())
248+ require .NoError (t , err )
249+ actionRun := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRun {RepoID : baseRepo .ID , WorkflowID : fmt .Sprintf ("%s.yml" , testCase .onType )})
250+
251+ assert .Equal (t , sha , actionRun .CommitSHA )
252+ assert .Equal (t , actions_module .GithubEventPullRequest , actionRun .TriggerEvent )
253+ event , err := actionRun .GetPullRequestEventPayload ()
254+ if testCase .hasLabel {
255+ assert .NotNil (t , event .Label )
256+ }
257+ require .NoError (t , err )
258+ assert .Equal (t , testCase .action , event .Action )
259+
260+ // verify the expected ActionRunJob was created and is StatusWaiting
261+ job := unittest .AssertExistsAndLoadBean (t , & actions_model.ActionRunJob {JobID : testCase .onType , CommitSHA : sha })
262+ assert .Equal (t , actions_model .StatusWaiting , job .Status )
263+
264+ // verify the commit status changes to CommitStatusSuccess when the job changes to StatusSuccess
265+ assert .True (t , checkCommitStatus (sha , context , api .CommitStatusPending ))
266+ job .Status = actions_model .StatusSuccess
267+ actions_service .CreateCommitStatus (db .DefaultContext , job )
268+ assert .True (t , checkCommitStatus (sha , context , api .CommitStatusSuccess ))
269+ })
270+ }
271+ })
272+ }
273+
38274func TestPullRequestTargetEvent (t * testing.T ) {
39275 onGiteaRun (t , func (t * testing.T , u * url.URL ) {
40276 user2 := unittest .AssertExistsAndLoadBean (t , & user_model.User {ID : 2 }) // owner of the base repo
0 commit comments