diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-07 21:10:08 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-07 21:10:08 +0000 |
commit | 3a966afb3ea2ef7a98bdc389e0dc906ef4bf0273 (patch) | |
tree | e22ca72e41a6d2eaca58ac9cc1390e5f8114ac1f /spec | |
parent | 39d41e02dca2139d0bbd88165affd818c9c82fb6 (diff) | |
download | gitlab-ce-3a966afb3ea2ef7a98bdc389e0dc906ef4bf0273.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
20 files changed, 696 insertions, 64 deletions
diff --git a/spec/controllers/confirmations_controller_spec.rb b/spec/controllers/confirmations_controller_spec.rb new file mode 100644 index 00000000000..49a39f257fe --- /dev/null +++ b/spec/controllers/confirmations_controller_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ConfirmationsController do + include DeviseHelpers + + before do + set_devise_mapping(context: @request) + end + + describe '#show' do + render_views + + subject { get :show, params: { confirmation_token: confirmation_token } } + + context 'user is already confirmed' do + let_it_be_with_reload(:user) { create(:user, :unconfirmed) } + let(:confirmation_token) { user.confirmation_token } + + before do + user.confirm + subject + end + + it 'renders `new`' do + expect(response).to render_template(:new) + end + + it 'displays an error message' do + expect(response.body).to include('Email was already confirmed, please try signing in') + end + + it 'does not display the email of the user' do + expect(response.body).not_to include(user.email) + end + end + + context 'user accesses the link after the expiry of confirmation token has passed' do + let_it_be_with_reload(:user) { create(:user, :unconfirmed) } + let(:confirmation_token) { user.confirmation_token } + + before do + allow(Devise).to receive(:confirm_within).and_return(1.day) + + travel_to(3.days.from_now) do + subject + end + end + + it 'renders `new`' do + expect(response).to render_template(:new) + end + + it 'displays an error message' do + expect(response.body).to include('Email needs to be confirmed within 1 day, please request a new one below') + end + + it 'does not display the email of the user' do + expect(response.body).not_to include(user.email) + end + end + + context 'with an invalid confirmation token' do + let(:confirmation_token) { 'invalid_confirmation_token' } + + before do + subject + end + + it 'renders `new`' do + expect(response).to render_template(:new) + end + + it 'displays an error message' do + expect(response.body).to include('Confirmation token is invalid') + end + end + end +end diff --git a/spec/controllers/projects/feature_flags_controller_spec.rb b/spec/controllers/projects/feature_flags_controller_spec.rb index ebe964aa465..d5fc80bd5a7 100644 --- a/spec/controllers/projects/feature_flags_controller_spec.rb +++ b/spec/controllers/projects/feature_flags_controller_spec.rb @@ -1419,6 +1419,40 @@ RSpec.describe Projects::FeatureFlagsController do expect(response).to have_gitlab_http_status(:not_found) end + it 'returns not found when trying to update a gitlabUserList strategy with a user list from another project' do + user_list = create(:operations_feature_flag_user_list, project: project, name: 'My List', user_xids: 'user1,user2') + strategy = create(:operations_strategy, feature_flag: new_version_flag, name: 'gitlabUserList', parameters: {}, user_list: user_list) + other_project = create(:project) + other_user_list = create(:operations_feature_flag_user_list, project: other_project, name: 'Other List', user_xids: 'some,one') + + put_request(new_version_flag, strategies_attributes: [{ + id: strategy.id, + user_list_id: other_user_list.id + }]) + + expect(response).to have_gitlab_http_status(:not_found) + expect(strategy.reload.user_list).to eq(user_list) + end + + it 'allows setting multiple gitlabUserList strategies to the same user list' do + user_list_a = create(:operations_feature_flag_user_list, project: project, name: 'My List A', user_xids: 'user1,user2') + user_list_b = create(:operations_feature_flag_user_list, project: project, name: 'My List B', user_xids: 'user3,user4') + strategy_a = create(:operations_strategy, feature_flag: new_version_flag, name: 'gitlabUserList', parameters: {}, user_list: user_list_a) + strategy_b = create(:operations_strategy, feature_flag: new_version_flag, name: 'gitlabUserList', parameters: {}, user_list: user_list_a) + + put_request(new_version_flag, strategies_attributes: [{ + id: strategy_a.id, + user_list_id: user_list_b.id + }, { + id: strategy_b.id, + user_list_id: user_list_b.id + }]) + + expect(response).to have_gitlab_http_status(:ok) + expect(strategy_a.reload.user_list).to eq(user_list_b) + expect(strategy_b.reload.user_list).to eq(user_list_b) + end + it 'updates an existing strategy' do strategy = create(:operations_strategy, feature_flag: new_version_flag, name: 'default', parameters: {}) diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index afebc6982c1..bbd39fd4c83 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -272,7 +272,7 @@ RSpec.describe SearchController do expect(last_payload[:metadata]['meta.search.group_id']).to eq('123') expect(last_payload[:metadata]['meta.search.project_id']).to eq('456') - expect(last_payload[:metadata]['meta.search.search']).to eq('hello world') + expect(last_payload[:metadata]).not_to have_key('meta.search.search') expect(last_payload[:metadata]['meta.search.scope']).to eq('issues') expect(last_payload[:metadata]['meta.search.force_search_results']).to eq('true') expect(last_payload[:metadata]['meta.search.filters.confidential']).to eq('true') diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index cabd01bcae1..8b89ced3031 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -354,32 +354,99 @@ RSpec.describe UsersController do describe 'GET #contributed' do let(:project) { create(:project, :public) } - let(:current_user) { create(:user) } + + subject do + get :contributed, params: { username: author.username }, format: format + end before do - sign_in(current_user) + sign_in(user) project.add_developer(public_user) project.add_developer(private_user) + create(:push_event, project: project, author: author) + + subject end - context 'with public profile' do + shared_examples_for 'renders contributed projects' do it 'renders contributed projects' do - create(:push_event, project: project, author: public_user) + expect(assigns[:contributed_projects]).not_to be_empty + expect(response).to have_gitlab_http_status(:ok) + end + end - get :contributed, params: { username: public_user.username } + %i(html json).each do |format| + context "format: #{format}" do + let(:format) { format } - expect(assigns[:contributed_projects]).not_to be_empty + context 'with public profile' do + let(:author) { public_user } + + it_behaves_like 'renders contributed projects' + end + + context 'with private profile' do + let(:author) { private_user } + + it 'returns 404' do + expect(response).to have_gitlab_http_status(:not_found) + end + + context 'with a user that has the ability to read private profiles', :enable_admin_mode do + let(:user) { create(:admin) } + + it_behaves_like 'renders contributed projects' + end + end + end + end + end + + describe 'GET #starred' do + let(:project) { create(:project, :public) } + + subject do + get :starred, params: { username: author.username }, format: format + end + + before do + author.toggle_star(project) + + sign_in(user) + subject + end + + shared_examples_for 'renders starred projects' do + it 'renders starred projects' do + expect(response).to have_gitlab_http_status(:ok) + expect(assigns[:starred_projects]).not_to be_empty end end - context 'with private profile' do - it 'does not render contributed projects' do - create(:push_event, project: project, author: private_user) + %i(html json).each do |format| + context "format: #{format}" do + let(:format) { format } + + context 'with public profile' do + let(:author) { public_user } + + it_behaves_like 'renders starred projects' + end + + context 'with private profile' do + let(:author) { private_user } + + it 'returns 404' do + expect(response).to have_gitlab_http_status(:not_found) + end - get :contributed, params: { username: private_user.username } + context 'with a user that has the ability to read private profiles', :enable_admin_mode do + let(:user) { create(:admin) } - expect(assigns[:contributed_projects]).to be_empty + it_behaves_like 'renders starred projects' + end + end end end end diff --git a/spec/features/explore/user_explores_projects_spec.rb b/spec/features/explore/user_explores_projects_spec.rb index bf4d6c946e1..c082ff1fb0c 100644 --- a/spec/features/explore/user_explores_projects_spec.rb +++ b/spec/features/explore/user_explores_projects_spec.rb @@ -47,6 +47,14 @@ RSpec.describe 'User explores projects' do end end + shared_examples 'minimum search length' do + it 'shows a prompt to enter a longer search term', :js do + fill_in 'name', with: 'z' + + expect(page).to have_content('Enter at least three characters to search') + end + end + context 'when viewing public projects' do before do visit(explore_projects_path) @@ -54,6 +62,7 @@ RSpec.describe 'User explores projects' do include_examples 'shows public and internal projects' include_examples 'empty search results' + include_examples 'minimum search length' end context 'when viewing most starred projects' do @@ -63,6 +72,7 @@ RSpec.describe 'User explores projects' do include_examples 'shows public and internal projects' include_examples 'empty search results' + include_examples 'minimum search length' end context 'when viewing trending projects' do @@ -76,6 +86,7 @@ RSpec.describe 'User explores projects' do include_examples 'shows public projects' include_examples 'empty search results' + include_examples 'minimum search length' end end end diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb index 9875fda17a9..6df844277db 100644 --- a/spec/features/markdown/mermaid_spec.rb +++ b/spec/features/markdown/mermaid_spec.rb @@ -19,6 +19,9 @@ RSpec.describe 'Mermaid rendering', :js do visit project_issue_path(project, issue) + wait_for_requests + wait_for_mermaid + %w[A B C D].each do |label| expect(page).to have_selector('svg text', text: label) end @@ -39,6 +42,7 @@ RSpec.describe 'Mermaid rendering', :js do visit project_issue_path(project, issue) wait_for_requests + wait_for_mermaid expected = '<text style=""><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>' expect(page.html.scan(expected).count).to be(4) @@ -65,6 +69,9 @@ RSpec.describe 'Mermaid rendering', :js do visit project_issue_path(project, issue) + wait_for_requests + wait_for_mermaid + page.within('.description') do expect(page).to have_selector('svg') expect(page).to have_selector('pre.mermaid') @@ -92,6 +99,9 @@ RSpec.describe 'Mermaid rendering', :js do visit project_issue_path(project, issue) + wait_for_requests + wait_for_mermaid + page.within('.description') do page.find('summary').click svg = page.find('svg.mermaid') @@ -118,6 +128,9 @@ RSpec.describe 'Mermaid rendering', :js do visit project_issue_path(project, issue) + wait_for_requests + wait_for_mermaid + expect(page).to have_css('svg.mermaid[style*="max-width"][width="100%"]') end @@ -147,6 +160,7 @@ RSpec.describe 'Mermaid rendering', :js do end wait_for_requests + wait_for_mermaid find('.js-lazy-render-mermaid').click @@ -156,4 +170,55 @@ RSpec.describe 'Mermaid rendering', :js do expect(page).not_to have_selector('.js-lazy-render-mermaid-container') end end + + it 'does not render more than 50 mermaid blocks', :js, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/234081' } do + graph_edges = "A-->B;B-->A;" + + description = <<~MERMAID + ```mermaid + graph LR + #{graph_edges} + ``` + MERMAID + + description *= 51 + + project = create(:project, :public) + + issue = create(:issue, project: project, description: description) + + visit project_issue_path(project, issue) + + wait_for_requests + wait_for_mermaid + + page.within('.description') do + expect(page).to have_selector('svg') + + expect(page).to have_selector('.lazy-alert-shown') + + expect(page).to have_selector('.js-lazy-render-mermaid-container') + end + end +end + +def wait_for_mermaid + run_idle_callback = <<~RUN_IDLE_CALLBACK + window.requestIdleCallback(() => { + window.__CAPYBARA_IDLE_CALLBACK_EXEC__ = 1; + }) + RUN_IDLE_CALLBACK + + page.evaluate_script(run_idle_callback) + + Timeout.timeout(Capybara.default_max_wait_time) do + loop until finished_rendering? + end +end + +def finished_rendering? + check_idle_callback = <<~CHECK_IDLE_CALLBACK + window.__CAPYBARA_IDLE_CALLBACK_EXEC__ + CHECK_IDLE_CALLBACK + page.evaluate_script(check_idle_callback) == 1 end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index 2d712bd44ce..57977fb69b4 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -161,6 +161,29 @@ RSpec.describe ProjectsFinder, :do_not_mock_admin_mode do it { is_expected.to eq([public_project]) } end + describe 'filter by search with minimum search length' do + context 'when search term is shorter than minimum length' do + let(:params) { { search: 'C', minimum_search_length: 3 } } + + it { is_expected.to be_empty } + end + + context 'when search term is longer than minimum length' do + let(:project) { create(:project, :public, group: group, name: 'test_project') } + let(:params) { { search: 'test', minimum_search_length: 3 } } + + it { is_expected.to eq([project]) } + end + + context 'when minimum length is invalid' do + let(:params) { { search: 'C', minimum_search_length: 'x' } } + + it 'ignores the minimum length param' do + is_expected.to eq([public_project]) + end + end + end + describe 'filter by group name' do let(:params) { { name: group.name, search_namespaces: true } } diff --git a/spec/finders/starred_projects_finder_spec.rb b/spec/finders/starred_projects_finder_spec.rb index 15d4ae52ddd..f5d3314021d 100644 --- a/spec/finders/starred_projects_finder_spec.rb +++ b/spec/finders/starred_projects_finder_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe StarredProjectsFinder do let(:project1) { create(:project, :public, :empty_repo) } let(:project2) { create(:project, :public, :empty_repo) } - let(:other_project) { create(:project, :public, :empty_repo) } + let(:private_project) { create(:project, :private, :empty_repo) } let(:user) { create(:user) } let(:other_user) { create(:user) } @@ -13,6 +13,9 @@ RSpec.describe StarredProjectsFinder do before do user.toggle_star(project1) user.toggle_star(project2) + + private_project.add_maintainer(user) + user.toggle_star(private_project) end describe '#execute' do @@ -20,22 +23,56 @@ RSpec.describe StarredProjectsFinder do subject { finder.execute } - describe 'as same user' do - let(:current_user) { user } + context 'user has a public profile' do + describe 'as same user' do + let(:current_user) { user } - it { is_expected.to contain_exactly(project1, project2) } - end + it { is_expected.to contain_exactly(project1, project2, private_project) } + end + + describe 'as other user' do + let(:current_user) { other_user } - describe 'as other user' do - let(:current_user) { other_user } + it { is_expected.to contain_exactly(project1, project2) } + end - it { is_expected.to contain_exactly(project1, project2) } + describe 'as no user' do + let(:current_user) { nil } + + it { is_expected.to contain_exactly(project1, project2) } + end end - describe 'as no user' do - let(:current_user) { nil } + context 'user has a private profile' do + before do + user.update!(private_profile: true) + end + + describe 'as same user' do + let(:current_user) { user } + + it { is_expected.to contain_exactly(project1, project2, private_project) } + end + + describe 'as other user' do + context 'user does not have access to view the private profile' do + let(:current_user) { other_user } + + it { is_expected.to be_empty } + end + + context 'user has access to view the private profile', :enable_admin_mode do + let(:current_user) { create(:admin) } + + it { is_expected.to contain_exactly(project1, project2, private_project) } + end + end + + describe 'as no user' do + let(:current_user) { nil } - it { is_expected.to contain_exactly(project1, project2) } + it { is_expected.to be_empty } + end end end end diff --git a/spec/helpers/operations_helper_spec.rb b/spec/helpers/operations_helper_spec.rb index 09f9bba8f9e..801d5de79b1 100644 --- a/spec/helpers/operations_helper_spec.rb +++ b/spec/helpers/operations_helper_spec.rb @@ -21,20 +21,15 @@ RSpec.describe OperationsHelper do end context 'initial service configuration' do - let_it_be(:alerts_service) { AlertsService.new(project: project) } let_it_be(:prometheus_service) { PrometheusService.new(project: project) } before do - allow(project).to receive(:find_or_initialize_service).with('alerts').and_return(alerts_service) + allow(project).to receive(:find_or_initialize_service).and_call_original allow(project).to receive(:find_or_initialize_service).with('prometheus').and_return(prometheus_service) end it 'returns the correct values' do expect(subject).to eq( - 'activated' => 'false', - 'url' => alerts_service.url, - 'authorization_key' => nil, - 'form_path' => project_service_path(project, alerts_service), 'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'), 'alerts_usage_url' => project_alert_management_index_path(project), 'prometheus_form_path' => project_service_path(project, prometheus_service), @@ -104,33 +99,6 @@ RSpec.describe OperationsHelper do end end end - - context 'with generic alerts service configured' do - let_it_be(:alerts_service) { create(:alerts_service, project: project) } - - context 'with generic alerts enabled' do - it 'returns the correct values' do - expect(subject).to include( - 'activated' => 'true', - 'authorization_key' => alerts_service.token, - 'url' => alerts_service.url - ) - end - end - - context 'with generic alerts disabled' do - before do - alerts_service.update!(active: false) - end - - it 'returns the correct values' do - expect(subject).to include( - 'activated' => 'false', - 'authorization_key' => alerts_service.token - ) - end - end - end end describe '#operations_settings_data' do diff --git a/spec/lib/banzai/filter/ascii_doc_sanitization_filter_spec.rb b/spec/lib/banzai/filter/ascii_doc_sanitization_filter_spec.rb new file mode 100644 index 00000000000..6eeddee63d7 --- /dev/null +++ b/spec/lib/banzai/filter/ascii_doc_sanitization_filter_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Banzai::Filter::AsciiDocSanitizationFilter do + include FilterSpecHelper + + it 'preserves footnotes refs' do + result = filter('<p>This paragraph has a footnote.<sup>[<a id="_footnoteref_1" href="#_footnotedef_1" title="View footnote.">1</a>]</sup></p>').to_html + expect(result).to eq('<p>This paragraph has a footnote.<sup>[<a id="_footnoteref_1" href="#_footnotedef_1" title="View footnote.">1</a>]</sup></p>') + end + + it 'preserves footnotes defs' do + result = filter('<div id="_footnotedef_1"> +<a href="#_footnoteref_1">1</a>. This is the text of the footnote.</div>').to_html + expect(result).to eq(%(<div id="_footnotedef_1"> +<a href="#_footnoteref_1">1</a>. This is the text of the footnote.</div>)) + end + + it 'preserves user-content- prefixed ids on anchors' do + result = filter('<p><a id="user-content-cross-references"></a>A link to another location within an AsciiDoc document.</p>').to_html + expect(result).to eq(%(<p><a id="user-content-cross-references"></a>A link to another location within an AsciiDoc document.</p>)) + end + + it 'preserves user-content- prefixed ids on div (blocks)' do + html_content = <<~HTML +<div id="user-content-open-block" class="openblock"> + <div class="content"> + <div class="paragraph"> + <p>This is an open block</p> + </div> + </div> +</div> + HTML + output = <<~SANITIZED_HTML + <div id="user-content-open-block"> + <div> + <div> + <p>This is an open block</p> + </div> + </div> + </div> + SANITIZED_HTML + expect(filter(html_content).to_html).to eq(output) + end + it 'preserves section anchor ids' do + result = filter(%(<h2 id="user-content-first-section"> +<a class="anchor" href="#user-content-first-section"></a>First section</h2>)).to_html + expect(result).to eq(%(<h2 id="user-content-first-section"> +<a class="anchor" href="#user-content-first-section"></a>First section</h2>)) + end + + it 'removes non prefixed ids' do + result = filter('<p><a id="cross-references"></a>A link to another location within an AsciiDoc document.</p>').to_html + expect(result).to eq(%(<p><a></a>A link to another location within an AsciiDoc document.</p>)) + end +end diff --git a/spec/lib/gitlab/asciidoc/html5_converter_spec.rb b/spec/lib/gitlab/asciidoc/html5_converter_spec.rb new file mode 100644 index 00000000000..84c2cda496e --- /dev/null +++ b/spec/lib/gitlab/asciidoc/html5_converter_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Asciidoc::Html5Converter do + describe 'convert AsciiDoc to HTML5' do + it 'appends user-content- prefix on ref (anchor)' do + doc = Asciidoctor::Document.new('') + anchor = Asciidoctor::Inline.new(doc, :anchor, '', type: :ref, id: 'cross-references') + converter = Gitlab::Asciidoc::Html5Converter.new('gitlab_html5') + html = converter.convert_inline_anchor(anchor) + expect(html).to eq('<a id="user-content-cross-references"></a>') + end + end +end diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 8012f5c510f..36e4decdead 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -252,6 +252,27 @@ module Gitlab end end + context 'with xrefs' do + it 'preserves ids' do + input = <<~ADOC + Learn how to xref:cross-references[use cross references]. + + [[cross-references]]A link to another location within an AsciiDoc document or between AsciiDoc documents is called a cross reference (also referred to as an xref). + ADOC + + output = <<~HTML + <div> + <p>Learn how to <a href="#cross-references">use cross references</a>.</p> + </div> + <div> + <p><a id="user-content-cross-references"></a>A link to another location within an AsciiDoc document or between AsciiDoc documents is called a cross reference (also referred to as an xref).</p> + </div> + HTML + + expect(render(input, context)).to include(output.strip) + end + end + context 'with checklist' do it 'preserves classes' do input = <<~ADOC diff --git a/spec/lib/gitlab/rack_attack/user_allowlist_spec.rb b/spec/lib/gitlab/rack_attack/user_allowlist_spec.rb new file mode 100644 index 00000000000..aa604dfab71 --- /dev/null +++ b/spec/lib/gitlab/rack_attack/user_allowlist_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::RackAttack::UserAllowlist do + using RSpec::Parameterized::TableSyntax + + subject { described_class.new(input)} + + where(:input, :elements) do + nil | [] + '' | [] + '123' | [123] + '123,456' | [123, 456] + '123,foobar, 456,' | [123, 456] + end + + with_them do + it 'has the expected elements' do + expect(subject).to contain_exactly(*elements) + end + + it 'implements empty?' do + expect(subject.empty?).to eq(elements.empty?) + end + + it 'implements include?' do + unless elements.empty? + expect(subject).to include(elements.first) + end + end + end +end diff --git a/spec/lib/gitlab/rack_attack_spec.rb b/spec/lib/gitlab/rack_attack_spec.rb index 8b53af6c333..d72863b0103 100644 --- a/spec/lib/gitlab/rack_attack_spec.rb +++ b/spec/lib/gitlab/rack_attack_spec.rb @@ -75,5 +75,22 @@ RSpec.describe Gitlab::RackAttack, :aggregate_failures do expect(fake_rack_attack).to have_received(:throttle).with(throttle.to_s, throttles[throttle]) end end + + context 'user allowlist' do + subject { described_class.user_allowlist } + + it 'is empty' do + described_class.configure(fake_rack_attack) + + expect(subject).to be_empty + end + + it 'reflects GITLAB_THROTTLE_USER_ALLOWLIST' do + stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', '123,456') + described_class.configure(fake_rack_attack) + + expect(subject).to contain_exactly(123, 456) + end + end end end diff --git a/spec/migrations/remove_orphan_service_hooks_spec.rb b/spec/migrations/remove_orphan_service_hooks_spec.rb new file mode 100644 index 00000000000..c06a8b97738 --- /dev/null +++ b/spec/migrations/remove_orphan_service_hooks_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! +require Rails.root.join('db', 'migrate', '20201119125730_add_web_hooks_service_foreign_key.rb') + +RSpec.describe RemoveOrphanServiceHooks, schema: 20201203123201 do + let(:web_hooks) { table(:web_hooks) } + let(:services) { table(:services) } + + before do + services.create! + web_hooks.create!(service_id: services.first.id, type: 'ServiceHook') + web_hooks.create!(service_id: nil) + + AddWebHooksServiceForeignKey.new.down + web_hooks.create!(service_id: non_existing_record_id, type: 'ServiceHook') + AddWebHooksServiceForeignKey.new.up + end + + it 'removes service hooks where the referenced service does not exist', :aggregate_failures do + expect { RemoveOrphanServiceHooks.new.up }.to change { web_hooks.count }.by(-1) + expect(web_hooks.where.not(service_id: services.select(:id)).count).to eq(0) + end +end diff --git a/spec/requests/api/graphql/user/starred_projects_query_spec.rb b/spec/requests/api/graphql/user/starred_projects_query_spec.rb index 8a1bd3d172f..b098058a735 100644 --- a/spec/requests/api/graphql/user/starred_projects_query_spec.rb +++ b/spec/requests/api/graphql/user/starred_projects_query_spec.rb @@ -70,4 +70,31 @@ RSpec.describe 'Getting starredProjects of the user' do ) end end + + context 'the user has a private profile' do + before do + user.update!(private_profile: true) + post_graphql(query, current_user: current_user) + end + + context 'the current user does not have access to view the private profile of the user' do + let(:current_user) { create(:user) } + + it 'finds no projects' do + expect(starred_projects).to be_empty + end + end + + context 'the current user has access to view the private profile of the user' do + let(:current_user) { create(:admin) } + + it 'finds all projects starred by the user, which the current user has access to' do + expect(starred_projects).to contain_exactly( + a_hash_including('id' => global_id_of(project_a)), + a_hash_including('id' => global_id_of(project_b)), + a_hash_including('id' => global_id_of(project_c)) + ) + end + end + end end diff --git a/spec/requests/api/graphql/user_query_spec.rb b/spec/requests/api/graphql/user_query_spec.rb index d495537c722..3e7ef8838c0 100644 --- a/spec/requests/api/graphql/user_query_spec.rb +++ b/spec/requests/api/graphql/user_query_spec.rb @@ -82,7 +82,7 @@ RSpec.describe 'getting user information' do 'username' => presenter.username, 'webUrl' => presenter.web_url, 'avatarUrl' => presenter.avatar_url, - 'email' => presenter.email, + 'email' => presenter.public_email, 'publicEmail' => presenter.public_email )) @@ -251,7 +251,7 @@ RSpec.describe 'getting user information' do context 'the user is private' do before do - user.update(private_profile: true) + user.update!(private_profile: true) post_graphql(query, current_user: current_user) end @@ -261,6 +261,50 @@ RSpec.describe 'getting user information' do it_behaves_like 'a working graphql query' end + context 'we request the groupMemberships' do + let_it_be(:membership_a) { create(:group_member, user: user) } + let(:group_memberships) { graphql_data_at(:user, :group_memberships, :nodes) } + let(:user_fields) { 'groupMemberships { nodes { id } }' } + + it_behaves_like 'a working graphql query' + + it 'cannot be found' do + expect(group_memberships).to be_empty + end + + context 'the current user is the user' do + let(:current_user) { user } + + it 'can be found' do + expect(group_memberships).to include( + a_hash_including('id' => global_id_of(membership_a)) + ) + end + end + end + + context 'we request the projectMemberships' do + let_it_be(:membership_a) { create(:project_member, user: user) } + let(:project_memberships) { graphql_data_at(:user, :project_memberships, :nodes) } + let(:user_fields) { 'projectMemberships { nodes { id } }' } + + it_behaves_like 'a working graphql query' + + it 'cannot be found' do + expect(project_memberships).to be_empty + end + + context 'the current user is the user' do + let(:current_user) { user } + + it 'can be found' do + expect(project_memberships).to include( + a_hash_including('id' => global_id_of(membership_a)) + ) + end + end + end + context 'we request the authoredMergeRequests' do let(:user_fields) { 'authoredMergeRequests { nodes { id } }' } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index eb3e610934d..83b32d9b3fd 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1255,13 +1255,46 @@ RSpec.describe API::Projects do expect(json_response['message']).to eq('404 User Not Found') end - it 'returns projects filtered by user' do - get api("/users/#{user3.id}/starred_projects/", user) + context 'with a public profile' do + it 'returns projects filtered by user' do + get api("/users/#{user3.id}/starred_projects/", user) - expect(response).to have_gitlab_http_status(:ok) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, project2.id, project3.id) + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }) + .to contain_exactly(project.id, project2.id, project3.id) + end + end + + context 'with a private profile' do + before do + user3.update!(private_profile: true) + user3.reload + end + + context 'user does not have access to view the private profile' do + it 'returns no projects' do + get api("/users/#{user3.id}/starred_projects/", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response).to be_empty + end + end + + context 'user has access to view the private profile' do + it 'returns projects filtered by user' do + get api("/users/#{user3.id}/starred_projects/", admin) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }) + .to contain_exactly(project.id, project2.id, project3.id) + end + end end end diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb index 323ac50808d..5d300d38e4a 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -23,6 +23,11 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds end + after do + stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', nil) + Gitlab::RackAttack.configure_user_allowlist + end + context 'when the throttle is enabled' do before do settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true @@ -30,6 +35,8 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do end it 'rejects requests over the rate limit' do + expect(Gitlab::Instrumentation::Throttle).not_to receive(:safelist=) + # At first, allow requests under the rate limit. requests_per_period.times do make_request(request_args) @@ -40,6 +47,18 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do expect_rejection { make_request(request_args) } end + it 'does not reject requests if the user is in the allowlist' do + stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', user.id.to_s) + Gitlab::RackAttack.configure_user_allowlist + + expect(Gitlab::Instrumentation::Throttle).to receive(:safelist=).with('throttle_user_allowlist').at_least(:once) + + (requests_per_period + 1).times do + make_request(request_args) + expect(response).not_to have_gitlab_http_status(:too_many_requests) + end + end + it 'allows requests after throttling and then waiting for the next period' do requests_per_period.times do make_request(request_args) @@ -167,6 +186,11 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds end + after do + stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', nil) + Gitlab::RackAttack.configure_user_allowlist + end + context 'when the throttle is enabled' do before do settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true @@ -174,6 +198,8 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do end it 'rejects requests over the rate limit' do + expect(Gitlab::Instrumentation::Throttle).not_to receive(:safelist=) + # At first, allow requests under the rate limit. requests_per_period.times do request_authenticated_web_url @@ -184,6 +210,18 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do expect_rejection { request_authenticated_web_url } end + it 'does not reject requests if the user is in the allowlist' do + stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', user.id.to_s) + Gitlab::RackAttack.configure_user_allowlist + + expect(Gitlab::Instrumentation::Throttle).to receive(:safelist=).with('throttle_user_allowlist').at_least(:once) + + (requests_per_period + 1).times do + request_authenticated_web_url + expect(response).not_to have_gitlab_http_status(:too_many_requests) + end + end + it 'allows requests after throttling and then waiting for the next period' do requests_per_period.times do request_authenticated_web_url diff --git a/spec/validators/zoom_url_validator_spec.rb b/spec/validators/zoom_url_validator_spec.rb new file mode 100644 index 00000000000..7d5c94bc249 --- /dev/null +++ b/spec/validators/zoom_url_validator_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ZoomUrlValidator do + let(:zoom_meeting) { build(:zoom_meeting) } + + describe 'validations' do + context 'when zoom link starts with https' do + it 'passes validation' do + zoom_meeting.url = 'https://zoom.us/j/123456789' + + expect(zoom_meeting.valid?).to eq(true) + expect(zoom_meeting.errors).to be_empty + end + end + + shared_examples 'zoom link does not start with https' do |url| + it 'fails validation' do + zoom_meeting.url = url + expect(zoom_meeting.valid?).to eq(false) + + expect(zoom_meeting.errors).to be_present + expect(zoom_meeting.errors.first[1]).to eq 'must contain one valid Zoom URL' + end + end + + context 'when zoom link does not start with https' do + include_examples 'zoom link does not start with https', 'http://zoom.us/j/123456789' + + context 'when zoom link does not start with a scheme' do + include_examples 'zoom link does not start with https', 'testinghttp://zoom.us/j/123456789' + end + end + end +end |