From daf581fa892320f5d495b4073d6812b0ad8ddfc8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 31 Mar 2026 01:28:45 +0800 Subject: [PATCH] Add tests for pull request's content_version in API (#37044) Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.6) --- modules/structs/issue.go | 6 ++- modules/structs/pull.go | 6 ++- templates/swagger/v1_json.tmpl | 4 ++ tests/integration/api_issue_test.go | 3 +- tests/integration/api_pull_test.go | 62 +++++++++++++++++++++++++++-- 5 files changed, 72 insertions(+), 9 deletions(-) diff --git a/modules/structs/issue.go b/modules/structs/issue.go index a34e4b0693..fd29727a43 100644 --- a/modules/structs/issue.go +++ b/modules/structs/issue.go @@ -80,7 +80,8 @@ type Issue struct { PullRequest *PullRequestMeta `json:"pull_request"` Repo *RepositoryMeta `json:"repository"` - PinOrder int `json:"pin_order"` + PinOrder int `json:"pin_order"` + // The version of the issue content for optimistic locking ContentVersion int `json:"content_version"` } @@ -115,7 +116,8 @@ type EditIssueOption struct { // swagger:strfmt date-time Deadline *time.Time `json:"due_date"` RemoveDeadline *bool `json:"unset_due_date"` - ContentVersion *int `json:"content_version"` + // The current version of the issue content to detect conflicts during editing + ContentVersion *int `json:"content_version"` } // EditDeadlineOption options for creating a deadline diff --git a/modules/structs/pull.go b/modules/structs/pull.go index ad320e2b82..cd2ffbe719 100644 --- a/modules/structs/pull.go +++ b/modules/structs/pull.go @@ -90,7 +90,8 @@ type PullRequest struct { Closed *time.Time `json:"closed_at"` // The pin order for the pull request - PinOrder int `json:"pin_order"` + PinOrder int `json:"pin_order"` + // The version of the pull request content for optimistic locking ContentVersion int `json:"content_version"` } @@ -169,7 +170,8 @@ type EditPullRequestOption struct { RemoveDeadline *bool `json:"unset_due_date"` // Whether to allow maintainer edits AllowMaintainerEdit *bool `json:"allow_maintainer_edit"` - ContentVersion *int `json:"content_version"` + // The current version of the pull request content to detect conflicts during editing + ContentVersion *int `json:"content_version"` } // ChangedFile store information about files affected by the pull request diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 5ae0f197df..282843e337 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -24768,6 +24768,7 @@ "x-go-name": "Body" }, "content_version": { + "description": "The current version of the issue content to detect conflicts during editing", "type": "integer", "format": "int64", "x-go-name": "ContentVersion" @@ -24945,6 +24946,7 @@ "x-go-name": "Body" }, "content_version": { + "description": "The current version of the pull request content to detect conflicts during editing", "type": "integer", "format": "int64", "x-go-name": "ContentVersion" @@ -26235,6 +26237,7 @@ "x-go-name": "Comments" }, "content_version": { + "description": "The version of the issue content for optimistic locking", "type": "integer", "format": "int64", "x-go-name": "ContentVersion" @@ -27742,6 +27745,7 @@ "x-go-name": "Comments" }, "content_version": { + "description": "The version of the pull request content for optimistic locking", "type": "integer", "format": "int64", "x-go-name": "ContentVersion" diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index c3e96059de..8dc9e31cfa 100644 --- a/tests/integration/api_issue_test.go +++ b/tests/integration/api_issue_test.go @@ -472,8 +472,7 @@ func testAPIIssueContentVersion(t *testing.T) { req := NewRequest(t, "GET", urlStr).AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - var before api.Issue - DecodeJSON(t, resp, &before) + before := DecodeJSON(t, resp, &api.Issue{}) req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{ Body: new("updated body with correct version"), ContentVersion: new(before.ContentVersion), diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go index fcd3554c52..39b54296e0 100644 --- a/tests/integration/api_pull_test.go +++ b/tests/integration/api_pull_test.go @@ -457,9 +457,8 @@ func TestAPIEditPull(t *testing.T) { Base: "master", Title: title, }).AddTokenAuth(token) - apiPull := new(api.PullRequest) resp := MakeRequest(t, req, http.StatusCreated) - DecodeJSON(t, resp, apiPull) + apiPull := DecodeJSON(t, resp, &api.PullRequest{}) assert.Equal(t, "master", apiPull.Base.Name) newTitle := "edit a this pr" @@ -470,8 +469,9 @@ func TestAPIEditPull(t *testing.T) { Body: &newBody, }).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusCreated) - DecodeJSON(t, resp, apiPull) + apiPull = DecodeJSON(t, resp, &api.PullRequest{}) assert.Equal(t, "feature/1", apiPull.Base.Name) + // check comment history pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID}) err := pull.LoadIssue(t.Context()) @@ -483,6 +483,62 @@ func TestAPIEditPull(t *testing.T) { Base: "not-exist", }).AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) + + t.Run("PullContentVersion", func(t *testing.T) { + testAPIPullContentVersion(t, pull.ID) + }) +} + +func testAPIPullContentVersion(t *testing.T, pullID int64) { + pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pullID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pull.BaseRepoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner.Name, repo.Name, pull.Index) + + t.Run("ResponseIncludesContentVersion", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", urlStr).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + apiPull := DecodeJSON(t, resp, &api.PullRequest{}) + assert.GreaterOrEqual(t, apiPull.ContentVersion, 0) + }) + + t.Run("EditWithCorrectVersion", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", urlStr).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + before := DecodeJSON(t, resp, &api.PullRequest{}) + req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditPullRequestOption{ + Body: new("updated body with correct version"), + ContentVersion: new(before.ContentVersion), + }).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusCreated) + after := DecodeJSON(t, resp, &api.PullRequest{}) + assert.Equal(t, "updated body with correct version", after.Body) + assert.Greater(t, after.ContentVersion, before.ContentVersion) + }) + + t.Run("EditWithWrongVersion", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditPullRequestOption{ + Body: new("should fail"), + ContentVersion: new(99999), + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusConflict) + }) + + t.Run("EditWithoutVersion", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditPullRequestOption{ + Body: new("edit without version succeeds"), + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + }) } func doAPIGetPullFiles(ctx APITestContext, pr *api.PullRequest, callback func(*testing.T, []*api.ChangedFile)) func(*testing.T) {