diff options
Diffstat (limited to 'spec/support')
20 files changed, 671 insertions, 32 deletions
diff --git a/spec/support/active_record_enum.rb b/spec/support/active_record_enum.rb new file mode 100644 index 00000000000..fb1189c7f17 --- /dev/null +++ b/spec/support/active_record_enum.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +shared_examples 'having unique enum values' do + described_class.defined_enums.each do |name, enum| + it "has unique values in #{name.inspect}" do + duplicated = enum.group_by(&:last).select { |key, value| value.size > 1 } + + expect(duplicated).to be_empty, + "Duplicated values detected: #{duplicated.values.map(&Hash.method(:[]))}" + end + end +end diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb new file mode 100644 index 00000000000..7e4958f177a --- /dev/null +++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb @@ -0,0 +1,92 @@ +shared_examples 'authenticates sessionless user' do |path, format, params| + params ||= {} + + before do + stub_authentication_activity_metrics(debug: false) + end + + let(:user) { create(:user) } + let(:personal_access_token) { create(:personal_access_token, user: user) } + let(:default_params) { { format: format }.merge(params.except(:public) || {}) } + + context "when the 'personal_access_token' param is populated with the personal access token" do + it 'logs the user in' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + .and increment(:user_session_override_counter) + .and increment(:user_sessionless_authentication_counter) + + get path, default_params.merge(private_token: personal_access_token.token) + + expect(response).to have_gitlab_http_status(200) + expect(controller.current_user).to eq(user) + end + + it 'does not log the user in if page is public', if: params[:public] do + get path, default_params + + expect(response).to have_gitlab_http_status(200) + expect(controller.current_user).to be_nil + end + end + + context 'when the personal access token has no api scope', unless: params[:public] do + it 'does not log the user in' do + expect(authentication_metrics) + .to increment(:user_unauthenticated_counter) + + personal_access_token.update(scopes: [:read_user]) + + get path, default_params.merge(private_token: personal_access_token.token) + + expect(response).not_to have_gitlab_http_status(200) + end + end + + context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do + it 'logs the user in' do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + .and increment(:user_session_override_counter) + .and increment(:user_sessionless_authentication_counter) + + @request.headers['PRIVATE-TOKEN'] = personal_access_token.token + get path, default_params + + expect(response).to have_gitlab_http_status(200) + end + end + + context "when the 'feed_token' param is populated with the feed token", if: format == :rss do + it "logs the user in" do + expect(authentication_metrics) + .to increment(:user_authenticated_counter) + .and increment(:user_session_override_counter) + .and increment(:user_sessionless_authentication_counter) + + get path, default_params.merge(feed_token: user.feed_token) + + expect(response).to have_gitlab_http_status 200 + end + end + + context "when the 'feed_token' param is populated with an invalid feed token", if: format == :rss, unless: params[:public] do + it "logs the user" do + expect(authentication_metrics) + .to increment(:user_unauthenticated_counter) + + get path, default_params.merge(feed_token: 'token') + + expect(response.status).not_to eq 200 + end + end + + it "doesn't log the user in otherwise", unless: params[:public] do + expect(authentication_metrics) + .to increment(:user_unauthenticated_counter) + + get path, default_params.merge(private_token: 'token') + + expect(response.status).not_to eq(200) + end +end diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index 18cf08f0b9e..922f3df144d 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -142,6 +142,14 @@ shared_examples 'discussion comments' do |resource_name| find(comments_selector, match: :first) end + def submit_reply(text) + find("#{comments_selector} .js-vue-discussion-reply").click + find("#{comments_selector} .note-textarea").send_keys(text) + + click_button "Comment" + wait_for_requests + end + it 'clicking "Start discussion" will post a discussion' do new_comment = all(comments_selector).last @@ -149,16 +157,29 @@ shared_examples 'discussion comments' do |resource_name| expect(new_comment).to have_selector '.discussion' end + if resource_name =~ /(issue|merge request)/ + it 'can be replied to' do + submit_reply('some text') + + expect(page).to have_css('.discussion-notes .note', count: 2) + expect(page).to have_content 'Collapse replies' + end + + it 'can be collapsed' do + submit_reply('another text') + + find('.js-collapse-replies').click + expect(page).to have_css('.discussion-notes .note', count: 1) + expect(page).to have_content '1 reply' + end + end + if resource_name == 'merge request' let(:note_id) { find("#{comments_selector} .note:first-child", match: :first)['data-note-id'] } let(:reply_id) { find("#{comments_selector} .note:last-child", match: :first)['data-note-id'] } it 'shows resolved discussion when toggled' do - find("#{comments_selector} .js-vue-discussion-reply").click - find("#{comments_selector} .note-textarea").send_keys('a') - - click_button "Comment" - wait_for_requests + submit_reply('a') click_button "Resolve discussion" wait_for_requests diff --git a/spec/support/helpers/features/list_rows_helpers.rb b/spec/support/helpers/features/list_rows_helpers.rb new file mode 100644 index 00000000000..0626415361c --- /dev/null +++ b/spec/support/helpers/features/list_rows_helpers.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +# These helpers allow you to access rows in the list +# +# Usage: +# describe "..." do +# include Spec::Support::Helpers::Features::ListRowsHelpers +# ... +# +# expect(first_row.text).to include("John Doe") +# expect(second_row.text).to include("John Smith") +# +module Spec + module Support + module Helpers + module Features + module ListRowsHelpers + def first_row + page.all('ul.content-list > li')[0] + end + + def second_row + page.all('ul.content-list > li')[1] + end + end + end + end + end +end diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb index ad0053ec5cf..003ecb251fe 100644 --- a/spec/support/helpers/features/sorting_helpers.rb +++ b/spec/support/helpers/features/sorting_helpers.rb @@ -13,9 +13,9 @@ module Spec module Features module SortingHelpers def sort_by(value) - find('button.dropdown-toggle').click + find('.filter-dropdown-container .dropdown').click - page.within('.content ul.dropdown-menu.dropdown-menu-right li') do + page.within('ul.dropdown-menu.dropdown-menu-right li') do click_link(value) end end diff --git a/spec/support/helpers/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb index b8289e6c5f1..9a5845af90c 100644 --- a/spec/support/helpers/git_http_helpers.rb +++ b/spec/support/helpers/git_http_helpers.rb @@ -60,9 +60,4 @@ module GitHttpHelpers message = Gitlab::GitAccessWiki::ERROR_MESSAGES[error_key] message || raise("GitAccessWiki error message key '#{error_key}' not found") end - - def change_access_error(error_key) - message = Gitlab::Checks::ChangeAccess::ERROR_MESSAGES[error_key] - message || raise("ChangeAccess error message key '#{error_key}' not found") - end end diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index ccaf86aa3a6..39bd305d88a 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -34,6 +34,17 @@ module KubernetesHelpers WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response) end + def stub_kubeclient_knative_services(**options) + options[:name] ||= "kubetest" + options[:namespace] ||= "default" + options[:domain] ||= "example.com" + + stub_kubeclient_discover(service.api_url) + knative_url = service.api_url + "/apis/serving.knative.dev/v1alpha1/services" + + WebMock.stub_request(:get, knative_url).to_return(kube_response(kube_knative_services_body(options))) + end + def stub_kubeclient_get_secret(api_url, **options) options[:metadata_name] ||= "default-token-1" options[:namespace] ||= "default" @@ -47,6 +58,11 @@ module KubernetesHelpers .to_return(status: [status, "Internal Server Error"]) end + def stub_kubeclient_get_service_account_error(api_url, name, namespace: 'default', status: 404) + WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts/#{name}") + .to_return(status: [status, "Internal Server Error"]) + end + def stub_kubeclient_create_service_account(api_url, namespace: 'default') WebMock.stub_request(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts") .to_return(kube_response({})) @@ -62,11 +78,26 @@ module KubernetesHelpers .to_return(kube_response({})) end + def stub_kubeclient_put_secret(api_url, name, namespace: 'default') + WebMock.stub_request(:put, api_url + "/api/v1/namespaces/#{namespace}/secrets/#{name}") + .to_return(kube_response({})) + end + + def stub_kubeclient_get_cluster_role_binding_error(api_url, name, status: 404) + WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/#{name}") + .to_return(status: [status, "Internal Server Error"]) + end + def stub_kubeclient_create_cluster_role_binding(api_url) WebMock.stub_request(:post, api_url + '/apis/rbac.authorization.k8s.io/v1/clusterrolebindings') .to_return(kube_response({})) end + def stub_kubeclient_get_role_binding_error(api_url, name, namespace: 'default', status: 404) + WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}") + .to_return(status: [status, "Internal Server Error"]) + end + def stub_kubeclient_create_role_binding(api_url, namespace: 'default') WebMock.stub_request(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings") .to_return(kube_response({})) @@ -161,6 +192,13 @@ module KubernetesHelpers } end + def kube_knative_services_body(**options) + { + "kind" => "List", + "items" => [kube_service(options)] + } + end + # This is a partial response, it will have many more elements in reality but # these are the ones we care about at the moment def kube_pod(name: "kube-pod", app: "valid-pod-label", status: "Running", track: nil) @@ -204,6 +242,54 @@ module KubernetesHelpers } end + def kube_service(name: "kubetest", namespace: "default", domain: "example.com") + { + "metadata" => { + "creationTimestamp" => "2018-11-21T06:16:33Z", + "name" => name, + "namespace" => namespace, + "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}" + }, + "spec" => { + "generation" => 2 + }, + "status" => { + "domain" => "#{name}.#{namespace}.#{domain}", + "domainInternal" => "#{name}.#{namespace}.svc.cluster.local", + "latestCreatedRevisionName" => "#{name}-00002", + "latestReadyRevisionName" => "#{name}-00002", + "observedGeneration" => 2 + } + } + end + + def kube_service_full(name: "kubetest", namespace: "kube-ns", domain: "example.com") + { + "metadata" => { + "creationTimestamp" => "2018-11-21T06:16:33Z", + "name" => name, + "namespace" => namespace, + "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}", + "annotation" => { + "description" => "This is a test description" + } + }, + "spec" => { + "generation" => 2, + "build" => { + "template" => "go-1.10.3" + } + }, + "status" => { + "domain" => "#{name}.#{namespace}.#{domain}", + "domainInternal" => "#{name}.#{namespace}.svc.cluster.local", + "latestCreatedRevisionName" => "#{name}-00002", + "latestReadyRevisionName" => "#{name}-00002", + "observedGeneration" => 2 + } + } + end + def kube_terminals(service, pod) pod_name = pod['metadata']['name'] containers = pod['spec']['containers'] diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb index 4212be2cc88..ce1f9fce10d 100644 --- a/spec/support/helpers/prometheus_helpers.rb +++ b/spec/support/helpers/prometheus_helpers.rb @@ -49,11 +49,11 @@ module PrometheusHelpers "https://prometheus.example.com/api/v1/series?#{query}" end - def stub_prometheus_request(url, body: {}, status: 200) + def stub_prometheus_request(url, body: {}, status: 200, headers: {}) WebMock.stub_request(:get, url) .to_return({ status: status, - headers: { 'Content-Type' => 'application/json' }, + headers: { 'Content-Type' => 'application/json' }.merge(headers), body: body.to_json }) end diff --git a/spec/support/helpers/sorting_helper.rb b/spec/support/helpers/sorting_helper.rb index 9496a94d8f4..e505a6b7258 100644 --- a/spec/support/helpers/sorting_helper.rb +++ b/spec/support/helpers/sorting_helper.rb @@ -10,7 +10,7 @@ # module SortingHelper def sorting_by(value) - find('button.dropdown-toggle').click + find('.filter-dropdown-container button.dropdown-menu-toggle').click page.within('.content ul.dropdown-menu.dropdown-menu-right li') do click_link value end diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index 776119564ec..2851cd9733c 100644 --- a/spec/support/helpers/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -27,6 +27,11 @@ module StubConfiguration allow(Gitlab.config.gitlab).to receive_messages(to_settings(messages)) end + def stub_default_url_options(host: "localhost", protocol: "http") + url_options = { host: host, protocol: protocol } + allow(Rails.application.routes).to receive(:default_url_options).and_return(url_options) + end + def stub_gravatar_setting(messages) allow(Gitlab.config.gravatar).to receive_messages(to_settings(messages)) end diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 1f00cdf7e92..d52c40ff4f1 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -54,6 +54,9 @@ module TestEnv 'add_images_and_changes' => '010d106', 'update-gitlab-shell-v-6-0-1' => '2f61d70', 'update-gitlab-shell-v-6-0-3' => 'de78448', + 'merge-commit-analyze-before' => '1adbdef', + 'merge-commit-analyze-side-branch' => '8a99451', + 'merge-commit-analyze-after' => '646ece5', '2-mb-file' => 'bf12d25', 'before-create-delete-modify-move' => '845009f', 'between-create-delete-modify-move' => '3f5f443', diff --git a/spec/support/shared_contexts/change_access_checks_shared_context.rb b/spec/support/shared_contexts/change_access_checks_shared_context.rb new file mode 100644 index 00000000000..aca18b0c73b --- /dev/null +++ b/spec/support/shared_contexts/change_access_checks_shared_context.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +shared_context 'change access checks context' do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:user_access) { Gitlab::UserAccess.new(user, project: project) } + let(:oldrev) { 'be93687618e4b132087f430a4d8fc3a609c9b77c' } + let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } + let(:ref) { 'refs/heads/master' } + let(:changes) { { oldrev: oldrev, newrev: newrev, ref: ref } } + let(:protocol) { 'ssh' } + let(:timeout) { Gitlab::GitAccess::INTERNAL_TIMEOUT } + let(:logger) { Gitlab::Checks::TimedLogger.new(timeout: timeout) } + let(:change_access) do + Gitlab::Checks::ChangeAccess.new( + changes, + project: project, + user_access: user_access, + protocol: protocol, + logger: logger + ) + end + + subject { described_class.new(change_access) } + + before do + project.add_developer(user) + end +end diff --git a/spec/support/shared_contexts/url_shared_context.rb b/spec/support/shared_contexts/url_shared_context.rb new file mode 100644 index 00000000000..1b1f67daac3 --- /dev/null +++ b/spec/support/shared_contexts/url_shared_context.rb @@ -0,0 +1,17 @@ +shared_context 'invalid urls' do + let(:urls_with_CRLF) do + ["http://127.0.0.1:333/pa\rth", + "http://127.0.0.1:333/pa\nth", + "http://127.0a.0.1:333/pa\r\nth", + "http://127.0.0.1:333/path?param=foo\r\nbar", + "http://127.0.0.1:333/path?param=foo\rbar", + "http://127.0.0.1:333/path?param=foo\nbar", + "http://127.0.0.1:333/pa%0dth", + "http://127.0.0.1:333/pa%0ath", + "http://127.0a.0.1:333/pa%0d%0th", + "http://127.0.0.1:333/pa%0D%0Ath", + "http://127.0.0.1:333/path?param=foo%0Abar", + "http://127.0.0.1:333/path?param=foo%0Dbar", + "http://127.0.0.1:333/path?param=foo%0D%0Abar"] + end +end diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb index 377bd82b67e..c603421d748 100644 --- a/spec/support/shared_examples/ci_trace_shared_examples.rb +++ b/spec/support/shared_examples/ci_trace_shared_examples.rb @@ -180,10 +180,9 @@ shared_examples_for 'common trace features' do end context 'runners token' do - let(:token) { 'my_secret_token' } + let(:token) { build.project.runners_token } before do - build.project.update(runners_token: token) trace.set(token) end @@ -193,10 +192,9 @@ shared_examples_for 'common trace features' do end context 'hides build token' do - let(:token) { 'my_secret_token' } + let(:token) { build.token } before do - build.update(token: token) trace.set(token) end diff --git a/spec/support/shared_examples/diff_file_collections.rb b/spec/support/shared_examples/diff_file_collections.rb index 55ce160add0..367ddf06c28 100644 --- a/spec/support/shared_examples/diff_file_collections.rb +++ b/spec/support/shared_examples/diff_file_collections.rb @@ -45,3 +45,19 @@ shared_examples 'diff statistics' do |test_include_stats_flag: true| end end end + +shared_examples 'unfoldable diff' do + let(:subject) { described_class.new(diffable, diff_options: nil) } + + it 'calls Gitlab::Diff::File#unfold_diff_lines with correct position' do + position = instance_double(Gitlab::Diff::Position, file_path: 'README') + readme_file = instance_double(Gitlab::Diff::File, file_path: 'README') + other_file = instance_double(Gitlab::Diff::File, file_path: 'foo.rb') + nil_path_file = instance_double(Gitlab::Diff::File, file_path: nil) + + allow(subject).to receive(:diff_files) { [readme_file, other_file, nil_path_file] } + expect(readme_file).to receive(:unfold_diff_lines).with(position) + + subject.unfold_diff_files([position]) + end +end diff --git a/spec/support/shared_examples/file_finder.rb b/spec/support/shared_examples/file_finder.rb index ef144bdf61c..0dc351b5149 100644 --- a/spec/support/shared_examples/file_finder.rb +++ b/spec/support/shared_examples/file_finder.rb @@ -3,18 +3,19 @@ shared_examples 'file finder' do let(:search_results) { subject.find(query) } it 'finds by name' do - filename, blob = search_results.find { |_, blob| blob.filename == expected_file_by_name } - expect(filename).to eq(expected_file_by_name) - expect(blob).to be_a(Gitlab::SearchResults::FoundBlob) + blob = search_results.find { |blob| blob.filename == expected_file_by_name } + + expect(blob.filename).to eq(expected_file_by_name) + expect(blob).to be_a(Gitlab::Search::FoundBlob) expect(blob.ref).to eq(subject.ref) expect(blob.data).not_to be_empty end it 'finds by content' do - filename, blob = search_results.find { |_, blob| blob.filename == expected_file_by_content } + blob = search_results.find { |blob| blob.filename == expected_file_by_content } - expect(filename).to eq(expected_file_by_content) - expect(blob).to be_a(Gitlab::SearchResults::FoundBlob) + expect(blob.filename).to eq(expected_file_by_content) + expect(blob).to be_a(Gitlab::Search::FoundBlob) expect(blob.ref).to eq(subject.ref) expect(blob.data).not_to be_empty end diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb new file mode 100644 index 00000000000..77376496854 --- /dev/null +++ b/spec/support/shared_examples/models/member_shared_examples.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +shared_examples_for 'inherited access level as a member of entity' do + let(:parent_entity) { create(:group) } + let(:user) { create(:user) } + let(:member) { entity.is_a?(Group) ? entity.group_member(user) : entity.project_member(user) } + + context 'with root parent_entity developer member' do + before do + parent_entity.add_developer(user) + end + + it 'is allowed to be a maintainer of the entity' do + entity.add_maintainer(user) + + expect(member.access_level).to eq(Gitlab::Access::MAINTAINER) + end + + it 'is not allowed to be a reporter of the entity' do + entity.add_reporter(user) + + expect(member).to be_nil + end + + it 'is allowed to change to be a developer of the entity' do + entity.add_maintainer(user) + + expect { member.update(access_level: Gitlab::Access::DEVELOPER) } + .to change { member.access_level }.to(Gitlab::Access::DEVELOPER) + end + + it 'is not allowed to change to be a guest of the entity' do + entity.add_maintainer(user) + + expect { member.update(access_level: Gitlab::Access::GUEST) } + .not_to change { member.reload.access_level } + end + + it "shows an error if the member can't be updated" do + entity.add_maintainer(user) + + member.update(access_level: Gitlab::Access::REPORTER) + + expect(member.errors.full_messages).to eq(["Access level should be higher than Developer inherited membership from group #{parent_entity.name}"]) + end + + it 'allows changing the level from a non existing member' do + non_member_user = create(:user) + + entity.add_maintainer(non_member_user) + + non_member = entity.is_a?(Group) ? entity.group_member(non_member_user) : entity.project_member(non_member_user) + + expect { non_member.update(access_level: Gitlab::Access::GUEST) } + .to change { non_member.reload.access_level } + end + end +end + +shared_examples_for '#valid_level_roles' do |entity_name| + let(:member_user) { create(:user) } + let(:group) { create(:group) } + let(:entity) { create(entity_name) } + let(:entity_member) { create("#{entity_name}_member", :developer, source: entity, user: member_user) } + let(:presenter) { described_class.new(entity_member, current_user: member_user) } + let(:expected_roles) { { 'Developer' => 30, 'Maintainer' => 40, 'Reporter' => 20 } } + + it 'returns all roles when no parent member is present' do + expect(presenter.valid_level_roles).to eq(entity_member.class.access_level_roles) + end + + it 'returns higher roles when a parent member is present' do + group.add_reporter(member_user) + + expect(presenter.valid_level_roles).to eq(expected_roles) + end +end diff --git a/spec/support/shared_examples/models/with_uploads_shared_examples.rb b/spec/support/shared_examples/models/with_uploads_shared_examples.rb index 47ad0c6345d..1d11b855459 100644 --- a/spec/support/shared_examples/models/with_uploads_shared_examples.rb +++ b/spec/support/shared_examples/models/with_uploads_shared_examples.rb @@ -1,6 +1,6 @@ require 'spec_helper' -shared_examples_for 'model with mounted uploader' do |supports_fileuploads| +shared_examples_for 'model with uploads' do |supports_fileuploads| describe '.destroy' do before do stub_uploads_object_storage(uploader_class) @@ -8,16 +8,62 @@ shared_examples_for 'model with mounted uploader' do |supports_fileuploads| model_object.public_send(upload_attribute).migrate!(ObjectStorage::Store::REMOTE) end - it 'deletes remote uploads' do - expect_any_instance_of(CarrierWave::Storage::Fog::File).to receive(:delete).and_call_original + context 'with mounted uploader' do + it 'deletes remote uploads' do + expect_any_instance_of(CarrierWave::Storage::Fog::File).to receive(:delete).and_call_original - expect { model_object.destroy }.to change { Upload.count }.by(-1) + expect { model_object.destroy }.to change { Upload.count }.by(-1) + end end - it 'deletes any FileUploader uploads which are not mounted', skip: !supports_fileuploads do - create(:upload, uploader: FileUploader, model: model_object) + context 'with not mounted uploads', :sidekiq, skip: !supports_fileuploads do + context 'with local files' do + let!(:uploads) { create_list(:upload, 2, uploader: FileUploader, model: model_object) } - expect { model_object.destroy }.to change { Upload.count }.by(-2) + it 'deletes any FileUploader uploads which are not mounted' do + expect { model_object.destroy }.to change { Upload.count }.by(-3) + end + + it 'deletes local files' do + expect_any_instance_of(Uploads::Local).to receive(:delete_keys).with(uploads.map(&:absolute_path)) + + model_object.destroy + end + end + + context 'with remote files' do + let!(:uploads) { create_list(:upload, 2, :object_storage, uploader: FileUploader, model: model_object) } + + it 'deletes any FileUploader uploads which are not mounted' do + expect { model_object.destroy }.to change { Upload.count }.by(-3) + end + + it 'deletes remote files' do + expect_any_instance_of(Uploads::Fog).to receive(:delete_keys).with(uploads.map(&:path)) + + model_object.destroy + end + end + + describe 'destroy strategy depending on feature flag' do + let!(:upload) { create(:upload, uploader: FileUploader, model: model_object) } + + it 'does not destroy uploads by default' do + expect(model_object).to receive(:delete_uploads) + expect(model_object).not_to receive(:destroy_uploads) + + model_object.destroy + end + + it 'uses before destroy callback if feature flag is disabled' do + stub_feature_flags(fast_destroy_uploads: false) + + expect(model_object).to receive(:destroy_uploads) + expect(model_object).not_to receive(:delete_uploads) + + model_object.destroy + end + end end end end diff --git a/spec/support/shared_examples/only_except_policy_examples.rb b/spec/support/shared_examples/only_except_policy_examples.rb new file mode 100644 index 00000000000..35240af1d74 --- /dev/null +++ b/spec/support/shared_examples/only_except_policy_examples.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +shared_examples 'correct only except policy' do + context 'when using simplified policy' do + describe 'validations' do + context 'when entry config value is valid' do + context 'when config is a branch or tag name' do + let(:config) { %w[master feature/branch] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns refs hash' do + expect(entry.value).to eq(refs: config) + end + end + end + + context 'when config is a regexp' do + let(:config) { ['/^issue-.*$/'] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when config is a special keyword' do + let(:config) { %w[tags triggers branches] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + end + + context 'when entry value is not valid' do + let(:config) { [1] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include /policy config should be an array of strings or regexps/ + end + end + end + end + end + + context 'when using complex policy' do + context 'when specifying refs policy' do + let(:config) { { refs: ['master'] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(refs: %w[master]) + end + end + + context 'when specifying kubernetes policy' do + let(:config) { { kubernetes: 'active' } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(kubernetes: 'active') + end + end + + context 'when specifying invalid kubernetes policy' do + let(:config) { { kubernetes: 'something' } } + + it 'reports an error about invalid policy' do + expect(entry.errors).to include /unknown value: something/ + end + end + + context 'when specifying valid variables expressions policy' do + let(:config) { { variables: ['$VAR == null'] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(config) + end + end + + context 'when specifying variables expressions in invalid format' do + let(:config) { { variables: '$MY_VAR' } } + + it 'reports an error about invalid format' do + expect(entry.errors).to include /should be an array of strings/ + end + end + + context 'when specifying invalid variables expressions statement' do + let(:config) { { variables: ['$MY_VAR =='] } } + + it 'reports an error about invalid statement' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when specifying invalid variables expressions token' do + let(:config) { { variables: ['$MY_VAR == 123'] } } + + it 'reports an error about invalid expression' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when using invalid variables expressions regexp' do + let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } } + + it 'reports an error about invalid expression' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when specifying a valid changes policy' do + let(:config) { { changes: %w[some/* paths/**/*.rb] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(config) + end + end + + context 'when changes policy is invalid' do + let(:config) { { changes: [1, 2] } } + + it 'returns errors' do + expect(entry.errors).to include /changes should be an array of strings/ + end + end + + context 'when specifying unknown policy' do + let(:config) { { refs: ['master'], invalid: :something } } + + it 'returns error about invalid key' do + expect(entry.errors).to include /unknown keys: invalid/ + end + end + + context 'when policy is empty' do + let(:config) { {} } + + it 'is not a valid configuration' do + expect(entry.errors).to include /can't be blank/ + end + end + end + + context 'when policy strategy does not match' do + let(:config) { 'string strategy' } + + it 'returns information about errors' do + expect(entry.errors) + .to include /has to be either an array of conditions or a hash/ + end + end +end diff --git a/spec/support/shared_examples/serializers/diff_file_entity_examples.rb b/spec/support/shared_examples/serializers/diff_file_entity_examples.rb new file mode 100644 index 00000000000..b8065886c42 --- /dev/null +++ b/spec/support/shared_examples/serializers/diff_file_entity_examples.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +shared_examples 'diff file base entity' do + it 'exposes essential attributes' do + expect(subject).to include(:content_sha, :submodule, :submodule_link, + :submodule_tree_url, :old_path_html, + :new_path_html, :blob, :can_modify_blob, + :file_hash, :file_path, :old_path, :new_path, + :collapsed, :text, :diff_refs, :stored_externally, + :external_storage, :renamed_file, :deleted_file, + :mode_changed, :a_mode, :b_mode, :new_file) + end + + # Converted diff files from GitHub import does not contain blob file + # and content sha. + context 'when diff file does not have a blob and content sha' do + it 'exposes some attributes as nil' do + allow(diff_file).to receive(:content_sha).and_return(nil) + allow(diff_file).to receive(:blob).and_return(nil) + + expect(subject[:context_lines_path]).to be_nil + expect(subject[:view_path]).to be_nil + expect(subject[:highlighted_diff_lines]).to be_nil + expect(subject[:can_modify_blob]).to be_nil + end + end +end + +shared_examples 'diff file entity' do + it_behaves_like 'diff file base entity' + + it 'exposes correct attributes' do + expect(subject).to include(:too_large, :added_lines, :removed_lines, + :context_lines_path, :highlighted_diff_lines, + :parallel_diff_lines) + end + + it 'includes viewer' do + expect(subject[:viewer].with_indifferent_access) + .to match_schema('entities/diff_viewer') + end +end + +shared_examples 'diff file discussion entity' do + it_behaves_like 'diff file base entity' +end |