diff options
Diffstat (limited to 'spec/features')
151 files changed, 8431 insertions, 2428 deletions
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb new file mode 100644 index 00000000000..1e11fb756b2 --- /dev/null +++ b/spec/features/abuse_report_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +feature 'Abuse reports', feature: true do + let(:another_user) { create(:user) } + + before do + login_as :user + end + + scenario 'Report abuse' do + visit user_path(another_user) + + click_link 'Report abuse' + + fill_in 'abuse_report_message', with: 'This user send spam' + click_button 'Send report' + + expect(page).to have_content 'Thank you for your report' + + visit user_path(another_user) + + expect(page).to have_button("Already reported for abuse") + end +end diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb index c1731e6414a..7fcfe5a54c7 100644 --- a/spec/features/admin/admin_abuse_reports_spec.rb +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -4,17 +4,21 @@ describe "Admin::AbuseReports", feature: true, js: true do let(:user) { create(:user) } context 'as an admin' do + before do + login_as :admin + end + describe 'if a user has been reported for abuse' do - before do - create(:abuse_report, user: user) - login_as :admin - end + let!(:abuse_report) { create(:abuse_report, user: user) } describe 'in the abuse report view' do - it "presents a link to the user's profile" do + it 'presents information about abuse report' do visit admin_abuse_reports_path - expect(page).to have_link user.name, href: user_path(user) + expect(page).to have_content('Abuse Reports') + expect(page).to have_content(abuse_report.message) + expect(page).to have_link(user.name, href: user_path(user)) + expect(page).to have_link('Remove user') end end diff --git a/spec/features/admin/admin_active_tab_spec.rb b/spec/features/admin/admin_active_tab_spec.rb new file mode 100644 index 00000000000..16064d60ce2 --- /dev/null +++ b/spec/features/admin/admin_active_tab_spec.rb @@ -0,0 +1,90 @@ +require 'spec_helper' + +RSpec.describe 'admin active tab' do + before do + login_as :admin + end + + shared_examples 'page has active tab' do |title| + it "activates #{title} tab" do + expect(page).to have_selector('.layout-nav .nav-links > li.active', count: 1) + expect(page.find('.layout-nav li.active')).to have_content(title) + end + end + + shared_examples 'page has active sub tab' do |title| + it "activates #{title} sub tab" do + expect(page).to have_selector('.sub-nav li.active', count: 1) + expect(page.find('.sub-nav li.active')).to have_content(title) + end + end + + context 'on home page' do + before do + visit admin_root_path + end + + it_behaves_like 'page has active tab', 'Overview' + end + + context 'on projects' do + before do + visit admin_projects_path + end + + it_behaves_like 'page has active tab', 'Overview' + it_behaves_like 'page has active sub tab', 'Projects' + end + + context 'on groups' do + before do + visit admin_groups_path + end + + it_behaves_like 'page has active tab', 'Overview' + it_behaves_like 'page has active sub tab', 'Groups' + end + + context 'on users' do + before do + visit admin_users_path + end + + it_behaves_like 'page has active tab', 'Overview' + it_behaves_like 'page has active sub tab', 'Users' + end + + context 'on logs' do + before do + visit admin_logs_path + end + + it_behaves_like 'page has active tab', 'Monitoring' + it_behaves_like 'page has active sub tab', 'Logs' + end + + context 'on messages' do + before do + visit admin_broadcast_messages_path + end + + it_behaves_like 'page has active tab', 'Messages' + end + + context 'on hooks' do + before do + visit admin_hooks_path + end + + it_behaves_like 'page has active tab', 'Hooks' + end + + context 'on background jobs' do + before do + visit admin_background_jobs_path + end + + it_behaves_like 'page has active tab', 'Monitoring' + it_behaves_like 'page has active sub tab', 'Background Jobs' + end +end diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb new file mode 100644 index 00000000000..96d715ef383 --- /dev/null +++ b/spec/features/admin/admin_appearance_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' + +feature 'Admin Appearance', feature: true do + let!(:appearance) { create(:appearance) } + + scenario 'Create new appearance' do + login_as :admin + visit admin_appearances_path + + fill_in 'appearance_title', with: 'MyCompany' + fill_in 'appearance_description', with: 'dev server' + click_button 'Save' + + expect(current_path).to eq admin_appearances_path + expect(page).to have_content 'Appearance settings' + + expect(page).to have_field('appearance_title', with: 'MyCompany') + expect(page).to have_field('appearance_description', with: 'dev server') + expect(page).to have_content 'Last edit' + end + + scenario 'Preview appearance' do + login_as :admin + + visit admin_appearances_path + click_link "Preview" + + expect_page_has_custom_appearance(appearance) + end + + scenario 'Custom sign-in page' do + visit new_user_session_path + expect_page_has_custom_appearance(appearance) + end + + scenario 'Appearance logo' do + login_as :admin + visit admin_appearances_path + + attach_file(:appearance_logo, logo_fixture) + click_button 'Save' + expect(page).to have_css(logo_selector) + + click_link 'Remove logo' + expect(page).not_to have_css(logo_selector) + end + + scenario 'Header logos' do + login_as :admin + visit admin_appearances_path + + attach_file(:appearance_header_logo, logo_fixture) + click_button 'Save' + expect(page).to have_css(header_logo_selector) + + click_link 'Remove header logo' + expect(page).not_to have_css(header_logo_selector) + end + + def expect_page_has_custom_appearance(appearance) + expect(page).to have_content appearance.title + expect(page).to have_content appearance.description + end + + def logo_selector + '//img[@src^="/uploads/appearance/logo"]' + end + + def header_logo_selector + '//img[@src^="/uploads/appearance/header_logo"]' + end + + def logo_fixture + Rails.root.join('spec', 'fixtures', 'dk.png') + end +end diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb new file mode 100644 index 00000000000..bc957ec72e1 --- /dev/null +++ b/spec/features/admin/admin_broadcast_messages_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +feature 'Admin Broadcast Messages', feature: true do + before do + login_as :admin + create(:broadcast_message, :expired, message: 'Migration to new server') + visit admin_broadcast_messages_path + end + + scenario 'See broadcast messages list' do + expect(page).to have_content 'Migration to new server' + end + + scenario 'Create a customized broadcast message' do + fill_in 'broadcast_message_message', with: 'Application update from **4:00 CST to 5:00 CST**' + fill_in 'broadcast_message_color', with: '#f2dede' + fill_in 'broadcast_message_font', with: '#b94a48' + select Date.today.next_year.year, from: 'broadcast_message_ends_at_1i' + click_button 'Add broadcast message' + + expect(current_path).to eq admin_broadcast_messages_path + expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST' + expect(page).to have_selector 'strong', text: '4:00 CST to 5:00 CST' + expect(page).to have_selector %(div[style="background-color: #f2dede; color: #b94a48"]) + end + + scenario 'Edit an existing broadcast message' do + click_link 'Edit' + fill_in 'broadcast_message_message', with: 'Application update RIGHT NOW' + click_button 'Update broadcast message' + + expect(current_path).to eq admin_broadcast_messages_path + expect(page).to have_content 'Application update RIGHT NOW' + end + + scenario 'Remove an existing broadcast message' do + click_link 'Remove' + + expect(current_path).to eq admin_broadcast_messages_path + expect(page).not_to have_content 'Migration to new server' + end + + scenario 'Live preview a customized broadcast message', js: true do + fill_in 'broadcast_message_message', with: "Live **Markdown** previews. :tada:" + + page.within('.broadcast-message-preview') do + expect(page).to have_selector('strong', text: 'Markdown') + expect(page).to have_selector('img.emoji') + end + end +end diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb new file mode 100644 index 00000000000..562ace92598 --- /dev/null +++ b/spec/features/admin/admin_browse_spam_logs_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'Admin browse spam logs' do + let!(:spam_log) { create(:spam_log) } + + before do + login_as :admin + end + + scenario 'Browse spam logs' do + visit admin_spam_logs_path + + expect(page).to have_content('Spam Logs') + expect(page).to have_content(spam_log.source_ip) + expect(page).to have_content(spam_log.noteable_type) + expect(page).to have_content('N') + expect(page).to have_content(spam_log.title) + expect(page).to have_content("#{spam_log.description[0...97]}...") + expect(page).to have_link('Remove user') + expect(page).to have_link('Block user') + end +end diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb new file mode 100644 index 00000000000..d880f3f07db --- /dev/null +++ b/spec/features/admin/admin_browses_logs_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'Admin browses logs' do + before do + login_as :admin + end + + it 'shows available log files' do + visit admin_logs_path + + expect(page).to have_content 'test.log' + expect(page).to have_content 'githost.log' + expect(page).to have_content 'application.log' + end +end diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb new file mode 100644 index 00000000000..7ce6cce0a5c --- /dev/null +++ b/spec/features/admin/admin_deploy_keys_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +RSpec.describe 'admin deploy keys', type: :feature do + let!(:deploy_key) { create(:deploy_key, public: true) } + let!(:another_deploy_key) { create(:another_deploy_key, public: true) } + + before do + login_as(:admin) + end + + it 'show all public deploy keys' do + visit admin_deploy_keys_path + + expect(page).to have_content(deploy_key.title) + expect(page).to have_content(another_deploy_key.title) + end + + describe 'create new deploy key' do + before do + visit admin_deploy_keys_path + click_link 'New Deploy Key' + end + + it 'creates new deploy key' do + fill_deploy_key + click_button 'Create' + + expect_renders_new_key + end + + it 'creates new deploy key with write access' do + fill_deploy_key + check "deploy_key_can_push" + click_button "Create" + + expect_renders_new_key + expect(page).to have_content('Yes') + end + + def expect_renders_new_key + expect(current_path).to eq admin_deploy_keys_path + expect(page).to have_content('laptop') + end + + def fill_deploy_key + fill_in 'deploy_key_title', with: 'laptop' + fill_in 'deploy_key_key', with: 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop' + end + end +end diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb new file mode 100644 index 00000000000..a871e370ba2 --- /dev/null +++ b/spec/features/admin/admin_groups_spec.rb @@ -0,0 +1,154 @@ +require 'spec_helper' + +feature 'Admin Groups', feature: true do + include Select2Helper + + let(:internal) { Gitlab::VisibilityLevel::INTERNAL } + let(:user) { create :user } + let!(:group) { create :group } + let!(:current_user) { login_as :admin } + + before do + stub_application_setting(default_group_visibility: internal) + end + + describe 'list' do + it 'renders groups' do + visit admin_groups_path + + expect(page).to have_content(group.name) + end + end + + describe 'create a group' do + it 'creates new group' do + visit admin_groups_path + + click_link "New Group" + fill_in 'group_path', with: 'gitlab' + fill_in 'group_description', with: 'Group description' + click_button "Create group" + + expect(current_path).to eq admin_group_path(Group.find_by(path: 'gitlab')) + expect(page).to have_content('Group: gitlab') + expect(page).to have_content('Group description') + end + + scenario 'shows the visibility level radio populated with the default value' do + visit new_admin_group_path + + expect_selected_visibility(internal) + end + end + + describe 'show a group' do + scenario 'shows the group' do + group = create(:group, :private) + + visit admin_group_path(group) + + expect(page).to have_content("Group: #{group.name}") + end + end + + describe 'group edit' do + scenario 'shows the visibility level radio populated with the group visibility_level value' do + group = create(:group, :private) + + visit admin_group_edit_path(group) + + expect_selected_visibility(group.visibility_level) + end + end + + describe 'add user into a group', js: true do + shared_context 'adds user into a group' do + it do + visit admin_group_path(group) + + select2(user_selector, from: '#user_ids', multiple: true) + page.within '#new_project_member' do + select2(Gitlab::Access::REPORTER, from: '#access_level') + end + click_button "Add users to group" + page.within ".group-users-list" do + expect(page).to have_content(user.name) + expect(page).to have_content('Reporter') + end + end + end + + it_behaves_like 'adds user into a group' do + let(:user_selector) { user.id } + end + + it_behaves_like 'adds user into a group' do + let(:user_selector) { user.email } + end + end + + describe 'add admin himself to a group' do + before do + group.add_user(:user, Gitlab::Access::OWNER) + end + + it 'adds admin a to a group as developer', js: true do + visit group_group_members_path(group) + + page.within '.users-group-form' do + select2(current_user.id, from: '#user_ids', multiple: true) + select 'Developer', from: 'access_level' + end + + click_button 'Add to group' + + page.within '.content-list' do + expect(page).to have_content(current_user.name) + expect(page).to have_content('Developer') + end + end + end + + describe 'admin remove himself from a group', js: true do + it 'removes admin from the group' do + group.add_user(current_user, Gitlab::Access::DEVELOPER) + + visit group_group_members_path(group) + + page.within '.content-list' do + expect(page).to have_content(current_user.name) + expect(page).to have_content('Developer') + end + + find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click + + visit group_group_members_path(group) + + page.within '.content-list' do + expect(page).not_to have_content(current_user.name) + expect(page).not_to have_content('Developer') + end + end + end + + describe 'shared projects' do + it 'renders shared project' do + empty_project = create(:empty_project) + empty_project.project_group_links.create!( + group_access: Gitlab::Access::MASTER, + group: group + ) + + visit admin_group_path(group) + + expect(page).to have_content(empty_project.name_with_namespace) + expect(page).to have_content('Projects shared with') + end + end + + def expect_selected_visibility(level) + selector = "#group_visibility_level_#{level}[checked=checked]" + + expect(page).to have_selector(selector, count: 1) + end +end diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index b3ce72b1452..f246997d5a2 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -26,16 +26,17 @@ describe "Admin::Hooks", feature: true do end describe "New Hook" do - before do - @url = FFaker::Internet.uri("http") + let(:url) { FFaker::Internet.uri('http') } + + it 'adds new hook' do visit admin_hooks_path - fill_in "hook_url", with: @url - expect { click_button "Add System Hook" }.to change(SystemHook, :count).by(1) - end + fill_in 'hook_url', with: url + check 'Enable SSL verification' - it "opens new hook popup" do + expect { click_button 'Add System Hook' }.to change(SystemHook, :count).by(1) + expect(page).to have_content 'SSL Verification: enabled' expect(current_path).to eq(admin_hooks_path) - expect(page).to have_content(@url) + expect(page).to have_content(url) end end diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb new file mode 100644 index 00000000000..eaa42aad0a7 --- /dev/null +++ b/spec/features/admin/admin_labels_spec.rb @@ -0,0 +1,99 @@ +require 'spec_helper' + +RSpec.describe 'admin issues labels' do + include WaitForAjax + + let!(:bug_label) { Label.create(title: 'bug', template: true) } + let!(:feature_label) { Label.create(title: 'feature', template: true) } + + before do + login_as :admin + end + + describe 'list' do + before do + visit admin_labels_path + end + + it 'renders labels list' do + page.within '.manage-labels-list' do + expect(page).to have_content('bug') + expect(page).to have_content('feature') + end + end + + it 'deletes label' do + page.within "#label_#{bug_label.id}" do + click_link 'Delete' + end + + page.within '.manage-labels-list' do + expect(page).not_to have_content('bug') + end + end + + it 'deletes all labels', js: true do + page.within '.labels' do + page.all('.btn-remove').each do |remove| + wait_for_ajax + remove.click + end + end + + page.within '.manage-labels-list' do + expect(page).not_to have_content('bug') + expect(page).not_to have_content('feature_label') + end + end + end + + describe 'create' do + before do + visit new_admin_label_path + end + + it 'creates new label' do + fill_in 'Title', with: 'support' + fill_in 'Background color', with: '#F95610' + click_button 'Save' + + page.within '.manage-labels-list' do + expect(page).to have_content('support') + end + end + + it 'does not creates label with invalid color' do + fill_in 'Title', with: 'support' + fill_in 'Background color', with: '#12' + click_button 'Save' + + page.within '.label-form' do + expect(page).to have_content('Color must be a valid color code') + end + end + + it 'does not creates label if label already exists' do + fill_in 'Title', with: 'bug' + fill_in 'Background color', with: '#F95610' + click_button 'Save' + + page.within '.label-form' do + expect(page).to have_content 'Title has already been taken' + end + end + end + + describe 'edit' do + it 'changes bug label' do + visit edit_admin_label_path(bug_label) + + fill_in 'Title', with: 'fix' + fill_in 'Background color', with: '#F15610' + click_button 'Save' + + page.within '.manage-labels-list' do + expect(page).to have_content('fix') + end + end + end +end diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb new file mode 100644 index 00000000000..c2c618b5659 --- /dev/null +++ b/spec/features/admin/admin_manage_applications_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +RSpec.describe 'admin manage applications', feature: true do + before do + login_as :admin + end + + it do + visit admin_applications_path + + click_on 'New Application' + expect(page).to have_content('New application') + + fill_in :doorkeeper_application_name, with: 'test' + fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com' + click_on 'Submit' + expect(page).to have_content('Application: test') + expect(page).to have_content('Application Id') + expect(page).to have_content('Secret') + + click_on 'Edit' + expect(page).to have_content('Edit application') + + fill_in :doorkeeper_application_name, with: 'test_changed' + click_on 'Submit' + expect(page).to have_content('test_changed') + expect(page).to have_content('Application Id') + expect(page).to have_content('Secret') + + visit admin_applications_path + page.within '.oauth-applications' do + click_on 'Destroy' + end + expect(page.find('.oauth-applications')).not_to have_content('test_changed') + end +end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index 30ded9202a4..87a8f62687a 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -1,34 +1,117 @@ require 'spec_helper' describe "Admin::Projects", feature: true do - before do - @project = create(:project) + include Select2Helper + + let(:user) { create :user } + let!(:project) { create(:project) } + let!(:current_user) do login_as :admin end describe "GET /admin/projects" do + let!(:archived_project) { create :project, :public, :archived } + before do - visit admin_namespaces_projects_path + visit admin_projects_path end it "is ok" do - expect(current_path).to eq(admin_namespaces_projects_path) + expect(current_path).to eq(admin_projects_path) end - it "has projects list" do - expect(page).to have_content(@project.name) + it 'renders projects list without archived project' do + expect(page).to have_content(project.name) + expect(page).not_to have_content(archived_project.name) + end + + it 'renders all projects', js: true do + find(:css, '#sort-projects-dropdown').click + click_link 'Show archived projects' + + expect(page).to have_content(project.name) + expect(page).to have_content(archived_project.name) + expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived') end end - describe "GET /admin/projects/:id" do + describe "GET /admin/projects/:namespace_id/:id" do before do - visit admin_namespaces_projects_path - click_link "#{@project.name}" + visit admin_projects_path + click_link "#{project.name}" + end + + it do + expect(current_path).to eq admin_namespace_project_path(project.namespace, project) end it "has project info" do - expect(page).to have_content(@project.path) - expect(page).to have_content(@project.name) + expect(page).to have_content(project.path) + expect(page).to have_content(project.name) + expect(page).to have_content(project.name_with_namespace) + expect(page).to have_content(project.creator.name) + end + end + + describe 'transfer project' do + before do + create(:group, name: 'Web') + + allow_any_instance_of(Projects::TransferService). + to receive(:move_uploads_to_new_namespace).and_return(true) + end + + it 'transfers project to group web', js: true do + visit admin_namespace_project_path(project.namespace, project) + + click_button 'Search for Namespace' + click_link 'group: web' + click_button 'Transfer' + + expect(page).to have_content("Web / #{project.name}") + expect(page).to have_content('Namespace: Web') + end + end + + describe 'add admin himself to a project' do + before do + project.team << [user, :master] + end + + it 'adds admin a to a project as developer', js: true do + visit namespace_project_project_members_path(project.namespace, project) + + page.within '.users-project-form' do + select2(current_user.id, from: '#user_ids', multiple: true) + select 'Developer', from: 'access_level' + end + + click_button 'Add to project' + + page.within '.content-list' do + expect(page).to have_content(current_user.name) + expect(page).to have_content('Developer') + end + end + end + + describe 'admin remove himself from a project' do + before do + project.team << [user, :master] + project.team << [current_user, :developer] + end + + it 'removes admin from the project' do + visit namespace_project_project_members_path(project.namespace, project) + + page.within '.content-list' do + expect(page).to have_content(current_user.name) + expect(page).to have_content('Developer') + end + + find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click + + expect(page).not_to have_selector(:css, '.content-list') end end end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb new file mode 100644 index 00000000000..47fa2f14307 --- /dev/null +++ b/spec/features/admin/admin_settings_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +feature 'Admin updates settings', feature: true do + before(:each) do + login_as :admin + visit admin_application_settings_path + end + + scenario 'Change application settings' do + uncheck 'Gravatar enabled' + fill_in 'Home page URL', with: 'https://about.gitlab.com/' + fill_in 'Help page text', with: 'Example text' + click_button 'Save' + + expect(current_application_settings.gravatar_enabled).to be_falsey + expect(current_application_settings.home_page_url).to eq "https://about.gitlab.com/" + expect(page).to have_content "Application settings saved successfully" + end + + scenario 'Change Slack Notifications Service template settings' do + click_link 'Service Templates' + click_link 'Slack notifications' + fill_in 'Webhook', with: 'http://localhost' + fill_in 'Username', with: 'test_user' + fill_in 'service_push_channel', with: '#test_channel' + page.check('Notify only broken builds') + + check_all_events + click_on 'Save' + + expect(page).to have_content 'Application settings saved successfully' + + click_link 'Slack notifications' + + page.all('input[type=checkbox]').each do |checkbox| + expect(checkbox).to be_checked + end + expect(find_field('Webhook').value).to eq 'http://localhost' + expect(find_field('Username').value).to eq 'test_user' + expect(find('#service_push_channel').value).to eq '#test_channel' + end + + def check_all_events + page.check('Active') + page.check('Push') + page.check('Tag push') + page.check('Note') + page.check('Issue') + page.check('Merge request') + page.check('Build') + page.check('Pipeline') + end +end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index cb3191dfdde..a586f8d3184 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -1,7 +1,13 @@ require 'spec_helper' -describe "Admin::Users", feature: true do - before { login_as :admin } +describe "Admin::Users", feature: true do + include WaitForAjax + + let!(:user) do + create(:omniauth_user, provider: 'twitter', extern_uid: '123456') + end + + let!(:current_user) { login_as :admin } describe "GET /admin/users" do before do @@ -13,8 +19,10 @@ describe "Admin::Users", feature: true do end it "has users list" do - expect(page).to have_content(@user.email) - expect(page).to have_content(@user.name) + expect(page).to have_content(current_user.email) + expect(page).to have_content(current_user.name) + expect(page).to have_content(user.email) + expect(page).to have_content(user.name) end describe 'Two-factor Authentication filters' do @@ -38,8 +46,6 @@ describe "Admin::Users", feature: true do end it 'counts users who have not enabled 2FA' do - create(:user) - visit admin_users_path page.within('.filter-two-factor-disabled small') do @@ -48,8 +54,6 @@ describe "Admin::Users", feature: true do end it 'filters by users who have not enabled 2FA' do - user = create(:user) - visit admin_users_path click_link '2FA Disabled' @@ -108,10 +112,10 @@ describe "Admin::Users", feature: true do describe "GET /admin/users/:id" do it "has user info" do visit admin_users_path - click_link @user.name + click_link user.name - expect(page).to have_content(@user.email) - expect(page).to have_content(@user.name) + expect(page).to have_content(user.email) + expect(page).to have_content(user.name) end describe 'Impersonation' do @@ -124,7 +128,7 @@ describe "Admin::Users", feature: true do end it 'does not show impersonate button for admin itself' do - visit admin_user_path(@user) + visit admin_user_path(current_user) expect(page).not_to have_content('Impersonate') end @@ -156,7 +160,7 @@ describe "Admin::Users", feature: true do it 'logs out of impersonated user back to original user' do find(:css, 'li.impersonation a').click - expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(@user.username) + expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(current_user.username) end it 'is redirected back to the impersonated users page in the admin after stopping' do @@ -169,15 +173,15 @@ describe "Admin::Users", feature: true do describe 'Two-factor Authentication status' do it 'shows when enabled' do - @user.update_attribute(:otp_required_for_login, true) + user.update_attribute(:otp_required_for_login, true) - visit admin_user_path(@user) + visit admin_user_path(user) expect_two_factor_status('Enabled') end it 'shows when disabled' do - visit admin_user_path(@user) + visit admin_user_path(user) expect_two_factor_status('Disabled') end @@ -192,9 +196,8 @@ describe "Admin::Users", feature: true do describe "GET /admin/users/:id/edit" do before do - @simple_user = create(:user) visit admin_users_path - click_link "edit_user_#{@simple_user.id}" + click_link "edit_user_#{user.id}" end it "has user edit page" do @@ -212,17 +215,168 @@ describe "Admin::Users", feature: true do click_button "Save changes" end - it "shows page with new data" do + it "shows page with new data" do expect(page).to have_content('bigbang@mail.com') expect(page).to have_content('Big Bang') end it "changes user entry" do - @simple_user.reload - expect(@simple_user.name).to eq('Big Bang') - expect(@simple_user.is_admin?).to be_truthy - expect(@simple_user.password_expires_at).to be <= Time.now + user.reload + expect(user.name).to eq('Big Bang') + expect(user.is_admin?).to be_truthy + expect(user.password_expires_at).to be <= Time.now + end + end + + describe 'update username to non ascii char' do + it do + fill_in 'user_username', with: '\u3042\u3044' + click_button('Save') + + page.within '#error_explanation' do + expect(page).to have_content('Username') + end + + expect(page).to have_selector(%(form[action="/admin/users/#{user.username}"])) + end + end + end + + describe "GET /admin/users/:id/projects" do + let(:group) { create(:group) } + let!(:project) { create(:project, group: group) } + + before do + group.add_developer(user) + + visit projects_admin_user_path(user) + end + + it "lists group projects" do + within(:css, '.append-bottom-default + .panel') do + expect(page).to have_content 'Group projects' + expect(page).to have_link group.name, admin_group_path(group) + end + end + + it 'allows navigation to the group details' do + within(:css, '.append-bottom-default + .panel') do + click_link group.name + end + within(:css, 'h3.page-title') do + expect(page).to have_content "Group: #{group.name}" end + expect(page).to have_content project.name + end + + it 'shows the group access level' do + within(:css, '.append-bottom-default + .panel') do + expect(page).to have_content 'Developer' + end + end + + it 'allows group membership to be revoked', js: true do + page.within(first('.group_member')) do + find('.btn-remove').click + end + wait_for_ajax + + expect(page).not_to have_selector('.group_member') + end + end + + describe 'show user attributes' do + it do + visit admin_users_path + + click_link user.name + + expect(page).to have_content 'Account' + expect(page).to have_content 'Personal projects limit' + end + end + + describe 'remove users secondary email', js: true do + let!(:secondary_email) do + create :email, email: 'secondary@example.com', user: user + end + + it do + visit admin_user_path(user.username) + + expect(page).to have_content("Secondary email: #{secondary_email.email}") + + find("#remove_email_#{secondary_email.id}").click + + expect(page).not_to have_content(secondary_email.email) + end + end + + describe 'show user keys' do + let!(:key1) do + create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1") + end + + let!(:key2) do + create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2") + end + + it do + visit admin_users_path + + click_link user.name + click_link 'SSH keys' + + expect(page).to have_content(key1.title) + expect(page).to have_content(key2.title) + + click_link key2.title + + expect(page).to have_content(key2.title) + expect(page).to have_content(key2.key) + + click_link 'Remove' + + expect(page).not_to have_content(key2.title) + end + end + + describe 'show user identities' do + it 'shows user identities' do + visit admin_user_identities_path(user) + + expect(page).to have_content(user.name) + expect(page).to have_content('twitter') + end + end + + describe 'update user identities' do + before do + allow(Gitlab::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated]) + end + + it 'modifies twitter identity' do + visit admin_user_identities_path(user) + + find('.table').find(:link, 'Edit').click + fill_in 'identity_extern_uid', with: '654321' + select 'twitter_updated', from: 'identity_provider' + click_button 'Save changes' + + expect(page).to have_content(user.name) + expect(page).to have_content('twitter_updated') + expect(page).to have_content('654321') + end + end + + describe 'remove user with identities' do + it 'removes user with twitter identity' do + visit admin_user_identities_path(user) + + click_link 'Delete' + + expect(page).to have_content(user.name) + expect(page).not_to have_content('twitter') end end end diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb new file mode 100644 index 00000000000..ea7a97d1d4f --- /dev/null +++ b/spec/features/auto_deploy_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe 'Auto deploy' do + include WaitForAjax + + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + project.create_kubernetes_service( + active: true, + properties: { + namespace: project.path, + api_url: 'https://kubernetes.example.com', + token: 'a' * 40, + } + ) + project.team << [user, :master] + login_as user + end + + context 'when no deployment service is active' do + before do + project.kubernetes_service.update!(active: false) + end + + it 'does not show a button to set up auto deploy' do + visit namespace_project_path(project.namespace, project) + expect(page).to have_no_content('Set up auto deploy') + end + end + + context 'when a deployment service is active' do + before do + project.kubernetes_service.update!(active: true) + visit namespace_project_path(project.namespace, project) + end + + it 'shows a button to set up auto deploy' do + expect(page).to have_link('Set up auto deploy') + end + + it 'includes OpenShift as an available template', js: true do + click_link 'Set up auto deploy' + click_button 'Choose a GitLab CI Yaml template' + + within '.gitlab-ci-yml-selector' do + expect(page).to have_content('OpenShift') + end + end + + it 'creates a merge request using "auto-deploy" branch', js: true do + click_link 'Set up auto deploy' + click_button 'Choose a GitLab CI Yaml template' + within '.gitlab-ci-yml-selector' do + click_on 'OpenShift' + end + wait_for_ajax + click_button 'Commit Changes' + + expect(page).to have_content('New Merge Request From auto-deploy into master') + end + end +end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 6cb8753e8fc..bfac5a1b8ab 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -109,7 +109,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'search backlog list' do - page.within('#js-boards-seach') do + page.within('#js-boards-search') do find('.form-control').set(issue1.title) end @@ -122,7 +122,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'search done list' do - page.within('#js-boards-seach') do + page.within('#js-boards-search') do find('.form-control').set(issue8.title) end @@ -135,7 +135,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'search list' do - page.within('#js-boards-seach') do + page.within('#js-boards-search') do find('.form-control').set(issue5.title) end @@ -158,7 +158,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'removes checkmark in new list dropdown after deleting' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within(find('.board:nth-child(2)')) do @@ -304,7 +304,7 @@ describe 'Issue Boards', feature: true, js: true do context 'new list' do it 'shows all labels in new list dropdown' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -315,7 +315,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'creates new list for label' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -328,7 +328,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'creates new list for Backlog label' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -341,7 +341,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'creates new list for Done label' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -354,7 +354,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'keeps dropdown open after adding new list' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -369,7 +369,7 @@ describe 'Issue Boards', feature: true, js: true do it 'moves issues from backlog into new list' do wait_for_board_cards(1, 6) - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -382,7 +382,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'creates new list from a new label' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax @@ -659,6 +659,10 @@ describe 'Issue Boards', feature: true, js: true do wait_for_vue_resource end + it 'displays lists' do + expect(page).to have_selector('.board') + end + it 'does not show create new list' do expect(page).not_to have_selector('.js-new-board-list') end diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 760a8967123..a03cd6fbf2d 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -46,7 +46,7 @@ describe 'Issue Boards new issue', feature: true, js: true do click_button 'Cancel' - expect(page).to have_selector('.board-new-issue-form', visible: false) + expect(page).not_to have_selector('.board-new-issue-form') end end diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index f160052a844..188d33e8ef4 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -125,7 +125,9 @@ describe 'Issue Boards', feature: true, js: true do first('.card').click end - page.within('.assignee') do + page.within(find('.assignee')) do + expect(page).to have_content('No assignee') + click_link 'assign yourself' wait_for_vue_resource @@ -304,8 +306,8 @@ describe 'Issue Boards', feature: true, js: true do page.within('.subscription') do click_button 'Subscribe' - - expect(page).to have_content("You're receiving notifications because you're subscribed to this thread.") + wait_for_ajax + expect(page).to have_content("Unsubscribe") end end end diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index 7fa0c95cae2..3e0b6364e0d 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -52,6 +52,10 @@ feature 'Contributions Calendar', js: true, feature: true do Event.create(push_params) end + def get_first_cell_content + find('.user-calendar-activities').text + end + before do login_as :user visit @user.username @@ -62,6 +66,43 @@ feature 'Contributions Calendar', js: true, feature: true do expect(page).to have_css('.js-contrib-calendar') end + describe 'select calendar day', js: true do + let(:cells) { page.all('.user-contrib-cell') } + let(:first_cell_content_before) { get_first_cell_content } + + before do + cells[0].click + wait_for_ajax + first_cell_content_before + end + + it 'displays calendar day activities', js: true do + expect(get_first_cell_content).not_to eq('') + end + + describe 'select another calendar day', js: true do + before do + cells[1].click + wait_for_ajax + end + + it 'displays different calendar day activities', js: true do + expect(get_first_cell_content).not_to eq(first_cell_content_before) + end + end + + describe 'deselect calendar day', js: true do + before do + cells[0].click + wait_for_ajax + end + + it 'hides calendar day activities', js: true do + expect(get_first_cell_content).to eq('') + end + end + end + describe '1 calendar activity' do before do Issues::CreateService.new(contributed_project, @user, issue_params).execute diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb index 81077f4b005..3ebc432206a 100644 --- a/spec/features/ci_lint_spec.rb +++ b/spec/features/ci_lint_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'CI Lint' do +describe 'CI Lint', js: true do before do login_as :user end @@ -8,7 +8,10 @@ describe 'CI Lint' do describe 'YAML parsing' do before do visit ci_lint_path - fill_in 'content', with: yaml_content + # Ace editor updates a hidden textarea and it happens asynchronously + # `sleep 0.1` is actually needed here because of this + execute_script("ace.edit('ci-editor').setValue(" + yaml_content.to_json + ");") + sleep 0.1 click_on 'Validate' end @@ -40,7 +43,7 @@ describe 'CI Lint' do let(:yaml_content) { 'my yaml content' } it 'loads previous YAML content after validation' do - expect(page).to have_field('content', with: 'my yaml content', type: 'textarea') + expect(page).to have_field('content', with: 'my yaml content', visible: false, type: 'textarea') end end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 44646ffc602..8f561c8f90b 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -107,7 +107,7 @@ describe 'Commits' do describe 'Cancel build' do it 'cancels build' do visit ci_status_path(pipeline) - click_on 'Cancel' + find('a.btn[title="Cancel"]').click expect(page).to have_content 'canceled' end end @@ -177,4 +177,23 @@ describe 'Commits' do end end end + + context 'viewing commits for a branch' do + let(:branch_name) { 'master' } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + login_with(user) + visit namespace_project_commits_path(project.namespace, project, branch_name) + end + + it 'includes the committed_date for each commit' do + commits = project.repository.commits(branch_name) + + commits.each do |commit| + expect(page).to have_content("committed #{commit.committed_date}") + end + end + end end diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb new file mode 100644 index 00000000000..0648c89a5c7 --- /dev/null +++ b/spec/features/cycle_analytics_spec.rb @@ -0,0 +1,125 @@ +require 'spec_helper' + +feature 'Cycle Analytics', feature: true, js: true do + include WaitForAjax + + let(:user) { create(:user) } + let(:guest) { create(:user) } + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } + let(:milestone) { create(:milestone, project: project) } + let(:mr) { create_merge_request_closing_issue(issue) } + let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha) } + + context 'as an allowed user' do + context 'when project is new' do + before do + project.team << [user, :master] + login_as(user) + visit namespace_project_cycle_analytics_path(project.namespace, project) + wait_for_ajax + end + + it 'shows introductory message' do + expect(page).to have_content('Introducing Cycle Analytics') + end + + it 'shows active stage with empty message' do + expect(page).to have_selector('.stage-nav-item.active', text: 'Issue') + expect(page).to have_content("We don't have enough data to show this stage.") + end + end + + context "when there's cycle analytics data" do + before do + project.team << [user, :master] + + allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue]) + create_cycle + deploy_master + + login_as(user) + visit namespace_project_cycle_analytics_path(project.namespace, project) + end + + it 'shows data on each stage' do + expect_issue_to_be_present + + click_stage('Plan') + expect(find('.stage-events')).to have_content(mr.commits.last.title) + + click_stage('Code') + expect_merge_request_to_be_present + + click_stage('Test') + expect_build_to_be_present + + click_stage('Review') + expect_merge_request_to_be_present + + click_stage('Staging') + expect_build_to_be_present + + click_stage('Production') + expect_issue_to_be_present + end + end + end + + context "as a guest" do + before do + project.team << [guest, :guest] + + allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue]) + create_cycle + deploy_master + + login_as(guest) + visit namespace_project_cycle_analytics_path(project.namespace, project) + wait_for_ajax + end + + it 'needs permissions to see restricted stages' do + expect(find('.stage-events')).to have_content(issue.title) + + click_stage('Code') + expect(find('.stage-events')).to have_content('You need permission.') + + click_stage('Review') + expect(find('.stage-events')).to have_content('You need permission.') + end + end + + def expect_issue_to_be_present + expect(find('.stage-events')).to have_content(issue.title) + expect(find('.stage-events')).to have_content(issue.author.name) + expect(find('.stage-events')).to have_content("##{issue.iid}") + end + + def expect_build_to_be_present + expect(find('.stage-events')).to have_content(@build.ref) + expect(find('.stage-events')).to have_content(@build.short_sha) + expect(find('.stage-events')).to have_content("##{@build.id}") + end + + def expect_merge_request_to_be_present + expect(find('.stage-events')).to have_content(mr.title) + expect(find('.stage-events')).to have_content(mr.author.name) + expect(find('.stage-events')).to have_content("!#{mr.iid}") + end + + def create_cycle + issue.update(milestone: milestone) + pipeline.run + + @build = create(:ci_build, pipeline: pipeline, status: :success, author: user) + + merge_merge_requests_closing_issue(issue) + ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.to_hash) + end + + def click_stage(stage_name) + find('.stage-nav li', text: stage_name).click + wait_for_ajax + end +end diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb new file mode 100644 index 00000000000..7d59fcac517 --- /dev/null +++ b/spec/features/dashboard/active_tab_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +RSpec.describe 'Dashboard Active Tab', feature: true do + before do + login_as :user + end + + shared_examples 'page has active tab' do |title| + it "#{title} tab" do + expect(page).to have_selector('.nav-sidebar li.active', count: 1) + expect(find('.nav-sidebar li.active')).to have_content(title) + end + end + + context 'on dashboard projects' do + before do + visit dashboard_projects_path + end + + it_behaves_like 'page has active tab', 'Projects' + end + + context 'on dashboard issues' do + before do + visit issues_dashboard_path + end + + it_behaves_like 'page has active tab', 'Issues' + end + + context 'on dashboard merge requests' do + before do + visit merge_requests_dashboard_path + end + + it_behaves_like 'page has active tab', 'Merge Requests' + end + + context 'on dashboard groups' do + before do + visit dashboard_groups_path + end + + it_behaves_like 'page has active tab', 'Groups' + end +end diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb new file mode 100644 index 00000000000..038c1641be9 --- /dev/null +++ b/spec/features/dashboard/archived_projects_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +RSpec.describe 'Dashboard Archived Project', feature: true do + let(:user) { create :user } + let(:project) { create :project} + let(:archived_project) { create(:project, :archived) } + + before do + project.team << [user, :master] + archived_project.team << [user, :master] + + login_as(user) + + visit dashboard_projects_path + end + + it 'renders non archived projects' do + expect(page).to have_link(project.name) + expect(page).not_to have_link(archived_project.name) + end + + it 'renders all projects' do + click_link 'Show archived projects' + + expect(page).to have_link(project.name) + expect(page).to have_link(archived_project.name) + end +end diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index 365cb445df1..dc9d09fa396 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -6,7 +6,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do let(:user) { create(:user) } let(:project) { create(:project, name: 'test', namespace: user.namespace) } let(:created_date) { Date.yesterday.to_time } - let(:expected_format) { created_date.strftime('%b %-d, %Y %l:%M%P UTC') } + let(:expected_format) { created_date.strftime('%b %-d, %Y %l:%M%P') } context 'on the activity tab' do before do @@ -36,7 +36,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do visit user_snippets_path(user) wait_for_ajax() - page.find('.js-timeago').hover + page.find('.js-timeago.snippet-created-ago').hover end it 'has the datetime formated correctly' do diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb new file mode 100644 index 00000000000..d5f8470fab0 --- /dev/null +++ b/spec/features/dashboard/group_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +RSpec.describe 'Dashboard Group', feature: true do + before do + login_as(:user) + end + + it 'creates new grpup' do + visit dashboard_groups_path + click_link 'New Group' + + fill_in 'group_path', with: 'Samurai' + fill_in 'group_description', with: 'Tokugawa Shogunate' + click_button 'Create group' + + expect(current_path).to eq group_path(Group.find_by(name: 'Samurai')) + expect(page).to have_content('Samurai') + expect(page).to have_content('Tokugawa Shogunate') + end +end diff --git a/spec/features/dashboard/help_spec.rb b/spec/features/dashboard/help_spec.rb new file mode 100644 index 00000000000..2803f7ec62b --- /dev/null +++ b/spec/features/dashboard/help_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +RSpec.describe 'Dashboard Help', feature: true do + before do + login_as(:user) + end + + it 'renders correctly markdown' do + visit help_page_path("administration/raketasks/maintenance") + + expect(page).to have_content('Gather information about GitLab and the system it runs on') + + node = find('.documentation h2 a#user-content-check-gitlab-configuration') + expect(node[:href]).to eq '#check-gitlab-configuration' + expect(find(:xpath, "#{node.path}/..").text).to eq 'Check GitLab configuration' + end +end diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb new file mode 100644 index 00000000000..41dcfe439c2 --- /dev/null +++ b/spec/features/dashboard/issuables_counter_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe 'Navigation bar counter', feature: true, js: true, caching: true do + let(:user) { create(:user) } + let(:project) { create(:empty_project, namespace: user.namespace) } + let(:issue) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, source_project: project) } + + before do + issue.update(assignee: user) + merge_request.update(assignee: user) + login_as(user) + end + + it 'reflects dashboard issues count' do + visit issues_dashboard_path + + expect_counters('issues', '1') + + issue.update(assignee: nil) + visit issues_dashboard_path + + expect_counters('issues', '1') + end + + it 'reflects dashboard merge requests count' do + visit merge_requests_dashboard_path + + expect_counters('merge_requests', '1') + + merge_request.update(assignee: nil) + visit merge_requests_dashboard_path + + expect_counters('merge_requests', '1') + end + + def expect_counters(issuable_type, count) + dashboard_count = find('li.active span.badge') + nav_count = find(".dashboard-shortcuts-#{issuable_type} span.count") + + expect(nav_count).to have_content(count) + expect(dashboard_count).to have_content(count) + end +end diff --git a/spec/features/environment_spec.rb b/spec/features/environment_spec.rb new file mode 100644 index 00000000000..56f6cd2e095 --- /dev/null +++ b/spec/features/environment_spec.rb @@ -0,0 +1,193 @@ +require 'spec_helper' + +feature 'Environment', :feature do + given(:project) { create(:empty_project) } + given(:user) { create(:user) } + given(:role) { :developer } + + background do + login_as(user) + project.team << [user, role] + end + + feature 'environment details page' do + given!(:environment) { create(:environment, project: project) } + given!(:deployment) { } + given!(:manual) { } + + before do + visit_environment(environment) + end + + context 'without deployments' do + scenario 'does show no deployments' do + expect(page).to have_content('You don\'t have any deployments right now.') + end + end + + context 'with deployments' do + context 'when there is no related deployable' do + given(:deployment) do + create(:deployment, environment: environment, deployable: nil) + end + + scenario 'does show deployment SHA' do + expect(page).to have_link(deployment.short_sha) + end + + scenario 'does not show a re-deploy button for deployment without build' do + expect(page).not_to have_link('Re-deploy') + end + + scenario 'does not show terminal button' do + expect(page).not_to have_terminal_button + end + end + + context 'with related deployable present' do + given(:pipeline) { create(:ci_pipeline, project: project) } + given(:build) { create(:ci_build, pipeline: pipeline) } + + given(:deployment) do + create(:deployment, environment: environment, deployable: build) + end + + scenario 'does show build name' do + expect(page).to have_link("#{build.name} (##{build.id})") + end + + scenario 'does show re-deploy button' do + expect(page).to have_link('Re-deploy') + end + + scenario 'does not show stop button' do + expect(page).not_to have_link('Stop') + end + + scenario 'does not show terminal button' do + expect(page).not_to have_terminal_button + end + + context 'with manual action' do + given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') } + + scenario 'does show a play button' do + expect(page).to have_link(manual.name.humanize) + end + + scenario 'does allow to play manual action' do + expect(manual).to be_skipped + expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count } + expect(page).to have_content(manual.name) + expect(manual.reload).to be_pending + end + + context 'with external_url' do + given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } + given(:build) { create(:ci_build, pipeline: pipeline) } + given(:deployment) { create(:deployment, environment: environment, deployable: build) } + + scenario 'does show an external link button' do + expect(page).to have_link(nil, href: environment.external_url) + end + end + + context 'with terminal' do + let(:project) { create(:kubernetes_project, :test_repo) } + + context 'for project master' do + let(:role) { :master } + + scenario 'it shows the terminal button' do + expect(page).to have_terminal_button + end + end + + context 'for developer' do + let(:role) { :developer } + + scenario 'does not show terminal button' do + expect(page).not_to have_terminal_button + end + end + end + + context 'with stop action' do + given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } + given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + + scenario 'does show stop button' do + expect(page).to have_link('Stop') + end + + scenario 'does allow to stop environment' do + click_link('Stop') + + expect(page).to have_content('close_app') + end + + context 'for reporter' do + let(:role) { :reporter } + + scenario 'does not show stop button' do + expect(page).not_to have_link('Stop') + end + end + end + end + end + end + end + + feature 'auto-close environment when branch is deleted' do + given(:project) { create(:project) } + + given!(:environment) do + create(:environment, :with_review_app, project: project, + ref: 'feature') + end + + scenario 'user visits environment page' do + visit_environment(environment) + + expect(page).to have_link('Stop') + end + + scenario 'user deletes the branch with running environment' do + visit namespace_project_branches_path(project.namespace, project) + + remove_branch_with_hooks(project, user, 'feature') do + page.within('.js-branch-feature') { find('a.btn-remove').click } + end + + visit_environment(environment) + + expect(page).to have_no_link('Stop') + end + + ## + # This is a workaround for problem described in #24543 + # + def remove_branch_with_hooks(project, user, branch) + params = { + oldrev: project.commit(branch).id, + newrev: Gitlab::Git::BLANK_SHA, + ref: "refs/heads/#{branch}" + } + + yield + + GitPushService.new(project, user, params).execute + end + end + + def visit_environment(environment) + visit namespace_project_environment_path(environment.project.namespace, + environment.project, + environment) + end + + def have_terminal_button + have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment)) + end +end diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index f75b197f4fe..34ff3a33c19 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -1,153 +1,56 @@ require 'spec_helper' -feature 'Environments', feature: true do +feature 'Environments page', :feature, :js do given(:project) { create(:empty_project) } given(:user) { create(:user) } given(:role) { :developer } background do - login_as(user) project.team << [user, role] + login_as(user) end - describe 'when showing environments' do - given!(:environment) { } - given!(:deployment) { } - given!(:manual) { } - - before do - visit namespace_project_environments_path(project.namespace, project) - end - - context 'shows two tabs' do - scenario 'shows "Available" and "Stopped" tab with links' do - expect(page).to have_link('Available') - expect(page).to have_link('Stopped') - end - end + given!(:environment) { } + given!(:deployment) { } + given!(:manual) { } - context 'without environments' do - scenario 'does show no environments' do - expect(page).to have_content('You don\'t have any environments right now.') - end + before do + visit_environments(project) + end - scenario 'does show 0 as counter for environments in both tabs' do - expect(page.find('.js-available-environments-count').text).to eq('0') - expect(page.find('.js-stopped-environments-count').text).to eq('0') - end + describe 'page tabs' do + scenario 'shows "Available" and "Stopped" tab with links' do + expect(page).to have_link('Available') + expect(page).to have_link('Stopped') end + end - context 'with environments' do - given(:environment) { create(:environment, project: project) } - - scenario 'does show environment name' do - expect(page).to have_link(environment.name) - end - - scenario 'does show number of available and stopped environments' do - expect(page.find('.js-available-environments-count').text).to eq('1') - expect(page.find('.js-stopped-environments-count').text).to eq('0') - end - - context 'without deployments' do - scenario 'does show no deployments' do - expect(page).to have_content('No deployments yet') - end - end - - context 'with deployments' do - given(:deployment) { create(:deployment, environment: environment) } - - scenario 'does show deployment SHA' do - expect(page).to have_link(deployment.short_sha) - end - - scenario 'does show deployment internal id' do - expect(page).to have_content(deployment.iid) - end - - context 'with build and manual actions' do - given(:pipeline) { create(:ci_pipeline, project: project) } - given(:build) { create(:ci_build, pipeline: pipeline) } - given(:deployment) { create(:deployment, environment: environment, deployable: build) } - given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') } - - scenario 'does show a play button' do - expect(page).to have_link(manual.name.humanize) - end - - scenario 'does allow to play manual action' do - expect(manual).to be_skipped - expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count } - expect(page).to have_content(manual.name) - expect(manual.reload).to be_pending - end - - scenario 'does show build name and id' do - expect(page).to have_link("#{build.name} (##{build.id})") - end - - scenario 'does not show stop button' do - expect(page).not_to have_selector('.stop-env-link') - end - - scenario 'does not show external link button' do - expect(page).not_to have_css('external-url') - end - - context 'with external_url' do - given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } - given(:build) { create(:ci_build, pipeline: pipeline) } - given(:deployment) { create(:deployment, environment: environment, deployable: build) } - - scenario 'does show an external link button' do - expect(page).to have_link(nil, href: environment.external_url) - end - end - - context 'with stop action' do - given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } - given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } - - scenario 'does show stop button' do - expect(page).to have_selector('.stop-env-link') - end - - scenario 'starts build when stop button clicked' do - first('.stop-env-link').click - - expect(page).to have_content('close_app') - end - - context 'for reporter' do - let(:role) { :reporter } - - scenario 'does not show stop button' do - expect(page).not_to have_selector('.stop-env-link') - end - end - end - end - end + context 'without environments' do + scenario 'does show no environments' do + expect(page).to have_content('You don\'t have any environments right now.') end - scenario 'does have a New environment button' do - expect(page).to have_link('New environment') + scenario 'does show 0 as counter for environments in both tabs' do + expect(page.find('.js-available-environments-count').text).to eq('0') + expect(page.find('.js-stopped-environments-count').text).to eq('0') end end describe 'when showing the environment' do given(:environment) { create(:environment, project: project) } - given!(:deployment) { } - given!(:manual) { } - before do - visit namespace_project_environment_path(project.namespace, project, environment) + scenario 'does show environment name' do + expect(page).to have_link(environment.name) + end + + scenario 'does show number of available and stopped environments' do + expect(page.find('.js-available-environments-count').text).to eq('1') + expect(page.find('.js-stopped-environments-count').text).to eq('0') end context 'without deployments' do scenario 'does show no deployments' do - expect(page).to have_content('You don\'t have any deployments right now.') + expect(page).to have_content('No deployments yet') end context 'for available environment' do @@ -170,76 +73,120 @@ feature 'Environments', feature: true do end context 'with deployments' do - given(:deployment) { create(:deployment, environment: environment) } + given(:project) { create(:project) } + + given(:deployment) do + create(:deployment, environment: environment, + sha: project.commit.id) + end scenario 'does show deployment SHA' do expect(page).to have_link(deployment.short_sha) end - scenario 'does not show a re-deploy button for deployment without build' do - expect(page).not_to have_link('Re-deploy') + scenario 'does show deployment internal id' do + expect(page).to have_content(deployment.iid) end - context 'with build' do + context 'with build and manual actions' do given(:pipeline) { create(:ci_pipeline, project: project) } given(:build) { create(:ci_build, pipeline: pipeline) } - given(:deployment) { create(:deployment, environment: environment, deployable: build) } - scenario 'does show build name' do - expect(page).to have_link("#{build.name} (##{build.id})") + given(:manual) do + create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') end - scenario 'does show re-deploy button' do - expect(page).to have_link('Re-deploy') + given(:deployment) do + create(:deployment, environment: environment, + deployable: build, + sha: project.commit.id) end - context 'with manual action' do - given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') } + scenario 'does show a play button' do + find('.js-dropdown-play-icon-container').click + expect(page).to have_content(manual.name.humanize) + end + + scenario 'does allow to play manual action', js: true do + expect(manual).to be_skipped + + find('.js-dropdown-play-icon-container').click + expect(page).to have_content(manual.name.humanize) + + expect { click_link(manual.name.humanize) } + .not_to change { Ci::Pipeline.count } + + expect(manual.reload).to be_pending + end - scenario 'does show a play button' do - expect(page).to have_link(manual.name.humanize) + scenario 'does show build name and id' do + expect(page).to have_link("#{build.name} ##{build.id}") + end + + scenario 'does not show stop button' do + expect(page).not_to have_selector('.stop-env-link') + end + + scenario 'does not show external link button' do + expect(page).not_to have_css('external-url') + end + + scenario 'does not show terminal button' do + expect(page).not_to have_terminal_button + end + + context 'with external_url' do + given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } + given(:build) { create(:ci_build, pipeline: pipeline) } + given(:deployment) { create(:deployment, environment: environment, deployable: build) } + + scenario 'does show an external link button' do + expect(page).to have_link(nil, href: environment.external_url) end + end + + context 'with stop action' do + given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } + given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } - scenario 'does allow to play manual action' do - expect(manual).to be_skipped - expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count } - expect(page).to have_content(manual.name) - expect(manual.reload).to be_pending + scenario 'does show stop button' do + expect(page).to have_selector('.stop-env-link') end - context 'with external_url' do - given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } - given(:build) { create(:ci_build, pipeline: pipeline) } - given(:deployment) { create(:deployment, environment: environment, deployable: build) } + scenario 'starts build when stop button clicked' do + find('.stop-env-link').click - scenario 'does show an external link button' do - expect(page).to have_link(nil, href: environment.external_url) - end + expect(page).to have_content('close_app') end - context 'with stop action' do - given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } - given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + context 'for reporter' do + let(:role) { :reporter } - scenario 'does show stop button' do - expect(page).to have_link('Stop') + scenario 'does not show stop button' do + expect(page).not_to have_selector('.stop-env-link') end + end + end - scenario 'does allow to stop environment' do - click_link('Stop') + context 'with terminal' do + let(:project) { create(:kubernetes_project, :test_repo) } + + context 'for project master' do + let(:role) { :master } - expect(page).to have_content('close_app') + scenario 'it shows the terminal button' do + expect(page).to have_terminal_button end + end - context 'for reporter' do - let(:role) { :reporter } + context 'for developer' do + let(:role) { :developer } - scenario 'does not show stop button' do - expect(page).not_to have_link('Stop') - end + scenario 'does not show terminal button' do + expect(page).not_to have_terminal_button end end - + context 'whitout stop action' do scenario 'does allow to stop environment' do click_link('Stop') @@ -252,9 +199,13 @@ feature 'Environments', feature: true do end end + scenario 'does have a New environment button' do + expect(page).to have_link('New environment') + end + describe 'when creating a new environment' do before do - visit namespace_project_environments_path(project.namespace, project) + visit_environments(project) end context 'when logged as developer' do @@ -293,4 +244,12 @@ feature 'Environments', feature: true do end end end + + def have_terminal_button + have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment)) + end + + def visit_environments(project) + visit namespace_project_environments_path(project.namespace, project) + end end diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index 6c938bdead8..8b3e2fa93a2 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -4,10 +4,10 @@ feature 'Expand and collapse diffs', js: true, feature: true do include WaitForAjax let(:branch) { 'expand-collapse-diffs' } + let(:project) { create(:project) } before do login_as :admin - project = create(:project) # Ensure that undiffable.md is in .gitattributes project.repository.copy_gitattributes(branch) @@ -31,6 +31,33 @@ feature 'Expand and collapse diffs', js: true, feature: true do define_method(file.split('.').first) { file_container(file) } end + it 'should show the diff content with a highlighted line when linking to line' do + expect(large_diff).not_to have_selector('.code') + expect(large_diff).to have_selector('.nothing-here-block') + + visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: "#{large_diff[:id]}_0_1") + execute_script('window.location.reload()') + + wait_for_ajax + + expect(large_diff).to have_selector('.code') + expect(large_diff).not_to have_selector('.nothing-here-block') + expect(large_diff).to have_selector('.hll') + end + + it 'should show the diff content when linking to file' do + expect(large_diff).not_to have_selector('.code') + expect(large_diff).to have_selector('.nothing-here-block') + + visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: large_diff[:id]) + execute_script('window.location.reload()') + + wait_for_ajax + + expect(large_diff).to have_selector('.code') + expect(large_diff).not_to have_selector('.nothing-here-block') + end + context 'visiting a commit with collapsed diffs' do it 'shows small diffs immediately' do expect(small_diff).to have_selector('.code') @@ -182,6 +209,20 @@ feature 'Expand and collapse diffs', js: true, feature: true do end end end + + context 'expanding a diff when symlink was converted to a regular file' do + let(:branch) { 'symlink-expand-diff' } + + it 'shows the content of the regular file' do + expect(page).to have_content('This diff is collapsed') + expect(page).to have_no_content('No longer a symlink') + + find('.click-to-expand').click + wait_for_ajax + + expect(page).to have_content('No longer a symlink') + end + end end context 'visiting a commit without collapsed diffs' do diff --git a/spec/features/groups/labels/edit_spec.rb b/spec/features/groups/labels/edit_spec.rb new file mode 100644 index 00000000000..69281cecb7b --- /dev/null +++ b/spec/features/groups/labels/edit_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +feature 'Edit group label', feature: true do + given(:user) { create(:user) } + given(:group) { create(:group) } + given(:label) { create(:group_label, group: group) } + + background do + group.add_owner(user) + login_as(user) + visit edit_group_label_path(group, label) + end + + scenario 'update label with new title' do + fill_in 'label_title', with: 'new label name' + click_button 'Save changes' + + expect(current_path).to eq(root_path) + expect(label.reload.title).to eq('new label name') + end +end diff --git a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb index 33bf6d3752f..be60b0489c7 100644 --- a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb +++ b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb @@ -10,7 +10,7 @@ feature 'Groups > Members > Last owner cannot leave group', feature: true do visit group_path(group) end - scenario 'user does not see a "Leave Group" link' do - expect(page).not_to have_content 'Leave Group' + scenario 'user does not see a "Leave group" link' do + expect(page).not_to have_content 'Leave group' end end diff --git a/spec/features/groups/members/member_leaves_group_spec.rb b/spec/features/groups/members/member_leaves_group_spec.rb index 3185ff924b9..ac4d94658ae 100644 --- a/spec/features/groups/members/member_leaves_group_spec.rb +++ b/spec/features/groups/members/member_leaves_group_spec.rb @@ -13,7 +13,7 @@ feature 'Groups > Members > Member leaves group', feature: true do end scenario 'user leaves group' do - click_link 'Leave Group' + click_link 'Leave group' expect(current_path).to eq(dashboard_groups_path) expect(group.users.exists?(user.id)).to be_falsey diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb index d811b05b0c3..dbe150823ba 100644 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Groups > Members > Owner manages access requests', feature: true do let(:user) { create(:user) } let(:owner) { create(:user) } - let(:group) { create(:group, :public) } + let(:group) { create(:group, :public, :access_requestable) } background do group.request_access(user) diff --git a/spec/features/groups/members/sorting_spec.rb b/spec/features/groups/members/sorting_spec.rb new file mode 100644 index 00000000000..608aedd3471 --- /dev/null +++ b/spec/features/groups/members/sorting_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +feature 'Groups > Members > Sorting', feature: true do + let(:owner) { create(:user, name: 'John Doe') } + let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) } + let(:group) { create(:group) } + + background do + create(:group_member, :owner, user: owner, group: group, created_at: 5.days.ago) + create(:group_member, :developer, user: developer, group: group, created_at: 3.days.ago) + + login_as(owner) + end + + scenario 'sorts alphabetically by default' do + visit_members_list(sort: nil) + + expect(first_member).to include(owner.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + end + + scenario 'sorts by access level ascending' do + visit_members_list(sort: :access_level_asc) + + expect(first_member).to include(developer.name) + expect(second_member).to include(owner.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') + end + + scenario 'sorts by access level descending' do + visit_members_list(sort: :access_level_desc) + + expect(first_member).to include(owner.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') + end + + scenario 'sorts by last joined' do + visit_members_list(sort: :last_joined) + + expect(first_member).to include(developer.name) + expect(second_member).to include(owner.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined') + end + + scenario 'sorts by oldest joined' do + visit_members_list(sort: :oldest_joined) + + expect(first_member).to include(owner.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') + end + + scenario 'sorts by name ascending' do + visit_members_list(sort: :name_asc) + + expect(first_member).to include(owner.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + end + + scenario 'sorts by name descending' do + visit_members_list(sort: :name_desc) + + expect(first_member).to include(developer.name) + expect(second_member).to include(owner.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') + end + + scenario 'sorts by recent sign in' do + visit_members_list(sort: :recent_sign_in) + + expect(first_member).to include(owner.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') + end + + scenario 'sorts by oldest sign in' do + visit_members_list(sort: :oldest_sign_in) + + expect(first_member).to include(developer.name) + expect(second_member).to include(owner.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') + end + + def visit_members_list(sort:) + visit group_group_members_path(group.to_param, sort: sort) + end + + def first_member + page.all('ul.content-list > li').first.text + end + + def second_member + page.all('ul.content-list > li').last.text + end +end diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb index b3baa2ab57c..e4b5ea91bd3 100644 --- a/spec/features/groups/members/user_requests_access_spec.rb +++ b/spec/features/groups/members/user_requests_access_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Groups > Members > User requests access', feature: true do let(:user) { create(:user) } let(:owner) { create(:user) } - let(:group) { create(:group, :public) } + let(:group) { create(:group, :public, :access_requestable) } let!(:project) { create(:project, :private, namespace: group) } background do @@ -29,7 +29,7 @@ feature 'Groups > Members > User requests access', feature: true do expect(page).to have_content 'Your request for access has been queued for review.' expect(page).to have_content 'Withdraw Access Request' - expect(page).not_to have_content 'Leave Group' + expect(page).not_to have_content 'Leave group' end scenario 'user does not see private projects' do diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb index a2791b57544..78a11ffee99 100644 --- a/spec/features/groups/merge_requests_spec.rb +++ b/spec/features/groups/merge_requests_spec.rb @@ -2,7 +2,35 @@ require 'spec_helper' feature 'Group merge requests page', feature: true do let(:path) { merge_requests_group_path(group) } - let(:issuable) { create(:merge_request, source_project: project, target_project: project, title: "this is my created issuable")} + let(:issuable) { create(:merge_request, source_project: project, target_project: project, title: 'this is my created issuable') } include_examples 'project features apply to issuables', MergeRequest + + context 'archived issuable' do + let(:project_archived) { create(:project, :archived, group: group, merge_requests_access_level: ProjectFeature::ENABLED) } + let(:issuable_archived) { create(:merge_request, source_project: project_archived, target_project: project_archived, title: 'issuable of an archived project') } + let(:access_level) { ProjectFeature::ENABLED } + let(:user) { user_in_group } + + before do + issuable_archived + visit path + end + + it 'hides archived merge requests' do + expect(page).to have_content(issuable.title) + expect(page).not_to have_content(issuable_archived.title) + end + + it 'ignores archived merge request count badges in navbar' do + expect( page.find('[title="Merge Requests"] span.badge.count').text).to eq("1") + end + + it 'ignores archived merge request count badges in state-filters' do + expect(page.find('#state-opened span.badge').text).to eq("1") + expect(page.find('#state-merged span.badge').text).to eq("0") + expect(page.find('#state-closed span.badge').text).to eq("0") + expect(page.find('#state-all span.badge').text).to eq("1") + end + end end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 13bfe90302c..a515c92db37 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -80,7 +80,7 @@ feature 'Group', feature: true do visit path - expect(page).to have_css('.description > p > strong') + expect(page).to have_css('.group-home-desc > p > strong') end it 'passes through html-pipeline' do @@ -88,7 +88,7 @@ feature 'Group', feature: true do visit path - expect(page).to have_css('.description > p > img') + expect(page).to have_css('.group-home-desc > p > img') end it 'sanitizes unwanted tags' do @@ -96,7 +96,7 @@ feature 'Group', feature: true do visit path - expect(page).not_to have_css('.description h1') + expect(page).not_to have_css('.group-home-desc h1') end it 'permits `rel` attribute on links' do @@ -104,7 +104,20 @@ feature 'Group', feature: true do visit path - expect(page).to have_css('.description a[rel]') + expect(page).to have_css('.group-home-desc a[rel]') + end + end + + describe 'group page with nested groups', js: true do + let!(:group) { create(:group) } + let!(:nested_group) { create(:group, parent: group) } + let!(:path) { group_path(group) } + + it 'has nested groups tab with nested groups inside' do + visit path + click_link 'Subgroups' + + expect(page).to have_content(nested_group.full_name) end end end diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index e2101b333e2..40a1fced8d8 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -1,13 +1,36 @@ require 'spec_helper' describe 'Help Pages', feature: true do - describe 'Show SSH page' do - before do - login_as :user + describe 'Get the main help page' do + shared_examples_for 'help page' do |prefix: ''| + it 'prefixes links correctly' do + expect(page).to have_selector(%(div.documentation-index > ul a[href="#{prefix}/help/api/README.md"])) + end end - it 'replaces the variable $your_email with the email of the user' do - visit help_page_path('ssh/README') - expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"") + + context 'without a trailing slash' do + before do + visit help_path + end + + it_behaves_like 'help page' + end + + context 'with a trailing slash' do + before do + visit help_path + '/' + end + + it_behaves_like 'help page' + end + + context 'with a relative installation' do + before do + stub_config_setting(relative_url_root: '/gitlab') + visit help_path + end + + it_behaves_like 'help page', prefix: '/gitlab' end end end diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb index 9a2b879e789..73553f97d6f 100644 --- a/spec/features/issuables/default_sort_order_spec.rb +++ b/spec/features/issuables/default_sort_order_spec.rb @@ -180,16 +180,10 @@ describe 'Projects > Issuables > Default sort order', feature: true do end def visit_merge_requests_with_state(project, state) - visit_merge_requests project - visit_issuables_with_state state + visit_merge_requests project, state: state end def visit_issues_with_state(project, state) - visit_issues project - visit_issuables_with_state state - end - - def visit_issuables_with_state(state) - within('.issues-state-filters') { find("span", text: state.titleize).click } + visit_issues project, state: state end end diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb index ef00f209998..73e43316dc7 100644 --- a/spec/features/issues/award_emoji_spec.rb +++ b/spec/features/issues/award_emoji_spec.rb @@ -3,72 +3,83 @@ require 'rails_helper' describe 'Awards Emoji', feature: true do include WaitForAjax - let!(:project) { create(:project) } + let!(:project) { create(:project, :public) } let!(:user) { create(:user) } - - before do - project.team << [user, :master] - login_as(user) + let(:issue) do + create(:issue, + assignee: @user, + project: project) end - describe 'Click award emoji from issue#show' do - let!(:issue) do - create(:issue, - assignee: @user, - project: project) - end - - let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") } - + context 'authorized user' do before do - visit namespace_project_issue_path(project.namespace, project, issue) + project.team << [user, :master] + login_as(user) end - it 'increments the thumbsdown emoji', js: true do - find('[data-emoji="thumbsdown"]').click - wait_for_ajax - expect(thumbsdown_emoji).to have_text("1") - end + describe 'Click award emoji from issue#show' do + let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") } - context 'click the thumbsup emoji' do - it 'increments the thumbsup emoji', js: true do - find('[data-emoji="thumbsup"]').click - wait_for_ajax - expect(thumbsup_emoji).to have_text("1") + before do + visit namespace_project_issue_path(project.namespace, project, issue) end - it 'decrements the thumbsdown emoji', js: true do - expect(thumbsdown_emoji).to have_text("0") - end - end - - context 'click the thumbsdown emoji' do it 'increments the thumbsdown emoji', js: true do find('[data-emoji="thumbsdown"]').click wait_for_ajax expect(thumbsdown_emoji).to have_text("1") end - it 'decrements the thumbsup emoji', js: true do - expect(thumbsup_emoji).to have_text("0") + context 'click the thumbsup emoji' do + it 'increments the thumbsup emoji', js: true do + find('[data-emoji="thumbsup"]').click + wait_for_ajax + expect(thumbsup_emoji).to have_text("1") + end + + it 'decrements the thumbsdown emoji', js: true do + expect(thumbsdown_emoji).to have_text("0") + end end - end - it 'toggles the smiley emoji on a note', js: true do - toggle_smiley_emoji(true) + context 'click the thumbsdown emoji' do + it 'increments the thumbsdown emoji', js: true do + find('[data-emoji="thumbsdown"]').click + wait_for_ajax + expect(thumbsdown_emoji).to have_text("1") + end - within('.note-awards') do - expect(find(emoji_counter)).to have_text("1") + it 'decrements the thumbsup emoji', js: true do + expect(thumbsup_emoji).to have_text("0") + end end - toggle_smiley_emoji(false) + it 'toggles the smiley emoji on a note', js: true do + toggle_smiley_emoji(true) + + within('.note-awards') do + expect(find(emoji_counter)).to have_text("1") + end + + toggle_smiley_emoji(false) - within('.note-awards') do - expect(page).not_to have_selector(emoji_counter) + within('.note-awards') do + expect(page).not_to have_selector(emoji_counter) + end end end end + context 'unauthorized user', js: true do + before do + visit namespace_project_issue_path(project.namespace, project, issue) + end + + it 'has disabled emoji button' do + expect(first('.award-control')[:class]).to have_text('disabled') + end + end + def thumbsup_emoji page.all(emoji_counter).first end diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb index bc2c087c9b9..832757b24d4 100644 --- a/spec/features/issues/bulk_assignment_labels_spec.rb +++ b/spec/features/issues/bulk_assignment_labels_spec.rb @@ -9,6 +9,7 @@ feature 'Issues > Labels bulk assignment', feature: true do let!(:issue2) { create(:issue, project: project, title: "Issue 2") } let!(:bug) { create(:label, project: project, title: 'bug') } let!(:feature) { create(:label, project: project, title: 'feature') } + let!(:wontfix) { create(:label, project: project, title: 'wontfix') } context 'as an allowed user', js: true do before do @@ -291,6 +292,45 @@ feature 'Issues > Labels bulk assignment', feature: true do expect(find("#issue_#{issue1.id}")).not_to have_content 'feature' end end + + # Special case https://gitlab.com/gitlab-org/gitlab-ce/issues/24877 + context 'unmarking common label' do + before do + issue1.labels << bug + issue1.labels << feature + issue2.labels << bug + + visit namespace_project_issues_path(project.namespace, project) + end + + it 'applies label from filtered results' do + check 'check_all_issues' + + page.within('.issues_bulk_update') do + click_button 'Labels' + wait_for_ajax + + expect(find('.dropdown-menu-labels li', text: 'bug')).to have_css('.is-active') + expect(find('.dropdown-menu-labels li', text: 'feature')).to have_css('.is-indeterminate') + + click_link 'bug' + find('.dropdown-input-field', visible: true).set('wontfix') + click_link 'wontfix' + end + + update_issues + + page.within '.issues-holder' do + expect(find("#issue_#{issue1.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue1.id}")).to have_content 'feature' + expect(find("#issue_#{issue1.id}")).to have_content 'wontfix' + + expect(find("#issue_#{issue2.id}")).not_to have_content 'bug' + expect(find("#issue_#{issue2.id}")).not_to have_content 'feature' + expect(find("#issue_#{issue2.id}")).to have_content 'wontfix' + end + end + end end context 'as a guest' do @@ -320,7 +360,7 @@ feature 'Issues > Labels bulk assignment', feature: true do def open_labels_dropdown(items = [], unmark = false) page.within('.issues_bulk_update') do - click_button 'Label' + click_button 'Labels' wait_for_ajax items.map do |item| click_link item diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb new file mode 100644 index 00000000000..762cab0c0e1 --- /dev/null +++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb @@ -0,0 +1,76 @@ +require 'rails_helper' + +feature 'Resolving all open discussions in a merge request from an issue', feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: true) } + let(:merge_request) { create(:merge_request, source_project: project) } + let!(:discussion) { Discussion.for_diff_notes([create(:diff_note_on_merge_request, noteable: merge_request, project: project)]).first } + + before do + project.team << [user, :master] + login_as user + end + + context 'with the internal tracker disabled' do + before do + project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'does not show a link to create a new issue' do + expect(page).not_to have_link 'open an issue to resolve them later' + end + end + + context 'merge request has discussions that need to be resolved' do + before do + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'shows a warning that the merge request contains unresolved discussions' do + expect(page).to have_content 'This merge request has unresolved discussions' + end + + it 'has a link to resolve all discussions by creating an issue' do + page.within '.mr-widget-body' do + expect(page).to have_link 'open an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_for_resolving_discussions: merge_request.iid) + end + end + + context 'creating an issue for discussions' do + before do + page.click_link 'open an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_for_resolving_discussions: merge_request.iid) + end + + it 'shows an issue with the title filled in' do + title_field = page.find_field('issue[title]') + + expect(title_field.value).to include(merge_request.title) + end + + it 'has a mention of the discussion in the description' do + description_field = page.find_field('issue[description]') + + expect(description_field.value).to include(discussion.first_note.note) + end + + it 'has a hidden field for the merge request' do + merge_request_field = find('#merge_request_for_resolving_discussions', visible: false) + + expect(merge_request_field.value).to eq(merge_request.iid.to_s) + end + + it 'can create a new issue for the project' do + expect { click_button 'Submit issue' }.to change { project.issues.reload.size }.by(1) + end + + it 'resolves the discussion in the merge request' do + click_button 'Submit issue' + + discussion.first_note.reload + + expect(discussion.resolved?).to eq(true) + end + end + end +end diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb deleted file mode 100644 index 9dfa5d1de19..00000000000 --- a/spec/features/issues/filter_by_milestone_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'rails_helper' - -feature 'Issue filtering by Milestone', feature: true do - let(:project) { create(:project, :public) } - let(:milestone) { create(:milestone, project: project) } - - scenario 'filters by no Milestone', js: true do - create(:issue, project: project) - create(:issue, project: project, milestone: milestone) - - visit_issues(project) - filter_by_milestone(Milestone::None.title) - - expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'No Milestone') - expect(page).to have_css('.issue', count: 1) - end - - context 'filters by upcoming milestone', js: true do - it 'does not show issues with no expiry' do - create(:issue, project: project) - create(:issue, project: project, milestone: milestone) - - visit_issues(project) - filter_by_milestone(Milestone::Upcoming.title) - - expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'Upcoming') - expect(page).to have_css('.issue', count: 0) - end - - it 'shows issues in future' do - milestone = create(:milestone, project: project, due_date: Date.tomorrow) - create(:issue, project: project) - create(:issue, project: project, milestone: milestone) - - visit_issues(project) - filter_by_milestone(Milestone::Upcoming.title) - - expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'Upcoming') - expect(page).to have_css('.issue', count: 1) - end - - it 'does not show issues in past' do - milestone = create(:milestone, project: project, due_date: Date.yesterday) - create(:issue, project: project) - create(:issue, project: project, milestone: milestone) - - visit_issues(project) - filter_by_milestone(Milestone::Upcoming.title) - - expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'Upcoming') - expect(page).to have_css('.issue', count: 0) - end - end - - scenario 'filters by a specific Milestone', js: true do - create(:issue, project: project, milestone: milestone) - create(:issue, project: project) - - visit_issues(project) - filter_by_milestone(milestone.title) - - expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: milestone.title) - expect(page).to have_css('.issue', count: 1) - end - - context 'when milestone has single quotes in title' do - background do - milestone.update(name: "rock 'n' roll") - end - - scenario 'filters by a specific Milestone', js: true do - create(:issue, project: project, milestone: milestone) - create(:issue, project: project) - - visit_issues(project) - filter_by_milestone(milestone.title) - - expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: milestone.title) - expect(page).to have_css('.issue', count: 1) - end - end - - def visit_issues(project) - visit namespace_project_issues_path(project.namespace, project) - end - - def filter_by_milestone(title) - find(".js-milestone-select").click - find(".milestone-filter .dropdown-content a", text: title).click - end -end diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb new file mode 100644 index 00000000000..8a155c3bfc5 --- /dev/null +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -0,0 +1,188 @@ +require 'rails_helper' + +describe 'Dropdown assignee', js: true, feature: true do + include WaitForAjax + + let!(:project) { create(:empty_project) } + let!(:user) { create(:user, name: 'administrator', username: 'root') } + let!(:user_john) { create(:user, name: 'John', username: 'th0mas') } + let!(:user_jacob) { create(:user, name: 'Jacob', username: 'otter32') } + let(:filtered_search) { find('.filtered-search') } + let(:js_dropdown_assignee) { '#js-dropdown-assignee' } + + def send_keys_to_filtered_search(input) + input.split("").each do |i| + filtered_search.send_keys(i) + sleep 5 + wait_for_ajax + end + end + + def dropdown_assignee_size + page.all('#js-dropdown-assignee .filter-dropdown .filter-dropdown-item').size + end + + def click_assignee(text) + find('#js-dropdown-assignee .filter-dropdown .filter-dropdown-item', text: text).click + end + + before do + project.team << [user, :master] + project.team << [user_john, :master] + project.team << [user_jacob, :master] + login_as(user) + create(:issue, project: project) + + visit namespace_project_issues_path(project.namespace, project) + end + + describe 'behavior' do + it 'opens when the search bar has assignee:' do + filtered_search.set('assignee:') + + expect(page).to have_css(js_dropdown_assignee, visible: true) + end + + it 'shows assigned to me link' do + filtered_search.set('assignee:') + + page.within js_dropdown_assignee do + expect(page).to have_content('Assigned to me') + end + end + + it 'closes when the search bar is unfocused' do + find('body').click() + + expect(page).to have_css(js_dropdown_assignee, visible: false) + end + + it 'should show loading indicator when opened' do + filtered_search.set('assignee:') + + expect(page).to have_css('#js-dropdown-assignee .filter-dropdown-loading', visible: true) + end + + it 'should hide loading indicator when loaded' do + send_keys_to_filtered_search('assignee:') + + expect(page).not_to have_css('#js-dropdown-assignee .filter-dropdown-loading') + end + + it 'should load all the assignees when opened' do + send_keys_to_filtered_search('assignee:') + + expect(dropdown_assignee_size).to eq(3) + end + + it 'shows current user at top of dropdown' do + send_keys_to_filtered_search('assignee:') + + expect(first('#js-dropdown-assignee .filter-dropdown li')).to have_content(user.name) + end + end + + describe 'filtering' do + before do + send_keys_to_filtered_search('assignee:') + end + + it 'filters by name' do + send_keys_to_filtered_search('j') + + expect(dropdown_assignee_size).to eq(2) + end + + it 'filters by case insensitive name' do + send_keys_to_filtered_search('J') + + expect(dropdown_assignee_size).to eq(2) + end + + it 'filters by username with symbol' do + send_keys_to_filtered_search('@ot') + + expect(dropdown_assignee_size).to eq(2) + end + + it 'filters by case insensitive username with symbol' do + send_keys_to_filtered_search('@OT') + + expect(dropdown_assignee_size).to eq(2) + end + + it 'filters by username without symbol' do + send_keys_to_filtered_search('ot') + + expect(dropdown_assignee_size).to eq(2) + end + + it 'filters by case insensitive username without symbol' do + send_keys_to_filtered_search('OT') + + expect(dropdown_assignee_size).to eq(2) + end + end + + describe 'selecting from dropdown' do + before do + filtered_search.set('assignee:') + end + + it 'filters by current user' do + page.within js_dropdown_assignee do + click_button 'Assigned to me' + end + + expect(filtered_search.value).to eq("assignee:#{user.to_reference} ") + end + + it 'fills in the assignee username when the assignee has not been filtered' do + click_assignee(user_jacob.name) + + expect(page).to have_css(js_dropdown_assignee, visible: false) + expect(filtered_search.value).to eq("assignee:@#{user_jacob.username} ") + end + + it 'fills in the assignee username when the assignee has been filtered' do + send_keys_to_filtered_search('roo') + click_assignee(user.name) + + expect(page).to have_css(js_dropdown_assignee, visible: false) + expect(filtered_search.value).to eq("assignee:@#{user.username} ") + end + + it 'selects `no assignee`' do + find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click + + expect(page).to have_css(js_dropdown_assignee, visible: false) + expect(filtered_search.value).to eq("assignee:none ") + end + end + + describe 'input has existing content' do + it 'opens assignee dropdown with existing search term' do + filtered_search.set('searchTerm assignee:') + + expect(page).to have_css(js_dropdown_assignee, visible: true) + end + + it 'opens assignee dropdown with existing author' do + filtered_search.set('author:@user assignee:') + + expect(page).to have_css(js_dropdown_assignee, visible: true) + end + + it 'opens assignee dropdown with existing label' do + filtered_search.set('label:~bug assignee:') + + expect(page).to have_css(js_dropdown_assignee, visible: true) + end + + it 'opens assignee dropdown with existing milestone' do + filtered_search.set('milestone:%v1.0 assignee:') + + expect(page).to have_css(js_dropdown_assignee, visible: true) + end + end +end diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb new file mode 100644 index 00000000000..a5d5d9d4c5e --- /dev/null +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -0,0 +1,160 @@ +require 'rails_helper' + +describe 'Dropdown author', js: true, feature: true do + include WaitForAjax + + let!(:project) { create(:empty_project) } + let!(:user) { create(:user, name: 'administrator', username: 'root') } + let!(:user_john) { create(:user, name: 'John', username: 'th0mas') } + let!(:user_jacob) { create(:user, name: 'Jacob', username: 'otter32') } + let(:filtered_search) { find('.filtered-search') } + let(:js_dropdown_author) { '#js-dropdown-author' } + + def send_keys_to_filtered_search(input) + input.split("").each do |i| + filtered_search.send_keys(i) + sleep 5 + wait_for_ajax + end + end + + def dropdown_author_size + page.all('#js-dropdown-author .filter-dropdown .filter-dropdown-item').size + end + + def click_author(text) + find('#js-dropdown-author .filter-dropdown .filter-dropdown-item', text: text).click + end + + before do + project.team << [user, :master] + project.team << [user_john, :master] + project.team << [user_jacob, :master] + login_as(user) + create(:issue, project: project) + + visit namespace_project_issues_path(project.namespace, project) + end + + describe 'behavior' do + it 'opens when the search bar has author:' do + filtered_search.set('author:') + + expect(page).to have_css(js_dropdown_author, visible: true) + end + + it 'closes when the search bar is unfocused' do + find('body').click() + + expect(page).to have_css(js_dropdown_author, visible: false) + end + + it 'should show loading indicator when opened' do + filtered_search.set('author:') + + expect(page).to have_css('#js-dropdown-author .filter-dropdown-loading', visible: true) + end + + it 'should hide loading indicator when loaded' do + send_keys_to_filtered_search('author:') + + expect(page).not_to have_css('#js-dropdown-author .filter-dropdown-loading') + end + + it 'should load all the authors when opened' do + send_keys_to_filtered_search('author:') + + expect(dropdown_author_size).to eq(3) + end + + it 'shows current user at top of dropdown' do + send_keys_to_filtered_search('author:') + + expect(first('#js-dropdown-author li')).to have_content(user.name) + end + end + + describe 'filtering' do + before do + filtered_search.set('author') + send_keys_to_filtered_search(':') + end + + it 'filters by name' do + send_keys_to_filtered_search('ja') + + expect(dropdown_author_size).to eq(1) + end + + it 'filters by case insensitive name' do + send_keys_to_filtered_search('Ja') + + expect(dropdown_author_size).to eq(1) + end + + it 'filters by username with symbol' do + send_keys_to_filtered_search('@ot') + + expect(dropdown_author_size).to eq(2) + end + + it 'filters by username without symbol' do + send_keys_to_filtered_search('ot') + + expect(dropdown_author_size).to eq(2) + end + + it 'filters by case insensitive username without symbol' do + send_keys_to_filtered_search('OT') + + expect(dropdown_author_size).to eq(2) + end + end + + describe 'selecting from dropdown' do + before do + filtered_search.set('author') + send_keys_to_filtered_search(':') + end + + it 'fills in the author username when the author has not been filtered' do + click_author(user_jacob.name) + + expect(page).to have_css(js_dropdown_author, visible: false) + expect(filtered_search.value).to eq("author:@#{user_jacob.username} ") + end + + it 'fills in the author username when the author has been filtered' do + click_author(user.name) + + expect(page).to have_css(js_dropdown_author, visible: false) + expect(filtered_search.value).to eq("author:@#{user.username} ") + end + end + + describe 'input has existing content' do + it 'opens author dropdown with existing search term' do + filtered_search.set('searchTerm author:') + + expect(page).to have_css(js_dropdown_author, visible: true) + end + + it 'opens author dropdown with existing assignee' do + filtered_search.set('assignee:@user author:') + + expect(page).to have_css(js_dropdown_author, visible: true) + end + + it 'opens author dropdown with existing label' do + filtered_search.set('label:~bug author:') + + expect(page).to have_css(js_dropdown_author, visible: true) + end + + it 'opens author dropdown with existing milestone' do + filtered_search.set('milestone:%v1.0 author:') + + expect(page).to have_css(js_dropdown_author, visible: true) + end + end +end diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb new file mode 100644 index 00000000000..04dd54ab459 --- /dev/null +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -0,0 +1,134 @@ +require 'rails_helper' + +describe 'Dropdown hint', js: true, feature: true do + include WaitForAjax + + let!(:project) { create(:empty_project) } + let!(:user) { create(:user) } + let(:filtered_search) { find('.filtered-search') } + let(:js_dropdown_hint) { '#js-dropdown-hint' } + + def dropdown_hint_size + page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size + end + + def click_hint(text) + find('#js-dropdown-hint .filter-dropdown .filter-dropdown-item', text: text).click + end + + before do + project.team << [user, :master] + login_as(user) + create(:issue, project: project) + + visit namespace_project_issues_path(project.namespace, project) + end + + describe 'behavior' do + before do + expect(page).to have_css(js_dropdown_hint, visible: false) + filtered_search.click + end + + it 'opens when the search bar is first focused' do + expect(page).to have_css(js_dropdown_hint, visible: true) + end + + it 'closes when the search bar is unfocused' do + find('body').click + + expect(page).to have_css(js_dropdown_hint, visible: false) + end + end + + describe 'filtering' do + it 'does not filter `Keep typing and press Enter`' do + filtered_search.set('randomtext') + + expect(page).to have_css(js_dropdown_hint, text: 'Keep typing and press Enter', visible: false) + expect(dropdown_hint_size).to eq(0) + end + + it 'filters with text' do + filtered_search.set('a') + + expect(dropdown_hint_size).to eq(3) + end + end + + describe 'selecting from dropdown with no input' do + before do + filtered_search.click + end + + it 'opens the author dropdown when you click on author' do + click_hint('author') + + expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css('#js-dropdown-author', visible: true) + expect(filtered_search.value).to eq('author:') + end + + it 'opens the assignee dropdown when you click on assignee' do + click_hint('assignee') + + expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css('#js-dropdown-assignee', visible: true) + expect(filtered_search.value).to eq('assignee:') + end + + it 'opens the milestone dropdown when you click on milestone' do + click_hint('milestone') + + expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css('#js-dropdown-milestone', visible: true) + expect(filtered_search.value).to eq('milestone:') + end + + it 'opens the label dropdown when you click on label' do + click_hint('label') + + expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css('#js-dropdown-label', visible: true) + expect(filtered_search.value).to eq('label:') + end + end + + describe 'selecting from dropdown with some input' do + it 'opens the author dropdown when you click on author' do + filtered_search.set('auth') + click_hint('author') + + expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css('#js-dropdown-author', visible: true) + expect(filtered_search.value).to eq('author:') + end + + it 'opens the assignee dropdown when you click on assignee' do + filtered_search.set('assign') + click_hint('assignee') + + expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css('#js-dropdown-assignee', visible: true) + expect(filtered_search.value).to eq('assignee:') + end + + it 'opens the milestone dropdown when you click on milestone' do + filtered_search.set('mile') + click_hint('milestone') + + expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css('#js-dropdown-milestone', visible: true) + expect(filtered_search.value).to eq('milestone:') + end + + it 'opens the label dropdown when you click on label' do + filtered_search.set('lab') + click_hint('label') + + expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css('#js-dropdown-label', visible: true) + expect(filtered_search.value).to eq('label:') + end + end +end diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb new file mode 100644 index 00000000000..bea00160f96 --- /dev/null +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -0,0 +1,242 @@ +require 'rails_helper' + +describe 'Dropdown label', js: true, feature: true do + include WaitForAjax + + let!(:project) { create(:empty_project) } + let!(:user) { create(:user) } + let!(:bug_label) { create(:label, project: project, title: 'bug') } + let!(:uppercase_label) { create(:label, project: project, title: 'BUG') } + let!(:two_words_label) { create(:label, project: project, title: 'High Priority') } + let!(:wont_fix_label) { create(:label, project: project, title: 'Won"t Fix') } + let!(:wont_fix_single_label) { create(:label, project: project, title: 'Won\'t Fix') } + let!(:special_label) { create(:label, project: project, title: '!@#$%^+&*()')} + let!(:long_label) { create(:label, project: project, title: 'this is a very long title this is a very long title this is a very long title this is a very long title this is a very long title')} + let(:filtered_search) { find('.filtered-search') } + let(:js_dropdown_label) { '#js-dropdown-label' } + + def send_keys_to_filtered_search(input) + input.split("").each do |i| + filtered_search.send_keys(i) + sleep 3 + wait_for_ajax + sleep 3 + end + end + + def dropdown_label_size + page.all('#js-dropdown-label .filter-dropdown .filter-dropdown-item').size + end + + def click_label(text) + find('#js-dropdown-label .filter-dropdown .filter-dropdown-item', text: text).click + end + + before do + project.team << [user, :master] + login_as(user) + create(:issue, project: project) + + visit namespace_project_issues_path(project.namespace, project) + end + + describe 'behavior' do + it 'opens when the search bar has label:' do + filtered_search.set('label:') + + expect(page).to have_css(js_dropdown_label, visible: true) + end + + it 'closes when the search bar is unfocused' do + find('body').click() + + expect(page).to have_css(js_dropdown_label, visible: false) + end + + it 'should show loading indicator when opened' do + filtered_search.set('label:') + + expect(page).to have_css('#js-dropdown-label .filter-dropdown-loading', visible: true) + end + + it 'should hide loading indicator when loaded' do + send_keys_to_filtered_search('label:') + + expect(page).not_to have_css('#js-dropdown-label .filter-dropdown-loading') + end + + it 'should load all the labels when opened' do + send_keys_to_filtered_search('label:') + + expect(dropdown_label_size).to be > 0 + end + end + + describe 'filtering' do + before do + filtered_search.set('label') + end + + it 'filters by name' do + send_keys_to_filtered_search(':b') + + expect(dropdown_label_size).to eq(2) + end + + it 'filters by case insensitive name' do + send_keys_to_filtered_search(':B') + + expect(dropdown_label_size).to eq(2) + end + + it 'filters by name with symbol' do + send_keys_to_filtered_search(':~bu') + + expect(dropdown_label_size).to eq(2) + end + + it 'filters by case insensitive name with symbol' do + send_keys_to_filtered_search(':~BU') + + expect(dropdown_label_size).to eq(2) + end + + it 'filters by multiple words' do + send_keys_to_filtered_search(':Hig') + + expect(dropdown_label_size).to eq(1) + end + + it 'filters by multiple words with symbol' do + send_keys_to_filtered_search(':~Hig') + + expect(dropdown_label_size).to eq(1) + end + + it 'filters by multiple words containing single quotes' do + send_keys_to_filtered_search(':won\'t') + + expect(dropdown_label_size).to eq(1) + end + + it 'filters by multiple words containing single quotes with symbol' do + send_keys_to_filtered_search(':~won\'t') + + expect(dropdown_label_size).to eq(1) + end + + it 'filters by multiple words containing double quotes' do + send_keys_to_filtered_search(':won"t') + + expect(dropdown_label_size).to eq(1) + end + + it 'filters by multiple words containing double quotes with symbol' do + send_keys_to_filtered_search(':~won"t') + + expect(dropdown_label_size).to eq(1) + end + + it 'filters by special characters' do + send_keys_to_filtered_search(':^+') + + expect(dropdown_label_size).to eq(1) + end + + it 'filters by special characters with symbol' do + send_keys_to_filtered_search(':~^+') + + expect(dropdown_label_size).to eq(1) + end + end + + describe 'selecting from dropdown' do + before do + filtered_search.set('label:') + end + + it 'fills in the label name when the label has not been filled' do + click_label(bug_label.title) + + expect(page).to have_css(js_dropdown_label, visible: false) + expect(filtered_search.value).to eq("label:~#{bug_label.title} ") + end + + it 'fills in the label name when the label is partially filled' do + send_keys_to_filtered_search('bu') + click_label(bug_label.title) + + expect(page).to have_css(js_dropdown_label, visible: false) + expect(filtered_search.value).to eq("label:~#{bug_label.title} ") + end + + it 'fills in the label name that contains multiple words' do + click_label(two_words_label.title) + + expect(page).to have_css(js_dropdown_label, visible: false) + expect(filtered_search.value).to eq("label:~\"#{two_words_label.title}\" ") + end + + it 'fills in the label name that contains multiple words and is very long' do + click_label(long_label.title) + + expect(page).to have_css(js_dropdown_label, visible: false) + expect(filtered_search.value).to eq("label:~\"#{long_label.title}\" ") + end + + it 'fills in the label name that contains double quotes' do + click_label(wont_fix_label.title) + + expect(page).to have_css(js_dropdown_label, visible: false) + expect(filtered_search.value).to eq("label:~'#{wont_fix_label.title}' ") + end + + it 'fills in the label name with the correct capitalization' do + click_label(uppercase_label.title) + + expect(page).to have_css(js_dropdown_label, visible: false) + expect(filtered_search.value).to eq("label:~#{uppercase_label.title} ") + end + + it 'fills in the label name with special characters' do + click_label(special_label.title) + + expect(page).to have_css(js_dropdown_label, visible: false) + expect(filtered_search.value).to eq("label:~#{special_label.title} ") + end + + it 'selects `no label`' do + find('#js-dropdown-label .filter-dropdown-item', text: 'No Label').click + + expect(page).to have_css(js_dropdown_label, visible: false) + expect(filtered_search.value).to eq("label:none ") + end + end + + describe 'input has existing content' do + it 'opens label dropdown with existing search term' do + filtered_search.set('searchTerm label:') + expect(page).to have_css(js_dropdown_label, visible: true) + end + + it 'opens label dropdown with existing author' do + filtered_search.set('author:@person label:') + expect(page).to have_css(js_dropdown_label, visible: true) + end + + it 'opens label dropdown with existing assignee' do + filtered_search.set('assignee:@person label:') + expect(page).to have_css(js_dropdown_label, visible: true) + end + + it 'opens label dropdown with existing label' do + filtered_search.set('label:~urgent label:') + expect(page).to have_css(js_dropdown_label, visible: true) + end + + it 'opens label dropdown with existing milestone' do + filtered_search.set('milestone:%v2.0 label:') + expect(page).to have_css(js_dropdown_label, visible: true) + end + end +end diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb new file mode 100644 index 00000000000..134e58ad586 --- /dev/null +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -0,0 +1,222 @@ +require 'rails_helper' + +describe 'Dropdown milestone', js: true, feature: true do + include WaitForAjax + + let!(:project) { create(:empty_project) } + let!(:user) { create(:user) } + let!(:milestone) { create(:milestone, title: 'v1.0', project: project) } + let!(:uppercase_milestone) { create(:milestone, title: 'CAP_MILESTONE', project: project) } + let!(:two_words_milestone) { create(:milestone, title: 'Future Plan', project: project) } + let!(:wont_fix_milestone) { create(:milestone, title: 'Won"t Fix', project: project) } + let!(:special_milestone) { create(:milestone, title: '!@#$%^&*(+)', project: project) } + let!(:long_milestone) { create(:milestone, title: 'this is a very long title this is a very long title this is a very long title this is a very long title this is a very long title', project: project) } + + let(:filtered_search) { find('.filtered-search') } + let(:js_dropdown_milestone) { '#js-dropdown-milestone' } + + def send_keys_to_filtered_search(input) + input.split("").each do |i| + filtered_search.send_keys(i) + sleep 3 + wait_for_ajax + sleep 3 + end + end + + def dropdown_milestone_size + page.all('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item').size + end + + def click_milestone(text) + find('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item', text: text).click + end + + def click_static_milestone(text) + find('#js-dropdown-milestone .filter-dropdown-item', text: text).click + end + + before do + project.team << [user, :master] + login_as(user) + create(:issue, project: project) + + visit namespace_project_issues_path(project.namespace, project) + end + + describe 'behavior' do + it 'opens when the search bar has milestone:' do + filtered_search.set('milestone:') + + expect(page).to have_css(js_dropdown_milestone, visible: true) + end + + it 'closes when the search bar is unfocused' do + find('body').click() + + expect(page).to have_css(js_dropdown_milestone, visible: false) + end + + it 'should show loading indicator when opened' do + filtered_search.set('milestone:') + + expect(page).to have_css('#js-dropdown-milestone .filter-dropdown-loading', visible: true) + end + + it 'should hide loading indicator when loaded' do + send_keys_to_filtered_search('milestone:') + + expect(page).not_to have_css('#js-dropdown-milestone .filter-dropdown-loading') + end + + it 'should load all the milestones when opened' do + send_keys_to_filtered_search('milestone:') + + expect(dropdown_milestone_size).to be > 0 + end + end + + describe 'filtering' do + before do + filtered_search.set('milestone') + end + + it 'filters by name' do + send_keys_to_filtered_search(':v1') + + expect(dropdown_milestone_size).to eq(1) + end + + it 'filters by case insensitive name' do + send_keys_to_filtered_search(':V1') + + expect(dropdown_milestone_size).to eq(1) + end + + it 'filters by name with symbol' do + send_keys_to_filtered_search(':%v1') + + expect(dropdown_milestone_size).to eq(1) + end + + it 'filters by case insensitive name with symbol' do + send_keys_to_filtered_search(':%V1') + + expect(dropdown_milestone_size).to eq(1) + end + + it 'filters by special characters' do + send_keys_to_filtered_search(':(+') + + expect(dropdown_milestone_size).to eq(1) + end + + it 'filters by special characters with symbol' do + send_keys_to_filtered_search(':%(+') + + expect(dropdown_milestone_size).to eq(1) + end + end + + describe 'selecting from dropdown' do + before do + filtered_search.set('milestone:') + end + + it 'fills in the milestone name when the milestone has not been filled' do + click_milestone(milestone.title) + + expect(page).to have_css(js_dropdown_milestone, visible: false) + expect(filtered_search.value).to eq("milestone:%#{milestone.title} ") + end + + it 'fills in the milestone name when the milestone is partially filled' do + send_keys_to_filtered_search('v') + click_milestone(milestone.title) + + expect(page).to have_css(js_dropdown_milestone, visible: false) + expect(filtered_search.value).to eq("milestone:%#{milestone.title} ") + end + + it 'fills in the milestone name that contains multiple words' do + click_milestone(two_words_milestone.title) + + expect(page).to have_css(js_dropdown_milestone, visible: false) + expect(filtered_search.value).to eq("milestone:%\"#{two_words_milestone.title}\" ") + end + + it 'fills in the milestone name that contains multiple words and is very long' do + click_milestone(long_milestone.title) + + expect(page).to have_css(js_dropdown_milestone, visible: false) + expect(filtered_search.value).to eq("milestone:%\"#{long_milestone.title}\" ") + end + + it 'fills in the milestone name that contains double quotes' do + click_milestone(wont_fix_milestone.title) + + expect(page).to have_css(js_dropdown_milestone, visible: false) + expect(filtered_search.value).to eq("milestone:%'#{wont_fix_milestone.title}' ") + end + + it 'fills in the milestone name with the correct capitalization' do + click_milestone(uppercase_milestone.title) + + expect(page).to have_css(js_dropdown_milestone, visible: false) + expect(filtered_search.value).to eq("milestone:%#{uppercase_milestone.title} ") + end + + it 'fills in the milestone name with special characters' do + click_milestone(special_milestone.title) + + expect(page).to have_css(js_dropdown_milestone, visible: false) + expect(filtered_search.value).to eq("milestone:%#{special_milestone.title} ") + end + + it 'selects `no milestone`' do + click_static_milestone('No Milestone') + + expect(page).to have_css(js_dropdown_milestone, visible: false) + expect(filtered_search.value).to eq("milestone:none ") + end + + it 'selects `upcoming milestone`' do + click_static_milestone('Upcoming') + + expect(page).to have_css(js_dropdown_milestone, visible: false) + expect(filtered_search.value).to eq("milestone:upcoming ") + end + end + + describe 'input has existing content' do + it 'opens milestone dropdown with existing search term' do + filtered_search.set('searchTerm milestone:') + + expect(page).to have_css(js_dropdown_milestone, visible: true) + end + + it 'opens milestone dropdown with existing author' do + filtered_search.set('author:@john milestone:') + + expect(page).to have_css(js_dropdown_milestone, visible: true) + end + + it 'opens milestone dropdown with existing assignee' do + filtered_search.set('assignee:@john milestone:') + + expect(page).to have_css(js_dropdown_milestone, visible: true) + end + + it 'opens milestone dropdown with existing label' do + filtered_search.set('label:~important milestone:') + + expect(page).to have_css(js_dropdown_milestone, visible: true) + end + + it 'opens milestone dropdown with existing milestone' do + filtered_search.set('milestone:%100 milestone:') + + expect(page).to have_css(js_dropdown_milestone, visible: true) + end + end +end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb new file mode 100644 index 00000000000..f48a0193545 --- /dev/null +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -0,0 +1,804 @@ +require 'rails_helper' + +describe 'Filter issues', js: true, feature: true do + include WaitForAjax + + let!(:group) { create(:group) } + let!(:project) { create(:project, group: group) } + let!(:user) { create(:user) } + let!(:user2) { create(:user) } + let!(:milestone) { create(:milestone, project: project) } + let!(:label) { create(:label, project: project) } + let!(:wontfix) { create(:label, project: project, title: "Won't fix") } + + let!(:bug_label) { create(:label, project: project, title: 'bug') } + let!(:caps_sensitive_label) { create(:label, project: project, title: 'CAPS_sensitive') } + let!(:milestone) { create(:milestone, title: "8", project: project) } + let!(:multiple_words_label) { create(:label, project: project, title: "Two words") } + + let!(:closed_issue) { create(:issue, title: 'bug that is closed', project: project, state: :closed) } + let(:filtered_search) { find('.filtered-search') } + + def input_filtered_search(search_term, submit: true) + filtered_search.set(search_term) + + if submit + filtered_search.send_keys(:enter) + end + end + + def expect_filtered_search_input(input) + expect(find('.filtered-search').value).to eq(input) + end + + def expect_no_issues_list + page.within '.issues-list' do + expect(page).not_to have_selector('.issue') + end + end + + def expect_issues_list_count(open_count, closed_count = 0) + all_count = open_count + closed_count + + expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count) + page.within '.issues-list' do + expect(page).to have_selector('.issue', count: open_count) + end + end + + def select_search_at_index(pos) + evaluate_script("el = document.querySelector('.filtered-search'); el.focus(); el.setSelectionRange(#{pos}, #{pos});") + end + + before do + project.team << [user, :master] + project.team << [user2, :master] + group.add_developer(user) + group.add_developer(user2) + login_as(user) + create(:issue, project: project) + + create(:issue, title: "Bug report 1", project: project) + create(:issue, title: "Bug report 2", project: project) + create(:issue, title: "issue with 'single quotes'", project: project) + create(:issue, title: "issue with \"double quotes\"", project: project) + create(:issue, title: "issue with !@\#{$%^&*()-+", project: project) + create(:issue, title: "issue by assignee", project: project, milestone: milestone, author: user, assignee: user) + create(:issue, title: "issue by assignee with searchTerm", project: project, milestone: milestone, author: user, assignee: user) + + issue = create(:issue, + title: "Bug 2", + project: project, + milestone: milestone, + author: user, + assignee: user) + issue.labels << bug_label + + issue_with_caps_label = create(:issue, + title: "issue by assignee with searchTerm and label", + project: project, + milestone: milestone, + author: user, + assignee: user) + issue_with_caps_label.labels << caps_sensitive_label + + issue_with_everything = create(:issue, + title: "Bug report with everything you thought was possible", + project: project, + milestone: milestone, + author: user, + assignee: user) + issue_with_everything.labels << bug_label + issue_with_everything.labels << caps_sensitive_label + + multiple_words_label_issue = create(:issue, title: "Issue with multiple words label", project: project) + multiple_words_label_issue.labels << multiple_words_label + + future_milestone = create(:milestone, title: "future", project: project, due_date: Time.now + 1.month) + + create(:issue, + title: "Issue with future milestone", + milestone: future_milestone, + project: project) + + visit namespace_project_issues_path(project.namespace, project) + end + + describe 'filter issues by author' do + context 'only author' do + it 'filters issues by searched author' do + input_filtered_search("author:@#{user.username}") + + expect_issues_list_count(5) + end + + it 'filters issues by invalid author' do + pending('to be tested, issue #26546') + expect(true).to be(false) + end + + it 'filters issues by multiple authors' do + pending('to be tested, issue #26546') + expect(true).to be(false) + end + end + + context 'author with other filters' do + it 'filters issues by searched author and text' do + search = "author:@#{user.username} issue" + input_filtered_search(search) + + expect_issues_list_count(3) + expect_filtered_search_input(search) + end + + it 'filters issues by searched author, assignee and text' do + search = "author:@#{user.username} assignee:@#{user.username} issue" + input_filtered_search(search) + + expect_issues_list_count(3) + expect_filtered_search_input(search) + end + + it 'filters issues by searched author, assignee, label, and text' do + search = "author:@#{user.username} assignee:@#{user.username} label:~#{caps_sensitive_label.title} issue" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'filters issues by searched author, assignee, label, milestone and text' do + search = "author:@#{user.username} assignee:@#{user.username} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} issue" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + end + + it 'sorting' do + pending('to be tested, issue #26546') + expect(true).to be(false) + end + end + + describe 'filter issues by assignee' do + context 'only assignee' do + it 'filters issues by searched assignee' do + search = "assignee:@#{user.username}" + input_filtered_search(search) + + expect_issues_list_count(5) + expect_filtered_search_input(search) + end + + it 'filters issues by no assignee' do + search = "assignee:none" + input_filtered_search(search) + + expect_issues_list_count(8, 1) + expect_filtered_search_input(search) + end + + it 'filters issues by invalid assignee' do + pending('to be tested, issue #26546') + expect(true).to be(false) + end + + it 'filters issues by multiple assignees' do + pending('to be tested, issue #26546') + expect(true).to be(false) + end + end + + context 'assignee with other filters' do + it 'filters issues by searched assignee and text' do + search = "assignee:@#{user.username} searchTerm" + input_filtered_search(search) + + expect_issues_list_count(2) + expect_filtered_search_input(search) + end + + it 'filters issues by searched assignee, author and text' do + search = "assignee:@#{user.username} author:@#{user.username} searchTerm" + input_filtered_search(search) + + expect_issues_list_count(2) + expect_filtered_search_input(search) + end + + it 'filters issues by searched assignee, author, label, text' do + search = "assignee:@#{user.username} author:@#{user.username} label:~#{caps_sensitive_label.title} searchTerm" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'filters issues by searched assignee, author, label, milestone and text' do + search = "assignee:@#{user.username} author:@#{user.username} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} searchTerm" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + end + + context 'sorting' do + it 'sorts' do + pending('to be tested, issue #26546') + expect(true).to be(false) + end + end + end + + describe 'filter issues by label' do + context 'only label' do + it 'filters issues by searched label' do + search = "label:~#{bug_label.title}" + input_filtered_search(search) + + expect_issues_list_count(2) + expect_filtered_search_input(search) + end + + it 'filters issues by no label' do + search = "label:none" + input_filtered_search(search) + + expect_issues_list_count(9, 1) + expect_filtered_search_input(search) + end + + it 'filters issues by invalid label' do + pending('to be tested, issue #26546') + expect(true).to be(false) + end + + it 'filters issues by multiple labels' do + search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title}" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'filters issues by label containing special characters' do + special_label = create(:label, project: project, title: '!@#{$%^&*()-+[]<>?/:{}|\}') + special_issue = create(:issue, title: "Issue with special character label", project: project) + special_issue.labels << special_label + + search = "label:~#{special_label.title}" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'does not show issues' do + new_label = create(:label, project: project, title: "new_label") + + search = "label:~#{new_label.title}" + input_filtered_search(search) + + expect_no_issues_list() + expect_filtered_search_input(search) + end + end + + context 'label with multiple words' do + it 'special characters' do + special_multiple_label = create(:label, project: project, title: "Utmost |mp0rt@nce") + special_multiple_issue = create(:issue, title: "Issue with special character multiple words label", project: project) + special_multiple_issue.labels << special_multiple_label + + search = "label:~'#{special_multiple_label.title}'" + input_filtered_search(search) + + expect_issues_list_count(1) + + # filtered search defaults quotations to double quotes + expect_filtered_search_input("label:~\"#{special_multiple_label.title}\"") + end + + it 'single quotes' do + search = "label:~'#{multiple_words_label.title}'" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input("label:~\"#{multiple_words_label.title}\"") + end + + it 'double quotes' do + search = "label:~\"#{multiple_words_label.title}\"" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'single quotes containing double quotes' do + double_quotes_label = create(:label, project: project, title: 'won"t fix') + double_quotes_label_issue = create(:issue, title: "Issue with double quotes label", project: project) + double_quotes_label_issue.labels << double_quotes_label + + search = "label:~'#{double_quotes_label.title}'" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'double quotes containing single quotes' do + single_quotes_label = create(:label, project: project, title: "won't fix") + single_quotes_label_issue = create(:issue, title: "Issue with single quotes label", project: project) + single_quotes_label_issue.labels << single_quotes_label + + search = "label:~\"#{single_quotes_label.title}\"" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + end + + context 'label with other filters' do + it 'filters issues by searched label and text' do + search = "label:~#{caps_sensitive_label.title} bug" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'filters issues by searched label, author and text' do + search = "label:~#{caps_sensitive_label.title} author:@#{user.username} bug" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'filters issues by searched label, author, assignee and text' do + search = "label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} bug" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'filters issues by searched label, author, assignee, milestone and text' do + search = "label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} milestone:%#{milestone.title} bug" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + end + + context 'multiple labels with other filters' do + it 'filters issues by searched label, label2, and text' do + search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} bug" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'filters issues by searched label, label2, author and text' do + search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} bug" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'filters issues by searched label, label2, author, assignee and text' do + search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} bug" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'filters issues by searched label, label2, author, assignee, milestone and text' do + search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} milestone:%#{milestone.title} bug" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + end + + context 'issue label clicked' do + before do + find('.issues-list .issue .issue-info a .label', text: multiple_words_label.title).click + sleep 1 + end + + it 'filters' do + expect_issues_list_count(1) + end + + it 'displays in search bar' do + expect(find('.filtered-search').value).to eq("label:~\"#{multiple_words_label.title}\"") + end + end + + context 'sorting' do + it 'sorts' do + pending('to be tested, issue #26546') + expect(true).to be(false) + end + end + end + + describe 'filter issues by milestone' do + context 'only milestone' do + it 'filters issues by searched milestone' do + input_filtered_search("milestone:%#{milestone.title}") + + expect_issues_list_count(5) + end + + it 'filters issues by no milestone' do + input_filtered_search("milestone:none") + + expect_issues_list_count(7, 1) + end + + it 'filters issues by upcoming milestones' do + input_filtered_search("milestone:upcoming") + + expect_issues_list_count(1) + end + + it 'filters issues by invalid milestones' do + pending('to be tested, issue #26546') + expect(true).to be(false) + end + + it 'filters issues by multiple milestones' do + pending('to be tested, issue #26546') + expect(true).to be(false) + end + + it 'filters issues by milestone containing special characters' do + special_milestone = create(:milestone, title: '!@\#{$%^&*()}', project: project) + create(:issue, title: "Issue with special character milestone", project: project, milestone: special_milestone) + + search = "milestone:%#{special_milestone.title}" + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'does not show issues' do + new_milestone = create(:milestone, title: "new", project: project) + + search = "milestone:%#{new_milestone.title}" + input_filtered_search(search) + + expect_no_issues_list() + expect_filtered_search_input(search) + end + end + + context 'milestone with other filters' do + it 'filters issues by searched milestone and text' do + search = "milestone:%#{milestone.title} bug" + input_filtered_search(search) + + expect_issues_list_count(2) + expect_filtered_search_input(search) + end + + it 'filters issues by searched milestone, author and text' do + search = "milestone:%#{milestone.title} author:@#{user.username} bug" + input_filtered_search(search) + + expect_issues_list_count(2) + expect_filtered_search_input(search) + end + + it 'filters issues by searched milestone, author, assignee and text' do + search = "milestone:%#{milestone.title} author:@#{user.username} assignee:@#{user.username} bug" + input_filtered_search(search) + + expect_issues_list_count(2) + expect_filtered_search_input(search) + end + + it 'filters issues by searched milestone, author, assignee, label and text' do + search = "milestone:%#{milestone.title} author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} bug" + input_filtered_search(search) + + expect_issues_list_count(2) + expect_filtered_search_input(search) + end + end + + context 'sorting' do + it 'sorts' do + pending('to be tested, issue #26546') + expect(true).to be(false) + end + end + end + + describe 'overwrites selected filter' do + it 'changes author' do + input_filtered_search("author:@#{user.username}", submit: false) + + select_search_at_index(3) + + page.within '#js-dropdown-author' do + click_button user2.username + end + + expect(filtered_search.value).to eq("author:@#{user2.username} ") + end + + it 'changes label' do + input_filtered_search("author:@#{user.username} label:~#{bug_label.title}", submit: false) + + select_search_at_index(27) + + page.within '#js-dropdown-label' do + click_button label.name + end + + expect(filtered_search.value).to eq("author:@#{user.username} label:~#{label.name} ") + end + + it 'changes label correctly space is in previous label' do + input_filtered_search("label:~\"#{multiple_words_label.title}\"", submit: false) + + select_search_at_index(0) + + page.within '#js-dropdown-label' do + click_button label.name + end + + expect(filtered_search.value).to eq("label:~#{label.name} ") + end + end + + describe 'filter issues by text' do + context 'only text' do + it 'filters issues by searched text' do + search = 'Bug' + input_filtered_search(search) + + expect_issues_list_count(4, 1) + expect_filtered_search_input(search) + end + + it 'filters issues by multiple searched text' do + search = 'Bug report' + input_filtered_search(search) + + expect_issues_list_count(3) + expect_filtered_search_input(search) + end + + it 'filters issues by case insensitive searched text' do + search = 'bug report' + input_filtered_search(search) + + expect_issues_list_count(3) + expect_filtered_search_input(search) + end + + it 'filters issues by searched text containing single quotes' do + search = '\'single quotes\'' + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'filters issues by searched text containing double quotes' do + search = '"double quotes"' + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'filters issues by searched text containing special characters' do + search = '!@#{$%^&*()-+' + input_filtered_search(search) + + expect_issues_list_count(1) + expect_filtered_search_input(search) + end + + it 'does not show any issues' do + search = 'testing' + input_filtered_search(search) + + expect_no_issues_list() + expect_filtered_search_input(search) + end + end + + context 'searched text with other filters' do + it 'filters issues by searched text and author' do + input_filtered_search("bug author:@#{user.username}") + + expect_issues_list_count(2) + expect_filtered_search_input("author:@#{user.username} bug") + end + + it 'filters issues by searched text, author and more text' do + input_filtered_search("bug author:@#{user.username} report") + + expect_issues_list_count(1) + expect_filtered_search_input("author:@#{user.username} bug report") + end + + it 'filters issues by searched text, author and assignee' do + input_filtered_search("bug author:@#{user.username} assignee:@#{user.username}") + + expect_issues_list_count(2) + expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} bug") + end + + it 'filters issues by searched text, author, more text and assignee' do + input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username}") + + expect_issues_list_count(1) + expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} bug report") + end + + it 'filters issues by searched text, author, more text, assignee and even more text' do + input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with") + + expect_issues_list_count(1) + expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} bug report with") + end + + it 'filters issues by searched text, author, assignee and label' do + input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title}") + + expect_issues_list_count(2) + expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} bug") + end + + it 'filters issues by searched text, author, text, assignee, text, label and text' do + input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything") + + expect_issues_list_count(1) + expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} bug report with everything") + end + + it 'filters issues by searched text, author, assignee, label and milestone' do + input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} milestone:%#{milestone.title}") + + expect_issues_list_count(2) + expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} milestone:%#{milestone.title} bug") + end + + it 'filters issues by searched text, author, text, assignee, text, label, text, milestone and text' do + input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything milestone:%#{milestone.title} you") + + expect_issues_list_count(1) + expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} milestone:%#{milestone.title} bug report with everything you") + end + + it 'filters issues by searched text, author, assignee, multiple labels and milestone' do + input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title}") + + expect_issues_list_count(1) + expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} bug") + end + + it 'filters issues by searched text, author, text, assignee, text, label1, text, label2, text, milestone and text' do + input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything label:~#{caps_sensitive_label.title} you milestone:%#{milestone.title} thought") + + expect_issues_list_count(1) + expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} bug report with everything you thought") + end + end + + context 'sorting' do + it 'sorts by oldest updated' do + create(:issue, + title: '3 days ago', + project: project, + author: user, + created_at: 3.days.ago, + updated_at: 3.days.ago) + + old_issue = create(:issue, + title: '5 days ago', + project: project, + author: user, + created_at: 5.days.ago, + updated_at: 5.days.ago) + + input_filtered_search('days ago') + + expect_issues_list_count(2) + + sort_toggle = find('.filtered-search-container .dropdown-toggle') + sort_toggle.click + + find('.filtered-search-container .dropdown-menu li a', text: 'Oldest updated').click + wait_for_ajax + + expect(find('.issues-list .issue:first-of-type .issue-title-text a')).to have_content(old_issue.title) + end + end + end + + describe 'retains filter when switching issue states' do + before do + input_filtered_search('bug') + + # Wait for search results to load + sleep 2 + end + + it 'open state' do + find('.issues-state-filters a', text: 'Closed').click + wait_for_ajax + + find('.issues-state-filters a', text: 'Open').click + wait_for_ajax + + expect(page).to have_selector('.issues-list .issue', count: 4) + end + + it 'closed state' do + find('.issues-state-filters a', text: 'Closed').click + wait_for_ajax + + expect(page).to have_selector('.issues-list .issue', count: 1) + expect(find('.issues-list .issue:first-of-type .issue-title-text a')).to have_content(closed_issue.title) + end + + it 'all state' do + find('.issues-state-filters a', text: 'All').click + wait_for_ajax + + expect(page).to have_selector('.issues-list .issue', count: 5) + end + end + + describe 'RSS feeds' do + it 'updates atom feed link for project issues' do + visit namespace_project_issues_path(project.namespace, project, milestone_title: milestone.title, assignee_id: user.id) + link = find('.nav-controls a', text: 'Subscribe') + params = CGI.parse(URI.parse(link[:href]).query) + auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) + auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) + + expect(params).to include('private_token' => [user.private_token]) + expect(params).to include('milestone_title' => [milestone.title]) + expect(params).to include('assignee_id' => [user.id.to_s]) + expect(auto_discovery_params).to include('private_token' => [user.private_token]) + expect(auto_discovery_params).to include('milestone_title' => [milestone.title]) + expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) + end + + it 'updates atom feed link for group issues' do + visit issues_group_path(group, milestone_title: milestone.title, assignee_id: user.id) + link = find('.nav-controls a', text: 'Subscribe') + params = CGI.parse(URI.parse(link[:href]).query) + auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) + auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) + + expect(params).to include('private_token' => [user.private_token]) + expect(params).to include('milestone_title' => [milestone.title]) + expect(params).to include('assignee_id' => [user.id.to_s]) + expect(auto_discovery_params).to include('private_token' => [user.private_token]) + expect(auto_discovery_params).to include('milestone_title' => [milestone.title]) + expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) + end + end +end diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb new file mode 100644 index 00000000000..56b1d354eb0 --- /dev/null +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -0,0 +1,88 @@ +require 'rails_helper' + +describe 'Search bar', js: true, feature: true do + include WaitForAjax + + let!(:project) { create(:empty_project) } + let!(:user) { create(:user) } + let(:filtered_search) { find('.filtered-search') } + + before do + project.team << [user, :master] + login_as(user) + create(:issue, project: project) + + visit namespace_project_issues_path(project.namespace, project) + end + + def get_left_style(style) + left_style = /left:\s\d*[.]\d*px/.match(style) + left_style.to_s.gsub('left: ', '').to_f + end + + describe 'clear search button' do + it 'clears text' do + search_text = 'search_text' + filtered_search.set(search_text) + + expect(filtered_search.value).to eq(search_text) + find('.filtered-search-input-container .clear-search').click + + expect(filtered_search.value).to eq('') + end + + it 'hides by default' do + expect(page).to have_css('.clear-search', visible: false) + end + + it 'hides after clicked' do + filtered_search.set('a') + find('.filtered-search-input-container .clear-search').click + + expect(page).to have_css('.clear-search', visible: false) + end + + it 'hides when there is no text' do + filtered_search.set('a') + filtered_search.set('') + + expect(page).to have_css('.clear-search', visible: false) + end + + it 'shows when there is text' do + filtered_search.set('a') + + expect(page).to have_css('.clear-search', visible: true) + end + + it 'resets the dropdown hint filter' do + filtered_search.click + original_size = page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size + + filtered_search.set('author') + + expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to eq(1) + + find('.filtered-search-input-container .clear-search').click + filtered_search.click + + expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to eq(original_size) + end + + it 'resets the dropdown filters' do + filtered_search.set('a') + hint_style = page.find('#js-dropdown-hint')['style'] + hint_offset = get_left_style(hint_style) + + filtered_search.set('author:') + + expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to eq(0) + + find('.filtered-search-input-container .clear-search').click + filtered_search.click + + expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to be > 0 + expect(get_left_style(page.find('#js-dropdown-hint')['style'])).to eq(hint_offset) + end + end +end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb new file mode 100644 index 00000000000..31156fcf994 --- /dev/null +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -0,0 +1,185 @@ +require 'rails_helper' + +feature 'GFM autocomplete', feature: true, js: true do + include WaitForAjax + let(:user) { create(:user, username: 'someone.special') } + let(:project) { create(:project) } + let(:label) { create(:label, project: project, title: 'special+') } + let(:issue) { create(:issue, project: project) } + + before do + project.team << [user, :master] + login_as(user) + visit namespace_project_issue_path(project.namespace, project, issue) + + wait_for_ajax + end + + it 'opens autocomplete menu when field starts with text' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys('@') + end + + expect(page).to have_selector('.atwho-container') + end + + it 'doesnt open autocomplete menu character is prefixed with text' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('testing') + find('#note_note').native.send_keys('@') + end + + expect(page).not_to have_selector('.atwho-view') + end + + it 'doesnt select the first item for non-assignee dropdowns' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys(':') + end + + expect(page).to have_selector('.atwho-container') + + wait_for_ajax + + expect(find('#at-view-58')).not_to have_selector('.cur:first-of-type') + end + + it 'selects the first item for assignee dropdowns' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys('@') + end + + expect(page).to have_selector('.atwho-container') + + wait_for_ajax + + expect(find('#at-view-64')).to have_selector('.cur:first-of-type') + end + + it 'selects the first item for non-assignee dropdowns if a query is entered' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys(':1') + end + + expect(page).to have_selector('.atwho-container') + + wait_for_ajax + + expect(find('#at-view-58')).to have_selector('.cur:first-of-type') + end + + context 'if a selected value has special characters' do + it 'wraps the result in double quotes' do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys("~#{label.title[0]}") + note.click + end + + label_item = find('.atwho-view li', text: label.title) + + expect_to_wrap(true, label_item, note, label.title) + end + + it "shows dropdown after a new line" do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('test') + note.native.send_keys(:enter) + note.native.send_keys(:enter) + note.native.send_keys('@') + end + + expect(page).to have_selector('.atwho-container') + end + + it "does not show dropdown when preceded with a special character" do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys("@") + note.click + end + + expect(page).to have_selector('.atwho-container') + + page.within '.timeline-content-form' do + note.native.send_keys("@") + note.click + end + + expect(page).to have_selector('.atwho-container', visible: false) + end + + it "does not throw an error if no labels exist" do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys('~') + note.click + end + + expect(page).to have_selector('.atwho-container', visible: false) + end + + it 'doesn\'t wrap for assignee values' do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys("@#{user.username[0]}") + note.click + end + + user_item = find('.atwho-view li', text: user.username) + + expect_to_wrap(false, user_item, note, user.username) + end + + it 'doesn\'t wrap for emoji values' do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys(":cartwheel") + note.click + end + + emoji_item = find('.atwho-view li', text: 'cartwheel_tone1') + + expect_to_wrap(false, emoji_item, note, 'cartwheel_tone1') + end + + it 'doesn\'t open autocomplete after non-word character' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys("@#{user.username[0..2]}!") + end + + expect(page).not_to have_selector('.atwho-view') + end + + it 'doesn\'t open autocomplete if there is no space before' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys("hello:#{user.username[0..2]}") + end + + expect(page).not_to have_selector('.atwho-view') + end + + def expect_to_wrap(should_wrap, item, note, value) + expect(item).to have_content(value) + expect(item).not_to have_content("\"#{value}\"") + + item.click + + if should_wrap + expect(note.value).to include("\"#{value}\"") + else + expect(note.value).not_to include("\"#{value}\"") + end + end + end +end diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index 4b1aec8bf71..bc068b5e7e0 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -1,7 +1,9 @@ require 'rails_helper' feature 'Issue Sidebar', feature: true do - let(:project) { create(:project) } + include WaitForAjax + + let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } let!(:user) { create(:user)} @@ -10,6 +12,37 @@ feature 'Issue Sidebar', feature: true do login_as(user) end + context 'assignee', js: true do + let(:user2) { create(:user) } + let(:issue2) { create(:issue, project: project, author: user2) } + + before do + project.team << [user, :developer] + visit_issue(project, issue2) + + find('.block.assignee .edit-link').click + + wait_for_ajax + end + + it 'shows author in assignee dropdown' do + page.within '.dropdown-menu-user' do + expect(page).to have_content(user2.name) + end + end + + it 'shows author when filtering assignee dropdown' do + page.within '.dropdown-menu-user' do + find('.dropdown-input-field').native.send_keys user2.name + sleep 1 # Required to wait for end of input delay + + wait_for_ajax + + expect(page).to have_content(user2.name) + end + end + end + context 'as a allowed user' do before do project.team << [user, :developer] diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb new file mode 100644 index 00000000000..c8c9c50396b --- /dev/null +++ b/spec/features/issues/markdown_toolbar_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +feature 'Issue markdown toolbar', feature: true, js: true do + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + let(:user) { create(:user) } + + before do + login_as(user) + + visit namespace_project_issue_path(project.namespace, project, issue) + end + + it "doesn't include first new line when adding bold" do + find('#note_note').native.send_keys('test') + find('#note_note').native.send_key(:enter) + find('#note_note').native.send_keys('bold') + + page.evaluate_script('document.querySelectorAll(".js-main-target-form #note_note")[0].setSelectionRange(4, 9)') + + first('.toolbar-btn').click + + expect(find('#note_note')[:value]).to eq("test\n**bold**\n") + end + + it "doesn't include first new line when adding underline" do + find('#note_note').native.send_keys('test') + find('#note_note').native.send_key(:enter) + find('#note_note').native.send_keys('underline') + + page.evaluate_script('document.querySelectorAll(".js-main-target-form #note_note")[0].setSelectionRange(4, 50)') + + find('.toolbar-btn:nth-child(2)').click + + expect(find('#note_note')[:value]).to eq("test\n*underline*\n") + end +end diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 055210399a7..f89b4db9e62 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -27,8 +27,8 @@ feature 'issue move to another project' do let!(:mr) { create(:merge_request, source_project: old_project) } let(:new_project) { create(:project) } let(:new_project_search) { create(:project) } - let(:text) { 'Text with !1' } - let(:cross_reference) { old_project.to_reference } + let(:text) { "Text with #{mr.to_reference}" } + let(:cross_reference) { old_project.to_reference(new_project) } background do old_project.team << [user, :reporter] @@ -43,8 +43,8 @@ feature 'issue move to another project' do expect(current_url).to include project_path(new_project) - expect(page).to have_content("Text with #{cross_reference}!1") - expect(page).to have_content("Moved from #{cross_reference}#1") + expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}") + expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}") expect(page).to have_content(issue.title) end diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb index ab901e74617..a4d3053d10c 100644 --- a/spec/features/issues/new_branch_button_spec.rb +++ b/spec/features/issues/new_branch_button_spec.rb @@ -20,12 +20,12 @@ feature 'Start new branch from an issue', feature: true do context "when there is a referenced merge request" do let!(:note) do create(:note, :on_issue, :system, project: project, noteable: issue, - note: "Mentioned in !#{referenced_mr.iid}") + note: "mentioned in #{referenced_mr.to_reference}") end let(:referenced_mr) do create(:merge_request, :simple, source_project: project, target_project: project, - description: "Fixes ##{issue.iid}", author: user) + description: "Fixes #{issue.to_reference}", author: user) end before do diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb deleted file mode 100644 index c9a3ecf16ea..00000000000 --- a/spec/features/issues/reset_filters_spec.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'rails_helper' - -feature 'Issues filter reset button', feature: true, js: true do - include WaitForAjax - include IssueHelpers - - let!(:project) { create(:project, :public) } - let!(:user) { create(:user)} - let!(:milestone) { create(:milestone, project: project) } - let!(:bug) { create(:label, project: project, name: 'bug')} - let!(:issue1) { create(:issue, project: project, milestone: milestone, author: user, assignee: user, title: 'Feature')} - let!(:issue2) { create(:labeled_issue, project: project, labels: [bug], title: 'Bugfix1')} - - before do - project.team << [user, :developer] - end - - context 'when a milestone filter has been applied' do - it 'resets the milestone filter' do - visit_issues(project, milestone_title: milestone.title) - expect(page).to have_css('.issue', count: 1) - - reset_filters - expect(page).to have_css('.issue', count: 2) - end - end - - context 'when a label filter has been applied' do - it 'resets the label filter' do - visit_issues(project, label_name: bug.name) - expect(page).to have_css('.issue', count: 1) - - reset_filters - expect(page).to have_css('.issue', count: 2) - end - end - - context 'when a text search has been conducted' do - it 'resets the text search filter' do - visit_issues(project, search: 'Bug') - expect(page).to have_css('.issue', count: 1) - - reset_filters - expect(page).to have_css('.issue', count: 2) - end - end - - context 'when author filter has been applied' do - it 'resets the author filter' do - visit_issues(project, author_id: user.id) - expect(page).to have_css('.issue', count: 1) - - reset_filters - expect(page).to have_css('.issue', count: 2) - end - end - - context 'when assignee filter has been applied' do - it 'resets the assignee filter' do - visit_issues(project, assignee_id: user.id) - expect(page).to have_css('.issue', count: 1) - - reset_filters - expect(page).to have_css('.issue', count: 2) - end - end - - context 'when all filters have been applied' do - it 'resets all filters' do - visit_issues(project, assignee_id: user.id, author_id: user.id, milestone_title: milestone.title, label_name: bug.name, search: 'Bug') - expect(page).to have_css('.issue', count: 0) - - reset_filters - expect(page).to have_css('.issue', count: 2) - end - end - - context 'when no filters have been applied' do - it 'the reset link should not be visible' do - visit_issues(project) - expect(page).to have_css('.issue', count: 2) - expect(page).not_to have_css '.reset_filters' - end - end - - def reset_filters - find('.reset-filters').click - end -end diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index de8fdda388d..41ff31d2b99 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -13,8 +13,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do it 'creates todo when clicking button' do page.within '.issuable-sidebar' do - click_button 'Add Todo' - expect(page).to have_content 'Mark Done' + click_button 'Add todo' + expect(page).to have_content 'Mark done' end page.within '.header-content .todos-pending-count' do @@ -30,8 +30,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do it 'marks a todo as done' do page.within '.issuable-sidebar' do - click_button 'Add Todo' - click_button 'Mark Done' + click_button 'Add todo' + click_button 'Mark done' end expect(page).to have_selector('.todos-pending-count', visible: false) diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index 3f2da1c380c..0a9cd11ad6e 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -30,7 +30,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do write_note("/due 2016-08-28") expect(page).not_to have_content '/due 2016-08-28' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' issue.reload @@ -51,7 +51,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do write_note("/due 2016-08-28") expect(page).to have_content '/due 2016-08-28' - expect(page).not_to have_content 'Your commands have been executed!' + expect(page).not_to have_content 'Commands applied' issue.reload @@ -70,7 +70,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do write_note("/remove_due_date") expect(page).not_to have_content '/remove_due_date' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' issue.reload @@ -91,7 +91,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do write_note("/remove_due_date") expect(page).to have_content '/remove_due_date' - expect(page).not_to have_content 'Your commands have been executed!' + expect(page).not_to have_content 'Commands applied' issue.reload @@ -100,6 +100,58 @@ feature 'Issues > User uses slash commands', feature: true, js: true do end end + describe 'Issuable time tracking' do + let(:issue) { create(:issue, project: project) } + + before do + project.team << [user, :developer] + end + + context 'Issue' do + before do + visit namespace_project_issue_path(project.namespace, project, issue) + end + + it_behaves_like 'issuable time tracker' + end + + context 'Merge Request' do + let(:merge_request) { create(:merge_request, source_project: project) } + + before do + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it_behaves_like 'issuable time tracker' + end + end + + describe 'Issuable time tracking' do + let(:issue) { create(:issue, project: project) } + + before do + project.team << [user, :developer] + end + + context 'Issue' do + before do + visit namespace_project_issue_path(project.namespace, project, issue) + end + + it_behaves_like 'issuable time tracker' + end + + context 'Merge Request' do + let(:merge_request) { create(:merge_request, source_project: project) } + + before do + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it_behaves_like 'issuable time tracker' + end + end + describe 'toggling the WIP prefix from the title from note' do let(:issue) { create(:issue, project: project) } diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index cdd02a8c8e3..394eb31aff8 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -371,9 +371,10 @@ describe 'Issues', feature: true do describe 'when I want to reset my incoming email token' do let(:project1) { create(:project, namespace: @user.namespace) } + let!(:issue) { create(:issue, project: project1) } before do - allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true) + stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") project1.team << [@user, :master] visit namespace_project_issues_path(@user.namespace, project1) end @@ -381,11 +382,14 @@ describe 'Issues', feature: true do it 'changes incoming email address token', js: true do find('.issue-email-modal-btn').click previous_token = find('input#issue_email').value - find('.incoming-email-token-reset').click - wait_for_ajax - expect(find('input#issue_email').value).not_to eq(previous_token) + expect(page).to have_no_field('issue_email', with: previous_token) + new_token = project1.new_issue_address(@user.reload) + expect(page).to have_field( + 'issue_email', + with: new_token + ) end end @@ -576,7 +580,10 @@ describe 'Issues', feature: true do describe 'new issue by email' do shared_examples 'show the email in the modal' do + let(:issue) { create(:issue, project: project) } + before do + project.issues << issue stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") visit namespace_project_issues_path(project.namespace, project) @@ -612,14 +619,18 @@ describe 'Issues', feature: true do end it 'adds due date to issue' do + date = Date.today.at_beginning_of_month + 2.days + page.within '.due_date' do click_link 'Edit' page.within '.ui-datepicker-calendar' do - first('.ui-state-default').click + click_link date.day end - expect(page).to have_no_content 'None' + wait_for_ajax + + expect(find('.value').text).to have_content date.strftime('%b %-d, %Y') end end @@ -631,6 +642,8 @@ describe 'Issues', feature: true do first('.ui-state-default').click end + wait_for_ajax + expect(page).to have_no_content 'No due date' click_link 'remove due date' diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb index 7f11db3c417..7f11db3c417 100644 --- a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions.rb +++ b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_requests/cherry_pick_spec.rb index 82bc5226d07..dfe7c910a10 100644 --- a/spec/features/merge_requests/cherry_pick_spec.rb +++ b/spec/features/merge_requests/cherry_pick_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe 'Cherry-pick Merge Requests' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user) } before do diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb new file mode 100644 index 00000000000..c73065cdce1 --- /dev/null +++ b/spec/features/merge_requests/closes_issues_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +feature 'Merge Request closing issues message', feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue_1) { create(:issue, project: project)} + let(:issue_2) { create(:issue, project: project)} + let(:merge_request) do + create( + :merge_request, + :simple, + source_project: project, + description: merge_request_description + ) + end + let(:merge_request_description) { 'Merge Request Description' } + + before do + project.team << [user, :master] + + login_as user + + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + context 'not closing or mentioning any issue' do + it 'does not display closing issue message' do + expect(page).not_to have_css('.mr-widget-footer') + end + end + + context 'closing issues but not mentioning any other issue' do + let(:merge_request_description) { "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}" } + + it 'does not display closing issue message' do + expect(page).to have_content("Accepting this merge request will close issues #{issue_1.to_reference} and #{issue_2.to_reference}") + end + end + + context 'mentioning issues but not closing them' do + let(:merge_request_description) { "Description\n\nRefers to #{issue_1.to_reference} and #{issue_2.to_reference}" } + + it 'does not display closing issue message' do + expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.") + end + end + + context 'closing some issues and mentioning, but not closing, others' do + let(:merge_request_description) { "Description\n\ncloses #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" } + + it 'does not display closing issue message' do + expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.") + end + end +end diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index d258ff52bbb..5bc4ab2dfe5 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -20,7 +20,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do all('button', text: 'Use ours').each do |button| - button.click + button.trigger('click') end end diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index c68e1ea4af9..f1b68a39343 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -40,7 +40,7 @@ feature 'Create New Merge Request', feature: true, js: true do visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id }) - expect(page).not_to have_content private_project.to_reference + expect(page).not_to have_content private_project.path_with_namespace end end @@ -67,4 +67,21 @@ feature 'Create New Merge Request', feature: true, js: true do expect(page).to have_content('Source branch "non-exist-source" does not exist') expect(page).to have_content('Target branch "non-exist-target" does not exist') end + + context 'when a branch contains commits that both delete and add the same image' do + it 'renders the diff successfully' do + visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'deleted-image-test' }) + + click_link "Changes" + + expect(page).to have_content "6049019_460s.jpg" + end + end + + # Isolates a regression (see #24627) + it 'does not show error messages on initial form' do + visit new_namespace_project_merge_request_path(project.namespace, project) + expect(page).not_to have_selector('#error_explanation') + expect(page).not_to have_content('The form contains the following error') + end end diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb index 142649297cc..73c5ef31edc 100644 --- a/spec/features/merge_requests/created_from_fork_spec.rb +++ b/spec/features/merge_requests/created_from_fork_spec.rb @@ -54,14 +54,14 @@ feature 'Merge request created from fork' do scenario 'user visits a pipelines page', js: true do visit_merge_request(merge_request) - page.within('.merge-request-tabs') { click_link 'Builds' } + page.within('.merge-request-tabs') { click_link 'Pipelines' } page.within('table.ci-table') do - expect(page).to have_content 'rspec' - expect(page).to have_content 'spinach' + expect(page).to have_content pipeline.status + expect(page).to have_content pipeline.id end - expect(find_link('Cancel running')[:href]) + expect(page.find('a.btn-remove')[:href]) .to include fork_project.path_with_namespace end end diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_requests/deleted_source_branch_spec.rb new file mode 100644 index 00000000000..0952b17b63e --- /dev/null +++ b/spec/features/merge_requests/deleted_source_branch_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +# This test serves as a regression test for a bug that caused an error +# message to be shown by JavaScript when the source branch was deleted. +# Please do not remove "js: true". +describe 'Deleted source branch', feature: true, js: true do + include WaitForAjax + + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request) } + + before do + login_as user + merge_request.project.team << [user, :master] + merge_request.update!(source_branch: 'this-branch-does-not-exist') + visit namespace_project_merge_request_path( + merge_request.project.namespace, + merge_request.project, + merge_request + ) + end + + it 'shows a message about missing source branch' do + expect(page).to have_content( + 'Source branch this-branch-does-not-exist does not exist' + ) + end + + it 'still contains Discussion, Commits and Changes tabs' do + within '.merge-request-details' do + expect(page).to have_content('Discussion') + expect(page).to have_content('Commits') + expect(page).to have_content('Changes') + end + + click_on 'Changes' + wait_for_ajax + + expect(page).to have_selector('.diffs.tab-pane .nothing-here-block') + expect(page).to have_content('Nothing to merge from this-branch-does-not-exist into feature') + end +end diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb index 5e6d8467217..d5e3d8e7eff 100644 --- a/spec/features/merge_requests/diff_notes_resolve_spec.rb +++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb @@ -69,8 +69,6 @@ feature 'Diff notes resolve', feature: true, js: true do page.within '.diff-content .note' do expect(page).to have_selector('.line-resolve-btn.is-active') - - expect(find('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}") end page.within '.line-resolve-all-container' do diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb index c9a0059645d..4a6c76a5caf 100644 --- a/spec/features/merge_requests/diffs_spec.rb +++ b/spec/features/merge_requests/diffs_spec.rb @@ -22,4 +22,18 @@ feature 'Diffs URL', js: true, feature: true do expect(page).to have_css('.diffs.tab-pane.active') end end + + context 'when merge request has overflow' do + it 'displays warning' do + allow_any_instance_of(MergeRequestDiff).to receive(:overflow?).and_return(true) + allow(Commit).to receive(:max_diff_options).and_return(max_files: 20, max_lines: 20) + + visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + + page.within('.alert') do + expect(page).to have_text("Too many changes to show. Plain diff Email patch To preserve + performance only 3 of 3+ files are displayed.") + end + end + end end diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb index c46bd8d449f..cb3bc392903 100644 --- a/spec/features/merge_requests/edit_mr_spec.rb +++ b/spec/features/merge_requests/edit_mr_spec.rb @@ -40,5 +40,32 @@ feature 'Edit Merge Request', feature: true do expect(page).to have_content 'Remove source branch' end + + it 'should preserve description textarea height', js: true do + long_description = %q( + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ac ornare ligula, ut tempus arcu. Etiam ultricies accumsan dolor vitae faucibus. Donec at elit lacus. Mauris orci ante, aliquam quis lorem eget, convallis faucibus arcu. Aenean at pulvinar lacus. Ut viverra quam massa, molestie ornare tortor dignissim a. Suspendisse tristique pellentesque tellus, id lacinia metus elementum id. Nam tristique, arcu rhoncus faucibus viverra, lacus ipsum sagittis ligula, vitae convallis odio lacus a nibh. Ut tincidunt est purus, ac vestibulum augue maximus in. Suspendisse vel erat et mi ultricies semper. Pellentesque volutpat pellentesque consequat. + + Cras congue nec ligula tristique viverra. Curabitur fringilla fringilla fringilla. Donec rhoncus dignissim orci ut accumsan. Ut rutrum urna a rhoncus varius. Maecenas blandit, mauris nec accumsan gravida, augue nibh finibus magna, sed maximus turpis libero nec neque. Suspendisse at semper est. Nunc imperdiet dapibus dui, varius sollicitudin erat luctus non. Sed pellentesque ligula eget posuere facilisis. Donec dictum commodo volutpat. Donec egestas dui ac magna sollicitudin bibendum. Vivamus purus neque, ullamcorper ac feugiat et, tempus sit amet metus. Praesent quis viverra neque. Sed bibendum viverra est, eu aliquam mi ornare vitae. Proin et dapibus ipsum. Nunc tortor diam, malesuada nec interdum vel, placerat quis justo. Ut viverra at erat eu laoreet. + + Pellentesque commodo, diam sit amet dignissim condimentum, tortor justo pretium est, non venenatis metus eros ut nunc. Etiam ut neque eget sem dapibus aliquam. Curabitur vel elit lorem. Nulla nec enim elit. Sed ut ex id justo facilisis convallis at ac augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nullam cursus egestas turpis non tristique. Suspendisse in erat sem. Fusce libero elit, fermentum gravida mauris id, auctor iaculis felis. Nullam vulputate tempor laoreet. + + Nam tempor et magna sed convallis. Fusce sit amet sollicitudin risus, a ullamcorper lacus. Morbi gravida quis sem eget porttitor. Donec eu egestas mauris, in elementum tortor. Sed eget ex mi. Mauris iaculis tortor ut est auctor, nec dignissim quam sagittis. Suspendisse vel metus non quam suscipit tincidunt. Cras molestie lacus non justo finibus sodales quis vitae erat. In a porttitor nisi, id sollicitudin urna. Ut at felis tellus. Suspendisse potenti. + + Maecenas leo ligula, varius at neque vitae, ornare maximus justo. Nullam convallis luctus risus et vulputate. Duis suscipit faucibus iaculis. Etiam quis tortor faucibus, tristique tellus sit amet, sodales neque. Nulla dapibus nisi vel aliquet consequat. Etiam faucibus, metus eget condimentum iaculis, enim urna lobortis sem, id efficitur eros sapien nec nisi. Aenean ut finibus ex. + ) + + fill_in 'merge_request_description', with: long_description + + height = get_textarea_height + find('.js-md-preview-button').click + find('.js-md-write-button').click + new_height = get_textarea_height + + expect(height).to eq(new_height) + end + + def get_textarea_height + page.evaluate_script('document.getElementById("merge_request_description").offsetHeight') + end end end diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb index 0253629f753..4c60329865c 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/merge_requests/filter_by_labels_spec.rb @@ -7,25 +7,27 @@ feature 'Issue filtering by Labels', feature: true, js: true do let!(:user) { create(:user) } let!(:label) { create(:label, project: project) } - before do - bug = create(:label, project: project, title: 'bug') - feature = create(:label, project: project, title: 'feature') - enhancement = create(:label, project: project, title: 'enhancement') + let!(:bug) { create(:label, project: project, title: 'bug') } + let!(:feature) { create(:label, project: project, title: 'feature') } + let!(:enhancement) { create(:label, project: project, title: 'enhancement') } + + let!(:mr1) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "bugfix1") } + let!(:mr2) { create(:merge_request, title: "Bugfix2", source_project: project, target_project: project, source_branch: "bugfix2") } + let!(:mr3) { create(:merge_request, title: "Feature1", source_project: project, target_project: project, source_branch: "feature1") } - issue1 = create(:issue, title: "Bugfix1", project: project) - issue1.labels << bug + before do + mr1.labels << bug - issue2 = create(:issue, title: "Bugfix2", project: project) - issue2.labels << bug - issue2.labels << enhancement + mr2.labels << bug + mr2.labels << enhancement - issue3 = create(:issue, title: "Feature1", project: project) - issue3.labels << feature + mr3.title = "Feature1" + mr3.labels << feature project.team << [user, :master] login_as(user) - visit namespace_project_issues_path(project.namespace, project) + visit namespace_project_merge_requests_path(project.namespace, project) end context 'filter by label bug' do diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb index 2798db92f0f..4642b5a530d 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/merge_requests/filter_merge_requests_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'Filter issues', feature: true do +describe 'Filter merge requests', feature: true do include WaitForAjax let!(:project) { create(:project) } @@ -14,12 +14,12 @@ describe 'Filter issues', feature: true do project.team << [user, :master] group.add_developer(user) login_as(user) - create(:issue, project: project) + create(:merge_request, source_project: project, target_project: project) end - describe 'for assignee from issues#index' do + describe 'for assignee from mr#index' do before do - visit namespace_project_issues_path(project.namespace, project) + visit namespace_project_merge_requests_path(project.namespace, project) find('.js-assignee-search').click @@ -47,9 +47,9 @@ describe 'Filter issues', feature: true do end end - describe 'for milestone from issues#index' do + describe 'for milestone from mr#index' do before do - visit namespace_project_issues_path(project.namespace, project) + visit namespace_project_merge_requests_path(project.namespace, project) find('.js-milestone-select').click @@ -77,9 +77,9 @@ describe 'Filter issues', feature: true do end end - describe 'for label from issues#index', js: true do + describe 'for label from mr#index', js: true do before do - visit namespace_project_issues_path(project.namespace, project) + visit namespace_project_merge_requests_path(project.namespace, project) find('.js-label-select').click wait_for_ajax end @@ -158,13 +158,13 @@ describe 'Filter issues', feature: true do describe 'for assignee and label from issues#index' do before do - visit namespace_project_issues_path(project.namespace, project) + visit namespace_project_merge_requests_path(project.namespace, project) find('.js-assignee-search').click find('.dropdown-menu-user-link', text: user.username).click - expect(page).not_to have_selector('.issues-list .issue') + expect(page).not_to have_selector('.mr-list .merge-request') find('.js-label-select').click @@ -196,38 +196,40 @@ describe 'Filter issues', feature: true do end end - describe 'filter issues by text' do + describe 'filter merge requests by text' do before do - create(:issue, title: "Bug", project: project) + create(:merge_request, title: "Bug", source_project: project, target_project: project, source_branch: "bug") bug_label = create(:label, project: project, title: 'bug') milestone = create(:milestone, title: "8", project: project) - issue = create(:issue, - title: "Bug 2", - project: project, + mr = create(:merge_request, + title: "Bug 2", + source_project: project, + target_project: project, + source_branch: "bug2", milestone: milestone, author: user, assignee: user) - issue.labels << bug_label + mr.labels << bug_label - visit namespace_project_issues_path(project.namespace, project) + visit namespace_project_merge_requests_path(project.namespace, project) end context 'only text', js: true do - it 'filters issues by searched text' do + it 'filters merge requests by searched text' do fill_in 'issuable_search', with: 'Bug' - page.within '.issues-list' do - expect(page).to have_selector('.issue', count: 2) + page.within '.mr-list' do + expect(page).to have_selector('.merge-request', count: 2) end end - it 'does not show any issues' do + it 'does not show any merge requests' do fill_in 'issuable_search', with: 'testing' - page.within '.issues-list' do - expect(page).not_to have_selector('.issue') + page.within '.mr-list' do + expect(page).not_to have_selector('.merge-request') end end end @@ -237,8 +239,8 @@ describe 'Filter issues', feature: true do fill_in 'issuable_search', with: 'Bug' expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) - page.within '.issues-list' do - expect(page).to have_selector('.issue', count: 2) + page.within '.mr-list' do + expect(page).to have_selector('.merge-request', count: 2) end click_button 'Label' @@ -248,8 +250,8 @@ describe 'Filter issues', feature: true do find('.dropdown-menu-close-icon').click expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) - page.within '.issues-list' do - expect(page).to have_selector('.issue', count: 1) + page.within '.mr-list' do + expect(page).to have_selector('.merge-request', count: 1) end end @@ -257,8 +259,8 @@ describe 'Filter issues', feature: true do fill_in 'issuable_search', with: 'Bug' expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) - page.within '.issues-list' do - expect(page).to have_selector('.issue', count: 2) + page.within '.mr-list' do + expect(page).to have_selector('.merge-request', count: 2) end click_button 'Milestone' @@ -267,8 +269,8 @@ describe 'Filter issues', feature: true do end expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) - page.within '.issues-list' do - expect(page).to have_selector('.issue', count: 1) + page.within '.mr-list' do + expect(page).to have_selector('.merge-request', count: 1) end end @@ -276,8 +278,8 @@ describe 'Filter issues', feature: true do fill_in 'issuable_search', with: 'Bug' expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) - page.within '.issues-list' do - expect(page).to have_selector('.issue', count: 2) + page.within '.mr-list' do + expect(page).to have_selector('.merge-request', count: 2) end click_button 'Assignee' @@ -286,8 +288,8 @@ describe 'Filter issues', feature: true do end expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) - page.within '.issues-list' do - expect(page).to have_selector('.issue', count: 1) + page.within '.mr-list' do + expect(page).to have_selector('.merge-request', count: 1) end end @@ -295,8 +297,8 @@ describe 'Filter issues', feature: true do fill_in 'issuable_search', with: 'Bug' expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) - page.within '.issues-list' do - expect(page).to have_selector('.issue', count: 2) + page.within '.mr-list' do + expect(page).to have_selector('.merge-request', count: 2) end click_button 'Author' @@ -305,26 +307,27 @@ describe 'Filter issues', feature: true do end expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) - page.within '.issues-list' do - expect(page).to have_selector('.issue', count: 1) + page.within '.mr-list' do + expect(page).to have_selector('.merge-request', count: 1) end end end end - describe 'filter issues and sort', js: true do + describe 'filter merge requests and sort', js: true do before do bug_label = create(:label, project: project, title: 'bug') - bug_one = create(:issue, title: "Frontend", project: project) - bug_two = create(:issue, title: "Bug 2", project: project) - bug_one.labels << bug_label - bug_two.labels << bug_label + mr1 = create(:merge_request, title: "Frontend", source_project: project, target_project: project, source_branch: "Frontend") + mr2 = create(:merge_request, title: "Bug 2", source_project: project, target_project: project, source_branch: "bug2") - visit namespace_project_issues_path(project.namespace, project) + mr1.labels << bug_label + mr2.labels << bug_label + + visit namespace_project_merge_requests_path(project.namespace, project) end - it 'is able to filter and sort issues' do + it 'is able to filter and sort merge requests' do click_button 'Label' wait_for_ajax page.within '.labels-filter' do @@ -334,8 +337,8 @@ describe 'Filter issues', feature: true do wait_for_ajax expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) - page.within '.issues-list' do - expect(page).to have_selector('.issue', count: 2) + page.within '.mr-list' do + expect(page).to have_selector('.merge-request', count: 2) end click_button 'Last created' @@ -344,41 +347,9 @@ describe 'Filter issues', feature: true do end wait_for_ajax - page.within '.issues-list' do + page.within '.mr-list' do expect(page).to have_content('Frontend') end end end - - it 'updates atom feed link for project issues' do - visit namespace_project_issues_path(project.namespace, project, milestone_title: '', assignee_id: user.id) - - link = find('.nav-controls a', text: 'Subscribe') - params = CGI::parse(URI.parse(link[:href]).query) - auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) - auto_discovery_params = CGI::parse(URI.parse(auto_discovery_link[:href]).query) - - expect(params).to include('private_token' => [user.private_token]) - expect(params).to include('milestone_title' => ['']) - expect(params).to include('assignee_id' => [user.id.to_s]) - expect(auto_discovery_params).to include('private_token' => [user.private_token]) - expect(auto_discovery_params).to include('milestone_title' => ['']) - expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) - end - - it 'updates atom feed link for group issues' do - visit issues_group_path(group, milestone_title: '', assignee_id: user.id) - - link = find('.nav-controls a', text: 'Subscribe') - params = CGI::parse(URI.parse(link[:href]).query) - auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) - auto_discovery_params = CGI::parse(URI.parse(auto_discovery_link[:href]).query) - - expect(params).to include('private_token' => [user.private_token]) - expect(params).to include('milestone_title' => ['']) - expect(params).to include('assignee_id' => [user.id.to_s]) - expect(auto_discovery_params).to include('private_token' => [user.private_token]) - expect(auto_discovery_params).to include('milestone_title' => ['']) - expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) - end end diff --git a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb new file mode 100644 index 00000000000..3dbe26cddb0 --- /dev/null +++ b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +feature 'Clicking toggle commit message link', feature: true, js: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue_1) { create(:issue, project: project)} + let(:issue_2) { create(:issue, project: project)} + let(:merge_request) do + create( + :merge_request, + :simple, + source_project: project, + description: "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}" + ) + end + let(:textbox) { page.find(:css, '.js-commit-message', visible: false) } + let(:include_link) { page.find(:css, '.js-with-description-link', visible: false) } + let(:do_not_include_link) { page.find(:css, '.js-without-description-link', visible: false) } + let(:default_message) do + [ + "Merge branch 'feature' into 'master'", + merge_request.title, + "Closes #{issue_1.to_reference} and #{issue_2.to_reference}", + "See merge request #{merge_request.to_reference}" + ].join("\n\n") + end + let(:message_with_description) do + [ + "Merge branch 'feature' into 'master'", + merge_request.title, + merge_request.description, + "See merge request #{merge_request.to_reference}" + ].join("\n\n") + end + + before do + project.team << [user, :master] + + login_as user + + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + + expect(textbox).not_to be_visible + click_link "Modify commit message" + expect(textbox).to be_visible + end + + it "toggles commit message between message with description and without description " do + expect(textbox.value).to eq(default_message) + + click_link "Include description in commit message" + + expect(textbox.value).to eq(message_with_description) + + click_link "Don't include description in commit message" + + expect(textbox.value).to eq(default_message) + end + + it "toggles link between 'Include description' and 'Don't include description'" do + expect(include_link).to be_visible + expect(do_not_include_link).not_to be_visible + + click_link "Include description in commit message" + + expect(include_link).not_to be_visible + expect(do_not_include_link).to be_visible + + click_link "Don't include description in commit message" + + expect(include_link).to be_visible + expect(do_not_include_link).not_to be_visible + end +end diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb new file mode 100644 index 00000000000..f2f8f11ab28 --- /dev/null +++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +feature 'Merge immediately', :feature, :js do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, + author: user, + title: 'Bug NS-04') + end + + let(:pipeline) do + create(:ci_pipeline, project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch) + end + + before { project.team << [user, :master] } + + context 'when there is active pipeline for merge request' do + background do + create(:ci_build, pipeline: pipeline) + end + + before do + login_as user + visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) + end + + it 'enables merge immediately' do + page.within '.mr-widget-body' do + find('.dropdown-toggle').click + + click_link 'Merge Immediately' + + expect(find('.js-merge-button')).to have_content('Merge in progress') + end + end + end +end diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb index 23cee891bac..04e85ed3f73 100644 --- a/spec/features/merge_requests/merge_request_versions_spec.rb +++ b/spec/features/merge_requests/merge_request_versions_spec.rb @@ -3,11 +3,12 @@ require 'spec_helper' feature 'Merge Request versions', js: true, feature: true do let(:merge_request) { create(:merge_request, importing: true) } let(:project) { merge_request.source_project } + let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } + let!(:merge_request_diff2) { merge_request.merge_request_diffs.create(head_commit_sha: nil) } + let!(:merge_request_diff3) { merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') } before do login_as :admin - merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') - merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) end @@ -23,7 +24,7 @@ feature 'Merge Request versions', js: true, feature: true do before do page.within '.mr-version-dropdown' do find('.btn-default').click - click_link 'version 1' + find(:link, 'version 1').trigger('click') end end @@ -44,7 +45,7 @@ feature 'Merge Request versions', js: true, feature: true do before do page.within '.mr-version-compare-dropdown' do find('.btn-default').click - click_link 'version 1' + find(:link, 'version 1').trigger('click') end end @@ -53,7 +54,7 @@ feature 'Merge Request versions', js: true, feature: true do project.namespace, project, merge_request.iid, - diff_id: 2, + diff_id: merge_request_diff3.id, start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' ) end @@ -80,4 +81,52 @@ feature 'Merge Request versions', js: true, feature: true do expect(page).to have_content '8 changed files' end end + + describe 'compare with same version' do + before do + page.within '.mr-version-compare-dropdown' do + find('.btn-default').click + click_link 'version 1' + end + end + + it 'should have 0 chages between versions' do + page.within '.mr-version-compare-dropdown' do + expect(page).to have_content 'version 1' + end + + page.within '.mr-version-dropdown' do + find('.btn-default').click + find(:link, 'version 1').trigger('click') + end + + expect(page).to have_content '0 changed files' + end + end + + describe 'compare with newer version' do + before do + page.within '.mr-version-compare-dropdown' do + find('.btn-default').click + click_link 'version 2' + end + end + + it 'should set the compared versions to be the same' do + page.within '.mr-version-compare-dropdown' do + expect(page).to have_content 'version 2' + end + + page.within '.mr-version-dropdown' do + find('.btn-default').click + find(:link, 'version 1').trigger('click') + end + + page.within '.mr-version-compare-dropdown' do + expect(page).to have_content 'version 1' + end + + expect(page).to have_content '0 changed files' + end + end end diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb deleted file mode 100644 index c3c3ab33872..00000000000 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ /dev/null @@ -1,108 +0,0 @@ -require 'spec_helper' - -feature 'Merge When Build Succeeds', feature: true, js: true do - let(:user) { create(:user) } - let(:project) { create(:project, :public) } - - let(:merge_request) do - create(:merge_request_with_diffs, source_project: project, - author: user, - title: 'Bug NS-04') - end - - let(:pipeline) do - create(:ci_pipeline, project: project, - sha: merge_request.diff_head_sha, - ref: merge_request.source_branch) - end - - before { project.team << [user, :master] } - - context 'when there is active build for merge request' do - background do - create(:ci_build, pipeline: pipeline) - end - - before do - login_as user - visit_merge_request(merge_request) - end - - it 'displays the Merge When Build Succeeds button' do - expect(page).to have_button "Merge When Build Succeeds" - end - - context "Merge When Build succeeds enabled" do - before do - click_button "Merge When Build Succeeds" - end - - it 'activates Merge When Build Succeeds feature' do - expect(page).to have_link "Cancel Automatic Merge" - - expect(page).to have_content "Set by #{user.name} to be merged automatically when the build succeeds." - expect(page).to have_content "The source branch will not be removed." - - visit_merge_request(merge_request) # Needed to refresh the page - expect(page).to have_content /Enabled an automatic merge when the build for [0-9a-f]{8} succeeds/i - end - end - end - - context 'when merge when build succeeds is enabled' do - let(:merge_request) do - create(:merge_request_with_diffs, :simple, source_project: project, - author: user, - merge_user: user, - title: 'MepMep', - merge_when_build_succeeds: true) - end - - let!(:build) do - create(:ci_build, pipeline: pipeline) - end - - before do - login_as user - visit_merge_request(merge_request) - end - - it 'allows to cancel the automatic merge' do - click_link "Cancel Automatic Merge" - - expect(page).to have_button "Merge When Build Succeeds" - - visit_merge_request(merge_request) # refresh the page - expect(page).to have_content "Canceled the automatic merge" - end - - it "allows the user to remove the source branch" do - expect(page).to have_link "Remove Source Branch When Merged" - - click_link "Remove Source Branch When Merged" - expect(page).to have_content "The source branch will be removed" - end - - context 'when build succeeds' do - background { build.success } - - it 'merges merge request' do - visit_merge_request(merge_request) # refresh the page - - expect(page).to have_content 'The changes were merged' - expect(merge_request.reload).to be_merged - end - end - end - - context 'when build is not active' do - it "does not allow to enable merge when build succeeds" do - visit_merge_request(merge_request) - expect(page).not_to have_link "Merge When Build Succeeds" - end - end - - def visit_merge_request(merge_request) - visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) - end -end diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb new file mode 100644 index 00000000000..2ea9c317bd1 --- /dev/null +++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb @@ -0,0 +1,151 @@ +require 'spec_helper' + +feature 'Merge When Pipeline Succeeds', :feature, :js do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, + author: user, + title: 'Bug NS-04') + end + + let(:pipeline) do + create(:ci_pipeline, project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch) + end + + before { project.team << [user, :master] } + + context 'when there is active pipeline for merge request' do + background do + create(:ci_build, pipeline: pipeline) + end + + before do + login_as user + visit_merge_request(merge_request) + end + + it 'displays the Merge When Pipeline Succeeds button' do + expect(page).to have_button "Merge When Pipeline Succeeds" + end + + describe 'enabling Merge When Pipeline Succeeds' do + shared_examples 'Merge When Pipeline Succeeds activator' do + it 'activates the Merge When Pipeline Succeeds feature' do + click_button "Merge When Pipeline Succeeds" + + expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds." + expect(page).to have_content "The source branch will not be removed." + expect(page).to have_link "Cancel Automatic Merge" + visit_merge_request(merge_request) # Needed to refresh the page + expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i + end + end + + context "when enabled immediately" do + it_behaves_like 'Merge When Pipeline Succeeds activator' + end + + context 'when enabled after pipeline status changed' do + before do + pipeline.run! + + # We depend on merge request widget being reloaded + # so we have to wait for asynchronous call to reload it + # and have_content expectation handles that. + # + expect(page).to have_content "Pipeline ##{pipeline.id} running" + end + + it_behaves_like 'Merge When Pipeline Succeeds activator' + end + + context 'when enabled after it was previously canceled' do + before do + click_button "Merge When Pipeline Succeeds" + click_link "Cancel Automatic Merge" + end + + it_behaves_like 'Merge When Pipeline Succeeds activator' + end + + context 'when it was enabled and then canceled' do + let(:merge_request) do + create(:merge_request_with_diffs, + :merge_when_build_succeeds, + source_project: project, + title: 'Bug NS-04', + author: user, + merge_user: user) + end + + before do + click_link "Cancel Automatic Merge" + end + + it_behaves_like 'Merge When Pipeline Succeeds activator' + end + end + end + + context 'when merge when pipeline succeeds is enabled' do + let(:merge_request) do + create(:merge_request_with_diffs, :simple, source_project: project, + author: user, + merge_user: user, + title: 'MepMep', + merge_when_build_succeeds: true) + end + + let!(:build) do + create(:ci_build, pipeline: pipeline) + end + + before do + login_as user + visit_merge_request(merge_request) + end + + it 'allows to cancel the automatic merge' do + click_link "Cancel Automatic Merge" + + expect(page).to have_button "Merge When Pipeline Succeeds" + + visit_merge_request(merge_request) # refresh the page + expect(page).to have_content "canceled the automatic merge" + end + + it "allows the user to remove the source branch" do + expect(page).to have_link "Remove Source Branch When Merged" + + click_link "Remove Source Branch When Merged" + expect(page).to have_content "The source branch will be removed" + end + + context 'when pipeline succeeds' do + background { build.success } + + it 'merges merge request' do + visit_merge_request(merge_request) # refresh the page + + expect(page).to have_content 'The changes were merged' + expect(merge_request.reload).to be_merged + end + end + end + + context 'when pipeline is not active' do + it "does not allow to enable merge when pipeline succeeds" do + visit_merge_request(merge_request) + + expect(page).not_to have_link 'Merge When Pipeline Succeeds' + end + end + + def visit_merge_request(merge_request) + visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) + end +end diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb index 80e8b8fc642..7e2907cd26f 100644 --- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb +++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' feature 'Only allow merge requests to be merged if the build succeeds', feature: true do - let(:project) { create(:project, :public) } - let(:merge_request) { create(:merge_request_with_diffs, source_project: project) } + let(:merge_request) { create(:merge_request_with_diffs) } + let(:project) { merge_request.target_project } before do login_as merge_request.author @@ -19,7 +19,13 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: end context 'when project has CI enabled' do - let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) } + given!(:pipeline) do + create(:ci_empty_pipeline, + project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch, + status: status) + end context 'when merge requests can only be merged if the build succeeds' do before do @@ -27,18 +33,29 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: end context 'when CI is running' do - before { pipeline.update_column(:status, :running) } + given(:status) { :running } it 'does not allow to merge immediately' do visit_merge_request(merge_request) - expect(page).to have_button 'Merge When Build Succeeds' + expect(page).to have_button 'Merge When Pipeline Succeeds' expect(page).not_to have_button 'Select Merge Moment' end end context 'when CI failed' do - before { pipeline.update_column(:status, :failed) } + given(:status) { :failed } + + it 'does not allow MR to be merged' do + visit_merge_request(merge_request) + + expect(page).not_to have_button 'Accept Merge Request' + expect(page).to have_content('Please retry the build or push a new commit to fix the failure.') + end + end + + context 'when CI canceled' do + given(:status) { :canceled } it 'does not allow MR to be merged' do visit_merge_request(merge_request) @@ -49,7 +66,17 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: end context 'when CI succeeded' do - before { pipeline.update_column(:status, :success) } + given(:status) { :success } + + it 'allows MR to be merged' do + visit_merge_request(merge_request) + + expect(page).to have_button 'Accept Merge Request' + end + end + + context 'when CI skipped' do + given(:status) { :skipped } it 'allows MR to be merged' do visit_merge_request(merge_request) @@ -65,12 +92,12 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: end context 'when CI is running' do - before { pipeline.update_column(:status, :running) } + given(:status) { :running } it 'allows MR to be merged immediately', js: true do visit_merge_request(merge_request) - expect(page).to have_button 'Merge When Build Succeeds' + expect(page).to have_button 'Merge When Pipeline Succeeds' click_button 'Select Merge Moment' expect(page).to have_content 'Merge Immediately' @@ -78,7 +105,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: end context 'when CI failed' do - before { pipeline.update_column(:status, :failed) } + given(:status) { :failed } it 'allows MR to be merged' do visit_merge_request(merge_request) @@ -88,7 +115,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: end context 'when CI succeeded' do - before { pipeline.update_column(:status, :success) } + given(:status) { :success } it 'allows MR to be merged' do visit_merge_request(merge_request) diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb new file mode 100644 index 00000000000..3a7ece7e1d6 --- /dev/null +++ b/spec/features/merge_requests/reset_filters_spec.rb @@ -0,0 +1,96 @@ +require 'rails_helper' + +feature 'Issues filter reset button', feature: true, js: true do + include WaitForAjax + include IssueHelpers + + let!(:project) { create(:project, :public) } + let!(:user) { create(:user)} + let!(:milestone) { create(:milestone, project: project) } + let!(:bug) { create(:label, project: project, name: 'bug')} + let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "Feature", milestone: milestone, author: user, assignee: user) } + let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "Bugfix1") } + + let(:merge_request_css) { '.merge-request' } + + before do + mr2.labels << bug + project.team << [user, :developer] + end + + context 'when a milestone filter has been applied' do + it 'resets the milestone filter' do + visit_merge_requests(project, milestone_title: milestone.title) + expect(page).to have_css(merge_request_css, count: 1) + + reset_filters + expect(page).to have_css(merge_request_css, count: 2) + end + end + + context 'when a label filter has been applied' do + it 'resets the label filter' do + visit_merge_requests(project, label_name: bug.name) + expect(page).to have_css(merge_request_css, count: 1) + + reset_filters + expect(page).to have_css(merge_request_css, count: 2) + end + end + + context 'when a text search has been conducted' do + it 'resets the text search filter' do + visit_merge_requests(project, search: 'Bug') + expect(page).to have_css(merge_request_css, count: 1) + + reset_filters + expect(page).to have_css(merge_request_css, count: 2) + end + end + + context 'when author filter has been applied' do + it 'resets the author filter' do + visit_merge_requests(project, author_id: user.id) + expect(page).to have_css(merge_request_css, count: 1) + + reset_filters + expect(page).to have_css(merge_request_css, count: 2) + end + end + + context 'when assignee filter has been applied' do + it 'resets the assignee filter' do + visit_merge_requests(project, assignee_id: user.id) + expect(page).to have_css(merge_request_css, count: 1) + + reset_filters + expect(page).to have_css(merge_request_css, count: 2) + end + end + + context 'when all filters have been applied' do + it 'resets all filters' do + visit_merge_requests(project, assignee_id: user.id, author_id: user.id, milestone_title: milestone.title, label_name: bug.name, search: 'Bug') + expect(page).to have_css(merge_request_css, count: 0) + + reset_filters + expect(page).to have_css(merge_request_css, count: 2) + end + end + + context 'when no filters have been applied' do + it 'the reset link should not be visible' do + visit_merge_requests(project) + expect(page).to have_css(merge_request_css, count: 2) + expect(page).not_to have_css '.reset_filters' + end + end + + def visit_merge_requests(project, opts = {}) + visit namespace_project_merge_requests_path project.namespace, project, opts + end + + def reset_filters + find('.reset-filters').click + end +end diff --git a/spec/features/merge_requests/target_branch_spec.rb b/spec/features/merge_requests/target_branch_spec.rb new file mode 100644 index 00000000000..b6134540273 --- /dev/null +++ b/spec/features/merge_requests/target_branch_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'Target branch', feature: true do + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } + + def path_to_merge_request + namespace_project_merge_request_path( + project.namespace, + project, merge_request + ) + end + + before do + login_as user + project.team << [user, :master] + end + + it 'shows link to target branch' do + visit path_to_merge_request + expect(page).to have_link('feature', href: namespace_project_commits_path(project.namespace, project, merge_request.target_branch)) + end + + context 'when branch was deleted' do + before do + DeleteBranchService.new(project, user).execute('feature') + visit path_to_merge_request + end + + it 'shows a message about missing target branch' do + expect(page).to have_content( + 'Target branch feature does not exist' + ) + end + + it 'does not show link to target branch' do + expect(page).not_to have_link('feature') + end + end +end diff --git a/spec/features/merge_requests/toggle_whitespace_changes.rb b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb index 0f98737b700..0f98737b700 100644 --- a/spec/features/merge_requests/toggle_whitespace_changes.rb +++ b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index 7b8af555f0e..b13674b4db9 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -31,7 +31,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do write_note("/wip") expect(page).not_to have_content '/wip' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' expect(merge_request.reload.work_in_progress?).to eq true end @@ -42,7 +42,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do write_note("/wip") expect(page).not_to have_content '/wip' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' expect(merge_request.reload.work_in_progress?).to eq false end @@ -61,13 +61,58 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do write_note("/wip") expect(page).not_to have_content '/wip' - expect(page).not_to have_content 'Your commands have been executed!' + expect(page).not_to have_content 'Commands applied' expect(merge_request.reload.work_in_progress?).to eq false end end end + describe 'merging the MR from the note' do + context 'when the current user can merge the MR' do + it 'merges the MR' do + write_note("/merge") + + expect(page).to have_content 'Commands applied' + + expect(merge_request.reload).to be_merged + end + end + + context 'when the head diff changes in the meanwhile' do + before do + merge_request.source_branch = 'another_branch' + merge_request.save + end + + it 'does not merge the MR' do + write_note("/merge") + + expect(page).not_to have_content 'Your commands have been executed!' + + expect(merge_request.reload).not_to be_merged + end + end + + context 'when the current user cannot merge the MR' do + let(:guest) { create(:user) } + before do + project.team << [guest, :guest] + logout + login_with(guest) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'does not merge the MR' do + write_note("/merge") + + expect(page).not_to have_content 'Your commands have been executed!' + + expect(merge_request.reload).not_to be_merged + end + end + end + describe 'adding a due date from note' do it 'does not recognize the command nor create a note' do write_note('/due 2016-08-28') diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_requests/wip_message_spec.rb new file mode 100644 index 00000000000..3311731b33b --- /dev/null +++ b/spec/features/merge_requests/wip_message_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +feature 'Work In Progress help message', feature: true do + let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let!(:user) { create(:user) } + + before do + project.team << [user, :master] + login_as(user) + end + + context 'with WIP commits' do + it 'shows a specific WIP hint' do + visit new_namespace_project_merge_request_path( + project.namespace, + project, + merge_request: { + source_project_id: project.id, + target_project_id: project.id, + source_branch: 'wip', + target_branch: 'master' + } + ) + + within_wip_explanation do + expect(page).to have_text( + 'It looks like you have some WIP commits in this branch' + ) + end + end + end + + context 'without WIP commits' do + it 'shows the regular WIP message' do + visit new_namespace_project_merge_request_path( + project.namespace, + project, + merge_request: { + source_project_id: project.id, + target_project_id: project.id, + source_branch: 'fix', + target_branch: 'master' + } + ) + + within_wip_explanation do + expect(page).not_to have_text( + 'It looks like you have some WIP commits in this branch' + ) + expect(page).to have_text( + "Start the title with WIP: to prevent a Work In Progress merge \ +request from being merged before it's ready" + ) + end + end + end + + def within_wip_explanation(&block) + page.within '.js-no-wip-explanation' do + yield + end + end +end diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index b8c838bf7ab..a2e40546588 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -14,12 +14,17 @@ feature 'Milestone', feature: true do feature 'Create a milestone' do scenario 'shows an informative message for a new milestone' do visit new_namespace_project_milestone_path(project.namespace, project) + page.within '.milestone-form' do fill_in "milestone_title", with: '8.7' + fill_in "milestone_start_date", with: '2016-11-16' + fill_in "milestone_due_date", with: '2016-12-16' end + find('input[name="commit"]').click expect(find('.alert-success')).to have_content('Assign some issues to this milestone.') + expect(page).to have_content('Nov 16, 2016 - Dec 16, 2016') end end diff --git a/spec/features/milestones/milestones_spec.rb b/spec/features/milestones/milestones_spec.rb index 8b603f51545..aadd72a9f8e 100644 --- a/spec/features/milestones/milestones_spec.rb +++ b/spec/features/milestones/milestones_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' describe 'Milestone draggable', feature: true, js: true do + include WaitForAjax + let(:milestone) { create(:milestone, project: project, title: 8.14) } let(:project) { create(:empty_project, :public) } let(:user) { create(:user) } @@ -74,6 +76,8 @@ describe 'Milestone draggable', feature: true, js: true do visit namespace_project_milestone_path(project.namespace, project, milestone) issue.drag_to(issue_target) + + wait_for_ajax end def create_and_drag_merge_request(params = {}) @@ -82,5 +86,7 @@ describe 'Milestone draggable', feature: true, js: true do visit namespace_project_milestone_path(project.namespace, project, milestone) page.find("a[href='#tab-merge-requests']").click merge_request.drag_to(merge_request_target) + + wait_for_ajax end end diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb new file mode 100644 index 00000000000..40b4dc63697 --- /dev/null +++ b/spec/features/milestones/show_spec.rb @@ -0,0 +1,26 @@ +require 'rails_helper' + +describe 'Milestone show', feature: true do + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + let(:milestone) { create(:milestone, project: project) } + let(:labels) { create_list(:label, 2, project: project) } + let(:issue_params) { { project: project, assignee: user, author: user, milestone: milestone, labels: labels } } + + before do + project.add_user(user, :developer) + login_as(user) + end + + def visit_milestone + visit namespace_project_milestone_path(project.namespace, project, milestone) + end + + it 'avoids N+1 database queries' do + create(:labeled_issue, issue_params) + control_count = ActiveRecord::QueryRecorder.new { visit_milestone }.count + create_list(:labeled_issue, 10, issue_params) + + expect { visit_milestone }.not_to exceed_query_limit(control_count) + end +end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 5d7247e2a62..b785b2f7704 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -70,16 +70,15 @@ describe 'Comments', feature: true do end describe 'when editing a note', js: true do - it 'contains the hidden edit form' do - page.within("#note_#{note.id}") do - is_expected.to have_css('.note-edit-form', visible: false) - end + it 'there should be a hidden edit form' do + is_expected.to have_css('.note-edit-form:not(.mr-note-edit-form)', visible: false, count: 1) + is_expected.to have_css('.note-edit-form.mr-note-edit-form', visible: false, count: 1) end describe 'editing the note' do before do find('.note').hover - find(".js-note-edit").click + find('.js-note-edit').click end it 'shows the note edit form and hide the note body' do @@ -90,14 +89,29 @@ describe 'Comments', feature: true do end end - # TODO: fix after 7.7 release - # it "should reset the edit note form textarea with the original content of the note if cancelled" do - # within(".current-note-edit-form") do - # fill_in "note[note]", with: "Some new content" - # find(".btn-cancel").click - # expect(find(".js-note-text", visible: false).text).to eq note.note - # end - # end + it 'should reset the edit note form textarea with the original content of the note if cancelled' do + within('.current-note-edit-form') do + fill_in 'note[note]', with: 'Some new content' + find('.btn-cancel').click + expect(find('.js-note-text', visible: false).text).to eq '' + end + end + + it 'allows using markdown buttons after saving a note and then trying to edit it again' do + page.within('.current-note-edit-form') do + fill_in 'note[note]', with: 'This is the new content' + find('.btn-save').click + end + + find('.note').hover + find('.js-note-edit').click + + page.within('.current-note-edit-form') do + expect(find('#note_note').value).to eq('This is the new content') + find('.js-md:first-child').click + expect(find('#note_note').value).to eq('This is the new content****') + end + end it 'appends the edited at time to the note' do page.within('.current-note-edit-form') do @@ -141,7 +155,7 @@ describe 'Comments', feature: true do let(:project2) { create(:project, :private) } let(:issue) { create(:issue, project: project2) } let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') } - let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "Mentioned in #{issue.to_reference(project)}") } + let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") } it 'shows the system note' do login_as :admin diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb index a78a1c9c890..c2545b0c259 100644 --- a/spec/features/participants_autocomplete_spec.rb +++ b/spec/features/participants_autocomplete_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' feature 'Member autocomplete', feature: true do + include WaitForAjax + let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:participant) { create(:user) } @@ -79,11 +81,10 @@ feature 'Member autocomplete', feature: true do end def open_member_suggestions - sleep 1 page.within('.new-note') do - sleep 1 - find('#note_note').native.send_keys('@') + find('#note_note').send_keys('@') end + wait_for_ajax end def visit_issue(project, issue) diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb new file mode 100644 index 00000000000..6f6f7029c0b --- /dev/null +++ b/spec/features/profiles/chat_names_spec.rb @@ -0,0 +1,77 @@ +require 'rails_helper' + +feature 'Profile > Chat', feature: true do + given(:user) { create(:user) } + given(:service) { create(:service) } + + before do + login_as(user) + end + + describe 'uses authorization link' do + given(:params) do + { team_id: 'T00', team_domain: 'my_chat_team', user_id: 'U01', user_name: 'my_chat_user' } + end + given!(:authorize_url) { ChatNames::AuthorizeUserService.new(service, params).execute } + given(:authorize_path) { URI.parse(authorize_url).request_uri } + + before do + visit authorize_path + end + + context 'clicks authorize' do + before do + click_button 'Authorize' + end + + scenario 'goes to list of chat names and see chat account' do + expect(page.current_path).to eq(profile_chat_names_path) + expect(page).to have_content('my_chat_team') + expect(page).to have_content('my_chat_user') + end + + scenario 'second use of link is denied' do + visit authorize_path + + expect(page).to have_http_status(:not_found) + end + end + + context 'clicks deny' do + before do + click_button 'Deny' + end + + scenario 'goes to list of chat names and do not see chat account' do + expect(page.current_path).to eq(profile_chat_names_path) + expect(page).not_to have_content('my_chat_team') + expect(page).not_to have_content('my_chat_user') + end + + scenario 'second use of link is denied' do + visit authorize_path + + expect(page).to have_http_status(:not_found) + end + end + end + + describe 'visits chat accounts' do + given!(:chat_name) { create(:chat_name, user: user, service: service) } + + before do + visit profile_chat_names_path + end + + scenario 'sees chat user' do + expect(page).to have_content(chat_name.team_domain) + expect(page).to have_content(chat_name.chat_name) + end + + scenario 'removes chat account' do + click_link 'Remove' + + expect(page).to have_content("You don't have any active chat names.") + end + end +end diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index a85930c7543..55a01057c83 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -27,28 +27,25 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do describe "token creation" do it "allows creation of a token" do - visit profile_personal_access_tokens_path - fill_in "Name", with: FFaker::Product.brand - - expect {click_on "Create Personal Access Token"}.to change { PersonalAccessToken.count }.by(1) - expect(created_personal_access_token).to eq(PersonalAccessToken.last.token) - expect(active_personal_access_tokens).to have_text(PersonalAccessToken.last.name) - expect(active_personal_access_tokens).to have_text("Never") - end + name = FFaker::Product.brand - it "allows creation of a token with an expiry date" do visit profile_personal_access_tokens_path - fill_in "Name", with: FFaker::Product.brand + fill_in "Name", with: name # Set date to 1st of next month find_field("Expires at").trigger('focus') find("a[title='Next']").click click_on "1" - expect {click_on "Create Personal Access Token"}.to change { PersonalAccessToken.count }.by(1) - expect(created_personal_access_token).to eq(PersonalAccessToken.last.token) - expect(active_personal_access_tokens).to have_text(PersonalAccessToken.last.name) + # Scopes + check "api" + check "read_user" + + click_on "Create Personal Access Token" + expect(active_personal_access_tokens).to have_text(name) expect(active_personal_access_tokens).to have_text(Date.today.next_month.at_beginning_of_month.to_s(:medium)) + expect(active_personal_access_tokens).to have_text('api') + expect(active_personal_access_tokens).to have_text('read_user') end context "when creation fails" do @@ -85,7 +82,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do disallow_personal_access_token_saves! visit profile_personal_access_tokens_path - expect { click_on "Revoke" }.not_to change { PersonalAccessToken.inactive.count } + click_on "Revoke" expect(active_personal_access_tokens).to have_text(personal_access_token.name) expect(page).to have_content("Could not revoke") end diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb index d14a1158b67..a6b841c0210 100644 --- a/spec/features/profiles/preferences_spec.rb +++ b/spec/features/profiles/preferences_spec.rb @@ -73,7 +73,7 @@ describe 'Profile > Preferences', feature: true do expect(page.current_path).to eq starred_dashboard_projects_path end - click_link 'Your Projects' + click_link 'Your projects' expect(page).not_to have_content("You don't have starred projects yet") expect(page.current_path).to eq dashboard_projects_path diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb new file mode 100644 index 00000000000..a820d07ab3b --- /dev/null +++ b/spec/features/projects/blobs/edit_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +feature 'Editing file blob', feature: true, js: true do + include WaitForAjax + + given(:user) { create(:user) } + given(:role) { :developer } + given(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') } + given(:project) { merge_request.target_project } + + background do + login_as(user) + project.team << [user, role] + end + + def edit_and_commit + wait_for_ajax + first('.file-actions').click_link 'Edit' + execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")') + click_button 'Commit Changes' + end + + context 'from MR diff' do + before do + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + edit_and_commit + end + + scenario 'returns me to the mr' do + expect(page).to have_content(merge_request.title) + end + end + + context 'from blob file path' do + before do + visit namespace_project_blob_path(project.namespace, project, '/feature/files/ruby/feature.rb') + edit_and_commit + end + + scenario 'updates content' do + expect(page).to have_content 'successfully committed' + expect(page).to have_content 'NextFeature' + end + end +end diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index a8022a5361f..11d27feab0b 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -1,52 +1,60 @@ require 'spec_helper' require 'tempfile' -describe "Builds" do - let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } +feature 'Builds', :feature do + let(:user) { create(:user) } + let(:user_access_level) { :developer } + let(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + let(:build) { create(:ci_build, :trace, pipeline: pipeline) } + let(:build2) { create(:ci_build) } + + let(:artifacts_file) do + fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') + end before do - login_as(:user) - @commit = FactoryGirl.create :ci_pipeline - @build = FactoryGirl.create :ci_build, :trace, pipeline: @commit - @build2 = FactoryGirl.create :ci_build - @project = @commit.project - @project.team << [@user, :developer] + project.team << [user, user_access_level] + login_as(user) end describe "GET /:project/builds" do + let!(:build) { create(:ci_build, pipeline: pipeline) } + context "Pending scope" do before do - visit namespace_project_builds_path(@project.namespace, @project, scope: :pending) + visit namespace_project_builds_path(project.namespace, project, scope: :pending) end it "shows Pending tab builds" do expect(page).to have_link 'Cancel running' expect(page).to have_selector('.nav-links li.active', text: 'Pending') - expect(page).to have_content @build.short_sha - expect(page).to have_content @build.ref - expect(page).to have_content @build.name + expect(page).to have_content build.short_sha + expect(page).to have_content build.ref + expect(page).to have_content build.name end end context "Running scope" do before do - @build.run! - visit namespace_project_builds_path(@project.namespace, @project, scope: :running) + build.run! + visit namespace_project_builds_path(project.namespace, project, scope: :running) end it "shows Running tab builds" do expect(page).to have_selector('.nav-links li.active', text: 'Running') expect(page).to have_link 'Cancel running' - expect(page).to have_content @build.short_sha - expect(page).to have_content @build.ref - expect(page).to have_content @build.name + expect(page).to have_content build.short_sha + expect(page).to have_content build.ref + expect(page).to have_content build.name end end context "Finished scope" do before do - @build.run! - visit namespace_project_builds_path(@project.namespace, @project, scope: :finished) + build.run! + visit namespace_project_builds_path(project.namespace, project, scope: :finished) end it "shows Finished tab builds" do @@ -58,15 +66,15 @@ describe "Builds" do context "All builds" do before do - @project.builds.running_or_pending.each(&:success) - visit namespace_project_builds_path(@project.namespace, @project) + project.builds.running_or_pending.each(&:success) + visit namespace_project_builds_path(project.namespace, project) end it "shows All tab builds" do expect(page).to have_selector('.nav-links li.active', text: 'All') - expect(page).to have_content @build.short_sha - expect(page).to have_content @build.ref - expect(page).to have_content @build.name + expect(page).to have_content build.short_sha + expect(page).to have_content build.ref + expect(page).to have_content build.name expect(page).not_to have_link 'Cancel running' end end @@ -74,17 +82,17 @@ describe "Builds" do describe "POST /:project/builds/:id/cancel_all" do before do - @build.run! - visit namespace_project_builds_path(@project.namespace, @project) + build.run! + visit namespace_project_builds_path(project.namespace, project) click_link "Cancel running" end it 'shows all necessary content' do expect(page).to have_selector('.nav-links li.active', text: 'All') expect(page).to have_content 'canceled' - expect(page).to have_content @build.short_sha - expect(page).to have_content @build.ref - expect(page).to have_content @build.name + expect(page).to have_content build.short_sha + expect(page).to have_content build.ref + expect(page).to have_content build.name expect(page).not_to have_link 'Cancel running' end end @@ -92,20 +100,20 @@ describe "Builds" do describe "GET /:project/builds/:id" do context "Build from project" do before do - visit namespace_project_build_path(@project.namespace, @project, @build) + visit namespace_project_build_path(project.namespace, project, build) end it 'shows commit`s data' do expect(page.status_code).to eq(200) - expect(page).to have_content @commit.sha[0..7] - expect(page).to have_content @commit.git_commit_message - expect(page).to have_content @commit.git_author_name + expect(page).to have_content pipeline.sha[0..7] + expect(page).to have_content pipeline.git_commit_message + expect(page).to have_content pipeline.git_author_name end end context "Build from other project" do before do - visit namespace_project_build_path(@project.namespace, @project, @build2) + visit namespace_project_build_path(project.namespace, project, build2) end it { expect(page.status_code).to eq(404) } @@ -113,8 +121,8 @@ describe "Builds" do context "Download artifacts" do before do - @build.update_attributes(artifacts_file: artifacts_file) - visit namespace_project_build_path(@project.namespace, @project, @build) + build.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_build_path(project.namespace, project, build) end it 'has button to download artifacts' do @@ -124,8 +132,10 @@ describe "Builds" do context 'Artifacts expire date' do before do - @build.update_attributes(artifacts_file: artifacts_file, artifacts_expire_at: expire_at) - visit namespace_project_build_path(@project.namespace, @project, @build) + build.update_attributes(artifacts_file: artifacts_file, + artifacts_expire_at: expire_at) + + visit namespace_project_build_path(project.namespace, project, build) end context 'no expire date defined' do @@ -139,12 +149,23 @@ describe "Builds" do context 'when expire date is defined' do let(:expire_at) { Time.now + 7.days } - it 'keeps artifacts when Keep button is clicked' do - expect(page).to have_content 'The artifacts will be removed' - click_link 'Keep' + context 'when user has ability to update build' do + it 'keeps artifacts when keep button is clicked' do + expect(page).to have_content 'The artifacts will be removed' - expect(page).not_to have_link 'Keep' - expect(page).not_to have_content 'The artifacts will be removed' + click_link 'Keep' + + expect(page).to have_no_link 'Keep' + expect(page).to have_no_content 'The artifacts will be removed' + end + end + + context 'when user does not have ability to update build' do + let(:user_access_level) { :guest } + + it 'does not have keep button' do + expect(page).to have_no_link 'Keep' + end end end @@ -158,10 +179,10 @@ describe "Builds" do end end - context 'Build raw trace' do + feature 'Raw trace' do before do - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) + build.run! + visit namespace_project_build_path(project.namespace, project, build) end it do @@ -169,11 +190,43 @@ describe "Builds" do end end - describe 'Variables' do + feature 'HTML trace', :js do before do - @trigger_request = create :ci_trigger_request_with_variables - @build = create :ci_build, pipeline: @commit, trigger_request: @trigger_request - visit namespace_project_build_path(@project.namespace, @project, @build) + build.run! + + visit namespace_project_build_path(project.namespace, project, build) + end + + context 'when build has an initial trace' do + it 'loads build trace' do + expect(page).to have_content 'BUILD TRACE' + + build.append_trace(' and more trace', 11) + + expect(page).to have_content 'BUILD TRACE and more trace' + end + end + + context 'when build does not have an initial trace' do + let(:build) { create(:ci_build, pipeline: pipeline) } + + it 'loads new trace' do + build.append_trace('build trace', 0) + + expect(page).to have_content 'build trace' + end + end + end + + feature 'Variables' do + let(:trigger_request) { create(:ci_trigger_request_with_variables) } + + let(:build) do + create :ci_build, pipeline: pipeline, trigger_request: trigger_request + end + + before do + visit namespace_project_build_path(project.namespace, project, build) end it 'shows variable key and value after click', js: true do @@ -188,13 +241,50 @@ describe "Builds" do expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1') end end + + context 'when build starts environment' do + let(:environment) { create(:environment, project: project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + context 'build is successfull and has deployment' do + let(:deployment) { create(:deployment) } + let(:build) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) } + + it 'shows a link for the build' do + visit namespace_project_build_path(project.namespace, project, build) + + expect(page).to have_link environment.name + end + end + + context 'build is complete and not successfull' do + let(:build) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) } + + it 'shows a link for the build' do + visit namespace_project_build_path(project.namespace, project, build) + + expect(page).to have_link environment.name + end + end + + context 'build creates a new deployment' do + let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) } + let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) } + + it 'shows a link to lastest deployment' do + visit namespace_project_build_path(project.namespace, project, build) + + expect(page).to have_link('latest deployment') + end + end + end end describe "POST /:project/builds/:id/cancel" do context "Build from project" do before do - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) + build.run! + visit namespace_project_build_path(project.namespace, project, build) click_link "Cancel" end @@ -207,9 +297,9 @@ describe "Builds" do context "Build from other project" do before do - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) - page.driver.post(cancel_namespace_project_build_path(@project.namespace, @project, @build2)) + build.run! + visit namespace_project_build_path(project.namespace, project, build) + page.driver.post(cancel_namespace_project_build_path(project.namespace, project, build2)) end it { expect(page.status_code).to eq(404) } @@ -219,8 +309,8 @@ describe "Builds" do describe "POST /:project/builds/:id/retry" do context "Build from project" do before do - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) + build.run! + visit namespace_project_build_path(project.namespace, project, build) click_link 'Cancel' page.within('.build-header') do click_link 'Retry build' @@ -238,10 +328,10 @@ describe "Builds" do context "Build from other project" do before do - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) + build.run! + visit namespace_project_build_path(project.namespace, project, build) click_link 'Cancel' - page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2)) + page.driver.post(retry_namespace_project_build_path(project.namespace, project, build2)) end it { expect(page).to have_http_status(404) } @@ -249,13 +339,13 @@ describe "Builds" do context "Build that current user is not allowed to retry" do before do - @build.run! - @build.cancel! - @project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + build.run! + build.cancel! + project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) logout_direct login_with(create(:user)) - visit namespace_project_build_path(@project.namespace, @project, @build) + visit namespace_project_build_path(project.namespace, project, build) end it 'does not show the Retry button' do @@ -268,15 +358,15 @@ describe "Builds" do describe "GET /:project/builds/:id/download" do before do - @build.update_attributes(artifacts_file: artifacts_file) - visit namespace_project_build_path(@project.namespace, @project, @build) + build.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_build_path(project.namespace, project, build) click_link 'Download' end context "Build from other project" do before do - @build2.update_attributes(artifacts_file: artifacts_file) - visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build2) + build2.update_attributes(artifacts_file: artifacts_file) + visit download_namespace_project_build_artifacts_path(project.namespace, project, build2) end it { expect(page.status_code).to eq(404) } @@ -288,23 +378,23 @@ describe "Builds" do context 'build from project' do before do Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) + build.run! + visit namespace_project_build_path(project.namespace, project, build) page.within('.js-build-sidebar') { click_link 'Raw' } end it 'sends the right headers' do expect(page.status_code).to eq(200) expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') - expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace) + expect(page.response_headers['X-Sendfile']).to eq(build.path_to_trace) end end context 'build from other project' do before do Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') - @build2.run! - visit raw_namespace_project_build_path(@project.namespace, @project, @build2) + build2.run! + visit raw_namespace_project_build_path(project.namespace, project, build2) end it 'sends the right headers' do @@ -325,8 +415,8 @@ describe "Builds" do context 'when build has trace in file' do before do Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) + build.run! + visit namespace_project_build_path(project.namespace, project, build) allow_any_instance_of(Project).to receive(:ci_id).and_return(nil) allow_any_instance_of(Ci::Build).to receive(:path_to_trace).and_return(existing_file) @@ -345,8 +435,8 @@ describe "Builds" do context 'when build has trace in old file' do before do Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) + build.run! + visit namespace_project_build_path(project.namespace, project, build) allow_any_instance_of(Project).to receive(:ci_id).and_return(999) allow_any_instance_of(Ci::Build).to receive(:path_to_trace).and_return(non_existing_file) @@ -365,8 +455,8 @@ describe "Builds" do context 'when build has trace in DB' do before do Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) + build.run! + visit namespace_project_build_path(project.namespace, project, build) allow_any_instance_of(Project).to receive(:ci_id).and_return(nil) allow_any_instance_of(Ci::Build).to receive(:path_to_trace).and_return(non_existing_file) @@ -385,7 +475,7 @@ describe "Builds" do describe "GET /:project/builds/:id/trace.json" do context "Build from project" do before do - visit trace_namespace_project_build_path(@project.namespace, @project, @build, format: :json) + visit trace_namespace_project_build_path(project.namespace, project, build, format: :json) end it { expect(page.status_code).to eq(200) } @@ -393,7 +483,7 @@ describe "Builds" do context "Build from other project" do before do - visit trace_namespace_project_build_path(@project.namespace, @project, @build2, format: :json) + visit trace_namespace_project_build_path(project.namespace, project, build2, format: :json) end it { expect(page.status_code).to eq(404) } @@ -403,7 +493,7 @@ describe "Builds" do describe "GET /:project/builds/:id/status" do context "Build from project" do before do - visit status_namespace_project_build_path(@project.namespace, @project, @build) + visit status_namespace_project_build_path(project.namespace, project, build) end it { expect(page.status_code).to eq(200) } @@ -411,7 +501,7 @@ describe "Builds" do context "Build from other project" do before do - visit status_namespace_project_build_path(@project.namespace, @project, @build2) + visit status_namespace_project_build_path(project.namespace, project, build2) end it { expect(page.status_code).to eq(404) } diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb index fcdf7870f34..33f1c323af1 100644 --- a/spec/features/projects/commit/builds_spec.rb +++ b/spec/features/projects/commit/builds_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'project commit builds' do +feature 'project commit pipelines' do given(:project) { create(:project) } background do @@ -16,11 +16,13 @@ feature 'project commit builds' do ref: 'master') end - scenario 'user views commit builds page' do - visit builds_namespace_project_commit_path(project.namespace, - project, project.commit.sha) + scenario 'user views commit pipelines page' do + visit pipelines_namespace_project_commit_path(project.namespace, project, project.commit.sha) - expect(page).to have_content('Builds') + page.within('.table-holder') do + expect(page).to have_content project.pipelines[0].status # pipeline status + expect(page).to have_content project.pipelines[0].id # pipeline ids + end end end end diff --git a/spec/features/projects/commits/cherry_pick_spec.rb b/spec/features/projects/commits/cherry_pick_spec.rb index e45e3a36d01..7baf7913424 100644 --- a/spec/features/projects/commits/cherry_pick_spec.rb +++ b/spec/features/projects/commits/cherry_pick_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' include WaitForAjax describe 'Cherry-pick Commits' do - let(:project) { create(:project) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } let(:master_pickable_merge) { project.commit('e56497bb5f03a90a51293fc6d516788730953899') } @@ -64,7 +65,7 @@ describe 'Cherry-pick Commits' do context "I cherry-pick a commit from a different branch", js: true do it do - find('.commit-action-buttons a.dropdown-toggle').click + find('.header-action-buttons a.dropdown-toggle').click find(:css, "a[href='#modal-cherry-pick-commit']").click page.within('#modal-cherry-pick-commit') do diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 09aa6758b5c..9079350186d 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -42,6 +42,17 @@ describe 'Edit Project Settings', feature: true do end end + context "When external issue tracker is enabled" do + it "does not hide issues tab" do + project.project_feature.update(issues_access_level: ProjectFeature::DISABLED) + allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(JiraService.new) + + visit namespace_project_path(project.namespace, project) + + expect(page).to have_selector(".shortcuts-issues") + end + end + context "pipelines subtabs" do it "shows builds when enabled" do visit namespace_project_pipelines_path(project.namespace, project) @@ -182,6 +193,44 @@ describe 'Edit Project Settings', feature: true do expect(page).not_to have_content("Comments") end end + + # Regression spec for https://gitlab.com/gitlab-org/gitlab-ce/issues/25272 + it "hides comments activity tab only on disabled issues, merge requests and repository" do + select "Disabled", from: "project_project_feature_attributes_issues_access_level" + + save_changes_and_check_activity_tab do + expect(page).to have_content("Comments") + end + + visit edit_namespace_project_path(project.namespace, project) + + select "Disabled", from: "project_project_feature_attributes_merge_requests_access_level" + + save_changes_and_check_activity_tab do + expect(page).to have_content("Comments") + end + + visit edit_namespace_project_path(project.namespace, project) + + select "Disabled", from: "project_project_feature_attributes_repository_access_level" + + save_changes_and_check_activity_tab do + expect(page).not_to have_content("Comments") + end + + visit edit_namespace_project_path(project.namespace, project) + end + + def save_changes_and_check_activity_tab + click_button "Save changes" + wait_for_ajax + + visit activity_namespace_project_path(project.namespace, project) + + page.within(".event-filter") do + yield + end + end end # Regression spec for https://gitlab.com/gitlab-org/gitlab-ce/issues/24056 diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb new file mode 100644 index 00000000000..ae448706130 --- /dev/null +++ b/spec/features/projects/files/creating_a_file_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +feature 'User wants to create a file', feature: true do + include WaitForAjax + + let(:project) { create(:project) } + let(:user) { create(:user) } + + background do + project.team << [user, :master] + login_as user + visit namespace_project_new_blob_path(project.namespace, project, project.default_branch) + end + + def submit_new_file(options) + file_name = find('#file_name') + file_name.set options[:file_name] || 'README.md' + + file_content = find('#file-content') + file_content.set options[:file_content] || 'Some content' + + click_button 'Commit Changes' + end + + scenario 'file name contains Chinese characters' do + submit_new_file(file_name: '测试.md') + expect(page).to have_content 'The file has been successfully created.' + end + + scenario 'directory name contains Chinese characters' do + submit_new_file(file_name: '中文/测试.md') + expect(page).to have_content 'The file has been successfully created.' + end + + scenario 'file name contains invalid characters' do + submit_new_file(file_name: '\\') + expect(page).to have_content 'Your changes could not be committed, because the file name can contain only' + end + + scenario 'file name contains directory traversal' do + submit_new_file(file_name: '../README.md') + expect(page).to have_content 'Your changes could not be committed, because the file name cannot include directory traversal.' + end +end diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb new file mode 100644 index 00000000000..32f33a3ca97 --- /dev/null +++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +feature 'User wants to add a Dockerfile file', feature: true do + include WaitForAjax + + before do + user = create(:user) + project = create(:project) + project.team << [user, :master] + login_as user + visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: 'Dockerfile') + end + + scenario 'user can see Dockerfile dropdown' do + expect(page).to have_css('.dockerfile-selector') + end + + scenario 'user can pick a Dockerfile file from the dropdown', js: true do + find('.js-dockerfile-selector').click + wait_for_ajax + within '.dockerfile-selector' do + find('.dropdown-input-field').set('HTTPd') + find('.dropdown-content li', text: 'HTTPd').click + end + wait_for_ajax + + expect(page).to have_css('.dockerfile-selector .dropdown-toggle-text', text: 'HTTPd') + expect(page).to have_content('COPY ./ /usr/local/apache2/htdocs/') + end +end diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb index 1921ea6d8ae..dd9622f16a0 100644 --- a/spec/features/projects/gfm_autocomplete_load_spec.rb +++ b/spec/features/projects/gfm_autocomplete_load_spec.rb @@ -10,12 +10,12 @@ describe 'GFM autocomplete loading', feature: true, js: true do end it 'does not load on project#show' do - expect(evaluate_script('GitLab.GfmAutoComplete.dataSource')).to eq('') + expect(evaluate_script('gl.GfmAutoComplete.dataSources')).to eq({}) end it 'loads on new issue page' do visit new_namespace_project_issue_path(project.namespace, project) - expect(evaluate_script('GitLab.GfmAutoComplete.dataSource')).not_to eq('') + expect(evaluate_script('gl.GfmAutoComplete.dataSources')).not_to eq({}) end end diff --git a/spec/features/projects/group_links_spec.rb b/spec/features/projects/group_links_spec.rb index 1a71a03fbd9..8b302a6aa23 100644 --- a/spec/features/projects/group_links_spec.rb +++ b/spec/features/projects/group_links_spec.rb @@ -14,10 +14,10 @@ feature 'Project group links', feature: true, js: true do context 'setting an expiration date for a group link' do before do - visit namespace_project_group_links_path(project.namespace, project) + visit namespace_project_settings_members_path(project.namespace, project) select2 group.id, from: '#link_group_id' - fill_in 'expires_at', with: (Time.current + 4.5.days).strftime('%Y-%m-%d') + fill_in 'expires_at_groups', with: (Time.current + 4.5.days).strftime('%Y-%m-%d') page.find('body').click click_on 'Share' end diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb index c22441f8929..8120a51c515 100644 --- a/spec/features/projects/guest_navigation_menu_spec.rb +++ b/spec/features/projects/guest_navigation_menu_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe "Guest navigation menu" do - let(:project) { create :empty_project, :private } - let(:guest) { create :user } + let(:project) { create(:empty_project, :private, public_builds: false) } + let(:guest) { create(:user) } before do project.team << [guest, :guest] diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb new file mode 100644 index 00000000000..d0bafc6168c --- /dev/null +++ b/spec/features/projects/import_export/namespace_export_file_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' + +feature 'Import/Export - Namespace export file cleanup', feature: true, js: true do + let(:export_path) { "#{Dir::tmpdir}/import_file_spec" } + let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys } + + let(:project) { create(:empty_project) } + + background do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + end + + after do + FileUtils.rm_rf(export_path, secure: true) + end + + context 'admin user' do + before do + login_as(:admin) + end + + context 'moving the namespace' do + scenario 'removes the export file' do + setup_export_project + + old_export_path = project.export_path.dup + + expect(File).to exist(old_export_path) + + project.namespace.update(path: 'new_path') + + expect(File).not_to exist(old_export_path) + end + end + + context 'deleting the namespace' do + scenario 'removes the export file' do + setup_export_project + + old_export_path = project.export_path.dup + + expect(File).to exist(old_export_path) + + project.namespace.destroy + + expect(File).not_to exist(old_export_path) + end + end + + def setup_export_project + visit edit_namespace_project_path(project.namespace, project) + + expect(page).to have_content('Export project') + + click_link 'Export project' + + visit edit_namespace_project_path(project.namespace, project) + + expect(page).to have_content('Download export') + end + end +end diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz Binary files differindex bfe59bdb90e..7655c2b351f 100644 --- a/spec/features/projects/import_export/test_project_export.tar.gz +++ b/spec/features/projects/import_export/test_project_export.tar.gz diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index 2f377312ea5..6dae5c64b30 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -27,7 +27,7 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects "bug" template' do select_template 'bug' wait_for_ajax - preview_template + assert_template save_changes end @@ -35,8 +35,7 @@ feature 'issuable templates', feature: true, js: true do select_template 'bug' wait_for_ajax select_option 'No template' - wait_for_ajax - preview_template('') + assert_template('') save_changes('') end @@ -44,9 +43,9 @@ feature 'issuable templates', feature: true, js: true do select_template 'bug' wait_for_ajax find_field('issue_description').send_keys(description_addition) - preview_template(template_content + description_addition) + assert_template(template_content + description_addition) select_option 'Reset template' - preview_template + assert_template save_changes end @@ -77,7 +76,7 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects "bug" template' do select_template 'bug' wait_for_ajax - preview_template("#{template_content}") + assert_template("#{template_content}") save_changes end end @@ -95,7 +94,7 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects "feature-proposal" template' do select_template 'feature-proposal' wait_for_ajax - preview_template + assert_template save_changes end end @@ -122,17 +121,15 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects template' do select_template 'feature-proposal' wait_for_ajax - preview_template + assert_template save_changes end end end end - def preview_template(expected_content = template_content) - click_link 'Preview' - expect(page).to have_content expected_content - click_link 'Write' + def assert_template(expected_content = template_content) + expect(find('textarea')['value']).to eq(expected_content) end def save_changes(expected_content = template_content) diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb new file mode 100644 index 00000000000..3130d87fba5 --- /dev/null +++ b/spec/features/projects/labels/subscription_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +feature 'Labels subscription', feature: true do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let!(:bug) { create(:label, project: project, title: 'bug') } + let!(:feature) { create(:group_label, group: group, title: 'feature') } + + context 'when signed in' do + before do + project.team << [user, :developer] + login_as user + end + + scenario 'users can subscribe/unsubscribe to labels', js: true do + visit namespace_project_labels_path(project.namespace, project) + + expect(page).to have_content('bug') + expect(page).to have_content('feature') + + within "#project_label_#{bug.id}" do + expect(page).not_to have_button 'Unsubscribe' + + click_button 'Subscribe' + + expect(page).not_to have_button 'Subscribe' + expect(page).to have_button 'Unsubscribe' + + click_button 'Unsubscribe' + + expect(page).to have_button 'Subscribe' + expect(page).not_to have_button 'Unsubscribe' + end + + within "#group_label_#{feature.id}" do + expect(page).not_to have_button 'Unsubscribe' + + click_link_on_dropdown('Group level') + + expect(page).not_to have_selector('.dropdown-group-label') + expect(page).to have_button 'Unsubscribe' + + click_button 'Unsubscribe' + + expect(page).to have_selector('.dropdown-group-label') + + click_link_on_dropdown('Project level') + + expect(page).not_to have_selector('.dropdown-group-label') + expect(page).to have_button 'Unsubscribe' + end + end + end + + context 'when not signed in' do + it 'users can not subscribe/unsubscribe to labels' do + visit namespace_project_labels_path(project.namespace, project) + + expect(page).to have_content 'bug' + expect(page).to have_content 'feature' + expect(page).not_to have_button('Subscribe') + expect(page).not_to have_selector('.dropdown-group-label') + end + end + + def click_link_on_dropdown(text) + find('.dropdown-group-label').click + + page.within('.dropdown-group-label') do + find('a.js-subscribe-button', text: text).click + end + end +end diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb index c5e3d143d91..d82cf53c690 100644 --- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb +++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb @@ -11,10 +11,10 @@ feature 'Projects > Members > Anonymous user sees members', feature: true do end scenario "anonymous user visits the project's members page and sees the list of members" do - visit namespace_project_project_members_path(project.namespace, project) + visit namespace_project_settings_members_path(project.namespace, project) expect(current_path).to eq( - namespace_project_project_members_path(project.namespace, project)) + namespace_project_settings_members_path(project.namespace, project)) expect(page).to have_content(user.name) end end diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb index cc2f695211c..cffb935ad5a 100644 --- a/spec/features/projects/members/group_links_spec.rb +++ b/spec/features/projects/members/group_links_spec.rb @@ -12,16 +12,21 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t @group_link = create(:project_group_link, project: project, group: group) login_as(user) - visit namespace_project_project_members_path(project.namespace, project) + visit namespace_project_settings_members_path(project.namespace, project) end it 'updates group access level' do - select 'Guest', from: "member_access_level_#{group.id}" + click_button @group_link.human_access + + page.within '.dropdown-menu' do + click_link 'Guest' + end + wait_for_ajax - visit namespace_project_project_members_path(project.namespace, project) + visit namespace_project_settings_members_path(project.namespace, project) - expect(page).to have_select("member_access_level_#{group.id}", selected: 'Guest') + expect(first('.group_member')).to have_content('Guest') end it 'updates expiry date' do diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb index 728c0e16361..b483ba4c54c 100644 --- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb +++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb @@ -12,6 +12,6 @@ feature 'Projects > Members > Group member cannot leave group project', feature: end scenario 'user does not see a "Leave project" link' do - expect(page).not_to have_content 'Leave Project' + expect(page).not_to have_content 'Leave project' end end diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb new file mode 100644 index 00000000000..3385e5972ff --- /dev/null +++ b/spec/features/projects/members/group_members_spec.rb @@ -0,0 +1,90 @@ +require 'spec_helper' + +feature 'Projects members', feature: true do + let(:user) { create(:user) } + let(:developer) { create(:user) } + let(:group) { create(:group, :public, :access_requestable) } + let(:project) { create(:empty_project, :public, :access_requestable, creator: user, group: group) } + let(:project_invitee) { create(:project_member, project: project, invite_token: '123', invite_email: 'test1@abc.com', user: nil) } + let(:group_invitee) { create(:group_member, group: group, invite_token: '123', invite_email: 'test2@abc.com', user: nil) } + let(:project_requester) { create(:user) } + let(:group_requester) { create(:user) } + + background do + project.team << [developer, :developer] + group.add_owner(user) + login_as(user) + end + + context 'with a group invitee' do + before do + group_invitee + visit namespace_project_settings_members_path(project.namespace, project) + end + + scenario 'does not appear in the project members page' do + page.within first('.content-list') do + expect(page).not_to have_content('test2@abc.com') + end + end + end + + context 'with a group and a project invitee' do + before do + group_invitee + project_invitee + visit namespace_project_settings_members_path(project.namespace, project) + end + + scenario 'shows the project invitee, the project developer, and the group owner' do + page.within first('.content-list') do + expect(page).to have_content('test1@abc.com') + expect(page).not_to have_content('test2@abc.com') + + # Project developer + expect(page).to have_content(developer.name) + + # Group owner + expect(page).to have_content(user.name) + expect(page).to have_content(group.name) + end + end + end + + context 'with a group requester' do + before do + group.request_access(group_requester) + visit namespace_project_settings_members_path(project.namespace, project) + end + + scenario 'does not appear in the project members page' do + page.within first('.content-list') do + expect(page).not_to have_content(group_requester.name) + end + end + end + + context 'with a group and a project requesters' do + before do + group.request_access(group_requester) + project.request_access(project_requester) + visit namespace_project_settings_members_path(project.namespace, project) + end + + scenario 'shows the project requester, the project developer, and the group owner' do + page.within first('.content-list') do + expect(page).to have_content(project_requester.name) + expect(page).not_to have_content(group_requester.name) + end + + page.within all('.content-list').last do + # Project developer + expect(page).to have_content(developer.name) + + # Group owner + expect(page).to have_content(user.name) + expect(page).to have_content(group.name) + end + end + end +end diff --git a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb index c4ed92d2780..bdeeef57273 100644 --- a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb +++ b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' -feature 'Projects > Members > Group requester cannot request access to project', feature: true do +feature 'Projects > Members > Group requester cannot request access to project', feature: true, js: true do let(:user) { create(:user) } let(:owner) { create(:user) } - let(:group) { create(:group, :public) } - let(:project) { create(:project, :public, namespace: group) } + let(:group) { create(:group, :public, :access_requestable) } + let(:project) { create(:project, :public, :access_requestable, namespace: group) } background do group.add_owner(owner) diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb index 27a83fdcd1f..f136d9ce0fa 100644 --- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb @@ -14,17 +14,17 @@ feature 'Projects > Members > Master adds member with expiration date', feature: login_as(master) end - scenario 'expiration date is displayed in the members list' do + scenario 'expiration date is displayed in the members list', js: true do travel_to Time.zone.parse('2016-08-06 08:00') do - visit namespace_project_project_members_path(project.namespace, project) - + visit namespace_project_settings_members_path(project.namespace, project) page.within '.users-project-form' do select2(new_member.id, from: '#user_ids', multiple: true) fill_in 'expires_at', with: '2016-08-10' - click_on 'Add to project' end + find('.users-project-form').click + click_on 'Add to project' - page.within '.project_member:first-child' do + page.within "#project_member_#{new_member.project_members.first.id}" do expect(page).to have_content('Expires in 4 days') end end @@ -35,7 +35,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature: project.team.add_users([new_member.id], :developer, expires_at: '2016-09-06') visit namespace_project_project_members_path(project.namespace, project) - page.within '.project_member:first-child' do + page.within "#project_member_#{new_member.project_members.first.id}" do find('.js-access-expiration-date').set '2016-08-09' wait_for_ajax expect(page).to have_content('Expires in 3 days') diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb index d15376931c3..143390b71cd 100644 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ b/spec/features/projects/members/master_manages_access_requests_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Projects > Members > Master manages access requests', feature: true do let(:user) { create(:user) } let(:master) { create(:user) } - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public, :access_requestable) } background do project.request_access(user) diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb index 79dec442818..5daa932e4e6 100644 --- a/spec/features/projects/members/member_leaves_project_spec.rb +++ b/spec/features/projects/members/member_leaves_project_spec.rb @@ -11,7 +11,7 @@ feature 'Projects > Members > Member leaves project', feature: true do end scenario 'user leaves project' do - click_link 'Leave Project' + click_link 'Leave project' expect(current_path).to eq(dashboard_projects_path) expect(project.users.exists?(user.id)).to be_falsey diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb index 6e948b7a616..b26d55c5d5d 100644 --- a/spec/features/projects/members/owner_cannot_leave_project_spec.rb +++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb @@ -8,7 +8,7 @@ feature 'Projects > Members > Owner cannot leave project', feature: true do visit namespace_project_path(project.namespace, project) end - scenario 'user does not see a "Leave Project" link' do - expect(page).not_to have_content 'Leave Project' + scenario 'user does not see a "Leave project" link' do + expect(page).not_to have_content 'Leave project' end end diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb new file mode 100644 index 00000000000..d6ebb523f95 --- /dev/null +++ b/spec/features/projects/members/sorting_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +feature 'Projects > Members > Sorting', feature: true do + let(:master) { create(:user, name: 'John Doe') } + let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) } + let(:project) { create(:empty_project) } + + background do + create(:project_member, :master, user: master, project: project, created_at: 5.days.ago) + create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago) + + login_as(master) + end + + scenario 'sorts alphabetically by default' do + visit_members_list(sort: nil) + + expect(first_member).to include(master.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + end + + scenario 'sorts by access level ascending' do + visit_members_list(sort: :access_level_asc) + + expect(first_member).to include(developer.name) + expect(second_member).to include(master.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') + end + + scenario 'sorts by access level descending' do + visit_members_list(sort: :access_level_desc) + + expect(first_member).to include(master.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') + end + + scenario 'sorts by last joined' do + visit_members_list(sort: :last_joined) + + expect(first_member).to include(developer.name) + expect(second_member).to include(master.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined') + end + + scenario 'sorts by oldest joined' do + visit_members_list(sort: :oldest_joined) + + expect(first_member).to include(master.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') + end + + scenario 'sorts by name ascending' do + visit_members_list(sort: :name_asc) + + expect(first_member).to include(master.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + end + + scenario 'sorts by name descending' do + visit_members_list(sort: :name_desc) + + expect(first_member).to include(developer.name) + expect(second_member).to include(master.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') + end + + scenario 'sorts by recent sign in' do + visit_members_list(sort: :recent_sign_in) + + expect(first_member).to include(master.name) + expect(second_member).to include(developer.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') + end + + scenario 'sorts by oldest sign in' do + visit_members_list(sort: :oldest_sign_in) + + expect(first_member).to include(developer.name) + expect(second_member).to include(master.name) + expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') + end + + def visit_members_list(sort:) + visit namespace_project_project_members_path(project.namespace.to_param, project.to_param, sort: sort) + end + + def first_member + page.all('ul.content-list > li').first.text + end + + def second_member + page.all('ul.content-list > li').last.text + end +end diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index 56ede8eb5be..0b4dcaa39c6 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Projects > Members > User requests access', feature: true do let(:user) { create(:user) } let(:master) { create(:user) } - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :access_requestable) } background do project.team << [master, :master] @@ -39,7 +39,7 @@ feature 'Projects > Members > User requests access', feature: true do open_project_settings_menu click_link 'Members' - visit namespace_project_project_members_path(project.namespace, project) + visit namespace_project_settings_members_path(project.namespace, project) page.within('.content') do expect(page).not_to have_content(user.name) end diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb new file mode 100644 index 00000000000..abfc46601fb --- /dev/null +++ b/spec/features/projects/new_project_spec.rb @@ -0,0 +1,19 @@ +require "spec_helper" + +feature "New project", feature: true do + context "Visibility level selector" do + let(:user) { create(:admin) } + + before { login_as(user) } + + Gitlab::VisibilityLevel.options.each do |key, level| + it "sets selector to #{key}" do + stub_application_setting(default_project_visibility: level) + + visit new_project_path + + expect(find_field("project_visibility_level_#{level}")).to be_checked + end + end + end +end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb new file mode 100644 index 00000000000..e673ece37c3 --- /dev/null +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -0,0 +1,257 @@ +require 'spec_helper' + +describe 'Pipeline', :feature, :js do + include GitlabRoutingHelper + + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + before do + login_as(user) + project.team << [user, :developer] + end + + shared_context 'pipeline builds' do + let!(:build_passed) do + create(:ci_build, :success, + pipeline: pipeline, stage: 'build', name: 'build') + end + + let!(:build_failed) do + create(:ci_build, :failed, + pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') + end + + let!(:build_running) do + create(:ci_build, :running, + pipeline: pipeline, stage: 'deploy', name: 'deploy') + end + + let!(:build_manual) do + create(:ci_build, :manual, + pipeline: pipeline, stage: 'deploy', name: 'manual-build') + end + + let!(:build_external) do + create(:generic_commit_status, status: 'success', + pipeline: pipeline, + name: 'jenkins', + stage: 'external', + target_url: 'http://gitlab.com/status') + end + end + + describe 'GET /:project/pipelines/:id' do + include_context 'pipeline builds' + + let(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) } + + before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } + + it 'shows the pipeline graph' do + expect(page).to have_selector('.pipeline-visualization') + expect(page).to have_content('Build') + expect(page).to have_content('Test') + expect(page).to have_content('Deploy') + expect(page).to have_content('Retry failed') + expect(page).to have_content('Cancel running') + end + + it 'shows Pipeline tab pane as active' do + expect(page).to have_css('#js-tab-pipeline.active') + end + + describe 'pipeline graph' do + context 'when pipeline has running builds' do + it 'shows a running icon and a cancel action for the running build' do + page.within('#ci-badge-deploy') do + expect(page).to have_selector('.ci-status-icon-running') + expect(page).to have_selector('.ci-action-icon-container .fa-ban') + expect(page).to have_content('deploy') + end + end + + it 'should be possible to cancel the running build' do + find('#ci-badge-deploy .ci-action-icon-container').trigger('click') + + expect(page).not_to have_content('Cancel running') + end + end + + context 'when pipeline has successful builds' do + it 'shows the success icon and a retry action for the successful build' do + page.within('#ci-badge-build') do + expect(page).to have_selector('.ci-status-icon-success') + expect(page).to have_content('build') + end + + page.within('#ci-badge-build .ci-action-icon-container') do + expect(page).to have_selector('.ci-action-icon-container .fa-refresh') + end + end + + it 'should be possible to retry the success build' do + find('#ci-badge-build .ci-action-icon-container').trigger('click') + + expect(page).not_to have_content('Retry build') + end + end + + context 'when pipeline has failed builds' do + it 'shows the failed icon and a retry action for the failed build' do + page.within('#ci-badge-test') do + expect(page).to have_selector('.ci-status-icon-failed') + expect(page).to have_content('test') + end + + page.within('#ci-badge-test .ci-action-icon-container') do + expect(page).to have_selector('.ci-action-icon-container .fa-refresh') + end + end + + it 'should be possible to retry the failed build' do + find('#ci-badge-test .ci-action-icon-container').trigger('click') + + expect(page).not_to have_content('Retry build') + end + end + + context 'when pipeline has manual builds' do + it 'shows the skipped icon and a play action for the manual build' do + page.within('#ci-badge-manual-build') do + expect(page).to have_selector('.ci-status-icon-manual') + expect(page).to have_content('manual') + end + + page.within('#ci-badge-manual-build .ci-action-icon-container') do + expect(page).to have_selector('.ci-action-icon-container .fa-play') + end + end + + it 'should be possible to play the manual build' do + find('#ci-badge-manual-build .ci-action-icon-container').trigger('click') + + expect(page).not_to have_content('Play build') + end + end + + context 'when pipeline has external build' do + it 'shows the success icon and the generic comit status build' do + expect(page).to have_selector('.ci-status-icon-success') + expect(page).to have_content('jenkins') + expect(page).to have_link('jenkins', href: 'http://gitlab.com/status') + end + end + end + + context 'page tabs' do + it 'shows Pipeline and Builds tabs with link' do + expect(page).to have_link('Pipeline') + expect(page).to have_link('Builds') + end + + it 'shows counter in Builds tab' do + expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s) + end + + it 'shows Pipeline tab as active' do + expect(page).to have_css('.js-pipeline-tab-link.active') + end + end + + context 'retrying builds' do + it { expect(page).not_to have_content('retried') } + + context 'when retrying' do + before { click_on 'Retry failed' } + + it { expect(page).not_to have_content('Retry failed') } + end + end + + context 'canceling builds' do + it { expect(page).not_to have_selector('.ci-canceled') } + + context 'when canceling' do + before { click_on 'Cancel running' } + + it { expect(page).not_to have_content('Cancel running') } + end + end + end + + describe 'GET /:project/pipelines/:id/builds' do + include_context 'pipeline builds' + + let(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) } + + before do + visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline) + end + + it 'shows a list of builds' do + expect(page).to have_content('Test') + expect(page).to have_content(build_passed.id) + expect(page).to have_content('Deploy') + expect(page).to have_content(build_failed.id) + expect(page).to have_content(build_running.id) + expect(page).to have_content(build_external.id) + expect(page).to have_content('Retry failed') + expect(page).to have_content('Cancel running') + expect(page).to have_link('Play') + end + + it 'shows Builds tab pane as active' do + expect(page).to have_css('#js-tab-builds.active') + end + + context 'page tabs' do + it 'shows Pipeline and Builds tabs with link' do + expect(page).to have_link('Pipeline') + expect(page).to have_link('Builds') + end + + it 'shows counter in Builds tab' do + expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s) + end + + it 'shows Builds tab as active' do + expect(page).to have_css('li.js-builds-tab-link.active') + end + end + + context 'retrying builds' do + it { expect(page).not_to have_content('retried') } + + context 'when retrying' do + before { click_on 'Retry failed' } + + it { expect(page).not_to have_content('Retry failed') } + it { expect(page).to have_selector('.retried') } + end + end + + context 'canceling builds' do + it { expect(page).not_to have_selector('.ci-canceled') } + + context 'when canceling' do + before { click_on 'Cancel running' } + + it { expect(page).not_to have_content('Cancel running') } + it { expect(page).to have_selector('.ci-canceled') } + end + end + + context 'playing manual build' do + before do + within '.pipeline-holder' do + click_link('Play') + end + end + + it { expect(build_manual.reload).to be_pending } + end + end +end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb new file mode 100644 index 00000000000..ca18ac073d8 --- /dev/null +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -0,0 +1,364 @@ +require 'spec_helper' + +describe 'Pipelines', :feature, :js do + include WaitForVueResource + + let(:project) { create(:empty_project) } + + context 'when user is logged in' do + let(:user) { create(:user) } + + before do + login_as(user) + project.team << [user, :developer] + end + + describe 'GET /:project/pipelines' do + let(:project) { create(:project) } + + let!(:pipeline) do + create( + :ci_empty_pipeline, + project: project, + ref: 'master', + status: 'running', + sha: project.commit.id, + ) + end + + [:all, :running, :branches].each do |scope| + context "when displaying #{scope}" do + before do + visit_project_pipelines(scope: scope) + end + + it 'contains pipeline commit short SHA' do + expect(page).to have_content(pipeline.short_sha) + end + end + end + + context 'when pipeline is cancelable' do + let!(:build) do + create(:ci_build, pipeline: pipeline, + stage: 'test', + commands: 'test') + end + + before do + build.run + visit_project_pipelines + end + + it 'indicates that pipeline can be canceled' do + expect(page).to have_link('Cancel') + expect(page).to have_selector('.ci-running') + end + + context 'when canceling' do + before { click_link('Cancel') } + + it 'indicated that pipelines was canceled' do + expect(page).not_to have_link('Cancel') + expect(page).to have_selector('.ci-canceled') + end + end + end + + context 'when pipeline is retryable' do + let!(:build) do + create(:ci_build, pipeline: pipeline, + stage: 'test', + commands: 'test') + end + + before do + build.drop + visit_project_pipelines + end + + it 'indicates that pipeline can be retried' do + expect(page).to have_link('Retry') + expect(page).to have_selector('.ci-failed') + end + + context 'when retrying' do + before { click_link('Retry') } + + it 'shows running pipeline that is not retryable' do + expect(page).not_to have_link('Retry') + expect(page).to have_selector('.ci-running') + end + end + end + + context 'when pipeline has configuration errors' do + let(:pipeline) do + create(:ci_pipeline, :invalid, project: project) + end + + before { visit_project_pipelines } + + it 'contains badge that indicates errors' do + expect(page).to have_content 'yaml invalid' + end + + it 'contains badge with tooltip which contains error' do + expect(pipeline).to have_yaml_errors + expect(page).to have_selector( + %Q{span[data-original-title="#{pipeline.yaml_errors}"]}) + end + end + + context 'with manual actions' do + let!(:manual) do + create(:ci_build, :manual, + pipeline: pipeline, + name: 'manual build', + stage: 'test', + commands: 'test') + end + + before { visit_project_pipelines } + + it 'has a dropdown with play button' do + expect(page).to have_selector('.dropdown-toggle.btn.btn-default .icon-play') + end + + it 'has link to the manual action' do + find('.js-pipeline-dropdown-manual-actions').click + + expect(page).to have_link('manual build') + end + + context 'when manual action was played' do + before do + find('.js-pipeline-dropdown-manual-actions').click + click_link('manual build') + end + + it 'enqueues manual action job' do + expect(manual.reload).to be_pending + end + end + end + + context 'for generic statuses' do + context 'when running' do + let!(:running) do + create(:generic_commit_status, + status: 'running', + pipeline: pipeline, + stage: 'test') + end + + before { visit_project_pipelines } + + it 'is cancelable' do + expect(page).to have_link('Cancel') + end + + it 'has pipeline running' do + expect(page).to have_selector('.ci-running') + end + + context 'when canceling' do + before { click_link('Cancel') } + + it 'indicates that pipeline was canceled' do + expect(page).not_to have_link('Cancel') + expect(page).to have_selector('.ci-canceled') + end + end + end + + context 'when failed' do + let!(:status) do + create(:generic_commit_status, :pending, + pipeline: pipeline, + stage: 'test') + end + + before do + status.drop + visit_project_pipelines + end + + it 'is not retryable' do + expect(page).not_to have_link('Retry') + end + + it 'has failed pipeline' do + expect(page).to have_selector('.ci-failed') + end + end + end + + context 'downloadable pipelines' do + context 'with artifacts' do + let!(:with_artifacts) do + create(:ci_build, :artifacts, :success, + pipeline: pipeline, + name: 'rspec tests', + stage: 'test') + end + + before { visit_project_pipelines } + + it 'has artifats' do + expect(page).to have_selector('.build-artifacts') + end + + it 'has artifacts download dropdown' do + find('.js-pipeline-dropdown-download').click + + expect(page).to have_link(with_artifacts.name) + end + end + + context 'with artifacts expired' do + let!(:with_artifacts_expired) do + create(:ci_build, :artifacts_expired, :success, + pipeline: pipeline, + name: 'rspec', + stage: 'test') + end + + before { visit_project_pipelines } + + it { expect(page).not_to have_selector('.build-artifacts') } + end + + context 'without artifacts' do + let!(:without_artifacts) do + create(:ci_build, :success, + pipeline: pipeline, + name: 'rspec', + stage: 'test') + end + + before { visit_project_pipelines } + + it { expect(page).not_to have_selector('.build-artifacts') } + end + end + + context 'mini pipeline graph' do + let!(:build) do + create(:ci_build, :pending, pipeline: pipeline, + stage: 'build', + name: 'build') + end + + before { visit_project_pipelines } + + it 'should render a mini pipeline graph' do + expect(page).to have_selector('.js-mini-pipeline-graph') + expect(page).to have_selector('.js-builds-dropdown-button') + end + + context 'when clicking a stage badge' do + it 'should open a dropdown' do + find('.js-builds-dropdown-button').trigger('click') + + expect(page).to have_link build.name + end + + it 'should be possible to cancel pending build' do + find('.js-builds-dropdown-button').trigger('click') + find('a.js-ci-action-icon').trigger('click') + + expect(page).to have_content('canceled') + expect(build.reload).to be_canceled + end + end + end + end + + describe 'POST /:project/pipelines' do + let(:project) { create(:project) } + + before do + visit new_namespace_project_pipeline_path(project.namespace, project) + end + + context 'for valid commit' do + before { fill_in('pipeline[ref]', with: 'master') } + + context 'with gitlab-ci.yml' do + before { stub_ci_pipeline_to_return_yaml_file } + + it 'creates a new pipeline' do + expect { click_on 'Create pipeline' } + .to change { Ci::Pipeline.count }.by(1) + end + end + + context 'without gitlab-ci.yml' do + before { click_on 'Create pipeline' } + + it { expect(page).to have_content('Missing .gitlab-ci.yml file') } + end + end + + context 'for invalid commit' do + before do + fill_in('pipeline[ref]', with: 'invalid-reference') + click_on 'Create pipeline' + end + + it { expect(page).to have_content('Reference not found') } + end + end + + describe 'Create pipelines' do + let(:project) { create(:project) } + + before do + visit new_namespace_project_pipeline_path(project.namespace, project) + end + + describe 'new pipeline page' do + it 'has field to add a new pipeline' do + expect(page).to have_field('pipeline[ref]') + expect(page).to have_content('Create for') + end + end + + describe 'find pipelines' do + it 'shows filtered pipelines', js: true do + fill_in('pipeline[ref]', with: 'fix') + find('input#ref').native.send_keys(:keydown) + + within('.ui-autocomplete') do + expect(page).to have_selector('li', text: 'fix') + end + end + end + end + end + + context 'when user is not logged in' do + before do + visit namespace_project_pipelines_path(project.namespace, project) + end + + context 'when project is public' do + let(:project) { create(:project, :public) } + + it { expect(page).to have_content 'No pipelines to show' } + it { expect(page).to have_http_status(:success) } + end + + context 'when project is private' do + let(:project) { create(:project, :private) } + + it { expect(page).to have_content 'You need to sign in' } + end + end + + def visit_project_pipelines(**query) + visit namespace_project_pipelines_path(project.namespace, project, query) + wait_for_vue_resource + end +end diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines_spec.rb deleted file mode 100644 index db56a50e058..00000000000 --- a/spec/features/projects/pipelines_spec.rb +++ /dev/null @@ -1,262 +0,0 @@ -require 'spec_helper' - -describe "Pipelines" do - include GitlabRoutingHelper - - let(:project) { create(:empty_project) } - let(:user) { create(:user) } - - before do - login_as(user) - project.team << [user, :developer] - end - - describe 'GET /:project/pipelines' do - let!(:pipeline) { create(:ci_empty_pipeline, project: project, ref: 'master', status: 'running') } - - [:all, :running, :branches].each do |scope| - context "displaying #{scope}" do - let(:project) { create(:project) } - - before { visit namespace_project_pipelines_path(project.namespace, project, scope: scope) } - - it { expect(page).to have_content(pipeline.short_sha) } - end - end - - context 'anonymous access' do - before { visit namespace_project_pipelines_path(project.namespace, project) } - - it { expect(page).to have_http_status(:success) } - end - - context 'cancelable pipeline' do - let!(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') } - - before do - build.run - visit namespace_project_pipelines_path(project.namespace, project) - end - - it { expect(page).to have_link('Cancel') } - it { expect(page).to have_selector('.ci-running') } - - context 'when canceling' do - before { click_link('Cancel') } - - it { expect(page).not_to have_link('Cancel') } - it { expect(page).to have_selector('.ci-canceled') } - end - end - - context 'retryable pipelines' do - let!(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') } - - before do - build.drop - visit namespace_project_pipelines_path(project.namespace, project) - end - - it { expect(page).to have_link('Retry') } - it { expect(page).to have_selector('.ci-failed') } - - context 'when retrying' do - before { click_link('Retry') } - - it { expect(page).not_to have_link('Retry') } - it { expect(page).to have_selector('.ci-running') } - end - end - - context 'with manual actions' do - let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'manual build', stage: 'test', commands: 'test') } - - before { visit namespace_project_pipelines_path(project.namespace, project) } - - it { expect(page).to have_link('Manual build') } - - context 'when playing' do - before { click_link('Manual build') } - - it { expect(manual.reload).to be_pending } - end - end - - context 'for generic statuses' do - context 'when running' do - let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') } - - before do - visit namespace_project_pipelines_path(project.namespace, project) - end - - it 'is not cancelable' do - expect(page).not_to have_link('Cancel') - end - - it 'has pipeline running' do - expect(page).to have_selector('.ci-running') - end - end - - context 'when failed' do - let!(:status) { create(:generic_commit_status, :pending, pipeline: pipeline, stage: 'test') } - - before do - status.drop - visit namespace_project_pipelines_path(project.namespace, project) - end - - it 'is not retryable' do - expect(page).not_to have_link('Retry') - end - - it 'has failed pipeline' do - expect(page).to have_selector('.ci-failed') - end - end - end - - context 'downloadable pipelines' do - context 'with artifacts' do - let!(:with_artifacts) { create(:ci_build, :artifacts, :success, pipeline: pipeline, name: 'rspec tests', stage: 'test') } - - before { visit namespace_project_pipelines_path(project.namespace, project) } - - it { expect(page).to have_selector('.build-artifacts') } - it { expect(page).to have_link(with_artifacts.name) } - end - - context 'with artifacts expired' do - let!(:with_artifacts_expired) { create(:ci_build, :artifacts_expired, :success, pipeline: pipeline, name: 'rspec', stage: 'test') } - - before { visit namespace_project_pipelines_path(project.namespace, project) } - - it { expect(page).not_to have_selector('.build-artifacts') } - end - - context 'without artifacts' do - let!(:without_artifacts) { create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage: 'test') } - - before { visit namespace_project_pipelines_path(project.namespace, project) } - - it { expect(page).not_to have_selector('.build-artifacts') } - end - end - end - - describe 'GET /:project/pipelines/:id' do - let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') } - - before do - @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') - @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') - @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') - @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build') - @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') - end - - before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } - - it 'shows a list of builds' do - expect(page).to have_content('Test') - expect(page).to have_content(@success.id) - expect(page).to have_content('Deploy') - expect(page).to have_content(@failed.id) - expect(page).to have_content(@running.id) - expect(page).to have_content(@external.id) - expect(page).to have_content('Retry failed') - expect(page).to have_content('Cancel running') - expect(page).to have_link('Play') - end - - context 'retrying builds' do - it { expect(page).not_to have_content('retried') } - - context 'when retrying' do - before { click_on 'Retry failed' } - - it { expect(page).not_to have_content('Retry failed') } - it { expect(page).to have_selector('.retried') } - end - end - - context 'canceling builds' do - it { expect(page).not_to have_selector('.ci-canceled') } - - context 'when canceling' do - before { click_on 'Cancel running' } - - it { expect(page).not_to have_content('Cancel running') } - it { expect(page).to have_selector('.ci-canceled') } - end - end - - context 'playing manual build' do - before do - within '.pipeline-holder' do - click_link('Play') - end - end - - it { expect(@manual.reload).to be_pending } - end - end - - describe 'POST /:project/pipelines' do - let(:project) { create(:project) } - - before { visit new_namespace_project_pipeline_path(project.namespace, project) } - - context 'for valid commit' do - before { fill_in('pipeline[ref]', with: 'master') } - - context 'with gitlab-ci.yml' do - before { stub_ci_pipeline_to_return_yaml_file } - - it { expect{ click_on 'Create pipeline' }.to change{ Ci::Pipeline.count }.by(1) } - end - - context 'without gitlab-ci.yml' do - before { click_on 'Create pipeline' } - - it { expect(page).to have_content('Missing .gitlab-ci.yml file') } - end - end - - context 'for invalid commit' do - before do - fill_in('pipeline[ref]', with: 'invalid-reference') - click_on 'Create pipeline' - end - - it { expect(page).to have_content('Reference not found') } - end - end - - describe 'Create pipelines', feature: true do - let(:project) { create(:project) } - - before do - visit new_namespace_project_pipeline_path(project.namespace, project) - end - - describe 'new pipeline page' do - it 'has field to add a new pipeline' do - expect(page).to have_field('pipeline[ref]') - expect(page).to have_content('Create for') - end - end - - describe 'find pipelines' do - it 'shows filtered pipelines', js: true do - fill_in('pipeline[ref]', with: 'fix') - find('input#ref').native.send_keys(:keydown) - - within('.ui-autocomplete') do - expect(page).to have_selector('li', text: 'fix') - end - end - end - end -end diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb index 3de25d7af7d..55d5d082c6e 100644 --- a/spec/features/projects/project_settings_spec.rb +++ b/spec/features/projects/project_settings_spec.rb @@ -18,9 +18,19 @@ describe 'Edit Project Settings', feature: true do click_button 'Save changes' expect(page).to have_field 'project_name_edit', with: 'foo&bar' - expect(page).to have_content "Name can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'." + expect(page).to have_content "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'." expect(page).to have_button 'Save changes' end + + scenario 'shows a successful notice when the project is updated' do + visit edit_namespace_project_path(project.namespace, project) + + fill_in 'project_name_edit', with: 'hello world' + + click_button 'Save changes' + + expect(page).to have_content "Project 'hello world' was successfully updated." + end end describe 'Rename repository' do @@ -34,8 +44,21 @@ describe 'Edit Project Settings', feature: true do expect(page).to have_field 'Project name', with: 'foo&bar' expect(page).to have_field 'Path', with: 'foo&bar' - expect(page).to have_content "Name can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'." + expect(page).to have_content "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'." expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'" end end + + describe 'Rename repository name with emojis' do + it 'shows error for invalid project name' do + visit edit_namespace_project_path(project.namespace, project) + + fill_in 'Project name', with: '🚀 foo bar ☁️' + + click_button 'Rename project' + + expect(page).to have_field 'Project name', with: '🚀 foo bar ☁️' + expect(page).not_to have_content "Name can contain only letters, digits, emojis '_', '.', dash and space. It must start with letter, digit, emoji or '_'." + end + end end diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb new file mode 100644 index 00000000000..86a07b2c679 --- /dev/null +++ b/spec/features/projects/services/mattermost_slash_command_spec.rb @@ -0,0 +1,137 @@ +require 'spec_helper' + +feature 'Setup Mattermost slash commands', feature: true do + include WaitForAjax + + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + let(:service) { project.create_mattermost_slash_commands_service } + let(:mattermost_enabled) { true } + + before do + Settings.mattermost['enabled'] = mattermost_enabled + project.team << [user, :master] + login_as(user) + visit edit_namespace_project_service_path(project.namespace, project, service) + end + + describe 'user visits the mattermost slash command config page', js: true do + it 'shows a help message' do + wait_for_ajax + + expect(page).to have_content("This service allows GitLab users to perform common") + end + + it 'shows the token after saving' do + token = ('a'..'z').to_a.join + + fill_in 'service_token', with: token + click_on 'Save' + + value = find_field('service_token').value + + expect(value).to eq(token) + end + + it 'shows the add to mattermost button' do + expect(page).to have_link('Add to Mattermost') + end + + it 'shows an explanation if user is a member of no teams' do + stub_teams(count: 0) + + click_link 'Add to Mattermost' + + expect(page).to have_content('You aren’t a member of any team on the Mattermost instance') + expect(page).to have_link('join a team', href: "#{Gitlab.config.mattermost.host}/select_team") + end + + it 'shows an explanation if user is a member of 1 team' do + stub_teams(count: 1) + + click_link 'Add to Mattermost' + + expect(page).to have_content('The team where the slash commands will be used in') + expect(page).to have_content('This is the only available team.') + end + + it 'shows a disabled prefilled select if user is a member of 1 team' do + teams = stub_teams(count: 1) + + click_link 'Add to Mattermost' + + team_name = teams.first[1]['display_name'] + select_element = find('select#mattermost_team_id') + selected_option = select_element.find('option[selected]') + + expect(select_element['disabled']).to be(true) + expect(selected_option).to have_content(team_name.to_s) + end + + it 'has a hidden input for the prefilled value if user is a member of 1 team' do + teams = stub_teams(count: 1) + + click_link 'Add to Mattermost' + + expect(find('input#mattermost_team_id', visible: false).value).to eq(teams.first[0].to_s) + end + + it 'shows an explanation user is a member of multiple teams' do + stub_teams(count: 2) + + click_link 'Add to Mattermost' + + expect(page).to have_content('Select the team where the slash commands will be used in') + expect(page).to have_content('The list shows all available teams.') + end + + it 'shows a select with team options user is a member of multiple teams' do + stub_teams(count: 2) + + click_link 'Add to Mattermost' + + select_element = find('select#mattermost_team_id') + selected_option = select_element.find('option[selected]') + + expect(select_element['disabled']).to be(false) + expect(selected_option).to have_content('Select team...') + # The 'Select team...' placeholder is item `0`. + expect(select_element.all('option').count).to eq(3) + end + + def stub_teams(count: 0) + teams = create_teams(count) + + allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { teams } + + teams + end + + def create_teams(count = 0) + teams = {} + + count.times do |i| + i += 1 + teams[i] = { id: i, display_name: i } + end + + teams + end + + describe 'mattermost service is not enabled' do + let(:mattermost_enabled) { false } + + it 'shows the correct trigger url' do + value = find_field('request_url').value + + expect(value).to match("api/v3/projects/#{project.id}/services/mattermost_slash_commands/trigger") + end + end + end + + describe 'stable logo url' do + it 'shows a publicly available logo' do + expect(File.exist?(Rails.root.join('public/slash-command-logo.png'))) + end + end +end diff --git a/spec/features/projects/slack_service/slack_service_spec.rb b/spec/features/projects/services/slack_service_spec.rb index 16541f51d98..16541f51d98 100644 --- a/spec/features/projects/slack_service/slack_service_spec.rb +++ b/spec/features/projects/services/slack_service_spec.rb diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb new file mode 100644 index 00000000000..32b32f7ae8e --- /dev/null +++ b/spec/features/projects/services/slack_slash_command_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +feature 'Slack slash commands', feature: true do + include WaitForAjax + + given(:user) { create(:user) } + given(:project) { create(:project) } + given(:service) { project.create_slack_slash_commands_service } + + background do + project.team << [user, :master] + login_as(user) + end + + scenario 'user visits the slack slash command config page and shows a help message', js: true do + visit edit_namespace_project_service_path(project.namespace, project, service) + + wait_for_ajax + + expect(page).to have_content('This service allows GitLab users to perform common') + end + + scenario 'shows the token after saving' do + visit edit_namespace_project_service_path(project.namespace, project, service) + + fill_in 'service_token', with: 'token' + click_on 'Save' + + value = find_field('service_token').value + + expect(value).to eq('token') + end + + scenario 'shows the correct trigger url' do + visit edit_namespace_project_service_path(project.namespace, project, service) + + value = find_field('url').value + expect(value).to match("api/v3/projects/#{project.id}/services/slack_slash_commands/trigger") + end +end diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb new file mode 100644 index 00000000000..4bfaa499272 --- /dev/null +++ b/spec/features/projects/settings/merge_requests_settings_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' + +feature 'Project settings > Merge Requests', feature: true, js: true do + include GitlabRoutingHelper + + let(:project) { create(:empty_project, :public) } + let(:user) { create(:user) } + + background do + project.team << [user, :master] + login_as(user) + end + + context 'when Merge Request and Builds are initially enabled' do + before do + project.project_feature.update_attribute('merge_requests_access_level', ProjectFeature::ENABLED) + end + + context 'when Builds are initially enabled' do + before do + project.project_feature.update_attribute('builds_access_level', ProjectFeature::ENABLED) + visit edit_project_path(project) + end + + scenario 'shows the Merge Requests settings' do + expect(page).to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') + + select 'Disabled', from: "project_project_feature_attributes_merge_requests_access_level" + + expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved') + end + end + + context 'when Builds are initially disabled' do + before do + project.project_feature.update_attribute('builds_access_level', ProjectFeature::DISABLED) + visit edit_project_path(project) + end + + scenario 'shows the Merge Requests settings that do not depend on Builds feature' do + expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') + + select 'Everyone with access', from: "project_project_feature_attributes_builds_access_level" + + expect(page).to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') + end + end + end + + context 'when Merge Request are initially disabled' do + before do + project.project_feature.update_attribute('merge_requests_access_level', ProjectFeature::DISABLED) + visit edit_project_path(project) + end + + scenario 'does not show the Merge Requests settings' do + expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved') + + select 'Everyone with access', from: "project_project_feature_attributes_merge_requests_access_level" + + expect(page).to have_content('Only allow merge requests to be merged if the build succeeds') + expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved') + end + end +end diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb new file mode 100644 index 00000000000..cef315ac9cd --- /dev/null +++ b/spec/features/projects/settings/visibility_settings_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +feature 'Visibility settings', feature: true, js: true do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace, visibility_level: 20) } + + context 'as owner' do + before do + login_as(user) + visit edit_namespace_project_path(project.namespace, project) + end + + scenario 'project visibility select is available' do + visibility_select_container = find('.js-visibility-select') + + expect(visibility_select_container.find('.visibility-select').value).to eq project.visibility_level.to_s + expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.' + end + + scenario 'project visibility description updates on change' do + visibility_select_container = find('.js-visibility-select') + visibility_select = visibility_select_container.find('.visibility-select') + visibility_select.select('Private') + + expect(visibility_select.value).to eq '0' + expect(visibility_select_container).to have_content 'Project access must be granted explicitly to each user.' + end + end + + context 'as master' do + let(:master_user) { create(:user) } + + before do + project.team << [master_user, :master] + login_as(master_user) + visit edit_namespace_project_path(project.namespace, project) + end + + scenario 'project visibility is locked' do + visibility_select_container = find('.js-visibility-select') + + expect(visibility_select_container).not_to have_select '.visibility-select' + expect(visibility_select_container).to have_content 'Public' + expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.' + end + end +end diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb index 7afd83b7250..fff8b9f3447 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -20,7 +20,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Home') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end @@ -41,7 +41,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Foo') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end @@ -55,7 +55,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Spaces in the name') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end @@ -69,7 +69,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Hyphens in the name') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end @@ -85,7 +85,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Home') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end @@ -105,7 +105,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Foo') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb index ef82d2375dd..f842d14fa96 100644 --- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb @@ -22,7 +22,7 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do click_button 'Save changes' expect(page).to have_content('Home') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end @@ -37,7 +37,7 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do click_button 'Save changes' expect(page).to have_content('Home') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index caecd027aaa..0fe5a897565 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -169,16 +169,16 @@ describe "Search", feature: true do find('.dropdown-menu').click_link 'Issues assigned to me' sleep 2 - expect(page).to have_selector('.issues-holder') - expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) + expect(page).to have_selector('.filtered-search') + expect(find('.filtered-search').value).to eq("assignee:@#{user.username}") end it 'takes user to her issues page when issues authored is clicked' do find('.dropdown-menu').click_link "Issues I've created" sleep 2 - expect(page).to have_selector('.issues-holder') - expect(find('.js-author-search .dropdown-toggle-text')).to have_content(user.name) + expect(page).to have_selector('.filtered-search') + expect(find('.filtered-search').value).to eq("author:@#{user.username}") end it 'takes user to her MR page when MR assigned is clicked' do @@ -211,4 +211,44 @@ describe "Search", feature: true do end end end + + describe 'search for commits' do + before do + visit search_path(project_id: project.id) + end + + it 'redirects to commit page when search by sha and only commit found' do + fill_in 'search', with: '6d394385cf567f80a8fd85055db1ab4c5295806f' + + click_button 'Search' + + expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f')) + end + + it 'redirects to single commit regardless of query case' do + fill_in 'search', with: '6D394385cf' + + click_button 'Search' + + expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f')) + end + + it 'holds on /search page when the only commit is found by message' do + create_commit('Message referencing another sha: "deadbeef" ', project, user, 'master') + + fill_in 'search', with: 'deadbeef' + click_button 'Search' + + expect(page).to have_current_path('/search', only_path: true) + end + + it 'shows multiple matching commits' do + fill_in 'search', with: 'See merge request' + + click_button 'Search' + click_link 'Commits' + + expect(page).to have_selector('.commit-row-description', count: 9) + end + end end diff --git a/spec/features/security/admin_access_spec.rb b/spec/features/security/admin_access_spec.rb index fe8cd7b7602..e180ca53eb5 100644 --- a/spec/features/security/admin_access_spec.rb +++ b/spec/features/security/admin_access_spec.rb @@ -4,7 +4,7 @@ describe "Admin::Projects", feature: true do include AccessMatchers describe "GET /admin/projects" do - subject { admin_namespaces_projects_path } + subject { admin_projects_path } it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for :user } diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index 35fcef7a712..87cce32d6c6 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Internal Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :internal) } + let(:group) { create(:group, :internal) } let(:project) { create(:project, :internal, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be internal" do @@ -34,75 +21,75 @@ describe 'Internal Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index 75a93342628..1d6b3e77c22 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Private Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :private) } + let(:group) { create(:group, :private) } let(:project) { create(:project, :private, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be private" do @@ -34,75 +21,75 @@ describe 'Private Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index 6c5ee93970b..d7d76177269 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Public Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :public) } + let(:group) { create(:group, :public) } let(:project) { create(:project, :public, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be public" do @@ -34,75 +21,75 @@ describe 'Public Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index b6acc509342..92d5a2fbc48 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -5,19 +5,6 @@ describe "Internal Project Access", feature: true do let(:project) { create(:project, :internal) } - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end - describe "Project should be internal" do describe '#internal?' do subject { project.internal? } @@ -28,213 +15,213 @@ describe "Internal Project Access", feature: true do describe "GET /:project_path" do subject { namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/tree/master" do subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/commits/master" do subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/commit/:sha" do subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/compare" do subject { namespace_project_compare_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end - describe "GET /:project_path/project_members" do - subject { namespace_project_project_members_path(project.namespace, project) } - - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + describe "GET /:project_path/settings/members" do + subject { namespace_project_settings_members_path(project.namespace, project) } + + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end describe "GET /:project_path/blob" do let(:commit) { project.repository.commit } subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/edit" do subject { edit_namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/deploy_keys" do subject { namespace_project_deploy_keys_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/issues" do subject { namespace_project_issues_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/issues/:id/edit" do let(:issue) { create(:issue, project: project) } subject { edit_namespace_project_issue_path(project.namespace, project, issue) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/new" do subject { new_namespace_project_snippet_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/merge_requests" do subject { namespace_project_merge_requests_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/merge_requests/new" do subject { new_namespace_project_merge_request_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/branches" do @@ -245,15 +232,15 @@ describe "Internal Project Access", feature: true do allow_any_instance_of(Project).to receive(:branches).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/tags" do @@ -264,58 +251,58 @@ describe "Internal Project Access", feature: true do allow_any_instance_of(Project).to receive(:tags).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end - describe "GET /:project_path/hooks" do - subject { namespace_project_hooks_path(project.namespace, project) } - - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + describe "GET /:project_path/settings/integrations" do + subject { namespace_project_settings_integrations_path(project.namespace, project) } + + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/pipelines" do subject { namespace_project_pipelines_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/pipelines/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } subject { namespace_project_pipeline_path(project.namespace, project, pipeline) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/builds" do @@ -324,29 +311,29 @@ describe "Internal Project Access", feature: true do context "when allowed for public and internal" do before { project.update(public_builds: true) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "when disallowed for public and internal" do before { project.update(public_builds: false) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end @@ -358,73 +345,73 @@ describe "Internal Project Access", feature: true do context "when allowed for public and internal" do before { project.update(public_builds: true) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "when disallowed for public and internal" do before { project.update(public_builds: false) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end describe "GET /:project_path/environments" do subject { namespace_project_environments_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/:id" do let(:environment) { create(:environment, project: project) } subject { namespace_project_environment_path(project.namespace, project, environment) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/new" do subject { new_namespace_project_environment_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/container_registry" do @@ -435,14 +422,14 @@ describe "Internal Project Access", feature: true do subject { namespace_project_container_registry_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 79417c769a8..b616e488487 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -3,20 +3,7 @@ require 'spec_helper' describe "Private Project Access", feature: true do include AccessMatchers - let(:project) { create(:project, :private) } - - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end + let(:project) { create(:project, :private, public_builds: false) } describe "Project should be private" do describe '#private?' do @@ -28,185 +15,185 @@ describe "Private Project Access", feature: true do describe "GET /:project_path" do subject { namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/tree/master" do subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/commits/master" do subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/commit/:sha" do subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/compare" do subject { namespace_project_compare_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end - describe "GET /:project_path/project_members" do - subject { namespace_project_project_members_path(project.namespace, project) } - - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + describe "GET /:project_path/settings/members" do + subject { namespace_project_settings_members_path(project.namespace, project) } + + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/blob" do let(:commit) { project.repository.commit } subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))} - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/edit" do subject { edit_namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/deploy_keys" do subject { namespace_project_deploy_keys_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/issues" do subject { namespace_project_issues_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/issues/:id/edit" do let(:issue) { create(:issue, project: project) } subject { edit_namespace_project_issue_path(project.namespace, project, issue) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/merge_requests" do subject { namespace_project_merge_requests_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/branches" do @@ -217,15 +204,15 @@ describe "Private Project Access", feature: true do allow_any_instance_of(Project).to receive(:branches).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/tags" do @@ -236,72 +223,108 @@ describe "Private Project Access", feature: true do allow_any_instance_of(Project).to receive(:tags).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end - describe "GET /:project_path/hooks" do - subject { namespace_project_hooks_path(project.namespace, project) } - - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + describe "GET /:project_path/namespace/hooks" do + subject { namespace_project_settings_integrations_path(project.namespace, project) } + + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/pipelines" do subject { namespace_project_pipelines_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } + + context 'when public builds is enabled' do + before do + project.update(public_builds: true) + end + + it { is_expected.to be_allowed_for(:guest).of(project) } + end + + context 'when public buils are disabled' do + it { is_expected.to be_denied_for(:guest).of(project) } + end end describe "GET /:project_path/pipelines/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } subject { namespace_project_pipeline_path(project.namespace, project, pipeline) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } + + context 'when public builds is enabled' do + before do + project.update(public_builds: true) + end + + it { is_expected.to be_allowed_for(:guest).of(project) } + end + + context 'when public buils are disabled' do + it { is_expected.to be_denied_for(:guest).of(project) } + end end describe "GET /:project_path/builds" do subject { namespace_project_builds_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } + + context 'when public builds is enabled' do + before do + project.update(public_builds: true) + end + + it { is_expected.to be_allowed_for(:guest).of(project) } + end + + context 'when public buils are disabled' do + it { is_expected.to be_denied_for(:guest).of(project) } + end end describe "GET /:project_path/builds/:id" do @@ -309,58 +332,75 @@ describe "Private Project Access", feature: true do let(:build) { create(:ci_build, pipeline: pipeline) } subject { namespace_project_build_path(project.namespace, project, build.id) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } + + context 'when public builds is enabled' do + before do + project.update(public_builds: true) + end + + it { is_expected.to be_allowed_for(:guest).of(project) } + end + + context 'when public buils are disabled' do + before do + project.public_builds = false + project.save + end + + it { is_expected.to be_denied_for(:guest).of(project) } + end end describe "GET /:project_path/environments" do subject { namespace_project_environments_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/:id" do let(:environment) { create(:environment, project: project) } subject { namespace_project_environment_path(project.namespace, project, environment) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/new" do subject { new_namespace_project_environment_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/container_registry" do @@ -371,14 +411,14 @@ describe "Private Project Access", feature: true do subject { namespace_project_container_registry_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 985663e7c98..ded85e837f4 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -5,19 +5,6 @@ describe "Public Project Access", feature: true do let(:project) { create(:project, :public) } - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end - describe "Project should be public" do describe '#public?' do subject { project.public? } @@ -28,114 +15,114 @@ describe "Public Project Access", feature: true do describe "GET /:project_path" do subject { namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/tree/master" do subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/commits/master" do subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/commit/:sha" do subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/compare" do subject { namespace_project_compare_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end - describe "GET /:project_path/project_members" do - subject { namespace_project_project_members_path(project.namespace, project) } - - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } - it { is_expected.to be_allowed_for :external } + describe "GET /:project_path/settings/members" do + subject { namespace_project_settings_members_path(project.namespace, project) } + + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:visitor) } + it { is_expected.to be_allowed_for(:external) } end describe "GET /:project_path/pipelines" do subject { namespace_project_pipelines_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/pipelines/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } subject { namespace_project_pipeline_path(project.namespace, project, pipeline) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/builds" do @@ -144,29 +131,29 @@ describe "Public Project Access", feature: true do context "when allowed for public" do before { project.update(public_builds: true) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end context "when disallowed for public" do before { project.update(public_builds: false) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end @@ -178,73 +165,73 @@ describe "Public Project Access", feature: true do context "when allowed for public" do before { project.update(public_builds: true) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end context "when disallowed for public" do before { project.update(public_builds: false) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end describe "GET /:project_path/environments" do subject { namespace_project_environments_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/:id" do let(:environment) { create(:environment, project: project) } subject { namespace_project_environment_path(project.namespace, project, environment) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/new" do subject { new_namespace_project_environment_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/blob" do @@ -252,127 +239,127 @@ describe "Public Project Access", feature: true do subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/edit" do subject { edit_namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/deploy_keys" do subject { namespace_project_deploy_keys_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/issues" do subject { namespace_project_issues_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/issues/:id/edit" do let(:issue) { create(:issue, project: project) } subject { edit_namespace_project_issue_path(project.namespace, project, issue) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/snippets/new" do subject { new_namespace_project_snippet_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/merge_requests" do subject { namespace_project_merge_requests_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/merge_requests/new" do subject { new_namespace_project_merge_request_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/branches" do @@ -383,15 +370,15 @@ describe "Public Project Access", feature: true do allow_any_instance_of(Project).to receive(:branches).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/tags" do @@ -402,29 +389,29 @@ describe "Public Project Access", feature: true do allow_any_instance_of(Project).to receive(:tags).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end - describe "GET /:project_path/hooks" do - subject { namespace_project_hooks_path(project.namespace, project) } - - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + describe "GET /:project_path/settings/integrations" do + subject { namespace_project_settings_integrations_path(project.namespace, project) } + + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/container_registry" do @@ -435,14 +422,14 @@ describe "Public Project Access", feature: true do subject { namespace_project_container_registry_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end end diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb index 49deacc5c74..2659b3ee3ec 100644 --- a/spec/features/security/project/snippet/internal_access_spec.rb +++ b/spec/features/security/project/snippet/internal_access_spec.rb @@ -5,76 +5,64 @@ describe "Internal Project Snippets Access", feature: true do let(:project) { create(:empty_project, :internal) } - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) } - let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end + let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) } + let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) } describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/new" do subject { new_namespace_project_snippet_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/:id" do context "for an internal snippet" do subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "for a private snippet" do subject { namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end @@ -82,29 +70,29 @@ describe "Internal Project Snippets Access", feature: true do context "for an internal snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "for a private snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end end diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb index a1bfc076d99..6eb9f163bd5 100644 --- a/spec/features/security/project/snippet/private_access_spec.rb +++ b/spec/features/security/project/snippet/private_access_spec.rb @@ -5,73 +5,61 @@ describe "Private Project Snippets Access", feature: true do let(:project) { create(:empty_project, :private) } - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end + let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) } describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/new" do subject { new_namespace_project_snippet_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/:id for a private snippet" do subject { namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/:id/raw for a private snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb index 30bcd87ef04..f3329d0bc96 100644 --- a/spec/features/security/project/snippet/public_access_spec.rb +++ b/spec/features/security/project/snippet/public_access_spec.rb @@ -5,91 +5,79 @@ describe "Public Project Snippets Access", feature: true do let(:project) { create(:empty_project, :public) } - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - let(:public_snippet) { create(:project_snippet, :public, project: project, author: owner) } - let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) } - let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end + let(:public_snippet) { create(:project_snippet, :public, project: project, author: project.owner) } + let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) } + let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) } describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/snippets/new" do subject { new_namespace_project_snippet_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/:id" do context "for a public snippet" do subject { namespace_project_snippet_path(project.namespace, project, public_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end context "for an internal snippet" do subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "for a private snippet" do subject { namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end @@ -97,43 +85,43 @@ describe "Public Project Snippets Access", feature: true do context "for a public snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, public_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end context "for an internal snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "for a private snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end end diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb index 65544f79eba..9fde8d6e5cf 100644 --- a/spec/features/signup_spec.rb +++ b/spec/features/signup_spec.rb @@ -10,10 +10,11 @@ feature 'Signup', feature: true do visit root_path - fill_in 'new_user_name', with: user.name - fill_in 'new_user_username', with: user.username - fill_in 'new_user_email', with: user.email - fill_in 'new_user_password', with: user.password + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: user.email + fill_in 'new_user_email_confirmation', with: user.email + fill_in 'new_user_password', with: user.password click_button "Register" expect(current_path).to eq users_almost_there_path @@ -29,10 +30,11 @@ feature 'Signup', feature: true do visit root_path - fill_in 'new_user_name', with: user.name - fill_in 'new_user_username', with: user.username - fill_in 'new_user_email', with: user.email - fill_in 'new_user_password', with: user.password + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: user.email + fill_in 'new_user_email_confirmation', with: user.email + fill_in 'new_user_password', with: user.password click_button "Register" expect(current_path).to eq dashboard_projects_path @@ -55,8 +57,9 @@ feature 'Signup', feature: true do click_button "Register" expect(current_path).to eq user_registration_path - expect(page).to have_content("error prohibited this user from being saved") + expect(page).to have_content("errors prohibited this user from being saved") expect(page).to have_content("Email has already been taken") + expect(page).to have_content("Email confirmation doesn't match") end it 'does not redisplay the password' do diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/create_snippet_spec.rb new file mode 100644 index 00000000000..5470276bf06 --- /dev/null +++ b/spec/features/snippets/create_snippet_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +feature 'Create Snippet', feature: true do + before do + login_as :user + visit new_snippet_path + end + + scenario 'Authenticated user creates a snippet' do + fill_in 'personal_snippet_title', with: 'My Snippet Title' + page.within('.file-editor') do + find(:xpath, "//input[@id='personal_snippet_content']").set 'Hello World!' + end + + click_button 'Create snippet' + + expect(page).to have_content('My Snippet Title') + expect(page).to have_content('Hello World!') + end + + scenario 'Authenticated user creates a snippet with + in filename' do + fill_in 'personal_snippet_title', with: 'My Snippet Title' + page.within('.file-editor') do + find(:xpath, "//input[@id='personal_snippet_file_name']").set 'snippet+file+name' + find(:xpath, "//input[@id='personal_snippet_content']").set 'Hello World!' + end + + click_button 'Create snippet' + + expect(page).to have_content('My Snippet Title') + expect(page).to have_content('snippet+file+name') + expect(page).to have_content('Hello World!') + end +end diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb new file mode 100644 index 00000000000..10a4597e467 --- /dev/null +++ b/spec/features/snippets/explore_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +feature 'Explore Snippets', feature: true do + scenario 'User should see snippets that are not private' do + public_snippet = create(:personal_snippet, :public) + internal_snippet = create(:personal_snippet, :internal) + private_snippet = create(:personal_snippet, :private) + + login_as create(:user) + visit explore_snippets_path + + expect(page).to have_content(public_snippet.title) + expect(page).to have_content(internal_snippet.title) + expect(page).not_to have_content(private_snippet.title) + end +end diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb new file mode 100644 index 00000000000..146cd3af848 --- /dev/null +++ b/spec/features/snippets/search_snippets_spec.rb @@ -0,0 +1,66 @@ +require 'rails_helper' + +feature 'Search Snippets', feature: true do + scenario 'User searches for snippets by title' do + public_snippet = create(:personal_snippet, :public, title: 'Beginning and Middle') + private_snippet = create(:personal_snippet, :private, title: 'Middle and End') + + login_as private_snippet.author + visit dashboard_snippets_path + + page.within '.search' do + fill_in 'search', with: 'Middle' + click_button 'Go' + end + + click_link 'Titles and Filenames' + + expect(page).to have_link(public_snippet.title) + expect(page).to have_link(private_snippet.title) + end + + scenario 'User searches for snippet contents' do + create(:personal_snippet, + :public, + title: 'Many lined snippet', + content: <<-CONTENT.strip_heredoc + |line one + |line two + |line three + |line four + |line five + |line six + |line seven + |line eight + |line nine + |line ten + |line eleven + |line twelve + |line thirteen + |line fourteen + CONTENT + ) + + login_as create(:user) + visit dashboard_snippets_path + + page.within '.search' do + fill_in 'search', with: 'line seven' + click_button 'Go' + end + + expect(page).to have_content('line seven') + + # 3 lines before the matched line should be visible + expect(page).to have_content('line six') + expect(page).to have_content('line five') + expect(page).to have_content('line four') + expect(page).not_to have_content('line three') + + # 3 lines after the matched line should be visible + expect(page).to have_content('line eight') + expect(page).to have_content('line nine') + expect(page).to have_content('line ten') + expect(page).not_to have_content('line eleven') + end +end diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb index 08a97085a9c..ca25c696f75 100644 --- a/spec/features/tags/master_creates_tag_spec.rb +++ b/spec/features/tags/master_creates_tag_spec.rb @@ -34,7 +34,7 @@ feature 'Master creates tag', feature: true do expect(current_path).to eq( namespace_project_tag_path(project.namespace, project, 'v3.0')) expect(page).to have_content 'v3.0' - page.within 'pre.body' do + page.within 'pre.wrap' do expect(page).to have_content "Awesome tag message\n\n- hello\n- world" end end diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index 3ae83ac082d..3850e930b6d 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -44,7 +44,7 @@ describe 'Dashboard Todos', feature: true do end it 'shows "All done" message' do - expect(page).to have_content("Good job! Looks like you don't have any todos left.") + expect(page).to have_selector('.todos-all-done', count: 1) end end @@ -64,7 +64,7 @@ describe 'Dashboard Todos', feature: true do end it 'shows "All done" message' do - expect(page).to have_content("Good job! Looks like you don't have any todos left.") + expect(page).to have_selector('.todos-all-done', count: 1) end end end @@ -152,7 +152,26 @@ describe 'Dashboard Todos', feature: true do within('.todos-pending-count') { expect(page).to have_content '0' } expect(page).to have_content 'To do 0' expect(page).to have_content 'Done 0' - expect(page).to have_content "Good job! Looks like you don't have any todos left." + expect(page).to have_selector('.todos-all-done', count: 1) + end + end + + context 'User has a Build Failed todo' do + let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) } + + before do + login_as user + visit dashboard_todos_path + end + + it 'shows the todo' do + expect(page).to have_content 'The build failed for merge request' + end + + it 'links to the pipelines for the merge request' do + href = pipelines_namespace_project_merge_request_path(project.namespace, project, todo.target) + + expect(page).to have_link "merge request #{todo.target.to_reference}", href end end end diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index b750f27ea72..a8d00bb8e5a 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -45,12 +45,12 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: it 'allows registering a new device with a name' do visit profile_account_path manage_two_factor_authentication - expect(page.body).to match("You've already enabled two-factor authentication using mobile") + expect(page).to have_content("You've already enabled two-factor authentication using mobile") u2f_device = register_u2f_device - expect(page.body).to match(u2f_device.name) - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content(u2f_device.name) + expect(page).to have_content('Your U2F device was registered') end it 'allows registering more than one device' do @@ -59,30 +59,30 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: # First device manage_two_factor_authentication first_device = register_u2f_device - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content('Your U2F device was registered') # Second device second_device = register_u2f_device - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content('Your U2F device was registered') - expect(page.body).to match(first_device.name) - expect(page.body).to match(second_device.name) + expect(page).to have_content(first_device.name) + expect(page).to have_content(second_device.name) expect(U2fRegistration.count).to eq(2) end it 'allows deleting a device' do visit profile_account_path manage_two_factor_authentication - expect(page.body).to match("You've already enabled two-factor authentication using mobile") + expect(page).to have_content("You've already enabled two-factor authentication using mobile") first_u2f_device = register_u2f_device second_u2f_device = register_u2f_device click_on "Delete", match: :first - expect(page.body).to match('Successfully deleted') + expect(page).to have_content('Successfully deleted') expect(page.body).not_to match(first_u2f_device.name) - expect(page.body).to match(second_u2f_device.name) + expect(page).to have_content(second_u2f_device.name) end end @@ -91,7 +91,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: visit profile_account_path manage_two_factor_authentication u2f_device = register_u2f_device - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content('Your U2F device was registered') logout # Second user @@ -100,7 +100,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: visit profile_account_path manage_two_factor_authentication register_u2f_device(u2f_device) - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content('Your U2F device was registered') expect(U2fRegistration.count).to eq(2) end @@ -117,8 +117,8 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: click_on 'Register U2F Device' expect(U2fRegistration.count).to eq(0) - expect(page.body).to match("The form contains the following error") - expect(page.body).to match("did not send a valid JSON response") + expect(page).to have_content("The form contains the following error") + expect(page).to have_content("did not send a valid JSON response") end it "allows retrying registration" do @@ -130,12 +130,12 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: click_on 'Setup New U2F Device' expect(page).to have_content('Your device was successfully set up') click_on 'Register U2F Device' - expect(page.body).to match("The form contains the following error") + expect(page).to have_content("The form contains the following error") # Successful registration register_u2f_device - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content('Your U2F device was registered') expect(U2fRegistration.count).to eq(1) end end @@ -160,11 +160,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user) @u2f_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" - expect(page.body).to match('Signed in successfully') + expect(page).to have_content('We heard back from your U2F device') + expect(page).to have_css('.sign-out-link', visible: false) end end @@ -174,11 +172,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user) @u2f_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" - expect(page.body).to match('Signed in successfully') + expect(page).to have_content('We heard back from your U2F device') + expect(page).to have_css('.sign-out-link', visible: false) end end @@ -186,8 +182,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user, remember: true) @u2f_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') + expect(page).to have_content('We heard back from your U2F device') within 'div#js-authenticate-u2f' do field = first('input#user_remember_me', visible: false) @@ -209,11 +204,8 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: # Try authenticating user with the old U2F device login_as(current_user) @u2f_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" - - expect(page.body).to match('Authentication via U2F device failed') + expect(page).to have_content('We heard back from your U2F device') + expect(page).to have_content('Authentication via U2F device failed') end end @@ -230,11 +222,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: # Try authenticating user with the same U2F device login_as(current_user) @u2f_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" + expect(page).to have_content('We heard back from your U2F device') - expect(page.body).to match('Signed in successfully') + expect(page).to have_css('.sign-out-link', visible: false) end end end @@ -244,11 +234,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: unregistered_device = FakeU2fDevice.new(page, FFaker::Name.first_name) login_as(user) unregistered_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" + expect(page).to have_content('We heard back from your U2F device') - expect(page.body).to match('Authentication via U2F device failed') + expect(page).to have_content('Authentication via U2F device failed') end end @@ -271,11 +259,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: [first_device, second_device].each do |device| login_as(user) device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" + expect(page).to have_content('We heard back from your U2F device') - expect(page.body).to match('Signed in successfully') + expect(page).to have_css('.sign-out-link', visible: false) logout end @@ -300,4 +286,50 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: end end end + + describe 'fallback code authentication' do + let(:user) { create(:user) } + + def assert_fallback_ui(page) + expect(page).to have_button('Verify code') + expect(page).to have_css('#user_otp_attempt') + expect(page).not_to have_link('Sign in via 2FA code') + expect(page).not_to have_css('#js-authenticate-u2f') + end + + before do + # Register and logout + login_as(user) + user.update_attribute(:otp_required_for_login, true) + visit profile_account_path + end + + describe 'when no u2f device is registered' do + before do + logout + login_with(user) + end + + it 'shows the fallback otp code UI' do + assert_fallback_ui(page) + end + end + + describe 'when a u2f device is registered' do + before do + manage_two_factor_authentication + @u2f_device = register_u2f_device + logout + login_with(user) + end + + it 'provides a button that shows the fallback otp code UI' do + expect(page).to have_link('Sign in via 2FA code') + + click_link('Sign in via 2FA code') + + assert_fallback_ui(page) + end + end + end end diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb index 33b52d1547e..e2d9cfdd0b0 100644 --- a/spec/features/unsubscribe_links_spec.rb +++ b/spec/features/unsubscribe_links_spec.rb @@ -26,11 +26,11 @@ describe 'Unsubscribe links', feature: true do expect(current_path).to eq unsubscribe_sent_notification_path(SentNotification.last) expect(page).to have_text(%(Unsubscribe from issue #{issue.title} (#{issue.to_reference}))) expect(page).to have_text(%(Are you sure you want to unsubscribe from issue #{issue.title} (#{issue.to_reference})?)) - expect(issue.subscribed?(recipient)).to be_truthy + expect(issue.subscribed?(recipient, project)).to be_truthy click_link 'Unsubscribe' - expect(issue.subscribed?(recipient)).to be_falsey + expect(issue.subscribed?(recipient, project)).to be_falsey expect(current_path).to eq new_user_session_path end @@ -38,11 +38,11 @@ describe 'Unsubscribe links', feature: true do visit body_link expect(current_path).to eq unsubscribe_sent_notification_path(SentNotification.last) - expect(issue.subscribed?(recipient)).to be_truthy + expect(issue.subscribed?(recipient, project)).to be_truthy click_link 'Cancel' - expect(issue.subscribed?(recipient)).to be_truthy + expect(issue.subscribed?(recipient, project)).to be_truthy expect(current_path).to eq new_user_session_path end end @@ -51,7 +51,7 @@ describe 'Unsubscribe links', feature: true do visit header_link expect(page).to have_text('unsubscribed') - expect(issue.subscribed?(recipient)).to be_falsey + expect(issue.subscribed?(recipient, project)).to be_falsey end end @@ -62,14 +62,14 @@ describe 'Unsubscribe links', feature: true do visit body_link expect(page).to have_text('unsubscribed') - expect(issue.subscribed?(recipient)).to be_falsey + expect(issue.subscribed?(recipient, project)).to be_falsey end it 'unsubscribes from the issue when visiting the link from the header' do visit header_link expect(page).to have_text('unsubscribed') - expect(issue.subscribed?(recipient)).to be_falsey + expect(issue.subscribed?(recipient, project)).to be_falsey end end end diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 111ca7f7a70..2de0fbe7ab2 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -6,10 +6,11 @@ feature 'Users', feature: true, js: true do scenario 'GET /users/sign_in creates a new user account' do visit new_user_session_path click_link 'Register' - fill_in 'new_user_name', with: 'Name Surname' - fill_in 'new_user_username', with: 'Great' - fill_in 'new_user_email', with: 'name@mail.com' - fill_in 'new_user_password', with: 'password1234' + fill_in 'new_user_name', with: 'Name Surname' + fill_in 'new_user_username', with: 'Great' + fill_in 'new_user_email', with: 'name@mail.com' + fill_in 'new_user_email_confirmation', with: 'name@mail.com' + fill_in 'new_user_password', with: 'password1234' expect { click_button 'Register' }.to change { User.count }.by(1) end @@ -33,10 +34,11 @@ feature 'Users', feature: true, js: true do scenario 'Should show one error if email is already taken' do visit new_user_session_path click_link 'Register' - fill_in 'new_user_name', with: 'Another user name' - fill_in 'new_user_username', with: 'anotheruser' - fill_in 'new_user_email', with: user.email - fill_in 'new_user_password', with: '12341234' + fill_in 'new_user_name', with: 'Another user name' + fill_in 'new_user_username', with: 'anotheruser' + fill_in 'new_user_email', with: user.email + fill_in 'new_user_email_confirmation', with: user.email + fill_in 'new_user_password', with: '12341234' expect { click_button 'Register' }.to change { User.count }.by(0) expect(page).to have_text('Email has already been taken') expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}' @@ -74,16 +76,29 @@ feature 'Users', feature: true, js: true do visit new_user_session_path click_link 'Register' end + + scenario 'doesn\'t show an error border if the username is available' do + fill_in username_input, with: 'new-user' + wait_for_ajax + expect(find('.username')).not_to have_css '.gl-field-error-outline' + end + + scenario 'does not show an error border if the username contains dots (.)' do + fill_in username_input, with: 'new.user.username' + wait_for_ajax + expect(find('.username')).not_to have_css '.gl-field-error-outline' + end + scenario 'shows an error border if the username already exists' do fill_in username_input, with: user.username wait_for_ajax expect(find('.username')).to have_css '.gl-field-error-outline' end - scenario 'doesn\'t show an error border if the username is available' do - fill_in username_input, with: 'new-user' + scenario 'shows an error border if the username contains special characters' do + fill_in username_input, with: 'new$user!username' wait_for_ajax - expect(find('#new_user_username')).not_to have_css '.gl-field-error-outline' + expect(find('.username')).to have_css '.gl-field-error-outline' end end diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb index d7880d5778f..ff30ffd7820 100644 --- a/spec/features/variables_spec.rb +++ b/spec/features/variables_spec.rb @@ -29,6 +29,31 @@ describe 'Project variables', js: true do end end + it 'reveals and hides new variable' do + fill_in('variable_key', with: 'key') + fill_in('variable_value', with: 'key value') + click_button('Add new variable') + + page.within('.variables-table') do + expect(page).to have_content('key') + expect(page).to have_content('******') + end + + click_button('Reveal Values') + + page.within('.variables-table') do + expect(page).to have_content('key') + expect(page).to have_content('key value') + end + + click_button('Hide Values') + + page.within('.variables-table') do + expect(page).to have_content('key') + expect(page).to have_content('******') + end + end + it 'deletes variable' do page.within('.variables-table') do find('.btn-variable-delete').click |