diff options
Diffstat (limited to 'spec')
70 files changed, 1497 insertions, 737 deletions
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index d40aae04fc3..3f99e2ff596 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -99,6 +99,42 @@ describe ApplicationController do end end + describe '#authenticate_user_from_rss_token' do + describe "authenticating a user from an RSS token" do + controller(described_class) do + def index + render text: 'authenticated' + end + end + + context "when the 'rss_token' param is populated with the RSS token" do + context 'when the request format is atom' do + it "logs the user in" do + get :index, rss_token: user.rss_token, format: :atom + expect(response).to have_http_status 200 + expect(response.body).to eq 'authenticated' + end + end + + context 'when the request format is not atom' do + it "doesn't log the user in" do + get :index, rss_token: user.rss_token + expect(response.status).not_to have_http_status 200 + expect(response.body).not_to eq 'authenticated' + end + end + end + + context "when the 'rss_token' param is populated with an invalid RSS token" do + it "doesn't log the user" do + get :index, rss_token: "token" + expect(response.status).not_to eq 200 + expect(response.body).not_to eq 'authenticated' + end + end + end + end + describe '#route_not_found' do it 'renders 404 if authenticated' do allow(controller).to receive(:current_user).and_return(user) diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index 010e3180ea4..0be7bc6a045 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -133,9 +133,13 @@ describe Import::BitbucketController do end context "when a namespace with the Bitbucket user's username already exists" do - let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) } + let!(:existing_namespace) { create(:group, name: other_username) } context "when the namespace is owned by the GitLab user" do + before do + existing_namespace.add_owner(user) + end + it "takes the existing namespace" do expect(Gitlab::BitbucketImport::ProjectCreator). to receive(:new).with(bitbucket_repo, bitbucket_repo.name, existing_namespace, user, access_params). @@ -146,11 +150,6 @@ describe Import::BitbucketController do end context "when the namespace is not owned by the GitLab user" do - before do - existing_namespace.owner = create(:user) - existing_namespace.save - end - it "doesn't create a project" do expect(Gitlab::BitbucketImport::ProjectCreator). not_to receive(:new) @@ -202,10 +201,14 @@ describe Import::BitbucketController do end context 'user has chosen an existing nested namespace and name for the project' do - let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) } - let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) } + let(:parent_namespace) { create(:group, name: 'foo', owner: user) } + let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:test_name) { 'test_name' } + before do + nested_namespace.add_owner(user) + end + it 'takes the selected namespace and name' do expect(Gitlab::BitbucketImport::ProjectCreator). to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params). @@ -248,7 +251,7 @@ describe Import::BitbucketController do context 'user has chosen existent and non-existent nested namespaces and name for the project' do let(:test_name) { 'test_name' } - let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) } + let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } it 'takes the selected namespace and name' do expect(Gitlab::BitbucketImport::ProjectCreator). diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index 3270ea059fa..3afd09063d7 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -108,9 +108,13 @@ describe Import::GitlabController do end context "when a namespace with the GitLab.com user's username already exists" do - let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) } + let!(:existing_namespace) { create(:group, name: other_username) } context "when the namespace is owned by the GitLab server user" do + before do + existing_namespace.add_owner(user) + end + it "takes the existing namespace" do expect(Gitlab::GitlabImport::ProjectCreator). to receive(:new).with(gitlab_repo, existing_namespace, user, access_params). @@ -121,11 +125,6 @@ describe Import::GitlabController do end context "when the namespace is not owned by the GitLab server user" do - before do - existing_namespace.owner = create(:user) - existing_namespace.save - end - it "doesn't create a project" do expect(Gitlab::GitlabImport::ProjectCreator). not_to receive(:new) @@ -176,8 +175,12 @@ describe Import::GitlabController do end context 'user has chosen an existing nested namespace for the project' do - let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) } - let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) } + let(:parent_namespace) { create(:group, name: 'foo', owner: user) } + let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } + + before do + nested_namespace.add_owner(user) + end it 'takes the selected namespace and name' do expect(Gitlab::GitlabImport::ProjectCreator). @@ -221,7 +224,7 @@ describe Import::GitlabController do context 'user has chosen existent and non-existent nested namespaces and name for the project' do let(:test_name) { 'test_name' } - let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) } + let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } it 'takes the selected namespace and name' do expect(Gitlab::GitlabImport::ProjectCreator). diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 28ddd0da753..3fad4d2d658 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -20,7 +20,6 @@ FactoryGirl.define do project factory: :empty_project active true properties({ - namespace: 'somepath', api_url: 'https://kubernetes.example.com', token: 'a' * 40 }) diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 33fa80772ff..e60fe713bc3 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -8,6 +8,10 @@ FactoryGirl.define do confirmation_token { nil } can_create_group true + before(:create) do |user| + user.ensure_rss_token + end + trait :admin do admin true end diff --git a/spec/factories/web_hook_log.rb b/spec/factories/web_hook_log.rb new file mode 100644 index 00000000000..230b3f6b26e --- /dev/null +++ b/spec/factories/web_hook_log.rb @@ -0,0 +1,14 @@ +FactoryGirl.define do + factory :web_hook_log do + web_hook factory: :project_hook + trigger 'push_hooks' + url { generate(:url) } + request_headers {} + request_data {} + response_headers {} + response_body '' + response_status '200' + execution_duration 2.0 + internal_error_message nil + end +end diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb new file mode 100644 index 00000000000..5b67f4de6ac --- /dev/null +++ b/spec/features/admin/admin_hook_logs_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +feature 'Admin::HookLogs', feature: true do + let(:project) { create(:project) } + let(:system_hook) { create(:system_hook) } + let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') } + + before do + login_as :admin + end + + scenario 'show list of hook logs' do + hook_log + visit edit_admin_hook_path(system_hook) + + expect(page).to have_content('Recent Deliveries') + expect(page).to have_content(hook_log.url) + end + + scenario 'show hook log details' do + hook_log + visit edit_admin_hook_path(system_hook) + click_link 'View details' + + expect(page).to have_content("POST #{hook_log.url}") + expect(page).to have_content(hook_log.internal_error_message) + expect(page).to have_content('Resend Request') + end + + scenario 'retry hook log' do + WebMock.stub_request(:post, system_hook.url) + + hook_log + visit edit_admin_hook_path(system_hook) + click_link 'View details' + click_link 'Resend Request' + + expect(current_path).to eq(edit_admin_hook_path(system_hook)) + end +end diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index c5f24d412d7..80f7ec43c06 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -58,10 +58,19 @@ describe 'Admin::Hooks', feature: true do end describe 'Remove existing hook' do - it 'remove existing hook' do - visit admin_hooks_path + context 'removes existing hook' do + it 'from hooks list page' do + visit admin_hooks_path + + expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1) + end - expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1) + it 'from hook edit page' do + visit admin_hooks_path + click_link 'Edit' + + expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1) + end end end diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index 9ea325ab41b..711c8a710f3 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -20,13 +20,20 @@ describe "Dashboard Issues Feed", feature: true do expect(body).to have_selector('title', text: "#{user.name} issues") end + it "renders atom feed via RSS token" do + visit issues_dashboard_path(:atom, rss_token: user.rss_token) + + expect(response_headers['Content-Type']).to have_content('application/atom+xml') + expect(body).to have_selector('title', text: "#{user.name} issues") + end + it "renders atom feed with url parameters" do - visit issues_dashboard_path(:atom, private_token: user.private_token, state: 'opened', assignee_id: user.id) + visit issues_dashboard_path(:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id) link = find('link[type="application/atom+xml"]') params = CGI.parse(URI.parse(link[:href]).query) - expect(params).to include('private_token' => [user.private_token]) + expect(params).to include('rss_token' => [user.rss_token]) expect(params).to include('state' => ['opened']) expect(params).to include('assignee_id' => [user.id.to_s]) end @@ -35,7 +42,7 @@ describe "Dashboard Issues Feed", feature: true do let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') } it "renders issue fields" do - visit issues_dashboard_path(:atom, private_token: user.private_token) + visit issues_dashboard_path(:atom, rss_token: user.rss_token) entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]") @@ -58,7 +65,7 @@ describe "Dashboard Issues Feed", feature: true do end it "renders issue label and milestone info" do - visit issues_dashboard_path(:atom, private_token: user.private_token) + visit issues_dashboard_path(:atom, rss_token: user.rss_token) entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]") diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb index 746df36bb25..1df058b023c 100644 --- a/spec/features/atom/dashboard_spec.rb +++ b/spec/features/atom/dashboard_spec.rb @@ -11,6 +11,13 @@ describe "Dashboard Feed", feature: true do end end + context "projects atom feed via RSS token" do + it "renders projects atom feed" do + visit dashboard_projects_path(:atom, rss_token: user.rss_token) + expect(body).to have_selector('feed title') + end + end + context 'feed content' do let(:project) { create(:project) } let(:issue) { create(:issue, project: project, author: user, description: '') } @@ -20,7 +27,7 @@ describe "Dashboard Feed", feature: true do project.team << [user, :master] issue_event(issue, user) note_event(note, user) - visit dashboard_projects_path(:atom, private_token: user.private_token) + visit dashboard_projects_path(:atom, rss_token: user.rss_token) end it "has issue opened event" do diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb index 4f6754ad541..a61231ea254 100644 --- a/spec/features/atom/issues_spec.rb +++ b/spec/features/atom/issues_spec.rb @@ -43,25 +43,40 @@ describe 'Issues Feed', feature: true do end end + context 'when authenticated via RSS token' do + it 'renders atom feed' do + visit namespace_project_issues_path(project.namespace, project, :atom, + rss_token: user.rss_token) + + expect(response_headers['Content-Type']). + to have_content('application/atom+xml') + expect(body).to have_selector('title', text: "#{project.name} issues") + expect(body).to have_selector('author email', text: issue.author_public_email) + expect(body).to have_selector('assignees assignee email', text: issue.assignees.first.public_email) + expect(body).to have_selector('assignee email', text: issue.assignees.first.public_email) + expect(body).to have_selector('entry summary', text: issue.title) + end + end + it "renders atom feed with url parameters for project issues" do visit namespace_project_issues_path(project.namespace, project, - :atom, private_token: user.private_token, state: 'opened', assignee_id: user.id) + :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id) link = find('link[type="application/atom+xml"]') params = CGI.parse(URI.parse(link[:href]).query) - expect(params).to include('private_token' => [user.private_token]) + expect(params).to include('rss_token' => [user.rss_token]) expect(params).to include('state' => ['opened']) expect(params).to include('assignee_id' => [user.id.to_s]) end it "renders atom feed with url parameters for group issues" do - visit issues_group_path(group, :atom, private_token: user.private_token, state: 'opened', assignee_id: user.id) + visit issues_group_path(group, :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id) link = find('link[type="application/atom+xml"]') params = CGI.parse(URI.parse(link[:href]).query) - expect(params).to include('private_token' => [user.private_token]) + expect(params).to include('rss_token' => [user.rss_token]) expect(params).to include('state' => ['opened']) expect(params).to include('assignee_id' => [user.id.to_s]) end diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index 7a2987e815d..fae5aaa52bd 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -11,6 +11,13 @@ describe "User Feed", feature: true do end end + context 'user atom feed via RSS token' do + it "renders user atom feed" do + visit user_path(user, :atom, rss_token: user.rss_token) + expect(body).to have_selector('feed title') + end + end + context 'feed content' do let(:project) { create(:project) } let(:issue) do @@ -40,7 +47,7 @@ describe "User Feed", feature: true do issue_event(issue, user) note_event(note, user) merge_request_event(merge_request, user) - visit user_path(user, :atom, private_token: user.private_token) + visit user_path(user, :atom, rss_token: user.rss_token) end it 'has issue opened event' do diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb index 43ea36c3cdd..740f60c05cc 100644 --- a/spec/features/copy_as_gfm_spec.rb +++ b/spec/features/copy_as_gfm_spec.rb @@ -51,7 +51,6 @@ describe 'Copy as GFM', feature: true, js: true do To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/). - - Manage Git repositories with fine grained access controls that keep your code secure - Perform code reviews and enhance collaboration with merge requests @@ -66,6 +65,38 @@ describe 'Copy as GFM', feature: true, js: true do GFM ) + aggregate_failures('an accidentally selected empty element') do + gfm = '# Heading1' + + html = <<-HTML.strip_heredoc + <h1>Heading1</h1> + + <h2></h2> + HTML + + output_gfm = html_to_gfm(html) + expect(output_gfm.strip).to eq(gfm.strip) + end + + aggregate_failures('an accidentally selected other element') do + gfm = 'Test comment with **Markdown!**' + + html = <<-HTML.strip_heredoc + <li class="note"> + <div class="md"> + <p> + Test comment with <strong>Markdown!</strong> + </p> + </div> + </li> + + <li class="note"></li> + HTML + + output_gfm = html_to_gfm(html) + expect(output_gfm.strip).to eq(gfm.strip) + end + verify( 'InlineDiffFilter', @@ -352,7 +383,6 @@ describe 'Copy as GFM', feature: true, js: true do <<-GFM.strip_heredoc, - Nested - - Lists GFM @@ -375,7 +405,6 @@ describe 'Copy as GFM', feature: true, js: true do <<-GFM.strip_heredoc, 1. Nested - 1. Numbered lists GFM diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb index c977f266296..0764044260e 100644 --- a/spec/features/dashboard/activity_spec.rb +++ b/spec/features/dashboard/activity_spec.rb @@ -5,7 +5,7 @@ RSpec.describe 'Dashboard Activity', feature: true do login_as(create :user) visit activity_dashboard_path end - - it_behaves_like "it has an RSS button with current_user's private token" - it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + + it_behaves_like "it has an RSS button with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" end diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 7a132dba1e9..2346a9ec2ed 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -62,6 +62,6 @@ RSpec.describe 'Dashboard Issues', feature: true do expect(page).to have_content(other_issue.title) end - it_behaves_like "it has an RSS button with current_user's private token" - it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + it_behaves_like "it has an RSS button with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" end diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index f1789fc9d43..01351548a99 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -31,5 +31,5 @@ RSpec.describe 'Dashboard Projects', feature: true do end end - it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" end diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb index ad60fb2c74f..1c53f6dff06 100644 --- a/spec/features/dashboard_issues_spec.rb +++ b/spec/features/dashboard_issues_spec.rb @@ -53,10 +53,10 @@ describe "Dashboard Issues filtering", feature: true, js: true do auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) - expect(params).to include('private_token' => [user.private_token]) + expect(params).to include('rss_token' => [user.rss_token]) expect(params).to include('milestone_title' => ['']) expect(params).to include('assignee_id' => [user.id.to_s]) - expect(auto_discovery_params).to include('private_token' => [user.private_token]) + expect(auto_discovery_params).to include('rss_token' => [user.rss_token]) expect(auto_discovery_params).to include('milestone_title' => ['']) expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) end diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb index 3b481cba424..81f9c103e95 100644 --- a/spec/features/groups/activity_spec.rb +++ b/spec/features/groups/activity_spec.rb @@ -11,8 +11,8 @@ feature 'Group activity page', feature: true do visit path end - it_behaves_like "it has an RSS button with current_user's private token" - it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + it_behaves_like "it has an RSS button with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" end context 'when signed out' do @@ -20,7 +20,7 @@ feature 'Group activity page', feature: true do visit path end - it_behaves_like "it has an RSS button without a private token" - it_behaves_like "an autodiscoverable RSS feed without a private token" + it_behaves_like "it has an RSS button without an RSS token" + it_behaves_like "an autodiscoverable RSS feed without an RSS token" end end diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index aa2e9632d6c..d6b88542ef7 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -12,15 +12,15 @@ feature 'Group issues page', feature: true do context 'when signed in' do let(:user) { user_in_group } - it_behaves_like "it has an RSS button with current_user's private token" - it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + it_behaves_like "it has an RSS button with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" end context 'when signed out' do let(:user) { nil } - it_behaves_like "it has an RSS button without a private token" - it_behaves_like "an autodiscoverable RSS feed without a private token" + it_behaves_like "it has an RSS button without an RSS token" + it_behaves_like "an autodiscoverable RSS feed without an RSS token" end end diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index fb39693e8ca..d3c49c37374 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -11,7 +11,7 @@ feature 'Group show page', feature: true do visit path end - it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" end context 'when signed out' do @@ -19,6 +19,6 @@ feature 'Group show page', feature: true do visit path end - it_behaves_like "an autodiscoverable RSS feed without a private token" + it_behaves_like "an autodiscoverable RSS feed without an RSS token" end end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 03ff1cffb3f..7958ad7e24f 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -810,10 +810,10 @@ describe 'Filter issues', js: true, feature: true do auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) - expect(params).to include('private_token' => [user.private_token]) + expect(params).to include('rss_token' => [user.rss_token]) expect(params).to include('milestone_title' => [milestone.title]) expect(params).to include('assignee_id' => [user.id.to_s]) - expect(auto_discovery_params).to include('private_token' => [user.private_token]) + expect(auto_discovery_params).to include('rss_token' => [user.rss_token]) expect(auto_discovery_params).to include('milestone_title' => [milestone.title]) expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) end @@ -825,10 +825,10 @@ describe 'Filter issues', js: true, feature: true do auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) - expect(params).to include('private_token' => [user.private_token]) + expect(params).to include('rss_token' => [user.rss_token]) expect(params).to include('milestone_title' => [milestone.title]) expect(params).to include('assignee_id' => [user.id.to_s]) - expect(auto_discovery_params).to include('private_token' => [user.private_token]) + expect(auto_discovery_params).to include('rss_token' => [user.rss_token]) expect(auto_discovery_params).to include('milestone_title' => [milestone.title]) expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) end diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_requests/discussion_spec.rb index f59d0faa274..1a09cc54c2e 100644 --- a/spec/features/merge_requests/discussion_spec.rb +++ b/spec/features/merge_requests/discussion_spec.rb @@ -43,7 +43,7 @@ feature 'Merge Request Discussions', feature: true do it 'shows a link to the outdated diff' do within(".discussion[data-discussion-id='#{outdated_discussion.id}']") do path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: old_merge_request_diff.id, anchor: outdated_discussion.line_code) - expect(page).to have_link('an outdated diff', href: path) + expect(page).to have_link('an old version of the diff', href: path) end end end diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb index aef8ef20938..aad522ee26e 100644 --- a/spec/features/merge_requests/versions_spec.rb +++ b/spec/features/merge_requests/versions_spec.rb @@ -124,6 +124,8 @@ feature 'Merge Request versions', js: true, feature: true do diff_refs: merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs ) outdated_diff_note = create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position) + outdated_diff_note.position = outdated_diff_note.original_position + outdated_diff_note.save! visit current_url wait_for_requests diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index e63feb14b7e..7df628fd7a0 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -47,6 +47,21 @@ describe 'Profile account page', feature: true do end end + describe 'when I reset RSS token' do + before do + visit profile_account_path + end + + it 'resets RSS token' do + previous_token = find("#rss-token").value + + click_link('Reset RSS token') + + expect(page).to have_content 'RSS token was successfully reset' + expect(find('#rss-token').value).not_to eq(previous_token) + end + end + describe 'when I reset incoming email token' do before do allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true) diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb index b47c6d431eb..3c1de5c09b2 100644 --- a/spec/features/projects/activity/rss_spec.rb +++ b/spec/features/projects/activity/rss_spec.rb @@ -16,7 +16,7 @@ feature 'Project Activity RSS' do visit path end - it_behaves_like "it has an RSS button with current_user's private token" + it_behaves_like "it has an RSS button with current_user's RSS token" end context 'when signed out' do @@ -24,6 +24,6 @@ feature 'Project Activity RSS' do visit path end - it_behaves_like "it has an RSS button without a private token" + it_behaves_like "it has an RSS button without an RSS token" end end diff --git a/spec/features/projects/commit/rss_spec.rb b/spec/features/projects/commit/rss_spec.rb index 6e0e1916f87..03b6d560c96 100644 --- a/spec/features/projects/commit/rss_spec.rb +++ b/spec/features/projects/commit/rss_spec.rb @@ -12,8 +12,8 @@ feature 'Project Commits RSS' do visit path end - it_behaves_like "it has an RSS button with current_user's private token" - it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + it_behaves_like "it has an RSS button with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" end context 'when signed out' do @@ -21,7 +21,7 @@ feature 'Project Commits RSS' do visit path end - it_behaves_like "it has an RSS button without a private token" - it_behaves_like "an autodiscoverable RSS feed without a private token" + it_behaves_like "it has an RSS button without an RSS token" + it_behaves_like "an autodiscoverable RSS feed without an RSS token" end end diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb index 294a63a5c6d..4162f2579d1 100644 --- a/spec/features/projects/compare_spec.rb +++ b/spec/features/projects/compare_spec.rb @@ -52,8 +52,12 @@ describe "Compare", js: true do def select_using_dropdown(dropdown_type, selection) dropdown = find(".js-compare-#{dropdown_type}-dropdown") dropdown.find(".compare-dropdown-toggle").click + # find input before using to wait for the inputs visiblity + dropdown.find('.dropdown-menu') dropdown.fill_in("Filter by Git revision", with: selection) wait_for_requests - dropdown.find_all("a[data-ref=\"#{selection}\"]", visible: true).last.click + # find before all to wait for the items visiblity + dropdown.find("a[data-ref=\"#{selection}\"]", match: :first) + dropdown.all("a[data-ref=\"#{selection}\"]").last.click end end diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb index 71429f00095..f6852192aef 100644 --- a/spec/features/projects/issues/rss_spec.rb +++ b/spec/features/projects/issues/rss_spec.rb @@ -16,8 +16,8 @@ feature 'Project Issues RSS' do visit path end - it_behaves_like "it has an RSS button with current_user's private token" - it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + it_behaves_like "it has an RSS button with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" end context 'when signed out' do @@ -25,7 +25,7 @@ feature 'Project Issues RSS' do visit path end - it_behaves_like "it has an RSS button without a private token" - it_behaves_like "an autodiscoverable RSS feed without a private token" + it_behaves_like "it has an RSS button without an RSS token" + it_behaves_like "an autodiscoverable RSS feed without an RSS token" end end diff --git a/spec/features/projects/main/rss_spec.rb b/spec/features/projects/main/rss_spec.rb index b1a3af612a1..53966229a2a 100644 --- a/spec/features/projects/main/rss_spec.rb +++ b/spec/features/projects/main/rss_spec.rb @@ -12,7 +12,7 @@ feature 'Project RSS' do visit path end - it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" end context 'when signed out' do @@ -20,6 +20,6 @@ feature 'Project RSS' do visit path end - it_behaves_like "an autodiscoverable RSS feed without a private token" + it_behaves_like "an autodiscoverable RSS feed without an RSS token" end end diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb index d3232f0cc16..fbaea14a2be 100644 --- a/spec/features/projects/settings/integration_settings_spec.rb +++ b/spec/features/projects/settings/integration_settings_spec.rb @@ -85,11 +85,55 @@ feature 'Integration settings', feature: true do expect(current_path).to eq(integrations_path) end - scenario 'remove existing webhook' do - hook - visit integrations_path + context 'remove existing webhook' do + scenario 'from webhooks list page' do + hook + visit integrations_path + + expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1) + end + + scenario 'from webhook edit page' do + hook + visit integrations_path + click_link 'Edit' + + expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1) + end + end + end + + context 'Webhook logs' do + let(:hook) { create(:project_hook, project: project) } + let(:hook_log) { create(:web_hook_log, web_hook: hook, internal_error_message: 'some error') } + + scenario 'show list of hook logs' do + hook_log + visit edit_namespace_project_hook_path(project.namespace, project, hook) + + expect(page).to have_content('Recent Deliveries') + expect(page).to have_content(hook_log.url) + end + + scenario 'show hook log details' do + hook_log + visit edit_namespace_project_hook_path(project.namespace, project, hook) + click_link 'View details' + + expect(page).to have_content("POST #{hook_log.url}") + expect(page).to have_content(hook_log.internal_error_message) + expect(page).to have_content('Resend Request') + end + + scenario 'retry hook log' do + WebMock.stub_request(:post, hook.url) + + hook_log + visit edit_namespace_project_hook_path(project.namespace, project, hook) + click_link 'View details' + click_link 'Resend Request' - expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1) + expect(current_path).to eq(edit_namespace_project_hook_path(project.namespace, project, hook)) end end end diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb new file mode 100644 index 00000000000..cf21b208f65 --- /dev/null +++ b/spec/features/projects/sub_group_issuables_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe 'Subgroup Issuables', :feature, :js do + let!(:group) { create(:group, name: 'group') } + let!(:subgroup) { create(:group, parent: group, name: 'subgroup') } + let!(:project) { create(:empty_project, namespace: subgroup, name: 'project') } + let(:user) { create(:user) } + + before do + project.add_master(user) + login_as user + end + + it 'shows the full subgroup title when issues index page is empty' do + visit namespace_project_issues_path(project.namespace.to_param, project.to_param) + + expect_to_have_full_subgroup_title + end + + it 'shows the full subgroup title when merge requests index page is empty' do + visit namespace_project_merge_requests_path(project.namespace.to_param, project.to_param) + + expect_to_have_full_subgroup_title + end + + def expect_to_have_full_subgroup_title + title = find('.title-container') + + expect(title).not_to have_selector '.initializing' + expect(title).to have_content 'group / subgroup / project' + end +end diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb index 9ac51997d65..9bf59c4139c 100644 --- a/spec/features/projects/tree/rss_spec.rb +++ b/spec/features/projects/tree/rss_spec.rb @@ -12,7 +12,7 @@ feature 'Project Tree RSS' do visit path end - it_behaves_like "an autodiscoverable RSS feed with current_user's private token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" end context 'when signed out' do @@ -20,6 +20,6 @@ feature 'Project Tree RSS' do visit path end - it_behaves_like "an autodiscoverable RSS feed without a private token" + it_behaves_like "an autodiscoverable RSS feed without an RSS token" end end diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb index 14564abb16d..dbd5f66b55e 100644 --- a/spec/features/users/rss_spec.rb +++ b/spec/features/users/rss_spec.rb @@ -9,7 +9,7 @@ feature 'User RSS' do visit path end - it_behaves_like "it has an RSS button with current_user's private token" + it_behaves_like "it has an RSS button with current_user's RSS token" end context 'when signed out' do @@ -17,6 +17,6 @@ feature 'User RSS' do visit path end - it_behaves_like "it has an RSS button without a private token" + it_behaves_like "it has an RSS button without an RSS token" end end diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb index 099146678ae..355a4845afb 100644 --- a/spec/helpers/notes_helper_spec.rb +++ b/spec/helpers/notes_helper_spec.rb @@ -92,7 +92,13 @@ describe NotesHelper do ) end - let(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position).to_discussion } + let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position) } + let(:discussion) { diff_note.to_discussion } + + before do + diff_note.position = diff_note.original_position + diff_note.save! + end it 'returns the diff version comparison path with the line code' do expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code)) diff --git a/spec/helpers/rss_helper_spec.rb b/spec/helpers/rss_helper_spec.rb index f3f174f3d14..269e1057e8d 100644 --- a/spec/helpers/rss_helper_spec.rb +++ b/spec/helpers/rss_helper_spec.rb @@ -3,17 +3,17 @@ require 'spec_helper' describe RssHelper do describe '#rss_url_options' do context 'when signed in' do - it "includes the current_user's private_token" do + it "includes the current_user's rss_token" do current_user = create(:user) allow(helper).to receive(:current_user).and_return(current_user) - expect(helper.rss_url_options).to include private_token: current_user.private_token + expect(helper.rss_url_options).to include rss_token: current_user.rss_token end end context 'when signed out' do - it "does not have a private_token" do + it "does not have an rss_token" do allow(helper).to receive(:current_user).and_return(nil) - expect(helper.rss_url_options[:private_token]).to be_nil + expect(helper.rss_url_options[:rss_token]).to be_nil end end end diff --git a/spec/javascripts/copy_as_gfm_spec.js b/spec/javascripts/copy_as_gfm_spec.js index 1a850bb56ab..ded450749d3 100644 --- a/spec/javascripts/copy_as_gfm_spec.js +++ b/spec/javascripts/copy_as_gfm_spec.js @@ -1,4 +1,4 @@ -require('~/copy_as_gfm'); +import '~/copy_as_gfm'; (() => { describe('gl.CopyAsGFM', () => { diff --git a/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js index 0ebb867b851..79447787fc9 100644 --- a/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js +++ b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import eventHub from '~/filtered_search/event_hub'; import RecentSearchesDropdownContent from '~/filtered_search/components/recent_searches_dropdown_content'; -require('~/filtered_search/filtered_search_token_keys'); +import '~/filtered_search/filtered_search_token_keys'; const createComponent = (propsData) => { const Component = Vue.extend(RecentSearchesDropdownContent); diff --git a/spec/javascripts/raven/raven_config_spec.js b/spec/javascripts/raven/raven_config_spec.js index b31a7c28ebe..c82658b9262 100644 --- a/spec/javascripts/raven/raven_config_spec.js +++ b/spec/javascripts/raven/raven_config_spec.js @@ -140,24 +140,6 @@ describe('RavenConfig', () => { }); }); - describe('bindRavenErrors', () => { - let $document; - let $; - - beforeEach(() => { - $document = jasmine.createSpyObj('$document', ['on']); - $ = jasmine.createSpy('$').and.returnValue($document); - - window.$ = $; - - RavenConfig.bindRavenErrors(); - }); - - it('should call .on', function () { - expect($document.on).toHaveBeenCalledWith('ajaxError.raven', RavenConfig.handleRavenErrors); - }); - }); - describe('handleRavenErrors', () => { let event; let req; diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js index 865951b2ad7..929ba75e67d 100644 --- a/spec/javascripts/sidebar/sidebar_assignees_spec.js +++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js @@ -43,4 +43,16 @@ describe('sidebar assignees', () => { expect(SidebarMediator.prototype.assignYourself).toHaveBeenCalled(); expect(this.mediator.store.assignees.length).toEqual(1); }); + + it('hides assignees until fetched', (done) => { + component = new SidebarAssigneeComponent().$mount(this.sidebarAssigneesEl); + const currentAssignee = this.sidebarAssigneesEl.querySelector('.value'); + expect(currentAssignee).toBe(null); + + component.store.isFetching.assignees = false; + Vue.nextTick(() => { + expect(component.$el.querySelector('.value')).toBeVisible(); + done(); + }); + }); }); diff --git a/spec/javascripts/sidebar/sidebar_store_spec.js b/spec/javascripts/sidebar/sidebar_store_spec.js index 29facf483b5..b3fa156eb64 100644 --- a/spec/javascripts/sidebar/sidebar_store_spec.js +++ b/spec/javascripts/sidebar/sidebar_store_spec.js @@ -35,6 +35,10 @@ describe('Sidebar store', () => { SidebarStore.singleton = null; }); + it('has default isFetching values', () => { + expect(this.store.isFetching.assignees).toBe(true); + }); + it('adds a new assignee', () => { this.store.addAssignee(assignee); expect(this.store.assignees.length).toEqual(1); @@ -67,6 +71,7 @@ describe('Sidebar store', () => { }; this.store.setAssigneeData(users); + expect(this.store.isFetching.assignees).toBe(false); expect(this.store.assignees.length).toEqual(3); }); diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb index c56fded7516..ce2b5d620fd 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb @@ -18,8 +18,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do let(:subject) { described_class.new(['parent/the-Path'], migration) } it 'includes the namespace' do - parent = create(:namespace, path: 'parent') - child = create(:namespace, path: 'the-path', parent: parent) + parent = create(:group, path: 'parent') + child = create(:group, path: 'the-path', parent: parent) found_ids = subject.namespaces_for_paths(type: :child). map(&:id) @@ -30,13 +30,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do context 'for child namespaces' do it 'only returns child namespaces with the correct path' do - _root_namespace = create(:namespace, path: 'THE-path') - _other_path = create(:namespace, + _root_namespace = create(:group, path: 'THE-path') + _other_path = create(:group, path: 'other', - parent: create(:namespace)) - namespace = create(:namespace, + parent: create(:group)) + namespace = create(:group, path: 'the-path', - parent: create(:namespace)) + parent: create(:group)) found_ids = subject.namespaces_for_paths(type: :child). map(&:id) @@ -45,13 +45,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do end it 'has no namespaces that look the same' do - _root_namespace = create(:namespace, path: 'THE-path') - _similar_path = create(:namespace, + _root_namespace = create(:group, path: 'THE-path') + _similar_path = create(:group, path: 'not-really-the-path', - parent: create(:namespace)) - namespace = create(:namespace, + parent: create(:group)) + namespace = create(:group, path: 'the-path', - parent: create(:namespace)) + parent: create(:group)) found_ids = subject.namespaces_for_paths(type: :child). map(&:id) @@ -62,11 +62,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do context 'for top levelnamespaces' do it 'only returns child namespaces with the correct path' do - root_namespace = create(:namespace, path: 'the-path') - _other_path = create(:namespace, path: 'other') - _child_namespace = create(:namespace, + root_namespace = create(:group, path: 'the-path') + _other_path = create(:group, path: 'other') + _child_namespace = create(:group, path: 'the-path', - parent: create(:namespace)) + parent: create(:group)) found_ids = subject.namespaces_for_paths(type: :top_level). map(&:id) @@ -75,11 +75,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do end it 'has no namespaces that just look the same' do - root_namespace = create(:namespace, path: 'the-path') - _similar_path = create(:namespace, path: 'not-really-the-path') - _child_namespace = create(:namespace, + root_namespace = create(:group, path: 'the-path') + _similar_path = create(:group, path: 'not-really-the-path') + _child_namespace = create(:group, path: 'the-path', - parent: create(:namespace)) + parent: create(:group)) found_ids = subject.namespaces_for_paths(type: :top_level). map(&:id) @@ -124,10 +124,10 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do describe "#child_ids_for_parent" do it "collects child ids for all levels" do - parent = create(:namespace) - first_child = create(:namespace, parent: parent) - second_child = create(:namespace, parent: parent) - third_child = create(:namespace, parent: second_child) + parent = create(:group) + first_child = create(:group, parent: parent) + second_child = create(:group, parent: parent) + third_child = create(:group, parent: second_child) all_ids = [parent.id, first_child.id, second_child.id, third_child.id] collected_ids = subject.child_ids_for_parent(parent, ids: [parent.id]) @@ -205,9 +205,9 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do end describe '#rename_namespaces' do - let!(:top_level_namespace) { create(:namespace, path: 'the-path') } + let!(:top_level_namespace) { create(:group, path: 'the-path') } let!(:child_namespace) do - create(:namespace, path: 'the-path', parent: create(:namespace)) + create(:group, path: 'the-path', parent: create(:group)) end it 'renames top level namespaces the namespace' do diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index 4d202a76e1b..93d30b90937 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -61,9 +61,10 @@ describe Gitlab::Diff::PositionTracer, lib: true do let(:old_diff_refs) { raise NotImplementedError } let(:new_diff_refs) { raise NotImplementedError } + let(:change_diff_refs) { raise NotImplementedError } let(:old_position) { raise NotImplementedError } - let(:position_tracer) { described_class.new(repository: project.repository, old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs) } + let(:position_tracer) { described_class.new(project: project, old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs) } subject { position_tracer.trace(old_position) } def diff_refs(base_commit, head_commit) @@ -77,16 +78,40 @@ describe Gitlab::Diff::PositionTracer, lib: true do Gitlab::Diff::Position.new(attrs) end - def expect_new_position(attrs, new_position = subject) - if attrs.nil? - expect(new_position).to be_nil - else - expect(new_position).not_to be_nil + def expect_new_position(attrs, result = subject) + aggregate_failures("expect new position #{attrs.inspect}") do + if attrs.nil? + expect(result[:outdated]).to be_truthy + else + expect(result[:outdated]).to be_falsey - expect(new_position.diff_refs).to eq(new_diff_refs) + new_position = result[:position] + expect(new_position).not_to be_nil - attrs.each do |attr, value| - expect(new_position.send(attr)).to eq(value) + expect(new_position.diff_refs).to eq(new_diff_refs) + + attrs.each do |attr, value| + expect(new_position.send(attr)).to eq(value) + end + end + end + end + + def expect_change_position(attrs, result = subject) + aggregate_failures("expect change position #{attrs.inspect}") do + expect(result[:outdated]).to be_truthy + + change_position = result[:position] + if attrs.nil? || attrs.empty? + expect(change_position).to be_nil + else + expect(change_position).not_to be_nil + + expect(change_position.diff_refs).to eq(change_diff_refs) + + attrs.each do |attr, value| + expect(change_position.send(attr)).to eq(value) + end end end end @@ -395,6 +420,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when that line was changed between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) } let(:old_position) { position(new_path: file_name, new_line: 2) } # old diff: @@ -407,14 +433,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 + BB # 3 + C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) end end context "when that line was deleted between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:change_diff_refs) { diff_refs(update_line_commit, delete_line_commit) } let(:old_position) { position(new_path: file_name, new_line: 3) } # old diff: @@ -426,8 +458,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 1 + A # 2 + BB - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 3, + new_line: nil + ) end end end @@ -512,6 +549,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when that line was changed between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) } let(:old_position) { position(new_path: file_name, new_line: 2) } # old diff: @@ -525,14 +563,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 + BB # 3 3 C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) end end context "when that line was deleted between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } let(:old_position) { position(new_path: file_name, new_line: 3) } # old diff: @@ -545,8 +589,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 2 A # 3 - C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 3, + new_line: nil + ) end end end @@ -558,6 +607,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when the file's content was unchanged between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } let(:new_diff_refs) { diff_refs(delete_line_commit, rename_file_commit) } + let(:change_diff_refs) { diff_refs(initial_commit, delete_line_commit) } let(:old_position) { position(new_path: file_name, new_line: 2) } # old diff: @@ -569,8 +619,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 1 1 BB # 2 2 A - it "returns nil since the line doesn't exist in the new diffs anymore" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: nil, + new_line: 2 + ) end end @@ -628,6 +683,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when that line was changed between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } + let(:change_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } let(:old_position) { position(new_path: file_name, new_line: 2) } # old diff: @@ -640,28 +696,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 - A # 2 + AA - it "returns nil" do - expect(subject).to be_nil - end - end - - context "when that line was deleted between the old and the new diff" do - let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } - let(:new_diff_refs) { diff_refs(delete_line_commit, delete_line_again_commit) } - let(:old_position) { position(new_path: file_name, new_line: 1) } - - # old diff: - # 1 + BB - # 2 + A - # - # new diff: - # file_name -> new_file_name - # 1 - BB - # 2 - A - # 1 + AA - - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: new_file_name, + old_line: 2, + new_line: nil + ) end end end @@ -673,6 +714,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when the file's content was unchanged between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } let(:old_position) { position(new_path: file_name, new_line: 2) } # old diff: @@ -683,8 +725,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 1 - BB # 2 - A - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) end end @@ -692,6 +739,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when that line was unchanged between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) } let(:old_position) { position(new_path: file_name, new_line: 2) } # old diff: @@ -703,14 +751,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 1 - BB # 2 - A - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) end end context "when that line was moved between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } let(:new_diff_refs) { diff_refs(move_line_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(update_line_commit, delete_file_commit) } let(:old_position) { position(new_path: file_name, new_line: 2) } # old diff: @@ -723,14 +777,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 - A # 3 - C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) end end context "when that line was changed between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } let(:new_diff_refs) { diff_refs(update_line_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, delete_file_commit) } let(:old_position) { position(new_path: file_name, new_line: 2) } # old diff: @@ -743,14 +803,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 - BB # 3 - C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) end end context "when that line was deleted between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) } let(:old_position) { position(new_path: file_name, new_line: 3) } # old diff: @@ -762,8 +828,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 1 - BB # 2 - A - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 3, + new_line: nil + ) end end end @@ -775,6 +846,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when the file's content was unchanged between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } let(:new_diff_refs) { diff_refs(create_file_commit, create_second_file_commit) } + let(:change_diff_refs) { diff_refs(initial_commit, create_file_commit) } let(:old_position) { position(new_path: file_name, new_line: 2) } # old diff: @@ -787,8 +859,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 2 B # 3 3 C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: nil, + new_line: 2 + ) end end @@ -796,6 +873,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when that line was unchanged between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) } + let(:change_diff_refs) { diff_refs(initial_commit, update_line_commit) } let(:old_position) { position(new_path: file_name, new_line: 1) } # old diff: @@ -808,14 +886,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 2 BB # 3 3 C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: nil, + new_line: 1 + ) end end context "when that line was moved between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } let(:new_diff_refs) { diff_refs(move_line_commit, move_second_file_line_commit) } + let(:change_diff_refs) { diff_refs(initial_commit, move_line_commit) } let(:old_position) { position(new_path: file_name, new_line: 2) } # old diff: @@ -828,14 +912,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 2 A # 3 3 C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: nil, + new_line: 1 + ) end end context "when that line was changed between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) } let(:old_position) { position(new_path: file_name, new_line: 2) } # old diff: @@ -848,14 +938,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 2 BB # 3 3 C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) end end context "when that line was deleted between the old and the new diff" do let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } let(:new_diff_refs) { diff_refs(delete_line_commit, delete_second_file_line_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, delete_second_file_line_commit) } let(:old_position) { position(new_path: file_name, new_line: 3) } # old diff: @@ -867,8 +963,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 1 1 BB # 2 2 A - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 3, + new_line: nil + ) end end end @@ -957,6 +1058,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when that line was changed or deleted between the old and the new diff" do let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } let(:new_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, create_file_commit) } let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } # old diff: @@ -970,8 +1072,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 + B # 3 + C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 1, + new_line: nil + ) end end end @@ -980,6 +1087,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when the position pointed at a deleted line in the old diff" do let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, initial_commit) } let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) } # old diff: @@ -993,8 +1101,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 + BB # 3 + C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 2, + new_line: nil + ) end end @@ -1076,6 +1189,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when that line was changed or deleted between the old and the new diff" do let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 3, new_line: 3) } # old diff: @@ -1088,8 +1202,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 1 + A # 2 + B - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 3, + new_line: nil + ) end end end @@ -1182,6 +1301,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do context "when that line was changed or deleted between the old and the new diff" do let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:change_diff_refs) { diff_refs(move_line_commit, update_line_commit) } let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } # old diff: @@ -1196,8 +1316,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do # 2 + BB # 3 3 C - it "returns nil" do - expect(subject).to be_nil + it "returns the position of the change" do + expect_change_position( + old_path: file_name, + new_path: file_name, + old_line: 1, + new_line: nil + ) end end end @@ -1239,7 +1364,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do describe "typical use scenarios" do let(:second_branch_name) { "#{branch_name}-2" } - def expect_positions(old_attrs, new_attrs) + def expect_new_positions(old_attrs, new_attrs) old_positions = old_attrs.map do |old_attrs| position(old_attrs) end @@ -1248,8 +1373,14 @@ describe Gitlab::Diff::PositionTracer, lib: true do position_tracer.trace(old_position) end - new_positions.zip(new_attrs).each do |new_position, new_attrs| - expect_new_position(new_attrs, new_position) + aggregate_failures do + new_positions.zip(new_attrs).each do |new_position, new_attrs| + if new_attrs&.delete(:change) + expect_change_position(new_attrs, new_position) + else + expect_new_position(new_attrs, new_position) + end + end end end @@ -1330,6 +1461,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do describe "simple push of new commit" do let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } let(:new_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:change_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) } # old diff: # 1 1 A @@ -1368,14 +1500,14 @@ describe Gitlab::Diff::PositionTracer, lib: true do { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, { old_path: file_name, old_line: 2 }, { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, - { old_path: file_name, old_line: 4, new_line: 4 }, - nil, + { new_path: file_name, new_line: 4, change: true }, + { new_path: file_name, old_line: 3, change: true }, { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, - { old_path: file_name, old_line: 6 }, + { new_path: file_name, old_line: 5, change: true }, { new_path: file_name, new_line: 7 } ] - expect_positions(old_position_attrs, new_position_attrs) + expect_new_positions(old_position_attrs, new_position_attrs) end end @@ -1402,6 +1534,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } let(:new_diff_refs) { diff_refs(create_file_commit, second_create_file_commit) } + let(:change_diff_refs) { diff_refs(update_file_commit, second_create_file_commit) } # old diff: # 1 1 A @@ -1440,20 +1573,21 @@ describe Gitlab::Diff::PositionTracer, lib: true do { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, { old_path: file_name, old_line: 2 }, { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, - { old_path: file_name, old_line: 4, new_line: 4 }, - nil, + { new_path: file_name, new_line: 4, change: true }, + { old_path: file_name, old_line: 3, change: true }, { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, - { old_path: file_name, old_line: 6 }, + { old_path: file_name, old_line: 5, change: true }, { new_path: file_name, new_line: 7 } ] - expect_positions(old_position_attrs, new_position_attrs) + expect_new_positions(old_position_attrs, new_position_attrs) end end describe "force push to delete last commit" do let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } let(:new_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:change_diff_refs) { diff_refs(update_file_again_commit, update_file_commit) } # old diff: # 1 1 A @@ -1492,16 +1626,16 @@ describe Gitlab::Diff::PositionTracer, lib: true do new_position_attrs = [ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, { old_path: file_name, old_line: 2 }, - nil, + { old_path: file_name, old_line: 2, change: true }, { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, - { old_path: file_name, old_line: 4 }, + { old_path: file_name, old_line: 4, change: true }, { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, - { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, - nil, + { new_path: file_name, new_line: 5, change: true }, + { old_path: file_name, old_line: 6, change: true }, { new_path: file_name, new_line: 6 } ] - expect_positions(old_position_attrs, new_position_attrs) + expect_new_positions(old_position_attrs, new_position_attrs) end end @@ -1567,6 +1701,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } let(:new_diff_refs) { diff_refs(create_file_commit, overwrite_update_file_again_commit) } + let(:change_diff_refs) { diff_refs(update_file_again_commit, overwrite_update_file_again_commit) } # old diff: # 1 1 A @@ -1618,7 +1753,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do { new_path: file_name, new_line: 10 }, # + G ] - expect_positions(old_position_attrs, new_position_attrs) + expect_new_positions(old_position_attrs, new_position_attrs) end end @@ -1643,6 +1778,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } let(:new_diff_refs) { diff_refs(create_file_commit, merge_commit) } + let(:change_diff_refs) { diff_refs(update_file_again_commit, merge_commit) } # old diff: # 1 1 A @@ -1694,13 +1830,14 @@ describe Gitlab::Diff::PositionTracer, lib: true do { new_path: file_name, new_line: 10 }, # + G ] - expect_positions(old_position_attrs, new_position_attrs) + expect_new_positions(old_position_attrs, new_position_attrs) end end describe "changing target branch" do let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } let(:new_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) } + let(:change_diff_refs) { diff_refs(create_file_commit, update_file_commit) } # old diff: # 1 1 A @@ -1739,7 +1876,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do new_position_attrs = [ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, - nil, + { old_path: file_name, old_line: 2, change: true }, { new_path: file_name, new_line: 2 }, { old_path: file_name, new_path: file_name, old_line: 2, new_line: 3 }, { new_path: file_name, new_line: 4 }, @@ -1749,7 +1886,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do { new_path: file_name, new_line: 7 } ] - expect_positions(old_position_attrs, new_position_attrs) + expect_new_positions(old_position_attrs, new_position_attrs) end end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 34f617e23a5..2e9646286df 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -131,6 +131,7 @@ services: - service_hook hooks: - project +- web_hook_logs protected_branches: - project - merge_access_levels diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index c22fba11225..96054c996fd 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -54,6 +54,7 @@ Note: - type - position - original_position +- change_position - resolved_at - resolved_by_id - discussion_id diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb new file mode 100644 index 00000000000..1eea710c80b --- /dev/null +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -0,0 +1,384 @@ +# coding: utf-8 +require 'spec_helper' + +describe Gitlab::PathRegex, lib: true do + # Pass in a full path to remove the format segment: + # `/ci/lint(.:format)` -> `/ci/lint` + def without_format(path) + path.split('(', 2)[0] + end + + # Pass in a full path and get the last segment before a wildcard + # That's not a parameter + # `/*namespace_id/:project_id/builds/artifacts/*ref_name_and_path` + # -> 'builds/artifacts' + def path_before_wildcard(path) + path = path.gsub(STARTING_WITH_NAMESPACE, "") + path_segments = path.split('/').reject(&:empty?) + wildcard_index = path_segments.index { |segment| parameter?(segment) } + + segments_before_wildcard = path_segments[0..wildcard_index - 1] + + segments_before_wildcard.join('/') + end + + def parameter?(segment) + segment =~ /[*:]/ + end + + # If the path is reserved. Then no conflicting paths can# be created for any + # route using this reserved word. + # + # Both `builds/artifacts` & `build` are covered by reserving the word + # `build` + def wildcards_include?(path) + described_class::PROJECT_WILDCARD_ROUTES.include?(path) || + described_class::PROJECT_WILDCARD_ROUTES.include?(path.split('/').first) + end + + def failure_message(missing_words, constant_name, migration_helper) + missing_words = Array(missing_words) + <<-MSG + Found new routes that could cause conflicts with existing namespaced routes + for groups or projects. + + Add <#{missing_words.join(', ')}> to `Gitlab::PathRegex::#{constant_name} + to make sure no projects or namespaces can be created with those paths. + + To rename any existing records with those paths you can use the + `Gitlab::Database::RenameReservedpathsMigration::<VERSION>.#{migration_helper}` + migration helper. + + Make sure to make a note of the renamed records in the release blog post. + + MSG + end + + let(:all_routes) do + route_set = Rails.application.routes + routes_collection = route_set.routes + routes_array = routes_collection.routes + routes_array.map { |route| route.path.spec.to_s } + end + + let(:routes_without_format) { all_routes.map { |path| without_format(path) } } + + # Routes not starting with `/:` or `/*` + # all routes not starting with a param + let(:routes_not_starting_in_wildcard) { routes_without_format.select { |p| p !~ %r{^/[:*]} } } + + let(:top_level_words) do + routes_not_starting_in_wildcard.map do |route| + route.split('/')[1] + end.compact.uniq + end + + # All routes that start with a namespaced path, that have 1 or more + # path-segments before having another wildcard parameter. + # - Starting with paths: + # - `/*namespace_id/:project_id/` + # - `/*namespace_id/:id/` + # - Followed by one or more path-parts not starting with `:` or `*` + # - Followed by a path-part that includes a wildcard parameter `*` + # At the time of writing these routes match: http://rubular.com/r/Rv2pDE5Dvw + STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id} + NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*} + ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*} + WILDCARD_SEGMENT = %r{\*} + let(:namespaced_wildcard_routes) do + routes_without_format.select do |p| + p =~ %r{#{STARTING_WITH_NAMESPACE}/#{NON_PARAM_PARTS}/#{ANY_OTHER_PATH_PART}#{WILDCARD_SEGMENT}} + end + end + + # This will return all paths that are used in a namespaced route + # before another wildcard path: + # + # /*namespace_id/:project_id/builds/artifacts/*ref_name_and_path + # /*namespace_id/:project_id/info/lfs/objects/*oid + # /*namespace_id/:project_id/commits/*id + # /*namespace_id/:project_id/builds/:build_id/artifacts/file/*path + # -> ['builds/artifacts', 'info/lfs/objects', 'commits', 'artifacts/file'] + let(:all_wildcard_paths) do + namespaced_wildcard_routes.map do |route| + path_before_wildcard(route) + end.uniq + end + + STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/} + let(:group_routes) do + routes_without_format.select do |path| + path =~ STARTING_WITH_GROUP + end + end + + let(:paths_after_group_id) do + group_routes.map do |route| + route.gsub(STARTING_WITH_GROUP, '').split('/').first + end.uniq + end + + describe 'TOP_LEVEL_ROUTES' do + it 'includes all the top level namespaces' do + failure_block = lambda do + missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES + failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths') + end + + expect(described_class::TOP_LEVEL_ROUTES) + .to include(*top_level_words), failure_block + end + end + + describe 'GROUP_ROUTES' do + it "don't contain a second wildcard" do + failure_block = lambda do + missing_words = paths_after_group_id - described_class::GROUP_ROUTES + failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths') + end + + expect(described_class::GROUP_ROUTES) + .to include(*paths_after_group_id), failure_block + end + end + + describe 'PROJECT_WILDCARD_ROUTES' do + it 'includes all paths that can be used after a namespace/project path' do + aggregate_failures do + all_wildcard_paths.each do |path| + expect(wildcards_include?(path)) + .to be(true), failure_message(path, 'PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths') + end + end + end + end + + describe '.root_namespace_path_regex' do + subject { described_class.root_namespace_path_regex } + + it 'rejects top level routes' do + expect(subject).not_to match('admin/') + expect(subject).not_to match('api/') + expect(subject).not_to match('.well-known/') + end + + it 'accepts project wildcard routes' do + expect(subject).to match('blob/') + expect(subject).to match('edit/') + expect(subject).to match('wikis/') + end + + it 'accepts group routes' do + expect(subject).to match('activity/') + expect(subject).to match('group_members/') + expect(subject).to match('subgroups/') + end + + it 'is not case sensitive' do + expect(subject).not_to match('Users/') + end + + it 'does not allow extra slashes' do + expect(subject).not_to match('/blob/') + expect(subject).not_to match('blob//') + end + end + + describe '.full_namespace_path_regex' do + subject { described_class.full_namespace_path_regex } + + context 'at the top level' do + context 'when the final level' do + it 'rejects top level routes' do + expect(subject).not_to match('admin/') + expect(subject).not_to match('api/') + expect(subject).not_to match('.well-known/') + end + + it 'accepts project wildcard routes' do + expect(subject).to match('blob/') + expect(subject).to match('edit/') + expect(subject).to match('wikis/') + end + + it 'accepts group routes' do + expect(subject).to match('activity/') + expect(subject).to match('group_members/') + expect(subject).to match('subgroups/') + end + end + + context 'when more levels follow' do + it 'rejects top level routes' do + expect(subject).not_to match('admin/more/') + expect(subject).not_to match('api/more/') + expect(subject).not_to match('.well-known/more/') + end + + it 'accepts project wildcard routes' do + expect(subject).to match('blob/more/') + expect(subject).to match('edit/more/') + expect(subject).to match('wikis/more/') + expect(subject).to match('environments/folders/') + expect(subject).to match('info/lfs/objects/') + end + + it 'accepts group routes' do + expect(subject).to match('activity/more/') + expect(subject).to match('group_members/more/') + expect(subject).to match('subgroups/more/') + end + end + end + + context 'at the second level' do + context 'when the final level' do + it 'accepts top level routes' do + expect(subject).to match('root/admin/') + expect(subject).to match('root/api/') + expect(subject).to match('root/.well-known/') + end + + it 'rejects project wildcard routes' do + expect(subject).not_to match('root/blob/') + expect(subject).not_to match('root/edit/') + expect(subject).not_to match('root/wikis/') + expect(subject).not_to match('root/environments/folders/') + expect(subject).not_to match('root/info/lfs/objects/') + end + + it 'rejects group routes' do + expect(subject).not_to match('root/activity/') + expect(subject).not_to match('root/group_members/') + expect(subject).not_to match('root/subgroups/') + end + end + + context 'when more levels follow' do + it 'accepts top level routes' do + expect(subject).to match('root/admin/more/') + expect(subject).to match('root/api/more/') + expect(subject).to match('root/.well-known/more/') + end + + it 'rejects project wildcard routes' do + expect(subject).not_to match('root/blob/more/') + expect(subject).not_to match('root/edit/more/') + expect(subject).not_to match('root/wikis/more/') + expect(subject).not_to match('root/environments/folders/more/') + expect(subject).not_to match('root/info/lfs/objects/more/') + end + + it 'rejects group routes' do + expect(subject).not_to match('root/activity/more/') + expect(subject).not_to match('root/group_members/more/') + expect(subject).not_to match('root/subgroups/more/') + end + end + end + + it 'is not case sensitive' do + expect(subject).not_to match('root/Blob/') + end + + it 'does not allow extra slashes' do + expect(subject).not_to match('/root/admin/') + expect(subject).not_to match('root/admin//') + end + end + + describe '.project_path_regex' do + subject { described_class.project_path_regex } + + it 'accepts top level routes' do + expect(subject).to match('admin/') + expect(subject).to match('api/') + expect(subject).to match('.well-known/') + end + + it 'rejects project wildcard routes' do + expect(subject).not_to match('blob/') + expect(subject).not_to match('edit/') + expect(subject).not_to match('wikis/') + expect(subject).not_to match('environments/folders/') + expect(subject).not_to match('info/lfs/objects/') + end + + it 'accepts group routes' do + expect(subject).to match('activity/') + expect(subject).to match('group_members/') + expect(subject).to match('subgroups/') + end + + it 'is not case sensitive' do + expect(subject).not_to match('Blob/') + end + + it 'does not allow extra slashes' do + expect(subject).not_to match('/admin/') + expect(subject).not_to match('admin//') + end + end + + describe '.full_project_path_regex' do + subject { described_class.full_project_path_regex } + + it 'accepts top level routes' do + expect(subject).to match('root/admin/') + expect(subject).to match('root/api/') + expect(subject).to match('root/.well-known/') + end + + it 'rejects project wildcard routes' do + expect(subject).not_to match('root/blob/') + expect(subject).not_to match('root/edit/') + expect(subject).not_to match('root/wikis/') + expect(subject).not_to match('root/environments/folders/') + expect(subject).not_to match('root/info/lfs/objects/') + end + + it 'accepts group routes' do + expect(subject).to match('root/activity/') + expect(subject).to match('root/group_members/') + expect(subject).to match('root/subgroups/') + end + + it 'is not case sensitive' do + expect(subject).not_to match('root/Blob/') + end + + it 'does not allow extra slashes' do + expect(subject).not_to match('/root/admin/') + expect(subject).not_to match('root/admin//') + end + end + + describe '.namespace_format_regex' do + subject { described_class.namespace_format_regex } + + it { is_expected.to match('gitlab-ce') } + it { is_expected.to match('gitlab_git') } + it { is_expected.to match('_underscore.js') } + it { is_expected.to match('100px.com') } + it { is_expected.to match('gitlab.org') } + it { is_expected.not_to match('?gitlab') } + it { is_expected.not_to match('git lab') } + it { is_expected.not_to match('gitlab.git') } + it { is_expected.not_to match('gitlab.org.') } + it { is_expected.not_to match('gitlab.org/') } + it { is_expected.not_to match('/gitlab.org') } + it { is_expected.not_to match('gitlab git') } + end + + describe '.project_path_format_regex' do + subject { described_class.project_path_format_regex } + + it { is_expected.to match('gitlab-ce') } + it { is_expected.to match('gitlab_git') } + it { is_expected.to match('_underscore.js') } + it { is_expected.to match('100px.com') } + it { is_expected.not_to match('?gitlab') } + it { is_expected.not_to match('git lab') } + it { is_expected.not_to match('gitlab.git') } + end +end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index a7d1283acb8..0bee892fe0c 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -2,386 +2,6 @@ require 'spec_helper' describe Gitlab::Regex, lib: true do - # Pass in a full path to remove the format segment: - # `/ci/lint(.:format)` -> `/ci/lint` - def without_format(path) - path.split('(', 2)[0] - end - - # Pass in a full path and get the last segment before a wildcard - # That's not a parameter - # `/*namespace_id/:project_id/builds/artifacts/*ref_name_and_path` - # -> 'builds/artifacts' - def path_before_wildcard(path) - path = path.gsub(STARTING_WITH_NAMESPACE, "") - path_segments = path.split('/').reject(&:empty?) - wildcard_index = path_segments.index { |segment| parameter?(segment) } - - segments_before_wildcard = path_segments[0..wildcard_index - 1] - - segments_before_wildcard.join('/') - end - - def parameter?(segment) - segment =~ /[*:]/ - end - - # If the path is reserved. Then no conflicting paths can# be created for any - # route using this reserved word. - # - # Both `builds/artifacts` & `build` are covered by reserving the word - # `build` - def wildcards_include?(path) - described_class::PROJECT_WILDCARD_ROUTES.include?(path) || - described_class::PROJECT_WILDCARD_ROUTES.include?(path.split('/').first) - end - - def failure_message(missing_words, constant_name, migration_helper) - missing_words = Array(missing_words) - <<-MSG - Found new routes that could cause conflicts with existing namespaced routes - for groups or projects. - - Add <#{missing_words.join(', ')}> to `Gitlab::Regex::#{constant_name} - to make sure no projects or namespaces can be created with those paths. - - To rename any existing records with those paths you can use the - `Gitlab::Database::RenameReservedpathsMigration::<VERSION>.#{migration_helper}` - migration helper. - - Make sure to make a note of the renamed records in the release blog post. - - MSG - end - - let(:all_routes) do - route_set = Rails.application.routes - routes_collection = route_set.routes - routes_array = routes_collection.routes - routes_array.map { |route| route.path.spec.to_s } - end - - let(:routes_without_format) { all_routes.map { |path| without_format(path) } } - - # Routes not starting with `/:` or `/*` - # all routes not starting with a param - let(:routes_not_starting_in_wildcard) { routes_without_format.select { |p| p !~ %r{^/[:*]} } } - - let(:top_level_words) do - routes_not_starting_in_wildcard.map do |route| - route.split('/')[1] - end.compact.uniq - end - - # All routes that start with a namespaced path, that have 1 or more - # path-segments before having another wildcard parameter. - # - Starting with paths: - # - `/*namespace_id/:project_id/` - # - `/*namespace_id/:id/` - # - Followed by one or more path-parts not starting with `:` or `*` - # - Followed by a path-part that includes a wildcard parameter `*` - # At the time of writing these routes match: http://rubular.com/r/Rv2pDE5Dvw - STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id} - NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*} - ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*} - WILDCARD_SEGMENT = %r{\*} - let(:namespaced_wildcard_routes) do - routes_without_format.select do |p| - p =~ %r{#{STARTING_WITH_NAMESPACE}/#{NON_PARAM_PARTS}/#{ANY_OTHER_PATH_PART}#{WILDCARD_SEGMENT}} - end - end - - # This will return all paths that are used in a namespaced route - # before another wildcard path: - # - # /*namespace_id/:project_id/builds/artifacts/*ref_name_and_path - # /*namespace_id/:project_id/info/lfs/objects/*oid - # /*namespace_id/:project_id/commits/*id - # /*namespace_id/:project_id/builds/:build_id/artifacts/file/*path - # -> ['builds/artifacts', 'info/lfs/objects', 'commits', 'artifacts/file'] - let(:all_wildcard_paths) do - namespaced_wildcard_routes.map do |route| - path_before_wildcard(route) - end.uniq - end - - STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/} - let(:group_routes) do - routes_without_format.select do |path| - path =~ STARTING_WITH_GROUP - end - end - - let(:paths_after_group_id) do - group_routes.map do |route| - route.gsub(STARTING_WITH_GROUP, '').split('/').first - end.uniq - end - - describe 'TOP_LEVEL_ROUTES' do - it 'includes all the top level namespaces' do - failure_block = lambda do - missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES - failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths') - end - - expect(described_class::TOP_LEVEL_ROUTES) - .to include(*top_level_words), failure_block - end - end - - describe 'GROUP_ROUTES' do - it "don't contain a second wildcard" do - failure_block = lambda do - missing_words = paths_after_group_id - described_class::GROUP_ROUTES - failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths') - end - - expect(described_class::GROUP_ROUTES) - .to include(*paths_after_group_id), failure_block - end - end - - describe 'PROJECT_WILDCARD_ROUTES' do - it 'includes all paths that can be used after a namespace/project path' do - aggregate_failures do - all_wildcard_paths.each do |path| - expect(wildcards_include?(path)) - .to be(true), failure_message(path, 'PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths') - end - end - end - end - - describe '.root_namespace_path_regex' do - subject { described_class.root_namespace_path_regex } - - it 'rejects top level routes' do - expect(subject).not_to match('admin/') - expect(subject).not_to match('api/') - expect(subject).not_to match('.well-known/') - end - - it 'accepts project wildcard routes' do - expect(subject).to match('blob/') - expect(subject).to match('edit/') - expect(subject).to match('wikis/') - end - - it 'accepts group routes' do - expect(subject).to match('activity/') - expect(subject).to match('group_members/') - expect(subject).to match('subgroups/') - end - - it 'is not case sensitive' do - expect(subject).not_to match('Users/') - end - - it 'does not allow extra slashes' do - expect(subject).not_to match('/blob/') - expect(subject).not_to match('blob//') - end - end - - describe '.full_namespace_path_regex' do - subject { described_class.full_namespace_path_regex } - - context 'at the top level' do - context 'when the final level' do - it 'rejects top level routes' do - expect(subject).not_to match('admin/') - expect(subject).not_to match('api/') - expect(subject).not_to match('.well-known/') - end - - it 'accepts project wildcard routes' do - expect(subject).to match('blob/') - expect(subject).to match('edit/') - expect(subject).to match('wikis/') - end - - it 'accepts group routes' do - expect(subject).to match('activity/') - expect(subject).to match('group_members/') - expect(subject).to match('subgroups/') - end - end - - context 'when more levels follow' do - it 'rejects top level routes' do - expect(subject).not_to match('admin/more/') - expect(subject).not_to match('api/more/') - expect(subject).not_to match('.well-known/more/') - end - - it 'accepts project wildcard routes' do - expect(subject).to match('blob/more/') - expect(subject).to match('edit/more/') - expect(subject).to match('wikis/more/') - expect(subject).to match('environments/folders/') - expect(subject).to match('info/lfs/objects/') - end - - it 'accepts group routes' do - expect(subject).to match('activity/more/') - expect(subject).to match('group_members/more/') - expect(subject).to match('subgroups/more/') - end - end - end - - context 'at the second level' do - context 'when the final level' do - it 'accepts top level routes' do - expect(subject).to match('root/admin/') - expect(subject).to match('root/api/') - expect(subject).to match('root/.well-known/') - end - - it 'rejects project wildcard routes' do - expect(subject).not_to match('root/blob/') - expect(subject).not_to match('root/edit/') - expect(subject).not_to match('root/wikis/') - expect(subject).not_to match('root/environments/folders/') - expect(subject).not_to match('root/info/lfs/objects/') - end - - it 'rejects group routes' do - expect(subject).not_to match('root/activity/') - expect(subject).not_to match('root/group_members/') - expect(subject).not_to match('root/subgroups/') - end - end - - context 'when more levels follow' do - it 'accepts top level routes' do - expect(subject).to match('root/admin/more/') - expect(subject).to match('root/api/more/') - expect(subject).to match('root/.well-known/more/') - end - - it 'rejects project wildcard routes' do - expect(subject).not_to match('root/blob/more/') - expect(subject).not_to match('root/edit/more/') - expect(subject).not_to match('root/wikis/more/') - expect(subject).not_to match('root/environments/folders/more/') - expect(subject).not_to match('root/info/lfs/objects/more/') - end - - it 'rejects group routes' do - expect(subject).not_to match('root/activity/more/') - expect(subject).not_to match('root/group_members/more/') - expect(subject).not_to match('root/subgroups/more/') - end - end - end - - it 'is not case sensitive' do - expect(subject).not_to match('root/Blob/') - end - - it 'does not allow extra slashes' do - expect(subject).not_to match('/root/admin/') - expect(subject).not_to match('root/admin//') - end - end - - describe '.project_path_regex' do - subject { described_class.project_path_regex } - - it 'accepts top level routes' do - expect(subject).to match('admin/') - expect(subject).to match('api/') - expect(subject).to match('.well-known/') - end - - it 'rejects project wildcard routes' do - expect(subject).not_to match('blob/') - expect(subject).not_to match('edit/') - expect(subject).not_to match('wikis/') - expect(subject).not_to match('environments/folders/') - expect(subject).not_to match('info/lfs/objects/') - end - - it 'accepts group routes' do - expect(subject).to match('activity/') - expect(subject).to match('group_members/') - expect(subject).to match('subgroups/') - end - - it 'is not case sensitive' do - expect(subject).not_to match('Blob/') - end - - it 'does not allow extra slashes' do - expect(subject).not_to match('/admin/') - expect(subject).not_to match('admin//') - end - end - - describe '.full_project_path_regex' do - subject { described_class.full_project_path_regex } - - it 'accepts top level routes' do - expect(subject).to match('root/admin/') - expect(subject).to match('root/api/') - expect(subject).to match('root/.well-known/') - end - - it 'rejects project wildcard routes' do - expect(subject).not_to match('root/blob/') - expect(subject).not_to match('root/edit/') - expect(subject).not_to match('root/wikis/') - expect(subject).not_to match('root/environments/folders/') - expect(subject).not_to match('root/info/lfs/objects/') - end - - it 'accepts group routes' do - expect(subject).to match('root/activity/') - expect(subject).to match('root/group_members/') - expect(subject).to match('root/subgroups/') - end - - it 'is not case sensitive' do - expect(subject).not_to match('root/Blob/') - end - - it 'does not allow extra slashes' do - expect(subject).not_to match('/root/admin/') - expect(subject).not_to match('root/admin//') - end - end - - describe '.namespace_regex' do - subject { described_class.namespace_regex } - - it { is_expected.to match('gitlab-ce') } - it { is_expected.to match('gitlab_git') } - it { is_expected.to match('_underscore.js') } - it { is_expected.to match('100px.com') } - it { is_expected.to match('gitlab.org') } - it { is_expected.not_to match('?gitlab') } - it { is_expected.not_to match('git lab') } - it { is_expected.not_to match('gitlab.git') } - it { is_expected.not_to match('gitlab.org.') } - it { is_expected.not_to match('gitlab.org/') } - it { is_expected.not_to match('/gitlab.org') } - it { is_expected.not_to match('gitlab git') } - end - - describe '.project_path_format_regex' do - subject { described_class.project_path_format_regex } - - it { is_expected.to match('gitlab-ce') } - it { is_expected.to match('gitlab_git') } - it { is_expected.to match('_underscore.js') } - it { is_expected.to match('100px.com') } - it { is_expected.not_to match('?gitlab') } - it { is_expected.not_to match('git lab') } - it { is_expected.not_to match('gitlab.git') } - end - describe '.project_name_regex' do subject { described_class.project_name_regex } @@ -412,16 +32,4 @@ describe Gitlab::Regex, lib: true do it { is_expected.not_to match('9foo') } it { is_expected.not_to match('foo-') } end - - describe '.full_namespace_regex' do - subject { described_class.full_namespace_regex } - - it { is_expected.to match('gitlab.org') } - it { is_expected.to match('gitlab.org/gitlab-git') } - it { is_expected.not_to match('gitlab.org.') } - it { is_expected.not_to match('gitlab.org/') } - it { is_expected.not_to match('/gitlab.org') } - it { is_expected.not_to match('gitlab.git') } - it { is_expected.not_to match('gitlab git') } - end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 2515fc3b00a..00e3b8e5689 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -965,7 +965,7 @@ describe Ci::Pipeline, models: true do end before do - ProjectWebHookWorker.drain + WebHookWorker.drain end context 'with pipeline hooks enabled' do diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb index 8571e85627c..f3e148f95f0 100644 --- a/spec/models/concerns/discussion_on_diff_spec.rb +++ b/spec/models/concerns/discussion_on_diff_spec.rb @@ -21,4 +21,30 @@ describe DiscussionOnDiff, model: true do end end end + + describe '#line_code_in_diffs' do + context 'when the discussion is active in the diff' do + let(:diff_refs) { subject.position.diff_refs } + + it 'returns the current line code' do + expect(subject.line_code_in_diffs(diff_refs)).to eq(subject.line_code) + end + end + + context 'when the discussion was created in the diff' do + let(:diff_refs) { subject.original_position.diff_refs } + + it 'returns the original line code' do + expect(subject.line_code_in_diffs(diff_refs)).to eq(subject.original_line_code) + end + end + + context 'when the discussion is unrelated to the diff' do + let(:diff_refs) { subject.project.commit(RepoHelpers.sample_commit.id).diff_refs } + + it 'returns nil' do + expect(subject.line_code_in_diffs(diff_refs)).to be_nil + end + end + end end diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb index 81f338745b1..45b2f6e4beb 100644 --- a/spec/models/diff_discussion_spec.rb +++ b/spec/models/diff_discussion_spec.rb @@ -48,7 +48,7 @@ describe DiffDiscussion, model: true do end it 'returns the diff ID for the version to show' do - expect(diff_id: merge_request_diff1.id) + expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff1.id) end end @@ -65,6 +65,11 @@ describe DiffDiscussion, model: true do let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position) } + before do + diff_note.position = diff_note.original_position + diff_note.save! + end + it 'returns the diff ID and start sha of the versions to compare' do expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha) end diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb index 1a83c836652..57454d2a773 100644 --- a/spec/models/hooks/service_hook_spec.rb +++ b/spec/models/hooks/service_hook_spec.rb @@ -1,36 +1,19 @@ -require "spec_helper" +require 'spec_helper' describe ServiceHook, models: true do - describe "Associations" do + describe 'associations' do it { is_expected.to belong_to :service } end - describe "execute" do - before(:each) do - @service_hook = create(:service_hook) - @data = { project_id: 1, data: {} } + describe 'execute' do + let(:hook) { build(:service_hook) } + let(:data) { { key: 'value' } } - WebMock.stub_request(:post, @service_hook.url) - end - - it "POSTs to the webhook URL" do - @service_hook.execute(@data) - expect(WebMock).to have_requested(:post, @service_hook.url).with( - headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Service Hook' } - ).once - end - - it "POSTs the data as JSON" do - @service_hook.execute(@data) - expect(WebMock).to have_requested(:post, @service_hook.url).with( - headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Service Hook' } - ).once - end - - it "catches exceptions" do - expect(WebHook).to receive(:post).and_raise("Some HTTP Post error") + it '#execute' do + expect(WebHookService).to receive(:new).with(hook, data, 'service_hook').and_call_original + expect_any_instance_of(WebHookService).to receive(:execute) - expect { @service_hook.execute(@data) }.to raise_error(RuntimeError) + hook.execute(data) end end end diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index 4340170888d..0d2b622132e 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -126,4 +126,26 @@ describe SystemHook, models: true do expect(SystemHook.repository_update_hooks).to eq([hook]) end end + + describe 'execute WebHookService' do + let(:hook) { build(:system_hook) } + let(:data) { { key: 'value' } } + let(:hook_name) { 'system_hook' } + + before do + expect(WebHookService).to receive(:new).with(hook, data, hook_name).and_call_original + end + + it '#execute' do + expect_any_instance_of(WebHookService).to receive(:execute) + + hook.execute(data, hook_name) + end + + it '#async_execute' do + expect_any_instance_of(WebHookService).to receive(:async_execute) + + hook.async_execute(data, hook_name) + end + end end diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb new file mode 100644 index 00000000000..c649cf3b589 --- /dev/null +++ b/spec/models/hooks/web_hook_log_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +describe WebHookLog, models: true do + it { is_expected.to belong_to(:web_hook) } + + it { is_expected.to serialize(:request_headers).as(Hash) } + it { is_expected.to serialize(:request_data).as(Hash) } + it { is_expected.to serialize(:response_headers).as(Hash) } + + it { is_expected.to validate_presence_of(:web_hook) } + + describe '#success?' do + let(:web_hook_log) { build(:web_hook_log, response_status: status) } + + describe '2xx' do + let(:status) { '200' } + it { expect(web_hook_log.success?).to be_truthy } + end + + describe 'not 2xx' do + let(:status) { '500' } + it { expect(web_hook_log.success?).to be_falsey } + end + + describe 'internal erorr' do + let(:status) { 'internal error' } + it { expect(web_hook_log.success?).to be_falsey } + end + end +end diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 9d4db1bfb52..53157c24477 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -1,89 +1,54 @@ require 'spec_helper' describe WebHook, models: true do - describe "Validations" do + let(:hook) { build(:project_hook) } + + describe 'associations' do + it { is_expected.to have_many(:web_hook_logs).dependent(:destroy) } + end + + describe 'validations' do it { is_expected.to validate_presence_of(:url) } describe 'url' do - it { is_expected.to allow_value("http://example.com").for(:url) } - it { is_expected.to allow_value("https://example.com").for(:url) } - it { is_expected.to allow_value(" https://example.com ").for(:url) } - it { is_expected.to allow_value("http://test.com/api").for(:url) } - it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) } - it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) } + it { is_expected.to allow_value('http://example.com').for(:url) } + it { is_expected.to allow_value('https://example.com').for(:url) } + it { is_expected.to allow_value(' https://example.com ').for(:url) } + it { is_expected.to allow_value('http://test.com/api').for(:url) } + it { is_expected.to allow_value('http://test.com/api?key=abc').for(:url) } + it { is_expected.to allow_value('http://test.com/api?key=abc&type=def').for(:url) } - it { is_expected.not_to allow_value("example.com").for(:url) } - it { is_expected.not_to allow_value("ftp://example.com").for(:url) } - it { is_expected.not_to allow_value("herp-and-derp").for(:url) } + it { is_expected.not_to allow_value('example.com').for(:url) } + it { is_expected.not_to allow_value('ftp://example.com').for(:url) } + it { is_expected.not_to allow_value('herp-and-derp').for(:url) } it 'strips :url before saving it' do - hook = create(:project_hook, url: ' https://example.com ') + hook.url = ' https://example.com ' + hook.save expect(hook.url).to eq('https://example.com') end end end - describe "execute" do - let(:project) { create(:empty_project) } - let(:project_hook) { create(:project_hook) } - - before(:each) do - project.hooks << [project_hook] - @data = { before: 'oldrev', after: 'newrev', ref: 'ref' } - - WebMock.stub_request(:post, project_hook.url) - end - - context 'when token is defined' do - let(:project_hook) { create(:project_hook, :token) } - - it 'POSTs to the webhook URL' do - project_hook.execute(@data, 'push_hooks') - expect(WebMock).to have_requested(:post, project_hook.url).with( - headers: { 'Content-Type' => 'application/json', - 'X-Gitlab-Event' => 'Push Hook', - 'X-Gitlab-Token' => project_hook.token } - ).once - end - end - - it "POSTs to the webhook URL" do - project_hook.execute(@data, 'push_hooks') - expect(WebMock).to have_requested(:post, project_hook.url).with( - headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' } - ).once - end - - it "POSTs the data as JSON" do - project_hook.execute(@data, 'push_hooks') - expect(WebMock).to have_requested(:post, project_hook.url).with( - headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' } - ).once - end - - it "catches exceptions" do - expect(WebHook).to receive(:post).and_raise("Some HTTP Post error") - - expect { project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError) - end - - it "handles SSL exceptions" do - expect(WebHook).to receive(:post).and_raise(OpenSSL::SSL::SSLError.new('SSL error')) + describe 'execute' do + let(:data) { { key: 'value' } } + let(:hook_name) { 'project hook' } - expect(project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error']) + before do + expect(WebHookService).to receive(:new).with(hook, data, hook_name).and_call_original end - it "handles 200 status code" do - WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "Success") + it '#execute' do + expect_any_instance_of(WebHookService).to receive(:execute) - expect(project_hook.execute(@data, 'push_hooks')).to eq([200, 'Success']) + hook.execute(data, hook_name) end - it "handles 2xx status codes" do - WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: "Success") + it '#async_execute' do + expect_any_instance_of(WebHookService).to receive(:async_execute) - expect(project_hook.execute(@data, 'push_hooks')).to eq([201, 'Success']) + hook.async_execute(data, hook_name) end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index e89b707fae7..3645964b09c 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1213,7 +1213,7 @@ describe MergeRequest, models: true do expect(Notes::DiffPositionUpdateService).to receive(:new).with( subject.project, - nil, + subject.author, old_diff_refs: old_diff_refs, new_diff_refs: commit.diff_refs, paths: note.position.paths @@ -1222,7 +1222,7 @@ describe MergeRequest, models: true do expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note) expect_any_instance_of(DiffNote).to receive(:save).once - subject.reload_diff + subject.reload_diff(subject.author) end end @@ -1540,4 +1540,36 @@ describe MergeRequest, models: true do it { expect(subject.base_pipeline).to eq(pipeline) } end + + describe '#version_params_for' do + subject { create(:merge_request, importing: true) } + let(:project) { subject.project } + let!(:merge_request_diff1) { subject.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } + let!(:merge_request_diff2) { subject.merge_request_diffs.create(head_commit_sha: nil) } + let!(:merge_request_diff3) { subject.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + + context 'when the diff refs are for an older merge request version' do + let(:diff_refs) { merge_request_diff1.diff_refs } + + it 'returns the diff ID for the version to show' do + expect(subject.version_params_for(diff_refs)).to eq(diff_id: merge_request_diff1.id) + end + end + + context 'when the diff refs are for a comparison between merge request versions' do + let(:diff_refs) { merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs } + + it 'returns the diff ID and start sha of the versions to compare' do + expect(subject.version_params_for(diff_refs)).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha) + end + end + + context 'when the diff refs are not for a merge request version' do + let(:diff_refs) { project.commit(sample_commit.id).diff_refs } + + it 'returns nil' do + expect(subject.version_params_for(diff_refs)).to be_nil + end + end + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 312302afdbb..ff5e7c350aa 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -238,8 +238,8 @@ describe Namespace, models: true do end context 'in sub-groups' do - let(:parent) { create(:namespace, path: 'parent') } - let(:child) { create(:namespace, parent: parent, path: 'child') } + let(:parent) { create(:group, path: 'parent') } + let(:child) { create(:group, parent: parent, path: 'child') } let!(:project) { create(:project_empty_repo, namespace: child) } let(:path_in_dir) { File.join(repository_storage_path, 'parent', 'child') } let(:deleted_path) { File.join('parent', "child+#{child.id}+deleted") } diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index c1c2f2a7219..0dcf4a4b5d6 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -13,7 +13,7 @@ describe KubernetesService, models: true, caching: true do let(:discovery_url) { service.api_url + '/api/v1' } let(:discovery_response) { { body: kube_discovery_body.to_json } } - let(:pods_url) { service.api_url + "/api/v1/namespaces/#{service.namespace}/pods" } + let(:pods_url) { service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods" } let(:pods_response) { { body: kube_pods_body(kube_pod).to_json } } def stub_kubeclient_discover @@ -100,7 +100,35 @@ describe KubernetesService, models: true, caching: true do it 'sets the namespace to the default' do expect(kube_namespace).not_to be_nil - expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/) + expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/) + end + end + end + + describe '#actual_namespace' do + subject { service.actual_namespace } + + it "returns the default namespace" do + is_expected.to eq(service.send(:default_namespace)) + end + + context 'when namespace is specified' do + before do + service.namespace = 'my-namespace' + end + + it "returns the user-namespace" do + is_expected.to eq('my-namespace') + end + end + + context 'when service is not assigned to project' do + before do + service.project = nil + end + + it "does not return namespace" do + is_expected.to be_nil end end end @@ -187,13 +215,14 @@ describe KubernetesService, models: true, caching: true do kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' } expect(kube_namespace).not_to be_nil - expect(kube_namespace[:value]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/) + expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/) end end end describe '#terminals' do let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") } + subject { service.terminals(environment) } context 'with invalid pods' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 6a15830a15c..aabdac4bb75 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -440,6 +440,22 @@ describe User, models: true do end end + describe 'ensure incoming email token' do + it 'has incoming email token' do + user = create(:user) + expect(user.incoming_email_token).not_to be_blank + end + end + + describe 'rss token' do + it 'ensures an rss token on read' do + user = create(:user, rss_token: nil) + rss_token = user.rss_token + expect(rss_token).not_to be_blank + expect(user.reload.rss_token).to eq rss_token + end + end + describe '#recently_sent_password_reset?' do it 'is false when reset_password_sent_at is nil' do user = build_stubbed(:user, reset_password_sent_at: nil) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 4919ad19833..a2503dbeb69 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -287,7 +287,7 @@ describe API::Users do expect(json_response['message']['projects_limit']). to eq(['must be greater than or equal to 0']) expect(json_response['message']['username']). - to eq([Gitlab::Regex.namespace_regex_message]) + to eq([Gitlab::PathRegex.namespace_format_message]) end it "is not available for non admin users" do @@ -459,7 +459,7 @@ describe API::Users do expect(json_response['message']['projects_limit']). to eq(['must be greater than or equal to 0']) expect(json_response['message']['username']). - to eq([Gitlab::Regex.namespace_regex_message]) + to eq([Gitlab::PathRegex.namespace_format_message]) end it 'returns 400 if provider is missing for identity update' do diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb index e5fc0b676af..179fc9733ad 100644 --- a/spec/routing/admin_routing_spec.rb +++ b/spec/routing/admin_routing_spec.rb @@ -103,6 +103,18 @@ describe Admin::HooksController, "routing" do end end +# admin_hook_hook_log_retry GET /admin/hooks/:hook_id/hook_logs/:id/retry(.:format) admin/hook_logs#retry +# admin_hook_hook_log GET /admin/hooks/:hook_id/hook_logs/:id(.:format) admin/hook_logs#show +describe Admin::HookLogsController, 'routing' do + it 'to #retry' do + expect(get('/admin/hooks/1/hook_logs/1/retry')).to route_to('admin/hook_logs#retry', hook_id: '1', id: '1') + end + + it 'to #show' do + expect(get('/admin/hooks/1/hook_logs/1')).to route_to('admin/hook_logs#show', hook_id: '1', id: '1') + end +end + # admin_logs GET /admin/logs(.:format) admin/logs#show describe Admin::LogsController, "routing" do it "to #show" do diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index a391c046f92..54417f6b3e1 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -349,6 +349,18 @@ describe 'project routing' do end end + # retry_namespace_project_hook_hook_log GET /:project_id/hooks/:hook_id/hook_logs/:id/retry(.:format) projects/hook_logs#retry + # namespace_project_hook_hook_log GET /:project_id/hooks/:hook_id/hook_logs/:id(.:format) projects/hook_logs#show + describe Projects::HookLogsController, 'routing' do + it 'to #retry' do + expect(get('/gitlab/gitlabhq/hooks/1/hook_logs/1/retry')).to route_to('projects/hook_logs#retry', namespace_id: 'gitlab', project_id: 'gitlabhq', hook_id: '1', id: '1') + end + + it 'to #show' do + expect(get('/gitlab/gitlabhq/hooks/1/hook_logs/1')).to route_to('projects/hook_logs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', hook_id: '1', id: '1') + end + end + # project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/} describe Projects::CommitController, 'routing' do it 'to #show' do diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index abacc50a371..a62af13cf0c 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -151,6 +151,10 @@ describe ProfilesController, "routing" do expect(put("/profile/reset_private_token")).to route_to('profiles#reset_private_token') end + it "to #reset_rss_token" do + expect(put("/profile/reset_rss_token")).to route_to('profiles#reset_rss_token') + end + it "to #show" do expect(get("/profile")).to route_to('profiles#show') end diff --git a/spec/services/notes/diff_position_update_service_spec.rb b/spec/services/notes/diff_position_update_service_spec.rb index d73ae51fbc3..380c296fd3a 100644 --- a/spec/services/notes/diff_position_update_service_spec.rb +++ b/spec/services/notes/diff_position_update_service_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe Notes::DiffPositionUpdateService, services: true do let(:project) { create(:project, :repository) } + let(:current_user) { project.owner } let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") } let(:modify_commit) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e") } let(:edit_commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") } @@ -25,7 +26,7 @@ describe Notes::DiffPositionUpdateService, services: true do subject do described_class.new( project, - nil, + current_user, old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs, paths: [path] @@ -170,6 +171,23 @@ describe Notes::DiffPositionUpdateService, services: true do expect(note.original_position).to eq(old_position) expect(note.position).to eq(old_position) end + + it 'sets the change position' do + subject.execute(note) + + change_position = note.change_position + expect(change_position.start_sha).to eq(old_diff_refs.head_sha) + expect(change_position.head_sha).to eq(new_diff_refs.head_sha) + expect(change_position.old_line).to eq(9) + expect(change_position.new_line).to be_nil + end + + it 'creates a system note' do + expect(SystemNoteService).to receive(:diff_discussion_outdated).with( + note.to_discussion, project, current_user, instance_of(Gitlab::Diff::Position)) + + subject.execute(note) + end end end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 7a9cd7553b1..5a7cfaff7fb 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -1034,4 +1034,35 @@ describe SystemNoteService, services: true do expect(subject.note).to eq 'resolved all discussions' end end + + describe '.diff_discussion_outdated' do + let(:discussion) { create(:diff_note_on_merge_request).to_discussion } + let(:merge_request) { discussion.noteable } + let(:project) { merge_request.source_project } + let(:change_position) { discussion.position } + + def reloaded_merge_request + MergeRequest.find(merge_request.id) + end + + subject { described_class.diff_discussion_outdated(discussion, project, author, change_position) } + + it_behaves_like 'a system note' do + let(:expected_noteable) { discussion.first_note.noteable } + let(:action) { 'outdated' } + end + + it 'creates a new note in the discussion' do + # we need to completely rebuild the merge request object, or the `@discussions` on the merge request are not reloaded. + expect { subject }.to change { reloaded_merge_request.discussions.first.notes.size }.by(1) + end + + it 'links to the diff in the system note' do + expect(subject.note).to include('version 1') + + diff_id = merge_request.merge_request_diff.id + line_code = change_position.line_code(project.repository) + expect(subject.note).to include(diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, diff_id: diff_id, anchor: line_code)) + end + end end diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb new file mode 100644 index 00000000000..b5abc46e80c --- /dev/null +++ b/spec/services/web_hook_service_spec.rb @@ -0,0 +1,137 @@ +require 'spec_helper' + +describe WebHookService, services: true do + let(:project) { create(:empty_project) } + let(:project_hook) { create(:project_hook) } + let(:headers) do + { + 'Content-Type' => 'application/json', + 'X-Gitlab-Event' => 'Push Hook' + } + end + let(:data) do + { before: 'oldrev', after: 'newrev', ref: 'ref' } + end + let(:service_instance) { WebHookService.new(project_hook, data, 'push_hooks') } + + describe '#execute' do + before(:each) do + project.hooks << [project_hook] + + WebMock.stub_request(:post, project_hook.url) + end + + context 'when token is defined' do + let(:project_hook) { create(:project_hook, :token) } + + it 'POSTs to the webhook URL' do + service_instance.execute + expect(WebMock).to have_requested(:post, project_hook.url).with( + headers: headers.merge({ 'X-Gitlab-Token' => project_hook.token }) + ).once + end + end + + it 'POSTs to the webhook URL' do + service_instance.execute + expect(WebMock).to have_requested(:post, project_hook.url).with( + headers: headers + ).once + end + + it 'POSTs the data as JSON' do + service_instance.execute + expect(WebMock).to have_requested(:post, project_hook.url).with( + headers: headers + ).once + end + + it 'catches exceptions' do + WebMock.stub_request(:post, project_hook.url).to_raise(StandardError.new('Some error')) + + expect { service_instance.execute }.to raise_error(StandardError) + end + + it 'handles exceptions' do + exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout] + exceptions.each do |exception_class| + exception = exception_class.new('Exception message') + + WebMock.stub_request(:post, project_hook.url).to_raise(exception) + expect(service_instance.execute).to eq([nil, exception.message]) + expect { service_instance.execute }.not_to raise_error + end + end + + it 'handles 200 status code' do + WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success') + + expect(service_instance.execute).to eq([200, 'Success']) + end + + it 'handles 2xx status codes' do + WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: 'Success') + + expect(service_instance.execute).to eq([201, 'Success']) + end + + context 'execution logging' do + let(:hook_log) { project_hook.web_hook_logs.last } + + context 'with success' do + before do + WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success') + service_instance.execute + end + + it 'log successful execution' do + expect(hook_log.trigger).to eq('push_hooks') + expect(hook_log.url).to eq(project_hook.url) + expect(hook_log.request_headers).to eq(headers) + expect(hook_log.response_body).to eq('Success') + expect(hook_log.response_status).to eq('200') + expect(hook_log.execution_duration).to be > 0 + expect(hook_log.internal_error_message).to be_nil + end + end + + context 'with exception' do + before do + WebMock.stub_request(:post, project_hook.url).to_raise(SocketError.new('Some HTTP Post error')) + service_instance.execute + end + + it 'log failed execution' do + expect(hook_log.trigger).to eq('push_hooks') + expect(hook_log.url).to eq(project_hook.url) + expect(hook_log.request_headers).to eq(headers) + expect(hook_log.response_body).to eq('') + expect(hook_log.response_status).to eq('internal error') + expect(hook_log.execution_duration).to be > 0 + expect(hook_log.internal_error_message).to eq('Some HTTP Post error') + end + end + + context 'should not log ServiceHooks' do + let(:service_hook) { create(:service_hook) } + let(:service_instance) { WebHookService.new(service_hook, data, 'service_hook') } + + before do + WebMock.stub_request(:post, service_hook.url).to_return(status: 200, body: 'Success') + end + + it { expect { service_instance.execute }.not_to change(WebHookLog, :count) } + end + end + end + + describe '#async_execute' do + let(:system_hook) { create(:system_hook) } + + it 'enqueue WebHookWorker' do + expect(Sidekiq::Client).to receive(:enqueue).with(WebHookWorker, project_hook.id, data, 'push_hooks') + + WebHookService.new(project_hook, data, 'push_hooks').async_execute + end + end +end diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb index c59b30c772d..d6b40db09ce 100644 --- a/spec/support/controllers/githubish_import_controller_shared_examples.rb +++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb @@ -209,9 +209,13 @@ shared_examples 'a GitHub-ish import controller: POST create' do end context 'user has chosen a namespace and name for the project' do - let(:test_namespace) { create(:namespace, name: 'test_namespace', owner: user) } + let(:test_namespace) { create(:group, name: 'test_namespace') } let(:test_name) { 'test_name' } + before do + test_namespace.add_owner(user) + end + it 'takes the selected namespace and name' do expect(Gitlab::GithubImport::ProjectCreator). to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider). @@ -230,10 +234,14 @@ shared_examples 'a GitHub-ish import controller: POST create' do end context 'user has chosen an existing nested namespace and name for the project' do - let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) } - let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) } + let(:parent_namespace) { create(:group, name: 'foo', owner: user) } + let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:test_name) { 'test_name' } + before do + nested_namespace.add_owner(user) + end + it 'takes the selected namespace and name' do expect(Gitlab::GithubImport::ProjectCreator). to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider). @@ -276,7 +284,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do context 'user has chosen existent and non-existent nested namespaces and name for the project' do let(:test_name) { 'test_name' } - let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) } + let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } it 'takes the selected namespace and name' do expect(Gitlab::GithubImport::ProjectCreator). diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb index 9a3b0a731ad..1cbb4134995 100644 --- a/spec/support/features/rss_shared_examples.rb +++ b/spec/support/features/rss_shared_examples.rb @@ -1,23 +1,23 @@ -shared_examples "an autodiscoverable RSS feed with current_user's private token" do - it "has an RSS autodiscovery link tag with current_user's private token" do - expect(page).to have_css("link[type*='atom+xml'][href*='private_token=#{Thread.current[:current_user].private_token}']", visible: false) +shared_examples "an autodiscoverable RSS feed with current_user's RSS token" do + it "has an RSS autodiscovery link tag with current_user's RSS token" do + expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{Thread.current[:current_user].rss_token}']", visible: false) end end -shared_examples "it has an RSS button with current_user's private token" do - it "shows the RSS button with current_user's private token" do - expect(page).to have_css("a:has(.fa-rss)[href*='private_token=#{Thread.current[:current_user].private_token}']") +shared_examples "it has an RSS button with current_user's RSS token" do + it "shows the RSS button with current_user's RSS token" do + expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{Thread.current[:current_user].rss_token}']") end end -shared_examples "an autodiscoverable RSS feed without a private token" do - it "has an RSS autodiscovery link tag without a private token" do - expect(page).to have_css("link[type*='atom+xml']:not([href*='private_token'])", visible: false) +shared_examples "an autodiscoverable RSS feed without an RSS token" do + it "has an RSS autodiscovery link tag without an RSS token" do + expect(page).to have_css("link[type*='atom+xml']:not([href*='rss_token'])", visible: false) end end -shared_examples "it has an RSS button without a private token" do - it "shows the RSS button without a private token" do - expect(page).to have_css("a:has(.fa-rss):not([href*='private_token'])") +shared_examples "it has an RSS button without an RSS token" do + it "shows the RSS button without an RSS token" do + expect(page).to have_css("a:has(.fa-rss):not([href*='rss_token'])") end end diff --git a/spec/support/kubernetes_helpers.rb b/spec/support/kubernetes_helpers.rb index d2a1ded57ff..9280fad4ace 100644 --- a/spec/support/kubernetes_helpers.rb +++ b/spec/support/kubernetes_helpers.rb @@ -41,7 +41,7 @@ module KubernetesHelpers containers.map do |container| terminal = { selectors: { pod: pod_name, container: container['name'] }, - url: container_exec_url(service.api_url, service.namespace, pod_name, container['name']), + url: container_exec_url(service.api_url, service.actual_namespace, pod_name, container['name']), subprotocols: ['channel.k8s.io'], headers: { 'Authorization' => ["Bearer #{service.token}"] }, created_at: DateTime.parse(pod['metadata']['creationTimestamp']), diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb index 19036c7677c..b84137eb365 100644 --- a/spec/tasks/tokens_spec.rb +++ b/spec/tasks/tokens_spec.rb @@ -18,4 +18,10 @@ describe 'tokens rake tasks' do expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token } end end + + describe 'reset_all_rss task' do + it 'invokes create_hooks task' do + expect { run_rake_task('tokens:reset_all_rss') }.to change { user.reload.rss_token } + end + end end diff --git a/spec/validators/dynamic_path_validator_spec.rb b/spec/validators/dynamic_path_validator_spec.rb index 03e23781d1b..5f998e78f07 100644 --- a/spec/validators/dynamic_path_validator_spec.rb +++ b/spec/validators/dynamic_path_validator_spec.rb @@ -15,31 +15,31 @@ describe DynamicPathValidator do end context 'for group' do - it 'calls valid_namespace_path?' do + it 'calls valid_group_path?' do group = build(:group, :nested, path: 'activity') - expect(described_class).to receive(:valid_namespace_path?).with(group.full_path).and_call_original + expect(described_class).to receive(:valid_group_path?).with(group.full_path).and_call_original expect(validator.path_valid_for_record?(group, 'activity')).to be_falsey end end context 'for user' do - it 'calls valid_namespace_path?' do + it 'calls valid_user_path?' do user = build(:user, username: 'activity') - expect(described_class).to receive(:valid_namespace_path?).with(user.full_path).and_call_original + expect(described_class).to receive(:valid_user_path?).with(user.full_path).and_call_original expect(validator.path_valid_for_record?(user, 'activity')).to be_truthy end end context 'for user namespace' do - it 'calls valid_namespace_path?' do + it 'calls valid_user_path?' do user = create(:user, username: 'activity') namespace = user.namespace - expect(described_class).to receive(:valid_namespace_path?).with(namespace.full_path).and_call_original + expect(described_class).to receive(:valid_user_path?).with(namespace.full_path).and_call_original expect(validator.path_valid_for_record?(namespace, 'activity')).to be_truthy end @@ -52,7 +52,7 @@ describe DynamicPathValidator do validator.validate_each(group, :path, "Path with spaces, and comma's!") - expect(group.errors[:path]).to include(Gitlab::Regex.namespace_regex_message) + expect(group.errors[:path]).to include(Gitlab::PathRegex.namespace_format_message) end it 'adds a message when the path is not in the correct format' do diff --git a/spec/workers/remove_old_web_hook_logs_worker_spec.rb b/spec/workers/remove_old_web_hook_logs_worker_spec.rb new file mode 100644 index 00000000000..6d26ba5dfa0 --- /dev/null +++ b/spec/workers/remove_old_web_hook_logs_worker_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe RemoveOldWebHookLogsWorker do + subject { described_class.new } + + describe '#perform' do + let!(:week_old_record) { create(:web_hook_log, created_at: Time.now - 1.week) } + let!(:three_days_old_record) { create(:web_hook_log, created_at: Time.now - 3.days) } + let!(:one_day_old_record) { create(:web_hook_log, created_at: Time.now - 1.day) } + + it 'removes web hook logs older than 2 days' do + subject.perform + + expect(WebHookLog.all).to include(one_day_old_record) + expect(WebHookLog.all).not_to include(week_old_record, three_days_old_record) + end + end +end |