mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-09-12 22:07:17 +00:00
fix(api): deactivate issue api for disabled or external issue-tracker (#8829)
- When the issue unit is disabled for a repository, don't allow issue related APIs. - Added integration tests. - Resolves #8408 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8829 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: zokki <zokki.softwareschmiede@gmail.com> Co-committed-by: zokki <zokki.softwareschmiede@gmail.com>
This commit is contained in:
parent
8f4ebab023
commit
4247c37300
7 changed files with 252 additions and 75 deletions
|
@ -69,6 +69,7 @@ package v1
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
actions_model "forgejo.org/models/actions"
|
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
|
// 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) {
|
func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
|
||||||
return 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() {
|
if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
|
||||||
ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
|
ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
|
||||||
return
|
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
|
// 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) {
|
func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
|
||||||
return 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() {
|
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")
|
ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
|
||||||
return
|
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) {
|
func mustEnableWiki(ctx *context.APIContext) {
|
||||||
if !(ctx.Repo.CanRead(unit.TypeWiki)) {
|
if !(ctx.Repo.CanRead(unit.TypeWiki)) {
|
||||||
ctx.NotFound()
|
ctx.NotFound()
|
||||||
|
@ -1426,7 +1457,7 @@ func Routes() *web.Route {
|
||||||
m.Group("/comments", func() {
|
m.Group("/comments", func() {
|
||||||
m.Combo("").Get(repo.ListIssueComments).
|
m.Combo("").Get(repo.ListIssueComments).
|
||||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
|
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)
|
Delete(repo.DeleteIssueCommentDeprecated)
|
||||||
})
|
})
|
||||||
m.Get("/timeline", repo.ListIssueCommentsAndTimeline)
|
m.Get("/timeline", repo.ListIssueCommentsAndTimeline)
|
||||||
|
@ -1483,7 +1514,7 @@ func Routes() *web.Route {
|
||||||
Delete(reqToken(), reqAdmin(), repo.UnpinIssue)
|
Delete(reqToken(), reqAdmin(), repo.UnpinIssue)
|
||||||
m.Patch("/{position}", reqToken(), reqAdmin(), repo.MoveIssuePin)
|
m.Patch("/{position}", reqToken(), reqAdmin(), repo.MoveIssuePin)
|
||||||
})
|
})
|
||||||
})
|
}, mustEnableLocalIssuesIfIsIssue)
|
||||||
}, mustEnableIssuesOrPulls)
|
}, mustEnableIssuesOrPulls)
|
||||||
m.Group("/labels", func() {
|
m.Group("/labels", func() {
|
||||||
m.Combo("").Get(repo.ListLabels).
|
m.Combo("").Get(repo.ListLabels).
|
||||||
|
|
|
@ -6,7 +6,6 @@ package integration
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -110,17 +109,11 @@ func TestAPICreateCommentAttachment(t *testing.T) {
|
||||||
body := &bytes.Buffer{}
|
body := &bytes.Buffer{}
|
||||||
|
|
||||||
// Setup multi-part
|
// Setup multi-part
|
||||||
writer := multipart.NewWriter(body)
|
contentType := tests.WriteImageBody(t, buff, filename, 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)
|
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), 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).
|
AddTokenAuth(token).
|
||||||
SetHeader("Content-Type", writer.FormDataContentType())
|
SetHeader("Content-Type", contentType)
|
||||||
resp := session.MakeRequest(t, req, http.StatusCreated)
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
apiAttachment := new(api.Attachment)
|
apiAttachment := new(api.Attachment)
|
||||||
|
@ -150,16 +143,10 @@ func TestAPICreateCommentAttachmentAutoDate(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
// Setup multi-part
|
// Setup multi-part
|
||||||
writer := multipart.NewWriter(body)
|
contentType := tests.WriteImageBody(t, buff, filename, 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)
|
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "POST", urlStr, body).AddTokenAuth(token)
|
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)
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||||
apiAttachment := new(api.Attachment)
|
apiAttachment := new(api.Attachment)
|
||||||
DecodeJSON(t, resp, &apiAttachment)
|
DecodeJSON(t, resp, &apiAttachment)
|
||||||
|
@ -181,16 +168,10 @@ func TestAPICreateCommentAttachmentAutoDate(t *testing.T) {
|
||||||
urlStr += fmt.Sprintf("?updated_at=%s", updatedAt.UTC().Format(time.RFC3339))
|
urlStr += fmt.Sprintf("?updated_at=%s", updatedAt.UTC().Format(time.RFC3339))
|
||||||
|
|
||||||
// Setup multi-part
|
// Setup multi-part
|
||||||
writer := multipart.NewWriter(body)
|
contentType := tests.WriteImageBody(t, buff, filename, 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)
|
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "POST", urlStr, body).AddTokenAuth(token)
|
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)
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||||
apiAttachment := new(api.Attachment)
|
apiAttachment := new(api.Attachment)
|
||||||
DecodeJSON(t, resp, &apiAttachment)
|
DecodeJSON(t, resp, &apiAttachment)
|
||||||
|
|
|
@ -6,7 +6,6 @@ package integration
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -79,17 +78,11 @@ func TestAPICreateIssueAttachment(t *testing.T) {
|
||||||
body := &bytes.Buffer{}
|
body := &bytes.Buffer{}
|
||||||
|
|
||||||
// Setup multi-part
|
// Setup multi-part
|
||||||
writer := multipart.NewWriter(body)
|
contentType := tests.WriteImageBody(t, buff, filename, 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)
|
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body).
|
req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body).
|
||||||
AddTokenAuth(token)
|
AddTokenAuth(token)
|
||||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
req.Header.Add("Content-Type", contentType)
|
||||||
resp := session.MakeRequest(t, req, http.StatusCreated)
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
apiAttachment := new(api.Attachment)
|
apiAttachment := new(api.Attachment)
|
||||||
|
@ -118,16 +111,10 @@ func TestAPICreateIssueAttachmentAutoDate(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
// Setup multi-part
|
// Setup multi-part
|
||||||
writer := multipart.NewWriter(body)
|
contentType := tests.WriteImageBody(t, buff, filename, 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)
|
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "POST", urlStr, body).AddTokenAuth(token)
|
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)
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
apiAttachment := new(api.Attachment)
|
apiAttachment := new(api.Attachment)
|
||||||
|
@ -150,16 +137,10 @@ func TestAPICreateIssueAttachmentAutoDate(t *testing.T) {
|
||||||
urlStr += fmt.Sprintf("?updated_at=%s", updatedAt.UTC().Format(time.RFC3339))
|
urlStr += fmt.Sprintf("?updated_at=%s", updatedAt.UTC().Format(time.RFC3339))
|
||||||
|
|
||||||
// Setup multi-part
|
// Setup multi-part
|
||||||
writer := multipart.NewWriter(body)
|
contentType := tests.WriteImageBody(t, buff, filename, 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)
|
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "POST", urlStr, body).AddTokenAuth(token)
|
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)
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
apiAttachment := new(api.Attachment)
|
apiAttachment := new(api.Attachment)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -16,14 +17,18 @@ import (
|
||||||
"forgejo.org/models/db"
|
"forgejo.org/models/db"
|
||||||
issues_model "forgejo.org/models/issues"
|
issues_model "forgejo.org/models/issues"
|
||||||
repo_model "forgejo.org/models/repo"
|
repo_model "forgejo.org/models/repo"
|
||||||
|
"forgejo.org/models/unit"
|
||||||
"forgejo.org/models/unittest"
|
"forgejo.org/models/unittest"
|
||||||
user_model "forgejo.org/models/user"
|
user_model "forgejo.org/models/user"
|
||||||
|
"forgejo.org/modules/optional"
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
api "forgejo.org/modules/structs"
|
api "forgejo.org/modules/structs"
|
||||||
"forgejo.org/tests"
|
"forgejo.org/tests"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"xorm.io/xorm/convert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPIListIssues(t *testing.T) {
|
func TestAPIListIssues(t *testing.T) {
|
||||||
|
@ -620,3 +625,185 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
|
||||||
DecodeJSON(t, resp, &apiIssues)
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
assert.Len(t, apiIssues, 2)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -6,8 +6,6 @@ package integration
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -319,18 +317,11 @@ func TestAPIUploadAssetRelease(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
body := &bytes.Buffer{}
|
body := &bytes.Buffer{}
|
||||||
|
contentType := tests.WriteImageBody(t, buff, filename, body)
|
||||||
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)
|
|
||||||
|
|
||||||
req := NewRequestWithBody(t, http.MethodPost, assetURL, bytes.NewReader(body.Bytes())).
|
req := NewRequestWithBody(t, http.MethodPost, assetURL, bytes.NewReader(body.Bytes())).
|
||||||
AddTokenAuth(token).
|
AddTokenAuth(token).
|
||||||
SetHeader("Content-Type", writer.FormDataContentType())
|
SetHeader("Content-Type", contentType)
|
||||||
resp := MakeRequest(t, req, http.StatusCreated)
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
var attachment *api.Attachment
|
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())).
|
req = NewRequestWithBody(t, http.MethodPost, assetURL+"?name=test-asset", bytes.NewReader(body.Bytes())).
|
||||||
AddTokenAuth(token).
|
AddTokenAuth(token).
|
||||||
SetHeader("Content-Type", writer.FormDataContentType())
|
SetHeader("Content-Type", contentType)
|
||||||
resp = MakeRequest(t, req, http.StatusCreated)
|
resp = MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
var attachment2 *api.Attachment
|
var attachment2 *api.Attachment
|
||||||
|
@ -467,18 +458,11 @@ func TestAPIDuplicateAssetRelease(t *testing.T) {
|
||||||
filename := "image.png"
|
filename := "image.png"
|
||||||
buff := generateImg()
|
buff := generateImg()
|
||||||
body := &bytes.Buffer{}
|
body := &bytes.Buffer{}
|
||||||
|
contentType := tests.WriteImageBody(t, buff, filename, body)
|
||||||
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)
|
|
||||||
|
|
||||||
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).
|
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)
|
AddTokenAuth(token)
|
||||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
req.Header.Add("Content-Type", contentType)
|
||||||
MakeRequest(t, req, http.StatusBadRequest)
|
MakeRequest(t, req, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,8 @@ func TestBadges(t *testing.T) {
|
||||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
repo, _, f := tests.CreateDeclarativeRepo(t, owner, "",
|
repo, _, f := tests.CreateDeclarativeRepo(t, owner, "",
|
||||||
[]unit_model.Type{unit_model.TypeActions},
|
[]unit_model.Type{unit_model.TypeActions, unit_model.TypeReleases},
|
||||||
[]unit_model.Type{unit_model.TypeIssues, unit_model.TypePullRequests, unit_model.TypeReleases},
|
[]unit_model.Type{unit_model.TypeIssues, unit_model.TypePullRequests},
|
||||||
[]*files_service.ChangeRepoFile{
|
[]*files_service.ChangeRepoFile{
|
||||||
{
|
{
|
||||||
Operation: "create",
|
Operation: "create",
|
||||||
|
|
|
@ -5,9 +5,12 @@
|
||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -524,3 +527,13 @@ func CreateDeclarativeRepo(t *testing.T, owner *user_model.User, name string, en
|
||||||
|
|
||||||
return CreateDeclarativeRepoWithOptions(t, owner, opts)
|
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()
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue