b02a6bed85
On epics roadmap page we list all epics in the given time frame without pagination (at least for the first iteration), in this case it would be nice to use the existing issuables index logic except pagination (see MR gitlab-ee!4281). For this reason this patch allows to easily disable pagination. Related gitlab-ee!4281
1013 lines
30 KiB
Ruby
1013 lines
30 KiB
Ruby
require('spec_helper')
|
|
|
|
describe Projects::IssuesController do
|
|
let(:project) { create(:project) }
|
|
let(:user) { create(:user) }
|
|
let(:issue) { create(:issue, project: project) }
|
|
|
|
describe "GET #index" do
|
|
context 'external issue tracker' do
|
|
before do
|
|
sign_in(user)
|
|
project.add_developer(user)
|
|
create(:jira_service, project: project)
|
|
end
|
|
|
|
context 'when GitLab issues disabled' do
|
|
it 'returns 404 status' do
|
|
project.issues_enabled = false
|
|
project.save!
|
|
|
|
get :index, namespace_id: project.namespace, project_id: project
|
|
|
|
expect(response).to have_gitlab_http_status(404)
|
|
end
|
|
end
|
|
|
|
context 'when GitLab issues enabled' do
|
|
it 'renders the "index" template' do
|
|
get :index, namespace_id: project.namespace, project_id: project
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
|
expect(response).to render_template(:index)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'internal issue tracker' do
|
|
before do
|
|
sign_in(user)
|
|
project.add_developer(user)
|
|
end
|
|
|
|
it_behaves_like "issuables list meta-data", :issue
|
|
|
|
it "returns index" do
|
|
get :index, namespace_id: project.namespace, project_id: project
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
|
end
|
|
|
|
it "returns 301 if request path doesn't match project path" do
|
|
get :index, namespace_id: project.namespace, project_id: project.path.upcase
|
|
|
|
expect(response).to redirect_to(project_issues_path(project))
|
|
end
|
|
|
|
it "returns 404 when issues are disabled" do
|
|
project.issues_enabled = false
|
|
project.save!
|
|
|
|
get :index, namespace_id: project.namespace, project_id: project
|
|
expect(response).to have_gitlab_http_status(404)
|
|
end
|
|
end
|
|
|
|
context 'with page param' do
|
|
let(:last_page) { project.issues.page().total_pages }
|
|
let!(:issue_list) { create_list(:issue, 2, project: project) }
|
|
|
|
before do
|
|
sign_in(user)
|
|
project.add_developer(user)
|
|
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
|
|
end
|
|
|
|
it 'redirects to last_page if page number is larger than number of pages' do
|
|
get :index,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
page: (last_page + 1).to_param
|
|
|
|
expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
|
|
end
|
|
|
|
it 'redirects to specified page' do
|
|
get :index,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
page: last_page.to_param
|
|
|
|
expect(assigns(:issues).current_page).to eq(last_page)
|
|
expect(response).to have_gitlab_http_status(200)
|
|
end
|
|
|
|
it 'does not redirect to external sites when provided a host field' do
|
|
external_host = "www.example.com"
|
|
get :index,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
page: (last_page + 1).to_param,
|
|
host: external_host
|
|
|
|
expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
|
|
end
|
|
|
|
it 'does not use pagination if disabled' do
|
|
allow(controller).to receive(:pagination_disabled?).and_return(true)
|
|
|
|
get :index,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
page: (last_page + 1).to_param
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
|
expect(assigns(:issues).size).to eq(2)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET #new' do
|
|
it 'redirects to signin if not logged in' do
|
|
get :new, namespace_id: project.namespace, project_id: project
|
|
|
|
expect(flash[:notice]).to eq 'Please sign in to create the new issue.'
|
|
expect(response).to redirect_to(new_user_session_path)
|
|
end
|
|
|
|
context 'internal issue tracker' do
|
|
before do
|
|
sign_in(user)
|
|
project.add_developer(user)
|
|
end
|
|
|
|
it 'builds a new issue' do
|
|
get :new, namespace_id: project.namespace, project_id: project
|
|
|
|
expect(assigns(:issue)).to be_a_new(Issue)
|
|
end
|
|
|
|
it 'fills in an issue for a merge request' do
|
|
project_with_repository = create(:project, :repository)
|
|
project_with_repository.add_developer(user)
|
|
mr = create(:merge_request_with_diff_notes, source_project: project_with_repository)
|
|
|
|
get :new, namespace_id: project_with_repository.namespace, project_id: project_with_repository, merge_request_to_resolve_discussions_of: mr.iid
|
|
|
|
expect(assigns(:issue).title).not_to be_empty
|
|
expect(assigns(:issue).description).not_to be_empty
|
|
end
|
|
|
|
it 'fills in an issue for a discussion' do
|
|
note = create(:note_on_merge_request, project: project)
|
|
|
|
get :new, namespace_id: project.namespace.path, project_id: project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id
|
|
|
|
expect(assigns(:issue).title).not_to be_empty
|
|
expect(assigns(:issue).description).not_to be_empty
|
|
end
|
|
end
|
|
|
|
context 'external issue tracker' do
|
|
let!(:service) do
|
|
create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker', new_issue_url: 'http://test.com')
|
|
end
|
|
|
|
before do
|
|
sign_in(user)
|
|
project.add_developer(user)
|
|
|
|
external = double
|
|
allow(project).to receive(:external_issue_tracker).and_return(external)
|
|
end
|
|
|
|
context 'when GitLab issues disabled' do
|
|
it 'returns 404 status' do
|
|
project.issues_enabled = false
|
|
project.save!
|
|
|
|
get :new, namespace_id: project.namespace, project_id: project
|
|
|
|
expect(response).to have_gitlab_http_status(404)
|
|
end
|
|
end
|
|
|
|
context 'when GitLab issues enabled' do
|
|
it 'renders the "new" template' do
|
|
get :new, namespace_id: project.namespace, project_id: project
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
|
expect(response).to render_template(:new)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'Redirect after sign in' do
|
|
context 'with an AJAX request' do
|
|
it 'does not store the visited URL' do
|
|
xhr :get,
|
|
:show,
|
|
format: :json,
|
|
namespace_id: project.namespace,
|
|
project_id: project,
|
|
id: issue.iid
|
|
|
|
expect(session['user_return_to']).to be_blank
|
|
end
|
|
end
|
|
|
|
context 'without an AJAX request' do
|
|
it 'stores the visited URL' do
|
|
get :show,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
id: issue.iid
|
|
|
|
expect(session['user_return_to']).to eq("/#{project.namespace.to_param}/#{project.to_param}/issues/#{issue.iid}")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST #move' do
|
|
before do
|
|
sign_in(user)
|
|
project.add_developer(user)
|
|
end
|
|
|
|
context 'when moving issue to another private project' do
|
|
let(:another_project) { create(:project, :private) }
|
|
|
|
context 'when user has access to move issue' do
|
|
before do
|
|
another_project.add_reporter(user)
|
|
end
|
|
|
|
it 'moves issue to another project' do
|
|
move_issue
|
|
|
|
expect(response).to have_gitlab_http_status :ok
|
|
expect(another_project.issues).not_to be_empty
|
|
end
|
|
end
|
|
|
|
context 'when user does not have access to move issue' do
|
|
it 'responds with 404' do
|
|
move_issue
|
|
|
|
expect(response).to have_gitlab_http_status :not_found
|
|
end
|
|
end
|
|
|
|
def move_issue
|
|
post :move,
|
|
format: :json,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
id: issue.iid,
|
|
move_to_project_id: another_project.id
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'PUT #update' do
|
|
subject do
|
|
put :update,
|
|
namespace_id: project.namespace,
|
|
project_id: project,
|
|
id: issue.to_param,
|
|
issue: { title: 'New title' }, format: :json
|
|
end
|
|
|
|
before do
|
|
sign_in(user)
|
|
end
|
|
|
|
context 'when user has access to update issue' do
|
|
before do
|
|
project.add_developer(user)
|
|
end
|
|
|
|
it 'updates the issue' do
|
|
subject
|
|
|
|
expect(response).to have_http_status(:ok)
|
|
expect(issue.reload.title).to eq('New title')
|
|
end
|
|
|
|
context 'when Akismet is enabled and the issue is identified as spam' do
|
|
before do
|
|
stub_application_setting(recaptcha_enabled: true)
|
|
allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
|
|
allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true)
|
|
end
|
|
|
|
it 'renders json with recaptcha_html' do
|
|
subject
|
|
|
|
expect(JSON.parse(response.body)).to have_key('recaptcha_html')
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when user does not have access to update issue' do
|
|
before do
|
|
project.add_guest(user)
|
|
end
|
|
|
|
it 'responds with 404' do
|
|
subject
|
|
|
|
expect(response).to have_http_status(:not_found)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET #realtime_changes' do
|
|
def go(id:)
|
|
get :realtime_changes,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
id: id
|
|
end
|
|
|
|
context 'when an issue was edited' do
|
|
before do
|
|
project.add_developer(user)
|
|
|
|
issue.update!(last_edited_by: user, last_edited_at: issue.created_at + 1.minute)
|
|
|
|
sign_in(user)
|
|
end
|
|
|
|
it 'returns last edited time' do
|
|
go(id: issue.iid)
|
|
|
|
data = JSON.parse(response.body)
|
|
|
|
expect(data).to include('updated_at')
|
|
expect(data['updated_at']).to eq(issue.last_edited_at.to_time.iso8601)
|
|
end
|
|
end
|
|
|
|
context 'when an issue was edited by a deleted user' do
|
|
let(:deleted_user) { create(:user) }
|
|
|
|
before do
|
|
project.add_developer(user)
|
|
|
|
issue.update!(last_edited_by: deleted_user, last_edited_at: Time.now)
|
|
|
|
deleted_user.destroy
|
|
sign_in(user)
|
|
end
|
|
|
|
it 'returns 200' do
|
|
go(id: issue.iid)
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'Confidential Issues' do
|
|
let(:project) { create(:project_empty_repo, :public) }
|
|
let(:assignee) { create(:assignee) }
|
|
let(:author) { create(:user) }
|
|
let(:non_member) { create(:user) }
|
|
let(:member) { create(:user) }
|
|
let(:admin) { create(:admin) }
|
|
let!(:issue) { create(:issue, project: project) }
|
|
let!(:unescaped_parameter_value) { create(:issue, :confidential, project: project, author: author) }
|
|
let!(:request_forgery_timing_attack) { create(:issue, :confidential, project: project, assignees: [assignee]) }
|
|
|
|
describe 'GET #index' do
|
|
it 'does not list confidential issues for guests' do
|
|
sign_out(:user)
|
|
get_issues
|
|
|
|
expect(assigns(:issues)).to eq [issue]
|
|
end
|
|
|
|
it 'does not list confidential issues for non project members' do
|
|
sign_in(non_member)
|
|
get_issues
|
|
|
|
expect(assigns(:issues)).to eq [issue]
|
|
end
|
|
|
|
it 'does not list confidential issues for project members with guest role' do
|
|
sign_in(member)
|
|
project.add_guest(member)
|
|
|
|
get_issues
|
|
|
|
expect(assigns(:issues)).to eq [issue]
|
|
end
|
|
|
|
it 'lists confidential issues for author' do
|
|
sign_in(author)
|
|
get_issues
|
|
|
|
expect(assigns(:issues)).to include unescaped_parameter_value
|
|
expect(assigns(:issues)).not_to include request_forgery_timing_attack
|
|
end
|
|
|
|
it 'lists confidential issues for assignee' do
|
|
sign_in(assignee)
|
|
get_issues
|
|
|
|
expect(assigns(:issues)).not_to include unescaped_parameter_value
|
|
expect(assigns(:issues)).to include request_forgery_timing_attack
|
|
end
|
|
|
|
it 'lists confidential issues for project members' do
|
|
sign_in(member)
|
|
project.add_developer(member)
|
|
|
|
get_issues
|
|
|
|
expect(assigns(:issues)).to include unescaped_parameter_value
|
|
expect(assigns(:issues)).to include request_forgery_timing_attack
|
|
end
|
|
|
|
it 'lists confidential issues for admin' do
|
|
sign_in(admin)
|
|
get_issues
|
|
|
|
expect(assigns(:issues)).to include unescaped_parameter_value
|
|
expect(assigns(:issues)).to include request_forgery_timing_attack
|
|
end
|
|
|
|
def get_issues
|
|
get :index,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project
|
|
end
|
|
end
|
|
|
|
shared_examples_for 'restricted action' do |http_status|
|
|
it 'returns 404 for guests' do
|
|
sign_out(:user)
|
|
go(id: unescaped_parameter_value.to_param)
|
|
|
|
expect(response).to have_gitlab_http_status :not_found
|
|
end
|
|
|
|
it 'returns 404 for non project members' do
|
|
sign_in(non_member)
|
|
go(id: unescaped_parameter_value.to_param)
|
|
|
|
expect(response).to have_gitlab_http_status :not_found
|
|
end
|
|
|
|
it 'returns 404 for project members with guest role' do
|
|
sign_in(member)
|
|
project.add_guest(member)
|
|
go(id: unescaped_parameter_value.to_param)
|
|
|
|
expect(response).to have_gitlab_http_status :not_found
|
|
end
|
|
|
|
it "returns #{http_status[:success]} for author" do
|
|
sign_in(author)
|
|
go(id: unescaped_parameter_value.to_param)
|
|
|
|
expect(response).to have_gitlab_http_status http_status[:success]
|
|
end
|
|
|
|
it "returns #{http_status[:success]} for assignee" do
|
|
sign_in(assignee)
|
|
go(id: request_forgery_timing_attack.to_param)
|
|
|
|
expect(response).to have_gitlab_http_status http_status[:success]
|
|
end
|
|
|
|
it "returns #{http_status[:success]} for project members" do
|
|
sign_in(member)
|
|
project.add_developer(member)
|
|
go(id: unescaped_parameter_value.to_param)
|
|
|
|
expect(response).to have_gitlab_http_status http_status[:success]
|
|
end
|
|
|
|
it "returns #{http_status[:success]} for admin" do
|
|
sign_in(admin)
|
|
go(id: unescaped_parameter_value.to_param)
|
|
|
|
expect(response).to have_gitlab_http_status http_status[:success]
|
|
end
|
|
end
|
|
|
|
describe 'PUT #update' do
|
|
def update_issue(issue_params: {}, additional_params: {}, id: nil)
|
|
id ||= issue.iid
|
|
params = {
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
id: id,
|
|
issue: { title: 'New title' }.merge(issue_params),
|
|
format: :json
|
|
}.merge(additional_params)
|
|
|
|
put :update, params
|
|
end
|
|
|
|
def go(id:)
|
|
update_issue(id: id)
|
|
end
|
|
|
|
before do
|
|
sign_in(user)
|
|
project.add_developer(user)
|
|
end
|
|
|
|
it_behaves_like 'restricted action', success: 200
|
|
it_behaves_like 'update invalid issuable', Issue
|
|
|
|
context 'changing the assignee' do
|
|
it 'limits the attributes exposed on the assignee' do
|
|
assignee = create(:user)
|
|
project.add_developer(assignee)
|
|
|
|
update_issue(issue_params: { assignee_ids: [assignee.id] })
|
|
|
|
body = JSON.parse(response.body)
|
|
|
|
expect(body['assignees'].first.keys)
|
|
.to match_array(%w(id name username avatar_url state web_url))
|
|
end
|
|
end
|
|
|
|
context 'Akismet is enabled' do
|
|
before do
|
|
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
|
stub_application_setting(recaptcha_enabled: true)
|
|
allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
|
|
end
|
|
|
|
context 'when an issue is not identified as spam' do
|
|
before do
|
|
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
|
|
allow_any_instance_of(AkismetService).to receive(:spam?).and_return(false)
|
|
end
|
|
|
|
it 'normally updates the issue' do
|
|
expect { update_issue(issue_params: { title: 'Foo' }) }.to change { issue.reload.title }.to('Foo')
|
|
end
|
|
end
|
|
|
|
context 'when an issue is identified as spam' do
|
|
before do
|
|
allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true)
|
|
end
|
|
|
|
context 'when captcha is not verified' do
|
|
before do
|
|
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
|
|
end
|
|
|
|
it 'rejects an issue recognized as a spam' do
|
|
expect { update_issue }.not_to change { issue.reload.title }
|
|
end
|
|
|
|
it 'rejects an issue recognized as a spam when recaptcha disabled' do
|
|
stub_application_setting(recaptcha_enabled: false)
|
|
|
|
expect { update_issue }.not_to change { issue.reload.title }
|
|
end
|
|
|
|
it 'creates a spam log' do
|
|
update_issue(issue_params: { title: 'Spam title' })
|
|
|
|
spam_logs = SpamLog.all
|
|
|
|
expect(spam_logs.count).to eq(1)
|
|
expect(spam_logs.first.title).to eq('Spam title')
|
|
expect(spam_logs.first.recaptcha_verified).to be_falsey
|
|
end
|
|
|
|
it 'renders recaptcha_html json response' do
|
|
update_issue
|
|
|
|
expect(json_response).to have_key('recaptcha_html')
|
|
end
|
|
|
|
it 'returns 200 status' do
|
|
update_issue
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
|
end
|
|
end
|
|
|
|
context 'when captcha is verified' do
|
|
let(:spammy_title) { 'Whatever' }
|
|
let!(:spam_logs) { create_list(:spam_log, 2, user: user, title: spammy_title) }
|
|
|
|
def update_verified_issue
|
|
update_issue(
|
|
issue_params: { title: spammy_title },
|
|
additional_params: { spam_log_id: spam_logs.last.id, recaptcha_verification: true })
|
|
end
|
|
|
|
before do
|
|
allow_any_instance_of(described_class).to receive(:verify_recaptcha)
|
|
.and_return(true)
|
|
end
|
|
|
|
it 'returns 200 status' do
|
|
expect(response).to have_gitlab_http_status(200)
|
|
end
|
|
|
|
it 'accepts an issue after recaptcha is verified' do
|
|
expect { update_verified_issue }.to change { issue.reload.title }.to(spammy_title)
|
|
end
|
|
|
|
it 'marks spam log as recaptcha_verified' do
|
|
expect { update_verified_issue }.to change { SpamLog.last.recaptcha_verified }.from(false).to(true)
|
|
end
|
|
|
|
it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do
|
|
spam_log = create(:spam_log)
|
|
|
|
expect { update_issue(issue_params: { spam_log_id: spam_log.id, recaptcha_verification: true }) }
|
|
.not_to change { SpamLog.last.recaptcha_verified }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET #show' do
|
|
it_behaves_like 'restricted action', success: 200
|
|
|
|
def go(id:)
|
|
get :show,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
id: id
|
|
end
|
|
end
|
|
|
|
describe 'GET #realtime_changes' do
|
|
it_behaves_like 'restricted action', success: 200
|
|
|
|
def go(id:)
|
|
get :realtime_changes,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
id: id
|
|
end
|
|
end
|
|
|
|
describe 'GET #edit' do
|
|
it_behaves_like 'restricted action', success: 200
|
|
|
|
def go(id:)
|
|
get :edit,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
id: id
|
|
end
|
|
end
|
|
|
|
describe 'PUT #update' do
|
|
it_behaves_like 'restricted action', success: 302
|
|
|
|
def go(id:)
|
|
put :update,
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
id: id,
|
|
issue: { title: 'New title' }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST #create' do
|
|
def post_new_issue(issue_attrs = {}, additional_params = {})
|
|
sign_in(user)
|
|
project = create(:project, :public)
|
|
project.add_developer(user)
|
|
|
|
post :create, {
|
|
namespace_id: project.namespace.to_param,
|
|
project_id: project,
|
|
issue: { title: 'Title', description: 'Description' }.merge(issue_attrs)
|
|
}.merge(additional_params)
|
|
|
|
project.issues.first
|
|
end
|
|
|
|
context 'resolving discussions in MergeRequest' do
|
|
let(:discussion) { create(:diff_note_on_merge_request).to_discussion }
|
|
let(:merge_request) { discussion.noteable }
|
|
let(:project) { merge_request.source_project }
|
|
|
|
before do
|
|
project.add_master(user)
|
|
sign_in user
|
|
end
|
|
|
|
let(:merge_request_params) do
|
|
{ merge_request_to_resolve_discussions_of: merge_request.iid }
|
|
end
|
|
|
|
def post_issue(issue_params, other_params: {})
|
|
post :create, { namespace_id: project.namespace.to_param, project_id: project, issue: issue_params, merge_request_to_resolve_discussions_of: merge_request.iid }.merge(other_params)
|
|
end
|
|
|
|
it 'creates an issue for the project' do
|
|
expect { post_issue({ title: 'Hello' }) }.to change { project.issues.reload.size }.by(1)
|
|
end
|
|
|
|
it "doesn't overwrite given params" do
|
|
post_issue(description: 'Manually entered description')
|
|
|
|
expect(assigns(:issue).description).to eq('Manually entered description')
|
|
end
|
|
|
|
it 'resolves the discussion in the merge_request' do
|
|
post_issue(title: 'Hello')
|
|
discussion.first_note.reload
|
|
|
|
expect(discussion.resolved?).to eq(true)
|
|
end
|
|
|
|
it 'sets a flash message' do
|
|
post_issue(title: 'Hello')
|
|
|
|
expect(flash[:notice]).to eq('Resolved all discussions.')
|
|
end
|
|
|
|
describe "resolving a single discussion" do
|
|
before do
|
|
post_issue({ title: 'Hello' }, other_params: { discussion_to_resolve: discussion.id })
|
|
end
|
|
it 'resolves a single discussion' do
|
|
discussion.first_note.reload
|
|
|
|
expect(discussion.resolved?).to eq(true)
|
|
end
|
|
|
|
it 'sets a flash message that one discussion was resolved' do
|
|
expect(flash[:notice]).to eq('Resolved 1 discussion.')
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'Akismet is enabled' do
|
|
before do
|
|
stub_application_setting(recaptcha_enabled: true)
|
|
allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
|
|
end
|
|
|
|
context 'when an issue is not identified as spam' do
|
|
before do
|
|
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
|
|
allow_any_instance_of(AkismetService).to receive(:spam?).and_return(false)
|
|
end
|
|
|
|
it 'does not create an issue' do
|
|
expect { post_new_issue(title: '') }.not_to change(Issue, :count)
|
|
end
|
|
end
|
|
|
|
context 'when an issue is identified as spam' do
|
|
before do
|
|
allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true)
|
|
end
|
|
|
|
context 'when captcha is not verified' do
|
|
def post_spam_issue
|
|
post_new_issue(title: 'Spam Title', description: 'Spam lives here')
|
|
end
|
|
|
|
before do
|
|
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
|
|
end
|
|
|
|
it 'rejects an issue recognized as a spam' do
|
|
expect { post_spam_issue }.not_to change(Issue, :count)
|
|
end
|
|
|
|
it 'creates a spam log' do
|
|
post_spam_issue
|
|
spam_logs = SpamLog.all
|
|
|
|
expect(spam_logs.count).to eq(1)
|
|
expect(spam_logs.first.title).to eq('Spam Title')
|
|
expect(spam_logs.first.recaptcha_verified).to be_falsey
|
|
end
|
|
|
|
it 'does not create an issue when it is not valid' do
|
|
expect { post_new_issue(title: '') }.not_to change(Issue, :count)
|
|
end
|
|
|
|
it 'does not create an issue when recaptcha is not enabled' do
|
|
stub_application_setting(recaptcha_enabled: false)
|
|
|
|
expect { post_spam_issue }.not_to change(Issue, :count)
|
|
end
|
|
end
|
|
|
|
context 'when captcha is verified' do
|
|
let!(:spam_logs) { create_list(:spam_log, 2, user: user, title: 'Title') }
|
|
|
|
def post_verified_issue
|
|
post_new_issue({}, { spam_log_id: spam_logs.last.id, recaptcha_verification: true } )
|
|
end
|
|
|
|
before do
|
|
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(true)
|
|
end
|
|
|
|
it 'accepts an issue after recaptcha is verified' do
|
|
expect { post_verified_issue }.to change(Issue, :count)
|
|
end
|
|
|
|
it 'marks spam log as recaptcha_verified' do
|
|
expect { post_verified_issue }.to change { SpamLog.last.recaptcha_verified }.from(false).to(true)
|
|
end
|
|
|
|
it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do
|
|
spam_log = create(:spam_log)
|
|
|
|
expect { post_new_issue({}, { spam_log_id: spam_log.id, recaptcha_verification: true } ) }
|
|
.not_to change { SpamLog.last.recaptcha_verified }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'user agent details are saved' do
|
|
before do
|
|
request.env['action_dispatch.remote_ip'] = '127.0.0.1'
|
|
end
|
|
|
|
it 'creates a user agent detail' do
|
|
expect { post_new_issue }.to change(UserAgentDetail, :count).by(1)
|
|
end
|
|
end
|
|
|
|
context 'when description has quick actions' do
|
|
before do
|
|
sign_in(user)
|
|
end
|
|
|
|
it 'can add spent time' do
|
|
issue = post_new_issue(description: '/spend 1h')
|
|
|
|
expect(issue.total_time_spent).to eq(3600)
|
|
end
|
|
|
|
it 'can set the time estimate' do
|
|
issue = post_new_issue(description: '/estimate 2h')
|
|
|
|
expect(issue.time_estimate).to eq(7200)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST #mark_as_spam' do
|
|
context 'properly submits to Akismet' do
|
|
before do
|
|
allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
|
|
allow_any_instance_of(ApplicationSetting).to receive_messages(akismet_enabled: true)
|
|
end
|
|
|
|
def post_spam
|
|
admin = create(:admin)
|
|
create(:user_agent_detail, subject: issue)
|
|
project.add_master(admin)
|
|
sign_in(admin)
|
|
post :mark_as_spam, {
|
|
namespace_id: project.namespace,
|
|
project_id: project,
|
|
id: issue.iid
|
|
}
|
|
end
|
|
|
|
it 'updates issue' do
|
|
post_spam
|
|
expect(issue.submittable_as_spam?).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "DELETE #destroy" do
|
|
context "when the user is a developer" do
|
|
before do
|
|
sign_in(user)
|
|
end
|
|
|
|
it "rejects a developer to destroy an issue" do
|
|
delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
|
|
expect(response).to have_gitlab_http_status(404)
|
|
end
|
|
end
|
|
|
|
context "when the user is owner" do
|
|
let(:owner) { create(:user) }
|
|
let(:namespace) { create(:namespace, owner: owner) }
|
|
let(:project) { create(:project, namespace: namespace) }
|
|
|
|
before do
|
|
sign_in(owner)
|
|
end
|
|
|
|
it "deletes the issue" do
|
|
delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
|
|
|
|
expect(response).to have_gitlab_http_status(302)
|
|
expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./)
|
|
end
|
|
|
|
it 'delegates the update of the todos count cache to TodoService' do
|
|
expect_any_instance_of(TodoService).to receive(:destroy_target).with(issue).once
|
|
|
|
delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST #toggle_award_emoji' do
|
|
before do
|
|
sign_in(user)
|
|
project.add_developer(user)
|
|
end
|
|
|
|
it "toggles the award emoji" do
|
|
expect do
|
|
post(:toggle_award_emoji, namespace_id: project.namespace,
|
|
project_id: project, id: issue.iid, name: "thumbsup")
|
|
end.to change { issue.award_emoji.count }.by(1)
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
|
end
|
|
end
|
|
|
|
describe 'POST create_merge_request' do
|
|
let(:project) { create(:project, :repository) }
|
|
|
|
before do
|
|
project.add_developer(user)
|
|
sign_in(user)
|
|
end
|
|
|
|
it 'creates a new merge request' do
|
|
expect { create_merge_request }.to change(project.merge_requests, :count).by(1)
|
|
end
|
|
|
|
it 'render merge request as json' do
|
|
create_merge_request
|
|
|
|
expect(response).to match_response_schema('merge_request')
|
|
end
|
|
|
|
def create_merge_request
|
|
post :create_merge_request, namespace_id: project.namespace.to_param,
|
|
project_id: project.to_param,
|
|
id: issue.to_param,
|
|
format: :json
|
|
end
|
|
end
|
|
|
|
describe 'GET #discussions' do
|
|
let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
|
|
context 'when authenticated' do
|
|
before do
|
|
project.add_developer(user)
|
|
sign_in(user)
|
|
end
|
|
|
|
it 'returns discussion json' do
|
|
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
|
|
|
|
expect(json_response.first.keys).to match_array(%w[id reply_id expanded notes individual_note])
|
|
end
|
|
|
|
context 'with cross-reference system note', :request_store do
|
|
let(:new_issue) { create(:issue) }
|
|
let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" }
|
|
|
|
before do
|
|
create(:discussion_note_on_issue, :system, noteable: issue, project: issue.project, note: cross_reference)
|
|
end
|
|
|
|
it 'filters notes that the user should not see' do
|
|
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
|
|
|
|
expect(JSON.parse(response.body).count).to eq(1)
|
|
end
|
|
|
|
it 'does not result in N+1 queries' do
|
|
# Instantiate the controller variables to ensure QueryRecorder has an accurate base count
|
|
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
|
|
|
|
RequestStore.clear!
|
|
|
|
control_count = ActiveRecord::QueryRecorder.new do
|
|
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
|
|
end.count
|
|
|
|
RequestStore.clear!
|
|
|
|
create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference)
|
|
|
|
expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }.not_to exceed_query_limit(control_count)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|