428 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			428 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Ruby
		
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| require 'spec_helper'
 | |
| 
 | |
| describe API::Issues do
 | |
|   let_it_be(:user) { create(:user) }
 | |
|   let_it_be(:project, reload: true) do
 | |
|     create(:project, :public, creator_id: user.id, namespace: user.namespace)
 | |
|   end
 | |
| 
 | |
|   let(:user2)             { create(:user) }
 | |
|   let(:non_member)        { create(:user) }
 | |
|   let_it_be(:guest)       { create(:user) }
 | |
|   let_it_be(:author)      { create(:author) }
 | |
|   let_it_be(:assignee)    { create(:assignee) }
 | |
|   let(:admin)             { create(:user, :admin) }
 | |
|   let(:issue_title)       { 'foo' }
 | |
|   let(:issue_description) { 'closed' }
 | |
|   let!(:closed_issue) do
 | |
|     create :closed_issue,
 | |
|       author: user,
 | |
|       assignees: [user],
 | |
|       project: project,
 | |
|       state: :closed,
 | |
|       milestone: milestone,
 | |
|       created_at: generate(:past_time),
 | |
|       updated_at: 3.hours.ago,
 | |
|       closed_at: 1.hour.ago
 | |
|   end
 | |
|   let!(:confidential_issue) do
 | |
|     create :issue,
 | |
|       :confidential,
 | |
|       project: project,
 | |
|       author: author,
 | |
|       assignees: [assignee],
 | |
|       created_at: generate(:past_time),
 | |
|       updated_at: 2.hours.ago
 | |
|   end
 | |
|   let!(:issue) do
 | |
|     create :issue,
 | |
|       author: user,
 | |
|       assignees: [user],
 | |
|       project: project,
 | |
|       milestone: milestone,
 | |
|       created_at: generate(:past_time),
 | |
|       updated_at: 1.hour.ago,
 | |
|       title: issue_title,
 | |
|       description: issue_description
 | |
|   end
 | |
|   let_it_be(:label) do
 | |
|     create(:label, title: 'label', color: '#FFAABB', project: project)
 | |
|   end
 | |
|   let!(:label_link) { create(:label_link, label: label, target: issue) }
 | |
|   let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
 | |
|   let_it_be(:empty_milestone) do
 | |
|     create(:milestone, title: '2.0.0', project: project)
 | |
|   end
 | |
|   let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
 | |
| 
 | |
|   let(:no_milestone_title) { 'None' }
 | |
|   let(:any_milestone_title) { 'Any' }
 | |
| 
 | |
|   before(:all) do
 | |
|     project.add_reporter(user)
 | |
|     project.add_guest(guest)
 | |
|   end
 | |
| 
 | |
|   before do
 | |
|     stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
 | |
|   end
 | |
| 
 | |
|   describe 'PUT /projects/:id/issues/:issue_iid to update only title' do
 | |
|     it 'updates a project issue' do
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|         params: { title: 'updated title' }
 | |
|       expect(response).to have_gitlab_http_status(:ok)
 | |
| 
 | |
|       expect(json_response['title']).to eq('updated title')
 | |
|     end
 | |
| 
 | |
|     it 'returns 404 error if issue iid not found' do
 | |
|       put api("/projects/#{project.id}/issues/44444", user),
 | |
|         params: { title: 'updated title' }
 | |
|       expect(response).to have_gitlab_http_status(:not_found)
 | |
|     end
 | |
| 
 | |
|     it 'returns 404 error if issue id is used instead of the iid' do
 | |
|       put api("/projects/#{project.id}/issues/#{issue.id}", user),
 | |
|         params: { title: 'updated title' }
 | |
|       expect(response).to have_gitlab_http_status(:not_found)
 | |
|     end
 | |
| 
 | |
|     it 'allows special label names' do
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|         params: {
 | |
|           title: 'updated title',
 | |
|           labels: 'label, label?, label&foo, ?, &'
 | |
|         }
 | |
| 
 | |
|       expect(response.status).to eq(200)
 | |
|       expect(json_response['labels']).to include 'label'
 | |
|       expect(json_response['labels']).to include 'label?'
 | |
|       expect(json_response['labels']).to include 'label&foo'
 | |
|       expect(json_response['labels']).to include '?'
 | |
|       expect(json_response['labels']).to include '&'
 | |
|     end
 | |
| 
 | |
|     it 'allows special label names with labels param as array' do
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|         params: {
 | |
|           title: 'updated title',
 | |
|           labels: ['label', 'label?', 'label&foo, ?, &']
 | |
|         }
 | |
| 
 | |
|       expect(response.status).to eq(200)
 | |
|       expect(json_response['labels']).to include 'label'
 | |
|       expect(json_response['labels']).to include 'label?'
 | |
|       expect(json_response['labels']).to include 'label&foo'
 | |
|       expect(json_response['labels']).to include '?'
 | |
|       expect(json_response['labels']).to include '&'
 | |
|     end
 | |
| 
 | |
|     context 'confidential issues' do
 | |
|       it 'returns 403 for non project members' do
 | |
|         put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member),
 | |
|           params: { title: 'updated title' }
 | |
|         expect(response).to have_gitlab_http_status(:forbidden)
 | |
|       end
 | |
| 
 | |
|       it 'returns 403 for project members with guest role' do
 | |
|         put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest),
 | |
|           params: { title: 'updated title' }
 | |
|         expect(response).to have_gitlab_http_status(:forbidden)
 | |
|       end
 | |
| 
 | |
|       it 'updates a confidential issue for project members' do
 | |
|         put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
 | |
|           params: { title: 'updated title' }
 | |
|         expect(response).to have_gitlab_http_status(:ok)
 | |
|         expect(json_response['title']).to eq('updated title')
 | |
|       end
 | |
| 
 | |
|       it 'updates a confidential issue for author' do
 | |
|         put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author),
 | |
|           params: { title: 'updated title' }
 | |
|         expect(response).to have_gitlab_http_status(:ok)
 | |
|         expect(json_response['title']).to eq('updated title')
 | |
|       end
 | |
| 
 | |
|       it 'updates a confidential issue for admin' do
 | |
|         put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin),
 | |
|           params: { title: 'updated title' }
 | |
|         expect(response).to have_gitlab_http_status(:ok)
 | |
|         expect(json_response['title']).to eq('updated title')
 | |
|       end
 | |
| 
 | |
|       it 'sets an issue to confidential' do
 | |
|         put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|           params: { confidential: true }
 | |
| 
 | |
|         expect(response).to have_gitlab_http_status(:ok)
 | |
|         expect(json_response['confidential']).to be_truthy
 | |
|       end
 | |
| 
 | |
|       it 'makes a confidential issue public' do
 | |
|         put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
 | |
|           params: { confidential: false }
 | |
| 
 | |
|         expect(response).to have_gitlab_http_status(:ok)
 | |
|         expect(json_response['confidential']).to be_falsy
 | |
|       end
 | |
| 
 | |
|       it 'does not update a confidential issue with wrong confidential flag' do
 | |
|         put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
 | |
|           params: { confidential: 'foo' }
 | |
| 
 | |
|         expect(response).to have_gitlab_http_status(:bad_request)
 | |
|         expect(json_response['error']).to eq('confidential is invalid')
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe 'PUT /projects/:id/issues/:issue_iid with spam filtering' do
 | |
|     def update_issue
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: params
 | |
|     end
 | |
| 
 | |
|     let(:params) do
 | |
|       {
 | |
|         title: 'updated title',
 | |
|         description: 'content here',
 | |
|         labels: 'label, label2'
 | |
|       }
 | |
|     end
 | |
| 
 | |
|     before do
 | |
|       expect_next_instance_of(Spam::SpamCheckService) do |spam_service|
 | |
|         expect(spam_service).to receive_messages(check_for_spam?: true)
 | |
|       end
 | |
|       expect_next_instance_of(Spam::AkismetService) do |akismet_service|
 | |
|         expect(akismet_service).to receive_messages(spam?: true)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     context 'when allow_possible_spam feature flag is false' do
 | |
|       before do
 | |
|         stub_feature_flags(allow_possible_spam: false)
 | |
|       end
 | |
| 
 | |
|       it 'does not update a project issue' do
 | |
|         expect { update_issue }.not_to change { issue.reload.title }
 | |
|       end
 | |
| 
 | |
|       it 'returns correct status and message' do
 | |
|         update_issue
 | |
| 
 | |
|         expect(response).to have_gitlab_http_status(:bad_request)
 | |
|         expect(json_response).to include('message' => { 'error' => 'Spam detected' })
 | |
|       end
 | |
| 
 | |
|       it 'creates a new spam log entry' do
 | |
|         expect { update_issue }
 | |
|           .to log_spam(title: 'updated title', description: 'content here', user_id: user.id, noteable_type: 'Issue')
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     context 'when allow_possible_spam feature flag is true' do
 | |
|       it 'updates a project issue' do
 | |
|         expect { update_issue }.to change { issue.reload.title }
 | |
|       end
 | |
| 
 | |
|       it 'returns correct status and message' do
 | |
|         update_issue
 | |
| 
 | |
|         expect(response).to have_gitlab_http_status(:ok)
 | |
|       end
 | |
| 
 | |
|       it 'creates a new spam log entry' do
 | |
|         expect { update_issue }
 | |
|           .to log_spam(title: 'updated title', description: 'content here', user_id: user.id, noteable_type: 'Issue')
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe 'PUT /projects/:id/issues/:issue_iid to update assignee' do
 | |
|     context 'support for deprecated assignee_id' do
 | |
|       it 'removes assignee' do
 | |
|         put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|           params: { assignee_id: 0 }
 | |
| 
 | |
|         expect(response).to have_gitlab_http_status(:ok)
 | |
| 
 | |
|         expect(json_response['assignee']).to be_nil
 | |
|       end
 | |
| 
 | |
|       it 'updates an issue with new assignee' do
 | |
|         put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|           params: { assignee_id: user2.id }
 | |
| 
 | |
|         expect(response).to have_gitlab_http_status(:ok)
 | |
| 
 | |
|         expect(json_response['assignee']['name']).to eq(user2.name)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     it 'removes assignee' do
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|         params: { assignee_ids: [0] }
 | |
| 
 | |
|       expect(response).to have_gitlab_http_status(:ok)
 | |
| 
 | |
|       expect(json_response['assignees']).to be_empty
 | |
|     end
 | |
| 
 | |
|     it 'updates an issue with new assignee' do
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|         params: { assignee_ids: [user2.id] }
 | |
| 
 | |
|       expect(response).to have_gitlab_http_status(:ok)
 | |
| 
 | |
|       expect(json_response['assignees'].first['name']).to eq(user2.name)
 | |
|     end
 | |
| 
 | |
|     context 'single assignee restrictions' do
 | |
|       it 'updates an issue with several assignees but only one has been applied' do
 | |
|         put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|           params: { assignee_ids: [user2.id, guest.id] }
 | |
| 
 | |
|         expect(response).to have_gitlab_http_status(:ok)
 | |
| 
 | |
|         expect(json_response['assignees'].size).to eq(1)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe 'PUT /projects/:id/issues/:issue_iid to update labels' do
 | |
|     let!(:label) { create(:label, title: 'dummy', project: project) }
 | |
|     let!(:label_link) { create(:label_link, label: label, target: issue) }
 | |
| 
 | |
|     it 'does not update labels if not present' do
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|         params: { title: 'updated title' }
 | |
|       expect(response).to have_gitlab_http_status(:ok)
 | |
|       expect(json_response['labels']).to eq([label.title])
 | |
|     end
 | |
| 
 | |
|     it 'removes all labels and touches the record' do
 | |
|       Timecop.travel(1.minute.from_now) do
 | |
|         put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: '' }
 | |
|       end
 | |
| 
 | |
|       expect(response).to have_gitlab_http_status(:ok)
 | |
|       expect(json_response['labels']).to eq([])
 | |
|       expect(json_response['updated_at']).to be > Time.now
 | |
|     end
 | |
| 
 | |
|     it 'removes all labels and touches the record with labels param as array' do
 | |
|       Timecop.travel(1.minute.from_now) do
 | |
|         put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: [''] }
 | |
|       end
 | |
| 
 | |
|       expect(response).to have_gitlab_http_status(:ok)
 | |
|       expect(json_response['labels']).to eq([])
 | |
|       expect(json_response['updated_at']).to be > Time.now
 | |
|     end
 | |
| 
 | |
|     it 'updates labels and touches the record' do
 | |
|       Timecop.travel(1.minute.from_now) do
 | |
|         put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|           params: { labels: 'foo,bar' }
 | |
|       end
 | |
|       expect(response).to have_gitlab_http_status(:ok)
 | |
|       expect(json_response['labels']).to include 'foo'
 | |
|       expect(json_response['labels']).to include 'bar'
 | |
|       expect(json_response['updated_at']).to be > Time.now
 | |
|     end
 | |
| 
 | |
|     it 'updates labels and touches the record with labels param as array' do
 | |
|       Timecop.travel(1.minute.from_now) do
 | |
|         put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|           params: { labels: %w(foo bar) }
 | |
|       end
 | |
|       expect(response).to have_gitlab_http_status(:ok)
 | |
|       expect(json_response['labels']).to include 'foo'
 | |
|       expect(json_response['labels']).to include 'bar'
 | |
|       expect(json_response['updated_at']).to be > Time.now
 | |
|     end
 | |
| 
 | |
|     it 'allows special label names' do
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|         params: { labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' }
 | |
|       expect(response.status).to eq(200)
 | |
|       expect(json_response['labels']).to include 'label:foo'
 | |
|       expect(json_response['labels']).to include 'label-bar'
 | |
|       expect(json_response['labels']).to include 'label_bar'
 | |
|       expect(json_response['labels']).to include 'label/bar'
 | |
|       expect(json_response['labels']).to include 'label?bar'
 | |
|       expect(json_response['labels']).to include 'label&bar'
 | |
|       expect(json_response['labels']).to include '?'
 | |
|       expect(json_response['labels']).to include '&'
 | |
|     end
 | |
| 
 | |
|     it 'allows special label names with labels param as array' do
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|         params: { labels: ['label:foo', 'label-bar', 'label_bar', 'label/bar,label?bar,label&bar,?,&'] }
 | |
|       expect(response.status).to eq(200)
 | |
|       expect(json_response['labels']).to include 'label:foo'
 | |
|       expect(json_response['labels']).to include 'label-bar'
 | |
|       expect(json_response['labels']).to include 'label_bar'
 | |
|       expect(json_response['labels']).to include 'label/bar'
 | |
|       expect(json_response['labels']).to include 'label?bar'
 | |
|       expect(json_response['labels']).to include 'label&bar'
 | |
|       expect(json_response['labels']).to include '?'
 | |
|       expect(json_response['labels']).to include '&'
 | |
|     end
 | |
| 
 | |
|     it 'returns 400 if title is too long' do
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|         params: { title: 'g' * 256 }
 | |
|       expect(response).to have_gitlab_http_status(:bad_request)
 | |
|       expect(json_response['message']['title']).to eq([
 | |
|         'is too long (maximum is 255 characters)'
 | |
|       ])
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe 'PUT /projects/:id/issues/:issue_iid to update state and label' do
 | |
|     it 'updates a project issue' do
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|         params: { labels: 'label2', state_event: 'close' }
 | |
|       expect(response).to have_gitlab_http_status(:ok)
 | |
| 
 | |
|       expect(json_response['labels']).to include 'label2'
 | |
|       expect(json_response['state']).to eq 'closed'
 | |
|     end
 | |
| 
 | |
|     it 'reopens a project isssue' do
 | |
|       put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), params: { state_event: 'reopen' }
 | |
| 
 | |
|       expect(response).to have_gitlab_http_status(:ok)
 | |
|       expect(json_response['state']).to eq 'opened'
 | |
|     end
 | |
| 
 | |
|     context 'when an admin or owner makes the request' do
 | |
|       it 'accepts the update date to be set' do
 | |
|         update_time = 2.weeks.ago
 | |
|         put api("/projects/#{project.id}/issues/#{issue.iid}", user),
 | |
|           params: { labels: 'label3', state_event: 'close', updated_at: update_time }
 | |
| 
 | |
|         expect(response).to have_gitlab_http_status(:ok)
 | |
|         expect(json_response['labels']).to include 'label3'
 | |
|         expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe 'PUT /projects/:id/issues/:issue_iid to update due date' do
 | |
|     it 'creates a new project issue' do
 | |
|       due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
 | |
| 
 | |
|       put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { due_date: due_date }
 | |
| 
 | |
|       expect(response).to have_gitlab_http_status(:ok)
 | |
|       expect(json_response['due_date']).to eq(due_date)
 | |
|     end
 | |
|   end
 | |
| end
 |