diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-16 18:25:58 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-16 18:25:58 +0000 |
commit | a5f4bba440d7f9ea47046a0a561d49adf0a1e6d4 (patch) | |
tree | fb69158581673816a8cd895f9d352dcb3c678b1e /spec/support | |
parent | d16b2e8639e99961de6ddc93909f3bb5c1445ba1 (diff) | |
download | gitlab-ce-14.0.0-rc42.tar.gz |
Add latest changes from gitlab-org/gitlab@14-0-stable-eev14.0.0-rc42
Diffstat (limited to 'spec/support')
74 files changed, 1702 insertions, 396 deletions
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index f9a28c8e40b..e48a7b322ac 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -21,7 +21,8 @@ JS_CONSOLE_FILTER = Regexp.union([ '"[WDS] Hot Module Replacement enabled."', '"[WDS] Live Reloading enabled."', 'Download the Vue Devtools extension', - 'Download the Apollo DevTools' + 'Download the Apollo DevTools', + "Unrecognized feature: 'interest-cohort'" ]) CAPYBARA_WINDOW_SIZE = [1366, 768].freeze diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb index 60d82f7e92a..f6339d7343c 100644 --- a/spec/support/database_cleaner.rb +++ b/spec/support/database_cleaner.rb @@ -35,8 +35,6 @@ RSpec.configure do |config| puts "Recreating the database" start = Gitlab::Metrics::System.monotonic_time - ActiveRecord::AdvisoryLockBase.clear_all_connections! - ActiveRecord::Tasks::DatabaseTasks.drop_current ActiveRecord::Tasks::DatabaseTasks.create_current ActiveRecord::Tasks::DatabaseTasks.load_schema_current diff --git a/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb b/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb index c9ff566e94c..de9735df546 100644 --- a/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb +++ b/spec/support/gitlab/usage/metrics_instrumentation_shared_examples.rb @@ -1,13 +1,38 @@ # frozen_string_literal: true -RSpec.shared_examples 'a correct instrumented metric value' do |options, expected_value| - let(:time_frame) { options[:time_frame] } +RSpec.shared_examples 'a correct instrumented metric value' do |params| + let(:time_frame) { params[:time_frame] } + let(:options) { params[:options] } + let(:metric) { described_class.new(time_frame: time_frame, options: options) } before do allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false) end it 'has correct value' do - expect(described_class.new(time_frame: time_frame).value).to eq(expected_value) + expect(metric.value).to eq(expected_value) end end + +RSpec.shared_examples 'a correct instrumented metric query' do |params| + let(:time_frame) { params[:time_frame] } + let(:options) { params[:options] } + let(:metric) { described_class.new(time_frame: time_frame, options: options) } + + around do |example| + freeze_time { example.run } + end + + before do + allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false) + end + + it 'has correct generate query' do + expect(metric.to_sql).to eq(expected_query) + end +end + +RSpec.shared_examples 'a correct instrumented metric value and query' do |params| + it_behaves_like 'a correct instrumented metric value', params + it_behaves_like 'a correct instrumented metric query', params +end diff --git a/spec/support/gitlab_stubs/gitlab_ci_for_sast.yml b/spec/support/gitlab_stubs/gitlab_ci_for_sast.yml index d20078c8904..0e021a85ba6 100644 --- a/spec/support/gitlab_stubs/gitlab_ci_for_sast.yml +++ b/spec/support/gitlab_stubs/gitlab_ci_for_sast.yml @@ -4,7 +4,6 @@ include: variables: SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers2" SAST_EXCLUDED_PATHS: "spec, executables" - SAST_DEFAULT_ANALYZERS: "bandit, brakeman" SAST_EXCLUDED_ANALYZERS: "brakeman" stages: diff --git a/spec/support/gitlab_stubs/gitlab_ci_for_sast_default_analyzers.yml b/spec/support/gitlab_stubs/gitlab_ci_for_sast_default_analyzers.yml deleted file mode 100644 index c4f3c3aace2..00000000000 --- a/spec/support/gitlab_stubs/gitlab_ci_for_sast_default_analyzers.yml +++ /dev/null @@ -1,15 +0,0 @@ -include: - - template: SAST.gitlab-ci.yml - -variables: - SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers2" - SAST_EXCLUDED_PATHS: "spec, executables" - SAST_DEFAULT_ANALYZERS: "bandit, gosec" - -stages: - - our_custom_security_stage -sast: - stage: our_custom_security_stage - variables: - SEARCH_MAX_DEPTH: 8 - SAST_BRAKEMAN_LEVEL: 2 diff --git a/spec/support/helpers/access_matchers_helpers.rb b/spec/support/helpers/access_matchers_helpers.rb index 9100f245d36..035653172c1 100644 --- a/spec/support/helpers/access_matchers_helpers.rb +++ b/spec/support/helpers/access_matchers_helpers.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module AccessMatchersHelpers + include Gitlab::Utils::StrongMemoize + USER_ACCESSOR_METHOD_NAME = 'user' def provide_user(role, membership = nil) @@ -61,11 +63,6 @@ module AccessMatchersHelpers # (or defined by `method_name`) method generated by `let` definition in example group before it's used by `subject`. # This override is per concrete example only because the example group class gets re-created for each example. instance_eval(<<~CODE, __FILE__, __LINE__ + 1) - if instance_variable_get(:@__#{USER_ACCESSOR_METHOD_NAME}_patched) - raise ArgumentError, 'An access matcher be_allowed_for/be_denied_for can be used only once per example (`it` block)' - end - instance_variable_set(:@__#{USER_ACCESSOR_METHOD_NAME}_patched, true) - def #{USER_ACCESSOR_METHOD_NAME} @#{USER_ACCESSOR_METHOD_NAME} ||= User.find(#{user.id}) end @@ -81,6 +78,13 @@ module AccessMatchersHelpers end end + def reset_matcher_environment + instance_eval(<<~CODE, __FILE__, __LINE__ + 1) + clear_memoization(:#{USER_ACCESSOR_METHOD_NAME}) + undef #{USER_ACCESSOR_METHOD_NAME} if defined? user + CODE + end + def run_matcher(action, role, membership, owned_objects) raise_if_non_block_expectation!(action) @@ -91,5 +95,7 @@ module AccessMatchersHelpers else action.call end + + reset_matcher_environment end end diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index 5510284b30d..4515b96c79e 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -7,6 +7,11 @@ module CycleAnalyticsHelpers page.find('[data-testid="dropdown-value-streams"]').click end + def path_nav_stage_names_without_median + # Returns the path names with the median value stripped out + page.all('.gl-path-button').collect(&:text).map {|name_with_median| name_with_median.split("\n")[0] } + end + def add_custom_stage_to_form page.find_button(s_('CreateValueStreamForm|Add another stage')).click diff --git a/spec/support/helpers/feature_flag_helpers.rb b/spec/support/helpers/feature_flag_helpers.rb index 93cd915879b..af7a674f3bc 100644 --- a/spec/support/helpers/feature_flag_helpers.rb +++ b/spec/support/helpers/feature_flag_helpers.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module FeatureFlagHelpers - def create_flag(project, name, active = true, description: nil, version: Operations::FeatureFlag.versions['legacy_flag']) + def create_flag(project, name, active = true, description: nil, version: Operations::FeatureFlag.versions['new_version_flag']) create(:operations_feature_flag, name: name, active: active, version: version, description: description, project: project) end @@ -90,6 +90,5 @@ module FeatureFlagHelpers def expect_user_to_see_feature_flags_index_page expect(page).to have_text('Feature Flags') - expect(page).to have_text('Lists') end end diff --git a/spec/support/helpers/features/top_nav_spec_helpers.rb b/spec/support/helpers/features/top_nav_spec_helpers.rb new file mode 100644 index 00000000000..ab664ce4283 --- /dev/null +++ b/spec/support/helpers/features/top_nav_spec_helpers.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# These helpers help you interact within the Editor Lite (single-file editor, snippets, etc.). +# +module Spec + module Support + module Helpers + module Features + module TopNavSpecHelpers + def open_top_nav + return unless Feature.enabled?(:combined_menu, default_enabled: :yaml) + + find('.js-top-nav-dropdown-toggle').click + end + + def within_top_nav + if Feature.enabled?(:combined_menu, default_enabled: :yaml) + within('.js-top-nav-dropdown-menu') do + yield + end + else + within('.navbar-sub-nav') do + yield + end + end + end + + def open_top_nav_projects + if Feature.enabled?(:combined_menu, default_enabled: :yaml) + open_top_nav + + within_top_nav do + click_button('Projects') + end + else + find('#nav-projects-dropdown').click + end + end + + def open_top_nav_groups + return unless Feature.enabled?(:combined_menu, default_enabled: :yaml) + + open_top_nav + + within_top_nav do + click_button('Groups') + end + end + end + end + end + end +end diff --git a/spec/support/helpers/gitaly_setup.rb b/spec/support/helpers/gitaly_setup.rb index 2ce4bcfa943..5cfd03ecea8 100644 --- a/spec/support/helpers/gitaly_setup.rb +++ b/spec/support/helpers/gitaly_setup.rb @@ -15,7 +15,7 @@ module GitalySetup default_name = ENV['CI'] ? 'DEBUG' : 'WARN' level_name = ENV['GITLAB_TESTING_LOG_LEVEL']&.upcase level = Logger.const_get(level_name || default_name, true) # rubocop: disable Gitlab/ConstGetInheritFalse - Logger.new(STDOUT, level: level, formatter: ->(_, _, _, msg) { msg }) + Logger.new($stdout, level: level, formatter: ->(_, _, _, msg) { msg }) end def tmp_tests_gitaly_dir @@ -153,7 +153,7 @@ module GitalySetup end LOGGER.debug "Checking gitaly-ruby bundle...\n" - out = ENV['CI'] ? STDOUT : '/dev/null' + out = ENV['CI'] ? $stdout : '/dev/null' abort 'bundle check failed' unless system(env, 'bundle', 'check', out: out, chdir: File.dirname(gemfile)) end diff --git a/spec/support/helpers/global_id_deprecation_helpers.rb b/spec/support/helpers/global_id_deprecation_helpers.rb new file mode 100644 index 00000000000..37ba1420fb3 --- /dev/null +++ b/spec/support/helpers/global_id_deprecation_helpers.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module GlobalIDDeprecationHelpers + def stub_global_id_deprecations(*deprecations) + old_name_map = deprecations.index_by(&:old_model_name) + new_name_map = deprecations.index_by(&:new_model_name) + old_graphql_name_map = deprecations.index_by { |d| Types::GlobalIDType.model_name_to_graphql_name(d.old_model_name) } + + stub_const('Gitlab::GlobalId::Deprecations::OLD_NAME_MAP', old_name_map) + stub_const('Gitlab::GlobalId::Deprecations::NEW_NAME_MAP', new_name_map) + stub_const('Gitlab::GlobalId::Deprecations::OLD_GRAPHQL_NAME_MAP', old_graphql_name_map) + end +end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 5dc6945ec5e..4857fa63114 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true module GraphqlHelpers + def self.included(base) + base.include(::Gitlab::Graphql::Laziness) + end + MutationDefinition = Struct.new(:query, :variables) NoData = Class.new(StandardError) diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index 28375c1d51e..8fd8a548011 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -66,6 +66,14 @@ module JavaScriptFixturesHelpers File.write(full_fixture_path, fixture) end + def parse_html(fixture) + if respond_to?(:use_full_html) && public_send(:use_full_html) + Nokogiri::HTML::Document.parse(fixture) + else + Nokogiri::HTML::DocumentFragment.parse(fixture) + end + end + # Private: Prepare a response object for use as a frontend fixture # # response - response object to prepare @@ -76,7 +84,7 @@ module JavaScriptFixturesHelpers response_mime_type = Mime::Type.lookup(response.media_type) if response_mime_type.html? - doc = Nokogiri::HTML::DocumentFragment.parse(fixture) + doc = parse_html(fixture) link_tags = doc.css('link') link_tags.remove diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb index fc3eb976276..cc88a3fc71e 100644 --- a/spec/support/helpers/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb @@ -77,7 +77,11 @@ module LoginHelpers # Requires Javascript driver. def gitlab_disable_admin_mode - click_on 'Leave Admin Mode' + open_top_nav + + within_top_nav do + click_on 'Leave Admin Mode' + end end private diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb index 05afbc336da..d18a1d23584 100644 --- a/spec/support/helpers/query_recorder.rb +++ b/spec/support/helpers/query_recorder.rb @@ -2,15 +2,16 @@ module ActiveRecord class QueryRecorder - attr_reader :log, :skip_cached, :cached, :data + attr_reader :log, :skip_cached, :skip_schema_queries, :cached, :data UNKNOWN = %w[unknown unknown].freeze - def initialize(skip_cached: true, log_file: nil, query_recorder_debug: false, &block) + def initialize(skip_cached: true, skip_schema_queries: true, log_file: nil, query_recorder_debug: false, &block) @data = Hash.new { |h, k| h[k] = { count: 0, occurrences: [], backtrace: [], durations: [] } } @log = [] @cached = [] @skip_cached = skip_cached + @skip_schema_queries = skip_schema_queries @query_recorder_debug = ENV['QUERY_RECORDER_DEBUG'] || query_recorder_debug @log_file = log_file record(&block) if block_given? @@ -79,7 +80,7 @@ module ActiveRecord if values[:cached] && skip_cached @cached << values[:sql] - elsif !values[:name]&.include?("SCHEMA") + elsif !skip_schema_queries || !values[:name]&.include?("SCHEMA") backtrace = @query_recorder_debug ? show_backtrace(values, duration) : nil @log << values[:sql] store_sql_by_source(values: values, duration: duration, backtrace: backtrace) diff --git a/spec/support/helpers/rake_helpers.rb b/spec/support/helpers/rake_helpers.rb index d8f354a69da..4c0fa9d1b0b 100644 --- a/spec/support/helpers/rake_helpers.rb +++ b/spec/support/helpers/rake_helpers.rb @@ -10,11 +10,6 @@ module RakeHelpers allow(main_object).to receive(:warn_user_is_not_gitlab) end - def silence_output - allow(main_object).to receive(:puts) - allow(main_object).to receive(:print) - end - def silence_progress_bar allow_any_instance_of(ProgressBar::Output).to receive(:stream).and_return(double.as_null_object) end diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb index e65cb8c96db..a6a7948d9d9 100644 --- a/spec/support/helpers/reference_parser_helpers.rb +++ b/spec/support/helpers/reference_parser_helpers.rb @@ -11,20 +11,20 @@ module ReferenceParserHelpers end RSpec.shared_examples 'no project N+1 queries' do - it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do + it 'avoids N+1 queries in #nodes_visible_to_user' do context = Banzai::RenderContext.new(project, user) - record_queries = lambda do |links| - ActiveRecord::QueryRecorder.new do - described_class.new(context).nodes_visible_to_user(user, links) - end + request = lambda do |links| + described_class.new(context).nodes_visible_to_user(user, links) end - control = record_queries.call(control_links) - actual = record_queries.call(actual_links) + control = ActiveRecord::QueryRecorder.new { request.call(control_links) } - expect(actual.count).to be <= control.count - expect(actual.cached_count).to be <= control.cached_count + create(:group_member, group: project.group) if project.group + create(:project_member, project: project) + create(:project_group_link, project: project) + + expect { request.call(actual_links) }.not_to exceed_query_limit(control) end end diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb index c6176b5bcbc..b1a9aade043 100644 --- a/spec/support/helpers/usage_data_helpers.rb +++ b/spec/support/helpers/usage_data_helpers.rb @@ -99,7 +99,6 @@ module UsageDataHelpers projects_with_repositories_enabled projects_with_error_tracking_enabled projects_with_enabled_alert_integrations - projects_with_prometheus_alerts projects_with_tracing_enabled projects_with_expiration_policy_enabled projects_with_expiration_policy_disabled @@ -163,7 +162,6 @@ module UsageDataHelpers database prometheus_metrics_enabled web_ide_clientside_preview_enabled - ingress_modsecurity_enabled object_store topology ).freeze diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb index a6b395ad4d5..5fb6af99b79 100644 --- a/spec/support/import_export/common_util.rb +++ b/spec/support/import_export/common_util.rb @@ -20,11 +20,11 @@ module ImportExport def setup_reader(reader) if reader == :ndjson_reader && Feature.enabled?(:project_import_ndjson, default_enabled: true) - allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:exist?).and_return(false) - allow_any_instance_of(Gitlab::ImportExport::JSON::NdjsonReader).to receive(:exist?).and_return(true) + allow_any_instance_of(Gitlab::ImportExport::Json::LegacyReader::File).to receive(:exist?).and_return(false) + allow_any_instance_of(Gitlab::ImportExport::Json::NdjsonReader).to receive(:exist?).and_return(true) else - allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:exist?).and_return(true) - allow_any_instance_of(Gitlab::ImportExport::JSON::NdjsonReader).to receive(:exist?).and_return(false) + allow_any_instance_of(Gitlab::ImportExport::Json::LegacyReader::File).to receive(:exist?).and_return(true) + allow_any_instance_of(Gitlab::ImportExport::Json::NdjsonReader).to receive(:exist?).and_return(false) end end diff --git a/spec/support/matchers/be_one_of.rb b/spec/support/matchers/be_one_of.rb new file mode 100644 index 00000000000..16ee32b67c3 --- /dev/null +++ b/spec/support/matchers/be_one_of.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec::Matchers.define :be_one_of do |collection| + match do |item| + expect(collection).to include(item) + end + + failure_message do |item| + "expected #{item} to be one of #{collection}" + end +end diff --git a/spec/support/redis.rb b/spec/support/redis.rb index 8539f202602..eeeb93fa811 100644 --- a/spec/support/redis.rb +++ b/spec/support/redis.rb @@ -30,4 +30,12 @@ RSpec.configure do |config| redis_queues_cleanup! end + + config.around(:each, :clean_gitlab_redis_trace_chunks) do |example| + redis_trace_chunks_cleanup! + + example.run + + redis_trace_chunks_cleanup! + end end diff --git a/spec/support/redis/redis_helpers.rb b/spec/support/redis/redis_helpers.rb index 7c571738a01..b8118bf94cc 100644 --- a/spec/support/redis/redis_helpers.rb +++ b/spec/support/redis/redis_helpers.rb @@ -17,4 +17,9 @@ module RedisHelpers def redis_shared_state_cleanup! Gitlab::Redis::SharedState.with(&:flushall) end + + # Usage: CI trace chunks + def redis_trace_chunks_cleanup! + Gitlab::Redis::TraceChunks.with(&:flushall) + end end diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb index f5f6a69738b..25eab5fd6e4 100644 --- a/spec/support/redis/redis_shared_examples.rb +++ b/spec/support/redis/redis_shared_examples.rb @@ -4,9 +4,22 @@ RSpec.shared_examples "redis_shared_examples" do include StubENV let(:test_redis_url) { "redis://redishost:#{redis_port}"} + let(:config_file_name) { instance_specific_config_file } + let(:config_old_format_socket) { "spec/fixtures/config/redis_old_format_socket.yml" } + let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" } + let(:old_socket_path) {"/path/to/old/redis.sock" } + let(:new_socket_path) {"/path/to/redis.sock" } + let(:config_old_format_host) { "spec/fixtures/config/redis_old_format_host.yml" } + let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" } + let(:redis_port) { 6379 } + let(:redis_database) { 99 } + let(:sentinel_port) { 26379 } + let(:config_with_environment_variable_inside) { "spec/fixtures/config/redis_config_with_env.yml"} + let(:config_env_variable_url) {"TEST_GITLAB_REDIS_URL"} + let(:rails_root) { Dir.mktmpdir('redis_shared_examples') } before do - stub_env(environment_config_file_name, Rails.root.join(config_file_name)) + allow(described_class).to receive(:config_file_name).and_return(Rails.root.join(config_file_name).to_s) clear_raw_config end @@ -14,8 +27,71 @@ RSpec.shared_examples "redis_shared_examples" do clear_raw_config end + describe '.config_file_name' do + subject { described_class.config_file_name } + + before do + # Undo top-level stub of config_file_name because we are testing that method now. + allow(described_class).to receive(:config_file_name).and_call_original + + allow(described_class).to receive(:rails_root).and_return(rails_root) + FileUtils.mkdir_p(File.join(rails_root, 'config')) + end + + after do + FileUtils.rm_rf(rails_root) + end + + context 'when there is no config file anywhere' do + it { expect(subject).to be_nil } + + context 'but resque.yml exists' do + before do + FileUtils.touch(File.join(rails_root, 'config', 'resque.yml')) + end + + it { expect(subject).to eq("#{rails_root}/config/resque.yml") } + + it 'returns a path that exists' do + expect(File.file?(subject)).to eq(true) + end + + context 'and there is a global env override' do + before do + stub_env('GITLAB_REDIS_CONFIG_FILE', 'global override') + end + + it { expect(subject).to eq('global override') } + + context 'and there is an instance specific config file' do + before do + FileUtils.touch(File.join(rails_root, instance_specific_config_file)) + end + + it { expect(subject).to eq("#{rails_root}/#{instance_specific_config_file}") } + + it 'returns a path that exists' do + expect(File.file?(subject)).to eq(true) + end + + context 'and there is a specific env override' do + before do + stub_env(environment_config_file_name, 'instance specific override') + end + + it { expect(subject).to eq('instance specific override') } + end + end + end + end + end + end + describe '.params' do - subject { described_class.params } + subject { described_class.new(rails_env).params } + + let(:rails_env) { 'development' } + let(:config_file_name) { config_old_format_socket } it 'withstands mutation' do params1 = described_class.params @@ -58,15 +134,27 @@ RSpec.shared_examples "redis_shared_examples" do context 'with new format' do let(:config_file_name) { config_new_format_host } - it 'returns hash with host, port, db, and password' do - is_expected.to include(host: 'localhost', password: 'mynewpassword', port: redis_port, db: redis_database) - is_expected.not_to have_key(:url) + where(:rails_env, :host) do + [ + %w[development development-host], + %w[test test-host], + %w[production production-host] + ] + end + + with_them do + it 'returns hash with host, port, db, and password' do + is_expected.to include(host: host, password: 'mynewpassword', port: redis_port, db: redis_database) + is_expected.not_to have_key(:url) + end end end end end describe '.url' do + let(:config_file_name) { config_old_format_socket } + it 'withstands mutation' do url1 = described_class.url url2 = described_class.url @@ -88,6 +176,12 @@ RSpec.shared_examples "redis_shared_examples" do end end + describe '.version' do + it 'returns a version' do + expect(described_class.version).to be_present + end + end + describe '._raw_config' do subject { described_class._raw_config } @@ -109,6 +203,8 @@ RSpec.shared_examples "redis_shared_examples" do end describe '.with' do + let(:config_file_name) { config_old_format_socket } + before do clear_pool end @@ -140,17 +236,46 @@ RSpec.shared_examples "redis_shared_examples" do described_class.with { |_redis_shared_example| true } end end + + context 'when there is no config at all' do + before do + # Undo top-level stub of config_file_name because we are testing that method now. + allow(described_class).to receive(:config_file_name).and_call_original + + allow(described_class).to receive(:rails_root).and_return(rails_root) + end + + after do + FileUtils.rm_rf(rails_root) + end + + it 'can run an empty block' do + expect { described_class.with { nil } }.not_to raise_error + end + end end describe '#sentinels' do - subject { described_class.new(Rails.env).sentinels } + subject { described_class.new(rails_env).sentinels } + + let(:rails_env) { 'development' } context 'when sentinels are defined' do let(:config_file_name) { config_new_format_host } - it 'returns an array of hashes with host and port keys' do - is_expected.to include(host: 'localhost', port: sentinel_port) - is_expected.to include(host: 'replica2', port: sentinel_port) + where(:rails_env, :hosts) do + [ + ['development', %w[development-replica1 development-replica2]], + ['test', %w[test-replica1 test-replica2]], + ['production', %w[production-replica1 production-replica2]] + ] + end + + with_them do + it 'returns an array of hashes with host and port keys' do + is_expected.to include(host: hosts[0], port: sentinel_port) + is_expected.to include(host: hosts[1], port: sentinel_port) + end end end @@ -184,12 +309,6 @@ RSpec.shared_examples "redis_shared_examples" do end describe '#raw_config_hash' do - it 'returns default redis url when no config file is present' do - expect(subject).to receive(:fetch_config) { false } - - expect(subject.send(:raw_config_hash)).to eq(url: class_redis_url ) - end - it 'returns old-style single url config in a hash' do expect(subject).to receive(:fetch_config) { test_redis_url } expect(subject.send(:raw_config_hash)).to eq(url: test_redis_url) diff --git a/spec/support/shared_contexts/changes_access_checks_shared_context.rb b/spec/support/shared_contexts/changes_access_checks_shared_context.rb new file mode 100644 index 00000000000..ec3727b6d6c --- /dev/null +++ b/spec/support/shared_contexts/changes_access_checks_shared_context.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +RSpec.shared_context 'changes access checks context' do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :repository) } + + let(:user_access) { Gitlab::UserAccess.new(user, container: project) } + let(:protocol) { 'ssh' } + let(:timeout) { Gitlab::GitAccess::INTERNAL_TIMEOUT } + let(:oldrev) { 'be93687618e4b132087f430a4d8fc3a609c9b77c' } + let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } + let(:ref) { 'refs/heads/master' } + let(:changes) do + [ + # Update of existing branch + { oldrev: oldrev, newrev: newrev, ref: ref }, + # Creation of new branch + { newrev: newrev, ref: 'refs/heads/something' }, + # Deletion of branch + { oldrev: oldrev, ref: 'refs/heads/deleteme' } + ] + end + + let(:logger) { Gitlab::Checks::TimedLogger.new(timeout: timeout) } + let(:changes_access) do + Gitlab::Checks::ChangesAccess.new( + changes, + project: project, + user_access: user_access, + protocol: protocol, + logger: logger + ) + end + + subject { described_class.new(changes_access) } + + before do + project.add_developer(user) + end +end diff --git a/spec/support/shared_contexts/features/integrations/group_integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/group_integrations_shared_context.rb new file mode 100644 index 00000000000..5996fcc6593 --- /dev/null +++ b/spec/support/shared_contexts/features/integrations/group_integrations_shared_context.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +RSpec.shared_context 'group integration activation' do + include_context 'instance and group integration activation' + + let_it_be(:group) { create(:group) } + let_it_be(:user) { create(:user) } + + before_all do + group.add_owner(user) + end + + before do + sign_in(user) + end + + def visit_group_integrations + visit group_settings_integrations_path(group) + end + + def visit_group_integration(name) + visit_group_integrations + + within('#content-body') do + click_link(name) + end + end +end diff --git a/spec/support/shared_contexts/features/integrations/instance_and_group_integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/instance_and_group_integrations_shared_context.rb new file mode 100644 index 00000000000..58ee341f71f --- /dev/null +++ b/spec/support/shared_contexts/features/integrations/instance_and_group_integrations_shared_context.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +RSpec.shared_context 'instance and group integration activation' do + include_context 'integration activation' + + def click_save_integration + click_save_changes_button + click_save_settings_modal + end + + def click_save_changes_button + click_button('Save changes') + end + + def click_save_settings_modal + click_button('Save') + end +end diff --git a/spec/support/shared_contexts/features/integrations/instance_integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/instance_integrations_shared_context.rb new file mode 100644 index 00000000000..3b02db994a3 --- /dev/null +++ b/spec/support/shared_contexts/features/integrations/instance_integrations_shared_context.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +RSpec.shared_context 'instance integration activation' do + include_context 'instance and group integration activation' + + let_it_be(:user) { create(:user, :admin) } + + before do + sign_in(user) + gitlab_enable_admin_mode_sign_in(user) + end + + def visit_instance_integrations + visit integrations_admin_application_settings_path + end + + def visit_instance_integration(name) + visit_instance_integrations + + within('#content-body') do + click_link(name) + end + end +end diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb index 34c92367efa..e532b42fd1c 100644 --- a/spec/support/shared_contexts/services_shared_context.rb +++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb @@ -5,8 +5,8 @@ Integration.available_services_names.each do |service| include JiraServiceHelper if service == 'jira' let(:dashed_service) { service.dasherize } - let(:service_method) { "#{service}_service".to_sym } - let(:service_klass) { Integration.service_name_to_model(service) } + let(:service_method) { Project.integration_association_name(service) } + let(:service_klass) { Integration.integration_name_to_model(service) } let(:service_instance) { service_klass.new } let(:service_fields) { service_instance.fields } let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } } @@ -70,3 +70,9 @@ Integration.available_services_names.each do |service| end end end + +RSpec.shared_context 'integration activation' do + def click_active_checkbox + find('label', text: 'Active').click + end +end diff --git a/spec/support/shared_contexts/project_service_jira_context.rb b/spec/support/shared_contexts/features/integrations/project_integrations_jira_context.rb index 54bb9fd108e..54bb9fd108e 100644 --- a/spec/support/shared_contexts/project_service_jira_context.rb +++ b/spec/support/shared_contexts/features/integrations/project_integrations_jira_context.rb diff --git a/spec/support/shared_contexts/project_service_shared_context.rb b/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb index 0e3540a3e15..b10844320d0 100644 --- a/spec/support/shared_contexts/project_service_shared_context.rb +++ b/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true RSpec.shared_context 'project service activation' do + include_context 'integration activation' + let(:project) { create(:project) } let(:user) { create(:user) } @@ -21,10 +23,6 @@ RSpec.shared_context 'project service activation' do end end - def click_active_checkbox - find('label', text: 'Active').click - end - def click_save_integration click_button('Save changes') end diff --git a/spec/support/shared_contexts/graphql/requests/packages_shared_context.rb b/spec/support/shared_contexts/graphql/requests/packages_shared_context.rb new file mode 100644 index 00000000000..334b11c9f6e --- /dev/null +++ b/spec/support/shared_contexts/graphql/requests/packages_shared_context.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +RSpec.shared_context 'package details setup' do + let_it_be(:project) { create(:project) } + let_it_be(:package) { create(:package, project: project) } + + let(:package_global_id) { global_id_of(package) } + + let(:depth) { 3 } + let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] } + let(:package_files) { all_graphql_fields_for('PackageFile') } + let(:user) { project.owner } + let(:package_details) { graphql_data_at(:package) } + let(:metadata_response) { graphql_data_at(:package, :metadata) } + let(:first_file) { package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } } + let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) } + let(:first_file_response) { graphql_data_at(:package, :package_files, :nodes, 0)} + let(:first_file_response_metadata) { graphql_data_at(:package, :package_files, :nodes, 0, :file_metadata)} + + let(:query) do + graphql_query_for(:package, { id: package_global_id }, <<~FIELDS) + #{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)} + metadata { + #{metadata} + } + packageFiles { + nodes { + #{package_files} + } + } + FIELDS + end +end diff --git a/spec/support/shared_contexts/load_balancing_configuration_shared_context.rb b/spec/support/shared_contexts/load_balancing_configuration_shared_context.rb new file mode 100644 index 00000000000..a61b8e9a074 --- /dev/null +++ b/spec/support/shared_contexts/load_balancing_configuration_shared_context.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +RSpec.shared_context 'clear DB Load Balancing configuration' do + def clear_load_balancing_configuration + proxy = ::Gitlab::Database::LoadBalancing.instance_variable_get(:@proxy) + proxy.load_balancer.release_host if proxy + ::Gitlab::Database::LoadBalancing.instance_variable_set(:@proxy, nil) + + ::Gitlab::Database::LoadBalancing::Session.clear_session + end + + around do |example| + clear_load_balancing_configuration + + example.run + + clear_load_balancing_configuration + end +end diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb index 4f8e88ae9da..c00b7203af6 100644 --- a/spec/support/shared_contexts/navbar_structure_context.rb +++ b/spec/support/shared_contexts/navbar_structure_context.rb @@ -18,8 +18,8 @@ RSpec.shared_context 'project navbar structure' do { nav_item: _('Security & Compliance'), nav_sub_items: [ - _('Configuration'), - (_('Audit Events') if Gitlab.ee?) + (_('Audit Events') if Gitlab.ee?), + _('Configuration') ] } end @@ -71,8 +71,16 @@ RSpec.shared_context 'project navbar structure' do ] end + let(:project_context_nav_item) do + { + nav_item: "#{project.name[0, 1].upcase} #{project.name}", + nav_sub_items: [] + } + end + let(:structure) do [ + project_context_nav_item, project_information_nav_item, { nav_item: _('Repository'), @@ -200,8 +208,16 @@ RSpec.shared_context 'group navbar structure' do ] end + let(:group_context_nav_item) do + { + nav_item: "#{group.name[0, 1].upcase} #{group.name}", + nav_sub_items: [] + } + end + let(:structure) do [ + group_context_nav_item, group_information_nav_item, { nav_item: _('Issues'), diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb index 35dc709b5d9..d638ffcf8fa 100644 --- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb @@ -26,7 +26,7 @@ RSpec.shared_context 'ProjectPolicy context' do let(:base_reporter_permissions) do %i[ admin_issue admin_issue_link admin_label admin_issue_board_list create_snippet - download_code download_wiki_code fork_project metrics_dashboard + daily_statistics download_code download_wiki_code fork_project metrics_dashboard read_build read_commit_status read_confidential_issues read_container_image read_deployment read_environment read_merge_request read_metrics_dashboard_annotation read_pipeline read_prometheus @@ -44,7 +44,7 @@ RSpec.shared_context 'ProjectPolicy context' do create_commit_status create_container_image create_deployment create_environment create_merge_request_from create_metrics_dashboard_annotation create_pipeline create_release - create_wiki daily_statistics delete_metrics_dashboard_annotation + create_wiki delete_metrics_dashboard_annotation destroy_container_image push_code read_pod_logs read_terraform_state resolve_note update_build update_commit_status update_container_image update_deployment update_environment update_merge_request diff --git a/spec/support/shared_contexts/read_ci_configuration_shared_context.rb b/spec/support/shared_contexts/read_ci_configuration_shared_context.rb index 04c50171766..f5d70d5ef5a 100644 --- a/spec/support/shared_contexts/read_ci_configuration_shared_context.rb +++ b/spec/support/shared_contexts/read_ci_configuration_shared_context.rb @@ -5,10 +5,6 @@ RSpec.shared_context 'read ci configuration for sast enabled project' do File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_for_sast.yml')) end - let_it_be(:gitlab_ci_yml_default_analyzers_content) do - File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_for_sast_default_analyzers.yml')) - end - let_it_be(:gitlab_ci_yml_excluded_analyzers_content) do File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_for_sast_excluded_analyzers.yml')) end diff --git a/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb b/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb index ac53be1a1cb..c69a987c00d 100644 --- a/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb +++ b/spec/support/shared_contexts/requests/api/conan_packages_shared_context.rb @@ -8,11 +8,11 @@ RSpec.shared_context 'conan api setup' do let_it_be(:personal_access_token) { create(:personal_access_token) } let_it_be(:user) { personal_access_token.user } let_it_be(:base_secret) { SecureRandom.base64(64) } - let_it_be(:job) { create(:ci_build, :running, user: user) } - let_it_be(:job_token) { job.token } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let(:project) { package.project } + let(:job) { create(:ci_build, :running, user: user, project: project) } + let(:job_token) { job.token } let(:auth_token) { personal_access_token.token } let(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } diff --git a/spec/support/shared_contexts/requests/api/helm_packages_shared_context.rb b/spec/support/shared_contexts/requests/api/helm_packages_shared_context.rb new file mode 100644 index 00000000000..099fdec0cc8 --- /dev/null +++ b/spec/support/shared_contexts/requests/api/helm_packages_shared_context.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +RSpec.shared_context 'helm api setup' do + include WorkhorseHelpers + include PackagesManagerApiSpecHelpers + include HttpBasicAuthHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } +end diff --git a/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb b/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb index 815108be447..c737091df48 100644 --- a/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb +++ b/spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb @@ -11,7 +11,7 @@ RSpec.shared_context 'npm api setup' do let_it_be(:package, reload: true) { create(:npm_package, project: project, name: "@#{group.path}/scoped_package") } let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } - let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) } + let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running, project: project) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } diff --git a/spec/support/shared_contexts/services/packages/debian/distribution_shared_context.rb b/spec/support/shared_contexts/services/packages/debian/distribution_shared_context.rb new file mode 100644 index 00000000000..67e2c0629cc --- /dev/null +++ b/spec/support/shared_contexts/services/packages/debian/distribution_shared_context.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +RSpec.shared_context 'with published Debian package' do + let_it_be(:group) { create(:group, :public) } + let_it_be(:project) { create(:project, :public, group: group) } + let_it_be(:project_distribution) { create(:debian_project_distribution, container: project, codename: 'unstable', valid_time_duration_seconds: 48.hours.to_i) } + let_it_be(:package) { create(:debian_package, project: project, published_in: project_distribution) } +end + +RSpec.shared_context 'with Debian distribution' do |container_type| + let_it_be(:container_type) { container_type } + + if container_type == :project + let_it_be(:container) { project } + let_it_be(:distribution, reload: true) { project_distribution } + else + let_it_be(:container) { group } + let_it_be(:distribution, reload: true) { create(:debian_group_distribution, container: group, codename: 'unstable', valid_time_duration_seconds: 48.hours.to_i) } + end +end diff --git a/spec/support/shared_contexts/change_access_checks_shared_context.rb b/spec/support/shared_contexts/single_change_access_checks_shared_context.rb index 4c55990c901..bf90c26047b 100644 --- a/spec/support/shared_contexts/change_access_checks_shared_context.rb +++ b/spec/support/shared_contexts/single_change_access_checks_shared_context.rb @@ -12,7 +12,7 @@ RSpec.shared_context 'change access checks context' do let(:timeout) { Gitlab::GitAccess::INTERNAL_TIMEOUT } let(:logger) { Gitlab::Checks::TimedLogger.new(timeout: timeout) } let(:change_access) do - Gitlab::Checks::ChangeAccess.new( + Gitlab::Checks::SingleChangeAccess.new( changes, project: project, user_access: user_access, diff --git a/spec/support/shared_examples/ci/badge_template_shared_examples.rb b/spec/support/shared_examples/ci/badge_template_shared_examples.rb new file mode 100644 index 00000000000..94aec33ecc2 --- /dev/null +++ b/spec/support/shared_examples/ci/badge_template_shared_examples.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a badge template' do |badge_type| + describe '#key_text' do + it "says #{badge_type} by default" do + expect(template.key_text).to eq(badge_type) + end + + context 'when custom key_text is defined' do + before do + allow(badge).to receive(:customization).and_return({ key_text: "custom text" }) + end + + it 'returns custom value' do + expect(template.key_text).to eq("custom text") + end + + context 'when its size is larger than the max allowed value' do + before do + allow(badge).to receive(:customization).and_return({ key_text: 't' * (::Gitlab::Ci::Badge::Template::MAX_KEY_TEXT_SIZE + 1) } ) + end + + it 'returns default value' do + expect(template.key_text).to eq(badge_type) + end + end + end + end + + describe '#key_width' do + let_it_be(:default_key_width) { ::Gitlab::Ci::Badge::Template::DEFAULT_KEY_WIDTH } + + it 'is fixed by default' do + expect(template.key_width).to eq(default_key_width) + end + + context 'when custom key_width is defined' do + before do + allow(badge).to receive(:customization).and_return({ key_width: 101 }) + end + + it 'returns custom value' do + expect(template.key_width).to eq(101) + end + + context 'when it is larger than the max allowed value' do + before do + allow(badge).to receive(:customization).and_return({ key_width: ::Gitlab::Ci::Badge::Template::MAX_KEY_WIDTH + 1 }) + end + + it 'returns default value' do + expect(template.key_width).to eq(default_key_width) + end + end + end + end +end diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb index cfee26a0d6a..9af35c189d0 100644 --- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb @@ -298,7 +298,7 @@ RSpec.shared_examples 'wiki controller actions' do expect(response.headers['Content-Disposition']).to match(/^inline/) expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true') expect(response.cache_control[:public]).to be(false) - expect(response.cache_control[:extras]).to include('no-store') + expect(response.headers['Cache-Control']).to eq('no-store') end end end @@ -486,7 +486,7 @@ RSpec.shared_examples 'wiki controller actions' do end.not_to change { wiki.list_pages.size } expect(response).to render_template('shared/wikis/edit') - expect(assigns(:error).message).to eq('Could not delete wiki page') + expect(assigns(:error)).to eq('Could not delete wiki page') end end end diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb index 808e0be6be2..ff2878f77b4 100644 --- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb +++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb @@ -11,6 +11,8 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name let(:comment) { 'My comment' } it 'clicking "Comment" will post a comment' do + wait_for_all_requests + expect(page).to have_selector toggle_selector find("#{form_selector} .note-textarea").send_keys(comment) @@ -29,6 +31,8 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name find("#{form_selector} .note-textarea").send_keys(comment) find(toggle_selector).click + + wait_for_all_requests end it 'has a "Comment" item (selected by default) and "Start thread" item' do diff --git a/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb b/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb new file mode 100644 index 00000000000..cfa043322db --- /dev/null +++ b/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'user activates the Mattermost Slash Command integration' do + it 'shows a help message' do + expect(page).to have_content('Use this service to perform common') + end + + it 'shows a token placeholder' do + token_placeholder = find_field('service_token')['placeholder'] + + expect(token_placeholder).to eq('XXxxXXxxXXxxXXxxXXxxXXxx') + end + + it 'redirects to the integrations page after saving but not activating' do + token = ('a'..'z').to_a.join + + fill_in 'service_token', with: token + click_active_checkbox + click_save_integration + + expect(current_path).to eq(edit_path) + expect(page).to have_content('Mattermost slash commands settings saved, but not active.') + end + + it 'redirects to the integrations page after activating' do + token = ('a'..'z').to_a.join + + fill_in 'service_token', with: token + click_save_integration + + expect(current_path).to eq(edit_path) + expect(page).to have_content('Mattermost slash commands settings saved and active.') + end +end diff --git a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb index 736c353c2aa..c0cfc27ceaf 100644 --- a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb +++ b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true -RSpec.shared_examples 'issuable invite members experiments' do +RSpec.shared_examples 'issuable invite members' do context 'when a privileged user can invite' do - it 'shows a link for inviting members and launches invite modal' do + before do project.add_maintainer(user) + end + + it 'shows a link for inviting members and launches invite modal' do visit issuable_path find('.block.assignee .edit-link').click @@ -23,8 +26,11 @@ RSpec.shared_examples 'issuable invite members experiments' do end context 'when user cannot invite members in assignee dropdown' do - it 'shows author in assignee dropdown and no invite link' do + before do project.add_developer(user) + end + + it 'shows author in assignee dropdown and no invite link' do visit issuable_path find('.block.assignee .edit-link').click diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb index 4b94411f009..997500415a9 100644 --- a/spec/support/shared_examples/features/variable_list_shared_examples.rb +++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb @@ -283,6 +283,8 @@ RSpec.shared_examples 'variable list' do end def fill_variable(key, value, protected: false, masked: false) + wait_for_requests + page.within('#add-ci-variable') do find('[data-qa-selector="ci_variable_key_field"] input').set(key) find('[data-qa-selector="ci_variable_value_field"]').set(value) if value.present? diff --git a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb index 96b05db4cd9..5cbbed1468f 100644 --- a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb +++ b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb @@ -24,6 +24,12 @@ RSpec.shared_examples 'assignee NOT username filter' do end end +RSpec.shared_examples 'assignee OR filter' do + it 'returns issuables assigned to the given users' do + expect(issuables).to contain_exactly(*expected_issuables) + end +end + RSpec.shared_examples 'no assignee filter' do let(:params) { { assignee_id: 'None' } } diff --git a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb index fc795012ce7..5e15c91cd41 100644 --- a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb @@ -6,9 +6,9 @@ RSpec.shared_examples 'a mutation which can mutate a spammable' do describe "#additional_spam_params" do it 'passes additional spam params to the service' do args = [ - anything, - anything, - hash_including( + project: anything, + current_user: anything, + params: hash_including( api: true, request: instance_of(ActionDispatch::Request), captcha_response: captcha_response, diff --git a/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb index ebba312e895..678bb908343 100644 --- a/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb @@ -2,44 +2,37 @@ require 'spec_helper' -RSpec.shared_examples 'a subscribeable graphql resource' do - let(:project) { resource.project } - let_it_be(:user) { create(:user) } - - subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } +RSpec.shared_examples 'a subscribeable not accessible graphql resource' do + let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } - specify { expect(described_class).to require_graphql_authorizations(permission_name) } + subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, subscribed_state: true) } - describe '#resolve' do - let(:subscribe) { true } - let(:mutated_resource) { subject[resource.class.name.underscore.to_sym] } - - subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, subscribed_state: subscribe) } + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end +end - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end +RSpec.shared_examples 'a subscribeable graphql resource' do + let(:mutated_resource) { subject[resource.class.name.underscore.to_sym] } + let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + let(:subscribe) { true } - context 'when the user can update the resource' do - before do - resource.project.add_developer(user) - end + subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, subscribed_state: subscribe) } - it 'subscribes to the resource' do - expect(mutated_resource).to eq(resource) - expect(mutated_resource.subscribed?(user, project)).to eq(true) - expect(subject[:errors]).to be_empty - end + it 'subscribes to the resource' do + expect(mutated_resource).to eq(resource) + expect(mutated_resource.subscribed?(user, project)).to eq(true) + expect(subject[:errors]).to be_empty + end - context 'when passing subscribe as false' do - let(:subscribe) { false } + context 'when passing subscribe as false' do + let(:subscribe) { false } - it 'unsubscribes from the discussion' do - resource.subscribe(user, project) + it 'unsubscribes from the discussion' do + resource.subscribe(user, project) - expect(mutated_resource.subscribed?(user, project)).to eq(false) - end - end + expect(mutated_resource.subscribed?(user, project)).to eq(false) + expect(subject[:errors]).to be_empty end end end diff --git a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb index 9c95d1ff9d9..3760325675a 100644 --- a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb @@ -29,6 +29,34 @@ RSpec.shared_examples 'common trace features' do end end + describe '#read' do + context 'gitlab_ci_archived_trace_consistent_reads feature flag enabled' do + before do + stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: trace.job.project) + end + + it 'calls ::Gitlab::Database::LoadBalancing::Sticking.unstick_or_continue_sticking' do + expect(::Gitlab::Database::LoadBalancing::Sticking).to receive(:unstick_or_continue_sticking) + .with(described_class::LOAD_BALANCING_STICKING_NAMESPACE, trace.job.id) + .and_call_original + + trace.read { |stream| stream } + end + end + + context 'gitlab_ci_archived_trace_consistent_reads feature flag disabled' do + before do + stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: false) + end + + it 'does not call ::Gitlab::Database::LoadBalancing::Sticking.unstick_or_continue_sticking' do + expect(::Gitlab::Database::LoadBalancing::Sticking).not_to receive(:unstick_or_continue_sticking) + + trace.read { |stream| stream } + end + end + end + describe '#extract_coverage' do let(:regex) { '\(\d+.\d+\%\) covered' } @@ -253,6 +281,52 @@ RSpec.shared_examples 'common trace features' do describe '#archive!' do subject { trace.archive! } + context 'when live trace chunks exists' do + before do + # Build a trace_chunk manually + # It is possible to do so with trace.set but only if ci_enable_live_trace FF is enabled + # + # We need the job to have a trace_chunk because we only use #stick in + # the case where trace_chunks exist. + stream = Gitlab::Ci::Trace::Stream.new do + Gitlab::Ci::Trace::ChunkedIO.new(trace.job) + end + + stream.set(+"12\n34") + end + + # We check the before setup actually sets up job trace_chunks + it 'has job trace_chunks' do + expect(trace.job.trace_chunks).to be_present + end + + context 'gitlab_ci_archived_trace_consistent_reads feature flag enabled' do + before do + stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: trace.job.project) + end + + it 'calls ::Gitlab::Database::LoadBalancing::Sticking.stick' do + expect(::Gitlab::Database::LoadBalancing::Sticking).to receive(:stick) + .with(described_class::LOAD_BALANCING_STICKING_NAMESPACE, trace.job.id) + .and_call_original + + subject + end + end + + context 'gitlab_ci_archived_trace_consistent_reads feature flag disabled' do + before do + stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: false) + end + + it 'does not call ::Gitlab::Database::LoadBalancing::Sticking.stick' do + expect(::Gitlab::Database::LoadBalancing::Sticking).not_to receive(:stick) + + subject + end + end + end + context 'when build status is success' do let!(:build) { create(:ci_build, :success, :trace_live) } diff --git a/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb new file mode 100644 index 00000000000..5baa6478225 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'tracks assignment and records the subject' do |experiment, subject_type| + it 'tracks the assignment', :experiment do + expect(experiment(experiment)) + .to track(:assignment) + .with_context(subject_type => subject) + .on_next_instance + + action + end + + it 'records the subject' do + stub_experiments(experiment => :candidate) + + expect(Experiment).to receive(:add_subject).with(experiment.to_s, variant: :experimental, subject: subject) + + action + end +end diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb index 4a47aad0957..9f3be3e2e06 100644 --- a/spec/support/shared_examples/models/chat_service_shared_examples.rb +++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -RSpec.shared_examples "chat service" do |service_name| +RSpec.shared_examples "chat integration" do |integration_name| describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } end describe "Validations" do - context "when service is active" do + context "when integration is active" do before do subject.active = true end @@ -16,7 +16,7 @@ RSpec.shared_examples "chat service" do |service_name| it_behaves_like "issue tracker service URL attribute", :webhook end - context "when service is inactive" do + context "when integration is inactive" do before do subject.active = false end @@ -47,12 +47,12 @@ RSpec.shared_examples "chat service" do |service_name| WebMock.stub_request(:post, webhook_url) end - shared_examples "triggered #{service_name} service" do |branches_to_be_notified: nil| + shared_examples "triggered #{integration_name} integration" do |branches_to_be_notified: nil| before do subject.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified end - it "calls #{service_name} API" do + it "calls #{integration_name} API" do result = subject.execute(sample_data) expect(result).to be(true) @@ -63,12 +63,12 @@ RSpec.shared_examples "chat service" do |service_name| end end - shared_examples "untriggered #{service_name} service" do |branches_to_be_notified: nil| + shared_examples "untriggered #{integration_name} integration" do |branches_to_be_notified: nil| before do subject.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified end - it "does not call #{service_name} API" do + it "does not call #{integration_name} API" do result = subject.execute(sample_data) expect(result).to be(false) @@ -81,7 +81,7 @@ RSpec.shared_examples "chat service" do |service_name| Gitlab::DataBuilder::Push.build_sample(project, user) end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" it "specifies the webhook when it is configured", if: defined?(client) do expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object) @@ -95,19 +95,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -121,19 +121,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -143,19 +143,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end end @@ -168,7 +168,7 @@ RSpec.shared_examples "chat service" do |service_name| service.hook_data(issue, "open") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with merge events" do @@ -191,7 +191,7 @@ RSpec.shared_examples "chat service" do |service_name| project.add_developer(user) end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with wiki page events" do @@ -207,7 +207,7 @@ RSpec.shared_examples "chat service" do |service_name| let(:wiki_page) { create(:wiki_page, wiki: project.wiki, **opts) } let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, "create") } - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with note events" do @@ -222,7 +222,7 @@ RSpec.shared_examples "chat service" do |service_name| note: "a comment on a commit") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with merge request comment" do @@ -230,7 +230,7 @@ RSpec.shared_examples "chat service" do |service_name| create(:note_on_merge_request, project: project, note: "merge request note") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with issue comment" do @@ -238,7 +238,7 @@ RSpec.shared_examples "chat service" do |service_name| create(:note_on_issue, project: project, note: "issue note") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with snippet comment" do @@ -246,7 +246,7 @@ RSpec.shared_examples "chat service" do |service_name| create(:note_on_project_snippet, project: project, note: "snippet note") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end end @@ -262,14 +262,14 @@ RSpec.shared_examples "chat service" do |service_name| context "with failed pipeline" do let(:status) { "failed" } - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with succeeded pipeline" do let(:status) { "success" } context "with default notify_only_broken_pipelines" do - it "does not call #{service_name} API" do + it "does not call #{integration_name} API" do result = subject.execute(sample_data) expect(result).to be_falsy @@ -281,7 +281,7 @@ RSpec.shared_examples "chat service" do |service_name| subject.notify_only_broken_pipelines = false end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end end @@ -291,19 +291,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -317,19 +317,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -339,19 +339,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end end diff --git a/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb index 09b7d1be704..66448aca2c5 100644 --- a/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -RSpec.shared_examples 'slack or mattermost notifications' do |service_name| +RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name| include StubRequests let(:chat_service) { described_class.new } let(:webhook_url) { 'https://example.gitlab.com' } def execute_with_options(options) - receive(:new).with(webhook_url, options.merge(http_client: SlackMattermost::Notifier::HTTPClient)) + receive(:new).with(webhook_url, options.merge(http_client: Integrations::SlackMattermostNotifier::HTTPClient)) .and_return(double(:slack_service).as_null_object) end @@ -81,7 +81,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| shared_examples 'calls the service API with the event message' do |event_message| specify do - expect_next_instance_of(Slack::Messenger) do |messenger| + expect_next_instance_of(::Slack::Messenger) do |messenger| expect(messenger).to receive(:ping).with(event_message, anything).and_call_original end @@ -95,7 +95,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { username: 'slack_username' } } it 'uses the username as an option' do - expect(Slack::Messenger).to execute_with_options(username: 'slack_username') + expect(::Slack::Messenger).to execute_with_options(username: 'slack_username') execute_service end @@ -110,7 +110,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { push_channel: 'random' } } it 'uses the right channel for push event' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -128,6 +128,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| context 'issue events' do let_it_be(:issue) { create(:issue) } + let(:data) { issue.to_hook_data(user) } it_behaves_like 'calls the service API with the event message', /Issue (.*?) opened by/ @@ -136,7 +137,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { issue_channel: 'random' } } it 'uses the right channel for issue event' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -147,7 +148,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| end it 'falls back to issue channel' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -156,7 +157,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { issue_channel: 'random', confidential_issue_channel: 'confidential' } } it 'uses the confidential issue channel when it is defined' do - expect(Slack::Messenger).to execute_with_options(channel: ['confidential']) + expect(::Slack::Messenger).to execute_with_options(channel: ['confidential']) execute_service end @@ -167,6 +168,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| context 'merge request events' do let_it_be(:merge_request) { create(:merge_request) } + let(:data) { merge_request.to_hook_data(user) } it_behaves_like 'calls the service API with the event message', /opened merge request/ @@ -175,7 +177,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { merge_request_channel: 'random' } } it 'uses the right channel for merge request event' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -184,15 +186,16 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| context 'wiki page events' do let_it_be(:wiki_page) { create(:wiki_page, wiki: project.wiki, message: 'user created page: Awesome wiki_page') } + let(:data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') } - it_behaves_like 'calls the service API with the event message', / created (.*?)wikis\/(.*?)|wiki page> in/ + it_behaves_like 'calls the service API with the event message', %r{ created (.*?)wikis/(.*?)|wiki page> in} context 'with event channel' do let(:chat_service_params) { { wiki_page_channel: 'random' } } it 'uses the right channel for wiki event' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -201,6 +204,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| context 'deployment events' do let_it_be(:deployment) { create(:deployment) } + let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.current) } it_behaves_like 'calls the service API with the event message', /Deploy to (.*?) created/ @@ -208,6 +212,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| context 'note event' do let_it_be(:issue_note) { create(:note_on_issue, project: project, note: "issue note") } + let(:data) { Gitlab::DataBuilder::Note.build(issue_note, user) } it_behaves_like 'calls the service API with the event message', /commented on issue/ @@ -216,7 +221,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { note_channel: 'random' } } it 'uses the right channel' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -227,7 +232,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| end it 'falls back to note channel' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -236,7 +241,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { note_channel: 'random', confidential_note_channel: 'confidential' } } it 'uses confidential channel' do - expect(Slack::Messenger).to execute_with_options(channel: ['confidential']) + expect(::Slack::Messenger).to execute_with_options(channel: ['confidential']) execute_service end diff --git a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb index 68142e667a4..39121b73bc5 100644 --- a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb @@ -86,45 +86,6 @@ RSpec.shared_examples 'a timebox' do |timebox_type| expect(timebox.errors[:project_id]).to include("#{timebox_type} should belong either to a project or a group.") end end - - describe "#uniqueness_of_title" do - context "per project" do - it "does not accept the same title in a project twice" do - new_timebox = timebox.dup - expect(new_timebox).not_to be_valid - end - - it "accepts the same title in another project" do - project = create(:project) - new_timebox = timebox.dup - new_timebox.project = project - - expect(new_timebox).to be_valid - end - end - - context "per group" do - let(:timebox) { create(timebox_type, *timebox_args, group: group) } - - before do - project.update!(group: group) - end - - it "does not accept the same title in a group twice" do - new_timebox = described_class.new(group: group, title: timebox.title) - - expect(new_timebox).not_to be_valid - end - - it "does not accept the same title of a child project timebox" do - create(timebox_type, *timebox_args, project: group.projects.first) - - new_timebox = described_class.new(group: group, title: timebox.title) - - expect(new_timebox).not_to be_valid - end - end - end end describe "Associations" do diff --git a/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb index 49729afce61..128999d02fa 100644 --- a/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb +++ b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'chat slash commands service' do +RSpec.shared_examples Integrations::BaseSlashCommands do describe "Associations" do it { is_expected.to respond_to :token } it { is_expected.to have_many :chat_names } diff --git a/spec/support/shared_examples/models/mentionable_shared_examples.rb b/spec/support/shared_examples/models/mentionable_shared_examples.rb index 2392658e584..04630484964 100644 --- a/spec/support/shared_examples/models/mentionable_shared_examples.rb +++ b/spec/support/shared_examples/models/mentionable_shared_examples.rb @@ -66,7 +66,7 @@ RSpec.shared_examples 'a mentionable' do expect(subject.gfm_reference).to eq(backref_text) end - it "extracts references from its reference property" do + it "extracts references from its reference property", :clean_gitlab_redis_cache do # De-duplicate and omit itself refs = subject.referenced_mentionables expect(refs.size).to eq(6) @@ -98,7 +98,7 @@ RSpec.shared_examples 'a mentionable' do end end - it 'creates cross-reference notes' do + it 'creates cross-reference notes', :clean_gitlab_redis_cache do mentioned_objects = [mentioned_issue, mentioned_mr, mentioned_commit, ext_issue, ext_mr, ext_commit] diff --git a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb index e6b16d5881d..f08ee820463 100644 --- a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb +++ b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb @@ -142,6 +142,14 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| end end + describe '.with_architecture' do + subject { described_class.with_architecture(architecture1_2) } + + it do + expect(subject.to_a).to contain_exactly(component_file_other_architecture) + end + end + describe '.with_architecture_name' do subject { described_class.with_architecture_name(architecture1_2.name) } @@ -166,12 +174,12 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| end end - describe '.created_before' do - let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 4.hours.ago) } - let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 3.hours.ago) } - let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 1.hour.ago) } + describe '.updated_before' do + let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, updated_at: 4.hours.ago) } + let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, updated_at: 3.hours.ago) } + let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, updated_at: 1.hour.ago) } - subject { described_class.created_before(2.hours.ago) } + subject { described_class.updated_before(2.hours.ago) } it do expect(subject.to_a).to contain_exactly(component_file1, component_file2) diff --git a/spec/support/shared_examples/models/packages/debian/distribution_key_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_key_shared_examples.rb new file mode 100644 index 00000000000..26794c83736 --- /dev/null +++ b/spec/support/shared_examples/models/packages/debian/distribution_key_shared_examples.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'Debian Distribution Key' do |container| + let_it_be_with_refind(:distribution_key) { create("debian_#{container}_distribution_key") } # rubocop:disable Rails/SaveBang + + subject { distribution_key } + + describe 'relationships' do + it { is_expected.to belong_to(:distribution).class_name("Packages::Debian::#{container.capitalize}Distribution").inverse_of(:key) } + end + + describe 'validations' do + describe "#distribution" do + it { is_expected.to validate_presence_of(:distribution) } + end + + describe '#private_key' do + it { is_expected.to validate_presence_of(:private_key) } + + it { is_expected.to allow_value("-----BEGIN PGP PRIVATE KEY BLOCK-----\n...").for(:private_key) } + it { is_expected.not_to allow_value('A').for(:private_key).with_message('must be ASCII armored') } + end + + describe '#passphrase' do + it { is_expected.to validate_presence_of(:passphrase) } + + it { is_expected.to allow_value('P@$$w0rd').for(:passphrase) } + it { is_expected.to allow_value('A' * 255).for(:passphrase) } + it { is_expected.not_to allow_value('A' * 256).for(:passphrase) } + end + + describe '#public_key' do + it { is_expected.to validate_presence_of(:public_key) } + + it { is_expected.to allow_value("-----BEGIN PGP PUBLIC KEY BLOCK-----\n...").for(:public_key) } + it { is_expected.not_to allow_value('A').for(:public_key).with_message('must be ASCII armored') } + end + + describe '#fingerprint' do + it { is_expected.to validate_presence_of(:passphrase) } + + it { is_expected.to allow_value('abc').for(:passphrase) } + it { is_expected.to allow_value('A' * 255).for(:passphrase) } + it { is_expected.not_to allow_value('A' * 256).for(:passphrase) } + end + end +end diff --git a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb index 8693d6868e9..5459d17b1df 100644 --- a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb +++ b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb @@ -17,6 +17,7 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| it { is_expected.to belong_to(container) } it { is_expected.to belong_to(:creator).class_name('User') } + it { is_expected.to have_one(:key).class_name("Packages::Debian::#{container.capitalize}DistributionKey").with_foreign_key(:distribution_id).inverse_of(:distribution) } it { is_expected.to have_many(:components).class_name("Packages::Debian::#{container.capitalize}Component").inverse_of(:distribution) } it { is_expected.to have_many(:architectures).class_name("Packages::Debian::#{container.capitalize}Architecture").inverse_of(:distribution) } end diff --git a/spec/support/shared_examples/namespaces/linear_traversal_examples.rb b/spec/support/shared_examples/namespaces/linear_traversal_examples.rb new file mode 100644 index 00000000000..2fd90c36953 --- /dev/null +++ b/spec/support/shared_examples/namespaces/linear_traversal_examples.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Traversal examples common to linear and recursive methods are in +# spec/support/shared_examples/namespaces/traversal_examples.rb + +RSpec.shared_examples 'linear namespace traversal' do + context 'when use_traversal_ids feature flag is enabled' do + before do + stub_feature_flags(use_traversal_ids: true) + end + + context 'scopes' do + describe '.as_ids' do + let_it_be(:namespace1) { create(:group) } + let_it_be(:namespace2) { create(:group) } + + subject { Namespace.where(id: [namespace1, namespace2]).as_ids.pluck(:id) } + + it { is_expected.to contain_exactly(namespace1.id, namespace2.id) } + end + end + end +end diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb index 77a1705627e..ccc64c80fd4 100644 --- a/spec/support/shared_examples/namespaces/traversal_examples.rb +++ b/spec/support/shared_examples/namespaces/traversal_examples.rb @@ -17,6 +17,28 @@ RSpec.shared_examples 'namespace traversal' do end end + describe '#root_ancestor' do + let_it_be(:group) { create(:group) } + let_it_be(:nested_group) { create(:group, parent: group) } + let_it_be(:deep_nested_group) { create(:group, parent: nested_group) } + + it 'returns the correct root ancestor' do + expect(group.root_ancestor).to eq(group) + expect(nested_group.root_ancestor).to eq(group) + expect(deep_nested_group.root_ancestor).to eq(group) + end + + describe '#recursive_root_ancestor' do + let(:groups) { [group, nested_group, deep_nested_group] } + + it "is equivalent to #recursive_root_ancestor" do + groups.each do |group| + expect(group.root_ancestor).to eq(group.recursive_root_ancestor) + end + end + end + end + describe '#self_and_hierarchy' do let!(:group) { create(:group, path: 'git_lab') } let!(:nested_group) { create(:group, parent: group) } @@ -122,4 +144,20 @@ RSpec.shared_examples 'namespace traversal' do it_behaves_like 'recursive version', :self_and_descendants end end + + describe '#self_and_descendant_ids' do + let!(:group) { create(:group, path: 'git_lab') } + let!(:nested_group) { create(:group, parent: group) } + let!(:deep_nested_group) { create(:group, parent: nested_group) } + + subject { group.self_and_descendant_ids.pluck(:id) } + + it { is_expected.to contain_exactly(group.id, nested_group.id, deep_nested_group.id) } + + describe '#recursive_self_and_descendant_ids' do + let(:groups) { [group, nested_group, deep_nested_group] } + + it_behaves_like 'recursive version', :self_and_descendant_ids + end + end end diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb index c938c6432fe..20606ae942d 100644 --- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb @@ -294,16 +294,6 @@ RSpec.shared_examples 'rejects invalid upload_url params' do end end -RSpec.shared_examples 'successful response when using Unicorn' do - context 'on Unicorn', :unicorn do - it 'returns successfully' do - subject - - expect(response).to have_gitlab_http_status(:ok) - end - end -end - RSpec.shared_examples 'recipe snapshot endpoint' do subject { get api(url), headers: headers } @@ -372,7 +362,6 @@ RSpec.shared_examples 'recipe upload_urls endpoint' do it_behaves_like 'rejects invalid recipe' it_behaves_like 'rejects invalid upload_url params' - it_behaves_like 'successful response when using Unicorn' it 'returns a set of upload urls for the files requested' do subject @@ -434,7 +423,6 @@ RSpec.shared_examples 'package upload_urls endpoint' do it_behaves_like 'rejects invalid recipe' it_behaves_like 'rejects invalid upload_url params' - it_behaves_like 'successful response when using Unicorn' it 'returns a set of upload urls for the files requested' do expected_response = { diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb index dfd19167dcd..0530aa8c760 100644 --- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb @@ -12,7 +12,35 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_ let_it_be(:user, freeze: true) { create(:user) } let_it_be(:personal_access_token, freeze: true) { create(:personal_access_token, user: user) } - let(:distribution) { 'bullseye' } + let_it_be(:private_distribution, freeze: true) { create("debian_#{container_type}_distribution", container: private_container, codename: 'existing-codename') } + let_it_be(:private_component, freeze: true) { create("debian_#{container_type}_component", distribution: private_distribution, name: 'existing-component') } + let_it_be(:private_architecture_all, freeze: true) { create("debian_#{container_type}_architecture", distribution: private_distribution, name: 'all') } + let_it_be(:private_architecture, freeze: true) { create("debian_#{container_type}_architecture", distribution: private_distribution, name: 'existing-arch') } + + let_it_be(:public_distribution, freeze: true) { create("debian_#{container_type}_distribution", container: public_container, codename: 'existing-codename') } + let_it_be(:public_component, freeze: true) { create("debian_#{container_type}_component", distribution: public_distribution, name: 'existing-component') } + let_it_be(:public_architecture_all, freeze: true) { create("debian_#{container_type}_architecture", distribution: public_distribution, name: 'all') } + let_it_be(:public_architecture, freeze: true) { create("debian_#{container_type}_architecture", distribution: public_distribution, name: 'existing-arch') } + + if container_type == :group + let_it_be(:private_project) { create(:project, :private, group: private_container) } + let_it_be(:public_project) { create(:project, :public, group: public_container) } + let_it_be(:private_project_distribution) { create(:debian_project_distribution, container: private_project, codename: 'existing-codename') } + let_it_be(:public_project_distribution) { create(:debian_project_distribution, container: public_project, codename: 'existing-codename') } + else + let_it_be(:private_project) { private_container } + let_it_be(:public_project) { public_container } + let_it_be(:private_project_distribution) { private_distribution } + let_it_be(:public_project_distribution) { public_distribution } + end + + let_it_be(:private_package) { create(:debian_package, project: private_project, published_in: private_project_distribution) } + let_it_be(:public_package) { create(:debian_package, project: public_project, published_in: public_project_distribution) } + + let(:visibility_level) { :public } + + let(:distribution) { { private: private_distribution, public: public_distribution }[visibility_level] } + let(:component) { 'main' } let(:architecture) { 'amd64' } let(:source_package) { 'sample' } @@ -97,7 +125,7 @@ RSpec.shared_examples 'Debian repository GET request' do |status, body = nil| expect(response).to have_gitlab_http_status(status) unless body.nil? - expect(response.body).to eq(body) + expect(response.body).to match(body) end end end @@ -107,16 +135,25 @@ RSpec.shared_examples 'Debian repository upload request' do |status, body = nil| if status == :created it 'creates package files', :aggregate_failures do - pending "Debian package creation not implemented" + expect(::Packages::Debian::FindOrCreateIncomingService).to receive(:new).with(container, user).and_call_original + expect(::Packages::Debian::CreatePackageFileService).to receive(:new).with(be_a(Packages::Package), be_an(Hash)).and_call_original + + if file_name.end_with? '.changes' + expect(::Packages::Debian::ProcessChangesWorker).to receive(:perform_async) + else + expect(::Packages::Debian::ProcessChangesWorker).not_to receive(:perform_async) + end expect { subject } .to change { container.packages.debian.count }.by(1) + .and change { container.packages.debian.where(name: 'incoming').count }.by(1) + .and change { container.package_files.count }.by(1) expect(response).to have_gitlab_http_status(status) expect(response.media_type).to eq('text/plain') unless body.nil? - expect(response.body).to eq(body) + expect(response.body).to match(body) end end it_behaves_like 'a package tracking event', described_class.name, 'push_package' @@ -127,7 +164,7 @@ RSpec.shared_examples 'Debian repository upload request' do |status, body = nil| expect(response).to have_gitlab_http_status(status) unless body.nil? - expect(response.body).to eq(body) + expect(response.body).to match(body) end end end @@ -173,18 +210,112 @@ RSpec.shared_examples 'Debian repository upload authorize request' do |status, b expect(response).to have_gitlab_http_status(status) unless body.nil? - expect(response.body).to eq(body) + expect(response.body).to match(body) + end + end + end +end + +RSpec.shared_examples 'Debian repository POST distribution request' do |status, body| + and_body = body.nil? ? '' : ' and expected body' + + if status == :created + it 'creates distribution', :aggregate_failures do + expect(::Packages::Debian::CreateDistributionService).to receive(:new).with(container, user, api_params).and_call_original + + expect { subject } + .to change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count }.by(1) + .and change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count }.by(1) + .and change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count }.by(2) + + expect(response).to have_gitlab_http_status(status) + expect(response.media_type).to eq('application/json') + + unless body.nil? + expect(response.body).to match(body) + end + end + else + it "returns #{status}#{and_body}", :aggregate_failures do + subject + + expect(response).to have_gitlab_http_status(status) + + unless body.nil? + expect(response.body).to match(body) + end + end + end +end + +RSpec.shared_examples 'Debian repository PUT distribution request' do |status, body| + and_body = body.nil? ? '' : ' and expected body' + + if status == :success + it 'updates distribution', :aggregate_failures do + expect(::Packages::Debian::UpdateDistributionService).to receive(:new).with(distribution, api_params.except(:codename)).and_call_original + + expect { subject } + .to not_change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count } + .and not_change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count } + .and not_change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count } + + expect(response).to have_gitlab_http_status(status) + expect(response.media_type).to eq('application/json') + + unless body.nil? + expect(response.body).to match(body) + end + end + else + it "returns #{status}#{and_body}", :aggregate_failures do + subject + + expect(response).to have_gitlab_http_status(status) + + unless body.nil? + expect(response.body).to match(body) end end end end -RSpec.shared_examples 'rejects Debian access with unknown container id' do +RSpec.shared_examples 'Debian repository DELETE distribution request' do |status, body| + and_body = body.nil? ? '' : ' and expected body' + + if status == :success + it 'updates distribution', :aggregate_failures do + expect { subject } + .to change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count }.by(-1) + .and change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count }.by(-1) + .and change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count }.by(-2) + + expect(response).to have_gitlab_http_status(status) + expect(response.media_type).to eq('application/json') + + unless body.nil? + expect(response.body).to match(body) + end + end + else + it "returns #{status}#{and_body}", :aggregate_failures do + subject + + expect(response).to have_gitlab_http_status(status) + + unless body.nil? + expect(response.body).to match(body) + end + end + end +end + +RSpec.shared_examples 'rejects Debian access with unknown container id' do |hidden_status| context 'with an unknown container' do let(:container) { double(id: non_existing_record_id) } context 'as anonymous' do - it_behaves_like 'Debian repository GET request', :unauthorized, nil + it_behaves_like 'Debian repository GET request', hidden_status, nil end context 'as authenticated user' do @@ -195,19 +326,25 @@ RSpec.shared_examples 'rejects Debian access with unknown container id' do end end -RSpec.shared_examples 'Debian repository read endpoint' do |desired_behavior, success_status, success_body| +RSpec.shared_examples 'Debian repository read endpoint' do |desired_behavior, success_status, success_body, authenticate_non_public: true| + hidden_status = if authenticate_non_public + :unauthorized + else + :not_found + end + context 'with valid container' do using RSpec::Parameterized::TableSyntax where(:visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do :public | :developer | true | true | success_status | success_body :public | :guest | true | true | success_status | success_body - :public | :developer | true | false | success_status | success_body - :public | :guest | true | false | success_status | success_body + :public | :developer | true | false | :unauthorized | nil + :public | :guest | true | false | :unauthorized | nil :public | :developer | false | true | success_status | success_body :public | :guest | false | true | success_status | success_body - :public | :developer | false | false | success_status | success_body - :public | :guest | false | false | success_status | success_body + :public | :developer | false | false | :unauthorized | nil + :public | :guest | false | false | :unauthorized | nil :public | :anonymous | false | true | success_status | success_body :private | :developer | true | true | success_status | success_body :private | :guest | true | true | :forbidden | nil @@ -217,7 +354,7 @@ RSpec.shared_examples 'Debian repository read endpoint' do |desired_behavior, su :private | :guest | false | true | :not_found | nil :private | :developer | false | false | :unauthorized | nil :private | :guest | false | false | :unauthorized | nil - :private | :anonymous | false | true | :unauthorized | nil + :private | :anonymous | false | true | hidden_status | nil end with_them do @@ -227,10 +364,16 @@ RSpec.shared_examples 'Debian repository read endpoint' do |desired_behavior, su end end - it_behaves_like 'rejects Debian access with unknown container id' + it_behaves_like 'rejects Debian access with unknown container id', hidden_status end -RSpec.shared_examples 'Debian repository write endpoint' do |desired_behavior, success_status, success_body| +RSpec.shared_examples 'Debian repository write endpoint' do |desired_behavior, success_status, success_body, authenticate_non_public: true| + hidden_status = if authenticate_non_public + :unauthorized + else + :not_found + end + context 'with valid container' do using RSpec::Parameterized::TableSyntax @@ -252,7 +395,50 @@ RSpec.shared_examples 'Debian repository write endpoint' do |desired_behavior, s :private | :guest | false | true | :not_found | nil :private | :developer | false | false | :unauthorized | nil :private | :guest | false | false | :unauthorized | nil - :private | :anonymous | false | true | :unauthorized | nil + :private | :anonymous | false | true | hidden_status | nil + end + + with_them do + include_context 'Debian repository access', params[:visibility_level], params[:user_role], params[:member], params[:user_token], :basic do + it_behaves_like "Debian repository #{desired_behavior}", params[:expected_status], params[:expected_body] + end + end + end + + it_behaves_like 'rejects Debian access with unknown container id', hidden_status +end + +RSpec.shared_examples 'Debian repository maintainer write endpoint' do |desired_behavior, success_status, success_body, authenticate_non_public: true| + hidden_status = if authenticate_non_public + :unauthorized + else + :not_found + end + + context 'with valid container' do + using RSpec::Parameterized::TableSyntax + + where(:visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do + :public | :maintainer | true | true | success_status | success_body + :public | :developer | true | true | :forbidden | nil + :public | :guest | true | true | :forbidden | nil + :public | :maintainer | true | false | :unauthorized | nil + :public | :guest | true | false | :unauthorized | nil + :public | :maintainer | false | true | :forbidden | nil + :public | :guest | false | true | :forbidden | nil + :public | :maintainer | false | false | :unauthorized | nil + :public | :guest | false | false | :unauthorized | nil + :public | :anonymous | false | true | :unauthorized | nil + :private | :maintainer | true | true | success_status | success_body + :private | :developer | true | true | :forbidden | nil + :private | :guest | true | true | :forbidden | nil + :private | :maintainer | true | false | :unauthorized | nil + :private | :guest | true | false | :unauthorized | nil + :private | :maintainer | false | true | :not_found | nil + :private | :guest | false | true | :not_found | nil + :private | :maintainer | false | false | :unauthorized | nil + :private | :guest | false | false | :unauthorized | nil + :private | :anonymous | false | true | hidden_status | nil end with_them do @@ -262,5 +448,5 @@ RSpec.shared_examples 'Debian repository write endpoint' do |desired_behavior, s end end - it_behaves_like 'rejects Debian access with unknown container id' + it_behaves_like 'rejects Debian access with unknown container id', hidden_status end diff --git a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb new file mode 100644 index 00000000000..41a61ba5fd7 --- /dev/null +++ b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a package detail' do + it_behaves_like 'a working graphql query' do + it 'matches the JSON schema' do + expect(package_details).to match_schema('graphql/packages/package_details') + end + end +end + +RSpec.shared_examples 'a package with files' do + it 'has the right amount of files' do + expect(package_files_response.length).to be(package.package_files.length) + end + + it 'has the basic package files data' do + expect(first_file_response).to include( + 'id' => global_id_of(first_file), + 'fileName' => first_file.file_name, + 'size' => first_file.size.to_s, + 'downloadPath' => first_file.download_path, + 'fileSha1' => first_file.file_sha1, + 'fileMd5' => first_file.file_md5, + 'fileSha256' => first_file.file_sha256 + ) + end +end diff --git a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb new file mode 100644 index 00000000000..585c4fb8a4e --- /dev/null +++ b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'rejects helm packages access' do |user_type, status, add_member = true| + context "for user type #{user_type}" do + before do + project.send("add_#{user_type}", user) if add_member && user_type != :anonymous + end + + it_behaves_like 'returning response status', status + + if status == :unauthorized + it 'has the correct response header' do + subject + + expect(response.headers['WWW-Authenticate']).to eq 'Basic realm="GitLab Packages Registry"' + end + end + end +end + +RSpec.shared_examples 'process helm download content request' do |user_type, status, add_member = true| + context "for user type #{user_type}" do + before do + project.send("add_#{user_type}", user) if add_member && user_type != :anonymous + end + + it_behaves_like 'returning response status', status + + it_behaves_like 'a package tracking event', 'API::HelmPackages', 'pull_package' + + it 'returns a valid package archive' do + subject + + expect(response.media_type).to eq('application/octet-stream') + end + end +end + +RSpec.shared_examples 'rejects helm access with unknown project id' do + context 'with an unknown project' do + let(:project) { OpenStruct.new(id: 1234567890) } + + context 'as anonymous' do + it_behaves_like 'rejects helm packages access', :anonymous, :unauthorized + end + + context 'as authenticated user' do + subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) } + + it_behaves_like 'rejects helm packages access', :anonymous, :not_found + end + end +end diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb index eb86b7c37d5..42c29084d7b 100644 --- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb @@ -146,6 +146,6 @@ RSpec.shared_examples 'a package tracking event' do |category, action| it "creates a gitlab tracking event #{action}", :snowplow do expect { subject }.to change { Packages::Event.count }.by(1) - expect_snowplow_event(category: category, action: action) + expect_snowplow_event(category: category, action: action, **snowplow_gitlab_standard_context) end end diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb index bbcf856350d..8a351226123 100644 --- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb @@ -110,6 +110,7 @@ RSpec.shared_examples 'PyPI package versions' do |user_type, status, add_member context "for user type #{user_type}" do before do project.send("add_#{user_type}", user) if add_member && user_type != :anonymous + group.send("add_#{user_type}", user) if add_member && user_type != :anonymous end it 'returns the package listing' do @@ -127,6 +128,7 @@ RSpec.shared_examples 'PyPI package download' do |user_type, status, add_member context "for user type #{user_type}" do before do project.send("add_#{user_type}", user) if add_member && user_type != :anonymous + group.send("add_#{user_type}", user) if add_member && user_type != :anonymous end it 'returns the package listing' do @@ -144,24 +146,185 @@ RSpec.shared_examples 'process PyPI api request' do |user_type, status, add_memb context "for user type #{user_type}" do before do project.send("add_#{user_type}", user) if add_member && user_type != :anonymous + group.send("add_#{user_type}", user) if add_member && user_type != :anonymous end it_behaves_like 'returning response status', status end end +RSpec.shared_examples 'unknown PyPI scope id' do + context 'as anonymous' do + it_behaves_like 'process PyPI api request', :anonymous, :not_found + end + + context 'as authenticated user' do + subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) } + + it_behaves_like 'process PyPI api request', :anonymous, :not_found + end +end + RSpec.shared_examples 'rejects PyPI access with unknown project id' do context 'with an unknown project' do let(:project) { OpenStruct.new(id: 1234567890) } - context 'as anonymous' do - it_behaves_like 'process PyPI api request', :anonymous, :not_found + it_behaves_like 'unknown PyPI scope id' + end +end + +RSpec.shared_examples 'rejects PyPI access with unknown group id' do + context 'with an unknown project' do + let(:group) { OpenStruct.new(id: 1234567890) } + + it_behaves_like 'unknown PyPI scope id' + end +end + +RSpec.shared_examples 'pypi simple API endpoint' do + using RSpec::Parameterized::TableSyntax + + context 'with valid project' do + where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + :public | :developer | true | true | 'PyPI package versions' | :success + :public | :guest | true | true | 'PyPI package versions' | :success + :public | :developer | true | false | 'PyPI package versions' | :success + :public | :guest | true | false | 'PyPI package versions' | :success + :public | :developer | false | true | 'PyPI package versions' | :success + :public | :guest | false | true | 'PyPI package versions' | :success + :public | :developer | false | false | 'PyPI package versions' | :success + :public | :guest | false | false | 'PyPI package versions' | :success + :public | :anonymous | false | true | 'PyPI package versions' | :success + :private | :developer | true | true | 'PyPI package versions' | :success + :private | :guest | true | true | 'process PyPI api request' | :forbidden + :private | :developer | true | false | 'process PyPI api request' | :unauthorized + :private | :guest | true | false | 'process PyPI api request' | :unauthorized + :private | :developer | false | true | 'process PyPI api request' | :not_found + :private | :guest | false | true | 'process PyPI api request' | :not_found + :private | :developer | false | false | 'process PyPI api request' | :unauthorized + :private | :guest | false | false | 'process PyPI api request' | :unauthorized + :private | :anonymous | false | true | 'process PyPI api request' | :unauthorized end - context 'as authenticated user' do - subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) } + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - it_behaves_like 'process PyPI api request', :anonymous, :not_found + before do + project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) + group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + + context 'with a normalized package name' do + let_it_be(:package) { create(:pypi_package, project: project, name: 'my.package') } + + let(:url) { "/projects/#{project.id}/packages/pypi/simple/my-package" } + let(:headers) { basic_auth_header(user.username, personal_access_token.token) } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } + + it_behaves_like 'PyPI package versions', :developer, :success + end +end + +RSpec.shared_examples 'pypi file download endpoint' do + using RSpec::Parameterized::TableSyntax + + context 'with valid project' do + where(:visibility_level, :user_role, :member, :user_token) do + :public | :developer | true | true + :public | :guest | true | true + :public | :developer | true | false + :public | :guest | true | false + :public | :developer | false | true + :public | :guest | false | true + :public | :developer | false | false + :public | :guest | false | false + :public | :anonymous | false | true + :private | :developer | true | true + :private | :guest | true | true + :private | :developer | true | false + :private | :guest | true | false + :private | :developer | false | true + :private | :guest | false | true + :private | :developer | false | false + :private | :guest | false | false + :private | :anonymous | false | true end + + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + before do + project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) + group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) + end + + it_behaves_like 'PyPI package download', params[:user_role], :success, params[:member] + end + end + + context 'with deploy token headers' do + let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) } + + context 'valid token' do + it_behaves_like 'returning response status', :success + end + + context 'invalid token' do + let(:headers) { basic_auth_header('foo', 'bar') } + + it_behaves_like 'returning response status', :success + end + end + + context 'with job token headers' do + let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token) } + + context 'valid token' do + it_behaves_like 'returning response status', :success + end + + context 'invalid token' do + let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, 'bar') } + + it_behaves_like 'returning response status', :unauthorized + end + + context 'invalid user' do + let(:headers) { basic_auth_header('foo', job.token) } + + it_behaves_like 'returning response status', :success + end + end +end + +RSpec.shared_examples 'a pypi user namespace endpoint' do + using RSpec::Parameterized::TableSyntax + + # only group namespaces are supported at this time + where(:visibility_level, :user_role, :expected_status) do + :public | :owner | :not_found + :private | :owner | :not_found + :public | :external | :not_found + :private | :external | :not_found + :public | :anonymous | :not_found + :private | :anonymous | :not_found + end + + with_them do + let_it_be_with_reload(:group) { create(:namespace) } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) } + + before do + group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) + group.update_column(:owner_id, user.id) if user_role == :owner + end + + it_behaves_like 'returning response status', params[:expected_status] end end diff --git a/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb b/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb index 675b6c5cef6..2ac78131e08 100644 --- a/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb @@ -48,7 +48,7 @@ RSpec.shared_examples 'resource_label_events API' do |parent_type, eventable_typ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", user) expect(json_response).to be_an Array - expect(json_response).to eq [] + expect(json_response).to be_empty end end end diff --git a/spec/support/shared_examples/requests/api/tracking_shared_examples.rb b/spec/support/shared_examples/requests/api/tracking_shared_examples.rb index 826139635ed..af13e3fc14d 100644 --- a/spec/support/shared_examples/requests/api/tracking_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/tracking_shared_examples.rb @@ -4,6 +4,6 @@ RSpec.shared_examples 'a gitlab tracking event' do |category, action| it "creates a gitlab tracking event #{action}", :snowplow do subject - expect_snowplow_event(category: category, action: action) + expect_snowplow_event(category: category, action: action, **snowplow_standard_context_params) end end diff --git a/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb b/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb deleted file mode 100644 index 466300017d9..00000000000 --- a/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples 'parse cluster applications artifact' do |release_name| - let(:application_class) { Clusters::Cluster::APPLICATIONS[release_name] } - let(:cluster_application) { cluster.public_send("application_#{release_name}") } - let(:file) { fixture_file_upload(Rails.root.join(fixture)) } - let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) } - - context 'release is missing' do - let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_missing.json.gz" } - - context 'application does not exist' do - it 'does not create or destroy an application' do - expect do - described_class.new(job, user).execute(artifact) - end.not_to change(application_class, :count) - end - end - - context 'application exists' do - before do - create("clusters_applications_#{release_name}".to_sym, :installed, cluster: cluster) - end - - it 'marks the application as uninstalled' do - described_class.new(job, user).execute(artifact) - - cluster_application.reload - expect(cluster_application).to be_uninstalled - end - end - end - - context 'release is deployed' do - let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_deployed.json.gz" } - - context 'application does not exist' do - it 'creates an application and marks it as installed' do - expect do - described_class.new(job, user).execute(artifact) - end.to change(application_class, :count) - - expect(cluster_application).to be_persisted - expect(cluster_application).to be_externally_installed - end - end - - context 'application exists' do - before do - create("clusters_applications_#{release_name}".to_sym, :errored, cluster: cluster) - end - - it 'marks the application as installed' do - described_class.new(job, user).execute(artifact) - - expect(cluster_application).to be_externally_installed - end - end - end - - context 'release is failed' do - let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_failed.json.gz" } - - context 'application does not exist' do - it 'creates an application and marks it as errored' do - expect do - described_class.new(job, user).execute(artifact) - end.to change(application_class, :count) - - expect(cluster_application).to be_persisted - expect(cluster_application).to be_errored - expect(cluster_application.status_reason).to eq('Helm release failed to install') - end - end - - context 'application exists' do - before do - create("clusters_applications_#{release_name}".to_sym, :installed, cluster: cluster) - end - - it 'marks the application as errored' do - described_class.new(job, user).execute(artifact) - - expect(cluster_application).to be_errored - expect(cluster_application.status_reason).to eq('Helm release failed to install') - end - end - end -end diff --git a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb new file mode 100644 index 00000000000..9ffeba1b1d0 --- /dev/null +++ b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'Generate Debian Distribution and component files' do + let_it_be(:component_main) { create("debian_#{container_type}_component", distribution: distribution, name: 'main') } + let_it_be(:component_contrib) { create("debian_#{container_type}_component", distribution: distribution, name: 'contrib') } + + let_it_be(:architecture_all) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'all') } + let_it_be(:architecture_amd64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'amd64') } + let_it_be(:architecture_arm64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'arm64') } + + let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T08:00:00Z', file_sha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', file_md5: 'd41d8cd98f00b204e9800998ecf8427e', file_fixture: nil, size: 0) } # updated + let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_all, updated_at: '2020-01-24T09:00:00Z', file_sha256: 'a') } # destroyed + let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_amd64, updated_at: '2020-01-24T10:54:59Z', file_sha256: 'b') } # destroyed, 1 second before last generation + let_it_be(:component_file4) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'c') } # kept, last generation + let_it_be(:component_file5) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'd') } # kept, last generation + let_it_be(:component_file6) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_amd64, updated_at: '2020-01-25T15:17:18Z', file_sha256: 'e') } # kept, less than 1 hour ago + + def check_component_file(release_date, component_name, component_file_type, architecture_name, expected_content) + component_file = distribution + .component_files + .with_component_name(component_name) + .with_file_type(component_file_type) + .with_architecture_name(architecture_name) + .order_updated_asc + .last + + expect(component_file).not_to be_nil + expect(component_file.updated_at).to eq(release_date) + + unless expected_content.nil? + component_file.file.use_file do |file_path| + expect(File.read(file_path)).to eq(expected_content) + end + end + end + + it 'generates Debian distribution and component files', :aggregate_failures do + current_time = Time.utc(2020, 01, 25, 15, 17, 18, 123456) + + travel_to(current_time) do + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + + initial_count = 6 + destroyed_count = 2 + # updated_count = 1 + created_count = 5 + + expect { subject } + .to not_change { Packages::Package.count } + .and not_change { Packages::PackageFile.count } + .and change { distribution.reload.updated_at }.to(current_time.round) + .and change { distribution.component_files.reset.count }.from(initial_count).to(initial_count - destroyed_count + created_count) + .and change { component_file1.reload.updated_at }.to(current_time.round) + + debs = package.package_files.with_debian_file_type(:deb).preload_debian_file_metadata.to_a + pool_prefix = "pool/unstable/#{project.id}/p/#{package.name}" + expected_main_amd64_content = <<~EOF + Package: libsample0 + Source: #{package.name} + Version: #{package.version} + Installed-Size: 7 + Maintainer: #{debs[0].debian_fields['Maintainer']} + Architecture: amd64 + Description: Some mostly empty lib + Used in GitLab tests. + . + Testing another paragraph. + Multi-Arch: same + Homepage: #{debs[0].debian_fields['Homepage']} + Section: libs + Priority: optional + Filename: #{pool_prefix}/libsample0_1.2.3~alpha2_amd64.deb + Size: 409600 + MD5sum: #{debs[0].file_md5} + SHA256: #{debs[0].file_sha256} + + Package: sample-dev + Source: #{package.name} (#{package.version}) + Version: 1.2.3~binary + Installed-Size: 7 + Maintainer: #{debs[1].debian_fields['Maintainer']} + Architecture: amd64 + Depends: libsample0 (= 1.2.3~binary) + Description: Some mostly empty development files + Used in GitLab tests. + . + Testing another paragraph. + Multi-Arch: same + Homepage: #{debs[1].debian_fields['Homepage']} + Section: libdevel + Priority: optional + Filename: #{pool_prefix}/sample-dev_1.2.3~binary_amd64.deb + Size: 409600 + MD5sum: #{debs[1].file_md5} + SHA256: #{debs[1].file_sha256} + EOF + + check_component_file(current_time.round, 'main', :packages, 'all', nil) + check_component_file(current_time.round, 'main', :packages, 'amd64', expected_main_amd64_content) + check_component_file(current_time.round, 'main', :packages, 'arm64', nil) + + check_component_file(current_time.round, 'contrib', :packages, 'all', nil) + check_component_file(current_time.round, 'contrib', :packages, 'amd64', nil) + check_component_file(current_time.round, 'contrib', :packages, 'arm64', nil) + + main_amd64_size = expected_main_amd64_content.length + main_amd64_md5sum = Digest::MD5.hexdigest(expected_main_amd64_content) + main_amd64_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_content) + + contrib_all_size = component_file1.size + contrib_all_md5sum = component_file1.file_md5 + contrib_all_sha256 = component_file1.file_sha256 + + expected_release_content = <<~EOF + Codename: unstable + Date: Sat, 25 Jan 2020 15:17:18 +0000 + Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000 + Architectures: all amd64 arm64 + Components: contrib main + MD5Sum: + #{contrib_all_md5sum} #{contrib_all_size} contrib/binary-all/Packages + d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-amd64/Packages + d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-arm64/Packages + d41d8cd98f00b204e9800998ecf8427e 0 main/binary-all/Packages + #{main_amd64_md5sum} #{main_amd64_size} main/binary-amd64/Packages + d41d8cd98f00b204e9800998ecf8427e 0 main/binary-arm64/Packages + SHA256: + #{contrib_all_sha256} #{contrib_all_size} contrib/binary-all/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages + #{main_amd64_sha256} #{main_amd64_size} main/binary-amd64/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages + EOF + + distribution.file.use_file do |file_path| + expect(File.read(file_path)).to eq(expected_release_content) + end + end + end +end + +RSpec.shared_examples 'Generate minimal Debian Distribution' do + it 'generates minimal distribution', :aggregate_failures do + travel_to(Time.utc(2020, 01, 25, 15, 17, 18, 123456)) do + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + + expect { subject } + .to not_change { Packages::Package.count } + .and not_change { Packages::PackageFile.count } + .and not_change { distribution.component_files.reset.count } + + expected_release_content = <<~EOF + Codename: unstable + Date: Sat, 25 Jan 2020 15:17:18 +0000 + Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000 + MD5Sum: + SHA256: + EOF + + distribution.file.use_file do |file_path| + expect(File.read(file_path)).to eq(expected_release_content) + end + end + end +end diff --git a/spec/support/shared_examples/services/users/build_service_shared_examples.rb b/spec/support/shared_examples/services/users/build_service_shared_examples.rb new file mode 100644 index 00000000000..6a8695e1786 --- /dev/null +++ b/spec/support/shared_examples/services/users/build_service_shared_examples.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'common user build items' do + it { is_expected.to be_valid } + + it 'sets the created_by_id' do + expect(user.created_by_id).to eq(current_user&.id) + end + + it 'calls UpdateCanonicalEmailService' do + expect(Users::UpdateCanonicalEmailService).to receive(:new).and_call_original + + user + end + + context 'when user_type is provided' do + context 'when project_bot' do + before do + params.merge!({ user_type: :project_bot }) + end + + it { expect(user.project_bot?).to be true } + end + + context 'when not a project_bot' do + before do + params.merge!({ user_type: :alert_bot }) + end + + it { expect(user).to be_human } + end + end +end + +RSpec.shared_examples_for 'current user not admin build items' do + using RSpec::Parameterized::TableSyntax + + context 'with "user_default_external" application setting' do + where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do + true | nil | 'fl@example.com' | nil | true + true | true | 'fl@example.com' | nil | true + true | false | 'fl@example.com' | nil | true # admin difference + + true | nil | 'fl@example.com' | '' | true + true | true | 'fl@example.com' | '' | true + true | false | 'fl@example.com' | '' | true # admin difference + + true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false + true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference + true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false + + true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true + true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true + true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true # admin difference + + false | nil | 'fl@example.com' | nil | false + false | true | 'fl@example.com' | nil | false # admin difference + false | false | 'fl@example.com' | nil | false + + false | nil | 'fl@example.com' | '' | false + false | true | 'fl@example.com' | '' | false # admin difference + false | false | 'fl@example.com' | '' | false + + false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false + false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference + false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false + + false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false + false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference + false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false + end + + with_them do + before do + stub_application_setting(user_default_external: user_default_external) + stub_application_setting(user_default_internal_regex: user_default_internal_regex) + + params.merge!({ external: external, email: email }.compact) + end + + it 'sets the value of Gitlab::CurrentSettings.user_default_external' do + expect(user.external).to eq(result) + end + end + end + + context 'when "send_user_confirmation_email" application setting is true' do + before do + stub_application_setting(send_user_confirmation_email: true, signup_enabled?: true) + end + + it 'does not confirm the user' do + expect(user).not_to be_confirmed + end + end + + context 'when "send_user_confirmation_email" application setting is false' do + before do + stub_application_setting(send_user_confirmation_email: false, signup_enabled?: true) + end + + it 'confirms the user' do + expect(user).to be_confirmed + end + end + + context 'with allowed params' do + let(:params) do + { + email: 1, + name: 1, + password: 1, + password_automatically_set: 1, + username: 1, + user_type: 'project_bot' + } + end + + it 'sets all allowed attributes' do + expect(User).to receive(:new).with(hash_including(params)).and_call_original + + user + end + end +end diff --git a/spec/support/shared_examples/uncached_response_shared_examples.rb b/spec/support/shared_examples/uncached_response_shared_examples.rb deleted file mode 100644 index 3997017ff35..00000000000 --- a/spec/support/shared_examples/uncached_response_shared_examples.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true -# -# Pairs with lib/gitlab/no_cache_headers.rb -# - -RSpec.shared_examples 'uncached response' do - it 'defines an uncached header response' do - expect(response.headers["Cache-Control"]).to include("no-store", "no-cache") - expect(response.headers["Pragma"]).to eq("no-cache") - expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") - end -end diff --git a/spec/support/unicorn.rb b/spec/support/unicorn.rb deleted file mode 100644 index 0b01fc9e26c..00000000000 --- a/spec/support/unicorn.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -REQUEST_CLASSES = [ - ::Grape::Request, - ::Rack::Request -].freeze - -def request_body_class - return ::Unicorn::TeeInput if defined?(::Unicorn) - - Class.new(StringIO) do - def string - raise NotImplementedError, '#string is only valid under Puma which uses StringIO, use #read instead' - end - end -end - -RSpec.configure do |config| - config.before(:each, :unicorn) do - REQUEST_CLASSES.each do |request_class| - allow_any_instance_of(request_class) - .to receive(:body).and_wrap_original do |m, *args| - request_body_class.new(m.call(*args).read) - end - end - end -end |