forgejo/tests/integration/api_issue_pin_test.go
JakobDev aaa1094663
Add the ability to pin Issues ()
This adds the ability to pin important Issues and Pull Requests. You can
also move pinned Issues around to change their Position. Resolves .

## Screenshots

![grafik](https://user-images.githubusercontent.com/15185051/235123207-0aa39869-bb48-45c3-abe2-ba1e836046ec.png)

![grafik](https://user-images.githubusercontent.com/15185051/235123297-152a16ea-a857-451d-9a42-61f2cd54dd75.png)

![grafik](https://user-images.githubusercontent.com/15185051/235640782-cbfe25ec-6254-479a-a3de-133e585d7a2d.png)

The Design was mostly copied from the Projects Board.

## Implementation
This uses a new `pin_order` Column in the `issue` table. If the value is
set to 0, the Issue is not pinned. If it's set to a bigger value, the
value is the Position. 1 means it's the first pinned Issue, 2 means it's
the second one etc. This is dived into Issues and Pull requests for each
Repo.

## TODO
- [x] You can currently pin as many Issues as you want. Maybe we should
add a Limit, which is configurable. GitHub uses 3, but I prefer 6, as
this is better for bigger Projects, but I'm open for suggestions.
- [x] Pin and Unpin events need to be added to the Issue history.
- [x] Tests
- [x] Migration

**The feature itself is currently fully working, so tester who may find
weird edge cases are very welcome!**

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
2023-05-25 15:17:19 +02:00

205 lines
7.2 KiB
Go

// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestAPIPinIssue(t *testing.T) {
defer tests.PrepareTestEnv(t)()
assert.NoError(t, unittest.LoadFixtures())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo)
// Pin the Issue
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin?token=%s",
repo.OwnerName, repo.Name, issue.Index, token)
req := NewRequest(t, "POST", urlStr)
MakeRequest(t, req, http.StatusNoContent)
// Check if the Issue is pinned
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)
req = NewRequest(t, "GET", urlStr)
resp := MakeRequest(t, req, http.StatusOK)
var issueAPI api.Issue
DecodeJSON(t, resp, &issueAPI)
assert.Equal(t, 1, issueAPI.PinOrder)
}
func TestAPIUnpinIssue(t *testing.T) {
defer tests.PrepareTestEnv(t)()
assert.NoError(t, unittest.LoadFixtures())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo)
// Pin the Issue
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin?token=%s",
repo.OwnerName, repo.Name, issue.Index, token)
req := NewRequest(t, "POST", urlStr)
MakeRequest(t, req, http.StatusNoContent)
// Check if the Issue is pinned
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)
req = NewRequest(t, "GET", urlStr)
resp := MakeRequest(t, req, http.StatusOK)
var issueAPI api.Issue
DecodeJSON(t, resp, &issueAPI)
assert.Equal(t, 1, issueAPI.PinOrder)
// Unpin the Issue
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin?token=%s",
repo.OwnerName, repo.Name, issue.Index, token)
req = NewRequest(t, "DELETE", urlStr)
MakeRequest(t, req, http.StatusNoContent)
// Check if the Issue is no longer pinned
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)
req = NewRequest(t, "GET", urlStr)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &issueAPI)
assert.Equal(t, 0, issueAPI.PinOrder)
}
func TestAPIMoveIssuePin(t *testing.T) {
defer tests.PrepareTestEnv(t)()
assert.NoError(t, unittest.LoadFixtures())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2, RepoID: repo.ID})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo)
// Pin the first Issue
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin?token=%s",
repo.OwnerName, repo.Name, issue.Index, token)
req := NewRequest(t, "POST", urlStr)
MakeRequest(t, req, http.StatusNoContent)
// Check if the first Issue is pinned at position 1
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)
req = NewRequest(t, "GET", urlStr)
resp := MakeRequest(t, req, http.StatusOK)
var issueAPI api.Issue
DecodeJSON(t, resp, &issueAPI)
assert.Equal(t, 1, issueAPI.PinOrder)
// Pin the second Issue
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin?token=%s",
repo.OwnerName, repo.Name, issue2.Index, token)
req = NewRequest(t, "POST", urlStr)
MakeRequest(t, req, http.StatusNoContent)
// Move the first Issue to position 2
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin/2?token=%s",
repo.OwnerName, repo.Name, issue.Index, token)
req = NewRequest(t, "PATCH", urlStr)
MakeRequest(t, req, http.StatusNoContent)
// Check if the first Issue is pinned at position 2
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)
req = NewRequest(t, "GET", urlStr)
resp = MakeRequest(t, req, http.StatusOK)
var issueAPI3 api.Issue
DecodeJSON(t, resp, &issueAPI3)
assert.Equal(t, 2, issueAPI3.PinOrder)
// Check if the second Issue is pinned at position 1
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue2.Index)
req = NewRequest(t, "GET", urlStr)
resp = MakeRequest(t, req, http.StatusOK)
var issueAPI4 api.Issue
DecodeJSON(t, resp, &issueAPI4)
assert.Equal(t, 1, issueAPI4.PinOrder)
}
func TestAPIListPinnedIssues(t *testing.T) {
defer tests.PrepareTestEnv(t)()
assert.NoError(t, unittest.LoadFixtures())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo)
// Pin the Issue
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin?token=%s",
repo.OwnerName, repo.Name, issue.Index, token)
req := NewRequest(t, "POST", urlStr)
MakeRequest(t, req, http.StatusNoContent)
// Check if the Issue is in the List
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/pinned", repo.OwnerName, repo.Name)
req = NewRequest(t, "GET", urlStr)
resp := MakeRequest(t, req, http.StatusOK)
var issueList []api.Issue
DecodeJSON(t, resp, &issueList)
assert.Equal(t, 1, len(issueList))
assert.Equal(t, issue.ID, issueList[0].ID)
}
func TestAPIListPinnedPullrequests(t *testing.T) {
defer tests.PrepareTestEnv(t)()
assert.NoError(t, unittest.LoadFixtures())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/pinned", repo.OwnerName, repo.Name)
req := NewRequest(t, "GET", urlStr)
resp := MakeRequest(t, req, http.StatusOK)
var prList []api.PullRequest
DecodeJSON(t, resp, &prList)
assert.Equal(t, 0, len(prList))
}
func TestAPINewPinAllowed(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/new_pin_allowed", owner.Name, repo.Name)
req := NewRequest(t, "GET", urlStr)
resp := MakeRequest(t, req, http.StatusOK)
var newPinsAllowed api.NewIssuePinsAllowed
DecodeJSON(t, resp, &newPinsAllowed)
assert.True(t, newPinsAllowed.Issues)
assert.True(t, newPinsAllowed.PullRequests)
}