diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index a6d8ff58e7..17bd0a246e 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -69,6 +69,7 @@ package v1 import ( "fmt" "net/http" + "slices" "strings" actions_model "forgejo.org/models/actions" @@ -468,6 +469,12 @@ func reqAdmin() func(ctx *context.APIContext) { // reqRepoWriter user should have a permission to write to a repo, or be a site admin func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { + if !slices.ContainsFunc(unitTypes, func(unitType unit.Type) bool { + return ctx.Repo.Repository.UnitEnabled(ctx, unitType) + }) { + ctx.NotFound() + return + } if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo") return @@ -487,6 +494,10 @@ func reqRepoBranchWriter(ctx *context.APIContext) { // reqRepoReader user should have specific read permission or be a repo admin or a site admin func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { + if !ctx.Repo.Repository.UnitEnabled(ctx, unitType) { + ctx.NotFound() + return + } if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin") return @@ -744,6 +755,26 @@ func mustEnableIssuesOrPulls(ctx *context.APIContext) { } } +func mustEnableLocalIssuesIfIsIssue(ctx *context.APIContext) { + if ctx.Repo.Repository.UnitEnabled(ctx, unit.TypeIssues) { + return + } + + issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if issues_model.IsErrIssueNotExist(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err) + } + return + } + if !issue.IsPull { + ctx.NotFound() + return + } +} + func mustEnableWiki(ctx *context.APIContext) { if !(ctx.Repo.CanRead(unit.TypeWiki)) { ctx.NotFound() @@ -1426,7 +1457,7 @@ func Routes() *web.Route { m.Group("/comments", func() { m.Combo("").Get(repo.ListIssueComments). Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment) - m.Combo("/{id}", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated). + m.Combo("/{id}", reqToken(), commentAssignment(":id")).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated). Delete(repo.DeleteIssueCommentDeprecated) }) m.Get("/timeline", repo.ListIssueCommentsAndTimeline) @@ -1483,7 +1514,7 @@ func Routes() *web.Route { Delete(reqToken(), reqAdmin(), repo.UnpinIssue) m.Patch("/{position}", reqToken(), reqAdmin(), repo.MoveIssuePin) }) - }) + }, mustEnableLocalIssuesIfIsIssue) }, mustEnableIssuesOrPulls) m.Group("/labels", func() { m.Combo("").Get(repo.ListLabels). diff --git a/tests/integration/api_comment_attachment_test.go b/tests/integration/api_comment_attachment_test.go index 16eb3c9b12..4bad3ca67e 100644 --- a/tests/integration/api_comment_attachment_test.go +++ b/tests/integration/api_comment_attachment_test.go @@ -6,7 +6,6 @@ package integration import ( "bytes" "fmt" - "io" "mime/multipart" "net/http" "testing" @@ -110,17 +109,11 @@ func TestAPICreateCommentAttachment(t *testing.T) { body := &bytes.Buffer{} // Setup multi-part - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", filename) - require.NoError(t, err) - _, err = io.Copy(part, &buff) - require.NoError(t, err) - err = writer.Close() - require.NoError(t, err) + contentType := tests.WriteImageBody(t, buff, filename, body) req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body). AddTokenAuth(token). - SetHeader("Content-Type", writer.FormDataContentType()) + SetHeader("Content-Type", contentType) resp := session.MakeRequest(t, req, http.StatusCreated) apiAttachment := new(api.Attachment) @@ -150,16 +143,10 @@ func TestAPICreateCommentAttachmentAutoDate(t *testing.T) { defer tests.PrintCurrentTest(t)() // Setup multi-part - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", filename) - require.NoError(t, err) - _, err = io.Copy(part, &buff) - require.NoError(t, err) - err = writer.Close() - require.NoError(t, err) + contentType := tests.WriteImageBody(t, buff, filename, body) req := NewRequestWithBody(t, "POST", urlStr, body).AddTokenAuth(token) - req.Header.Add("Content-Type", writer.FormDataContentType()) + req.Header.Add("Content-Type", contentType) resp := session.MakeRequest(t, req, http.StatusCreated) apiAttachment := new(api.Attachment) DecodeJSON(t, resp, &apiAttachment) @@ -181,16 +168,10 @@ func TestAPICreateCommentAttachmentAutoDate(t *testing.T) { urlStr += fmt.Sprintf("?updated_at=%s", updatedAt.UTC().Format(time.RFC3339)) // Setup multi-part - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", filename) - require.NoError(t, err) - _, err = io.Copy(part, &buff) - require.NoError(t, err) - err = writer.Close() - require.NoError(t, err) + contentType := tests.WriteImageBody(t, buff, filename, body) req := NewRequestWithBody(t, "POST", urlStr, body).AddTokenAuth(token) - req.Header.Add("Content-Type", writer.FormDataContentType()) + req.Header.Add("Content-Type", contentType) resp := session.MakeRequest(t, req, http.StatusCreated) apiAttachment := new(api.Attachment) DecodeJSON(t, resp, &apiAttachment) diff --git a/tests/integration/api_issue_attachment_test.go b/tests/integration/api_issue_attachment_test.go index ef7e6183f9..8f676e5017 100644 --- a/tests/integration/api_issue_attachment_test.go +++ b/tests/integration/api_issue_attachment_test.go @@ -6,7 +6,6 @@ package integration import ( "bytes" "fmt" - "io" "mime/multipart" "net/http" "testing" @@ -79,17 +78,11 @@ func TestAPICreateIssueAttachment(t *testing.T) { body := &bytes.Buffer{} // Setup multi-part - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", filename) - require.NoError(t, err) - _, err = io.Copy(part, &buff) - require.NoError(t, err) - err = writer.Close() - require.NoError(t, err) + contentType := tests.WriteImageBody(t, buff, filename, body) req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body). AddTokenAuth(token) - req.Header.Add("Content-Type", writer.FormDataContentType()) + req.Header.Add("Content-Type", contentType) resp := session.MakeRequest(t, req, http.StatusCreated) apiAttachment := new(api.Attachment) @@ -118,16 +111,10 @@ func TestAPICreateIssueAttachmentAutoDate(t *testing.T) { defer tests.PrintCurrentTest(t)() // Setup multi-part - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", filename) - require.NoError(t, err) - _, err = io.Copy(part, &buff) - require.NoError(t, err) - err = writer.Close() - require.NoError(t, err) + contentType := tests.WriteImageBody(t, buff, filename, body) req := NewRequestWithBody(t, "POST", urlStr, body).AddTokenAuth(token) - req.Header.Add("Content-Type", writer.FormDataContentType()) + req.Header.Add("Content-Type", contentType) resp := session.MakeRequest(t, req, http.StatusCreated) apiAttachment := new(api.Attachment) @@ -150,16 +137,10 @@ func TestAPICreateIssueAttachmentAutoDate(t *testing.T) { urlStr += fmt.Sprintf("?updated_at=%s", updatedAt.UTC().Format(time.RFC3339)) // Setup multi-part - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", filename) - require.NoError(t, err) - _, err = io.Copy(part, &buff) - require.NoError(t, err) - err = writer.Close() - require.NoError(t, err) + contentType := tests.WriteImageBody(t, buff, filename, body) req := NewRequestWithBody(t, "POST", urlStr, body).AddTokenAuth(token) - req.Header.Add("Content-Type", writer.FormDataContentType()) + req.Header.Add("Content-Type", contentType) resp := session.MakeRequest(t, req, http.StatusCreated) apiAttachment := new(api.Attachment) diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index 764ddd38d2..e8ea447463 100644 --- a/tests/integration/api_issue_test.go +++ b/tests/integration/api_issue_test.go @@ -4,6 +4,7 @@ package integration import ( + "bytes" "fmt" "net/http" "net/url" @@ -16,14 +17,18 @@ import ( "forgejo.org/models/db" issues_model "forgejo.org/models/issues" repo_model "forgejo.org/models/repo" + "forgejo.org/models/unit" "forgejo.org/models/unittest" user_model "forgejo.org/models/user" + "forgejo.org/modules/optional" "forgejo.org/modules/setting" api "forgejo.org/modules/structs" "forgejo.org/tests" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "xorm.io/xorm/convert" ) func TestAPIListIssues(t *testing.T) { @@ -620,3 +625,185 @@ func TestAPISearchIssuesWithLabels(t *testing.T) { DecodeJSON(t, resp, &apiIssues) assert.Len(t, apiIssues, 2) } + +func TestAPIInternalAndExternalIssueTracker(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + otherUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeAll) + + internalIssueRepo, _, reset := tests.CreateDeclarativeRepoWithOptions(t, user, tests.DeclarativeRepoOptions{ + Name: optional.Some("internal-issues"), + EnabledUnits: optional.Some([]unit.Type{unit.TypeIssues}), + DisabledUnits: optional.Some([]unit.Type{unit.TypeExternalTracker}), + UnitConfig: optional.Some(map[unit.Type]convert.Conversion{ + unit.TypeIssues: &repo_model.IssuesConfig{ + EnableTimetracker: true, + EnableDependencies: true, + }, + }), + }) + defer reset() + + externalIssueRepo, _, reset := tests.CreateDeclarativeRepoWithOptions(t, user, tests.DeclarativeRepoOptions{ + Name: optional.Some("external-issues"), + EnabledUnits: optional.Some([]unit.Type{unit.TypeExternalTracker}), + DisabledUnits: optional.Some([]unit.Type{unit.TypeIssues}), + }) + defer reset() + + disabledIssueRepo, _, reset := tests.CreateDeclarativeRepoWithOptions(t, user, tests.DeclarativeRepoOptions{ + Name: optional.Some("disabled-issues"), + DisabledUnits: optional.Some([]unit.Type{unit.TypeIssues, unit.TypeExternalTracker}), + }) + defer reset() + + runTest := func(t *testing.T, repo *repo_model.Repository, requestAllowed bool) { + t.Helper() + getPath := func(path string, args ...any) string { + suffix := path + if len(args) > 0 { + suffix = fmt.Sprintf(path, args...) + } + return fmt.Sprintf("/api/v1/repos/%s/%s/issues%s", repo.OwnerName, repo.Name, suffix) + } + getStatus := func(allowStatus int) int { + if requestAllowed { + return allowStatus + } + return http.StatusNotFound + } + okStatus := getStatus(http.StatusOK) + createdStatus := getStatus(http.StatusCreated) + noContentStatus := getStatus(http.StatusNoContent) + + // setup + issue := createIssue(t, user, repo, "normal issue", uuid.NewString()) + deleteIssue := createIssue(t, user, repo, "delete this issue", uuid.NewString()) + dependencyIssue := createIssue(t, user, repo, "depend on this issue", uuid.NewString()) + blocksIssue := createIssue(t, user, repo, "depend on this issue", uuid.NewString()) + + // issues + MakeRequest(t, NewRequest(t, "GET", getPath("/")).AddTokenAuth(token), http.StatusOK) + MakeRequest(t, NewRequestWithValues(t, "POST", getPath("/"), map[string]string{"title": uuid.NewString()}).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequest(t, "GET", getPath("/%d", issue.Index)).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequestWithValues(t, "PATCH", getPath("/%d", deleteIssue.Index), map[string]string{"title": uuid.NewString()}).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequest(t, "DELETE", getPath("/%d", deleteIssue.Index)).AddTokenAuth(token), noContentStatus) + + MakeRequest(t, NewRequest(t, "GET", getPath("/pinned")).AddTokenAuth(token), okStatus) + + // comments + MakeRequest(t, NewRequest(t, "GET", getPath("/comments")).AddTokenAuth(token), http.StatusOK) + MakeRequest(t, NewRequest(t, "GET", getPath("/%d/comments", issue.Index)).AddTokenAuth(token), okStatus) + resp := MakeRequest(t, NewRequestWithValues(t, "POST", getPath("/%d/comments", issue.Index), map[string]string{"body": uuid.NewString()}).AddTokenAuth(token), createdStatus) + var comment api.Comment + DecodeJSON(t, resp, &comment) + resp = MakeRequest(t, NewRequestWithValues(t, "POST", getPath("/%d/comments", issue.Index), map[string]string{"body": uuid.NewString()}).AddTokenAuth(token), createdStatus) + var commentTwo api.Comment + DecodeJSON(t, resp, &commentTwo) + resp = MakeRequest(t, NewRequestWithValues(t, "POST", getPath("/%d/comments", issue.Index), map[string]string{"body": uuid.NewString()}).AddTokenAuth(token), createdStatus) + var commentThree api.Comment + DecodeJSON(t, resp, &commentThree) + MakeRequest(t, NewRequest(t, "GET", getPath("/comments/%d", commentTwo.ID)).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequestWithValues(t, "PATCH", getPath("/comments/%d", commentTwo.ID), map[string]string{"body": uuid.NewString()}).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequest(t, "DELETE", getPath("/comments/%d", commentTwo.ID)).AddTokenAuth(token), noContentStatus) + MakeRequest(t, NewRequestWithValues(t, "PATCH", getPath("/%d/comments/%d", issue.Index, commentThree.ID), map[string]string{"body": uuid.NewString()}).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequest(t, "DELETE", getPath("/%d/comments/%d", issue.Index, commentThree.ID)).AddTokenAuth(token), noContentStatus) + // comment-reactions + MakeRequest(t, NewRequest(t, "GET", getPath("/comments/%d/reactions", comment.ID)).AddTokenAuth(token), okStatus) + reaction := &api.EditReactionOption{Reaction: "+1"} + MakeRequest(t, NewRequestWithJSON(t, "POST", getPath("/comments/%d/reactions", comment.ID), reaction).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequestWithJSON(t, "DELETE", getPath("/comments/%d/reactions", comment.ID), reaction).AddTokenAuth(token), okStatus) + // comment-assets + MakeRequest(t, NewRequest(t, "GET", getPath("/comments/%d/assets", comment.ID)).AddTokenAuth(token), okStatus) + body := &bytes.Buffer{} + contentType := tests.WriteImageBody(t, generateImg(), "image.png", body) + req := NewRequestWithBody(t, "POST", getPath("/comments/%d/assets", comment.ID), bytes.NewReader(body.Bytes())).AddTokenAuth(token) + req.Header.Add("Content-Type", contentType) + resp = MakeRequest(t, req, createdStatus) + var commentAttachment api.Attachment + DecodeJSON(t, resp, &commentAttachment) + MakeRequest(t, NewRequest(t, "GET", getPath("/comments/%d/assets/%d", comment.ID, commentAttachment.ID)).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequestWithValues(t, "PATCH", getPath("/comments/%d/assets/%d", comment.ID, commentAttachment.ID), map[string]string{"name": uuid.NewString()}).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequest(t, "DELETE", getPath("/comments/%d/assets/%d", comment.ID, commentAttachment.ID)).AddTokenAuth(token), noContentStatus) + + // timeline + MakeRequest(t, NewRequest(t, "GET", getPath("/%d/timeline", issue.Index)).AddTokenAuth(token), okStatus) + + // labels + labelName := uuid.NewString() + labelCreateURL := fmt.Sprintf("/api/v1/repos/%s/%s/labels", repo.OwnerName, repo.Name) + resp = MakeRequest(t, NewRequestWithValues(t, "POST", labelCreateURL, map[string]string{"name": labelName, "color": "#333333"}).AddTokenAuth(token), http.StatusCreated) + var label api.Label + DecodeJSON(t, resp, &label) + + MakeRequest(t, NewRequest(t, "GET", getPath("/%d/labels", issue.Index)).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequestWithJSON(t, "POST", getPath("/%d/labels", issue.Index), api.IssueLabelsOption{Labels: []any{labelName}}).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequestWithJSON(t, "PUT", getPath("/%d/labels", issue.Index), api.IssueLabelsOption{Labels: []any{labelName}}).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequest(t, "DELETE", getPath("/%d/labels", issue.Index)).AddTokenAuth(token), noContentStatus) + MakeRequest(t, NewRequest(t, "DELETE", getPath("/%d/labels/%d", issue.Index, label.ID)).AddTokenAuth(token), noContentStatus) + + // times + MakeRequest(t, NewRequest(t, "GET", getPath("/%d/times", issue.Index)).AddTokenAuth(token), okStatus) + resp = MakeRequest(t, NewRequestWithJSON(t, "POST", getPath("/%d/times", issue.Index), api.AddTimeOption{Time: 60}).AddTokenAuth(token), okStatus) + var trackedTime api.TrackedTime + DecodeJSON(t, resp, &trackedTime) + MakeRequest(t, NewRequest(t, "DELETE", getPath("/%d/times", issue.Index)).AddTokenAuth(token), noContentStatus) + resp = MakeRequest(t, NewRequestWithJSON(t, "POST", getPath("/%d/times", issue.Index), api.AddTimeOption{Time: 75}).AddTokenAuth(token), okStatus) + DecodeJSON(t, resp, &trackedTime) + MakeRequest(t, NewRequest(t, "DELETE", getPath("/%d/times/%d", issue.Index, trackedTime.ID)).AddTokenAuth(token), noContentStatus) + + // deadline + MakeRequest(t, NewRequestWithValues(t, "POST", getPath("/%d/deadline", issue.Index), map[string]string{"due_date": "2022-04-06T00:00:00.000Z"}).AddTokenAuth(token), createdStatus) + + // stopwatch + MakeRequest(t, NewRequest(t, "POST", getPath("/%d/stopwatch/start", issue.Index)).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequest(t, "POST", getPath("/%d/stopwatch/stop", issue.Index)).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequest(t, "POST", getPath("/%d/stopwatch/start", issue.Index)).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequest(t, "DELETE", getPath("/%d/stopwatch/delete", issue.Index)).AddTokenAuth(token), noContentStatus) + + // subscriptions + MakeRequest(t, NewRequest(t, "GET", getPath("/%d/subscriptions", issue.Index)).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequest(t, "GET", getPath("/%d/subscriptions/check", issue.Index)).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequest(t, "PUT", getPath("/%d/subscriptions/%s", issue.Index, otherUser.Name)).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequest(t, "DELETE", getPath("/%d/subscriptions/%s", issue.Index, otherUser.Name)).AddTokenAuth(token), createdStatus) + + // reactions + MakeRequest(t, NewRequest(t, "GET", getPath("/%d/reactions", issue.Index)).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequestWithJSON(t, "POST", getPath("/%d/reactions", issue.Index), api.EditReactionOption{Reaction: "+1"}).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequestWithJSON(t, "DELETE", getPath("/%d/reactions", issue.Index), api.EditReactionOption{Reaction: "+1"}).AddTokenAuth(token), okStatus) + + // assets + MakeRequest(t, NewRequest(t, "GET", getPath("/%d/assets", issue.Index)).AddTokenAuth(token), okStatus) + req = NewRequestWithBody(t, "POST", getPath("/%d/assets", issue.Index), bytes.NewReader(body.Bytes())).AddTokenAuth(token) + req.Header.Add("Content-Type", contentType) + resp = MakeRequest(t, req, createdStatus) + var attachment api.Attachment + DecodeJSON(t, resp, &attachment) + MakeRequest(t, NewRequest(t, "GET", getPath("/%d/assets/%d", issue.Index, attachment.ID)).AddTokenAuth(token), okStatus) + MakeRequest(t, NewRequestWithValues(t, "PATCH", getPath("/%d/assets/%d", issue.Index, attachment.ID), map[string]string{"name": uuid.NewString()}).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequest(t, "DELETE", getPath("/%d/assets/%d", issue.Index, attachment.ID)).AddTokenAuth(token), noContentStatus) + + // dependencies + MakeRequest(t, NewRequest(t, "GET", getPath("/%d/dependencies", issue.Index)).AddTokenAuth(token), okStatus) + dependencyMeta := api.IssueMeta{Index: dependencyIssue.Index, Owner: dependencyIssue.Repo.OwnerName, Name: dependencyIssue.Repo.Name} + MakeRequest(t, NewRequestWithJSON(t, "POST", getPath("/%d/dependencies", issue.Index), dependencyMeta).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequestWithJSON(t, "DELETE", getPath("/%d/dependencies", issue.Index), dependencyMeta).AddTokenAuth(token), createdStatus) + + // blocks + MakeRequest(t, NewRequest(t, "GET", getPath("/%d/blocks", issue.Index)).AddTokenAuth(token), okStatus) + blockMeta := api.IssueMeta{Index: blocksIssue.Index, Owner: blocksIssue.Repo.OwnerName, Name: blocksIssue.Repo.Name} + MakeRequest(t, NewRequestWithJSON(t, "POST", getPath("/%d/blocks", issue.Index), blockMeta).AddTokenAuth(token), createdStatus) + MakeRequest(t, NewRequestWithJSON(t, "DELETE", getPath("/%d/blocks", issue.Index), blockMeta).AddTokenAuth(token), createdStatus) + + // pin + MakeRequest(t, NewRequestWithJSON(t, "POST", getPath("/%d/pin", issue.Index), blockMeta).AddTokenAuth(token), noContentStatus) + MakeRequest(t, NewRequestWithJSON(t, "PATCH", getPath("/%d/pin/1", issue.Index), blockMeta).AddTokenAuth(token), noContentStatus) + MakeRequest(t, NewRequestWithJSON(t, "DELETE", getPath("/%d/pin", issue.Index), blockMeta).AddTokenAuth(token), noContentStatus) + } + + runTest(t, internalIssueRepo, true) + runTest(t, externalIssueRepo, false) + runTest(t, disabledIssueRepo, false) +} diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index f25948989a..a904f837b6 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -6,8 +6,6 @@ package integration import ( "bytes" "fmt" - "io" - "mime/multipart" "net/http" "net/url" "strings" @@ -319,18 +317,11 @@ func TestAPIUploadAssetRelease(t *testing.T) { defer tests.PrintCurrentTest(t)() body := &bytes.Buffer{} - - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", filename) - require.NoError(t, err) - _, err = io.Copy(part, bytes.NewReader(buff.Bytes())) - require.NoError(t, err) - err = writer.Close() - require.NoError(t, err) + contentType := tests.WriteImageBody(t, buff, filename, body) req := NewRequestWithBody(t, http.MethodPost, assetURL, bytes.NewReader(body.Bytes())). AddTokenAuth(token). - SetHeader("Content-Type", writer.FormDataContentType()) + SetHeader("Content-Type", contentType) resp := MakeRequest(t, req, http.StatusCreated) var attachment *api.Attachment @@ -341,7 +332,7 @@ func TestAPIUploadAssetRelease(t *testing.T) { req = NewRequestWithBody(t, http.MethodPost, assetURL+"?name=test-asset", bytes.NewReader(body.Bytes())). AddTokenAuth(token). - SetHeader("Content-Type", writer.FormDataContentType()) + SetHeader("Content-Type", contentType) resp = MakeRequest(t, req, http.StatusCreated) var attachment2 *api.Attachment @@ -467,18 +458,11 @@ func TestAPIDuplicateAssetRelease(t *testing.T) { filename := "image.png" buff := generateImg() body := &bytes.Buffer{} - - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", filename) - require.NoError(t, err) - _, err = io.Copy(part, &buff) - require.NoError(t, err) - err = writer.Close() - require.NoError(t, err) + contentType := tests.WriteImageBody(t, buff, filename, body) req := NewRequestWithBody(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets?name=test-asset&external_url=https%%3A%%2F%%2Fforgejo.org%%2F", owner.Name, repo.Name, r.ID), body). AddTokenAuth(token) - req.Header.Add("Content-Type", writer.FormDataContentType()) + req.Header.Add("Content-Type", contentType) MakeRequest(t, req, http.StatusBadRequest) } diff --git a/tests/integration/repo_badges_test.go b/tests/integration/repo_badges_test.go index baaa8c136a..928a9975fe 100644 --- a/tests/integration/repo_badges_test.go +++ b/tests/integration/repo_badges_test.go @@ -36,8 +36,8 @@ func TestBadges(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo, _, f := tests.CreateDeclarativeRepo(t, owner, "", - []unit_model.Type{unit_model.TypeActions}, - []unit_model.Type{unit_model.TypeIssues, unit_model.TypePullRequests, unit_model.TypeReleases}, + []unit_model.Type{unit_model.TypeActions, unit_model.TypeReleases}, + []unit_model.Type{unit_model.TypeIssues, unit_model.TypePullRequests}, []*files_service.ChangeRepoFile{ { Operation: "create", diff --git a/tests/test_utils.go b/tests/test_utils.go index d72ac476da..7ef316d79a 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -5,9 +5,12 @@ package tests import ( + "bytes" "context" "database/sql" "fmt" + "io" + "mime/multipart" "os" "path" "path/filepath" @@ -524,3 +527,13 @@ func CreateDeclarativeRepo(t *testing.T, owner *user_model.User, name string, en return CreateDeclarativeRepoWithOptions(t, owner, opts) } + +func WriteImageBody(t *testing.T, buff bytes.Buffer, filename string, body *bytes.Buffer) string { + writer := multipart.NewWriter(body) + defer writer.Close() + part, err := writer.CreateFormFile("attachment", filename) + require.NoError(t, err) + _, err = io.Copy(part, &buff) + require.NoError(t, err) + return writer.FormDataContentType() +}