code.gitea.io/gitea@v1.22.3/tests/integration/api_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  	"fmt"
     8  	"net/http"
     9  	"net/url"
    10  	"strconv"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  
    15  	auth_model "code.gitea.io/gitea/models/auth"
    16  	"code.gitea.io/gitea/models/db"
    17  	issues_model "code.gitea.io/gitea/models/issues"
    18  	repo_model "code.gitea.io/gitea/models/repo"
    19  	"code.gitea.io/gitea/models/unittest"
    20  	user_model "code.gitea.io/gitea/models/user"
    21  	"code.gitea.io/gitea/modules/setting"
    22  	api "code.gitea.io/gitea/modules/structs"
    23  	"code.gitea.io/gitea/tests"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  )
    27  
    28  func TestAPIListIssues(t *testing.T) {
    29  	defer tests.PrepareTestEnv(t)()
    30  
    31  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
    32  	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
    33  
    34  	session := loginUser(t, owner.Name)
    35  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
    36  	link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repo.Name))
    37  
    38  	link.RawQuery = url.Values{"token": {token}, "state": {"all"}}.Encode()
    39  	resp := MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
    40  	var apiIssues []*api.Issue
    41  	DecodeJSON(t, resp, &apiIssues)
    42  	assert.Len(t, apiIssues, unittest.GetCount(t, &issues_model.Issue{RepoID: repo.ID}))
    43  	for _, apiIssue := range apiIssues {
    44  		unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: apiIssue.ID, RepoID: repo.ID})
    45  	}
    46  
    47  	// test milestone filter
    48  	link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "type": {"all"}, "milestones": {"ignore,milestone1,3,4"}}.Encode()
    49  	resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
    50  	DecodeJSON(t, resp, &apiIssues)
    51  	if assert.Len(t, apiIssues, 2) {
    52  		assert.EqualValues(t, 3, apiIssues[0].Milestone.ID)
    53  		assert.EqualValues(t, 1, apiIssues[1].Milestone.ID)
    54  	}
    55  
    56  	link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "created_by": {"user2"}}.Encode()
    57  	resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
    58  	DecodeJSON(t, resp, &apiIssues)
    59  	if assert.Len(t, apiIssues, 1) {
    60  		assert.EqualValues(t, 5, apiIssues[0].ID)
    61  	}
    62  
    63  	link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "assigned_by": {"user1"}}.Encode()
    64  	resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
    65  	DecodeJSON(t, resp, &apiIssues)
    66  	if assert.Len(t, apiIssues, 1) {
    67  		assert.EqualValues(t, 1, apiIssues[0].ID)
    68  	}
    69  
    70  	link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "mentioned_by": {"user4"}}.Encode()
    71  	resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
    72  	DecodeJSON(t, resp, &apiIssues)
    73  	if assert.Len(t, apiIssues, 1) {
    74  		assert.EqualValues(t, 1, apiIssues[0].ID)
    75  	}
    76  }
    77  
    78  func TestAPIListIssuesPublicOnly(t *testing.T) {
    79  	defer tests.PrepareTestEnv(t)()
    80  
    81  	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
    82  	owner1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo1.OwnerID})
    83  
    84  	session := loginUser(t, owner1.Name)
    85  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
    86  	link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner1.Name, repo1.Name))
    87  	link.RawQuery = url.Values{"state": {"all"}}.Encode()
    88  	req := NewRequest(t, "GET", link.String()).AddTokenAuth(token)
    89  	MakeRequest(t, req, http.StatusOK)
    90  
    91  	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
    92  	owner2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID})
    93  
    94  	session = loginUser(t, owner2.Name)
    95  	token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
    96  	link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner2.Name, repo2.Name))
    97  	link.RawQuery = url.Values{"state": {"all"}}.Encode()
    98  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
    99  	MakeRequest(t, req, http.StatusOK)
   100  
   101  	publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
   102  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
   103  	MakeRequest(t, req, http.StatusForbidden)
   104  }
   105  
   106  func TestAPICreateIssue(t *testing.T) {
   107  	defer tests.PrepareTestEnv(t)()
   108  	const body, title = "apiTestBody", "apiTestTitle"
   109  
   110  	repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
   111  	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
   112  
   113  	session := loginUser(t, owner.Name)
   114  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
   115  	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repoBefore.Name)
   116  	req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{
   117  		Body:     body,
   118  		Title:    title,
   119  		Assignee: owner.Name,
   120  	}).AddTokenAuth(token)
   121  	resp := MakeRequest(t, req, http.StatusCreated)
   122  	var apiIssue api.Issue
   123  	DecodeJSON(t, resp, &apiIssue)
   124  	assert.Equal(t, body, apiIssue.Body)
   125  	assert.Equal(t, title, apiIssue.Title)
   126  
   127  	unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{
   128  		RepoID:     repoBefore.ID,
   129  		AssigneeID: owner.ID,
   130  		Content:    body,
   131  		Title:      title,
   132  	})
   133  
   134  	repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
   135  	assert.Equal(t, repoBefore.NumIssues+1, repoAfter.NumIssues)
   136  	assert.Equal(t, repoBefore.NumClosedIssues, repoAfter.NumClosedIssues)
   137  
   138  	user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34})
   139  	req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{
   140  		Title: title,
   141  	}).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteIssue))
   142  	MakeRequest(t, req, http.StatusForbidden)
   143  }
   144  
   145  func TestAPICreateIssueParallel(t *testing.T) {
   146  	defer tests.PrepareTestEnv(t)()
   147  	const body, title = "apiTestBody", "apiTestTitle"
   148  
   149  	repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
   150  	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
   151  
   152  	session := loginUser(t, owner.Name)
   153  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
   154  	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repoBefore.Name)
   155  
   156  	var wg sync.WaitGroup
   157  	for i := 0; i < 10; i++ {
   158  		wg.Add(1)
   159  		go func(parentT *testing.T, i int) {
   160  			parentT.Run(fmt.Sprintf("ParallelCreateIssue_%d", i), func(t *testing.T) {
   161  				newTitle := title + strconv.Itoa(i)
   162  				newBody := body + strconv.Itoa(i)
   163  				req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{
   164  					Body:     newBody,
   165  					Title:    newTitle,
   166  					Assignee: owner.Name,
   167  				}).AddTokenAuth(token)
   168  				resp := MakeRequest(t, req, http.StatusCreated)
   169  				var apiIssue api.Issue
   170  				DecodeJSON(t, resp, &apiIssue)
   171  				assert.Equal(t, newBody, apiIssue.Body)
   172  				assert.Equal(t, newTitle, apiIssue.Title)
   173  
   174  				unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{
   175  					RepoID:     repoBefore.ID,
   176  					AssigneeID: owner.ID,
   177  					Content:    newBody,
   178  					Title:      newTitle,
   179  				})
   180  
   181  				wg.Done()
   182  			})
   183  		}(t, i)
   184  	}
   185  	wg.Wait()
   186  }
   187  
   188  func TestAPIEditIssue(t *testing.T) {
   189  	defer tests.PrepareTestEnv(t)()
   190  
   191  	issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
   192  	repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
   193  	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
   194  	assert.NoError(t, issueBefore.LoadAttributes(db.DefaultContext))
   195  	assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix))
   196  	assert.Equal(t, api.StateOpen, issueBefore.State())
   197  
   198  	session := loginUser(t, owner.Name)
   199  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
   200  
   201  	// update values of issue
   202  	issueState := "closed"
   203  	removeDeadline := true
   204  	milestone := int64(4)
   205  	body := "new content!"
   206  	title := "new title from api set"
   207  
   208  	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", owner.Name, repoBefore.Name, issueBefore.Index)
   209  	req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
   210  		State:          &issueState,
   211  		RemoveDeadline: &removeDeadline,
   212  		Milestone:      &milestone,
   213  		Body:           &body,
   214  		Title:          title,
   215  
   216  		// ToDo change more
   217  	}).AddTokenAuth(token)
   218  	resp := MakeRequest(t, req, http.StatusCreated)
   219  	var apiIssue api.Issue
   220  	DecodeJSON(t, resp, &apiIssue)
   221  
   222  	issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
   223  	repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
   224  
   225  	// check comment history
   226  	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issueAfter.ID, OldTitle: issueBefore.Title, NewTitle: title})
   227  	unittest.AssertExistsAndLoadBean(t, &issues_model.ContentHistory{IssueID: issueAfter.ID, ContentText: body, IsFirstCreated: false})
   228  
   229  	// check deleted user
   230  	assert.Equal(t, int64(500), issueAfter.PosterID)
   231  	assert.NoError(t, issueAfter.LoadAttributes(db.DefaultContext))
   232  	assert.Equal(t, int64(-1), issueAfter.PosterID)
   233  	assert.Equal(t, int64(-1), issueBefore.PosterID)
   234  	assert.Equal(t, int64(-1), apiIssue.Poster.ID)
   235  
   236  	// check repo change
   237  	assert.Equal(t, repoBefore.NumClosedIssues+1, repoAfter.NumClosedIssues)
   238  
   239  	// API response
   240  	assert.Equal(t, api.StateClosed, apiIssue.State)
   241  	assert.Equal(t, milestone, apiIssue.Milestone.ID)
   242  	assert.Equal(t, body, apiIssue.Body)
   243  	assert.True(t, apiIssue.Deadline == nil)
   244  	assert.Equal(t, title, apiIssue.Title)
   245  
   246  	// in database
   247  	assert.Equal(t, api.StateClosed, issueAfter.State())
   248  	assert.Equal(t, milestone, issueAfter.MilestoneID)
   249  	assert.Equal(t, int64(0), int64(issueAfter.DeadlineUnix))
   250  	assert.Equal(t, body, issueAfter.Content)
   251  	assert.Equal(t, title, issueAfter.Title)
   252  }
   253  
   254  func TestAPISearchIssues(t *testing.T) {
   255  	defer tests.PrepareTestEnv(t)()
   256  
   257  	// as this API was used in the frontend, it uses UI page size
   258  	expectedIssueCount := 20 // from the fixtures
   259  	if expectedIssueCount > setting.UI.IssuePagingNum {
   260  		expectedIssueCount = setting.UI.IssuePagingNum
   261  	}
   262  
   263  	link, _ := url.Parse("/api/v1/repos/issues/search")
   264  	token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue)
   265  	query := url.Values{}
   266  	var apiIssues []*api.Issue
   267  
   268  	link.RawQuery = query.Encode()
   269  	req := NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   270  	resp := MakeRequest(t, req, http.StatusOK)
   271  	DecodeJSON(t, resp, &apiIssues)
   272  	assert.Len(t, apiIssues, expectedIssueCount)
   273  
   274  	publicOnlyToken := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
   275  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
   276  	resp = MakeRequest(t, req, http.StatusOK)
   277  	DecodeJSON(t, resp, &apiIssues)
   278  	assert.Len(t, apiIssues, 15) // 15 public issues
   279  
   280  	since := "2000-01-01T00:50:01+00:00" // 946687801
   281  	before := time.Unix(999307200, 0).Format(time.RFC3339)
   282  	query.Add("since", since)
   283  	query.Add("before", before)
   284  	link.RawQuery = query.Encode()
   285  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   286  	resp = MakeRequest(t, req, http.StatusOK)
   287  	DecodeJSON(t, resp, &apiIssues)
   288  	assert.Len(t, apiIssues, 11)
   289  	query.Del("since")
   290  	query.Del("before")
   291  
   292  	query.Add("state", "closed")
   293  	link.RawQuery = query.Encode()
   294  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   295  	resp = MakeRequest(t, req, http.StatusOK)
   296  	DecodeJSON(t, resp, &apiIssues)
   297  	assert.Len(t, apiIssues, 2)
   298  
   299  	query.Set("state", "all")
   300  	link.RawQuery = query.Encode()
   301  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   302  	resp = MakeRequest(t, req, http.StatusOK)
   303  	DecodeJSON(t, resp, &apiIssues)
   304  	assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count"))
   305  	assert.Len(t, apiIssues, 20)
   306  
   307  	query.Add("limit", "10")
   308  	link.RawQuery = query.Encode()
   309  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   310  	resp = MakeRequest(t, req, http.StatusOK)
   311  	DecodeJSON(t, resp, &apiIssues)
   312  	assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count"))
   313  	assert.Len(t, apiIssues, 10)
   314  
   315  	query = url.Values{"assigned": {"true"}, "state": {"all"}}
   316  	link.RawQuery = query.Encode()
   317  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   318  	resp = MakeRequest(t, req, http.StatusOK)
   319  	DecodeJSON(t, resp, &apiIssues)
   320  	assert.Len(t, apiIssues, 2)
   321  
   322  	query = url.Values{"milestones": {"milestone1"}, "state": {"all"}}
   323  	link.RawQuery = query.Encode()
   324  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   325  	resp = MakeRequest(t, req, http.StatusOK)
   326  	DecodeJSON(t, resp, &apiIssues)
   327  	assert.Len(t, apiIssues, 1)
   328  
   329  	query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}}
   330  	link.RawQuery = query.Encode()
   331  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   332  	resp = MakeRequest(t, req, http.StatusOK)
   333  	DecodeJSON(t, resp, &apiIssues)
   334  	assert.Len(t, apiIssues, 2)
   335  
   336  	query = url.Values{"owner": {"user2"}} // user
   337  	link.RawQuery = query.Encode()
   338  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   339  	resp = MakeRequest(t, req, http.StatusOK)
   340  	DecodeJSON(t, resp, &apiIssues)
   341  	assert.Len(t, apiIssues, 8)
   342  
   343  	query = url.Values{"owner": {"org3"}} // organization
   344  	link.RawQuery = query.Encode()
   345  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   346  	resp = MakeRequest(t, req, http.StatusOK)
   347  	DecodeJSON(t, resp, &apiIssues)
   348  	assert.Len(t, apiIssues, 5)
   349  
   350  	query = url.Values{"owner": {"org3"}, "team": {"team1"}} // organization + team
   351  	link.RawQuery = query.Encode()
   352  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   353  	resp = MakeRequest(t, req, http.StatusOK)
   354  	DecodeJSON(t, resp, &apiIssues)
   355  	assert.Len(t, apiIssues, 2)
   356  }
   357  
   358  func TestAPISearchIssuesWithLabels(t *testing.T) {
   359  	defer tests.PrepareTestEnv(t)()
   360  
   361  	// as this API was used in the frontend, it uses UI page size
   362  	expectedIssueCount := 20 // from the fixtures
   363  	if expectedIssueCount > setting.UI.IssuePagingNum {
   364  		expectedIssueCount = setting.UI.IssuePagingNum
   365  	}
   366  
   367  	link, _ := url.Parse("/api/v1/repos/issues/search")
   368  	token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue)
   369  	query := url.Values{}
   370  	var apiIssues []*api.Issue
   371  
   372  	link.RawQuery = query.Encode()
   373  	req := NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   374  	resp := MakeRequest(t, req, http.StatusOK)
   375  	DecodeJSON(t, resp, &apiIssues)
   376  	assert.Len(t, apiIssues, expectedIssueCount)
   377  
   378  	query.Add("labels", "label1")
   379  	link.RawQuery = query.Encode()
   380  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   381  	resp = MakeRequest(t, req, http.StatusOK)
   382  	DecodeJSON(t, resp, &apiIssues)
   383  	assert.Len(t, apiIssues, 2)
   384  
   385  	// multiple labels
   386  	query.Set("labels", "label1,label2")
   387  	link.RawQuery = query.Encode()
   388  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   389  	resp = MakeRequest(t, req, http.StatusOK)
   390  	DecodeJSON(t, resp, &apiIssues)
   391  	assert.Len(t, apiIssues, 2)
   392  
   393  	// an org label
   394  	query.Set("labels", "orglabel4")
   395  	link.RawQuery = query.Encode()
   396  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   397  	resp = MakeRequest(t, req, http.StatusOK)
   398  	DecodeJSON(t, resp, &apiIssues)
   399  	assert.Len(t, apiIssues, 1)
   400  
   401  	// org and repo label
   402  	query.Set("labels", "label2,orglabel4")
   403  	query.Add("state", "all")
   404  	link.RawQuery = query.Encode()
   405  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   406  	resp = MakeRequest(t, req, http.StatusOK)
   407  	DecodeJSON(t, resp, &apiIssues)
   408  	assert.Len(t, apiIssues, 2)
   409  
   410  	// org and repo label which share the same issue
   411  	query.Set("labels", "label1,orglabel4")
   412  	link.RawQuery = query.Encode()
   413  	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
   414  	resp = MakeRequest(t, req, http.StatusOK)
   415  	DecodeJSON(t, resp, &apiIssues)
   416  	assert.Len(t, apiIssues, 2)
   417  }