diff options
author | Matija Čupić <matteeyah@gmail.com> | 2018-05-02 15:54:06 +0200 |
---|---|---|
committer | Matija Čupić <matteeyah@gmail.com> | 2018-05-02 15:54:06 +0200 |
commit | fc42ba6c5da7c99cc3ad393cf23299d1051d8447 (patch) | |
tree | dff01aa9a0c3d24e65800668c29a565018d04b43 /spec | |
parent | d3327e0dfacd27d547ffdb3b7f4a1a76c76ae281 (diff) | |
parent | 75f6e91fcde12029068f48b6b7fb3fd13a612c61 (diff) | |
download | gitlab-ce-fc42ba6c5da7c99cc3ad393cf23299d1051d8447.tar.gz |
Merge branch 'master' into 44059-specify-variables-when-executing-a-manual-pipeline-from-the-ui
Diffstat (limited to 'spec')
46 files changed, 1424 insertions, 513 deletions
diff --git a/spec/controllers/projects/clusters/gcp_controller_spec.rb b/spec/controllers/projects/clusters/gcp_controller_spec.rb index e14ba29fa70..715bb9f5e52 100644 --- a/spec/controllers/projects/clusters/gcp_controller_spec.rb +++ b/spec/controllers/projects/clusters/gcp_controller_spec.rb @@ -142,7 +142,7 @@ describe Projects::Clusters::GcpController do context 'when google project billing is enabled' do before do - redis_double = double + redis_double = double.as_null_object allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) allow(redis_double).to receive(:get).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for('token')).and_return('true') end diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index 08e2ccf893a..c3468536ae1 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -54,9 +54,9 @@ describe Projects::RawController do end context 'and lfs uses object storage' do + let(:lfs_object) { create(:lfs_object, :with_file, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', size: '1575078') } + before do - lfs_object.file = fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") - lfs_object.save! stub_lfs_object_storage lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE) end diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb index 25309033571..ce61e6bf759 100644 --- a/spec/factories/ci/stages.rb +++ b/spec/factories/ci/stages.rb @@ -21,6 +21,7 @@ FactoryBot.define do pipeline factory: :ci_empty_pipeline name 'test' + position 1 status 'pending' end end diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb index ce5fbc343ee..53368c64e10 100644 --- a/spec/factories/commit_statuses.rb +++ b/spec/factories/commit_statuses.rb @@ -2,6 +2,7 @@ FactoryBot.define do factory :commit_status, class: CommitStatus do name 'default' stage 'test' + stage_idx 0 status 'success' description 'commit status' pipeline factory: :ci_pipeline_with_one_job diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb index a71020002dc..ed47f7ed390 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -40,7 +40,7 @@ feature 'Dashboard Groups page', :js do expect(page).to have_content(nested_group.name) end - describe 'when filtering groups', :nested_groups do + context 'when filtering groups', :nested_groups do before do group.add_owner(user) nested_group.add_owner(user) @@ -75,7 +75,7 @@ feature 'Dashboard Groups page', :js do end end - describe 'group with subgroups', :nested_groups do + context 'with subgroups', :nested_groups do let!(:subgroup) { create(:group, :public, parent: group) } before do @@ -106,7 +106,7 @@ feature 'Dashboard Groups page', :js do end end - describe 'when using pagination' do + context 'when using pagination' do let(:group) { create(:group, created_at: 5.days.ago) } let(:group2) { create(:group, created_at: 2.days.ago) } @@ -141,4 +141,20 @@ feature 'Dashboard Groups page', :js do expect(page).not_to have_selector("#group-#{group2.id}") end end + + context 'when signed in as admin' do + let(:admin) { create(:admin) } + + it 'shows only groups admin is member of' do + group.add_owner(admin) + expect(another_group).to be_persisted + + sign_in(admin) + visit dashboard_groups_path + wait_for_requests + + expect(page).to have_content(group.name) + expect(page).not_to have_content(another_group.name) + end + end end diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb new file mode 100644 index 00000000000..4045cfd21c4 --- /dev/null +++ b/spec/features/profiles/active_sessions_spec.rb @@ -0,0 +1,89 @@ +require 'rails_helper' + +feature 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do + let(:user) do + create(:user).tap do |user| + user.current_sign_in_at = Time.current + end + end + + around do |example| + Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do + example.run + end + end + + scenario 'User sees their active sessions' do + Capybara::Session.new(:session1) + Capybara::Session.new(:session2) + + # note: headers can only be set on the non-js (aka. rack-test) driver + using_session :session1 do + Capybara.page.driver.header( + 'User-Agent', + 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0' + ) + + gitlab_sign_in(user) + end + + # set an additional session on another device + using_session :session2 do + Capybara.page.driver.header( + 'User-Agent', + 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12B466 [FBDV/iPhone7,2]' + ) + + gitlab_sign_in(user) + end + + using_session :session1 do + visit profile_active_sessions_path + + expect(page).to have_content( + '127.0.0.1 ' \ + 'This is your current session ' \ + 'Firefox on Ubuntu ' \ + 'Signed in on 12 Mar 09:06' + ) + + expect(page).to have_selector '[title="Desktop"]', count: 1 + + expect(page).to have_content( + '127.0.0.1 ' \ + 'Last accessed on 12 Mar 09:06 ' \ + 'Mobile Safari on iOS ' \ + 'Signed in on 12 Mar 09:06' + ) + + expect(page).to have_selector '[title="Smartphone"]', count: 1 + end + end + + scenario 'User can revoke a session', :js, :redis_session_store do + Capybara::Session.new(:session1) + Capybara::Session.new(:session2) + + # set an additional session in another browser + using_session :session2 do + gitlab_sign_in(user) + end + + using_session :session1 do + gitlab_sign_in(user) + visit profile_active_sessions_path + + expect(page).to have_link('Revoke', count: 1) + + accept_confirm { click_on 'Revoke' } + + expect(page).not_to have_link('Revoke') + end + + using_session :session2 do + visit profile_active_sessions_path + + expect(page).to have_content('You need to sign in or sign up before continuing.') + end + end +end diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index 9c1f11f4c12..41f6c52fb8a 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -1,14 +1,12 @@ -require 'spec_helper' +require "spec_helper" -describe 'Projects > Files > User browses files' do +describe "User browses files" do let(:fork_message) do "You're not allowed to make changes to this project directly. "\ "A fork of this project has been created that you can make changes in, so you can submit a merge request." end - let(:project) { create(:project, :repository, name: 'Shop') } - let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } - let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } - let(:tree_path_ref_6d39438) { project_tree_path(project, '6d39438') } + let(:project) { create(:project, :repository, name: "Shop") } + let(:project2) { create(:project, :repository, name: "Another Project", path: "another-project") } let(:tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } let(:user) { project.owner } @@ -16,57 +14,55 @@ describe 'Projects > Files > User browses files' do sign_in(user) end - it 'shows last commit for current directory' do + it "shows last commit for current directory" do visit(tree_path_root_ref) - click_link 'files' + click_link("files") - last_commit = project.repository.last_commit_for_path(project.default_branch, 'files') - page.within('.blob-commit-info') do - expect(page).to have_content last_commit.short_id - expect(page).to have_content last_commit.author_name + last_commit = project.repository.last_commit_for_path(project.default_branch, "files") + + page.within(".blob-commit-info") do + expect(page).to have_content(last_commit.short_id).and have_content(last_commit.author_name) end end - context 'when browsing the master branch' do + context "when browsing the master branch" do before do visit(tree_path_root_ref) end - it 'shows files from a repository' do - expect(page).to have_content('VERSION') - expect(page).to have_content('.gitignore') - expect(page).to have_content('LICENSE') + it "shows files from a repository" do + expect(page).to have_content("VERSION") + .and have_content(".gitignore") + .and have_content("LICENSE") end - it 'shows the "Browse Directory" link' do - click_link('files') - click_link('History') + it "shows the `Browse Directory` link" do + click_link("files") + click_link("History") - expect(page).to have_link('Browse Directory') - expect(page).not_to have_link('Browse Code') + expect(page).to have_link("Browse Directory").and have_no_link("Browse Code") end - it 'shows the "Browse File" link' do - page.within('.tree-table') do - click_link('README.md') + it "shows the `Browse File` link" do + page.within(".tree-table") do + click_link("README.md") end - click_link('History') - expect(page).to have_link('Browse File') - expect(page).not_to have_link('Browse Files') + click_link("History") + + expect(page).to have_link("Browse File").and have_no_link("Browse Files") end - it 'shows the "Browse Files" link' do - click_link('History') + it "shows the `Browse Files` link" do + click_link("History") - expect(page).to have_link('Browse Files') - expect(page).not_to have_link('Browse Directory') + expect(page).to have_link("Browse Files").and have_no_link("Browse Directory") end - it 'redirects to the permalink URL' do - click_link('.gitignore') - click_link('Permalink') + it "redirects to the permalink URL" do + click_link(".gitignore") + click_link("Permalink") permalink_path = project_blob_path(project, "#{project.repository.commit.sha}/.gitignore") @@ -74,80 +70,180 @@ describe 'Projects > Files > User browses files' do end end - context 'when browsing a specific ref' do + context "when browsing the `markdown` branch", :js do + context "when browsing the root" do + before do + visit(project_tree_path(project, "markdown")) + end + + it "shows correct files and links" do + # rubocop:disable Lint/Void + # Test the full URLs of links instead of relative paths by `have_link(text: "...", href: "...")`. + find("a", text: /^empty$/)["href"] == project_tree_url(project, "markdown") + find("a", text: /^#id$/)["href"] == project_tree_url(project, "markdown", anchor: "#id") + find("a", text: %r{^/#id$})["href"] == project_tree_url(project, "markdown", anchor: "#id") + find("a", text: /^README.md#id$/)["href"] == project_blob_url(project, "markdown/README.md", anchor: "#id") + find("a", text: %r{^d/README.md#id$})["href"] == project_blob_url(project, "d/markdown/README.md", anchor: "#id") + # rubocop:enable Lint/Void + + expect(current_path).to eq(project_tree_path(project, "markdown")) + expect(page).to have_content("README.md") + .and have_content("CHANGELOG") + .and have_content("Welcome to GitLab GitLab is a free project and repository management application") + .and have_link("GitLab API doc") + .and have_link("GitLab API website") + .and have_link("Rake tasks") + .and have_link("backup and restore procedure") + .and have_link("GitLab API doc directory") + .and have_link("Maintenance") + .and have_header_with_correct_id_and_link(2, "Application details", "application-details") + end + + it "shows correct content of file" do + click_link("GitLab API doc") + + expect(current_path).to eq(project_blob_path(project, "markdown/doc/api/README.md")) + expect(page).to have_content("All API requests require authentication") + .and have_content("Contents") + .and have_link("Users") + .and have_link("Rake tasks") + .and have_header_with_correct_id_and_link(1, "GitLab API", "gitlab-api") + + click_link("Users") + + expect(current_path).to eq(project_blob_path(project, "markdown/doc/api/users.md")) + expect(page).to have_content("Get a list of users.") + + page.go_back + + click_link("Rake tasks") + + expect(current_path).to eq(project_tree_path(project, "markdown/doc/raketasks")) + expect(page).to have_content("backup_restore.md").and have_content("maintenance.md") + + click_link("shop") + click_link("Maintenance") + + expect(current_path).to eq(project_blob_path(project, "markdown/doc/raketasks/maintenance.md")) + expect(page).to have_content("bundle exec rake gitlab:env:info RAILS_ENV=production") + + click_link("shop") + + page.within(".tree-table") do + click_link("README.md") + end + + page.go_back + + page.within(".tree-table") do + click_link("d") + end + + # rubocop:disable Lint/Void + # Test the full URLs of links instead of relative paths by `have_link(text: "...", href: "...")`. + find("a", text: /^empty$/)["href"] == project_tree_url(project, "markdown/d") + # rubocop:enable Lint/Void + + page.within(".tree-table") do + click_link("README.md") + end + + # rubocop:disable Lint/Void + # Test the full URLs of links instead of relative paths by `have_link(text: "...", href: "...")`. + find("a", text: /^empty$/)["href"] == project_blob_url(project, "markdown/d/README.md") + # rubocop:enable Lint/Void + end + + it "shows correct content of directory" do + click_link("GitLab API doc directory") + + expect(current_path).to eq(project_tree_path(project, "markdown/doc/api")) + expect(page).to have_content("README.md").and have_content("users.md") + + click_link("Users") + + expect(current_path).to eq(project_blob_path(project, "markdown/doc/api/users.md")) + expect(page).to have_content("List users").and have_content("Get a list of users.") + end + end + end + + context "when browsing a specific ref" do + let(:ref) { project_tree_path(project, "6d39438") } + before do - visit(tree_path_ref_6d39438) + visit(ref) end - it 'shows files from a repository for "6d39438"' do - expect(current_path).to eq(tree_path_ref_6d39438) - expect(page).to have_content('.gitignore') - expect(page).to have_content('LICENSE') + it "shows files from a repository for `6d39438`" do + expect(current_path).to eq(ref) + expect(page).to have_content(".gitignore").and have_content("LICENSE") end - it 'shows files from a repository with apostroph in its name', :js do - first('.js-project-refs-dropdown').click + it "shows files from a repository with apostroph in its name", :js do + first(".js-project-refs-dropdown").click - page.within('.project-refs-form') do + page.within(".project-refs-form") do click_link("'test'") end - expect(page).to have_selector('.dropdown-toggle-text', text: "'test'") + expect(page).to have_selector(".dropdown-toggle-text", text: "'test'") visit(project_tree_path(project, "'test'")) - expect(page).to have_css('.tree-commit-link', visible: true) - expect(page).not_to have_content('Loading commit data...') + expect(page).to have_css(".tree-commit-link").and have_no_content("Loading commit data...") end - it 'shows the code with a leading dot in the directory', :js do - first('.js-project-refs-dropdown').click + it "shows the code with a leading dot in the directory", :js do + first(".js-project-refs-dropdown").click - page.within('.project-refs-form') do - click_link('fix') + page.within(".project-refs-form") do + click_link("fix") end - visit(project_tree_path(project, 'fix/.testdir')) + visit(project_tree_path(project, "fix/.testdir")) - expect(page).to have_css('.tree-commit-link', visible: true) - expect(page).not_to have_content('Loading commit data...') + expect(page).to have_css(".tree-commit-link").and have_no_content("Loading commit data...") end - it 'does not show the permalink link' do - click_link('.gitignore') + it "does not show the permalink link" do + click_link(".gitignore") - expect(page).not_to have_link('permalink') + expect(page).not_to have_link("permalink") end end - context 'when browsing a file content' do + context "when browsing a file content" do before do visit(tree_path_root_ref) - click_link('.gitignore') + + click_link(".gitignore") end - it 'shows a file content', :js do - wait_for_requests - expect(page).to have_content('*.rbc') + it "shows a file content", :js do + expect(page).to have_content("*.rbc") end - it 'is possible to blame' do - click_link 'Blame' + it "is possible to blame" do + click_link("Blame") - expect(page).to have_content "*.rb" - expect(page).to have_content "Dmitriy Zaporozhets" - expect(page).to have_content "Initial commit" + expect(page).to have_content("*.rb") + .and have_content("Dmitriy Zaporozhets") + .and have_content("Initial commit") end end - context 'when browsing a raw file' do + context "when browsing a raw file" do before do - visit(project_blob_path(project, File.join(RepoHelpers.sample_commit.id, RepoHelpers.sample_blob.path))) + path = File.join(RepoHelpers.sample_commit.id, RepoHelpers.sample_blob.path) + + visit(project_blob_path(project, path)) end - it 'shows a raw file content' do - click_link('Open raw') - expect(source).to eq('') # Body is filled in by gitlab-workhorse + it "shows a raw file content" do + click_link("Open raw") + + expect(source).to eq("") # Body is filled in by gitlab-workhorse end end end diff --git a/spec/features/projects/issues/user_toggles_subscription_spec.rb b/spec/features/projects/issues/user_toggles_subscription_spec.rb index 117a614b980..c2b2a193682 100644 --- a/spec/features/projects/issues/user_toggles_subscription_spec.rb +++ b/spec/features/projects/issues/user_toggles_subscription_spec.rb @@ -1,9 +1,9 @@ require "spec_helper" describe "User toggles subscription", :js do - set(:project) { create(:project_empty_repo, :public) } - set(:user) { create(:user) } - set(:issue) { create(:issue, project: project, author: user) } + let(:project) { create(:project_empty_repo, :public) } + let(:user) { create(:user) } + let(:issue) { create(:issue, project: project, author: user) } before do project.add_developer(user) @@ -12,7 +12,7 @@ describe "User toggles subscription", :js do visit(project_issue_path(project, issue)) end - it "unsibscribes from issue" do + it "unsubscribes from issue" do subscription_button = find(".js-issuable-subscribe-button") # Check we're subscribed. diff --git a/spec/features/projects/settings/lfs_settings_spec.rb b/spec/features/projects/settings/lfs_settings_spec.rb index 0fd28a5681c..342be1d2a9d 100644 --- a/spec/features/projects/settings/lfs_settings_spec.rb +++ b/spec/features/projects/settings/lfs_settings_spec.rb @@ -1,21 +1,27 @@ require 'rails_helper' describe 'Projects > Settings > LFS settings' do - let(:admin) { create(:admin) } let(:project) { create(:project) } + let(:user) { create(:user) } + let(:role) { :master } context 'LFS enabled setting' do before do allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) - sign_in(admin) + sign_in(user) + project.add_role(user, role) end - it 'displays the correct elements', :js do - visit edit_project_path(project) + context 'for master' do + let(:role) { :master } - expect(page).to have_content('Git Large File Storage') - expect(page).to have_selector('input[name="project[lfs_enabled]"] + button', visible: true) + it 'displays the correct elements', :js do + visit edit_project_path(project) + + expect(page).to have_content('Git Large File Storage') + expect(page).to have_selector('input[name="project[lfs_enabled]"] + button', visible: true) + end 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 4a9d1cb87e1..fe6fa55fa75 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -1,6 +1,6 @@ -require 'spec_helper' +require "spec_helper" -describe 'User creates wiki page' do +describe "User creates wiki page" do let(:user) { create(:user) } before do @@ -10,67 +10,104 @@ describe 'User creates wiki page' do visit(project_wikis_path(project)) end - context 'when wiki is empty' do - context 'in a user namespace' do + context "when wiki is empty" do + context "in a user namespace" do let(:project) { create(:project, namespace: user.namespace) } - it 'shows validation error message' do - page.within('.wiki-form') do - fill_in(:wiki_content, with: '') - click_on('Create page') + it "shows validation error message" do + page.within(".wiki-form") do + fill_in(:wiki_content, with: "") + + click_on("Create page") end - expect(page).to have_content('The form contains the following error:') - expect(page).to have_content("Content can't be blank") + expect(page).to have_content("The form contains the following error:").and have_content("Content can't be blank") + + page.within(".wiki-form") do + fill_in(:wiki_content, with: "[link test](test)") - page.within('.wiki-form') do - fill_in(:wiki_content, with: '[link test](test)') - click_on('Create page') + click_on("Create page") end - expect(page).to have_content('Home') - expect(page).to have_content('link test') + expect(page).to have_content("Home").and have_content("link test") - click_link('link test') + click_link("link test") - expect(page).to have_content('Create Page') + expect(page).to have_content("Create Page") end - it 'shows non-escaped link in the pages list', :js do - click_link('New page') + it "shows non-escaped link in the pages list", :js do + click_link("New page") - page.within('#modal-new-wiki') do - fill_in(:new_wiki_path, with: 'one/two/three-test') - click_on('Create page') + page.within("#modal-new-wiki") do + fill_in(:new_wiki_path, with: "one/two/three-test") + + click_on("Create page") end - page.within('.wiki-form') do - fill_in(:wiki_content, with: 'wiki content') - click_on('Create page') + page.within(".wiki-form") do + fill_in(:wiki_content, with: "wiki content") + + click_on("Create page") end - expect(current_path).to include('one/two/three-test') + expect(current_path).to include("one/two/three-test") expect(page).to have_xpath("//a[@href='/#{project.full_path}/wikis/one/two/three-test']") end - it 'has "Create home" as a commit message' do - expect(page).to have_field('wiki[message]', with: 'Create home') + it "has `Create home` as a commit message" do + expect(page).to have_field("wiki[message]", with: "Create home") end - it 'creates a page from the home page' do - fill_in(:wiki_content, with: 'My awesome wiki!') + it "creates a page from the home page" do + fill_in(:wiki_content, with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n# Wiki header\n") + fill_in(:wiki_message, with: "Adding links to wiki") + + page.within(".wiki-form") do + click_button("Create page") + end + + expect(current_path).to eq(project_wiki_path(project, "home")) + expect(page).to have_content("test GitLab API doc Rake tasks Wiki header") + .and have_content("Home") + .and have_content("Last edited by #{user.name}") + .and have_header_with_correct_id_and_link(1, "Wiki header", "wiki-header") + + click_link("test") - page.within('.wiki-form') do - click_button('Create page') + expect(current_path).to eq(project_wiki_path(project, "test")) + + page.within(:css, ".nav-text") do + expect(page).to have_content("Test").and have_content("Create Page") + end + + click_link("Home") + + expect(current_path).to eq(project_wiki_path(project, "home")) + + click_link("GitLab API") + + expect(current_path).to eq(project_wiki_path(project, "api")) + + page.within(:css, ".nav-text") do + expect(page).to have_content("Create").and have_content("Api") end - expect(page).to have_content('Home') - expect(page).to have_content("Last edited by #{user.name}") - expect(page).to have_content('My awesome wiki!') + click_link("Home") + + expect(current_path).to eq(project_wiki_path(project, "home")) + + click_link("Rake tasks") + + expect(current_path).to eq(project_wiki_path(project, "raketasks")) + + page.within(:css, ".nav-text") do + expect(page).to have_content("Create").and have_content("Rake") + end end - it 'creates ASCII wiki with LaTeX blocks', :js do - stub_application_setting(plantuml_url: 'http://localhost', plantuml_enabled: true) + it "creates ASCII wiki with LaTeX blocks", :js do + stub_application_setting(plantuml_url: "http://localhost", plantuml_enabled: true) ascii_content = <<~MD :stem: latexmath @@ -90,153 +127,164 @@ describe 'User creates wiki page' do stem:[2+2] is 4 MD - find('#wiki_format option[value=asciidoc]').select_option + find("#wiki_format option[value=asciidoc]").select_option + fill_in(:wiki_content, with: ascii_content) - page.within('.wiki-form') do - click_button('Create page') + page.within(".wiki-form") do + click_button("Create page") end - page.within '.wiki' do - expect(page).to have_selector('.katex', count: 3) - expect(page).to have_content('2+2 is 4') + page.within ".wiki" do + expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4") end end end - context 'in a group namespace', :js do + context "in a group namespace", :js do let(:project) { create(:project, namespace: create(:group, :public)) } - it 'has "Create home" as a commit message' do - expect(page).to have_field('wiki[message]', with: 'Create home') + it "has `Create home` as a commit message" do + expect(page).to have_field("wiki[message]", with: "Create home") end - it 'creates a page from from the home page' do - page.within('.wiki-form') do - fill_in(:wiki_content, with: 'My awesome wiki!') - click_button('Create page') + it "creates a page from from the home page" do + page.within(".wiki-form") do + fill_in(:wiki_content, with: "My awesome wiki!") + + click_button("Create page") end - expect(page).to have_content('Home') - expect(page).to have_content("Last edited by #{user.name}") - expect(page).to have_content('My awesome wiki!') + expect(page).to have_content("Home") + .and have_content("Last edited by #{user.name}") + .and have_content("My awesome wiki!") end end end - context 'when wiki is not empty', :js do + context "when wiki is not empty", :js do before do - create(:wiki_page, wiki: create(:project, namespace: user.namespace).wiki, attrs: { title: 'home', content: 'Home page' }) + create(:wiki_page, wiki: create(:project, namespace: user.namespace).wiki, attrs: { title: "home", content: "Home page" }) end - context 'in a user namespace' do + context "in a user namespace" do let(:project) { create(:project, namespace: user.namespace) } - context 'via the "new wiki page" page' do - it 'creates a page with a single word' do - click_link('New page') + context "via the `new wiki page` page" do + it "creates a page with a single word" do + click_link("New page") - page.within('#modal-new-wiki') do - fill_in(:new_wiki_path, with: 'foo') - click_button('Create page') + page.within("#modal-new-wiki") do + fill_in(:new_wiki_path, with: "foo") + + click_button("Create page") end # Commit message field should have correct value. - expect(page).to have_field('wiki[message]', with: 'Create foo') + expect(page).to have_field("wiki[message]", with: "Create foo") + + page.within(".wiki-form") do + fill_in(:wiki_content, with: "My awesome wiki!") - page.within('.wiki-form') do - fill_in(:wiki_content, with: 'My awesome wiki!') - click_button('Create page') + click_button("Create page") end - expect(page).to have_content('Foo') - expect(page).to have_content("Last edited by #{user.name}") - expect(page).to have_content('My awesome wiki!') + expect(page).to have_content("Foo") + .and have_content("Last edited by #{user.name}") + .and have_content("My awesome wiki!") end - it 'creates a page with spaces in the name' do - click_link('New page') + it "creates a page with spaces in the name" do + click_link("New page") - page.within('#modal-new-wiki') do - fill_in(:new_wiki_path, with: 'Spaces in the name') - click_button('Create page') + page.within("#modal-new-wiki") do + fill_in(:new_wiki_path, with: "Spaces in the name") + + click_button("Create page") end # Commit message field should have correct value. - expect(page).to have_field('wiki[message]', with: 'Create spaces in the name') + expect(page).to have_field("wiki[message]", with: "Create spaces in the name") + + page.within(".wiki-form") do + fill_in(:wiki_content, with: "My awesome wiki!") - page.within('.wiki-form') do - fill_in(:wiki_content, with: 'My awesome wiki!') - click_button('Create page') + click_button("Create page") end - expect(page).to have_content('Spaces in the name') - expect(page).to have_content("Last edited by #{user.name}") - expect(page).to have_content('My awesome wiki!') + expect(page).to have_content("Spaces in the name") + .and have_content("Last edited by #{user.name}") + .and have_content("My awesome wiki!") end - it 'creates a page with hyphens in the name' do - click_link('New page') + it "creates a page with hyphens in the name" do + click_link("New page") - page.within('#modal-new-wiki') do - fill_in(:new_wiki_path, with: 'hyphens-in-the-name') - click_button('Create page') + page.within("#modal-new-wiki") do + fill_in(:new_wiki_path, with: "hyphens-in-the-name") + + click_button("Create page") end # Commit message field should have correct value. - expect(page).to have_field('wiki[message]', with: 'Create hyphens in the name') + expect(page).to have_field("wiki[message]", with: "Create hyphens in the name") + + page.within(".wiki-form") do + fill_in(:wiki_content, with: "My awesome wiki!") - page.within('.wiki-form') do - fill_in(:wiki_content, with: 'My awesome wiki!') - click_button('Create page') + click_button("Create page") end - expect(page).to have_content('Hyphens in the name') - expect(page).to have_content("Last edited by #{user.name}") - expect(page).to have_content('My awesome wiki!') + expect(page).to have_content("Hyphens in the name") + .and have_content("Last edited by #{user.name}") + .and have_content("My awesome wiki!") end end - it 'shows the autocompletion dropdown' do - click_link('New page') + it "shows the autocompletion dropdown" do + click_link("New page") - page.within('#modal-new-wiki') do - fill_in(:new_wiki_path, with: 'test-autocomplete') - click_button('Create page') + page.within("#modal-new-wiki") do + fill_in(:new_wiki_path, with: "test-autocomplete") + + click_button("Create page") end - page.within('.wiki-form') do - find('#wiki_content').native.send_keys('') - fill_in(:wiki_content, with: '@') + page.within(".wiki-form") do + find("#wiki_content").native.send_keys("") + + fill_in(:wiki_content, with: "@") end - expect(page).to have_selector('.atwho-view') + expect(page).to have_selector(".atwho-view") end end - context 'in a group namespace' do + context "in a group namespace" do let(:project) { create(:project, namespace: create(:group, :public)) } - context 'via the "new wiki page" page' do - it 'creates a page' do - click_link('New page') + context "via the `new wiki page` page" do + it "creates a page" do + click_link("New page") - page.within('#modal-new-wiki') do - fill_in(:new_wiki_path, with: 'foo') - click_button('Create page') + page.within("#modal-new-wiki") do + fill_in(:new_wiki_path, with: "foo") + + click_button("Create page") end # Commit message field should have correct value. - expect(page).to have_field('wiki[message]', with: 'Create foo') + expect(page).to have_field("wiki[message]", with: "Create foo") + + page.within(".wiki-form") do + fill_in(:wiki_content, with: "My awesome wiki!") - page.within('.wiki-form') do - fill_in(:wiki_content, with: 'My awesome wiki!') - click_button('Create page') + click_button("Create page") end - expect(page).to have_content('Foo') - expect(page).to have_content("Last edited by #{user.name}") - expect(page).to have_content('My awesome wiki!') + expect(page).to have_content("Foo") + .and have_content("Last edited by #{user.name}") + .and have_content("My awesome wiki!") end end end diff --git a/spec/features/users/active_sessions_spec.rb b/spec/features/users/active_sessions_spec.rb new file mode 100644 index 00000000000..631d7e3bced --- /dev/null +++ b/spec/features/users/active_sessions_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +feature 'Active user sessions', :clean_gitlab_redis_shared_state do + scenario 'Successful login adds a new active user login' do + now = Time.zone.parse('2018-03-12 09:06') + Timecop.freeze(now) do + user = create(:user) + gitlab_sign_in(user) + expect(current_path).to eq root_path + + sessions = ActiveSession.list(user) + expect(sessions.count).to eq 1 + + # refresh the current page updates the updated_at + Timecop.freeze(now + 1.minute) do + visit current_path + + sessions = ActiveSession.list(user) + expect(sessions.first).to have_attributes( + created_at: Time.zone.parse('2018-03-12 09:06'), + updated_at: Time.zone.parse('2018-03-12 09:07') + ) + end + end + end + + scenario 'Successful login cleans up obsolete entries' do + user = create(:user) + + Gitlab::Redis::SharedState.with do |redis| + redis.sadd("session:lookup:user:gitlab:#{user.id}", '59822c7d9fcdfa03725eff41782ad97d') + end + + gitlab_sign_in(user) + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.smembers("session:lookup:user:gitlab:#{user.id}")).not_to include '59822c7d9fcdfa03725eff41782ad97d' + end + end + + scenario 'Sessionless login does not clean up obsolete entries' do + user = create(:user) + personal_access_token = create(:personal_access_token, user: user) + + Gitlab::Redis::SharedState.with do |redis| + redis.sadd("session:lookup:user:gitlab:#{user.id}", '59822c7d9fcdfa03725eff41782ad97d') + end + + visit user_path(user, :atom, private_token: personal_access_token.token) + expect(page.status_code).to eq 200 + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.smembers("session:lookup:user:gitlab:#{user.id}")).to include '59822c7d9fcdfa03725eff41782ad97d' + end + end + + scenario 'Logout deletes the active user login' do + user = create(:user) + gitlab_sign_in(user) + expect(current_path).to eq root_path + + expect(ActiveSession.list(user).count).to eq 1 + + gitlab_sign_out + expect(current_path).to eq new_user_session_path + + expect(ActiveSession.list(user)).to be_empty + end +end diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb index abc470788e1..16c0d418d98 100644 --- a/spec/finders/groups_finder_spec.rb +++ b/spec/finders/groups_finder_spec.rb @@ -2,43 +2,71 @@ require 'spec_helper' describe GroupsFinder do describe '#execute' do - let(:user) { create(:user) } - - context 'root level groups' do - let!(:private_group) { create(:group, :private) } - let!(:internal_group) { create(:group, :internal) } - let!(:public_group) { create(:group, :public) } - - context 'without a user' do - subject { described_class.new.execute } - - it { is_expected.to eq([public_group]) } + let(:user) { create(:user) } + + describe 'root level groups' do + using RSpec::Parameterized::TableSyntax + + where(:user_type, :params, :results) do + nil | { all_available: true } | %i(public_group user_public_group) + nil | { all_available: false } | %i(public_group user_public_group) + nil | {} | %i(public_group user_public_group) + + :regular | { all_available: true } | %i(public_group internal_group user_public_group user_internal_group + user_private_group) + :regular | { all_available: false } | %i(user_public_group user_internal_group user_private_group) + :regular | {} | %i(public_group internal_group user_public_group user_internal_group user_private_group) + + :external | { all_available: true } | %i(public_group user_public_group user_internal_group user_private_group) + :external | { all_available: false } | %i(user_public_group user_internal_group user_private_group) + :external | {} | %i(public_group user_public_group user_internal_group user_private_group) + + :admin | { all_available: true } | %i(public_group internal_group private_group user_public_group + user_internal_group user_private_group) + :admin | { all_available: false } | %i(user_public_group user_internal_group user_private_group) + :admin | {} | %i(public_group internal_group private_group user_public_group user_internal_group + user_private_group) end - context 'with a user' do - subject { described_class.new(user).execute } - - context 'normal user' do - it { is_expected.to contain_exactly(public_group, internal_group) } - end - - context 'external user' do - let(:user) { create(:user, external: true) } - - it { is_expected.to contain_exactly(public_group) } + with_them do + before do + # Fixme: Because of an issue: https://github.com/tomykaira/rspec-parameterized/issues/8#issuecomment-381888428 + # The groups need to be created here, not with let syntax, and also compared by name and not ids + + @groups = { + private_group: create(:group, :private, name: 'private_group'), + internal_group: create(:group, :internal, name: 'internal_group'), + public_group: create(:group, :public, name: 'public_group'), + + user_private_group: create(:group, :private, name: 'user_private_group'), + user_internal_group: create(:group, :internal, name: 'user_internal_group'), + user_public_group: create(:group, :public, name: 'user_public_group') + } + + if user_type + user = + case user_type + when :regular + create(:user) + when :external + create(:user, external: true) + when :admin + create(:user, :admin) + end + @groups.values_at(:user_private_group, :user_internal_group, :user_public_group).each do |group| + group.add_developer(user) + end + end end - context 'user is member of the private group' do - before do - private_group.add_guest(user) - end + subject { described_class.new(User.last, params).execute.to_a } - it { is_expected.to contain_exactly(public_group, internal_group, private_group) } - end + it { is_expected.to match_array(@groups.values_at(*results)) } end end context 'subgroups', :nested_groups do + let(:user) { create(:user) } let!(:parent_group) { create(:group, :public) } let!(:public_subgroup) { create(:group, :public, parent: parent_group) } let!(:internal_subgroup) { create(:group, :internal, parent: parent_group) } diff --git a/spec/fixtures/api/schemas/public_api/v4/notes.json b/spec/fixtures/api/schemas/public_api/v4/notes.json index 4c4ca3b582f..0f9c221fd6d 100644 --- a/spec/fixtures/api/schemas/public_api/v4/notes.json +++ b/spec/fixtures/api/schemas/public_api/v4/notes.json @@ -24,7 +24,10 @@ "system": { "type": "boolean" }, "noteable_id": { "type": "integer" }, "noteable_iid": { "type": "integer" }, - "noteable_type": { "type": "string" } + "noteable_type": { "type": "string" }, + "resolved": { "type": "boolean" }, + "resolvable": { "type": "boolean" }, + "resolved_by": { "type": ["string", "null"] } }, "required": [ "id", "body", "attachment", "author", "created_at", "updated_at", diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 43cb0dfe163..5e454f8b310 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -2,8 +2,6 @@ require 'spec_helper' describe ApplicationHelper do - include UploadHelpers - describe 'current_controller?' do it 'returns true when controller matches argument' do stub_controller_name('foo') @@ -54,143 +52,6 @@ describe ApplicationHelper do end end - describe 'project_icon' do - it 'returns an url for the avatar' do - project = create(:project, :public, avatar: File.open(uploaded_image_temp_path)) - - expect(helper.project_icon(project.full_path).to_s) - .to eq "<img data-src=\"#{project.avatar.url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" - end - end - - describe 'avatar_icon_for' do - let!(:user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: 'bar@example.com') } - let(:email) { 'foo@example.com' } - let!(:another_user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: email) } - - it 'prefers the user to retrieve the avatar_url' do - expect(helper.avatar_icon_for(user, email).to_s) - .to eq(user.avatar.url) - end - - it 'falls back to email lookup if no user given' do - expect(helper.avatar_icon_for(nil, email).to_s) - .to eq(another_user.avatar.url) - end - end - - describe 'avatar_icon_for_email' do - let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) } - - context 'using an email' do - context 'when there is a matching user' do - it 'returns a relative URL for the avatar' do - expect(helper.avatar_icon_for_email(user.email).to_s) - .to eq(user.avatar.url) - end - end - - context 'when no user exists for the email' do - it 'calls gravatar_icon' do - expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2) - - helper.avatar_icon_for_email('foo@example.com', 20, 2) - end - end - - context 'without an email passed' do - it 'calls gravatar_icon' do - expect(helper).to receive(:gravatar_icon).with(nil, 20, 2) - - helper.avatar_icon_for_email(nil, 20, 2) - end - end - end - end - - describe 'avatar_icon_for_user' do - let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) } - - context 'with a user object passed' do - it 'returns a relative URL for the avatar' do - expect(helper.avatar_icon_for_user(user).to_s) - .to eq(user.avatar.url) - end - end - - context 'without a user object passed' do - it 'calls gravatar_icon' do - expect(helper).to receive(:gravatar_icon).with(nil, 20, 2) - - helper.avatar_icon_for_user(nil, 20, 2) - end - end - end - - describe 'gravatar_icon' do - let(:user_email) { 'user@email.com' } - - context 'with Gravatar disabled' do - before do - stub_application_setting(gravatar_enabled?: false) - end - - it 'returns a generic avatar' do - expect(helper.gravatar_icon(user_email)).to match_asset_path('no_avatar.png') - end - end - - context 'with Gravatar enabled' do - before do - stub_application_setting(gravatar_enabled?: true) - end - - it 'returns a generic avatar when email is blank' do - expect(helper.gravatar_icon('')).to match_asset_path('no_avatar.png') - end - - it 'returns a valid Gravatar URL' do - stub_config_setting(https: false) - - expect(helper.gravatar_icon(user_email)) - .to match('https://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118') - end - - it 'uses HTTPs when configured' do - stub_config_setting(https: true) - - expect(helper.gravatar_icon(user_email)) - .to match('https://secure.gravatar.com') - end - - it 'returns custom gravatar path when gravatar_url is set' do - stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}') - - expect(gravatar_icon(user_email, 20)) - .to eq('http://example.local/?s=40&hash=b58c6f14d292556214bd64909bcdb118') - end - - it 'accepts a custom size argument' do - expect(helper.gravatar_icon(user_email, 64)).to include '?s=128' - end - - it 'defaults size to 40@2x when given an invalid size' do - expect(helper.gravatar_icon(user_email, nil)).to include '?s=80' - end - - it 'accepts a scaling factor' do - expect(helper.gravatar_icon(user_email, 40, 3)).to include '?s=120' - end - - it 'ignores case and surrounding whitespace' do - normal = helper.gravatar_icon('foo@example.com') - upcase = helper.gravatar_icon(' FOO@EXAMPLE.COM ') - - expect(normal).to eq upcase - end - end - end - describe 'simple_sanitize' do let(:a_tag) { '<a href="#">Foo</a>' } diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index c94fedd615b..120b23e66ac 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -18,6 +18,30 @@ describe AuthHelper do end end + describe "providers_for_base_controller" do + it 'returns all enabled providers from devise' do + allow(helper).to receive(:auth_providers) { [:twitter, :github] } + expect(helper.providers_for_base_controller).to include(*[:twitter, :github]) + end + + it 'excludes ldap providers' do + allow(helper).to receive(:auth_providers) { [:twitter, :ldapmain] } + expect(helper.providers_for_base_controller).not_to include(:ldapmain) + end + end + + describe "form_based_providers" do + it 'includes LDAP providers' do + allow(helper).to receive(:auth_providers) { [:twitter, :ldapmain] } + expect(helper.form_based_providers).to eq %i(ldapmain) + end + + it 'includes crowd provider' do + allow(helper).to receive(:auth_providers) { [:twitter, :crowd] } + expect(helper.form_based_providers).to eq %i(crowd) + end + end + describe 'enabled_button_based_providers' do before do allow(helper).to receive(:auth_providers) { [:twitter, :github] } diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb index 04c6d259135..5856bccb5b8 100644 --- a/spec/helpers/avatars_helper_spec.rb +++ b/spec/helpers/avatars_helper_spec.rb @@ -1,10 +1,147 @@ require 'rails_helper' describe AvatarsHelper do - include ApplicationHelper + include UploadHelpers let(:user) { create(:user) } + describe '#project_icon' do + it 'returns an url for the avatar' do + project = create(:project, :public, avatar: File.open(uploaded_image_temp_path)) + + expect(helper.project_icon(project.full_path).to_s) + .to eq "<img data-src=\"#{project.avatar.url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" + end + end + + describe '#avatar_icon_for' do + let!(:user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: 'bar@example.com') } + let(:email) { 'foo@example.com' } + let!(:another_user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: email) } + + it 'prefers the user to retrieve the avatar_url' do + expect(helper.avatar_icon_for(user, email).to_s) + .to eq(user.avatar.url) + end + + it 'falls back to email lookup if no user given' do + expect(helper.avatar_icon_for(nil, email).to_s) + .to eq(another_user.avatar.url) + end + end + + describe '#avatar_icon_for_email' do + let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) } + + context 'using an email' do + context 'when there is a matching user' do + it 'returns a relative URL for the avatar' do + expect(helper.avatar_icon_for_email(user.email).to_s) + .to eq(user.avatar.url) + end + end + + context 'when no user exists for the email' do + it 'calls gravatar_icon' do + expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2) + + helper.avatar_icon_for_email('foo@example.com', 20, 2) + end + end + + context 'without an email passed' do + it 'calls gravatar_icon' do + expect(helper).to receive(:gravatar_icon).with(nil, 20, 2) + + helper.avatar_icon_for_email(nil, 20, 2) + end + end + end + end + + describe '#avatar_icon_for_user' do + let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) } + + context 'with a user object passed' do + it 'returns a relative URL for the avatar' do + expect(helper.avatar_icon_for_user(user).to_s) + .to eq(user.avatar.url) + end + end + + context 'without a user object passed' do + it 'calls gravatar_icon' do + expect(helper).to receive(:gravatar_icon).with(nil, 20, 2) + + helper.avatar_icon_for_user(nil, 20, 2) + end + end + end + + describe '#gravatar_icon' do + let(:user_email) { 'user@email.com' } + + context 'with Gravatar disabled' do + before do + stub_application_setting(gravatar_enabled?: false) + end + + it 'returns a generic avatar' do + expect(helper.gravatar_icon(user_email)).to match_asset_path('no_avatar.png') + end + end + + context 'with Gravatar enabled' do + before do + stub_application_setting(gravatar_enabled?: true) + end + + it 'returns a generic avatar when email is blank' do + expect(helper.gravatar_icon('')).to match_asset_path('no_avatar.png') + end + + it 'returns a valid Gravatar URL' do + stub_config_setting(https: false) + + expect(helper.gravatar_icon(user_email)) + .to match('https://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118') + end + + it 'uses HTTPs when configured' do + stub_config_setting(https: true) + + expect(helper.gravatar_icon(user_email)) + .to match('https://secure.gravatar.com') + end + + it 'returns custom gravatar path when gravatar_url is set' do + stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}') + + expect(gravatar_icon(user_email, 20)) + .to eq('http://example.local/?s=40&hash=b58c6f14d292556214bd64909bcdb118') + end + + it 'accepts a custom size argument' do + expect(helper.gravatar_icon(user_email, 64)).to include '?s=128' + end + + it 'defaults size to 40@2x when given an invalid size' do + expect(helper.gravatar_icon(user_email, nil)).to include '?s=80' + end + + it 'accepts a scaling factor' do + expect(helper.gravatar_icon(user_email, 40, 3)).to include '?s=120' + end + + it 'ignores case and surrounding whitespace' do + normal = helper.gravatar_icon('foo@example.com') + upcase = helper.gravatar_icon(' FOO@EXAMPLE.COM ') + + expect(normal).to eq upcase + end + end + end + describe '#user_avatar' do subject { helper.user_avatar(user: user) } diff --git a/spec/javascripts/sidebar/sidebar_subscriptions_spec.js b/spec/javascripts/sidebar/sidebar_subscriptions_spec.js index 56a2543660b..9e437084224 100644 --- a/spec/javascripts/sidebar/sidebar_subscriptions_spec.js +++ b/spec/javascripts/sidebar/sidebar_subscriptions_spec.js @@ -3,7 +3,6 @@ import sidebarSubscriptions from '~/sidebar/components/subscriptions/sidebar_sub import SidebarMediator from '~/sidebar/sidebar_mediator'; import SidebarService from '~/sidebar/services/sidebar_service'; import SidebarStore from '~/sidebar/stores/sidebar_store'; -import eventHub from '~/sidebar/event_hub'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; import Mock from './mock_data'; @@ -32,7 +31,7 @@ describe('Sidebar Subscriptions', function () { mediator, }); - eventHub.$emit('toggleSubscription'); + vm.onToggleSubscription(); expect(mediator.toggleSubscription).toHaveBeenCalled(); }); diff --git a/spec/javascripts/sidebar/subscriptions_spec.js b/spec/javascripts/sidebar/subscriptions_spec.js index aee8f0acbb9..11ff126dfb5 100644 --- a/spec/javascripts/sidebar/subscriptions_spec.js +++ b/spec/javascripts/sidebar/subscriptions_spec.js @@ -39,4 +39,12 @@ describe('Subscriptions', function () { expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).toHaveClass('is-checked'); }); + + it('toggleSubscription method emits `toggleSubscription` event on component', () => { + vm = mountComponent(Subscriptions, { subscribed: true }); + spyOn(vm, '$emit'); + + vm.toggleSubscription(); + expect(vm.$emit).toHaveBeenCalledWith('toggleSubscription', jasmine.any(Object)); + }); }); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index bcd15f5eae2..2411d33a496 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -84,21 +84,11 @@ beforeEach(() => { const axiosDefaultAdapter = getDefaultAdapter(); -let testFiles = process.env.TEST_FILES || []; -if (testFiles.length > 0) { - testFiles = testFiles.map(path => path.replace(/^spec\/javascripts\//, '').replace(/\.js$/, '')); - console.log(`Running only tests matching: ${testFiles}`); -} else { - console.log('Running all tests'); -} - // render all of our tests const testsContext = require.context('.', true, /_spec$/); testsContext.keys().forEach(function(path) { try { - if (testFiles.length === 0 || testFiles.some(p => path.includes(p))) { - testsContext(path); - } + testsContext(path); } catch (err) { console.error('[ERROR] Unable to load spec: ', path); describe('Test bundle', function() { diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb index 1fd145116df..068cdc85a07 100644 --- a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb @@ -47,16 +47,36 @@ describe Banzai::Filter::CommitTrailersFilter do ) end - it 'non GitLab users and replaces them with mailto links' do - _, message_html = build_commit_message( - trailer: trailer, - name: FFaker::Name.name, - email: email - ) + context 'non GitLab users' do + shared_examples 'mailto links' do + it 'replaces them with mailto links' do + _, message_html = build_commit_message( + trailer: trailer, + name: FFaker::Name.name, + email: email + ) - doc = filter(message_html) + doc = filter(message_html) - expect_to_have_mailto_link(doc, email: email, trailer: trailer) + expect_to_have_mailto_link_with_avatar(doc, email: email, trailer: trailer) + end + end + + context 'when Gravatar is disabled' do + before do + stub_application_setting(gravatar_enabled: false) + end + + it_behaves_like 'mailto links' + end + + context 'when Gravatar is enabled' do + before do + stub_application_setting(gravatar_enabled: true) + end + + it_behaves_like 'mailto links' + end end it 'multiple trailers in the same message' do @@ -69,7 +89,7 @@ describe Banzai::Filter::CommitTrailersFilter do doc = filter(message) expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer) - expect_to_have_mailto_link(doc, email: email, trailer: different_trailer) + expect_to_have_mailto_link_with_avatar(doc, email: email, trailer: different_trailer) end context 'special names' do @@ -90,7 +110,7 @@ describe Banzai::Filter::CommitTrailersFilter do doc = filter(message_html) - expect_to_have_mailto_link(doc, email: email, trailer: trailer) + expect_to_have_mailto_link_with_avatar(doc, email: email, trailer: trailer) expect(doc.text).to match Regexp.escape(message) end end diff --git a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb new file mode 100644 index 00000000000..f8107dd40b9 --- /dev/null +++ b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::MigrateStageIndex, :migration, schema: 20180420080616 do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:pipelines) { table(:ci_pipelines) } + let(:stages) { table(:ci_stages) } + let(:jobs) { table(:ci_builds) } + + before do + namespaces.create(id: 10, name: 'gitlab-org', path: 'gitlab-org') + projects.create!(id: 11, namespace_id: 10, name: 'gitlab', path: 'gitlab') + pipelines.create!(id: 12, project_id: 11, ref: 'master', sha: 'adf43c3a') + + stages.create(id: 100, project_id: 11, pipeline_id: 12, name: 'build') + stages.create(id: 101, project_id: 11, pipeline_id: 12, name: 'test') + + jobs.create!(id: 121, commit_id: 12, project_id: 11, + stage_idx: 2, stage_id: 100) + jobs.create!(id: 122, commit_id: 12, project_id: 11, + stage_idx: 2, stage_id: 100) + jobs.create!(id: 123, commit_id: 12, project_id: 11, + stage_idx: 10, stage_id: 100) + jobs.create!(id: 124, commit_id: 12, project_id: 11, + stage_idx: 3, stage_id: 101) + end + + it 'correctly migrates stages indices' do + expect(stages.all.pluck(:position)).to all(be_nil) + + described_class.new.perform(100, 101) + + expect(stages.all.pluck(:position)).to eq [2, 3] + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb index dc12ba076bc..0edc3f315bb 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb @@ -17,7 +17,7 @@ describe Gitlab::Ci::Pipeline::Chain::Create do context 'when pipeline is ready to be saved' do before do - pipeline.stages.build(name: 'test', project: project) + pipeline.stages.build(name: 'test', position: 0, project: project) step.perform! end diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb index eb1b285c7bd..05ce3412fd8 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb @@ -24,7 +24,8 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do describe '#attributes' do it 'returns hash attributes of a stage' do expect(subject.attributes).to be_a Hash - expect(subject.attributes).to include(:name, :project) + expect(subject.attributes) + .to include(:name, :position, :pipeline, :project) end end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 31141807cb2..62da967cf96 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -232,6 +232,7 @@ Ci::Stage: - id - name - status +- position - lock_version - project_id - pipeline_id diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb index 3cfdae794f6..7be8be54d5e 100644 --- a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb @@ -4,22 +4,10 @@ describe Gitlab::Kubernetes::Helm::BaseCommand do let(:application) { create(:clusters_applications_helm) } let(:base_command) { described_class.new(application.name) } - describe '#generate_script' do - let(:helm_version) { Gitlab::Kubernetes::Helm::HELM_VERSION } - let(:command) do - <<~HEREDOC - set -eo pipefail - apk add -U ca-certificates openssl >/dev/null - wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v#{helm_version}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null - mv /tmp/linux-amd64/helm /usr/bin/ - HEREDOC - end - - subject { base_command.generate_script } + subject { base_command } - it 'should return a command that prepares the environment for helm-cli' do - expect(subject).to eq(command) - end + it_behaves_like 'helm commands' do + let(:commands) { '' } end describe '#pod_resource' do diff --git a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb index e6920b0a76f..89e36a298f8 100644 --- a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb @@ -2,23 +2,9 @@ require 'spec_helper' describe Gitlab::Kubernetes::Helm::InitCommand do let(:application) { create(:clusters_applications_helm) } - let(:init_command) { described_class.new(application.name) } + let(:commands) { 'helm init >/dev/null' } - describe '#generate_script' do - let(:command) do - <<~MSG.chomp - set -eo pipefail - apk add -U ca-certificates openssl >/dev/null - wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null - mv /tmp/linux-amd64/helm /usr/bin/ - helm init >/dev/null - MSG - end + subject { described_class.new(application.name) } - subject { init_command.generate_script } - - it 'should return the appropriate command' do - is_expected.to eq(command) - end - end + it_behaves_like 'helm commands' end diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb index 137b8f718de..547f3f1752c 100644 --- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb @@ -12,50 +12,36 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do ) end - describe '#generate_script' do - let(:command) do - <<~MSG - set -eo pipefail - apk add -U ca-certificates openssl >/dev/null - wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null - mv /tmp/linux-amd64/helm /usr/bin/ - helm init --client-only >/dev/null - helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null - MSG - end - - subject { install_command.generate_script } + subject { install_command } - it 'should return appropriate command' do - is_expected.to eq(command) + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + helm init --client-only >/dev/null + helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null + EOS end + end - context 'with an application with a repository' do - let(:ci_runner) { create(:ci_runner) } - let(:application) { create(:clusters_applications_runner, runner: ci_runner) } - let(:install_command) do - described_class.new( - application.name, - chart: application.chart, - values: application.values, - repository: application.repository - ) - end - - let(:command) do - <<~MSG - set -eo pipefail - apk add -U ca-certificates openssl >/dev/null - wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null - mv /tmp/linux-amd64/helm /usr/bin/ - helm init --client-only >/dev/null - helm repo add #{application.name} #{application.repository} - helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null - MSG - end + context 'with an application with a repository' do + let(:ci_runner) { create(:ci_runner) } + let(:application) { create(:clusters_applications_runner, runner: ci_runner) } + let(:install_command) do + described_class.new( + application.name, + chart: application.chart, + values: application.values, + repository: application.repository + ) + end - it 'should return appropriate command' do - is_expected.to eq(command) + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + helm init --client-only >/dev/null + helm repo add #{application.name} #{application.repository} + helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null + EOS end end end diff --git a/spec/migrations/schedule_stages_index_migration_spec.rb b/spec/migrations/schedule_stages_index_migration_spec.rb new file mode 100644 index 00000000000..710264da375 --- /dev/null +++ b/spec/migrations/schedule_stages_index_migration_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180420080616_schedule_stages_index_migration') + +describe ScheduleStagesIndexMigration, :sidekiq, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:pipelines) { table(:ci_pipelines) } + let(:stages) { table(:ci_stages) } + + before do + stub_const("#{described_class}::BATCH_SIZE", 1) + + namespaces.create(id: 12, name: 'gitlab-org', path: 'gitlab-org') + projects.create!(id: 123, namespace_id: 12, name: 'gitlab', path: 'gitlab') + pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a') + stages.create!(id: 121, project_id: 123, pipeline_id: 1, name: 'build') + stages.create!(id: 122, project_id: 123, pipeline_id: 1, name: 'test') + stages.create!(id: 123, project_id: 123, pipeline_id: 1, name: 'deploy') + end + + it 'schedules delayed background migrations in batches' do + Sidekiq::Testing.fake! do + Timecop.freeze do + expect(stages.all).to all(have_attributes(position: be_nil)) + + migrate! + + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, 121, 121) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, 122, 122) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(15.minutes, 123, 123) + expect(BackgroundMigrationWorker.jobs.size).to eq 3 + end + end + end +end diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb new file mode 100644 index 00000000000..129b2f92683 --- /dev/null +++ b/spec/models/active_session_spec.rb @@ -0,0 +1,216 @@ +require 'rails_helper' + +RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do + let(:user) do + create(:user).tap do |user| + user.current_sign_in_at = Time.current + end + end + + let(:session) { double(:session, id: '6919a6f1bb119dd7396fadc38fd18d0d') } + + let(:request) do + double(:request, { + user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 ' \ + '(KHTML, like Gecko) Mobile/12B466 [FBDV/iPhone7,2]', + ip: '127.0.0.1', + session: session + }) + end + + describe '#current?' do + it 'returns true if the active session matches the current session' do + active_session = ActiveSession.new(session_id: '6919a6f1bb119dd7396fadc38fd18d0d') + + expect(active_session.current?(session)).to be true + end + + it 'returns false if the active session does not match the current session' do + active_session = ActiveSession.new(session_id: '59822c7d9fcdfa03725eff41782ad97d') + + expect(active_session.current?(session)).to be false + end + + it 'returns false if the session id is nil' do + active_session = ActiveSession.new(session_id: nil) + session = double(:session, id: nil) + + expect(active_session.current?(session)).to be false + end + end + + describe '.list' do + it 'returns all sessions by user' do + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:user:gitlab:#{user.id}:6919a6f1bb119dd7396fadc38fd18d0d", Marshal.dump({ session_id: 'a' })) + redis.set("session:user:gitlab:#{user.id}:59822c7d9fcdfa03725eff41782ad97d", Marshal.dump({ session_id: 'b' })) + redis.set("session:user:gitlab:9999:5c8611e4f9c69645ad1a1492f4131358", '') + + redis.sadd( + "session:lookup:user:gitlab:#{user.id}", + %w[ + 6919a6f1bb119dd7396fadc38fd18d0d + 59822c7d9fcdfa03725eff41782ad97d + ] + ) + end + + expect(ActiveSession.list(user)).to match_array [{ session_id: 'a' }, { session_id: 'b' }] + end + + it 'does not return obsolete entries and cleans them up' do + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:user:gitlab:#{user.id}:6919a6f1bb119dd7396fadc38fd18d0d", Marshal.dump({ session_id: 'a' })) + + redis.sadd( + "session:lookup:user:gitlab:#{user.id}", + %w[ + 6919a6f1bb119dd7396fadc38fd18d0d + 59822c7d9fcdfa03725eff41782ad97d + ] + ) + end + + expect(ActiveSession.list(user)).to eq [{ session_id: 'a' }] + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.sscan_each("session:lookup:user:gitlab:#{user.id}").to_a).to eq ['6919a6f1bb119dd7396fadc38fd18d0d'] + end + end + + it 'returns an empty array if the use does not have any active session' do + expect(ActiveSession.list(user)).to eq [] + end + end + + describe '.set' do + it 'sets a new redis entry for the user session and a lookup entry' do + ActiveSession.set(user, request) + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.scan_each.to_a).to match_array [ + "session:user:gitlab:#{user.id}:6919a6f1bb119dd7396fadc38fd18d0d", + "session:lookup:user:gitlab:#{user.id}" + ] + end + end + + it 'adds timestamps and information from the request' do + Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do + ActiveSession.set(user, request) + + session = ActiveSession.list(user) + + expect(session.count).to eq 1 + expect(session.first).to have_attributes( + ip_address: '127.0.0.1', + browser: 'Mobile Safari', + os: 'iOS', + device_name: 'iPhone 6', + device_type: 'smartphone', + created_at: Time.zone.parse('2018-03-12 09:06'), + updated_at: Time.zone.parse('2018-03-12 09:06'), + session_id: '6919a6f1bb119dd7396fadc38fd18d0d' + ) + end + end + + it 'keeps the created_at from the login on consecutive requests' do + now = Time.zone.parse('2018-03-12 09:06') + + Timecop.freeze(now) do + ActiveSession.set(user, request) + + Timecop.freeze(now + 1.minute) do + ActiveSession.set(user, request) + + session = ActiveSession.list(user) + + expect(session.first).to have_attributes( + created_at: Time.zone.parse('2018-03-12 09:06'), + updated_at: Time.zone.parse('2018-03-12 09:07') + ) + end + end + end + end + + describe '.destroy' do + it 'removes the entry associated with the currently killed user session' do + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:user:gitlab:#{user.id}:6919a6f1bb119dd7396fadc38fd18d0d", '') + redis.set("session:user:gitlab:#{user.id}:59822c7d9fcdfa03725eff41782ad97d", '') + redis.set("session:user:gitlab:9999:5c8611e4f9c69645ad1a1492f4131358", '') + end + + ActiveSession.destroy(user, request.session.id) + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.scan_each(match: "session:user:gitlab:*")).to match_array [ + "session:user:gitlab:#{user.id}:59822c7d9fcdfa03725eff41782ad97d", + "session:user:gitlab:9999:5c8611e4f9c69645ad1a1492f4131358" + ] + end + end + + it 'removes the lookup entry' do + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:user:gitlab:#{user.id}:6919a6f1bb119dd7396fadc38fd18d0d", '') + redis.sadd("session:lookup:user:gitlab:#{user.id}", '6919a6f1bb119dd7396fadc38fd18d0d') + end + + ActiveSession.destroy(user, request.session.id) + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.scan_each(match: "session:lookup:user:gitlab:#{user.id}").to_a).to be_empty + end + end + + it 'removes the devise session' do + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:user:gitlab:#{user.id}:6919a6f1bb119dd7396fadc38fd18d0d", '') + redis.set("session:gitlab:6919a6f1bb119dd7396fadc38fd18d0d", '') + end + + ActiveSession.destroy(user, request.session.id) + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.scan_each(match: "session:gitlab:*").to_a).to be_empty + end + end + + it 'does not remove the devise session if the active session could not be found' do + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:gitlab:6919a6f1bb119dd7396fadc38fd18d0d", '') + end + + other_user = create(:user) + + ActiveSession.destroy(other_user, request.session.id) + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.scan_each(match: "session:gitlab:*").to_a).not_to be_empty + end + end + end + + describe '.cleanup' do + it 'removes obsolete lookup entries' do + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:user:gitlab:#{user.id}:6919a6f1bb119dd7396fadc38fd18d0d", '') + redis.sadd("session:lookup:user:gitlab:#{user.id}", '6919a6f1bb119dd7396fadc38fd18d0d') + redis.sadd("session:lookup:user:gitlab:#{user.id}", '59822c7d9fcdfa03725eff41782ad97d') + end + + ActiveSession.cleanup(user) + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.smembers("session:lookup:user:gitlab:#{user.id}")).to eq ['6919a6f1bb119dd7396fadc38fd18d0d'] + end + end + + it 'does not bail if there are no lookup entries' do + ActiveSession.cleanup(user) + end + end +end diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 586d073eb5e..a00db1d2bfc 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -51,7 +51,7 @@ describe Ci::Stage, :models do end end - describe 'update_status' do + describe '#update_status' do context 'when stage objects needs to be updated' do before do create(:ci_build, :success, stage_id: stage.id) @@ -87,4 +87,36 @@ describe Ci::Stage, :models do end end end + + describe '#index' do + context 'when stage has been imported and does not have position index set' do + before do + stage.update_column(:position, nil) + end + + context 'when stage has statuses' do + before do + create(:ci_build, :running, stage_id: stage.id, stage_idx: 10) + end + + it 'recalculates index before updating status' do + expect(stage.reload.position).to be_nil + + stage.update_status + + expect(stage.reload.position).to eq 10 + end + end + + context 'when stage does not have statuses' do + it 'fallbacks to zero' do + expect(stage.reload.position).to be_nil + + stage.update_status + + expect(stage.reload.position).to eq 0 + end + end + end + end end diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 2705421e540..fb51c0172ab 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -85,12 +85,35 @@ describe DiffNote do end describe "#diff_file" do - it "returns the correct diff file" do - diff_file = subject.diff_file + context 'when the discussion was created in the diff' do + it 'returns correct diff file' do + diff_file = subject.diff_file - expect(diff_file.old_path).to eq(position.old_path) - expect(diff_file.new_path).to eq(position.new_path) - expect(diff_file.diff_refs).to eq(position.diff_refs) + expect(diff_file.old_path).to eq(position.old_path) + expect(diff_file.new_path).to eq(position.new_path) + expect(diff_file.diff_refs).to eq(position.diff_refs) + end + end + + context 'when discussion is outdated or not created in the diff' do + let(:diff_refs) { project.commit(sample_commit.id).diff_refs } + let(:position) do + Gitlab::Diff::Position.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: diff_refs + ) + end + + it 'returns the correct diff file' do + diff_file = subject.diff_file + + expect(diff_file.old_path).to eq(position.old_path) + expect(diff_file.new_path).to eq(position.new_path) + expect(diff_file.diff_refs).to eq(position.diff_refs) + end end end diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb index ba06ff42d87..6e35511e848 100644 --- a/spec/models/lfs_object_spec.rb +++ b/spec/models/lfs_object_spec.rb @@ -62,9 +62,7 @@ describe LfsObject do .with('LfsObjectUploader', described_class.name, :file, kind_of(Numeric)) .once - lfs_object = create(:lfs_object) - lfs_object.file = fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") - lfs_object.save! + create(:lfs_object, :with_file) end end end diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index 2a0d102d3fe..12681a147b4 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -40,7 +40,12 @@ RSpec.describe NotificationSetting do expect(notification_setting.new_issue).to eq(true) expect(notification_setting.close_issue).to eq(true) expect(notification_setting.merge_merge_request).to eq(true) - expect(notification_setting.close_merge_request).to eq(false) + + # In Rails 5 assigning a value which is not explicitly `true` or `false` ("nil" in this case) + # to a boolean column transforms it to `true`. + # In Rails 4 it transforms the value to `false` with deprecation warning. + # Replace `eq(Gitlab.rails5?)` with `eq(true)` when removing rails5? code. + expect(notification_setting.close_merge_request).to eq(Gitlab.rails5?) expect(notification_setting.reopen_merge_request).to eq(false) end end diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb index 4a44b219a67..ef34192f888 100644 --- a/spec/requests/api/discussions_spec.rb +++ b/spec/requests/api/discussions_spec.rb @@ -2,32 +2,53 @@ require 'spec_helper' describe API::Discussions do let(:user) { create(:user) } - let!(:project) { create(:project, :public, namespace: user.namespace) } + let!(:project) { create(:project, :public, :repository, namespace: user.namespace) } let(:private_user) { create(:user) } before do - project.add_reporter(user) + project.add_developer(user) end - context "when noteable is an Issue" do + context 'when noteable is an Issue' do let!(:issue) { create(:issue, project: project, author: user) } let!(:issue_note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: user) } - it_behaves_like "discussions API", 'projects', 'issues', 'iid' do + it_behaves_like 'discussions API', 'projects', 'issues', 'iid' do let(:parent) { project } let(:noteable) { issue } let(:note) { issue_note } end end - context "when noteable is a Snippet" do + context 'when noteable is a Snippet' do let!(:snippet) { create(:project_snippet, project: project, author: user) } let!(:snippet_note) { create(:discussion_note_on_snippet, noteable: snippet, project: project, author: user) } - it_behaves_like "discussions API", 'projects', 'snippets', 'id' do + it_behaves_like 'discussions API', 'projects', 'snippets', 'id' do let(:parent) { project } let(:noteable) { snippet } let(:note) { snippet_note } end end + + context 'when noteable is a Merge Request' do + let!(:noteable) { create(:merge_request_with_diffs, source_project: project, target_project: project, author: user) } + let!(:note) { create(:discussion_note_on_merge_request, noteable: noteable, project: project, author: user) } + let!(:diff_note) { create(:diff_note_on_merge_request, noteable: noteable, project: project, author: user) } + let(:parent) { project } + + it_behaves_like 'discussions API', 'projects', 'merge_requests', 'iid' + it_behaves_like 'diff discussions API', 'projects', 'merge_requests', 'iid' + it_behaves_like 'resolvable discussions API', 'projects', 'merge_requests', 'iid' + end + + context 'when noteable is a Commit' do + let!(:noteable) { create(:commit, project: project, author: user) } + let!(:note) { create(:discussion_note_on_commit, commit_id: noteable.id, project: project, author: user) } + let!(:diff_note) { create(:diff_note_on_commit, commit_id: noteable.id, project: project, author: user) } + let(:parent) { project } + + it_behaves_like 'discussions API', 'projects', 'repository/commits', 'id' + it_behaves_like 'diff discussions API', 'projects', 'repository/commits', 'id' + end end diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 3ffdfdc0e9a..0a2963452e4 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -281,7 +281,7 @@ describe API::Jobs do get_artifact_file(artifact) expect(response).to have_gitlab_http_status(200) - expect(response.headers) + expect(response.headers.to_h) .to include('Content-Type' => 'application/json', 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/) end @@ -311,7 +311,7 @@ describe API::Jobs do it 'returns specific job artifacts' do expect(response).to have_gitlab_http_status(200) - expect(response.headers).to include(download_headers) + expect(response.headers.to_h).to include(download_headers) expect(response.body).to match_file(job.artifacts_file.file.file) end end @@ -462,7 +462,7 @@ describe API::Jobs do end it { expect(response).to have_http_status(:ok) } - it { expect(response.headers).to include(download_headers) } + it { expect(response.headers.to_h).to include(download_headers) } end context 'when artifacts are stored remotely' do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 17c7a511857..6c1c13c9d80 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -1330,7 +1330,7 @@ describe API::Runner do it 'download artifacts' do expect(response).to have_http_status(200) - expect(response.headers).to include download_headers + expect(response.headers.to_h).to include download_headers end end @@ -1345,7 +1345,7 @@ describe API::Runner do it 'uses workhorse send-url' do expect(response).to have_gitlab_http_status(200) - expect(response.headers).to include( + expect(response.headers.to_h).to include( 'Gitlab-Workhorse-Send-Data' => /send-url:/) end end diff --git a/spec/requests/api/v3/builds_spec.rb b/spec/requests/api/v3/builds_spec.rb index 00f067889a0..485d7c2cc43 100644 --- a/spec/requests/api/v3/builds_spec.rb +++ b/spec/requests/api/v3/builds_spec.rb @@ -232,7 +232,7 @@ describe API::V3::Builds do it 'returns specific job artifacts' do expect(response).to have_http_status(200) - expect(response.headers).to include(download_headers) + expect(response.headers.to_h).to include(download_headers) expect(response.body).to match_file(build.artifacts_file.file.file) end end @@ -332,7 +332,7 @@ describe API::V3::Builds do end it { expect(response).to have_http_status(200) } - it { expect(response.headers).to include(download_headers) } + it { expect(response.headers.to_h).to include(download_headers) } end context 'when artifacts are stored remotely' do diff --git a/spec/services/applications/create_service_spec.rb b/spec/services/applications/create_service_spec.rb index 47a2a9d6403..9c43b56744b 100644 --- a/spec/services/applications/create_service_spec.rb +++ b/spec/services/applications/create_service_spec.rb @@ -1,13 +1,17 @@ -require 'spec_helper' +require "spec_helper" describe ::Applications::CreateService do let(:user) { create(:user) } let(:params) { attributes_for(:application) } - let(:request) { ActionController::TestRequest.new(remote_ip: '127.0.0.1') } + let(:request) do + if Gitlab.rails5? + ActionController::TestRequest.new({ remote_ip: "127.0.0.1" }, ActionController::TestSession.new) + else + ActionController::TestRequest.new(remote_ip: "127.0.0.1") + end + end subject { described_class.new(user, params) } - it 'creates an application' do - expect { subject.execute(request) }.to change { Doorkeeper::Application.count }.by(1) - end + it { expect { subject.execute(request) }.to change { Doorkeeper::Application.count }.by(1) } end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 8de0bdf92e2..5bc6031388e 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -6,7 +6,9 @@ describe Ci::RetryBuildService do set(:pipeline) { create(:ci_pipeline, project: project) } let(:stage) do - Ci::Stage.create!(project: project, pipeline: pipeline, name: 'test') + create(:ci_stage_entity, project: project, + pipeline: pipeline, + name: 'test') end let(:build) { create(:ci_build, pipeline: pipeline, stage_id: stage.id) } diff --git a/spec/services/notes/resolve_service_spec.rb b/spec/services/notes/resolve_service_spec.rb new file mode 100644 index 00000000000..b54d40a7a5c --- /dev/null +++ b/spec/services/notes/resolve_service_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Notes::ResolveService do + let(:merge_request) { create(:merge_request) } + let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.project) } + let(:user) { merge_request.author } + + describe '#execute' do + it "resolves the note" do + described_class.new(merge_request.project, user).execute(note) + note.reload + + expect(note.resolved?).to be true + expect(note.resolved_by).to eq(user) + end + + it "sends notifications if all discussions are resolved" do + expect_any_instance_of(MergeRequests::ResolvedDiscussionNotificationService).to receive(:execute).with(merge_request) + + described_class.new(merge_request.project, user).execute(note) + end + end +end diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index a418808fd26..347ac13828c 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -123,11 +123,13 @@ describe Projects::UpdatePagesService do expect(execute).not_to eq(:success) end - it 'fails for empty file fails' do - build.job_artifacts_archive.update_attributes(file: empty_file) + context 'when using empty file' do + let(:file) { empty_file } - expect { execute } - .to raise_error(Projects::UpdatePagesService::FailedToExtractError) + it 'fails to extract' do + expect { execute } + .to raise_error(Projects::UpdatePagesService::FailedToExtractError) + end end context 'when timeout happens by DNS error' do diff --git a/spec/support/commit_trailers_spec_helper.rb b/spec/support/commit_trailers_spec_helper.rb index add359946db..efa317fd2f9 100644 --- a/spec/support/commit_trailers_spec_helper.rb +++ b/spec/support/commit_trailers_spec_helper.rb @@ -8,7 +8,7 @@ module CommitTrailersSpecHelper expect(wrapper.attribute('data-user').value).to eq user.id.to_s end - def expect_to_have_mailto_link(doc, email:, trailer:) + def expect_to_have_mailto_link_with_avatar(doc, email:, trailer:) wrapper = find_user_wrapper(doc, trailer) expect_to_have_links_with_url_and_avatar(wrapper, "mailto:#{CGI.escape_html(email)}", email) diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb new file mode 100644 index 00000000000..56e86a87ab9 --- /dev/null +++ b/spec/support/shared_examples/helm_generated_script.rb @@ -0,0 +1,19 @@ +shared_examples 'helm commands' do + describe '#generate_script' do + let(:helm_setup) do + <<~EOS + set -eo pipefail + ALPINE_VERSION=$(cat /etc/alpine-release | cut -d '.' -f 1,2) + echo http://mirror.clarkson.edu/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories + echo http://mirror1.hs-esslingen.de/pub/Mirrors/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories + apk add -U ca-certificates openssl >/dev/null + wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null + mv /tmp/linux-amd64/helm /usr/bin/ + EOS + end + + it 'should return appropriate command' do + expect(subject.generate_script).to eq(helm_setup + commands) + end + end +end diff --git a/spec/support/shared_examples/requests/api/diff_discussions.rb b/spec/support/shared_examples/requests/api/diff_discussions.rb new file mode 100644 index 00000000000..85a4bd8ca27 --- /dev/null +++ b/spec/support/shared_examples/requests/api/diff_discussions.rb @@ -0,0 +1,57 @@ +shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name| + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do + it "includes diff discussions" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user) + + discussion = json_response.find { |record| record['id'] == diff_note.discussion_id } + + expect(response).to have_gitlab_http_status(200) + expect(discussion).not_to be_nil + expect(discussion['individual_note']).to eq(false) + expect(discussion['notes'].first['body']).to eq(diff_note.note) + end + end + + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do + it "returns a discussion by id" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/#{diff_note.discussion_id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(diff_note.discussion_id) + expect(json_response['notes'].first['body']).to eq(diff_note.note) + expect(json_response['notes'].first['position']).to eq(diff_note.position.to_h.stringify_keys) + end + end + + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do + it "creates a new diff note" do + position = diff_note.position.to_h + + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!', position: position + + expect(response).to have_gitlab_http_status(201) + expect(json_response['notes'].first['body']).to eq('hi!') + expect(json_response['notes'].first['type']).to eq('DiffNote') + expect(json_response['notes'].first['position']).to eq(position.stringify_keys) + end + + it "returns a 400 bad request error when position is invalid" do + position = diff_note.position.to_h.merge(new_line: '100000') + + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!', position: position + + expect(response).to have_gitlab_http_status(400) + end + end + + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do + it 'adds a new note to the diff discussion' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{diff_note.discussion_id}/notes", user), body: 'hi!' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq('hi!') + expect(json_response['type']).to eq('DiffNote') + end + end +end diff --git a/spec/support/shared_examples/requests/api/resolvable_discussions.rb b/spec/support/shared_examples/requests/api/resolvable_discussions.rb new file mode 100644 index 00000000000..408ad08cc48 --- /dev/null +++ b/spec/support/shared_examples/requests/api/resolvable_discussions.rb @@ -0,0 +1,87 @@ +shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_name| + describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do + it "resolves discussion if resolved is true" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}", user), resolved: true + + expect(response).to have_gitlab_http_status(200) + expect(json_response['notes'].size).to eq(1) + expect(json_response['notes'][0]['resolved']).to eq(true) + end + + it "unresolves discussion if resolved is false" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}", user), resolved: false + + expect(response).to have_gitlab_http_status(200) + expect(json_response['notes'].size).to eq(1) + expect(json_response['notes'][0]['resolved']).to eq(false) + end + + it "returns a 400 bad request error if resolved parameter is not passed" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "returns a 401 unauthorized error if user is not authenticated" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}"), resolved: true + + expect(response).to have_gitlab_http_status(401) + end + + it "returns a 403 error if user resolves discussion of someone else" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}", private_user), resolved: true + + expect(response).to have_gitlab_http_status(403) + end + + context 'when user does not have access to read the discussion' do + before do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it 'responds with 404' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}", private_user), resolved: true + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + it 'returns resolved note when resolved parameter is true' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user), resolved: true + + expect(response).to have_gitlab_http_status(200) + expect(json_response['resolved']).to eq(true) + end + + it 'returns a 404 error when note id not found' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/12345", user), + body: 'Hello!' + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns a 400 bad request error if neither body nor resolved parameter is given' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "returns a 403 error if user resolves note of someone else" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", private_user), resolved: true + + expect(response).to have_gitlab_http_status(403) + end + end +end diff --git a/spec/uploaders/lfs_object_uploader_spec.rb b/spec/uploaders/lfs_object_uploader_spec.rb index a2fb3886610..9f28510c3e4 100644 --- a/spec/uploaders/lfs_object_uploader_spec.rb +++ b/spec/uploaders/lfs_object_uploader_spec.rb @@ -46,8 +46,7 @@ describe LfsObjectUploader do end describe 'remote file' do - let(:remote) { described_class::Store::REMOTE } - let(:lfs_object) { create(:lfs_object, file_store: remote) } + let(:lfs_object) { create(:lfs_object, :object_storage, :with_file) } context 'with object storage enabled' do before do @@ -57,16 +56,11 @@ describe LfsObjectUploader do it 'can store file remotely' do allow(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async) - store_file(lfs_object) + lfs_object - expect(lfs_object.file_store).to eq remote + expect(lfs_object.file_store).to eq(described_class::Store::REMOTE) expect(lfs_object.file.path).not_to be_blank end end end - - def store_file(lfs_object) - lfs_object.file = fixture_file_upload(Rails.root.join("spec/fixtures/dk.png"), "`/png") - lfs_object.save! - end end |