code.gitea.io/gitea@v1.21.7/tests/integration/issue_test.go (about)

     1  // Copyright 2017 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"path"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"code.gitea.io/gitea/models/db"
    18  	issues_model "code.gitea.io/gitea/models/issues"
    19  	repo_model "code.gitea.io/gitea/models/repo"
    20  	"code.gitea.io/gitea/models/unittest"
    21  	user_model "code.gitea.io/gitea/models/user"
    22  	"code.gitea.io/gitea/modules/indexer/issues"
    23  	"code.gitea.io/gitea/modules/references"
    24  	"code.gitea.io/gitea/modules/setting"
    25  	api "code.gitea.io/gitea/modules/structs"
    26  	"code.gitea.io/gitea/modules/test"
    27  	"code.gitea.io/gitea/tests"
    28  
    29  	"github.com/PuerkitoBio/goquery"
    30  	"github.com/stretchr/testify/assert"
    31  )
    32  
    33  func getIssuesSelection(t testing.TB, htmlDoc *HTMLDoc) *goquery.Selection {
    34  	issueList := htmlDoc.doc.Find("#issue-list")
    35  	assert.EqualValues(t, 1, issueList.Length())
    36  	return issueList.Find(".flex-item").Find(".issue-title")
    37  }
    38  
    39  func getIssue(t *testing.T, repoID int64, issueSelection *goquery.Selection) *issues_model.Issue {
    40  	href, exists := issueSelection.Attr("href")
    41  	assert.True(t, exists)
    42  	indexStr := href[strings.LastIndexByte(href, '/')+1:]
    43  	index, err := strconv.Atoi(indexStr)
    44  	assert.NoError(t, err, "Invalid issue href: %s", href)
    45  	return unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repoID, Index: int64(index)})
    46  }
    47  
    48  func assertMatch(t testing.TB, issue *issues_model.Issue, keyword string) {
    49  	matches := strings.Contains(strings.ToLower(issue.Title), keyword) ||
    50  		strings.Contains(strings.ToLower(issue.Content), keyword)
    51  	for _, comment := range issue.Comments {
    52  		matches = matches || strings.Contains(
    53  			strings.ToLower(comment.Content),
    54  			keyword,
    55  		)
    56  	}
    57  	assert.True(t, matches)
    58  }
    59  
    60  func TestNoLoginViewIssues(t *testing.T) {
    61  	defer tests.PrepareTestEnv(t)()
    62  
    63  	req := NewRequest(t, "GET", "/user2/repo1/issues")
    64  	MakeRequest(t, req, http.StatusOK)
    65  }
    66  
    67  func TestViewIssuesSortByType(t *testing.T) {
    68  	defer tests.PrepareTestEnv(t)()
    69  
    70  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
    71  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
    72  
    73  	session := loginUser(t, user.Name)
    74  	req := NewRequest(t, "GET", repo.Link()+"/issues?type=created_by")
    75  	resp := session.MakeRequest(t, req, http.StatusOK)
    76  
    77  	htmlDoc := NewHTMLParser(t, resp.Body)
    78  	issuesSelection := getIssuesSelection(t, htmlDoc)
    79  	expectedNumIssues := unittest.GetCount(t,
    80  		&issues_model.Issue{RepoID: repo.ID, PosterID: user.ID},
    81  		unittest.Cond("is_closed=?", false),
    82  		unittest.Cond("is_pull=?", false),
    83  	)
    84  	if expectedNumIssues > setting.UI.IssuePagingNum {
    85  		expectedNumIssues = setting.UI.IssuePagingNum
    86  	}
    87  	assert.EqualValues(t, expectedNumIssues, issuesSelection.Length())
    88  
    89  	issuesSelection.Each(func(_ int, selection *goquery.Selection) {
    90  		issue := getIssue(t, repo.ID, selection)
    91  		assert.EqualValues(t, user.ID, issue.PosterID)
    92  	})
    93  }
    94  
    95  func TestViewIssuesKeyword(t *testing.T) {
    96  	defer tests.PrepareTestEnv(t)()
    97  
    98  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
    99  	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{
   100  		RepoID: repo.ID,
   101  		Index:  1,
   102  	})
   103  	issues.UpdateIssueIndexer(context.Background(), issue.ID)
   104  	time.Sleep(time.Second * 1)
   105  	const keyword = "first"
   106  	req := NewRequestf(t, "GET", "%s/issues?q=%s", repo.Link(), keyword)
   107  	resp := MakeRequest(t, req, http.StatusOK)
   108  
   109  	htmlDoc := NewHTMLParser(t, resp.Body)
   110  	issuesSelection := getIssuesSelection(t, htmlDoc)
   111  	assert.EqualValues(t, 1, issuesSelection.Length())
   112  	issuesSelection.Each(func(_ int, selection *goquery.Selection) {
   113  		issue := getIssue(t, repo.ID, selection)
   114  		assert.False(t, issue.IsClosed)
   115  		assert.False(t, issue.IsPull)
   116  		assertMatch(t, issue, keyword)
   117  	})
   118  }
   119  
   120  func TestNoLoginViewIssue(t *testing.T) {
   121  	defer tests.PrepareTestEnv(t)()
   122  
   123  	req := NewRequest(t, "GET", "/user2/repo1/issues/1")
   124  	MakeRequest(t, req, http.StatusOK)
   125  }
   126  
   127  func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content string) string {
   128  	req := NewRequest(t, "GET", path.Join(user, repo, "issues", "new"))
   129  	resp := session.MakeRequest(t, req, http.StatusOK)
   130  
   131  	htmlDoc := NewHTMLParser(t, resp.Body)
   132  	link, exists := htmlDoc.doc.Find("form.ui.form").Attr("action")
   133  	assert.True(t, exists, "The template has changed")
   134  	req = NewRequestWithValues(t, "POST", link, map[string]string{
   135  		"_csrf":   htmlDoc.GetCSRF(),
   136  		"title":   title,
   137  		"content": content,
   138  	})
   139  	resp = session.MakeRequest(t, req, http.StatusOK)
   140  
   141  	issueURL := test.RedirectURL(resp)
   142  	req = NewRequest(t, "GET", issueURL)
   143  	resp = session.MakeRequest(t, req, http.StatusOK)
   144  
   145  	htmlDoc = NewHTMLParser(t, resp.Body)
   146  	val := htmlDoc.doc.Find("#issue-title").Text()
   147  	assert.Contains(t, val, title)
   148  	val = htmlDoc.doc.Find(".comment .render-content p").First().Text()
   149  	assert.Equal(t, content, val)
   150  
   151  	return issueURL
   152  }
   153  
   154  func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) int64 {
   155  	req := NewRequest(t, "GET", issueURL)
   156  	resp := session.MakeRequest(t, req, http.StatusOK)
   157  
   158  	htmlDoc := NewHTMLParser(t, resp.Body)
   159  	link, exists := htmlDoc.doc.Find("#comment-form").Attr("action")
   160  	assert.True(t, exists, "The template has changed")
   161  
   162  	commentCount := htmlDoc.doc.Find(".comment-list .comment .render-content").Length()
   163  
   164  	req = NewRequestWithValues(t, "POST", link, map[string]string{
   165  		"_csrf":   htmlDoc.GetCSRF(),
   166  		"content": content,
   167  		"status":  status,
   168  	})
   169  	resp = session.MakeRequest(t, req, http.StatusOK)
   170  
   171  	req = NewRequest(t, "GET", test.RedirectURL(resp))
   172  	resp = session.MakeRequest(t, req, http.StatusOK)
   173  
   174  	htmlDoc = NewHTMLParser(t, resp.Body)
   175  
   176  	val := htmlDoc.doc.Find(".comment-list .comment .render-content p").Eq(commentCount).Text()
   177  	assert.Equal(t, content, val)
   178  
   179  	idAttr, has := htmlDoc.doc.Find(".comment-list .comment").Eq(commentCount).Attr("id")
   180  	idStr := idAttr[strings.LastIndexByte(idAttr, '-')+1:]
   181  	assert.True(t, has)
   182  	id, err := strconv.Atoi(idStr)
   183  	assert.NoError(t, err)
   184  	return int64(id)
   185  }
   186  
   187  func TestNewIssue(t *testing.T) {
   188  	defer tests.PrepareTestEnv(t)()
   189  	session := loginUser(t, "user2")
   190  	testNewIssue(t, session, "user2", "repo1", "Title", "Description")
   191  }
   192  
   193  func TestIssueCommentClose(t *testing.T) {
   194  	defer tests.PrepareTestEnv(t)()
   195  	session := loginUser(t, "user2")
   196  	issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
   197  	testIssueAddComment(t, session, issueURL, "Test comment 1", "")
   198  	testIssueAddComment(t, session, issueURL, "Test comment 2", "")
   199  	testIssueAddComment(t, session, issueURL, "Test comment 3", "close")
   200  
   201  	// Validate that issue content has not been updated
   202  	req := NewRequest(t, "GET", issueURL)
   203  	resp := session.MakeRequest(t, req, http.StatusOK)
   204  	htmlDoc := NewHTMLParser(t, resp.Body)
   205  	val := htmlDoc.doc.Find(".comment-list .comment .render-content p").First().Text()
   206  	assert.Equal(t, "Description", val)
   207  }
   208  
   209  func TestIssueCommentDelete(t *testing.T) {
   210  	defer tests.PrepareTestEnv(t)()
   211  	session := loginUser(t, "user2")
   212  	issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
   213  	comment1 := "Test comment 1"
   214  	commentID := testIssueAddComment(t, session, issueURL, comment1, "")
   215  	comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
   216  	assert.Equal(t, comment1, comment.Content)
   217  
   218  	// Using the ID of a comment that does not belong to the repository must fail
   219  	req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user5", "repo4", commentID), map[string]string{
   220  		"_csrf": GetCSRF(t, session, issueURL),
   221  	})
   222  	session.MakeRequest(t, req, http.StatusNotFound)
   223  	req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user2", "repo1", commentID), map[string]string{
   224  		"_csrf": GetCSRF(t, session, issueURL),
   225  	})
   226  	session.MakeRequest(t, req, http.StatusOK)
   227  	unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: commentID})
   228  }
   229  
   230  func TestIssueCommentUpdate(t *testing.T) {
   231  	defer tests.PrepareTestEnv(t)()
   232  	session := loginUser(t, "user2")
   233  	issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
   234  	comment1 := "Test comment 1"
   235  	commentID := testIssueAddComment(t, session, issueURL, comment1, "")
   236  
   237  	comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
   238  	assert.Equal(t, comment1, comment.Content)
   239  
   240  	modifiedContent := comment.Content + "MODIFIED"
   241  
   242  	// Using the ID of a comment that does not belong to the repository must fail
   243  	req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user5", "repo4", commentID), map[string]string{
   244  		"_csrf":   GetCSRF(t, session, issueURL),
   245  		"content": modifiedContent,
   246  	})
   247  	session.MakeRequest(t, req, http.StatusNotFound)
   248  
   249  	req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
   250  		"_csrf":   GetCSRF(t, session, issueURL),
   251  		"content": modifiedContent,
   252  	})
   253  	session.MakeRequest(t, req, http.StatusOK)
   254  
   255  	comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
   256  	assert.Equal(t, modifiedContent, comment.Content)
   257  }
   258  
   259  func TestIssueReaction(t *testing.T) {
   260  	defer tests.PrepareTestEnv(t)()
   261  	session := loginUser(t, "user2")
   262  	issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
   263  
   264  	req := NewRequest(t, "GET", issueURL)
   265  	resp := session.MakeRequest(t, req, http.StatusOK)
   266  	htmlDoc := NewHTMLParser(t, resp.Body)
   267  
   268  	req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/react"), map[string]string{
   269  		"_csrf":   htmlDoc.GetCSRF(),
   270  		"content": "8ball",
   271  	})
   272  	session.MakeRequest(t, req, http.StatusInternalServerError)
   273  	req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/react"), map[string]string{
   274  		"_csrf":   htmlDoc.GetCSRF(),
   275  		"content": "eyes",
   276  	})
   277  	session.MakeRequest(t, req, http.StatusOK)
   278  	req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/unreact"), map[string]string{
   279  		"_csrf":   htmlDoc.GetCSRF(),
   280  		"content": "eyes",
   281  	})
   282  	session.MakeRequest(t, req, http.StatusOK)
   283  }
   284  
   285  func TestIssueCrossReference(t *testing.T) {
   286  	defer tests.PrepareTestEnv(t)()
   287  
   288  	// Issue that will be referenced
   289  	_, issueBase := testIssueWithBean(t, "user2", 1, "Title", "Description")
   290  
   291  	// Ref from issue title
   292  	issueRefURL, issueRef := testIssueWithBean(t, "user2", 1, fmt.Sprintf("Title ref #%d", issueBase.Index), "Description")
   293  	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{
   294  		IssueID:      issueBase.ID,
   295  		RefRepoID:    1,
   296  		RefIssueID:   issueRef.ID,
   297  		RefCommentID: 0,
   298  		RefIsPull:    false,
   299  		RefAction:    references.XRefActionNone,
   300  	})
   301  
   302  	// Edit title, neuter ref
   303  	testIssueChangeInfo(t, "user2", issueRefURL, "title", "Title no ref")
   304  	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{
   305  		IssueID:      issueBase.ID,
   306  		RefRepoID:    1,
   307  		RefIssueID:   issueRef.ID,
   308  		RefCommentID: 0,
   309  		RefIsPull:    false,
   310  		RefAction:    references.XRefActionNeutered,
   311  	})
   312  
   313  	// Ref from issue content
   314  	issueRefURL, issueRef = testIssueWithBean(t, "user2", 1, "TitleXRef", fmt.Sprintf("Description ref #%d", issueBase.Index))
   315  	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{
   316  		IssueID:      issueBase.ID,
   317  		RefRepoID:    1,
   318  		RefIssueID:   issueRef.ID,
   319  		RefCommentID: 0,
   320  		RefIsPull:    false,
   321  		RefAction:    references.XRefActionNone,
   322  	})
   323  
   324  	// Edit content, neuter ref
   325  	testIssueChangeInfo(t, "user2", issueRefURL, "content", "Description no ref")
   326  	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{
   327  		IssueID:      issueBase.ID,
   328  		RefRepoID:    1,
   329  		RefIssueID:   issueRef.ID,
   330  		RefCommentID: 0,
   331  		RefIsPull:    false,
   332  		RefAction:    references.XRefActionNeutered,
   333  	})
   334  
   335  	// Ref from a comment
   336  	session := loginUser(t, "user2")
   337  	commentID := testIssueAddComment(t, session, issueRefURL, fmt.Sprintf("Adding ref from comment #%d", issueBase.Index), "")
   338  	comment := &issues_model.Comment{
   339  		IssueID:      issueBase.ID,
   340  		RefRepoID:    1,
   341  		RefIssueID:   issueRef.ID,
   342  		RefCommentID: commentID,
   343  		RefIsPull:    false,
   344  		RefAction:    references.XRefActionNone,
   345  	}
   346  	unittest.AssertExistsAndLoadBean(t, comment)
   347  
   348  	// Ref from a different repository
   349  	_, issueRef = testIssueWithBean(t, "user12", 10, "TitleXRef", fmt.Sprintf("Description ref user2/repo1#%d", issueBase.Index))
   350  	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{
   351  		IssueID:      issueBase.ID,
   352  		RefRepoID:    10,
   353  		RefIssueID:   issueRef.ID,
   354  		RefCommentID: 0,
   355  		RefIsPull:    false,
   356  		RefAction:    references.XRefActionNone,
   357  	})
   358  }
   359  
   360  func testIssueWithBean(t *testing.T, user string, repoID int64, title, content string) (string, *issues_model.Issue) {
   361  	session := loginUser(t, user)
   362  	issueURL := testNewIssue(t, session, user, fmt.Sprintf("repo%d", repoID), title, content)
   363  	indexStr := issueURL[strings.LastIndexByte(issueURL, '/')+1:]
   364  	index, err := strconv.Atoi(indexStr)
   365  	assert.NoError(t, err, "Invalid issue href: %s", issueURL)
   366  	issue := &issues_model.Issue{RepoID: repoID, Index: int64(index)}
   367  	unittest.AssertExistsAndLoadBean(t, issue)
   368  	return issueURL, issue
   369  }
   370  
   371  func testIssueChangeInfo(t *testing.T, user, issueURL, info, value string) {
   372  	session := loginUser(t, user)
   373  
   374  	req := NewRequest(t, "GET", issueURL)
   375  	resp := session.MakeRequest(t, req, http.StatusOK)
   376  	htmlDoc := NewHTMLParser(t, resp.Body)
   377  
   378  	req = NewRequestWithValues(t, "POST", path.Join(issueURL, info), map[string]string{
   379  		"_csrf": htmlDoc.GetCSRF(),
   380  		info:    value,
   381  	})
   382  	_ = session.MakeRequest(t, req, http.StatusOK)
   383  }
   384  
   385  func TestIssueRedirect(t *testing.T) {
   386  	defer tests.PrepareTestEnv(t)()
   387  	session := loginUser(t, "user2")
   388  
   389  	// Test external tracker where style not set (shall default numeric)
   390  	req := NewRequest(t, "GET", path.Join("org26", "repo_external_tracker", "issues", "1"))
   391  	resp := session.MakeRequest(t, req, http.StatusSeeOther)
   392  	assert.Equal(t, "https://tracker.com/org26/repo_external_tracker/issues/1", test.RedirectURL(resp))
   393  
   394  	// Test external tracker with numeric style
   395  	req = NewRequest(t, "GET", path.Join("org26", "repo_external_tracker_numeric", "issues", "1"))
   396  	resp = session.MakeRequest(t, req, http.StatusSeeOther)
   397  	assert.Equal(t, "https://tracker.com/org26/repo_external_tracker_numeric/issues/1", test.RedirectURL(resp))
   398  
   399  	// Test external tracker with alphanumeric style (for a pull request)
   400  	req = NewRequest(t, "GET", path.Join("org26", "repo_external_tracker_alpha", "issues", "1"))
   401  	resp = session.MakeRequest(t, req, http.StatusSeeOther)
   402  	assert.Equal(t, "/"+path.Join("org26", "repo_external_tracker_alpha", "pulls", "1"), test.RedirectURL(resp))
   403  }
   404  
   405  func TestSearchIssues(t *testing.T) {
   406  	defer tests.PrepareTestEnv(t)()
   407  
   408  	session := loginUser(t, "user2")
   409  
   410  	expectedIssueCount := 18 // from the fixtures
   411  	if expectedIssueCount > setting.UI.IssuePagingNum {
   412  		expectedIssueCount = setting.UI.IssuePagingNum
   413  	}
   414  
   415  	link, _ := url.Parse("/issues/search")
   416  	req := NewRequest(t, "GET", link.String())
   417  	resp := session.MakeRequest(t, req, http.StatusOK)
   418  	var apiIssues []*api.Issue
   419  	DecodeJSON(t, resp, &apiIssues)
   420  	assert.Len(t, apiIssues, expectedIssueCount)
   421  
   422  	since := "2000-01-01T00:50:01+00:00" // 946687801
   423  	before := time.Unix(999307200, 0).Format(time.RFC3339)
   424  	query := url.Values{}
   425  	query.Add("since", since)
   426  	query.Add("before", before)
   427  	link.RawQuery = query.Encode()
   428  	req = NewRequest(t, "GET", link.String())
   429  	resp = session.MakeRequest(t, req, http.StatusOK)
   430  	DecodeJSON(t, resp, &apiIssues)
   431  	assert.Len(t, apiIssues, 11)
   432  	query.Del("since")
   433  	query.Del("before")
   434  
   435  	query.Add("state", "closed")
   436  	link.RawQuery = query.Encode()
   437  	req = NewRequest(t, "GET", link.String())
   438  	resp = session.MakeRequest(t, req, http.StatusOK)
   439  	DecodeJSON(t, resp, &apiIssues)
   440  	assert.Len(t, apiIssues, 2)
   441  
   442  	query.Set("state", "all")
   443  	link.RawQuery = query.Encode()
   444  	req = NewRequest(t, "GET", link.String())
   445  	resp = session.MakeRequest(t, req, http.StatusOK)
   446  	DecodeJSON(t, resp, &apiIssues)
   447  	assert.EqualValues(t, "20", resp.Header().Get("X-Total-Count"))
   448  	assert.Len(t, apiIssues, 20)
   449  
   450  	query.Add("limit", "5")
   451  	link.RawQuery = query.Encode()
   452  	req = NewRequest(t, "GET", link.String())
   453  	resp = session.MakeRequest(t, req, http.StatusOK)
   454  	DecodeJSON(t, resp, &apiIssues)
   455  	assert.EqualValues(t, "20", resp.Header().Get("X-Total-Count"))
   456  	assert.Len(t, apiIssues, 5)
   457  
   458  	query = url.Values{"assigned": {"true"}, "state": {"all"}}
   459  	link.RawQuery = query.Encode()
   460  	req = NewRequest(t, "GET", link.String())
   461  	resp = session.MakeRequest(t, req, http.StatusOK)
   462  	DecodeJSON(t, resp, &apiIssues)
   463  	assert.Len(t, apiIssues, 2)
   464  
   465  	query = url.Values{"milestones": {"milestone1"}, "state": {"all"}}
   466  	link.RawQuery = query.Encode()
   467  	req = NewRequest(t, "GET", link.String())
   468  	resp = session.MakeRequest(t, req, http.StatusOK)
   469  	DecodeJSON(t, resp, &apiIssues)
   470  	assert.Len(t, apiIssues, 1)
   471  
   472  	query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}}
   473  	link.RawQuery = query.Encode()
   474  	req = NewRequest(t, "GET", link.String())
   475  	resp = session.MakeRequest(t, req, http.StatusOK)
   476  	DecodeJSON(t, resp, &apiIssues)
   477  	assert.Len(t, apiIssues, 2)
   478  
   479  	query = url.Values{"owner": {"user2"}} // user
   480  	link.RawQuery = query.Encode()
   481  	req = NewRequest(t, "GET", link.String())
   482  	resp = session.MakeRequest(t, req, http.StatusOK)
   483  	DecodeJSON(t, resp, &apiIssues)
   484  	assert.Len(t, apiIssues, 8)
   485  
   486  	query = url.Values{"owner": {"org3"}} // organization
   487  	link.RawQuery = query.Encode()
   488  	req = NewRequest(t, "GET", link.String())
   489  	resp = session.MakeRequest(t, req, http.StatusOK)
   490  	DecodeJSON(t, resp, &apiIssues)
   491  	assert.Len(t, apiIssues, 5)
   492  
   493  	query = url.Values{"owner": {"org3"}, "team": {"team1"}} // organization + team
   494  	link.RawQuery = query.Encode()
   495  	req = NewRequest(t, "GET", link.String())
   496  	resp = session.MakeRequest(t, req, http.StatusOK)
   497  	DecodeJSON(t, resp, &apiIssues)
   498  	assert.Len(t, apiIssues, 2)
   499  }
   500  
   501  func TestSearchIssuesWithLabels(t *testing.T) {
   502  	defer tests.PrepareTestEnv(t)()
   503  
   504  	expectedIssueCount := 18 // from the fixtures
   505  	if expectedIssueCount > setting.UI.IssuePagingNum {
   506  		expectedIssueCount = setting.UI.IssuePagingNum
   507  	}
   508  
   509  	session := loginUser(t, "user1")
   510  	link, _ := url.Parse("/issues/search")
   511  	query := url.Values{}
   512  	var apiIssues []*api.Issue
   513  
   514  	link.RawQuery = query.Encode()
   515  	req := NewRequest(t, "GET", link.String())
   516  	resp := session.MakeRequest(t, req, http.StatusOK)
   517  	DecodeJSON(t, resp, &apiIssues)
   518  	assert.Len(t, apiIssues, expectedIssueCount)
   519  
   520  	query.Add("labels", "label1")
   521  	link.RawQuery = query.Encode()
   522  	req = NewRequest(t, "GET", link.String())
   523  	resp = session.MakeRequest(t, req, http.StatusOK)
   524  	DecodeJSON(t, resp, &apiIssues)
   525  	assert.Len(t, apiIssues, 2)
   526  
   527  	// multiple labels
   528  	query.Set("labels", "label1,label2")
   529  	link.RawQuery = query.Encode()
   530  	req = NewRequest(t, "GET", link.String())
   531  	resp = session.MakeRequest(t, req, http.StatusOK)
   532  	DecodeJSON(t, resp, &apiIssues)
   533  	assert.Len(t, apiIssues, 2)
   534  
   535  	// an org label
   536  	query.Set("labels", "orglabel4")
   537  	link.RawQuery = query.Encode()
   538  	req = NewRequest(t, "GET", link.String())
   539  	resp = session.MakeRequest(t, req, http.StatusOK)
   540  	DecodeJSON(t, resp, &apiIssues)
   541  	assert.Len(t, apiIssues, 1)
   542  
   543  	// org and repo label
   544  	query.Set("labels", "label2,orglabel4")
   545  	query.Add("state", "all")
   546  	link.RawQuery = query.Encode()
   547  	req = NewRequest(t, "GET", link.String())
   548  	resp = session.MakeRequest(t, req, http.StatusOK)
   549  	DecodeJSON(t, resp, &apiIssues)
   550  	assert.Len(t, apiIssues, 2)
   551  
   552  	// org and repo label which share the same issue
   553  	query.Set("labels", "label1,orglabel4")
   554  	link.RawQuery = query.Encode()
   555  	req = NewRequest(t, "GET", link.String())
   556  	resp = session.MakeRequest(t, req, http.StatusOK)
   557  	DecodeJSON(t, resp, &apiIssues)
   558  	assert.Len(t, apiIssues, 2)
   559  }
   560  
   561  func TestGetIssueInfo(t *testing.T) {
   562  	defer tests.PrepareTestEnv(t)()
   563  
   564  	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
   565  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
   566  	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
   567  	assert.NoError(t, issue.LoadAttributes(db.DefaultContext))
   568  	assert.Equal(t, int64(1019307200), int64(issue.DeadlineUnix))
   569  	assert.Equal(t, api.StateOpen, issue.State())
   570  
   571  	session := loginUser(t, owner.Name)
   572  
   573  	urlStr := fmt.Sprintf("/%s/%s/issues/%d/info", owner.Name, repo.Name, issue.Index)
   574  	req := NewRequest(t, "GET", urlStr)
   575  	resp := session.MakeRequest(t, req, http.StatusOK)
   576  	var apiIssue api.Issue
   577  	DecodeJSON(t, resp, &apiIssue)
   578  
   579  	assert.EqualValues(t, issue.ID, apiIssue.ID)
   580  }
   581  
   582  func TestUpdateIssueDeadline(t *testing.T) {
   583  	defer tests.PrepareTestEnv(t)()
   584  
   585  	issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
   586  	repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
   587  	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
   588  	assert.NoError(t, issueBefore.LoadAttributes(db.DefaultContext))
   589  	assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix))
   590  	assert.Equal(t, api.StateOpen, issueBefore.State())
   591  
   592  	session := loginUser(t, owner.Name)
   593  
   594  	issueURL := fmt.Sprintf("%s/%s/issues/%d", owner.Name, repoBefore.Name, issueBefore.Index)
   595  	req := NewRequest(t, "GET", issueURL)
   596  	resp := session.MakeRequest(t, req, http.StatusOK)
   597  	htmlDoc := NewHTMLParser(t, resp.Body)
   598  
   599  	urlStr := issueURL + "/deadline?_csrf=" + htmlDoc.GetCSRF()
   600  	req = NewRequestWithJSON(t, "POST", urlStr, map[string]string{
   601  		"due_date": "2022-04-06T00:00:00.000Z",
   602  	})
   603  
   604  	resp = session.MakeRequest(t, req, http.StatusCreated)
   605  	var apiIssue api.IssueDeadline
   606  	DecodeJSON(t, resp, &apiIssue)
   607  
   608  	assert.EqualValues(t, "2022-04-06", apiIssue.Deadline.Format("2006-01-02"))
   609  }
   610  
   611  func TestIssueReferenceURL(t *testing.T) {
   612  	defer tests.PrepareTestEnv(t)()
   613  	session := loginUser(t, "user2")
   614  
   615  	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
   616  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
   617  
   618  	req := NewRequest(t, "GET", fmt.Sprintf("%s/issues/%d", repo.FullName(), issue.Index))
   619  	resp := session.MakeRequest(t, req, http.StatusOK)
   620  	htmlDoc := NewHTMLParser(t, resp.Body)
   621  
   622  	// the "reference" uses relative URLs, then JS code will convert them to absolute URLs for current origin, in case users are using multiple domains
   623  	ref, _ := htmlDoc.Find(`.timeline-item.comment.first .reference-issue`).Attr("data-reference")
   624  	assert.EqualValues(t, "/user2/repo1/issues/1#issue-1", ref)
   625  
   626  	ref, _ = htmlDoc.Find(`.timeline-item.comment:not(.first) .reference-issue`).Attr("data-reference")
   627  	assert.EqualValues(t, "/user2/repo1/issues/1#issuecomment-2", ref)
   628  }