diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-14 11:41:52 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-14 11:41:52 +0300 |
commit | 585826cb22ecea5998a2c2a4675735c94bdeedac (patch) | |
tree | 5b05f0b30d33cef48963609e8a18a4dff260eab3 /spec/lib | |
parent | df221d036e5d0c6c0ee4d55b9c97f481ee05dee8 (diff) |
Add latest changes from gitlab-org/gitlab@16-6-stable-eev16.6.0-rc42
Diffstat (limited to 'spec/lib')
393 files changed, 6904 insertions, 3198 deletions
diff --git a/spec/lib/api/entities/bulk_imports/entity_failure_spec.rb b/spec/lib/api/entities/bulk_imports/entity_failure_spec.rb index 0132102b117..217e6c11630 100644 --- a/spec/lib/api/entities/bulk_imports/entity_failure_spec.rb +++ b/spec/lib/api/entities/bulk_imports/entity_failure_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Entities::BulkImports::EntityFailure do +RSpec.describe API::Entities::BulkImports::EntityFailure, feature_category: :importers do let_it_be(:failure) { create(:bulk_import_failure) } subject { described_class.new(failure).as_json } @@ -10,11 +10,11 @@ RSpec.describe API::Entities::BulkImports::EntityFailure do it 'has the correct attributes' do expect(subject).to include( :relation, - :step, - :exception_class, :exception_message, + :exception_class, :correlation_id_value, - :created_at + :source_url, + :source_title ) end diff --git a/spec/lib/api/entities/wiki_page_spec.rb b/spec/lib/api/entities/wiki_page_spec.rb index a3566293c5c..da75ade997b 100644 --- a/spec/lib/api/entities/wiki_page_spec.rb +++ b/spec/lib/api/entities/wiki_page_spec.rb @@ -24,7 +24,7 @@ RSpec.describe API::Entities::WikiPage do context "with front matter content" do let(:wiki_page) { create(:wiki_page) } - let(:content_with_front_matter) { "---\nxxx: abc\n---\nHome Page" } + let(:content_with_front_matter) { "---\ntitle: abc\n---\nHome Page" } before do wiki_page.update(content: content_with_front_matter) # rubocop:disable Rails/SaveBang @@ -33,6 +33,10 @@ RSpec.describe API::Entities::WikiPage do it 'returns the raw wiki page content' do expect(subject[:content]).to eq content_with_front_matter end + + it 'return the front matter title' do + expect(subject[:front_matter]).to eq({ title: "abc" }) + end end context 'when render_html param is passed' do diff --git a/spec/lib/api/github/entities_spec.rb b/spec/lib/api/github/entities_spec.rb deleted file mode 100644 index 63c54b259a2..00000000000 --- a/spec/lib/api/github/entities_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe API::Github::Entities do - describe API::Github::Entities::User do - let(:user) { create(:user, username: username) } - let(:username) { 'name_of_user' } - let(:gitlab_protocol_and_host) { "#{Gitlab.config.gitlab.protocol}://#{Gitlab.config.gitlab.host}" } - let(:expected_user_url) { "#{gitlab_protocol_and_host}/#{username}" } - let(:entity) { described_class.new(user) } - - subject { entity.as_json } - - specify :aggregate_failures do - expect(subject[:id]).to eq user.id - expect(subject[:login]).to eq 'name_of_user' - expect(subject[:url]).to eq expected_user_url - expect(subject[:html_url]).to eq expected_user_url - expect(subject[:avatar_url]).to include('https://www.gravatar.com/avatar') - end - - context 'with avatar' do - let(:user) { create(:user, :with_avatar, username: username) } - - specify do - expect(subject[:avatar_url]).to include("#{gitlab_protocol_and_host}/uploads/-/system/user/avatar/") - end - end - end -end diff --git a/spec/lib/api/helpers/rate_limiter_spec.rb b/spec/lib/api/helpers/rate_limiter_spec.rb index 531140a32a3..101b1c97184 100644 --- a/spec/lib/api/helpers/rate_limiter_spec.rb +++ b/spec/lib/api/helpers/rate_limiter_spec.rb @@ -69,4 +69,12 @@ RSpec.describe API::Helpers::RateLimiter do end end end + + describe '#mark_throttle!' do + it 'calls ApplicationRateLimiter#throttle?' do + expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(key, scope: scope).and_return(false) + + subject.mark_throttle!(key, scope: scope) + end + end end diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb index 5d343ec2777..21b3b8e6927 100644 --- a/spec/lib/api/helpers_spec.rb +++ b/spec/lib/api/helpers_spec.rb @@ -1327,4 +1327,79 @@ RSpec.describe API::Helpers, feature_category: :shared do end end end + + describe '#authenticate_by_gitlab_shell_or_workhorse_token!' do + include GitlabShellHelpers + include WorkhorseHelpers + + include_context 'workhorse headers' + + let(:headers) { {} } + let(:params) { {} } + + context 'when request from gitlab shell' do + let(:valid_secret_token) { 'valid' } + let(:invalid_secret_token) { 'invalid' } + + before do + allow(helper).to receive_messages(headers: headers) + end + + context 'with invalid token' do + let(:headers) { gitlab_shell_internal_api_request_header(secret_token: invalid_secret_token) } + + it 'unauthorized' do + expect(helper).to receive(:unauthorized!) + + helper.authenticate_by_gitlab_shell_or_workhorse_token! + end + end + + context 'with valid token' do + let(:headers) { gitlab_shell_internal_api_request_header } + + it 'authorized' do + expect(helper).not_to receive(:unauthorized!) + + helper.authenticate_by_gitlab_shell_or_workhorse_token! + end + end + end + + context 'when request from gitlab workhorse' do + let(:env) { {} } + let(:request) { ActionDispatch::Request.new(env) } + + before do + allow_any_instance_of(ActionDispatch::Request).to receive(:headers).and_return(headers) + allow(helper).to receive(:request).and_return(request) + allow(helper).to receive_messages(params: params, headers: headers, env: env) + end + + context 'with invalid token' do + let(:headers) { { Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => JWT.encode({ 'iss' => 'gitlab-workhorse' }, 'wrongkey', 'HS256') } } + + before do + allow(JWT).to receive(:decode).and_return([{ 'iss' => 'gitlab-workhorse' }]) + end + + it 'unauthorized' do + expect(helper).to receive(:forbidden!) + + helper.authenticate_by_gitlab_shell_or_workhorse_token! + end + end + + context 'with valid token' do + let(:headers) { workhorse_headers } + let(:env) { { 'HTTP_GITLAB_WORKHORSE' => 1 } } + + it 'authorized' do + expect(helper).not_to receive(:forbidden!) + + helper.authenticate_by_gitlab_shell_or_workhorse_token! + end + end + end + end end diff --git a/spec/lib/atlassian/jira_connect/client_spec.rb b/spec/lib/atlassian/jira_connect/client_spec.rb index f7597579e7a..a692d76da77 100644 --- a/spec/lib/atlassian/jira_connect/client_spec.rb +++ b/spec/lib/atlassian/jira_connect/client_spec.rb @@ -447,38 +447,77 @@ RSpec.describe Atlassian::JiraConnect::Client, feature_category: :integrations d end describe '#user_info' do - let(:account_id) { '12345' } - let(:response_body) do - { - groups: { - items: [ - { name: 'site-admins' } - ] - } - }.to_json - end + context 'when user is a site administrator' do + let(:account_id) { '12345' } + let(:response_body) do + { + groups: { + items: [ + { name: 'site-admins' } + ] + } + }.to_json + end - before do - stub_full_request("https://gitlab-test.atlassian.net/rest/api/3/user?accountId=#{account_id}&expand=groups") - .to_return(status: response_status, body: response_body, headers: { 'Content-Type': 'application/json' }) - end + before do + stub_full_request("https://gitlab-test.atlassian.net/rest/api/3/user?accountId=#{account_id}&expand=groups") + .to_return(status: response_status, body: response_body, headers: { 'Content-Type': 'application/json' }) + end + + context 'with a successful response' do + let(:response_status) { 200 } - context 'with a successful response' do - let(:response_status) { 200 } + it 'returns a JiraUser instance' do + jira_user = client.user_info(account_id) - it 'returns a JiraUser instance' do - jira_user = client.user_info(account_id) + expect(jira_user).to be_a(Atlassian::JiraConnect::JiraUser) + expect(jira_user).to be_jira_admin + end + end + + context 'with a failed response' do + let(:response_status) { 401 } - expect(jira_user).to be_a(Atlassian::JiraConnect::JiraUser) - expect(jira_user).to be_site_admin + it 'returns nil' do + expect(client.user_info(account_id)).to be_nil + end end end - context 'with a failed response' do - let(:response_status) { 401 } + context 'when user is an organization administrator' do + let(:account_id) { '12345' } + let(:response_body) do + { + groups: { + items: [ + { name: 'org-admins' } + ] + } + }.to_json + end + + before do + stub_full_request("https://gitlab-test.atlassian.net/rest/api/3/user?accountId=#{account_id}&expand=groups") + .to_return(status: response_status, body: response_body, headers: { 'Content-Type': 'application/json' }) + end + + context 'with a successful response' do + let(:response_status) { 200 } + + it 'returns a JiraUser instance' do + jira_user = client.user_info(account_id) + + expect(jira_user).to be_a(Atlassian::JiraConnect::JiraUser) + expect(jira_user).to be_jira_admin + end + end + + context 'with a failed response' do + let(:response_status) { 401 } - it 'returns nil' do - expect(client.user_info(account_id)).to be_nil + it 'returns nil' do + expect(client.user_info(account_id)).to be_nil + end end end end diff --git a/spec/lib/backup/gitaly_backup_spec.rb b/spec/lib/backup/gitaly_backup_spec.rb index 6c2656b1c48..058c7f12f63 100644 --- a/spec/lib/backup/gitaly_backup_spec.rb +++ b/spec/lib/backup/gitaly_backup_spec.rb @@ -166,25 +166,40 @@ RSpec.describe Backup::GitalyBackup, feature_category: :backup_restore do let_it_be(:personal_snippet) { create(:personal_snippet, author: project.first_owner) } let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.first_owner) } - def copy_fixture_to_backup_path(backup_name, repo_disk_path) - FileUtils.mkdir_p(File.join(Gitlab.config.backup.path, 'repositories', File.dirname(repo_disk_path))) + def create_repo_backup(backup_name, repo) + repo_backup_root = File.join(Gitlab.config.backup.path, 'repositories') + + FileUtils.mkdir_p(File.join(repo_backup_root, 'manifests', repo.storage, repo.relative_path)) + FileUtils.mkdir_p(File.join(repo_backup_root, repo.relative_path)) %w[.bundle .refs].each do |filetype| FileUtils.cp( Rails.root.join('spec/fixtures/lib/backup', backup_name + filetype), - File.join(Gitlab.config.backup.path, 'repositories', repo_disk_path + filetype) + File.join(repo_backup_root, repo.relative_path + filetype) ) end + + manifest = <<-TOML + object_format = 'sha1' + head_references = 'heads/refs/master' + + [[steps]] + bundle_path = '#{repo.relative_path}.bundle' + ref_path = '#{repo.relative_path}.refs' + custom_hooks_path = '#{repo.relative_path}.custom_hooks.tar' + TOML + + File.write(File.join(repo_backup_root, 'manifests', repo.storage, repo.relative_path, backup_id + '.toml'), manifest) end it 'restores from repository bundles', :aggregate_failures do - copy_fixture_to_backup_path('project_repo', project.disk_path) - copy_fixture_to_backup_path('wiki_repo', project.wiki.disk_path) - copy_fixture_to_backup_path('design_repo', project.design_repository.disk_path) - copy_fixture_to_backup_path('personal_snippet_repo', personal_snippet.disk_path) - copy_fixture_to_backup_path('project_snippet_repo', project_snippet.disk_path) + create_repo_backup('project_repo', project.repository.raw) + create_repo_backup('wiki_repo', project.wiki.repository) + create_repo_backup('design_repo', project.design_repository) + create_repo_backup('personal_snippet_repo', personal_snippet.repository) + create_repo_backup('project_snippet_repo', project_snippet.repository) - expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer').and_call_original + expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer', '-id', backup_id).and_call_original subject.start(:restore, destination, backup_id: backup_id) subject.enqueue(project, Gitlab::GlRepository::PROJECT) @@ -204,9 +219,9 @@ RSpec.describe Backup::GitalyBackup, feature_category: :backup_restore do end it 'clears specified storages when remove_all_repositories is set' do - expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer', '-remove-all-repositories', 'default').and_call_original + expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer', '-remove-all-repositories', 'default', '-id', backup_id).and_call_original - copy_fixture_to_backup_path('project_repo', project.disk_path) + create_repo_backup('project_repo', project.repository.raw) subject.start(:restore, destination, backup_id: backup_id, remove_all_repositories: %w[default]) subject.enqueue(project, Gitlab::GlRepository::PROJECT) subject.finish! @@ -216,7 +231,7 @@ RSpec.describe Backup::GitalyBackup, feature_category: :backup_restore do let(:max_parallelism) { 3 } it 'passes parallel option through' do - expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer', '-parallel', '3').and_call_original + expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer', '-parallel', '3', '-id', backup_id).and_call_original subject.start(:restore, destination, backup_id: backup_id) subject.finish! @@ -227,7 +242,7 @@ RSpec.describe Backup::GitalyBackup, feature_category: :backup_restore do let(:storage_parallelism) { 3 } it 'passes parallel option through' do - expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer', '-parallel-storage', '3').and_call_original + expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer', '-parallel-storage', '3', '-id', backup_id).and_call_original subject.start(:restore, destination, backup_id: backup_id) subject.finish! diff --git a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb index baa22e08971..cb696e21e65 100644 --- a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb +++ b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb @@ -62,6 +62,8 @@ RSpec.describe Banzai::Filter::AssetProxyFilter, feature_category: :team_plannin end context 'when properly configured' do + using RSpec::Parameterized::TableSyntax + before do stub_asset_proxy_setting(enabled: true) stub_asset_proxy_setting(secret_key: 'shared-secret') @@ -71,84 +73,33 @@ RSpec.describe Banzai::Filter::AssetProxyFilter, feature_category: :team_plannin @context = described_class.transform_context({}) end - it 'replaces img src' do - src = 'http://example.com/test.png' - new_src = 'https://assets.example.com/08df250eeeef1a8cf2c761475ac74c5065105612/687474703a2f2f6578616d706c652e636f6d2f746573742e706e67' - doc = filter(image(src), @context) - - expect(doc.at_css('img')['src']).to eq new_src - expect(doc.at_css('img')['data-canonical-src']).to eq src - end - - it 'replaces invalid URLs' do - src = '///example.com/test.png' - new_src = 'https://assets.example.com/3368d2c7b9bed775bdd1e811f36a4b80a0dcd8ab/2f2f2f6578616d706c652e636f6d2f746573742e706e67' - doc = filter(image(src), @context) - - expect(doc.at_css('img')['src']).to eq new_src - expect(doc.at_css('img')['data-canonical-src']).to eq src - end - - it 'replaces by default, even strings that do not look like URLs' do - src = 'oigjsie8787%$**(#(%0' - new_src = 'https://assets.example.com/1b893f9a71d66c99437f27e19b9a061a6f5d9391/6f69676a7369653837383725242a2a2823282530' - doc = filter(image(src), @context) - - expect(doc.at_css('img')['src']).to eq new_src - expect(doc.at_css('img')['data-canonical-src']).to eq src - end - - it 'replaces URL with non-ASCII characters' do - src = 'https://example.com/x?¬' - new_src = 'https://assets.example.com/2f29a8c7f13f3ae14dc18c154dbbd657d703e75f/68747470733a2f2f6578616d706c652e636f6d2f783fc2ac' - doc = filter(image(src), @context) - - expect(doc.at_css('img')['src']).to eq new_src - expect(doc.at_css('img')['data-canonical-src']).to eq src - end - - it 'replaces out-of-spec URL that would still be rendered in the browser' do - src = 'https://example.com/##' - new_src = 'https://assets.example.com/d7d0c845cc553d9430804c07e9456545ef3e6fe6/68747470733a2f2f6578616d706c652e636f6d2f2323' - doc = filter(image(src), @context) - - expect(doc.at_css('img')['src']).to eq new_src - expect(doc.at_css('img')['data-canonical-src']).to eq src + where(:data_canonical_src, :src) do + 'http://example.com/test.png' | 'https://assets.example.com/08df250eeeef1a8cf2c761475ac74c5065105612/687474703a2f2f6578616d706c652e636f6d2f746573742e706e67' + '///example.com/test.png' | 'https://assets.example.com/3368d2c7b9bed775bdd1e811f36a4b80a0dcd8ab/2f2f2f6578616d706c652e636f6d2f746573742e706e67' + '//example.com/test.png' | 'https://assets.example.com/a2e9aa56319e31bbd05be72e633f2864ff08becb/2f2f6578616d706c652e636f6d2f746573742e706e67' + # If it can't be parsed, default to use asset proxy + 'oigjsie8787%$**(#(%0' | 'https://assets.example.com/1b893f9a71d66c99437f27e19b9a061a6f5d9391/6f69676a7369653837383725242a2a2823282530' + 'https://example.com/x?¬' | 'https://assets.example.com/2f29a8c7f13f3ae14dc18c154dbbd657d703e75f/68747470733a2f2f6578616d706c652e636f6d2f783fc2ac' + # The browser loads this as if it was a valid URL + 'http:example.com' | 'https://assets.example.com/bcefecd18484ec2850887d6730273e5e70f5ed1a/687474703a6578616d706c652e636f6d' + 'https:example.com' | 'https://assets.example.com/648e074361143780357db0b5cf73d4438d5484d3/68747470733a6578616d706c652e636f6d' + 'https://example.com/##' | 'https://assets.example.com/d7d0c845cc553d9430804c07e9456545ef3e6fe6/68747470733a2f2f6578616d706c652e636f6d2f2323' + nil | "test.png" + nil | "/test.png" + nil | "#{Gitlab.config.gitlab.url}/test.png" + nil | 'http://gitlab.com/test.png' + nil | 'http://gitlab.com/test.png?url=http://example.com/test.png' + nil | 'http://images.mydomain.com/test.png' end - it 'skips internal images' do - src = "#{Gitlab.config.gitlab.url}/test.png" - doc = filter(image(src), @context) + with_them do + it 'correctly modifies the img tag' do + original_url = data_canonical_src || src + doc = filter(image(original_url), @context) - expect(doc.at_css('img')['src']).to eq src - end - - it 'skip relative urls' do - src = "/test.png" - doc = filter(image(src), @context) - - expect(doc.at_css('img')['src']).to eq src - end - - it 'skips single domain' do - src = "http://gitlab.com/test.png" - doc = filter(image(src), @context) - - expect(doc.at_css('img')['src']).to eq src - end - - it 'skips single domain and ignores url in query string' do - src = "http://gitlab.com/test.png?url=http://example.com/test.png" - doc = filter(image(src), @context) - - expect(doc.at_css('img')['src']).to eq src - end - - it 'skips wildcarded domain' do - src = "http://images.mydomain.com/test.png" - doc = filter(image(src), @context) - - expect(doc.at_css('img')['src']).to eq src + expect(doc.at_css('img')['src']).to eq src + expect(doc.at_css('img')['data-canonical-src']).to eq data_canonical_src + end end end end diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb index 3fa0f9028e8..9e9e4110b44 100644 --- a/spec/lib/banzai/filter/math_filter_spec.rb +++ b/spec/lib/banzai/filter/math_filter_spec.rb @@ -210,18 +210,31 @@ RSpec.describe Banzai::Filter::MathFilter, feature_category: :team_planning do context 'when limiting how many elements can be marked as math' do subject { pipeline_filter('$`2+2`$ + $3+3$ + $$4+4$$') } - it 'enforces limits by default' do + before do stub_const('Banzai::Filter::MathFilter::RENDER_NODES_LIMIT', 2) + end + it 'enforces limits by default' do expect(subject.search('.js-render-math').count).to eq(2) end it 'does not limit when math_rendering_limits_enabled is false' do stub_application_setting(math_rendering_limits_enabled: false) - stub_const('Banzai::Filter::MathFilter::RENDER_NODES_LIMIT', 2) expect(subject.search('.js-render-math').count).to eq(3) end + + it 'does not limit for the wiki' do + doc = pipeline_filter('$`2+2`$ + $3+3$ + $$4+4$$', { wiki: true }) + + expect(doc.search('.js-render-math').count).to eq(3) + end + + it 'does not limit for blobs' do + doc = pipeline_filter('$`2+2`$ + $3+3$ + $$4+4$$', { text_source: :blob }) + + expect(doc.search('.js-render-math').count).to eq(3) + end end it 'protects against malicious backtracking' do @@ -232,14 +245,14 @@ RSpec.describe Banzai::Filter::MathFilter, feature_category: :team_planning do end.not_to raise_error end - def pipeline_filter(text) - context = { project: nil, no_sourcepos: true } + def pipeline_filter(text, context = {}) + context = { project: nil, no_sourcepos: true }.merge(context) doc = Banzai::Pipeline::PreProcessPipeline.call(text, {}) doc = Banzai::Pipeline::PlainMarkdownPipeline.call(doc[:output], context) doc = Banzai::Filter::CodeLanguageFilter.call(doc[:output], context, nil) doc = Banzai::Filter::SanitizationFilter.call(doc, context, nil) - filter(doc) + filter(doc, context) end end diff --git a/spec/lib/banzai/filter/references/user_reference_filter_spec.rb b/spec/lib/banzai/filter/references/user_reference_filter_spec.rb index 7a11ff3ac3d..b4f9d1a22cf 100644 --- a/spec/lib/banzai/filter/references/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/user_reference_filter_spec.rb @@ -231,16 +231,17 @@ RSpec.describe Banzai::Filter::References::UserReferenceFilter, feature_category it 'does not have N+1 per multiple user references', :use_sql_query_cache do markdown = reference.to_s + reference_filter(markdown) # warm up control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do reference_filter(markdown) - end.count + end markdown = "#{reference} @qwertyuiopzx @wertyuio @ertyu @rtyui #{reference2} #{reference3}" expect do reference_filter(markdown) - end.not_to exceed_all_query_limit(control_count) + end.to issue_same_number_of_queries_as(control_count) end end end diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb index 179e6e73fa3..096f1d3792b 100644 --- a/spec/lib/banzai/reference_parser/user_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb @@ -25,8 +25,14 @@ RSpec.describe Banzai::ReferenceParser::UserParser, feature_category: :team_plan context 'when group has members' do let!(:group_member) { create(:group_member, group: group, user: user) } - - it 'returns the users of the group' do + let!(:user2) { create(:user) } + let!(:user3) { create(:user) } + let!(:user4) { create(:user) } + let!(:group_member2) { create(:group_member, :minimal_access, group: group, user: user2) } + let!(:group_member3) { create(:group_member, :access_request, group: group, user: user3) } + let!(:group_member4) { create(:group_member, :invited, group: group, user: user4) } + + it 'returns the relevant users of the group with enough access' do expect(subject.referenced_by([link])).to eq([user]) end diff --git a/spec/lib/bitbucket/representation/pull_request_comment_spec.rb b/spec/lib/bitbucket/representation/pull_request_comment_spec.rb index e748cd7b955..6fdd1dfa561 100644 --- a/spec/lib/bitbucket/representation/pull_request_comment_spec.rb +++ b/spec/lib/bitbucket/representation/pull_request_comment_spec.rb @@ -2,7 +2,7 @@ require 'fast_spec_helper' -RSpec.describe Bitbucket::Representation::PullRequestComment do +RSpec.describe Bitbucket::Representation::PullRequestComment, feature_category: :importers do describe '#iid' do it { expect(described_class.new('id' => 1).iid).to eq(1) } end @@ -33,4 +33,10 @@ RSpec.describe Bitbucket::Representation::PullRequestComment do it { expect(described_class.new('parent' => {}).has_parent?).to be_truthy } it { expect(described_class.new({}).has_parent?).to be_falsey } end + + describe '#deleted?' do + it { expect(described_class.new('deleted' => true).deleted?).to be_truthy } + it { expect(described_class.new('deleted' => false).deleted?).to be_falsey } + it { expect(described_class.new({}).deleted?).to be_falsey } + end end diff --git a/spec/lib/bitbucket/representation/repo_spec.rb b/spec/lib/bitbucket/representation/repo_spec.rb index ba5a3306d07..ac6095dedd1 100644 --- a/spec/lib/bitbucket/representation/repo_spec.rb +++ b/spec/lib/bitbucket/representation/repo_spec.rb @@ -2,7 +2,7 @@ require 'fast_spec_helper' -RSpec.describe Bitbucket::Representation::Repo do +RSpec.describe Bitbucket::Representation::Repo, feature_category: :importers do describe '#has_wiki?' do it { expect(described_class.new({ 'has_wiki' => false }).has_wiki?).to be_falsey } it { expect(described_class.new({ 'has_wiki' => true }).has_wiki?).to be_truthy } @@ -42,6 +42,11 @@ RSpec.describe Bitbucket::Representation::Repo do it { expect(described_class.new({ 'full_name' => 'ben/test' }).slug).to eq('test') } end + describe '#default_branch' do + it { expect(described_class.new({ 'mainbranch' => { 'name' => 'master' } }).default_branch).to eq('master') } + it { expect(described_class.new({}).default_branch).to eq(nil) } + end + describe '#clone_url' do it 'builds url' do data = { 'links' => { 'clone' => [{ 'name' => 'https', 'href' => 'https://bibucket.org/test/test.git' }] } } diff --git a/spec/lib/bulk_imports/clients/graphql_spec.rb b/spec/lib/bulk_imports/clients/graphql_spec.rb index 9bb37a7c438..16f98ed462e 100644 --- a/spec/lib/bulk_imports/clients/graphql_spec.rb +++ b/spec/lib/bulk_imports/clients/graphql_spec.rb @@ -8,13 +8,13 @@ RSpec.describe BulkImports::Clients::Graphql, feature_category: :importers do subject { described_class.new(url: config.url, token: config.access_token) } describe '#execute' do - let(:graphql_client_double) { double } let(:response_double) { double } describe 'network errors' do before do allow(Gitlab::HTTP) .to receive(:post) + .with(an_instance_of(String), a_hash_including(timeout: 60)) .and_return(response_double) end diff --git a/spec/lib/bulk_imports/common/pipelines/badges_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/badges_pipeline_spec.rb index a18d26bedf3..aeb48bed314 100644 --- a/spec/lib/bulk_imports/common/pipelines/badges_pipeline_spec.rb +++ b/spec/lib/bulk_imports/common/pipelines/badges_pipeline_spec.rb @@ -39,18 +39,6 @@ RSpec.describe BulkImports::Common::Pipelines::BadgesPipeline do expect { pipeline.run }.to not_change(Badge, :count) end - context 'with FF bulk_import_idempotent_workers disabled' do - before do - stub_feature_flags(bulk_import_idempotent_workers: false) - end - - it 'creates duplicated badges' do - expect { pipeline.run }.to change(Badge, :count).by(2) - - expect { pipeline.run }.to change(Badge, :count) - end - end - context 'when project entity' do let(:first_page) { extracted_data(has_next_page: true) } let(:last_page) { extracted_data(name: 'badge2', kind: 'project') } diff --git a/spec/lib/bulk_imports/common/pipelines/entity_finisher_spec.rb b/spec/lib/bulk_imports/common/pipelines/entity_finisher_spec.rb index 8ca74565788..b96ea20c676 100644 --- a/spec/lib/bulk_imports/common/pipelines/entity_finisher_spec.rb +++ b/spec/lib/bulk_imports/common/pipelines/entity_finisher_spec.rb @@ -9,7 +9,7 @@ RSpec.describe BulkImports::Common::Pipelines::EntityFinisher, feature_category: context = BulkImports::Pipeline::Context.new(pipeline_tracker) subject = described_class.new(context) - expect_next_instance_of(Gitlab::Import::Logger) do |logger| + expect_next_instance_of(BulkImports::Logger) do |logger| expect(logger) .to receive(:info) .with( @@ -19,8 +19,7 @@ RSpec.describe BulkImports::Common::Pipelines::EntityFinisher, feature_category: source_full_path: entity.source_full_path, pipeline_class: described_class.name, message: 'Entity finished', - source_version: entity.bulk_import.source_version_info.to_s, - importer: 'gitlab_migration' + source_version: entity.bulk_import.source_version_info.to_s ) end diff --git a/spec/lib/bulk_imports/pipeline/runner_spec.rb b/spec/lib/bulk_imports/pipeline/runner_spec.rb index 8d48606633a..4540408990c 100644 --- a/spec/lib/bulk_imports/pipeline/runner_spec.rb +++ b/spec/lib/bulk_imports/pipeline/runner_spec.rb @@ -43,7 +43,9 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do stub_const('BulkImports::MyPipeline', pipeline) end - let_it_be_with_reload(:entity) { create(:bulk_import_entity) } + let_it_be(:bulk_import) { create(:bulk_import) } + let_it_be(:configuration) { create(:bulk_import_configuration, bulk_import: bulk_import) } + let_it_be_with_reload(:entity) { create(:bulk_import_entity, bulk_import: bulk_import) } let(:tracker) { create(:bulk_import_tracker, entity: entity) } let(:context) { BulkImports::Pipeline::Context.new(tracker, extra: :data) } @@ -52,7 +54,7 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do shared_examples 'failed pipeline' do |exception_class, exception_message| it 'logs import failure' do - expect_next_instance_of(Gitlab::Import::Logger) do |logger| + expect_next_instance_of(BulkImports::Logger) do |logger| expect(logger).to receive(:error) .with( a_hash_including( @@ -67,7 +69,6 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do 'correlation_id' => anything, 'class' => 'BulkImports::MyPipeline', 'message' => 'An object of a pipeline failed to import', - 'importer' => 'gitlab_migration', 'exception.backtrace' => anything, 'source_version' => entity.bulk_import.source_version_info.to_s ) @@ -92,14 +93,13 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do end it 'logs a warn message and marks entity and tracker as failed' do - expect_next_instance_of(Gitlab::Import::Logger) do |logger| + expect_next_instance_of(BulkImports::Logger) do |logger| expect(logger).to receive(:warn) .with( log_params( context, message: 'Aborting entity migration due to pipeline failure', - pipeline_class: 'BulkImports::MyPipeline', - importer: 'gitlab_migration' + pipeline_class: 'BulkImports::MyPipeline' ) ) end @@ -119,6 +119,56 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do expect(entity.failed?).to eq(false) end end + + context 'when failure happens during loader' do + before do + allow(tracker).to receive(:pipeline_class).and_return(BulkImports::MyPipeline) + allow(BulkImports::MyPipeline).to receive(:relation).and_return(relation) + + allow_next_instance_of(BulkImports::Extractor) do |extractor| + allow(extractor).to receive(:extract).with(context).and_return(extracted_data) + end + + allow_next_instance_of(BulkImports::Transformer) do |transformer| + allow(transformer).to receive(:transform).with(context, extracted_data.data.first).and_return(entry) + end + + allow_next_instance_of(BulkImports::Loader) do |loader| + allow(loader).to receive(:load).with(context, entry).and_raise(StandardError, 'Error!') + end + end + + context 'when entry has title' do + let(:relation) { 'issues' } + let(:entry) { Issue.new(iid: 1, title: 'hello world') } + + it 'creates failure record with source url and title' do + subject.run + + failure = entity.failures.first + expected_source_url = File.join(configuration.url, 'groups', entity.source_full_path, '-', 'issues', '1') + + expect(failure).to be_present + expect(failure.source_url).to eq(expected_source_url) + expect(failure.source_title).to eq('hello world') + end + end + + context 'when entry has name' do + let(:relation) { 'boards' } + let(:entry) { Board.new(name: 'hello world') } + + it 'creates failure record with name' do + subject.run + + failure = entity.failures.first + + expect(failure).to be_present + expect(failure.source_url).to be_nil + expect(failure.source_title).to eq('hello world') + end + end + end end describe 'pipeline runner' do @@ -144,6 +194,8 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do .with(context, extracted_data.data.first) end + expect(subject).to receive(:on_finish) + expect_next_instance_of(Gitlab::Import::Logger) do |logger| expect(logger).to receive(:info) .with( @@ -185,6 +237,14 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do log_params( context, pipeline_class: 'BulkImports::MyPipeline', + pipeline_step: :on_finish + ) + ) + expect(logger).to receive(:info) + .with( + log_params( + context, + pipeline_class: 'BulkImports::MyPipeline', pipeline_step: :after_run ) ) @@ -201,6 +261,28 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do subject.run end + context 'when the pipeline is batched' do + let(:tracker) { create(:bulk_import_tracker, :batched, entity: entity) } + + before do + allow_next_instance_of(BulkImports::Extractor) do |extractor| + allow(extractor).to receive(:extract).and_return(extracted_data) + end + end + + it 'calls after_run' do + expect(subject).to receive(:after_run) + + subject.run + end + + it 'does not call on_finish' do + expect(subject).not_to receive(:on_finish) + + subject.run + end + end + context 'when extracted data has multiple pages' do it 'updates tracker information and runs pipeline again' do first_page = extracted_data(has_next_page: true) @@ -299,34 +381,6 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do subject.run end - - context 'with FF bulk_import_idempotent_workers disabled' do - before do - stub_feature_flags(bulk_import_idempotent_workers: false) - end - - it 'does not touch the cache' do - expect_next_instance_of(BulkImports::Extractor) do |extractor| - expect(extractor) - .to receive(:extract) - .with(context) - .and_return(extracted_data) - end - - expect_next_instance_of(BulkImports::Transformer) do |transformer| - expect(transformer) - .to receive(:transform) - .with(context, extracted_data.data.first) - .and_return(extracted_data.data.first) - end - - expect_next_instance_of(BulkImports::MyPipeline) do |klass| - expect(klass).not_to receive(:save_processed_entry) - end - - subject.run - end - end end context 'when the entry is already processed' do @@ -356,43 +410,13 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do subject.run end - - context 'with FF bulk_import_idempotent_workers disabled' do - before do - stub_feature_flags(bulk_import_idempotent_workers: false) - end - - it 'calls extractor, transformer, and loader' do - expect_next_instance_of(BulkImports::Extractor) do |extractor| - expect(extractor) - .to receive(:extract) - .with(context) - .and_return(extracted_data) - end - - expect_next_instance_of(BulkImports::Transformer) do |transformer| - expect(transformer) - .to receive(:transform) - .with(context, extracted_data.data.first) - .and_return(extracted_data.data.first) - end - - expect_next_instance_of(BulkImports::Loader) do |loader| - expect(loader) - .to receive(:load) - .with(context, extracted_data.data.first) - end - - subject.run - end - end end context 'when entity is marked as failed' do it 'logs and returns without execution' do entity.fail_op! - expect_next_instance_of(Gitlab::Import::Logger) do |logger| + expect_next_instance_of(BulkImports::Logger) do |logger| expect(logger).to receive(:warn) .with( log_params( @@ -414,14 +438,17 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do bulk_import_entity_type: context.entity.source_type, source_full_path: entity.source_full_path, source_version: context.entity.bulk_import.source_version_info.to_s, - importer: 'gitlab_migration', context_extra: context.extra }.merge(extra) end def extracted_data(has_next_page: false) BulkImports::Pipeline::ExtractedData.new( - data: { foo: :bar }, + data: { + 'foo' => 'bar', + 'title' => 'hello world', + 'iid' => 1 + }, page_info: { 'has_next_page' => has_next_page, 'next_page' => has_next_page ? 'cursor' : nil diff --git a/spec/lib/bulk_imports/pipeline_schema_info_spec.rb b/spec/lib/bulk_imports/pipeline_schema_info_spec.rb new file mode 100644 index 00000000000..45dd92ca26d --- /dev/null +++ b/spec/lib/bulk_imports/pipeline_schema_info_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe BulkImports::PipelineSchemaInfo, feature_category: :importers do + let(:entity) { build(:bulk_import_entity, :project_entity) } + let(:tracker) { build(:bulk_import_tracker, entity: entity, pipeline_name: pipeline_name) } + + let(:pipeline_name) { BulkImports::Common::Pipelines::LabelsPipeline.to_s } + + subject { described_class.new(tracker.pipeline_class, tracker.entity.portable_class) } + + describe '#db_schema' do + context 'when pipeline defines a relation name which is an association' do + it 'returns the schema name of the table used by the association' do + expect(subject.db_schema).to eq(:gitlab_main_cell) + end + end + + context 'when pipeline does not define a relation name' do + let(:pipeline_name) { BulkImports::Common::Pipelines::EntityFinisher.to_s } + + it 'returns nil' do + expect(subject.db_schema).to eq(nil) + end + end + + context 'when pipeline relation name is not an association' do + let(:pipeline_name) { BulkImports::Projects::Pipelines::CommitNotesPipeline.to_s } + + it 'returns nil' do + expect(subject.db_schema).to eq(nil) + end + end + end + + describe '#db_table' do + context 'when pipeline defines a relation name which is an association' do + it 'returns the name of the table used by the association' do + expect(subject.db_table).to eq('labels') + end + end + + context 'when pipeline does not define a relation name' do + let(:pipeline_name) { BulkImports::Common::Pipelines::EntityFinisher.to_s } + + it 'returns nil' do + expect(subject.db_table).to eq(nil) + end + end + + context 'when pipeline relation name is not an association' do + let(:pipeline_name) { BulkImports::Projects::Pipelines::CommitNotesPipeline.to_s } + + it 'returns nil' do + expect(subject.db_table).to eq(nil) + end + end + end +end diff --git a/spec/lib/bulk_imports/projects/pipelines/container_expiration_policy_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/container_expiration_policy_pipeline_spec.rb index 9dac8e45ef9..334c2004b59 100644 --- a/spec/lib/bulk_imports/projects/pipelines/container_expiration_policy_pipeline_spec.rb +++ b/spec/lib/bulk_imports/projects/pipelines/container_expiration_policy_pipeline_spec.rb @@ -2,10 +2,10 @@ require 'spec_helper' -RSpec.describe BulkImports::Projects::Pipelines::ContainerExpirationPolicyPipeline do +RSpec.describe BulkImports::Projects::Pipelines::ContainerExpirationPolicyPipeline, feature_category: :importers do let_it_be(:project) { create(:project) } let_it_be(:entity) { create(:bulk_import_entity, :project_entity, project: project) } - let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) } + let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity, pipeline_name: described_class) } let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) } let_it_be(:policy) do diff --git a/spec/lib/bulk_imports/projects/pipelines/external_pull_requests_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/external_pull_requests_pipeline_spec.rb index b7197814f9c..f00da47d9f5 100644 --- a/spec/lib/bulk_imports/projects/pipelines/external_pull_requests_pipeline_spec.rb +++ b/spec/lib/bulk_imports/projects/pipelines/external_pull_requests_pipeline_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' -RSpec.describe BulkImports::Projects::Pipelines::ExternalPullRequestsPipeline do +RSpec.describe BulkImports::Projects::Pipelines::ExternalPullRequestsPipeline, feature_category: :importers do let_it_be(:project) { create(:project) } let_it_be(:bulk_import) { create(:bulk_import) } let_it_be(:entity) { create(:bulk_import_entity, :project_entity, project: project, bulk_import: bulk_import) } - let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) } + let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity, pipeline_name: described_class) } let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) } let(:attributes) { {} } diff --git a/spec/lib/bulk_imports/projects/pipelines/merge_requests_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/merge_requests_pipeline_spec.rb index 3fb7e28036e..b9e424f4a7d 100644 --- a/spec/lib/bulk_imports/projects/pipelines/merge_requests_pipeline_spec.rb +++ b/spec/lib/bulk_imports/projects/pipelines/merge_requests_pipeline_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkImports::Projects::Pipelines::MergeRequestsPipeline do +RSpec.describe BulkImports::Projects::Pipelines::MergeRequestsPipeline, feature_category: :importers do let_it_be(:user) { create(:user) } let_it_be(:another_user) { create(:user) } let_it_be(:group) { create(:group) } @@ -43,6 +43,7 @@ RSpec.describe BulkImports::Projects::Pipelines::MergeRequestsPipeline do 'base_commit_sha' => 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f', 'head_commit_sha' => 'a97f74ddaa848b707bea65441c903ae4bf5d844d', 'start_commit_sha' => '9eea46b5c72ead701c22f516474b95049c9d9462', + 'diff_type' => 1, 'merge_request_diff_commits' => [ { 'sha' => 'COMMIT1', @@ -99,6 +100,8 @@ RSpec.describe BulkImports::Projects::Pipelines::MergeRequestsPipeline do allow(project.repository).to receive(:branch_exists?).and_return(false) allow(project.repository).to receive(:create_branch) + allow(::Projects::ImportExport::AfterImportMergeRequestsWorker).to receive(:perform_async) + pipeline.run end @@ -244,8 +247,10 @@ RSpec.describe BulkImports::Projects::Pipelines::MergeRequestsPipeline do expect(imported_mr.merge_request_diff).to be_present end - it 'has the correct data for merge request latest_merge_request_diff' do - expect(imported_mr.latest_merge_request_diff_id).to eq(imported_mr.merge_request_diffs.maximum(:id)) + it 'enqueues AfterImportMergeRequestsWorker worker' do + expect(::Projects::ImportExport::AfterImportMergeRequestsWorker) + .to have_received(:perform_async) + .with(project.id) end it 'imports diff files' do diff --git a/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb index 9e0b5af6bfe..fa85e24189c 100644 --- a/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb +++ b/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline do +RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline, feature_category: :importers do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } diff --git a/spec/lib/bulk_imports/source_url_builder_spec.rb b/spec/lib/bulk_imports/source_url_builder_spec.rb new file mode 100644 index 00000000000..2c0e042314b --- /dev/null +++ b/spec/lib/bulk_imports/source_url_builder_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe BulkImports::SourceUrlBuilder, feature_category: :importers do + let_it_be(:bulk_import) { create(:bulk_import) } + let_it_be(:configuration) { create(:bulk_import_configuration, bulk_import: bulk_import) } + + let(:entity) { create(:bulk_import_entity, bulk_import: bulk_import) } + let(:tracker) { create(:bulk_import_tracker, entity: entity) } + let(:context) { BulkImports::Pipeline::Context.new(tracker) } + let(:entry) { Issue.new(iid: 1, title: 'hello world') } + + describe '#url' do + subject { described_class.new(context, entry) } + + before do + allow(subject).to receive(:relation).and_return('issues') + end + + context 'when relation is allowed' do + context 'when entity is a group' do + it 'returns the url specific to groups' do + expected_url = File.join( + configuration.url, + 'groups', + entity.source_full_path, + '-', + 'issues', + '1' + ) + + expect(subject.url).to eq(expected_url) + end + end + + context 'when entity is a project' do + let(:entity) { create(:bulk_import_entity, :project_entity, bulk_import: bulk_import) } + + it 'returns the url' do + expected_url = File.join( + configuration.url, + entity.source_full_path, + '-', + 'issues', + '1' + ) + + expect(subject.url).to eq(expected_url) + end + end + end + + context 'when entry is not an ApplicationRecord' do + let(:entry) { 'not an ApplicationRecord' } + + it 'returns nil' do + expect(subject.url).to be_nil + end + end + + context 'when relation is not allowed' do + it 'returns nil' do + allow(subject).to receive(:relation).and_return('not_allowed') + + expect(subject.url).to be_nil + end + end + + context 'when entry has no iid' do + let(:entry) { Issue.new } + + it 'returns nil' do + expect(subject.url).to be_nil + end + end + end +end diff --git a/spec/lib/click_house/models/audit_event_spec.rb b/spec/lib/click_house/models/audit_event_spec.rb new file mode 100644 index 00000000000..ea3f1a6cbd4 --- /dev/null +++ b/spec/lib/click_house/models/audit_event_spec.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ClickHouse::Models::AuditEvent, feature_category: :audit_events do + let(:instance) { described_class.new } + + describe '#by_entity_type' do + it 'builds the correct SQL' do + expected_sql = <<~SQL + SELECT * FROM "audit_events" WHERE "audit_events"."entity_type" = 'Project' + SQL + + result_sql = instance.by_entity_type("Project").to_sql + + expect(result_sql.strip).to eq(expected_sql.strip) + end + end + + describe '#by_entity_id' do + it 'builds the correct SQL' do + expected_sql = <<~SQL + SELECT * FROM "audit_events" WHERE "audit_events"."entity_id" = 42 + SQL + + result_sql = instance.by_entity_id(42).to_sql + + expect(result_sql.strip).to eq(expected_sql.strip) + end + end + + describe '#by_author_id' do + it 'builds the correct SQL' do + expected_sql = <<~SQL + SELECT * FROM "audit_events" WHERE "audit_events"."author_id" = 5 + SQL + + result_sql = instance.by_author_id(5).to_sql + + expect(result_sql.strip).to eq(expected_sql.strip) + end + end + + describe '#by_entity_username' do + let_it_be(:user) { create(:user, username: 'Dummy') } + + it 'builds the correct SQL' do + expected_sql = <<~SQL + SELECT * FROM "audit_events" WHERE "audit_events"."entity_id" = #{user.id} + SQL + + result_sql = instance.by_entity_username('Dummy').to_sql + + expect(result_sql.strip).to eq(expected_sql.strip) + end + end + + describe '#by_author_username' do + let_it_be(:user) { create(:user, username: 'Dummy') } + + it 'builds the correct SQL' do + expected_sql = <<~SQL + SELECT * FROM "audit_events" WHERE "audit_events"."author_id" = #{user.id} + SQL + + result_sql = instance.by_author_username('Dummy').to_sql + + expect(result_sql.strip).to eq(expected_sql.strip) + end + end + + describe 'class methods' do + before do + allow(described_class).to receive(:new).and_return(instance) + end + + describe '.by_entity_type' do + it 'calls the corresponding instance method' do + expect(instance).to receive(:by_entity_type).with("Project") + + described_class.by_entity_type("Project") + end + end + + describe '.by_entity_id' do + it 'calls the corresponding instance method' do + expect(instance).to receive(:by_entity_id).with(42) + + described_class.by_entity_id(42) + end + end + + describe '.by_author_id' do + it 'calls the corresponding instance method' do + expect(instance).to receive(:by_author_id).with(5) + + described_class.by_author_id(5) + end + end + + describe '.by_entity_username' do + it 'calls the corresponding instance method' do + expect(instance).to receive(:by_entity_username).with('Dummy') + + described_class.by_entity_username('Dummy') + end + end + + describe '.by_author_username' do + it 'calls the corresponding instance method' do + expect(instance).to receive(:by_author_username).with('Dummy') + + described_class.by_author_username('Dummy') + end + end + end + + describe 'method chaining' do + it 'builds the correct SQL with chained methods' do + expected_sql = <<~SQL.lines(chomp: true).join(' ') + SELECT * FROM "audit_events" + WHERE "audit_events"."entity_type" = 'Project' + AND "audit_events"."author_id" = 1 + SQL + + instance = described_class.new + result_sql = instance.by_entity_type("Project").by_author_id(1).to_sql + + expect(result_sql.strip).to eq(expected_sql.strip) + end + end +end diff --git a/spec/lib/click_house/models/base_model_spec.rb b/spec/lib/click_house/models/base_model_spec.rb new file mode 100644 index 00000000000..376300d7781 --- /dev/null +++ b/spec/lib/click_house/models/base_model_spec.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ClickHouse::Models::BaseModel, feature_category: :database do + let(:table_name) { "dummy_table" } + let(:query_builder) { instance_double("ClickHouse::QueryBuilder") } + let(:updated_query_builder) { instance_double("ClickHouse::QueryBuilder") } + + let(:dummy_class) do + Class.new(described_class) do + def self.table_name + "dummy_table" + end + end + end + + describe '#to_sql' do + it 'delegates to the query builder' do + expect(query_builder).to receive(:to_sql).and_return("SELECT * FROM dummy_table") + + dummy_instance = dummy_class.new(query_builder) + + expect(dummy_instance.to_sql).to eq("SELECT * FROM dummy_table") + end + end + + describe '#where' do + it 'returns a new instance with refined query' do + dummy_instance = dummy_class.new(query_builder) + + expect(query_builder).to receive(:where).with({ foo: "bar" }).and_return(updated_query_builder) + + new_instance = dummy_instance.where(foo: "bar") + + expect(new_instance).to be_a(dummy_class) + expect(new_instance).not_to eq(dummy_instance) + end + end + + describe '#order' do + it 'returns a new instance with an order clause' do + dummy_instance = dummy_class.new(query_builder) + + expect(query_builder).to receive(:order).with(:created_at, :asc).and_return(updated_query_builder) + + new_instance = dummy_instance.order(:created_at) + + expect(new_instance).to be_a(dummy_class) + expect(new_instance).not_to eq(dummy_instance) + end + + context "when direction is also passed" do + it 'returns a new instance with an order clause' do + dummy_instance = dummy_class.new(query_builder) + + expect(query_builder).to receive(:order).with(:created_at, :desc).and_return(updated_query_builder) + + new_instance = dummy_instance.order(:created_at, :desc) + + expect(new_instance).to be_a(dummy_class) + expect(new_instance).not_to eq(dummy_instance) + end + end + end + + describe '#limit' do + it 'returns a new instance with a limit clause' do + dummy_instance = dummy_class.new(query_builder) + + expect(query_builder).to receive(:limit).with(10).and_return(updated_query_builder) + + new_instance = dummy_instance.limit(10) + + expect(new_instance).to be_a(dummy_class) + expect(new_instance).not_to eq(dummy_instance) + end + end + + describe '#offset' do + it 'returns a new instance with an offset clause' do + dummy_instance = dummy_class.new(query_builder) + + expect(query_builder).to receive(:offset).with(5).and_return(updated_query_builder) + + new_instance = dummy_instance.offset(5) + + expect(new_instance).to be_a(dummy_class) + expect(new_instance).not_to eq(dummy_instance) + end + end + + describe '#select' do + it 'returns a new instance with selected fields' do + dummy_instance = dummy_class.new(query_builder) + + expect(query_builder).to receive(:select).with(:id, :name).and_return(updated_query_builder) + + new_instance = dummy_instance.select(:id, :name) + + expect(new_instance).to be_a(dummy_class) + expect(new_instance).not_to eq(dummy_instance) + end + end + + describe '.table_name' do + it 'raises a NotImplementedError for the base model' do + expect do + described_class.table_name + end.to raise_error(NotImplementedError, "Subclasses must define a `table_name` class method") + end + + it 'does not raise an error for the subclass' do + expect(dummy_class.table_name).to eq(table_name) + end + end +end diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb index 39409cf8d3a..37161119744 100644 --- a/spec/lib/container_registry/client_spec.rb +++ b/spec/lib/container_registry/client_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ContainerRegistry::Client do +RSpec.describe ContainerRegistry::Client, feature_category: :container_registry do using RSpec::Parameterized::TableSyntax include_context 'container registry client' @@ -307,12 +307,12 @@ RSpec.describe ContainerRegistry::Client do end end - describe '#delete_repository_tag_by_name' do - subject { client.delete_repository_tag_by_name('group/test', 'a') } + describe '#delete_repository_tag_by_digest' do + subject { client.delete_repository_tag_by_digest('group/test', 'a') } context 'when the tag exists' do before do - stub_request(:delete, "http://container-registry/v2/group/test/tags/reference/a") + stub_request(:delete, "http://container-registry/v2/group/test/manifests/a") .with(headers: headers_with_accept_types) .to_return(status: 200, body: "") end @@ -322,7 +322,7 @@ RSpec.describe ContainerRegistry::Client do context 'when the tag does not exist' do before do - stub_request(:delete, "http://container-registry/v2/group/test/tags/reference/a") + stub_request(:delete, "http://container-registry/v2/group/test/manifests/a") .with(headers: headers_with_accept_types) .to_return(status: 404, body: "") end @@ -332,7 +332,7 @@ RSpec.describe ContainerRegistry::Client do context 'when an error occurs' do before do - stub_request(:delete, "http://container-registry/v2/group/test/tags/reference/a") + stub_request(:delete, "http://container-registry/v2/group/test/manifests/a") .with(headers: headers_with_accept_types) .to_return(status: 500, body: "") end @@ -485,7 +485,7 @@ RSpec.describe ContainerRegistry::Client do def stub_registry_tags_support(supported = true) status_code = supported ? 200 : 404 - stub_request(:options, "#{registry_api_url}/v2/name/tags/reference/tag") + stub_request(:options, "#{registry_api_url}/v2/name/manifests/tag") .to_return( status: status_code, body: '', diff --git a/spec/lib/container_registry/gitlab_api_client_spec.rb b/spec/lib/container_registry/gitlab_api_client_spec.rb index 86675ba27f6..3c87af3a1c8 100644 --- a/spec/lib/container_registry/gitlab_api_client_spec.rb +++ b/spec/lib/container_registry/gitlab_api_client_spec.rb @@ -220,6 +220,7 @@ RSpec.describe ContainerRegistry::GitlabApiClient, feature_category: :container_ { name: '0.1.0', digest: 'sha256:1234567890', + config_digest: 'sha256:13828381121', media_type: 'application/vnd.oci.image.manifest.v1+json', size_bytes: 1234567890, created_at: 5.minutes.ago @@ -227,6 +228,7 @@ RSpec.describe ContainerRegistry::GitlabApiClient, feature_category: :container_ { name: 'latest', digest: 'sha256:1234567892', + config_digest: 'sha256:33139438113', media_type: 'application/vnd.oci.image.manifest.v1+json', size_bytes: 1234567892, created_at: 10.minutes.ago diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb index cb5c6a60e1d..8f9308f2127 100644 --- a/spec/lib/container_registry/tag_spec.rb +++ b/spec/lib/container_registry/tag_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ContainerRegistry::Tag do +RSpec.describe ContainerRegistry::Tag, feature_category: :container_registry do let(:group) { create(:group, name: 'group') } let(:project) { create(:project, path: 'test', group: group) } @@ -74,6 +74,77 @@ RSpec.describe ContainerRegistry::Tag do end end + describe '#total_size' do + context 'when total_size is set' do + before do + tag.total_size = 1000 + end + + it 'returns the set size' do + expect(tag.total_size).to eq(1000) + end + end + end + + describe '#revision' do + context 'when revision is set' do + before do + tag.revision = 'xyz789' + end + + it 'returns the set revision' do + expect(tag.revision).to eq('xyz789') + end + end + + context 'when revision is not set' do + context 'when config_blob is not nil' do + let(:blob) { ContainerRegistry::Blob.new(repository, {}) } + + before do + allow(tag).to receive(:config_blob).and_return(blob) + allow(blob).to receive(:revision).and_return('abc123') + end + + it 'returns the revision from config_blob' do + expect(tag.revision).to eq('abc123') + end + end + + context 'when config_blob is nil' do + before do + allow(tag).to receive(:config_blob).and_return(nil) + end + + it 'returns nil' do + expect(tag.revision).to be_nil + end + end + end + end + + describe '#short_revision' do + context 'when revision is not nil' do + before do + allow(tag).to receive(:revision).and_return('abcdef1234567890') + end + + it 'returns the first 9 characters of the revision' do + expect(tag.short_revision).to eq('abcdef123') + end + end + + context 'when revision is nil' do + before do + allow(tag).to receive(:revision).and_return(nil) + end + + it 'returns nil' do + expect(tag.short_revision).to be_nil + end + end + end + context 'schema v1' do before do stub_request(:get, 'http://registry.gitlab/v2/group/test/manifests/tag') @@ -277,6 +348,16 @@ RSpec.describe ContainerRegistry::Tag do end describe '#digest' do + context 'when manifest_digest is set' do + before do + tag.manifest_digest = 'sha256:manifestdigest' + end + + it 'returns the set manifest_digest' do + expect(tag.digest).to eq('sha256:manifestdigest') + end + end + it 'returns a correct tag digest' do expect(tag.digest).to eq 'sha256:digest' end diff --git a/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb b/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb index 2d48b83be4c..893cf976074 100644 --- a/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb +++ b/spec/lib/generators/batched_background_migration/batched_background_migration_generator_spec.rb @@ -14,6 +14,7 @@ RSpec.describe BatchedBackgroundMigration::BatchedBackgroundMigrationGenerator, before do prepare_destination + allow(Gitlab).to receive(:current_milestone).and_return('16.6') end after do diff --git a/spec/lib/generators/batched_background_migration/expected_files/queue_my_batched_migration.txt b/spec/lib/generators/batched_background_migration/expected_files/queue_my_batched_migration.txt index aa79062422b..36f7885b591 100644 --- a/spec/lib/generators/batched_background_migration/expected_files/queue_my_batched_migration.txt +++ b/spec/lib/generators/batched_background_migration/expected_files/queue_my_batched_migration.txt @@ -5,7 +5,9 @@ # Update below commented lines with appropriate values. -class QueueMyBatchedMigration < Gitlab::Database::Migration[2.1] +class QueueMyBatchedMigration < Gitlab::Database::Migration[2.2] + milestone '16.6' + MIGRATION = "MyBatchedMigration" # DELAY_INTERVAL = 2.minutes # BATCH_SIZE = 1000 diff --git a/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb b/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb deleted file mode 100644 index 740cfa767e4..00000000000 --- a/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb +++ /dev/null @@ -1,95 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout, feature_category: :product_analytics_data_management do - let(:ce_temp_dir) { Dir.mktmpdir } - let(:ee_temp_dir) { Dir.mktmpdir } - let(:timestamp) { Time.now.utc.strftime('%Y%m%d%H%M%S') } - - let(:generator_options) do - { 'category' => 'Projects::Pipelines::EmailCampaignsController', 'action' => 'click' } - end - - before do - stub_const("#{described_class}::CE_DIR", ce_temp_dir) - stub_const("#{described_class}::EE_DIR", ee_temp_dir) - end - - around do |example| - freeze_time { example.run } - end - - after do - FileUtils.rm_rf([ce_temp_dir, ee_temp_dir]) - end - - describe 'Creating event definition file' do - before do - stub_const('Gitlab::VERSION', '13.11.0-pre') - end - - let(:sample_event_dir) { 'lib/generators/gitlab/snowplow_event_definition_generator' } - let(:file_name) { Dir.children(ce_temp_dir).first } - - it 'creates CE event definition file using the template' do - sample_event = ::Gitlab::Config::Loader::Yaml - .new(fixture_file(File.join(sample_event_dir, 'sample_event.yml'))).load_raw! - - described_class.new([], generator_options).invoke_all - - event_definition_path = File.join(ce_temp_dir, file_name) - expect(::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw!).to eq(sample_event) - end - - describe 'generated filename' do - it 'includes timestamp' do - described_class.new([], generator_options).invoke_all - - expect(file_name).to include(timestamp.to_s) - end - - it 'removes special characters' do - generator_options = { 'category' => '"`ui:[mavenpackages | t5%348()-=@ ]`"', 'action' => 'click' } - - described_class.new([], generator_options).invoke_all - - expect(file_name).to include('uimavenpackagest') - end - - it 'cuts name if longer than 100 characters' do - generator_options = { 'category' => 'a' * 100, 'action' => 'click' } - - described_class.new([], generator_options).invoke_all - - expect(file_name.length).to eq(100) - end - end - - context 'when event definition with same file name already exists' do - before do - stub_const('Gitlab::VERSION', '12.11.0-pre') - described_class.new([], generator_options).invoke_all - end - - it 'raises error' do - expect { described_class.new([], generator_options.merge('force' => false)).invoke_all } - .to raise_error(StandardError, /Event definition already exists at/) - end - end - - describe 'EE' do - let(:file_name) { Dir.children(ee_temp_dir).first } - - it 'creates EE event definition file using the template' do - sample_event = ::Gitlab::Config::Loader::Yaml - .new(fixture_file(File.join(sample_event_dir, 'sample_event_ee.yml'))).load_raw! - - described_class.new([], generator_options.merge('ee' => true)).invoke_all - - event_definition_path = File.join(ee_temp_dir, file_name) - expect(::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw!).to eq(sample_event) - end - end - end -end diff --git a/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb b/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb index b6e1d59f6c0..5265b608ab4 100644 --- a/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb +++ b/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::UsageMetricDefinition::RedisHllGenerator, :silence_stdout do +RSpec.describe Gitlab::UsageMetricDefinition::RedisHllGenerator, :silence_stdout, feature_category: :service_ping do include UsageDataHelpers let(:category) { 'test_category' } @@ -16,6 +16,10 @@ RSpec.describe Gitlab::UsageMetricDefinition::RedisHllGenerator, :silence_stdout stub_const("#{Gitlab::UsageMetricDefinitionGenerator}::TOP_LEVEL_DIR", temp_dir) # Stub Prometheus requests from Gitlab::Utils::UsageData stub_prometheus_queries + + allow_next_instance_of(Gitlab::UsageMetricDefinitionGenerator) do |instance| + allow(instance).to receive(:ask).and_return('y') # confirm deprecation warning + end end after do diff --git a/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb b/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb index f7a4bac39d7..e0cb74d8559 100644 --- a/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb +++ b/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::UsageMetricDefinitionGenerator, :silence_stdout do +RSpec.describe Gitlab::UsageMetricDefinitionGenerator, :silence_stdout, feature_category: :service_ping do include UsageDataHelpers let(:key_path) { 'counts_weekly.test_metric' } @@ -14,6 +14,10 @@ RSpec.describe Gitlab::UsageMetricDefinitionGenerator, :silence_stdout do stub_const("#{described_class}::TOP_LEVEL_DIR", temp_dir) # Stub Prometheus requests from Gitlab::Utils::UsageData stub_prometheus_queries + + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:ask).and_return('y') # confirm deprecation warning + end end after do @@ -100,4 +104,19 @@ RSpec.describe Gitlab::UsageMetricDefinitionGenerator, :silence_stdout do expect(files.count).to eq(2) end end + + ['n', 'N', 'random word', nil].each do |answer| + context "when user agreed with deprecation warning by typing: #{answer}" do + it 'does not create definition file' do + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:ask).and_return(answer) + end + + described_class.new([key_path], { 'dir' => dir, 'class_name' => class_name }).invoke_all + files = Dir.glob(File.join(temp_dir, 'metrics/counts_7d/*_metric.yml')) + + expect(files.count).to eq(0) + end + end + end end diff --git a/spec/lib/generators/model/mocks/migration_file.txt b/spec/lib/generators/model/mocks/migration_file.txt index c9e51e51863..d0a5c71ffc3 100644 --- a/spec/lib/generators/model/mocks/migration_file.txt +++ b/spec/lib/generators/model/mocks/migration_file.txt @@ -3,7 +3,7 @@ # See https://docs.gitlab.com/ee/development/migration_style_guide.html # for more information on how to write migrations for GitLab. -class CreateModelGeneratorTestFoos < Gitlab::Database::Migration[2.1] +class CreateModelGeneratorTestFoos < Gitlab::Database::Migration[2.2] # When using the methods "add_concurrent_index" or "remove_concurrent_index" # you must disable the use of transactions # as these methods can not run in an existing transaction. @@ -16,6 +16,7 @@ class CreateModelGeneratorTestFoos < Gitlab::Database::Migration[2.1] # To disable transactions uncomment the following line and remove these # comments: # disable_ddl_transaction! + milestone '16.5' # Add dependent 'batched_background_migrations.queued_migration_version' values. # DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = [] diff --git a/spec/lib/generators/model/model_generator_spec.rb b/spec/lib/generators/model/model_generator_spec.rb index 0e770190d25..7284fc8b28a 100644 --- a/spec/lib/generators/model/model_generator_spec.rb +++ b/spec/lib/generators/model/model_generator_spec.rb @@ -17,6 +17,10 @@ RSpec.describe Model::ModelGenerator do FileUtils.rm_rf(temp_dir) end + before do + allow(Gitlab).to receive(:current_milestone).and_return('16.5') + end + it 'creates the model file with the right content' do subject.invoke_all diff --git a/spec/lib/gitlab/alert_management/payload/base_spec.rb b/spec/lib/gitlab/alert_management/payload/base_spec.rb index 3e8d71ac673..bfde0a69f98 100644 --- a/spec/lib/gitlab/alert_management/payload/base_spec.rb +++ b/spec/lib/gitlab/alert_management/payload/base_spec.rb @@ -32,7 +32,7 @@ RSpec.describe Gitlab::AlertManagement::Payload::Base do context 'with multiple paths provided' do let(:payload_class) do Class.new(described_class) do - attribute :test, paths: [['test'], %w(alt test)] + attribute :test, paths: [['test'], %w[alt test]] end end @@ -204,8 +204,8 @@ RSpec.describe Gitlab::AlertManagement::Payload::Base do end context 'with too-long hosts array' do - let(:hosts) { %w(abc def ghij) } - let(:shortened_hosts) { %w(abc def ghi) } + let(:hosts) { %w[abc def ghij] } + let(:shortened_hosts) { %w[abc def ghi] } before do stub_const('::AlertManagement::Alert::HOSTS_MAX_LENGTH', 9) @@ -215,15 +215,15 @@ RSpec.describe Gitlab::AlertManagement::Payload::Base do it { is_expected.to eq(hosts: shortened_hosts, project_id: project.id) } context 'with host cut off between elements' do - let(:hosts) { %w(abcde fghij) } - let(:shortened_hosts) { %w(abcde fghi) } + let(:hosts) { %w[abcde fghij] } + let(:shortened_hosts) { %w[abcde fghi] } it { is_expected.to eq({ hosts: shortened_hosts, project_id: project.id }) } end context 'with nested hosts' do let(:hosts) { ['abc', ['de', 'f'], 'g', 'hij'] } # rubocop:disable Style/WordArray - let(:shortened_hosts) { %w(abc de f g hi) } + let(:shortened_hosts) { %w[abc de f g hi] } it { is_expected.to eq({ hosts: shortened_hosts, project_id: project.id }) } end diff --git a/spec/lib/gitlab/analytics/cycle_analytics/average_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/average_spec.rb index 261d587506f..b2a267d42ec 100644 --- a/spec/lib/gitlab/analytics/cycle_analytics/average_spec.rb +++ b/spec/lib/gitlab/analytics/cycle_analytics/average_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' RSpec.describe Gitlab::Analytics::CycleAnalytics::Average, feature_category: :value_stream_management do let_it_be(:project) { create(:project) } - let_it_be(:issue_1) do # Duration: 10 days create(:issue, project: project, created_at: 20.days.ago).tap do |issue| @@ -30,8 +29,12 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Average, feature_category: :va let(:query) { Issue.joins(:metrics).in_projects(project.id) } - around do |example| - freeze_time { example.run } + before_all do + freeze_time + end + + after :all do + unfreeze_time end subject(:average) { described_class.new(stage: stage, query: query) } @@ -45,8 +48,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Average, feature_category: :va it { is_expected.to eq(nil) } end - context 'returns the average duration in seconds', - quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/413223' do + context 'returns the average duration in seconds' do it { is_expected.to be_within(0.5).of(7.5.days.to_f) } end end diff --git a/spec/lib/gitlab/asset_proxy_spec.rb b/spec/lib/gitlab/asset_proxy_spec.rb index 7d7952d5741..af8721739a0 100644 --- a/spec/lib/gitlab/asset_proxy_spec.rb +++ b/spec/lib/gitlab/asset_proxy_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Gitlab::AssetProxy do context 'when asset proxy is enabled' do before do - stub_asset_proxy_setting(allowlist: %w(gitlab.com *.mydomain.com)) + stub_asset_proxy_setting(allowlist: %w[gitlab.com *.mydomain.com]) stub_asset_proxy_setting( enabled: true, url: 'https://assets.example.com', diff --git a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb index c19d890a703..0208255d24d 100644 --- a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb +++ b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb @@ -52,7 +52,7 @@ RSpec.describe Gitlab::Auth::Ldap::AuthHash do let(:attributes) do { - 'username' => %w(mail email), + 'username' => %w[mail email], 'name' => 'fullName' } end diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb index 48039b58216..f97b16254e7 100644 --- a/spec/lib/gitlab/auth/ldap/config_spec.rb +++ b/spec/lib/gitlab/auth/ldap/config_spec.rb @@ -90,7 +90,7 @@ AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK end it 'returns one provider' do - expect(described_class.available_providers).to match_array(%w(ldapmain)) + expect(described_class.available_providers).to match_array(%w[ldapmain]) end end @@ -552,15 +552,15 @@ AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK stub_ldap_config( options: { 'attributes' => { - 'username' => %w(sAMAccountName), - 'email' => %w(userPrincipalName) + 'username' => %w[sAMAccountName], + 'email' => %w[userPrincipalName] } } ) expect(config.attributes).to include({ - 'username' => %w(sAMAccountName), - 'email' => %w(userPrincipalName), + 'username' => %w[sAMAccountName], + 'email' => %w[userPrincipalName], 'name' => 'cn' }) end diff --git a/spec/lib/gitlab/auth/ldap/person_spec.rb b/spec/lib/gitlab/auth/ldap/person_spec.rb index f8268bb1666..b5fd44d4aa9 100644 --- a/spec/lib/gitlab/auth/ldap/person_spec.rb +++ b/spec/lib/gitlab/auth/ldap/person_spec.rb @@ -13,13 +13,13 @@ RSpec.describe Gitlab::Auth::Ldap::Person do 'uid' => 'uid', 'attributes' => { 'name' => 'cn', - 'email' => %w(mail email userPrincipalName), + 'email' => %w[mail email userPrincipalName], 'username' => username_attribute } } ) end - let(:username_attribute) { %w(uid sAMAccountName userid) } + let(:username_attribute) { %w[uid sAMAccountName userid] } describe '.normalize_dn' do subject { described_class.normalize_dn(given) } @@ -57,7 +57,7 @@ RSpec.describe Gitlab::Auth::Ldap::Person do 'attributes' => { 'name' => 'cn', 'email' => 'mail', - 'username' => %w(uid mail), + 'username' => %w[uid mail], 'first_name' => '' } } diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb index 8a9182f6457..c137ca88589 100644 --- a/spec/lib/gitlab/auth/o_auth/user_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb @@ -369,7 +369,7 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :system_access do context "and at least one LDAP provider is defined" do before do - stub_ldap_config(providers: %w(ldapmain)) + stub_ldap_config(providers: %w[ldapmain]) end context "and a corresponding LDAP person" do @@ -570,7 +570,7 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :system_access do before do allow(ldap_user).to receive(:uid) { uid } allow(ldap_user).to receive(:username) { 'johndoe@example.com' } - allow(ldap_user).to receive(:email) { %w(johndoe@example.com john2@example.com) } + allow(ldap_user).to receive(:email) { %w[johndoe@example.com john2@example.com] } allow(ldap_user).to receive(:dn) { dn } end @@ -605,7 +605,7 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :system_access do context "and at least one LDAP provider is defined" do before do - stub_ldap_config(providers: %w(ldapmain)) + stub_ldap_config(providers: %w[ldapmain]) end context "and a corresponding LDAP person" do @@ -1055,7 +1055,7 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :system_access do context "update only requested info" do before do stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) - stub_omniauth_setting(sync_profile_attributes: %w(name location)) + stub_omniauth_setting(sync_profile_attributes: %w[name location]) end it "updates the user name" do diff --git a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb index 5286e22abc9..e37b9b10834 100644 --- a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb +++ b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Auth::Saml::AuthHash do include LoginHelpers - let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers) } } + let(:raw_info_attr) { { 'groups' => %w[Developers Freelancers] } } subject(:saml_auth_hash) { described_class.new(omniauth_auth_hash) } let(:info_hash) do @@ -23,12 +23,12 @@ RSpec.describe Gitlab::Auth::Saml::AuthHash do end before do - stub_saml_group_config(%w(Developers Freelancers Designers)) + stub_saml_group_config(%w[Developers Freelancers Designers]) end describe '#groups' do it 'returns array of groups' do - expect(saml_auth_hash.groups).to eq(%w(Developers Freelancers)) + expect(saml_auth_hash.groups).to eq(%w[Developers Freelancers]) end context 'raw info hash attributes empty' do diff --git a/spec/lib/gitlab/auth/saml/config_spec.rb b/spec/lib/gitlab/auth/saml/config_spec.rb index d657622c9f2..c19171bb6f8 100644 --- a/spec/lib/gitlab/auth/saml/config_spec.rb +++ b/spec/lib/gitlab/auth/saml/config_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::Auth::Saml::Config do + include LoginHelpers + describe '.enabled?' do subject { described_class.enabled? } @@ -10,13 +12,48 @@ RSpec.describe Gitlab::Auth::Saml::Config do context 'when SAML is enabled' do before do - allow(Gitlab::Auth::OAuth::Provider).to receive(:providers).and_return([:saml]) + stub_basic_saml_config end it { is_expected.to eq(true) } end end + describe '.default_attribute_statements' do + it 'includes upstream defaults, nickname and Microsoft values' do + expect(described_class.default_attribute_statements).to match_array( + { + nickname: %w[username nickname], + name: [ + 'name', + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name', + 'http://schemas.microsoft.com/ws/2008/06/identity/claims/name' + ], + email: [ + 'email', + 'mail', + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', + 'http://schemas.microsoft.com/ws/2008/06/identity/claims/emailaddress' + ], + first_name: [ + 'first_name', + 'firstname', + 'firstName', + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname', + 'http://schemas.microsoft.com/ws/2008/06/identity/claims/givenname' + ], + last_name: [ + 'last_name', + 'lastname', + 'lastName', + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname', + 'http://schemas.microsoft.com/ws/2008/06/identity/claims/surname' + ] + } + ) + end + end + describe '#external_groups' do let(:config_1) { described_class.new('saml1') } diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb index a8a5d8ae5df..034d1a69a0b 100644 --- a/spec/lib/gitlab/auth/saml/user_spec.rb +++ b/spec/lib/gitlab/auth/saml/user_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Gitlab::Auth::Saml::User do let(:uid) { 'my-uid' } let(:dn) { 'uid=user1,ou=people,dc=example' } let(:provider) { 'saml' } - let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers Designers) } } + let(:raw_info_attr) { { 'groups' => %w[Developers Freelancers Designers] } } let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash, extra: { raw_info: OneLogin::RubySaml::Attributes.new(raw_info_attr) }) } let(:info_hash) do { @@ -47,12 +47,12 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'external groups' do before do - stub_saml_group_config(%w(Interns)) + stub_saml_group_config(%w[Interns]) end context 'are defined' do it 'marks the user as external' do - stub_saml_group_config(%w(Freelancers)) + stub_saml_group_config(%w[Freelancers]) saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_truthy @@ -119,7 +119,7 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'external groups' do context 'are defined' do it 'marks the user as external' do - stub_saml_group_config(%w(Freelancers)) + stub_saml_group_config(%w[Freelancers]) saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_truthy @@ -128,7 +128,7 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'are defined but the user does not belong there' do it 'does not mark the user as external' do - stub_saml_group_config(%w(Interns)) + stub_saml_group_config(%w[Interns]) saml_user.save # rubocop:disable Rails/SaveBang expect(gl_user).to be_valid expect(gl_user.external).to be_falsey @@ -151,7 +151,7 @@ RSpec.describe Gitlab::Auth::Saml::User do context 'and at least one LDAP provider is defined' do before do - stub_ldap_config(providers: %w(ldapmain)) + stub_ldap_config(providers: %w[ldapmain]) end context 'and a corresponding LDAP person' do @@ -160,7 +160,7 @@ RSpec.describe Gitlab::Auth::Saml::User do before do allow(ldap_user).to receive(:uid) { uid } allow(ldap_user).to receive(:username) { uid } - allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) } + allow(ldap_user).to receive(:email) { %w[john@mail.com john2@example.com] } allow(ldap_user).to receive(:dn) { dn } allow(Gitlab::Auth::Ldap::Adapter).to receive(:new).and_return(adapter) allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).with(uid, adapter).and_return(ldap_user) @@ -190,14 +190,14 @@ RSpec.describe Gitlab::Auth::Saml::User do info: info_hash, extra: { raw_info: OneLogin::RubySaml::Attributes.new( - { 'groups' => %w(Developers Freelancers Designers) } + { 'groups' => %w[Developers Freelancers Designers] } ) } } end let(:auth_hash) { OmniAuth::AuthHash.new(auth_hash_base_attributes) } - let(:uid_types) { %w(uid dn email) } + let(:uid_types) { %w[uid dn email] } before do create(:omniauth_user, @@ -410,7 +410,7 @@ RSpec.describe Gitlab::Auth::Saml::User do let(:raw_info_attr) { {} } it 'does not mark user as external' do - stub_saml_group_config(%w(Freelancers)) + stub_saml_group_config(%w[Freelancers]) expect(saml_user.find_user.external).to be_falsy end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index f5b9555916c..020089b3880 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -8,6 +8,8 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate let(:auth_failure) { { actor: nil, project: nil, type: nil, authentication_abilities: nil } } let(:gl_auth) { described_class } + let(:request) { instance_double(ActionDispatch::Request, ip: 'ip') } + describe 'constants' do it 'API_SCOPES contains all scopes for API access' do expect(subject::API_SCOPES).to match_array %i[api read_user read_api create_runner k8s_proxy] @@ -202,7 +204,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate end context 'when IP is already banned' do - subject { gl_auth.find_for_git_client('username-does-not-matter', 'password-does-not-matter', project: nil, ip: 'ip') } + subject { gl_auth.find_for_git_client('username-does-not-matter', 'password-does-not-matter', project: nil, request: request) } before do expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter| @@ -223,7 +225,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate expect(rate_limiter).not_to receive(:reset!) end - gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: build.project, ip: 'ip') + gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: build.project, request: request) end it 'skips rate limiting for failed auth' do @@ -231,7 +233,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate expect(rate_limiter).not_to receive(:register_fail!) end - gl_auth.find_for_git_client('gitlab-ci-token', 'wrong_token', project: build.project, ip: 'ip') + gl_auth.find_for_git_client('gitlab-ci-token', 'wrong_token', project: build.project, request: request) end end @@ -243,7 +245,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate expect(rate_limiter).to receive(:reset!) end - gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip') + gl_auth.find_for_git_client(user.username, user.password, project: nil, request: request) end it 'rate limits a user by unique IPs' do @@ -252,7 +254,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate end expect(Gitlab::Auth::UniqueIpsLimiter).to receive(:limit_user!).twice.and_call_original - gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip') + gl_auth.find_for_git_client(user.username, user.password, project: nil, request: request) end it 'registers failure for failed auth' do @@ -260,13 +262,36 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate expect(rate_limiter).to receive(:register_fail!) end - gl_auth.find_for_git_client(user.username, 'wrong_password', project: nil, ip: 'ip') + gl_auth.find_for_git_client(user.username, 'wrong_password', project: nil, request: request) + end + + context 'when failure goes over threshold' do + let(:request) { instance_double(ActionDispatch::Request, fullpath: '/some/project.git/info/refs', request_method: 'GET', ip: 'ip') } + + before do + expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter| + expect(rate_limiter).to receive(:register_fail!).and_return(true) + end + end + + it 'logs a message' do + expect(Gitlab::AuthLogger).to receive(:error).with( + message: include('IP has been temporarily banned from Git auth'), + env: :blocklist, + remote_ip: request.ip, + request_method: request.request_method, + path: request.fullpath, + login: user.username + ) + + gl_auth.find_for_git_client(user.username, 'wrong_password', project: nil, request: request) + end end end end context 'build token' do - subject { gl_auth.find_for_git_client(username, build.token, project: project, ip: 'ip') } + subject { gl_auth.find_for_git_client(username, build.token, project: project, request: request) } let(:username) { 'gitlab-ci-token' } @@ -344,20 +369,20 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate project.create_drone_ci_integration(active: true) project.drone_ci_integration.update!(token: 'token', drone_url: generate(:url)) - expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: 'ip')).to have_attributes(actor: nil, project: project, type: :ci, authentication_abilities: described_class.build_authentication_abilities) + expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, request: request)).to have_attributes(actor: nil, project: project, type: :ci, authentication_abilities: described_class.build_authentication_abilities) end it 'recognizes master passwords' do user = create(:user) - expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities) + expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, request: request)).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities) end include_examples 'user login operation with unique ip limit' do let(:user) { create(:user) } def operation - expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities) + expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, request: request)).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities) end end @@ -366,14 +391,14 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate user = create(:user) token = Gitlab::LfsToken.new(user).token - expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :lfs_token, authentication_abilities: described_class.read_write_project_authentication_abilities) + expect(gl_auth.find_for_git_client(user.username, token, project: nil, request: request)).to have_attributes(actor: user, project: nil, type: :lfs_token, authentication_abilities: described_class.read_write_project_authentication_abilities) end it 'recognizes deploy key lfs tokens' do key = create(:deploy_key) token = Gitlab::LfsToken.new(key).token - expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to have_attributes(actor: key, project: nil, type: :lfs_deploy_token, authentication_abilities: described_class.read_only_authentication_abilities) + expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, request: request)).to have_attributes(actor: key, project: nil, type: :lfs_deploy_token, authentication_abilities: described_class.read_only_authentication_abilities) end it 'does not try password auth before oauth' do @@ -382,7 +407,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate expect(gl_auth).not_to receive(:find_with_user_password) - gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip') + gl_auth.find_for_git_client(user.username, token, project: nil, request: request) end it 'grants deploy key write permissions' do @@ -390,14 +415,14 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate create(:deploy_keys_project, :write_access, deploy_key: key, project: project) token = Gitlab::LfsToken.new(key).token - expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to have_attributes(actor: key, project: nil, type: :lfs_deploy_token, authentication_abilities: described_class.read_write_authentication_abilities) + expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, request: request)).to have_attributes(actor: key, project: nil, type: :lfs_deploy_token, authentication_abilities: described_class.read_write_authentication_abilities) end it 'does not grant deploy key write permissions' do key = create(:deploy_key) token = Gitlab::LfsToken.new(key).token - expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to have_attributes(actor: key, project: nil, type: :lfs_deploy_token, authentication_abilities: described_class.read_only_authentication_abilities) + expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, request: request)).to have_attributes(actor: key, project: nil, type: :lfs_deploy_token, authentication_abilities: described_class.read_only_authentication_abilities) end end @@ -409,7 +434,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate it 'fails' do access_token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api') - expect(gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, request: request)) .to have_attributes(auth_failure) end end @@ -436,7 +461,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate it 'authenticates with correct abilities' do access_token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: scopes) - expect(gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, request: request)) .to have_attributes(actor: user, project: nil, type: :oauth, authentication_abilities: abilities) end end @@ -447,7 +472,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate expect(gl_auth).not_to receive(:find_with_user_password) - gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, ip: 'ip') + gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, request: request) end context 'blocked user' do @@ -513,7 +538,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api']) - expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, request: request)) .to have_attributes(auth_failure) end @@ -536,7 +561,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate end it 'fails if user is blocked' do - expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, request: request)) .to have_attributes(auth_failure) end end @@ -544,19 +569,19 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate context 'when using a resource access token' do shared_examples 'with a valid access token' do it 'successfully authenticates the project bot' do - expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: project, request: request)) .to have_attributes(actor: project_bot_user, project: nil, type: :personal_access_token, authentication_abilities: described_class.full_authentication_abilities) end it 'successfully authenticates the project bot with a nil project' do - expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: nil, request: request)) .to have_attributes(actor: project_bot_user, project: nil, type: :personal_access_token, authentication_abilities: described_class.full_authentication_abilities) end end shared_examples 'with an invalid access token' do it 'fails for a non-member' do - expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: project, request: request)) .to have_attributes(auth_failure) end @@ -566,7 +591,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate end it 'fails for a blocked project bot' do - expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: project, request: request)) .to have_attributes(auth_failure) end end @@ -637,7 +662,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate it 'updates last_used_at column if token is valid' do personal_access_token = create(:personal_access_token, scopes: ['write_repository']) - expect { gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip') }.to change { personal_access_token.reload.last_used_at } + expect { gl_auth.find_for_git_client('', personal_access_token.token, project: nil, request: request) }.to change { personal_access_token.reload.last_used_at } end end @@ -649,7 +674,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate username: 'normal_user' ) - expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, request: request)) .to have_attributes(auth_failure) end @@ -665,14 +690,14 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate it 'fails if grace period expired' do stub_application_setting(two_factor_grace_period: 0) - expect { gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip') } + expect { gl_auth.find_for_git_client(user.username, user.password, project: nil, request: request) } .to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError) end it 'goes through if grace period is not expired yet' do stub_application_setting(two_factor_grace_period: 72) - expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, request: request)) .to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities) end end @@ -683,7 +708,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate end it 'fails' do - expect { gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip') } + expect { gl_auth.find_for_git_client(user.username, user.password, project: nil, request: request) } .to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError) end end @@ -694,7 +719,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate username: 'normal_user' ) - expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, request: request)) .to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities) end @@ -704,7 +729,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate username: 'oauth2' ) - expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, request: request)) .to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities) end end @@ -712,34 +737,34 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate it 'returns double nil for invalid credentials' do login = 'foo' - expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: 'ip')).to have_attributes(auth_failure) + expect(gl_auth.find_for_git_client(login, 'bar', project: nil, request: request)).to have_attributes(auth_failure) end it 'throws an error suggesting user create a PAT when internal auth is disabled' do allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled_for_git?) { false } - expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError) + expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, request: request) }.to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError) end context 'while using deploy tokens' do shared_examples 'registry token scope' do it 'fails when login is not valid' do - expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, request: request)) .to have_attributes(auth_failure) end it 'fails when token is not valid' do - expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(login, '123123', project: project, request: request)) .to have_attributes(auth_failure) end it 'fails if token is nil' do - expect(gl_auth.find_for_git_client(login, nil, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client(login, nil, project: nil, request: request)) .to have_attributes(auth_failure) end it 'fails if token is not related to project' do - expect(gl_auth.find_for_git_client(login, 'abcdef', project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client(login, 'abcdef', project: nil, request: request)) .to have_attributes(auth_failure) end @@ -747,7 +772,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate deploy_token.revoke! expect(deploy_token.revoked?).to be_truthy - expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: nil, request: request)) .to have_attributes(auth_failure) end end @@ -759,7 +784,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate end it 'fails when login and token are valid' do - expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, request: request)) .to have_attributes(auth_failure) end end @@ -768,7 +793,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate let(:project) { create(:project, :repository_disabled) } it 'fails when login and token are valid' do - expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, request: request)) .to have_attributes(auth_failure) end end @@ -782,14 +807,14 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate it 'succeeds for the token' do auth_success = { actor: deploy_token, project: project, type: :deploy_token, authentication_abilities: [:download_code] } - expect(gl_auth.find_for_git_client(username, deploy_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(username, deploy_token.token, project: project, request: request)) .to have_attributes(auth_success) end it 'succeeds for the user' do auth_success = { actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities } - expect(gl_auth.find_for_git_client(username, user.password, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(username, user.password, project: project, request: request)) .to have_attributes(auth_success) end end @@ -801,12 +826,12 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate let(:auth_success) { { actor: read_repository, project: project, type: :deploy_token, authentication_abilities: [:download_code] } } it 'succeeds for the right token' do - expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, request: request)) .to have_attributes(auth_success) end it 'fails for the wrong token' do - expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, request: request)) .not_to have_attributes(auth_success) end end @@ -819,12 +844,12 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate let(:auth_success) { { actor: read_repository, project: other_project, type: :deploy_token, authentication_abilities: [:download_code] } } it 'succeeds for the right token' do - expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: other_project, ip: 'ip')) + expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: other_project, request: request)) .to have_attributes(auth_success) end it 'fails for the wrong token' do - expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: other_project, ip: 'ip')) + expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: other_project, request: request)) .not_to have_attributes(auth_success) end end @@ -837,7 +862,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate it 'succeeds when login and token are valid' do auth_success = { actor: deploy_token, project: project, type: :deploy_token, authentication_abilities: [:download_code] } - expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, request: request)) .to have_attributes(auth_success) end @@ -845,34 +870,34 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate deploy_token = create(:deploy_token, username: 'deployer', read_registry: false, projects: [project]) auth_success = { actor: deploy_token, project: project, type: :deploy_token, authentication_abilities: [:download_code] } - expect(gl_auth.find_for_git_client('deployer', deploy_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client('deployer', deploy_token.token, project: project, request: request)) .to have_attributes(auth_success) end it 'does not attempt to rate limit unique IPs for a deploy token' do expect(Gitlab::Auth::UniqueIpsLimiter).not_to receive(:limit_user!) - gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip') + gl_auth.find_for_git_client(login, deploy_token.token, project: project, request: request) end it 'fails when login is not valid' do - expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, request: request)) .to have_attributes(auth_failure) end it 'fails when token is not valid' do - expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(login, '123123', project: project, request: request)) .to have_attributes(auth_failure) end it 'fails if token is nil' do - expect(gl_auth.find_for_git_client(login, nil, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(login, nil, project: project, request: request)) .to have_attributes(auth_failure) end it 'fails if token is not related to project' do another_deploy_token = create(:deploy_token) - expect(gl_auth.find_for_git_client(another_deploy_token.username, another_deploy_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(another_deploy_token.username, another_deploy_token.token, project: project, request: request)) .to have_attributes(auth_failure) end @@ -880,7 +905,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate deploy_token.revoke! expect(deploy_token.revoked?).to be_truthy - expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: project, request: request)) .to have_attributes(auth_failure) end end @@ -890,7 +915,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate let(:deploy_token) { create(:deploy_token, :group, read_repository: true, groups: [project_with_group.group]) } let(:login) { deploy_token.username } - subject { gl_auth.find_for_git_client(login, deploy_token.token, project: project_with_group, ip: 'ip') } + subject { gl_auth.find_for_git_client(login, deploy_token.token, project: project_with_group, request: request) } it 'succeeds when login and a group deploy token are valid' do auth_success = { actor: deploy_token, project: project_with_group, type: :deploy_token, authentication_abilities: [:download_code, :read_container_image] } @@ -901,7 +926,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate it 'fails if token is not related to group' do another_deploy_token = create(:deploy_token, :group, read_repository: true) - expect(gl_auth.find_for_git_client(another_deploy_token.username, another_deploy_token.token, project: project_with_group, ip: 'ip')) + expect(gl_auth.find_for_git_client(another_deploy_token.username, another_deploy_token.token, project: project_with_group, request: request)) .to have_attributes(auth_failure) end end @@ -918,7 +943,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate it 'succeeds when login and a project token are valid' do auth_success = { actor: deploy_token, project: project, type: :deploy_token, authentication_abilities: [:read_container_image] } - expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, request: request)) .to have_attributes(auth_success) end @@ -940,7 +965,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate it 'succeeds when login and a project token are valid' do auth_success = { actor: deploy_token, project: project, type: :deploy_token, authentication_abilities: [:create_container_image] } - expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, request: request)) .to have_attributes(auth_success) end @@ -953,7 +978,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate end describe '#build_access_token_check' do - subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: build.project, ip: '1.2.3.4') } + subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: build.project, request: request) } let_it_be(:user) { create(:user) } @@ -1143,7 +1168,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate private def expect_results_with_abilities(personal_access_token, abilities, success = true) - expect(gl_auth.find_for_git_client('', personal_access_token&.token, project: nil, ip: 'ip')) + expect(gl_auth.find_for_git_client('', personal_access_token&.token, project: nil, request: request)) .to have_attributes(actor: personal_access_token&.user, project: nil, type: personal_access_token.nil? ? nil : :personal_access_token, authentication_abilities: abilities) end end diff --git a/spec/lib/gitlab/background_migration/backfill_packages_tags_project_id_spec.rb b/spec/lib/gitlab/background_migration/backfill_packages_tags_project_id_spec.rb new file mode 100644 index 00000000000..423d9fe76ac --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_packages_tags_project_id_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::BackfillPackagesTagsProjectId, + feature_category: :package_registry, + schema: 20231030051837 do # schema before we introduced the invalid not-null constraint + let!(:tags_without_project_id) do + (0...13).map do |i| + namespace = table(:namespaces).create!(name: 'my namespace', path: 'my-namespace') + project = table(:projects).create!(name: 'my project', path: 'my-project', namespace_id: namespace.id, + project_namespace_id: namespace.id) + package = table(:packages_packages).create!(project_id: project.id, created_at: Time.current, + updated_at: Time.current, name: "Package #{i}", package_type: 1, status: 1) + table(:packages_tags).create!(package_id: package.id, name: "Tag #{i}", created_at: Time.current, + updated_at: Time.current, project_id: nil) + end + end + + let!(:starting_id) { table(:packages_tags).pluck(:id).min } + let!(:end_id) { table(:packages_tags).pluck(:id).max } + + let!(:migration) do + described_class.new( + start_id: starting_id, + end_id: end_id, + batch_table: :packages_tags, + batch_column: :id, + sub_batch_size: 10, + pause_ms: 2, + connection: ::ApplicationRecord.connection + ) + end + + it 'backfills the missing project_id for the batch' do + expect do + migration.perform + end.to change { table(:packages_tags).where(project_id: nil).count } + .from(13) + .to(0) + end +end diff --git a/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb b/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb index 781bf93dd85..da24e9b7978 100644 --- a/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb +++ b/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb @@ -2,22 +2,22 @@ require 'spec_helper' -RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do +RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob, feature_category: :database do let(:connection) { Gitlab::Database.database_base_models[:main].connection } describe '.generic_instance' do it 'defines generic instance with only some of the attributes set' do generic_instance = described_class.generic_instance( batch_table: 'projects', batch_column: 'id', - job_arguments: %w(x y), connection: connection + job_arguments: %w[x y], connection: connection ) expect(generic_instance.send(:batch_table)).to eq('projects') expect(generic_instance.send(:batch_column)).to eq('id') - expect(generic_instance.instance_variable_get(:@job_arguments)).to eq(%w(x y)) + expect(generic_instance.instance_variable_get(:@job_arguments)).to eq(%w[x y]) expect(generic_instance.send(:connection)).to eq(connection) - %i(start_id end_id sub_batch_size pause_ms).each do |attr| + %i[start_id end_id sub_batch_size pause_ms].each do |attr| expect(generic_instance.send(attr)).to eq(0) end end @@ -31,13 +31,16 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do end subject(:job_instance) do - job_class.new(start_id: 1, end_id: 10, - batch_table: '_test_table', - batch_column: 'id', - sub_batch_size: 2, - pause_ms: 1000, - job_arguments: %w(a b), - connection: connection) + job_class.new( + start_id: 1, + end_id: 10, + batch_table: '_test_table', + batch_column: 'id', + sub_batch_size: 2, + pause_ms: 1000, + job_arguments: %w[a b], + connection: connection + ) end it 'defines methods' do @@ -61,13 +64,16 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do subject(:perform_job) { job_instance.perform } let(:job_instance) do - job_class.new(start_id: 1, end_id: 10, - batch_table: '_test_table', - batch_column: 'id', - sub_batch_size: 2, - pause_ms: 1000, - job_arguments: %w(a b), - connection: connection) + job_class.new( + start_id: 1, + end_id: 10, + batch_table: '_test_table', + batch_column: 'id', + sub_batch_size: 2, + pause_ms: 1000, + job_arguments: %w[a b], + connection: connection + ) end let(:job_class) do @@ -124,13 +130,16 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do describe '.scope_to' do subject(:job_instance) do - job_class.new(start_id: 1, end_id: 10, - batch_table: '_test_table', - batch_column: 'id', - sub_batch_size: 2, - pause_ms: 1000, - job_arguments: %w(a b), - connection: connection) + job_class.new( + start_id: 1, + end_id: 10, + batch_table: '_test_table', + batch_column: 'id', + sub_batch_size: 2, + pause_ms: 1000, + job_arguments: %w[a b], + connection: connection + ) end context 'when additional scoping is defined' do @@ -203,12 +212,15 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do let(:job_class) { Class.new(described_class) } let(:job_instance) do - job_class.new(start_id: 1, end_id: 10, - batch_table: '_test_table', - batch_column: 'id', - sub_batch_size: 2, - pause_ms: 1000, - connection: connection) + job_class.new( + start_id: 1, + end_id: 10, + batch_table: '_test_table', + batch_column: 'id', + sub_batch_size: 2, + pause_ms: 1000, + connection: connection + ) end subject(:perform_job) { job_instance.perform } @@ -313,9 +325,16 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do end let(:job_instance) do - job_class.new(start_id: 1, end_id: 10, batch_table: '_test_table', batch_column: 'id', - sub_batch_size: 2, pause_ms: 1000, connection: connection, - sub_batch_exception: StandardError) + job_class.new( + start_id: 1, + end_id: 10, + batch_table: '_test_table', + batch_column: 'id', + sub_batch_size: 2, + pause_ms: 1000, + connection: connection, + sub_batch_exception: StandardError + ) end it 'raises the expected error type' do @@ -336,13 +355,15 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do context 'when the subclass uses distinct each batch' do let(:job_instance) do - job_class.new(start_id: 1, - end_id: 100, - batch_table: '_test_table', - batch_column: 'from_column', - sub_batch_size: 2, - pause_ms: 10, - connection: connection) + job_class.new( + start_id: 1, + end_id: 100, + batch_table: '_test_table', + batch_column: 'from_column', + sub_batch_size: 2, + pause_ms: 10, + connection: connection + ) end let(:job_class) do diff --git a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb index 9c33100a0b3..a827116a900 100644 --- a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb +++ b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb @@ -16,16 +16,18 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo ActiveRecord::Migration.new.extend(Gitlab::Database::MigrationHelpers) end - let(:job_arguments) { %w(name name_convert_to_text) } + let(:job_arguments) { %w[name name_convert_to_text] } let(:copy_job) do - described_class.new(start_id: 12, - end_id: 20, - batch_table: table_name, - batch_column: 'id', - sub_batch_size: sub_batch_size, - pause_ms: pause_ms, - job_arguments: job_arguments, - connection: connection) + described_class.new( + start_id: 12, + end_id: 20, + batch_table: table_name, + batch_column: 'id', + sub_batch_size: sub_batch_size, + pause_ms: pause_ms, + job_arguments: job_arguments, + connection: connection + ) end before do @@ -82,7 +84,7 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo end context 'columns with NULLs' do - let(:job_arguments) { %w(name name_convert_to_text) } + let(:job_arguments) { %w[name name_convert_to_text] } it 'copies all in range' do expect { copy_job.perform } diff --git a/spec/lib/gitlab/background_migration/delete_invalid_protected_branch_merge_access_levels_spec.rb b/spec/lib/gitlab/background_migration/delete_invalid_protected_branch_merge_access_levels_spec.rb new file mode 100644 index 00000000000..1e5b9d30436 --- /dev/null +++ b/spec/lib/gitlab/background_migration/delete_invalid_protected_branch_merge_access_levels_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::DeleteInvalidProtectedBranchMergeAccessLevels, + feature_category: :source_code_management do + let(:projects_table) { table(:projects) } + let(:protected_branches_table) { table(:protected_branches) } + let(:namespaces_table) { table(:namespaces) } + let(:protected_branch_merge_access_levels_table) { table(:protected_branch_merge_access_levels) } + let(:project_group_links_table) { table(:project_group_links) } + let(:users_table) { table(:users) } + + let(:user1) { users_table.create!(name: 'user1', email: 'user1@example.com', projects_limit: 5) } + + let(:project_group) { namespaces_table.create!(name: 'group-1', path: 'group-1', type: 'Group') } + let(:project_namespace) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-2', type: 'Project') } + let!(:project_1) do + projects_table + .create!( + name: 'project1', + path: 'path1', + namespace_id: project_group.id, + project_namespace_id: project_namespace.id, + visibility_level: 0 + ) + end + + subject(:perform_migration) do + described_class.new(start_id: protected_branch_merge_access_levels_table.minimum(:id), + end_id: protected_branch_merge_access_levels_table.maximum(:id), + batch_table: :protected_branch_merge_access_levels, + batch_column: :id, + sub_batch_size: 1, + pause_ms: 0, + connection: ApplicationRecord.connection) + .perform + end + + context 'when there are merge access levels' do + let(:protected_branch1) { protected_branches_table.create!(project_id: project_1.id, name: 'name') } + let!(:merge_access_level_for_user) do + protected_branch_merge_access_levels_table.create!( + protected_branch_id: protected_branch1.id, + user_id: user1.id + ) + end + + let(:invited_group) { namespaces_table.create!(name: 'group-2', path: 'group-2', type: 'Group') } + let!(:invited_group_link) do + project_group_links_table.create!(project_id: project_1.id, group_id: invited_group.id) + end + + let!(:merge_access_level_with_linked_group) do + protected_branch_merge_access_levels_table.create!( + protected_branch_id: protected_branch1.id, + group_id: invited_group.id + ) + end + + let!(:merge_access_level_with_unlinked_group) do + protected_branch_merge_access_levels_table.create!( + protected_branch_id: protected_branch1.id, + group_id: project_group.id + ) + end + + it 'deletes merge access levels with groups that do not have project_group_links to the project' do + expect { subject }.to change { protected_branch_merge_access_levels_table.count }.from(3).to(2) + expect(protected_branch_merge_access_levels_table.all).to contain_exactly( + merge_access_level_with_linked_group, + merge_access_level_for_user + ) + end + end +end diff --git a/spec/lib/gitlab/background_migration/delete_invalid_protected_branch_push_access_levels_spec.rb b/spec/lib/gitlab/background_migration/delete_invalid_protected_branch_push_access_levels_spec.rb new file mode 100644 index 00000000000..62201831dd1 --- /dev/null +++ b/spec/lib/gitlab/background_migration/delete_invalid_protected_branch_push_access_levels_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::DeleteInvalidProtectedBranchPushAccessLevels, + feature_category: :source_code_management do + let(:projects_table) { table(:projects) } + let(:protected_branches_table) { table(:protected_branches) } + let(:namespaces_table) { table(:namespaces) } + let(:protected_branch_push_access_levels_table) { table(:protected_branch_push_access_levels) } + let(:project_group_links_table) { table(:project_group_links) } + let(:users_table) { table(:users) } + + let(:user1) { users_table.create!(name: 'user1', email: 'user1@example.com', projects_limit: 5) } + + let(:project_group) { namespaces_table.create!(name: 'group-1', path: 'group-1', type: 'Group') } + let(:project_namespace) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-2', type: 'Project') } + let!(:project_1) do + projects_table + .create!( + name: 'project1', + path: 'path1', + namespace_id: project_group.id, + project_namespace_id: project_namespace.id, + visibility_level: 0 + ) + end + + subject(:perform_migration) do + described_class.new(start_id: protected_branch_push_access_levels_table.minimum(:id), + end_id: protected_branch_push_access_levels_table.maximum(:id), + batch_table: :protected_branch_push_access_levels, + batch_column: :id, + sub_batch_size: 1, + pause_ms: 0, + connection: ApplicationRecord.connection) + .perform + end + + context 'when there are push access levels' do + let(:protected_branch1) { protected_branches_table.create!(project_id: project_1.id, name: 'name') } + let!(:push_access_level_for_user) do + protected_branch_push_access_levels_table.create!( + protected_branch_id: protected_branch1.id, + user_id: user1.id + ) + end + + let(:invited_group) { namespaces_table.create!(name: 'group-2', path: 'group-2', type: 'Group') } + let!(:invited_group_link) do + project_group_links_table.create!(project_id: project_1.id, group_id: invited_group.id) + end + + let!(:push_access_level_with_linked_group) do + protected_branch_push_access_levels_table.create!( + protected_branch_id: protected_branch1.id, + group_id: invited_group.id + ) + end + + let!(:push_access_level_with_unlinked_group) do + protected_branch_push_access_levels_table.create!( + protected_branch_id: protected_branch1.id, + group_id: project_group.id + ) + end + + it 'deletes push access levels with groups that do not have project_group_links to the project' do + expect { subject }.to change { protected_branch_push_access_levels_table.count }.from(3).to(2) + expect(protected_branch_push_access_levels_table.all).to contain_exactly( + push_access_level_with_linked_group, + push_access_level_for_user + ) + end + end +end diff --git a/spec/lib/gitlab/background_migration/delete_invalid_protected_tag_create_access_levels_spec.rb b/spec/lib/gitlab/background_migration/delete_invalid_protected_tag_create_access_levels_spec.rb new file mode 100644 index 00000000000..fd6cee9e4db --- /dev/null +++ b/spec/lib/gitlab/background_migration/delete_invalid_protected_tag_create_access_levels_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::DeleteInvalidProtectedTagCreateAccessLevels, + feature_category: :source_code_management do + let(:projects_table) { table(:projects) } + let(:protected_tags_table) { table(:protected_tags) } + let(:namespaces_table) { table(:namespaces) } + let(:protected_tag_create_access_levels_table) { table(:protected_tag_create_access_levels) } + let(:project_group_links_table) { table(:project_group_links) } + let(:users_table) { table(:users) } + + let(:user1) { users_table.create!(name: 'user1', email: 'user1@example.com', projects_limit: 5) } + + let(:project_group) { namespaces_table.create!(name: 'group-1', path: 'group-1', type: 'Group') } + let(:project_namespace) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-2', type: 'Project') } + let!(:project_1) do + projects_table + .create!( + name: 'project1', + path: 'path1', + namespace_id: project_group.id, + project_namespace_id: project_namespace.id, + visibility_level: 0 + ) + end + + subject(:perform_migration) do + described_class.new(start_id: protected_tag_create_access_levels_table.minimum(:id), + end_id: protected_tag_create_access_levels_table.maximum(:id), + batch_table: :protected_tag_create_access_levels, + batch_column: :id, + sub_batch_size: 1, + pause_ms: 0, + connection: ApplicationRecord.connection) + .perform + end + + context 'when there are push access levels' do + let(:protected_tag) { protected_tags_table.create!(project_id: project_1.id, name: 'name') } + let!(:push_access_level_for_user) do + protected_tag_create_access_levels_table.create!( + protected_tag_id: protected_tag.id, + user_id: user1.id + ) + end + + let(:invited_group) { namespaces_table.create!(name: 'group-2', path: 'group-2', type: 'Group') } + let!(:invited_group_link) do + project_group_links_table.create!(project_id: project_1.id, group_id: invited_group.id) + end + + let!(:push_access_level_with_linked_group) do + protected_tag_create_access_levels_table.create!( + protected_tag_id: protected_tag.id, + group_id: invited_group.id + ) + end + + let!(:push_access_level_with_unlinked_group) do + protected_tag_create_access_levels_table.create!( + protected_tag_id: protected_tag.id, + group_id: project_group.id + ) + end + + it 'deletes push access levels with groups that do not have project_group_links to the project' do + expect { subject }.to change { protected_tag_create_access_levels_table.count }.from(3).to(2) + expect(protected_tag_create_access_levels_table.all).to contain_exactly( + push_access_level_with_linked_group, + push_access_level_for_user + ) + end + end +end diff --git a/spec/lib/gitlab/background_migration/delete_orphaned_operational_vulnerabilities_spec.rb b/spec/lib/gitlab/background_migration/delete_orphaned_operational_vulnerabilities_spec.rb index c03962c8d21..4a1985eeccd 100644 --- a/spec/lib/gitlab/background_migration/delete_orphaned_operational_vulnerabilities_spec.rb +++ b/spec/lib/gitlab/background_migration/delete_orphaned_operational_vulnerabilities_spec.rb @@ -87,13 +87,15 @@ RSpec.describe Gitlab::BackgroundMigration::DeleteOrphanedOperationalVulnerabili end subject(:background_migration) do - described_class.new(start_id: vulnerabilities.minimum(:id), - end_id: vulnerabilities.maximum(:id), - batch_table: :vulnerabilities, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) + described_class.new( + start_id: vulnerabilities.minimum(:id), + end_id: vulnerabilities.maximum(:id), + batch_table: :vulnerabilities, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ) end it 'drops Cluster Image Scanning and Custom Vulnerabilities without any Findings' do diff --git a/spec/lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules_spec.rb b/spec/lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules_spec.rb index c5b46d3f57c..1ac4d184912 100644 --- a/spec/lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules_spec.rb +++ b/spec/lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules_spec.rb @@ -22,9 +22,12 @@ RSpec.describe Gitlab::BackgroundMigration::DeleteOrphansApprovalMergeRequestRul let(:namespace_2) { namespaces.create!(name: 'name_2', path: 'path_2') } let(:security_project) do - projects - .create!(name: "security_project", path: "security_project", namespace_id: namespace_2.id, - project_namespace_id: namespace_2.id) + projects.create!( + name: "security_project", + path: "security_project", + namespace_id: namespace_2.id, + project_namespace_id: namespace_2.id + ) end let!(:security_orchestration_policy_configuration) do diff --git a/spec/lib/gitlab/background_migration/delete_orphans_approval_project_rules_spec.rb b/spec/lib/gitlab/background_migration/delete_orphans_approval_project_rules_spec.rb index 16253255764..23026f76001 100644 --- a/spec/lib/gitlab/background_migration/delete_orphans_approval_project_rules_spec.rb +++ b/spec/lib/gitlab/background_migration/delete_orphans_approval_project_rules_spec.rb @@ -22,9 +22,12 @@ RSpec.describe Gitlab::BackgroundMigration::DeleteOrphansApprovalProjectRules do let(:namespace_2) { namespaces.create!(name: 'name_2', path: 'path_2') } let(:security_project) do - projects - .create!(name: "security_project", path: "security_project", namespace_id: namespace_2.id, - project_namespace_id: namespace_2.id) + projects.create!( + name: "security_project", + path: "security_project", + namespace_id: namespace_2.id, + project_namespace_id: namespace_2.id + ) end let!(:security_orchestration_policy_configuration) do diff --git a/spec/lib/gitlab/background_migration/destroy_invalid_group_members_spec.rb b/spec/lib/gitlab/background_migration/destroy_invalid_group_members_spec.rb index 76a9ea82c76..4e136808a36 100644 --- a/spec/lib/gitlab/background_migration/destroy_invalid_group_members_spec.rb +++ b/spec/lib/gitlab/background_migration/destroy_invalid_group_members_spec.rb @@ -76,13 +76,29 @@ RSpec.describe Gitlab::BackgroundMigration::DestroyInvalidGroupMembers, :migrati end def create_invalid_group_member(id:, user_id:) - members_table.create!(id: id, user_id: user_id, source_id: non_existing_record_id, access_level: Gitlab::Access::MAINTAINER, - type: "GroupMember", source_type: "Namespace", notification_level: 3, member_namespace_id: nil) + members_table.create!( + id: id, + user_id: user_id, + source_id: non_existing_record_id, + access_level: Gitlab::Access::MAINTAINER, + type: "GroupMember", + source_type: "Namespace", + notification_level: 3, + member_namespace_id: nil + ) end def create_valid_group_member(id:, user_id:, group_id:) - members_table.create!(id: id, user_id: user_id, source_id: group_id, access_level: Gitlab::Access::MAINTAINER, - type: "GroupMember", source_type: "Namespace", member_namespace_id: group_id, notification_level: 3) + members_table.create!( + id: id, + user_id: user_id, + source_id: group_id, + access_level: Gitlab::Access::MAINTAINER, + type: "GroupMember", + source_type: "Namespace", + member_namespace_id: group_id, + notification_level: 3 + ) end # rubocop: enable Layout/LineLength # rubocop: enable RSpec/ScatteredLet diff --git a/spec/lib/gitlab/background_migration/destroy_invalid_members_spec.rb b/spec/lib/gitlab/background_migration/destroy_invalid_members_spec.rb index 5059ad620aa..e5965d4a1d8 100644 --- a/spec/lib/gitlab/background_migration/destroy_invalid_members_spec.rb +++ b/spec/lib/gitlab/background_migration/destroy_invalid_members_spec.rb @@ -33,23 +33,39 @@ RSpec.describe Gitlab::BackgroundMigration::DestroyInvalidMembers, :migration, s let!(:group1) { namespaces_table.create!(name: 'marvellous group 1', path: 'group-path-1', type: 'Group') } let!(:group2) { namespaces_table.create!(name: 'outstanding group 2', path: 'group-path-2', type: 'Group') } let!(:project_namespace1) do - namespaces_table.create!(name: 'fabulous project', path: 'project-path-1', - type: 'ProjectNamespace', parent_id: group1.id) + namespaces_table.create!( + name: 'fabulous project', + path: 'project-path-1', + type: 'ProjectNamespace', + parent_id: group1.id + ) end let!(:project1) do - projects_table.create!(name: 'fabulous project', path: 'project-path-1', - project_namespace_id: project_namespace1.id, namespace_id: group1.id) + projects_table.create!( + name: 'fabulous project', + path: 'project-path-1', + project_namespace_id: project_namespace1.id, + namespace_id: group1.id + ) end let!(:project_namespace2) do - namespaces_table.create!(name: 'splendiferous project', path: 'project-path-2', - type: 'ProjectNamespace', parent_id: group1.id) + namespaces_table.create!( + name: 'splendiferous project', + path: 'project-path-2', + type: 'ProjectNamespace', + parent_id: group1.id + ) end let!(:project2) do - projects_table.create!(name: 'splendiferous project', path: 'project-path-2', - project_namespace_id: project_namespace2.id, namespace_id: group1.id) + projects_table.create!( + name: 'splendiferous project', + path: 'project-path-2', + project_namespace_id: project_namespace2.id, + namespace_id: group1.id + ) end # create valid project member records @@ -115,27 +131,55 @@ RSpec.describe Gitlab::BackgroundMigration::DestroyInvalidMembers, :migration, s end def create_invalid_project_member(id:, user_id:) - members_table.create!(id: id, user_id: user_id, source_id: non_existing_record_id, - access_level: Gitlab::Access::MAINTAINER, type: "ProjectMember", - source_type: "Project", notification_level: 3, member_namespace_id: nil) + members_table.create!( + id: id, + user_id: user_id, + source_id: non_existing_record_id, + access_level: Gitlab::Access::MAINTAINER, + type: "ProjectMember", + source_type: "Project", + notification_level: 3, + member_namespace_id: nil + ) end def create_valid_project_member(id:, user_id:, project:) - members_table.create!(id: id, user_id: user_id, source_id: project.id, - access_level: Gitlab::Access::MAINTAINER, type: "ProjectMember", source_type: "Project", - member_namespace_id: project.project_namespace_id, notification_level: 3) + members_table.create!( + id: id, + user_id: user_id, + source_id: project.id, + access_level: Gitlab::Access::MAINTAINER, + type: "ProjectMember", + source_type: "Project", + member_namespace_id: project.project_namespace_id, + notification_level: 3 + ) end def create_invalid_group_member(id:, user_id:) - members_table.create!(id: id, user_id: user_id, source_id: non_existing_record_id, - access_level: Gitlab::Access::MAINTAINER, type: "GroupMember", - source_type: "Namespace", notification_level: 3, member_namespace_id: nil) + members_table.create!( + id: id, + user_id: user_id, + source_id: non_existing_record_id, + access_level: Gitlab::Access::MAINTAINER, + type: "GroupMember", + source_type: "Namespace", + notification_level: 3, + member_namespace_id: nil + ) end def create_valid_group_member(id:, user_id:, group_id:) - members_table.create!(id: id, user_id: user_id, source_id: group_id, - access_level: Gitlab::Access::MAINTAINER, type: "GroupMember", - source_type: "Namespace", member_namespace_id: group_id, notification_level: 3) + members_table.create!( + id: id, + user_id: user_id, + source_id: group_id, + access_level: Gitlab::Access::MAINTAINER, + type: "GroupMember", + source_type: "Namespace", + member_namespace_id: group_id, + notification_level: 3 + ) end end # rubocop: enable RSpec/MultipleMemoizedHelpers diff --git a/spec/lib/gitlab/background_migration/destroy_invalid_project_members_spec.rb b/spec/lib/gitlab/background_migration/destroy_invalid_project_members_spec.rb index 029a6adf831..090c31049b4 100644 --- a/spec/lib/gitlab/background_migration/destroy_invalid_project_members_spec.rb +++ b/spec/lib/gitlab/background_migration/destroy_invalid_project_members_spec.rb @@ -3,7 +3,6 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::DestroyInvalidProjectMembers, :migration, schema: 20220901035725 do - # rubocop: disable Layout/LineLength # rubocop: disable RSpec/ScatteredLet let!(:migration_attrs) do { @@ -36,23 +35,33 @@ RSpec.describe Gitlab::BackgroundMigration::DestroyInvalidProjectMembers, :migra let!(:group1) { namespaces_table.create!(name: 'marvellous group 1', path: 'group-path-1', type: 'Group') } let!(:project_namespace1) do - namespaces_table.create!(name: 'fabulous project', path: 'project-path-1', type: 'ProjectNamespace', - parent_id: group1.id) + namespaces_table.create!( + name: 'fabulous project', path: 'project-path-1', type: 'ProjectNamespace', parent_id: group1.id + ) end let!(:project1) do - projects_table.create!(name: 'fabulous project', path: 'project-path-1', project_namespace_id: project_namespace1.id, - namespace_id: group1.id) + projects_table.create!( + name: 'fabulous project', + path: 'project-path-1', + project_namespace_id: project_namespace1.id, + namespace_id: group1.id + ) end let!(:project_namespace2) do - namespaces_table.create!(name: 'splendiferous project', path: 'project-path-2', type: 'ProjectNamespace', - parent_id: group1.id) + namespaces_table.create!( + name: 'splendiferous project', path: 'project-path-2', type: 'ProjectNamespace', parent_id: group1.id + ) end let!(:project2) do - projects_table.create!(name: 'splendiferous project', path: 'project-path-2', project_namespace_id: project_namespace2.id, - namespace_id: group1.id) + projects_table.create!( + name: 'splendiferous project', + path: 'project-path-2', + project_namespace_id: project_namespace2.id, + namespace_id: group1.id + ) end # create project member records, a mix of both valid and invalid @@ -72,7 +81,8 @@ RSpec.describe Gitlab::BackgroundMigration::DestroyInvalidProjectMembers, :migra end expect(queries.count).to eq(4) - expect(members_table.where(type: 'ProjectMember')).to match_array([project_member2, project_member3, project_member5]) + expect(members_table.where(type: 'ProjectMember')) + .to match_array([project_member2, project_member3, project_member5]) end it 'tracks timings of queries' do @@ -82,21 +92,33 @@ RSpec.describe Gitlab::BackgroundMigration::DestroyInvalidProjectMembers, :migra end it 'logs IDs of deleted records' do - expect(Gitlab::AppLogger).to receive(:info).with({ message: 'Removing invalid project member records', - deleted_count: 3, ids: [project_member1, project_member4, project_member6].map(&:id) }) + expect(Gitlab::AppLogger).to receive(:info).with({ + message: 'Removing invalid project member records', + deleted_count: 3, + ids: [project_member1, project_member4, project_member6].map(&:id) + }) perform_migration end def create_invalid_project_member(id:, user_id:) - members_table.create!(id: id, user_id: user_id, source_id: non_existing_record_id, access_level: Gitlab::Access::MAINTAINER, - type: "ProjectMember", source_type: "Project", notification_level: 3, member_namespace_id: nil) + members_table.create!( + id: id, user_id: user_id, source_id: non_existing_record_id, access_level: Gitlab::Access::MAINTAINER, + type: "ProjectMember", source_type: "Project", notification_level: 3, member_namespace_id: nil + ) end def create_valid_project_member(id:, user_id:, project:) - members_table.create!(id: id, user_id: user_id, source_id: project.id, access_level: Gitlab::Access::MAINTAINER, - type: "ProjectMember", source_type: "Project", member_namespace_id: project.project_namespace_id, notification_level: 3) + members_table.create!( + id: id, + user_id: user_id, + source_id: project.id, + access_level: Gitlab::Access::MAINTAINER, + type: "ProjectMember", + source_type: "Project", + member_namespace_id: project.project_namespace_id, + notification_level: 3 + ) end - # rubocop: enable Layout/LineLength # rubocop: enable RSpec/ScatteredLet end diff --git a/spec/lib/gitlab/background_migration/disable_legacy_open_source_licence_for_recent_public_projects_spec.rb b/spec/lib/gitlab/background_migration/disable_legacy_open_source_licence_for_recent_public_projects_spec.rb index 7edba8cf524..740a90e0494 100644 --- a/spec/lib/gitlab/background_migration/disable_legacy_open_source_licence_for_recent_public_projects_spec.rb +++ b/spec/lib/gitlab/background_migration/disable_legacy_open_source_licence_for_recent_public_projects_spec.rb @@ -73,14 +73,15 @@ RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenceForRec let(:project_settings_table) { table(:project_settings) } subject(:perform_migration) do - described_class.new(start_id: projects_table.minimum(:id), - end_id: projects_table.maximum(:id), - batch_table: :projects, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: projects_table.minimum(:id), + end_id: projects_table.maximum(:id), + batch_table: :projects, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end before do @@ -94,7 +95,7 @@ RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenceForRec end it 'sets `legacy_open_source_license_available` attribute to false for public projects created after threshold time', - :aggregate_failures do + :aggregate_failures do record = ActiveRecord::QueryRecorder.new do expect { perform_migration } .to not_change { migrated_attribute(project_1.id) }.from(true) diff --git a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_inactive_public_projects_spec.rb b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_inactive_public_projects_spec.rb index f5a2dc91185..953eb09032f 100644 --- a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_inactive_public_projects_spec.rb +++ b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_inactive_public_projects_spec.rb @@ -8,14 +8,15 @@ RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForIna let(:project_settings_table) { table(:project_settings) } subject(:perform_migration) do - described_class.new(start_id: projects_table.minimum(:id), - end_id: projects_table.maximum(:id), - batch_table: :projects, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: projects_table.minimum(:id), + end_id: projects_table.maximum(:id), + batch_table: :projects, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end let(:queries) { ActiveRecord::QueryRecorder.new { perform_migration } } @@ -27,32 +28,28 @@ RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForIna let(:project_namespace_5) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-5', type: 'Project') } let(:project_1) do - projects_table - .create!( + projects_table.create!( name: 'proj-1', path: 'path-1', namespace_id: namespace_1.id, project_namespace_id: project_namespace_2.id, visibility_level: 0 ) end let(:project_2) do - projects_table - .create!( + projects_table.create!( name: 'proj-2', path: 'path-2', namespace_id: namespace_1.id, project_namespace_id: project_namespace_3.id, visibility_level: 10 ) end let(:project_3) do - projects_table - .create!( + projects_table.create!( name: 'proj-3', path: 'path-3', namespace_id: namespace_1.id, project_namespace_id: project_namespace_4.id, visibility_level: 20, last_activity_at: '2021-01-01' ) end let(:project_4) do - projects_table - .create!( + projects_table.create!( name: 'proj-4', path: 'path-4', namespace_id: namespace_1.id, project_namespace_id: project_namespace_5.id, visibility_level: 20, last_activity_at: '2022-01-01' ) @@ -66,7 +63,7 @@ RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForIna end it 'sets `legacy_open_source_license_available` attribute to false for inactive, public projects', - :aggregate_failures do + :aggregate_failures do expect(queries.count).to eq(5) expect(migrated_attribute(project_1.id)).to be_truthy diff --git a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb index d60874c3159..93913a2742b 100644 --- a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb +++ b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForNoIssuesNoRepoProjects, - :migration, - schema: 20220722084543 do + :migration, + schema: 20220722084543 do let(:namespaces_table) { table(:namespaces) } let(:projects_table) { table(:projects) } let(:project_settings_table) { table(:project_settings) } @@ -12,18 +12,19 @@ RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForNoI let(:issues_table) { table(:issues) } subject(:perform_migration) do - described_class.new(start_id: projects_table.minimum(:id), - end_id: projects_table.maximum(:id), - batch_table: :projects, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: projects_table.minimum(:id), + end_id: projects_table.maximum(:id), + batch_table: :projects, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end it 'sets `legacy_open_source_license_available` to false only for public projects with no issues and no repo', - :aggregate_failures do + :aggregate_failures do project_with_no_issues_no_repo = create_legacy_license_public_project('project-with-no-issues-no-repo') project_with_repo = create_legacy_license_public_project('project-with-repo', repo_size: 1) project_with_issues = create_legacy_license_public_project('project-with-issues', with_issue: true) @@ -41,13 +42,13 @@ RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForNoI def create_legacy_license_public_project(path, repo_size: 0, with_issue: false) namespace = namespaces_table.create!(name: "namespace-#{path}", path: "namespace-#{path}") - project_namespace = - namespaces_table.create!(name: "-project-namespace-#{path}", path: "project-namespace-#{path}", type: 'Project') - project = projects_table - .create!( - name: path, path: path, namespace_id: namespace.id, - project_namespace_id: project_namespace.id, visibility_level: 20 - ) + project_namespace = namespaces_table.create!( + name: "-project-namespace-#{path}", path: "project-namespace-#{path}", type: 'Project' + ) + project = projects_table.create!( + name: path, path: path, namespace_id: namespace.id, + project_namespace_id: project_namespace.id, visibility_level: 20 + ) project_statistics_table.create!(project_id: project.id, namespace_id: namespace.id, repository_size: repo_size) issues_table.create!(project_id: project.id, namespace_id: project.project_namespace_id) if with_issue diff --git a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_one_member_no_repo_projects_spec.rb b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_one_member_no_repo_projects_spec.rb index 0dba1d7c8a2..285e5ebbee2 100644 --- a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_one_member_no_repo_projects_spec.rb +++ b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_one_member_no_repo_projects_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForOneMemberNoRepoProjects, - :migration, - schema: 20220721031446 do + :migration, + schema: 20220721031446 do let(:namespaces_table) { table(:namespaces) } let(:projects_table) { table(:projects) } let(:project_settings_table) { table(:project_settings) } @@ -13,18 +13,19 @@ RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForOne let(:project_authorizations_table) { table(:project_authorizations) } subject(:perform_migration) do - described_class.new(start_id: projects_table.minimum(:id), - end_id: projects_table.maximum(:id), - batch_table: :projects, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: projects_table.minimum(:id), + end_id: projects_table.maximum(:id), + batch_table: :projects, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end it 'sets `legacy_open_source_license_available` to false only for public projects with 1 member and no repo', - :aggregate_failures do + :aggregate_failures do project_with_no_repo_one_member = create_legacy_license_public_project('project-with-one-member-no-repo') project_with_repo_one_member = create_legacy_license_public_project('project-with-repo', repo_size: 1) project_with_no_repo_two_members = create_legacy_license_public_project('project-with-two-members', members: 2) @@ -42,13 +43,13 @@ RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForOne def create_legacy_license_public_project(path, repo_size: 0, members: 1) namespace = namespaces_table.create!(name: "namespace-#{path}", path: "namespace-#{path}") - project_namespace = - namespaces_table.create!(name: "-project-namespace-#{path}", path: "project-namespace-#{path}", type: 'Project') - project = projects_table - .create!( - name: path, path: path, namespace_id: namespace.id, - project_namespace_id: project_namespace.id, visibility_level: 20 - ) + project_namespace = namespaces_table.create!( + name: "-project-namespace-#{path}", path: "project-namespace-#{path}", type: 'Project' + ) + project = projects_table.create!( + name: path, path: path, namespace_id: namespace.id, + project_namespace_id: project_namespace.id, visibility_level: 20 + ) members.times do |member_id| user = users_table.create!(email: "user#{member_id}-project-#{project.id}@gitlab.com", projects_limit: 100) diff --git a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_five_mb_spec.rb b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_five_mb_spec.rb index a153507837c..fedee9c5068 100644 --- a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_five_mb_spec.rb +++ b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_five_mb_spec.rb @@ -3,23 +3,24 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForProjectsLessThanFiveMb, - :migration, - schema: 20221018095434, - feature_category: :groups_and_projects do + :migration, + schema: 20221018095434, + feature_category: :groups_and_projects do let(:namespaces_table) { table(:namespaces) } let(:projects_table) { table(:projects) } let(:project_settings_table) { table(:project_settings) } let(:project_statistics_table) { table(:project_statistics) } subject(:perform_migration) do - described_class.new(start_id: project_settings_table.minimum(:project_id), - end_id: project_settings_table.maximum(:project_id), - batch_table: :project_settings, - batch_column: :project_id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: project_settings_table.minimum(:project_id), + end_id: project_settings_table.maximum(:project_id), + batch_table: :project_settings, + batch_column: :project_id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end it 'sets `legacy_open_source_license_available` to false only for projects less than 5 MiB', :aggregate_failures do @@ -45,10 +46,12 @@ RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForPro def create_legacy_license_project_setting(repo_size:) path = "path-for-repo-size-#{repo_size}" namespace = namespaces_table.create!(name: "namespace-#{path}", path: "namespace-#{path}") - project_namespace = - namespaces_table.create!(name: "-project-namespace-#{path}", path: "project-namespace-#{path}", type: 'Project') - project = projects_table - .create!(name: path, path: path, namespace_id: namespace.id, project_namespace_id: project_namespace.id) + project_namespace = namespaces_table.create!( + name: "-project-namespace-#{path}", path: "project-namespace-#{path}", type: 'Project' + ) + project = projects_table.create!( + name: path, path: path, namespace_id: namespace.id, project_namespace_id: project_namespace.id + ) size_in_bytes = 1.megabyte * repo_size project_statistics_table.create!(project_id: project.id, namespace_id: namespace.id, repository_size: size_in_bytes) diff --git a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_one_mb_spec.rb b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_one_mb_spec.rb index 2e6bc2f77ae..cf544c87b31 100644 --- a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_one_mb_spec.rb +++ b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_one_mb_spec.rb @@ -3,26 +3,27 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForProjectsLessThanOneMb, - :migration, - schema: 20220906074449 do + :migration, + schema: 20220906074449 do let(:namespaces_table) { table(:namespaces) } let(:projects_table) { table(:projects) } let(:project_settings_table) { table(:project_settings) } let(:project_statistics_table) { table(:project_statistics) } subject(:perform_migration) do - described_class.new(start_id: project_settings_table.minimum(:project_id), - end_id: project_settings_table.maximum(:project_id), - batch_table: :project_settings, - batch_column: :project_id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: project_settings_table.minimum(:project_id), + end_id: project_settings_table.maximum(:project_id), + batch_table: :project_settings, + batch_column: :project_id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end it 'sets `legacy_open_source_license_available` to false only for projects less than 1 MiB', - :aggregate_failures do + :aggregate_failures do project_setting_1_mb = create_legacy_license_project_setting(repo_size: 1) project_setting_2_mb = create_legacy_license_project_setting(repo_size: 2) project_setting_quarter_mb = create_legacy_license_project_setting(repo_size: 0.25) @@ -43,10 +44,12 @@ RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForPro def create_legacy_license_project_setting(repo_size:) path = "path-for-repo-size-#{repo_size}" namespace = namespaces_table.create!(name: "namespace-#{path}", path: "namespace-#{path}") - project_namespace = - namespaces_table.create!(name: "-project-namespace-#{path}", path: "project-namespace-#{path}", type: 'Project') - project = projects_table - .create!(name: path, path: path, namespace_id: namespace.id, project_namespace_id: project_namespace.id) + project_namespace = namespaces_table.create!( + name: "-project-namespace-#{path}", path: "project-namespace-#{path}", type: 'Project' + ) + project = projects_table.create!( + name: path, path: path, namespace_id: namespace.id, project_namespace_id: project_namespace.id + ) size_in_bytes = 1.megabyte * repo_size project_statistics_table.create!(project_id: project.id, namespace_id: namespace.id, repository_size: size_in_bytes) diff --git a/spec/lib/gitlab/background_migration/expire_o_auth_tokens_spec.rb b/spec/lib/gitlab/background_migration/expire_o_auth_tokens_spec.rb index cffcda0a2ca..ba3aab03f2a 100644 --- a/spec/lib/gitlab/background_migration/expire_o_auth_tokens_spec.rb +++ b/spec/lib/gitlab/background_migration/expire_o_auth_tokens_spec.rb @@ -9,14 +9,15 @@ RSpec.describe Gitlab::BackgroundMigration::ExpireOAuthTokens, :migration, schem let(:table_name) { 'oauth_access_tokens' } subject(:perform_migration) do - described_class.new(start_id: 1, - end_id: 30, - batch_table: :oauth_access_tokens, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: 1, + end_id: 30, + batch_table: :oauth_access_tokens, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end before do diff --git a/spec/lib/gitlab/background_migration/fix_incoherent_packages_size_on_project_statistics_spec.rb b/spec/lib/gitlab/background_migration/fix_incoherent_packages_size_on_project_statistics_spec.rb index f71b54a7eb4..2a53d39b6b1 100644 --- a/spec/lib/gitlab/background_migration/fix_incoherent_packages_size_on_project_statistics_spec.rb +++ b/spec/lib/gitlab/background_migration/fix_incoherent_packages_size_on_project_statistics_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' # rubocop: disable RSpec/MultipleMemoizedHelpers RSpec.describe Gitlab::BackgroundMigration::FixIncoherentPackagesSizeOnProjectStatistics, - feature_category: :package_registry do + feature_category: :package_registry do let(:project_statistics_table) { table(:project_statistics) } let(:packages_table) { table(:packages_packages) } let(:package_files_table) { table(:packages_package_files) } @@ -197,8 +197,8 @@ RSpec.describe Gitlab::BackgroundMigration::FixIncoherentPackagesSizeOnProjectSt context 'with incoherent packages_size' do it_behaves_like 'enqueuing a buffered updates', - incoherent_non_zero_statistics: 195, - incoherent_zero_statistics: 200 + incoherent_non_zero_statistics: 195, + incoherent_zero_statistics: 200 context 'with updates waiting on redis' do before do @@ -207,8 +207,8 @@ RSpec.describe Gitlab::BackgroundMigration::FixIncoherentPackagesSizeOnProjectSt end it_behaves_like 'enqueuing a buffered updates', - incoherent_non_zero_statistics: 195, - incoherent_zero_statistics: 200 + incoherent_non_zero_statistics: 195, + incoherent_zero_statistics: 200 end end diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb index 71e9a568370..f4c5cd79863 100644 --- a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb +++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb @@ -18,9 +18,14 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover, :aggregate_failur let(:legacy_upload) { create_upload(note, filename) } def create_remote_upload(model, filename) - create(:upload, :attachment_upload, - path: "note/attachment/#{model.id}/#{filename}", secret: nil, - store: ObjectStorage::Store::REMOTE, model: model) + create( + :upload, + :attachment_upload, + path: "note/attachment/#{model.id}/#{filename}", + secret: nil, + store: ObjectStorage::Store::REMOTE, + model: model + ) end def create_upload(model, filename, with_file = true) @@ -147,14 +152,23 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover, :aggregate_failur end let(:legacy_upload) do - create(:upload, :with_file, :attachment_upload, - path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note) + create( + :upload, + :with_file, + :attachment_upload, + path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", + model: note + ) end context 'when the file does not exist for the upload' do let(:legacy_upload) do - create(:upload, :attachment_upload, - path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note) + create( + :upload, + :attachment_upload, + path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", + model: note + ) end it_behaves_like 'move error' @@ -162,8 +176,13 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover, :aggregate_failur context 'when the file does not exist on expected path' do let(:legacy_upload) do - create(:upload, :attachment_upload, :with_file, - path: "uploads/-/system/note/attachment/some_part/#{note.id}/#{filename}", model: note) + create( + :upload, + :attachment_upload, + :with_file, + path: "uploads/-/system/note/attachment/some_part/#{note.id}/#{filename}", + model: note + ) end it_behaves_like 'move error' @@ -171,8 +190,13 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover, :aggregate_failur context 'when the file path does not include system/note/attachment' do let(:legacy_upload) do - create(:upload, :attachment_upload, :with_file, - path: "uploads/-/system#{note.id}/#{filename}", model: note) + create( + :upload, + :attachment_upload, + :with_file, + path: "uploads/-/system#{note.id}/#{filename}", + model: note + ) end it_behaves_like 'move error' @@ -188,8 +212,14 @@ RSpec.describe Gitlab::BackgroundMigration::LegacyUploadMover, :aggregate_failur context 'when upload has mount_point nil' do let(:legacy_upload) do - create(:upload, :with_file, :attachment_upload, - path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note, mount_point: nil) + create( + :upload, + :with_file, + :attachment_upload, + path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", + model: note, + mount_point: nil + ) end it_behaves_like 'migrates the file correctly', false diff --git a/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb b/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb index af8b5240e40..4c989ba9cef 100644 --- a/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb +++ b/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveBackfilledJobArtifactsExpireAt describe '#perform' do let(:job_artifact) { table(:ci_job_artifacts, database: :ci) } - let(:jobs) { table(:ci_builds, database: :ci) { |model| model.primary_key = :id } } + let(:jobs) { table(:p_ci_builds, database: :ci) { |model| model.primary_key = :id } } let(:test_worker) do described_class.new( diff --git a/spec/lib/gitlab/batch_worker_context_spec.rb b/spec/lib/gitlab/batch_worker_context_spec.rb index 31641f7449e..a0a5bf0cba1 100644 --- a/spec/lib/gitlab/batch_worker_context_spec.rb +++ b/spec/lib/gitlab/batch_worker_context_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::BatchWorkerContext do subject(:batch_context) do described_class.new( - %w(hello world), + %w[hello world], arguments_proc: -> (word) { word }, context_proc: -> (word) { { user: build_stubbed(:user, username: word) } } ) @@ -13,13 +13,13 @@ RSpec.describe Gitlab::BatchWorkerContext do describe "#arguments" do it "returns all the expected arguments in arrays" do - expect(batch_context.arguments).to eq([%w(hello), %w(world)]) + expect(batch_context.arguments).to eq([%w[hello], %w[world]]) end end describe "#context_for" do it "returns the correct application context for the arguments" do - context = batch_context.context_for(%w(world)) + context = batch_context.context_for(%w[world]) expect(context).to be_a(Gitlab::ApplicationContext) expect(context.to_lazy_hash[:user].call).to eq("world") diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 517d557d665..d468483661a 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -220,7 +220,7 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea subject.execute expect(subject.errors.count).to eq(1) - expect(subject.errors.first.keys).to match_array(%i(type iid errors)) + expect(subject.errors.first.keys).to match_array(%i[type iid errors]) end end diff --git a/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb index 8f79390d2d9..8732c787657 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb @@ -99,5 +99,13 @@ RSpec.describe Gitlab::BitbucketImport::Importers::IssueImporter, :clean_gitlab_ importer.execute end + + it 'increments the issue counter' do + expect_next_instance_of(Gitlab::Import::Metrics) do |metrics| + expect(metrics).to receive_message_chain(:issues_counter, :increment) + end + + importer.execute + end end end diff --git a/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb index a361a9343dd..af5a929683e 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb @@ -12,22 +12,37 @@ RSpec.describe Gitlab::BitbucketImport::Importers::IssuesImporter, feature_categ ) end + let(:client) { Bitbucket::Client.new(project.import_data.credentials) } + + before do + allow(Bitbucket::Client).to receive(:new).and_return(client) + allow(client).to receive(:repo).and_return(Bitbucket::Representation::Repo.new({ 'has_issues' => true })) + allow(client).to receive(:last_issue).and_return(Bitbucket::Representation::Issue.new({ 'id' => 2 })) + allow(client).to receive(:issues).and_return( + [ + Bitbucket::Representation::Issue.new({ 'id' => 1 }), + Bitbucket::Representation::Issue.new({ 'id' => 2 }) + ], + [] + ) + end + subject(:importer) { described_class.new(project) } describe '#execute', :clean_gitlab_redis_cache do - before do - allow_next_instance_of(Bitbucket::Client) do |client| - allow(client).to receive(:issues).and_return( - [ - Bitbucket::Representation::Issue.new({ 'id' => 1 }), - Bitbucket::Representation::Issue.new({ 'id' => 2 }) - ], - [] - ) + context 'when the repo does not have issue tracking enabled' do + before do + allow(client).to receive(:repo).and_return(Bitbucket::Representation::Repo.new({ 'has_issues' => false })) + end + + it 'does not import issues' do + expect(Gitlab::BitbucketImport::ImportIssueWorker).not_to receive(:perform_in) + + importer.execute end end - it 'imports each issue in parallel', :aggregate_failures do + it 'imports each issue in parallel' do expect(Gitlab::BitbucketImport::ImportIssueWorker).to receive(:perform_in).twice waiter = importer.execute @@ -38,11 +53,15 @@ RSpec.describe Gitlab::BitbucketImport::Importers::IssuesImporter, feature_categ .to match_array(%w[1 2]) end + it 'allocates internal ids' do + expect(Issue).to receive(:track_namespace_iid!).with(project.project_namespace, 2) + + importer.execute + end + context 'when the client raises an error' do before do - allow_next_instance_of(Bitbucket::Client) do |client| - allow(client).to receive(:issues).and_raise(StandardError) - end + allow(client).to receive(:issues).and_raise(StandardError) end it 'tracks the failure and does not fail' do @@ -57,7 +76,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::IssuesImporter, feature_categ Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 1) end - it 'does not schedule job for enqueued issues', :aggregate_failures do + it 'does not schedule job for enqueued issues' do expect(Gitlab::BitbucketImport::ImportIssueWorker).to receive(:perform_in).once waiter = importer.execute diff --git a/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb index 043cd7f17b9..a04543b0511 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb @@ -4,15 +4,13 @@ require 'spec_helper' RSpec.describe Gitlab::BitbucketImport::Importers::IssuesNotesImporter, feature_category: :importers do let_it_be(:project) { create(:project, :import_started) } - # let_it_be(:merge_request_1) { create(:merge_request, source_project: project) } - # let_it_be(:merge_request_2) { create(:merge_request, source_project: project, source_branch: 'other-branch') } let_it_be(:issue_1) { create(:issue, project: project) } let_it_be(:issue_2) { create(:issue, project: project) } subject(:importer) { described_class.new(project) } describe '#execute', :clean_gitlab_redis_cache do - it 'imports the notes from each issue in parallel', :aggregate_failures do + it 'imports the notes from each issue in parallel' do expect(Gitlab::BitbucketImport::ImportIssueNotesWorker).to receive(:perform_in).twice waiter = importer.execute @@ -40,7 +38,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::IssuesNotesImporter, feature_ Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 2) end - it 'does not schedule job for enqueued issues', :aggregate_failures do + it 'does not schedule job for enqueued issues' do expect(Gitlab::BitbucketImport::ImportIssueNotesWorker).to receive(:perform_in).once waiter = importer.execute diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_request_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_request_importer_spec.rb index 2eca6bb47d6..1f36a353724 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/pull_request_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/pull_request_importer_spec.rb @@ -162,5 +162,13 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestImporter, :clean_g importer.execute end + + it 'increments the merge requests counter' do + expect_next_instance_of(Gitlab::Import::Metrics) do |metrics| + expect(metrics).to receive_message_chain(:merge_requests_counter, :increment) + end + + importer.execute + end end end diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_request_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_request_notes_importer_spec.rb index 4a30f225d66..332f6e5bd03 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/pull_request_notes_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/pull_request_notes_importer_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' -RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestNotesImporter, feature_category: :importers do +RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestNotesImporter, :clean_gitlab_redis_cache, feature_category: :importers do let_it_be(:project) do - create(:project, :import_started, + create(:project, :repository, :import_started, import_data_attributes: { credentials: { 'base_uri' => 'http://bitbucket.org/', 'user' => 'bitbucket', 'password' => 'password' } } @@ -12,28 +12,216 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestNotesImporter, fea end let_it_be(:merge_request) { create(:merge_request, source_project: project) } - + let_it_be(:merge_request_diff) { create(:merge_request_diff, :external, merge_request: merge_request) } + let_it_be(:bitbucket_user) { create(:user) } + let_it_be(:identity) { create(:identity, user: bitbucket_user, extern_uid: 'bitbucket_user', provider: :bitbucket) } let(:hash) { { iid: merge_request.iid } } - let(:importer_helper) { Gitlab::BitbucketImport::Importer.new(project) } + let(:client) { Bitbucket::Client.new({}) } + let(:ref_converter) { Gitlab::BitbucketImport::RefConverter.new(project) } + let(:user_finder) { Gitlab::BitbucketImport::UserFinder.new(project) } + let(:note_body) { 'body' } + let(:comments) { [Bitbucket::Representation::PullRequestComment.new(note_hash)] } + let(:created_at) { Date.today - 2.days } + let(:updated_at) { Date.today } + let(:note_hash) do + { + 'id' => 12, + 'user' => { 'nickname' => 'bitbucket_user' }, + 'content' => { 'raw' => note_body }, + 'created_on' => created_at, + 'updated_on' => updated_at + } + end subject(:importer) { described_class.new(project, hash) } before do - allow(Gitlab::BitbucketImport::Importer).to receive(:new).and_return(importer_helper) + allow(Bitbucket::Client).to receive(:new).and_return(client) + allow(Gitlab::BitbucketImport::RefConverter).to receive(:new).and_return(ref_converter) + allow(Gitlab::BitbucketImport::UserFinder).to receive(:new).and_return(user_finder) + allow(client).to receive(:pull_request_comments).and_return(comments) end describe '#execute' do - it 'calls Importer.import_pull_request_comments' do - expect(importer_helper).to receive(:import_pull_request_comments).once + context 'for standalone pr comments' do + it 'calls RefConverter' do + expect(ref_converter).to receive(:convert_note).once.and_call_original + + importer.execute + end + + it 'creates a note with the correct attributes' do + expect { importer.execute }.to change { merge_request.notes.count }.from(0).to(1) + + note = merge_request.notes.first + + expect(note.note).to eq(note_body) + expect(note.author).to eq(bitbucket_user) + expect(note.created_at).to eq(created_at) + expect(note.updated_at).to eq(updated_at) + end + + context 'when the author does not have a bitbucket identity' do + before do + identity.update!(provider: :github) + end + + it 'sets the author to the project creator and adds the author to the note' do + importer.execute + + note = merge_request.notes.first + + expect(note.author).to eq(project.creator) + expect(note.note).to eq("*Created by: bitbucket_user*\n\nbody") + end + end + + context 'when the note is deleted' do + let(:note_hash) do + { + 'id' => 12, + 'user' => { 'nickname' => 'bitbucket_user' }, + 'content' => { 'raw' => note_body }, + 'deleted' => true, + 'created_on' => created_at, + 'updated_on' => updated_at + } + end + + it 'does not create a note' do + expect { importer.execute }.not_to change { merge_request.notes.count } + end + end + end + + context 'for threaded inline comments' do + let(:path) { project.repository.commit.raw_diffs.first.new_path } + let(:reply_body) { 'Some reply' } + let(:comments) do + [ + Bitbucket::Representation::PullRequestComment.new(pr_comment_1), + Bitbucket::Representation::PullRequestComment.new(pr_comment_2) + ] + end - importer.execute + let(:pr_comment_1) do + { + 'id' => 14, + 'inline' => { + 'path' => path, + 'from' => nil, + 'to' => 1 + }, + 'parent' => { 'id' => 13 }, + 'user' => { 'nickname' => 'bitbucket_user' }, + 'content' => { 'raw' => reply_body }, + 'created_on' => created_at, + 'updated_on' => updated_at + } + end + + let(:pr_comment_2) do + { + 'id' => 13, + 'inline' => { + 'path' => path, + 'from' => nil, + 'to' => 1 + }, + 'user' => { 'nickname' => 'non_existent_user' }, + 'content' => { 'raw' => note_body }, + 'created_on' => created_at, + 'updated_on' => updated_at + } + end + + it 'creates notes in the correct position with the right attributes' do + expect { importer.execute }.to change { merge_request.notes.count }.from(0).to(2) + + expect(merge_request.notes.map(&:discussion_id).uniq.count).to eq(1) + + notes = merge_request.notes.order(:id).to_a + + start_note = notes.first + expect(start_note).to be_a(DiffNote) + expect(start_note.note).to eq("*Created by: non_existent_user*\n\n#{note_body}") + expect(start_note.author).to eq(project.creator) + + reply_note = notes.last + expect(reply_note).to be_a(DiffNote) + expect(reply_note.note).to eq(reply_body) + expect(reply_note.author).to eq(bitbucket_user) + end + + context 'when the comments are not part of the diff' do + let(:pr_comment_1) do + { + 'id' => 14, + 'inline' => { + 'path' => path, + 'from' => nil, + 'to' => nil + }, + 'parent' => { 'id' => 13 }, + 'user' => { 'nickname' => 'bitbucket_user' }, + 'content' => { 'raw' => reply_body }, + 'created_on' => created_at, + 'updated_on' => updated_at + } + end + + let(:pr_comment_2) do + { + 'id' => 13, + 'inline' => { + 'path' => path, + 'from' => nil, + 'to' => nil + }, + 'user' => { 'nickname' => 'bitbucket_user' }, + 'content' => { 'raw' => note_body }, + 'created_on' => created_at, + 'updated_on' => updated_at + } + end + + it 'creates them as normal notes' do + expect { importer.execute }.to change { merge_request.notes.count }.from(0).to(2) + + notes = merge_request.notes.order(:id).to_a + + first_note = notes.first + expect(first_note).not_to be_a(DiffNote) + expect(first_note.note).to eq("*Comment on*\n\n#{note_body}") + expect(first_note.author).to eq(bitbucket_user) + + second_note = notes.last + expect(second_note).not_to be_a(DiffNote) + expect(second_note.note).to eq("*Comment on*\n\n#{reply_body}") + expect(second_note.author).to eq(bitbucket_user) + end + end + + context 'when an error is raised for one note' do + before do + allow(user_finder).to receive(:gitlab_user_id).and_call_original + allow(user_finder).to receive(:gitlab_user_id).with(project, 'bitbucket_user').and_raise(StandardError) + end + + it 'tracks the error and continues to import other notes' do + expect(Gitlab::ErrorTracking).to receive(:log_exception) + .with(anything, hash_including(comment_id: 14)).and_call_original + + expect { importer.execute }.to change { merge_request.notes.count }.from(0).to(1) + end + end end context 'when the merge request does not exist' do let(:hash) { { iid: 'nonexistent' } } - it 'does not call Importer.import_pull_request_comments' do - expect(importer_helper).not_to receive(:import_pull_request_comments) + it 'does not call #import_pull_request_comments' do + expect(importer).not_to receive(:import_pull_request_comments) importer.execute end @@ -46,8 +234,8 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestNotesImporter, fea merge_request.update!(source_project: another_project, target_project: another_project) end - it 'does not call Importer.import_pull_request_comments' do - expect(importer_helper).not_to receive(:import_pull_request_comments) + it 'does not call #import_pull_request_comments' do + expect(importer).not_to receive(:import_pull_request_comments) importer.execute end @@ -55,7 +243,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestNotesImporter, fea context 'when an error is raised' do before do - allow(importer_helper).to receive(:import_pull_request_comments).and_raise(StandardError) + allow(importer).to receive(:import_pull_request_comments).and_raise(StandardError) end it 'tracks the failure and does not fail' do diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_requests_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_importer_spec.rb index 46bf099de0c..eba7ec92aba 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/pull_requests_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_importer_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestsImporter, feature end end - it 'imports each pull request in parallel', :aggregate_failures do + it 'imports each pull request in parallel' do expect(Gitlab::BitbucketImport::ImportPullRequestWorker).to receive(:perform_in).exactly(3).times waiter = importer.execute @@ -58,7 +58,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestsImporter, feature Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 1) end - it 'does not schedule job for enqueued pull requests', :aggregate_failures do + it 'does not schedule job for enqueued pull requests' do expect(Gitlab::BitbucketImport::ImportPullRequestWorker).to receive(:perform_in).twice waiter = importer.execute diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb index c44fc259c3b..78a08accf82 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestsNotesImporter, fe subject(:importer) { described_class.new(project) } describe '#execute', :clean_gitlab_redis_cache do - it 'imports the notes from each merge request in parallel', :aggregate_failures do + it 'imports the notes from each merge request in parallel' do expect(Gitlab::BitbucketImport::ImportPullRequestNotesWorker).to receive(:perform_in).twice waiter = importer.execute @@ -38,7 +38,7 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestsNotesImporter, fe Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 2) end - it 'does not schedule job for enqueued merge requests', :aggregate_failures do + it 'does not schedule job for enqueued merge requests' do expect(Gitlab::BitbucketImport::ImportPullRequestNotesWorker).to receive(:perform_in).once waiter = importer.execute diff --git a/spec/lib/gitlab/bitbucket_import/importers/repository_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/repository_importer_spec.rb index 1caf0b884c2..9e458780c78 100644 --- a/spec/lib/gitlab/bitbucket_import/importers/repository_importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importers/repository_importer_spec.rb @@ -7,6 +7,14 @@ RSpec.describe Gitlab::BitbucketImport::Importers::RepositoryImporter, feature_c subject(:importer) { described_class.new(project) } + before do + allow_next_instance_of(Bitbucket::Client) do |client| + allow(client).to receive(:repo).and_return(Bitbucket::Representation::Repo.new( + { 'mainbranch' => { 'name' => 'develop' } } + )) + end + end + describe '#execute' do context 'when repository is empty' do it 'imports the repository' do @@ -17,6 +25,15 @@ RSpec.describe Gitlab::BitbucketImport::Importers::RepositoryImporter, feature_c importer.execute end + + it 'sets the default branch' do + allow(project.repository).to receive(:import_repository) + allow(project.repository).to receive(:fetch_as_mirror) + + expect(project).to receive(:change_head).with('develop') + + importer.execute + end end context 'when repository is not empty' do diff --git a/spec/lib/gitlab/bullet/exclusions_spec.rb b/spec/lib/gitlab/bullet/exclusions_spec.rb index ccedfee28c7..2cb824674dc 100644 --- a/spec/lib/gitlab/bullet/exclusions_spec.rb +++ b/spec/lib/gitlab/bullet/exclusions_spec.rb @@ -3,7 +3,7 @@ require 'fast_spec_helper' require 'tempfile' -RSpec.describe Gitlab::Bullet::Exclusions, feature_category: :application_performance do +RSpec.describe Gitlab::Bullet::Exclusions, feature_category: :cloud_connector do let(:config_file) do file = Tempfile.new('bullet.yml') File.basename(file) diff --git a/spec/lib/gitlab/cache_spec.rb b/spec/lib/gitlab/cache_spec.rb index 67c70a77880..92a5ea7bdfb 100644 --- a/spec/lib/gitlab/cache_spec.rb +++ b/spec/lib/gitlab/cache_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Gitlab::Cache, :request_store do end describe '.delete' do - let(:key) { %w{a cache key} } + let(:key) { %w[a cache key] } subject(:delete) { described_class.delete(key) } diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb index 30359a7170f..2990599f840 100644 --- a/spec/lib/gitlab/ci/ansi2html_spec.rb +++ b/spec/lib/gitlab/ci/ansi2html_spec.rb @@ -227,7 +227,7 @@ RSpec.describe Gitlab::Ci::Ansi2html do text = "#{section_start}Some text#{section_end}" class_name_start = section_start.gsub("\033[0K", '').gsub('<', '<') class_name_end = section_end.gsub("\033[0K", '').gsub('<', '<') - html = %{<span>#{class_name_start}Some text#{class_name_end}</span>} + html = %(<span>#{class_name_start}Some text#{class_name_end}</span>) expect(convert_html(text)).to eq(html) end @@ -238,9 +238,9 @@ RSpec.describe Gitlab::Ci::Ansi2html do it 'prints light red' do text = "#{section_start}\e[91mHello\e[0m\nLine 1\nLine 2\nLine 3\n#{section_end}" - header = %{<span class="term-fg-l-red section section-header js-s-#{class_name(section_name)}">Hello</span>} - line_break = %{<span class="section section-header js-s-#{class_name(section_name)}"><br/></span>} - output_line = %{<span class="section line js-s-#{class_name(section_name)}">Line 1<br/>Line 2<br/>Line 3<br/></span>} + header = %(<span class="term-fg-l-red section section-header js-s-#{class_name(section_name)}">Hello</span>) + line_break = %(<span class="section section-header js-s-#{class_name(section_name)}"><br/></span>) + output_line = %(<span class="section line js-s-#{class_name(section_name)}">Line 1<br/>Line 2<br/>Line 3<br/></span>) html = "#{section_start_html}#{header}#{line_break}#{output_line}#{section_end_html}" expect(convert_html(text)).to eq(html) diff --git a/spec/lib/gitlab/ci/ansi2json/line_spec.rb b/spec/lib/gitlab/ci/ansi2json/line_spec.rb index b8563bb1d1c..475a54b275d 100644 --- a/spec/lib/gitlab/ci/ansi2json/line_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json/line_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Ansi2json::Line do +RSpec.describe Gitlab::Ci::Ansi2json::Line, feature_category: :continuous_integration do let(:offset) { 0 } let(:style) { Gitlab::Ci::Ansi2json::Style.new } @@ -75,6 +75,14 @@ RSpec.describe Gitlab::Ci::Ansi2json::Line do end end + describe '#set_as_section_footer' do + it 'change the section_footer to true' do + expect { subject.set_as_section_footer } + .to change { subject.section_footer } + .to be_truthy + end + end + describe '#set_section_duration' do using RSpec::Parameterized::TableSyntax @@ -178,6 +186,23 @@ RSpec.describe Gitlab::Ci::Ansi2json::Line do expect(subject.to_h).to eq(result) end end + + context 'when section footer is set' do + before do + subject.set_as_section_footer + end + + it 'serializes the attributes set' do + result = { + offset: 0, + content: [{ text: 'some data', style: 'term-bold' }], + section: 'section_2', + section_footer: true + } + + expect(subject.to_h).to eq(result) + end + end end context 'when there are no sections' do diff --git a/spec/lib/gitlab/ci/ansi2json/state_spec.rb b/spec/lib/gitlab/ci/ansi2json/state_spec.rb index 8dd4092f3d8..07e6579829a 100644 --- a/spec/lib/gitlab/ci/ansi2json/state_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json/state_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Ansi2json::State, feature_category: :continuous_integ state.offset = 1 state.new_line!(style: { fg: 'some-fg', bg: 'some-bg', mask: 1234 }) state.set_last_line_offset - state.open_section('hello', 111, {}) + state.open_section('hello', 100, {}) end end @@ -24,7 +24,7 @@ RSpec.describe Gitlab::Ci::Ansi2json::State, feature_category: :continuous_integ fg: 'some-fg', mask: 1234 }) - expect(new_state.open_sections).to eq({ 'hello' => 111 }) + expect(new_state.open_sections).to eq({ 'hello' => 100 }) end it 'ignores unsigned prior state', :aggregate_failures do @@ -44,6 +44,23 @@ RSpec.describe Gitlab::Ci::Ansi2json::State, feature_category: :continuous_integ expect(new_state.open_sections).to eq({}) end + it 'opens and closes a section', :aggregate_failures do + new_state = described_class.new('', 1000) + + new_state.new_line!(style: {}) + new_state.open_section('hello', 100, {}) + + expect(new_state.current_line.section_header).to eq(true) + expect(new_state.current_line.section_footer).to eq(false) + + new_state.new_line!(style: {}) + new_state.close_section('hello', 101) + + expect(new_state.current_line.section_header).to eq(false) + expect(new_state.current_line.section_duration).to eq('00:01') + expect(new_state.current_line.section_footer).to eq(true) + end + it 'ignores bad input', :aggregate_failures do expect(::Gitlab::AppLogger).to( receive(:warn).with( diff --git a/spec/lib/gitlab/ci/ansi2json_spec.rb b/spec/lib/gitlab/ci/ansi2json_spec.rb index 98fca40e8ea..23be3209171 100644 --- a/spec/lib/gitlab/ci/ansi2json_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json_spec.rb @@ -145,6 +145,7 @@ RSpec.describe Gitlab::Ci::Ansi2json, feature_category: :continuous_integration offset: 63, content: [], section_duration: '01:03', + section_footer: true, section: 'prepare-script' } ]) @@ -163,7 +164,8 @@ RSpec.describe Gitlab::Ci::Ansi2json, feature_category: :continuous_integration offset: 56, content: [], section: 'prepare-script', - section_duration: '01:03' + section_duration: '01:03', + section_footer: true } ]) end @@ -181,7 +183,8 @@ RSpec.describe Gitlab::Ci::Ansi2json, feature_category: :continuous_integration offset: 49, content: [], section: 'prepare-script', - section_duration: '01:03' + section_duration: '01:03', + section_footer: true }, { offset: 91, @@ -262,7 +265,8 @@ RSpec.describe Gitlab::Ci::Ansi2json, feature_category: :continuous_integration offset: 75, content: [], section: 'prepare-script', - section_duration: '01:03' + section_duration: '01:03', + section_footer: true } ]) end @@ -300,7 +304,8 @@ RSpec.describe Gitlab::Ci::Ansi2json, feature_category: :continuous_integration offset: 106, content: [], section: 'prepare-script-nested', - section_duration: '00:02' + section_duration: '00:02', + section_footer: true }, { offset: 155, @@ -311,7 +316,8 @@ RSpec.describe Gitlab::Ci::Ansi2json, feature_category: :continuous_integration offset: 158, content: [], section: 'prepare-script', - section_duration: '01:03' + section_duration: '01:03', + section_footer: true }, { offset: 200, @@ -345,13 +351,15 @@ RSpec.describe Gitlab::Ci::Ansi2json, feature_category: :continuous_integration offset: 115, content: [], section: 'prepare-script-nested', - section_duration: '00:02' + section_duration: '00:02', + section_footer: true }, { offset: 164, content: [], section: 'prepare-script', - section_duration: '01:03' + section_duration: '01:03', + section_footer: true } ]) end @@ -378,7 +386,8 @@ RSpec.describe Gitlab::Ci::Ansi2json, feature_category: :continuous_integration offset: 83, content: [], section: 'prepare-script', - section_duration: '01:03' + section_duration: '01:03', + section_footer: true } ]) end @@ -554,7 +563,8 @@ RSpec.describe Gitlab::Ci::Ansi2json, feature_category: :continuous_integration offset: 77, content: [], section: 'prepare-script', - section_duration: '01:03' + section_duration: '01:03', + section_footer: true } ] end diff --git a/spec/lib/gitlab/ci/badge/pipeline/status_spec.rb b/spec/lib/gitlab/ci/badge/pipeline/status_spec.rb index 45d0d781090..d6b59d0da64 100644 --- a/spec/lib/gitlab/ci/badge/pipeline/status_spec.rb +++ b/spec/lib/gitlab/ci/badge/pipeline/status_spec.rb @@ -117,10 +117,7 @@ RSpec.describe Gitlab::Ci::Badge::Pipeline::Status do end def create_pipeline(project, sha, branch) - pipeline = create(:ci_empty_pipeline, - project: project, - sha: sha, - ref: branch) + pipeline = create(:ci_empty_pipeline, project: project, sha: sha, ref: branch) create(:ci_build, pipeline: pipeline, stage: 'notify') end diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb index 7b35c9ba483..6cd9432c6c5 100644 --- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb @@ -69,9 +69,11 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do it { is_expected.to all(be_an_instance_of(described_class)) } it do - is_expected.to contain_exactly entry('path/dir_1/file_1'), - entry('path/dir_1/file_b'), - entry('path/dir_1/subdir/') + is_expected.to contain_exactly( + entry('path/dir_1/file_1'), + entry('path/dir_1/file_b'), + entry('path/dir_1/subdir/') + ) end end @@ -82,8 +84,10 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do it { is_expected.to all(be_an_instance_of(described_class)) } it do - is_expected.to contain_exactly entry('path/dir_1/file_1'), - entry('path/dir_1/file_b') + is_expected.to contain_exactly( + entry('path/dir_1/file_1'), + entry('path/dir_1/file_b') + ) end end @@ -103,8 +107,10 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do it { is_expected.to all(be_an_instance_of(described_class)) } it do - is_expected.to contain_exactly entry('path/dir_1/subdir/'), - entry('path/') + is_expected.to contain_exactly( + entry('path/dir_1/subdir/'), + entry('path/') + ) end end diff --git a/spec/lib/gitlab/ci/build/context/build_spec.rb b/spec/lib/gitlab/ci/build/context/build_spec.rb index fae02e140f2..9fdb4ee9393 100644 --- a/spec/lib/gitlab/ci/build/context/build_spec.rb +++ b/spec/lib/gitlab/ci/build/context/build_spec.rb @@ -41,16 +41,6 @@ RSpec.describe Gitlab::Ci::Build::Context::Build, feature_category: :pipeline_co it { expect(context.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } it_behaves_like 'variables collection' - - context 'with FF disabled' do - before do - stub_feature_flags(reduced_build_attributes_list_for_rules: false) - end - - it { expect(context.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } - - it_behaves_like 'variables collection' - end end describe '#variables_hash' do @@ -59,15 +49,5 @@ RSpec.describe Gitlab::Ci::Build::Context::Build, feature_category: :pipeline_co it { expect(context.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } it_behaves_like 'variables collection' - - context 'with FF disabled' do - before do - stub_feature_flags(reduced_build_attributes_list_for_rules: false) - end - - it { expect(context.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } - - it_behaves_like 'variables collection' - end end end diff --git a/spec/lib/gitlab/ci/build/hook_spec.rb b/spec/lib/gitlab/ci/build/hook_spec.rb index 6c9175b4260..da9a680f110 100644 --- a/spec/lib/gitlab/ci/build/hook_spec.rb +++ b/spec/lib/gitlab/ci/build/hook_spec.rb @@ -4,8 +4,10 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Build::Hook, feature_category: :pipeline_composition do let_it_be(:build1) do - FactoryBot.build(:ci_build, - options: { hooks: { pre_get_sources_script: ["echo 'hello pre_get_sources_script'"] } }) + build( + :ci_build, + options: { hooks: { pre_get_sources_script: ["echo 'hello pre_get_sources_script'"] } } + ) end describe '.from_hooks' do diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb index 5d5a212b9a5..00e44650d44 100644 --- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb @@ -8,11 +8,14 @@ RSpec.describe Gitlab::Ci::Build::Policy::Changes do describe '#satisfied_by?' do describe 'paths matching' do let(:pipeline) do - build(:ci_empty_pipeline, project: project, - ref: 'master', - source: :push, - sha: '1234abcd', - before_sha: '0123aabb') + build( + :ci_empty_pipeline, + project: project, + ref: 'master', + source: :push, + sha: '1234abcd', + before_sha: '0123aabb' + ) end let(:ci_build) do @@ -92,11 +95,14 @@ RSpec.describe Gitlab::Ci::Build::Policy::Changes do let_it_be(:project) { create(:project, :repository) } let(:pipeline) do - create(:ci_empty_pipeline, project: project, - ref: 'master', - source: :push, - sha: '498214d', - before_sha: '281d3a7') + create( + :ci_empty_pipeline, + project: project, + ref: 'master', + source: :push, + sha: '498214d', + before_sha: '281d3a7' + ) end let(:build) do @@ -122,12 +128,15 @@ RSpec.describe Gitlab::Ci::Build::Policy::Changes do let_it_be(:project) { create(:project, :repository) } let(:pipeline) do - create(:ci_empty_pipeline, project: project, - ref: 'feature', - source: source, - sha: '0b4bc9a4', - before_sha: Gitlab::Git::BLANK_SHA, - merge_request: merge_request) + create( + :ci_empty_pipeline, + project: project, + ref: 'feature', + source: source, + sha: '0b4bc9a4', + before_sha: Gitlab::Git::BLANK_SHA, + merge_request: merge_request + ) end let(:ci_build) do @@ -140,11 +149,13 @@ RSpec.describe Gitlab::Ci::Build::Policy::Changes do let(:source) { :merge_request_event } let(:merge_request) do - create(:merge_request, - source_project: project, - source_branch: 'feature', - target_project: project, - target_branch: 'master') + create( + :merge_request, + source_project: project, + source_branch: 'feature', + target_project: project, + target_branch: 'master' + ) end it 'is satified by changes in the merge request' do diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb index e560f1c2b5a..1073df60d4a 100644 --- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb @@ -104,23 +104,32 @@ RSpec.describe Gitlab::Ci::Build::Policy::Variables do context 'when using project ci variables in environment scope' do let(:ci_build) do - build(:ci_build, pipeline: pipeline, - project: project, - ref: 'master', - stage: 'review', - environment: 'test/$CI_JOB_STAGE/1', - ci_stage: build(:ci_stage, name: 'review', project: project, pipeline: pipeline)) + build( + :ci_build, + pipeline: pipeline, + project: project, + ref: 'master', + stage: 'review', + environment: 'test/$CI_JOB_STAGE/1', + ci_stage: build(:ci_stage, name: 'review', project: project, pipeline: pipeline) + ) end before do - create(:ci_variable, project: project, - key: 'SCOPED_VARIABLE', - value: 'my-value-1') - - create(:ci_variable, project: project, - key: 'SCOPED_VARIABLE', - value: 'my-value-2', - environment_scope: 'test/review/*') + create( + :ci_variable, + project: project, + key: 'SCOPED_VARIABLE', + value: 'my-value-1' + ) + + create( + :ci_variable, + project: project, + key: 'SCOPED_VARIABLE', + value: 'my-value-2', + environment_scope: 'test/review/*' + ) end it 'is satisfied by scoped variable match' do diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb index a22aa30304b..1fe54b9f2d5 100644 --- a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb @@ -95,8 +95,11 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do context 'when using compare_to' do let_it_be(:project) do - create(:project, :custom_repo, - files: { 'README.md' => 'readme' }) + create( + :project, + :custom_repo, + files: { 'README.md' => 'readme' } + ) end let_it_be(:user) { project.owner } diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb index 567ffa68836..6e6b9d949c5 100644 --- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb @@ -101,14 +101,16 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do describe '#value' do it 'is returns a bridge job configuration' do - expect(subject.value).to eq(name: :my_bridge, - trigger: { project: 'some/project' }, - ignore: false, - stage: 'test', - only: { refs: %w[branches tags] }, - job_variables: {}, - root_variables_inheritance: true, - scheduling_type: :stage) + expect(subject.value).to eq( + name: :my_bridge, + trigger: { project: 'some/project' }, + ignore: false, + stage: 'test', + only: { refs: %w[branches tags] }, + job_variables: {}, + root_variables_inheritance: true, + scheduling_type: :stage + ) end end end @@ -124,15 +126,16 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do describe '#value' do it 'is returns a bridge job configuration hash' do - expect(subject.value).to eq(name: :my_bridge, - trigger: { project: 'some/project', - branch: 'feature' }, - ignore: false, - stage: 'test', - only: { refs: %w[branches tags] }, - job_variables: {}, - root_variables_inheritance: true, - scheduling_type: :stage) + expect(subject.value).to eq( + name: :my_bridge, + trigger: { project: 'some/project', branch: 'feature' }, + ignore: false, + stage: 'test', + only: { refs: %w[branches tags] }, + job_variables: {}, + root_variables_inheritance: true, + scheduling_type: :stage + ) end end end @@ -283,8 +286,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do ignore: false, stage: 'test', only: { refs: %w[branches tags] }, - parallel: { matrix: [{ 'PROVIDER' => ['aws'], 'STACK' => %w(monitoring app1) }, - { 'PROVIDER' => ['gcp'], 'STACK' => %w(data) }] }, + parallel: { matrix: [{ 'PROVIDER' => ['aws'], 'STACK' => %w[monitoring app1] }, + { 'PROVIDER' => ['gcp'], 'STACK' => %w[data] }] }, job_variables: {}, root_variables_inheritance: true, scheduling_type: :stage @@ -305,15 +308,16 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do describe '#value' do it 'returns a bridge job configuration hash' do - expect(subject.value).to eq(name: :my_bridge, - trigger: { project: 'some/project', - forward: { pipeline_variables: true } }, - ignore: false, - stage: 'test', - only: { refs: %w[branches tags] }, - job_variables: {}, - root_variables_inheritance: true, - scheduling_type: :stage) + expect(subject.value).to eq( + name: :my_bridge, + trigger: { project: 'some/project', forward: { pipeline_variables: true } }, + ignore: false, + stage: 'test', + only: { refs: %w[branches tags] }, + job_variables: {}, + root_variables_inheritance: true, + scheduling_type: :stage + ) end end end diff --git a/spec/lib/gitlab/ci/config/entry/commands_spec.rb b/spec/lib/gitlab/ci/config/entry/commands_spec.rb index 1b8dfae692a..f84a78b4804 100644 --- a/spec/lib/gitlab/ci/config/entry/commands_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/commands_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Commands do let(:entry) { described_class.new(config) } context 'when entry config value is an array of strings' do - let(:config) { %w(ls pwd) } + let(:config) { %w[ls pwd] } describe '#value' do it 'returns array of strings' do diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb index 3562706ff33..cff94a96c99 100644 --- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb @@ -93,7 +93,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Environment do context 'when valid action is used' do where(:action) do - %w(start stop prepare verify access) + %w[start stop prepare verify access] end with_them do diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb index b37498ba10a..17c45ec4c2c 100644 --- a/spec/lib/gitlab/ci/config/entry/image_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb @@ -56,7 +56,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do end context 'when configuration is a hash' do - let(:config) { { name: 'image:1.0', entrypoint: %w(/bin/sh run) } } + let(:config) { { name: 'image:1.0', entrypoint: %w[/bin/sh run] } } describe '#value' do it 'returns image hash' do @@ -84,13 +84,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do describe '#entrypoint' do it "returns image's entrypoint" do - expect(entry.entrypoint).to eq %w(/bin/sh run) + expect(entry.entrypoint).to eq %w[/bin/sh run] end end context 'when configuration has ports' do let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] } - let(:config) { { name: 'image:1.0', entrypoint: %w(/bin/sh run), ports: ports } } + let(:config) { { name: 'image:1.0', entrypoint: %w[/bin/sh run], ports: ports } } let(:entry) { described_class.new(config, with_image_ports: image_ports) } let(:image_ports) { false } diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 1a78d929871..24d3cac6616 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -598,7 +598,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo end end - context 'when job is not a pages job' do + context 'when job is not a pages job', feature_category: :pages do let(:name) { :rspec } context 'if the config contains a publish entry' do @@ -609,9 +609,18 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo expect(entry.errors).to include /job publish can only be used within a `pages` job/ end end + + context 'if the config contains a pages entry' do + let(:entry) { described_class.new({ script: 'echo', pages: { path_prefix: 'foo' } }, name: name) } + + it 'is invalid' do + expect(entry).not_to be_valid + expect(entry.errors).to include /job pages can only be used within a `pages` job/ + end + end end - context 'when job is a pages job' do + context 'when job is a pages job', feature_category: :pages do let(:name) { :pages } context 'when it does not have a publish entry' do @@ -629,6 +638,28 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo expect(entry).to be_valid end end + + context 'when it has a pages entry' do + let(:entry) { described_class.new({ script: 'echo', pages: { path_prefix: 'foo' } }, name: name) } + + it 'is valid' do + expect(entry).to be_valid + end + end + end + end + + describe '#pages_job?', :aggregate_failures, feature_category: :pages do + where(:name, :result) do + :pages | true + :'pages:staging' | false + :'something:pages:else' | false + end + + with_them do + subject { described_class.new({}, name: name).pages_job? } + + it { is_expected.to eq(result) } end end @@ -739,20 +770,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo end end - describe '#pages_job?', :aggregate_failures, feature_category: :pages do - where(:name, :result) do - :pages | true - :'pages:staging' | false - :'something:pages:else' | false - end - - with_them do - subject { described_class.new({}, name: name).pages_job? } - - it { is_expected.to eq(result) } - end - end - context 'when composed' do before do entry.compose! @@ -773,19 +790,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo end it 'returns correct value' do - expect(entry.value) - .to eq(name: :rspec, - before_script: %w[ls pwd], - script: %w[rspec], - stage: 'test', - ignore: false, - after_script: %w[cleanup], - hooks: { pre_get_sources_script: ['echo hello'] }, - only: { refs: %w[branches tags] }, - job_variables: {}, - root_variables_inheritance: true, - scheduling_type: :stage, - id_tokens: { TEST_ID_TOKEN: { aud: 'https://gitlab.com' } }) + expect(entry.value).to eq( + name: :rspec, + before_script: %w[ls pwd], + script: %w[rspec], + stage: 'test', + ignore: false, + after_script: %w[cleanup], + hooks: { pre_get_sources_script: ['echo hello'] }, + only: { refs: %w[branches tags] }, + job_variables: {}, + root_variables_inheritance: true, + scheduling_type: :stage, + id_tokens: { TEST_ID_TOKEN: { aud: 'https://gitlab.com' } } + ) end end end diff --git a/spec/lib/gitlab/ci/config/entry/pages_spec.rb b/spec/lib/gitlab/ci/config/entry/pages_spec.rb new file mode 100644 index 00000000000..0ee692a443e --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/pages_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Config::Entry::Pages, feature_category: :pages do + subject(:entry) { described_class.new(config) } + + describe 'validation' do + context 'when value given is not a hash' do + let(:config) { 'value' } + + it 'is invalid' do + expect(entry).not_to be_valid + expect(entry.errors).to include('pages config should be a hash') + end + end + + context 'when value is a hash' do + context 'when the hash is valid' do + let(:config) { { path_prefix: 'prefix' } } + + it 'is valid' do + expect(entry).to be_valid + expect(entry.value).to eq({ + path_prefix: 'prefix' + }) + end + end + + context 'when path_prefix key is not a string' do + let(:config) { { path_prefix: 1 } } + + it 'is invalid' do + expect(entry).not_to be_valid + expect(entry.errors).to include('pages path prefix should be a string') + end + end + + context 'when hash contains not allowed keys' do + let(:config) { { unknown: 'echo' } } + + it 'is invalid' do + expect(entry).not_to be_valid + expect(entry.errors).to include('pages config contains unknown keys: unknown') + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 7093a0a6edf..77a895b75c0 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -221,8 +221,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Policy, feature_category: :continuous_ let(:config) { { variables: %w[$VARIABLE] } } it 'includes default values' do - expect(entry.value).to eq(refs: %w[branches tags], - variables: %w[$VARIABLE]) + expect(entry.value).to eq(refs: %w[branches tags], variables: %w[$VARIABLE]) end end end diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb index 132e75a808b..44e2fdbac37 100644 --- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb @@ -119,6 +119,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable, feature_category: :pipeli end end + context 'when script: and trigger: are used together' do + let(:config) do + { + script: 'echo', + trigger: 'test-group/test-project' + } + end + + it 'returns is invalid' do + expect(entry).not_to be_valid + expect(entry.errors).to include(/these keys cannot be used together: script, trigger/) + end + end + context 'when only: is used with rules:' do let(:config) { { only: ['merge_requests'], rules: [{ if: '$THIS' }] } } diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index 5fac5298e8e..0370bcbccf5 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do context 'when top-level entries are defined' do let(:hash) do { - before_script: %w(ls pwd), + before_script: %w[ls pwd], image: 'image:1.0', default: {}, services: ['postgres:9.1', 'mysql:5.5'], @@ -41,7 +41,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do VAR3: { value: 'val3', options: %w[val3 val4 val5], description: 'this is var 3 and some options' } }, after_script: ['make clean'], - stages: %w(build pages release), + stages: %w[build pages release], cache: { key: 'k', untracked: true, paths: ['public/'] }, rspec: { script: %w[rspec ls] }, spinach: { before_script: [], variables: {}, script: 'spinach' }, @@ -123,7 +123,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do expect(root.jobs_value[:rspec]).to eq( { name: :rspec, script: %w[rspec ls], - before_script: %w(ls pwd), + before_script: %w[ls pwd], image: { name: 'image:1.0' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', @@ -162,7 +162,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }], cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success', unprotect: false, fallback_keys: [] }], - only: { refs: %w(branches tags) }, + only: { refs: %w[branches tags] }, job_variables: { 'VAR' => { value: 'job' } }, root_variables_inheritance: true, after_script: [], @@ -176,14 +176,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do context 'when a mix of top-level and default entries is used' do let(:hash) do - { before_script: %w(ls pwd), + { before_script: %w[ls pwd], after_script: ['make clean'], default: { image: 'image:1.0', services: ['postgres:9.1', 'mysql:5.5'] }, variables: { VAR: 'root' }, - stages: %w(build pages), + stages: %w[build pages], cache: { key: 'k', untracked: true, paths: ['public/'] }, rspec: { script: %w[rspec ls] }, spinach: { before_script: [], variables: { VAR: 'job' }, script: 'spinach' } } @@ -205,7 +205,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do expect(root.jobs_value).to eq( rspec: { name: :rspec, script: %w[rspec ls], - before_script: %w(ls pwd), + before_script: %w[ls pwd], image: { name: 'image:1.0' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb index e36484bb0ae..1f935bebed5 100644 --- a/spec/lib/gitlab/ci/config/entry/service_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb @@ -51,7 +51,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do context 'when configuration is a hash' do let(:config) do - { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run) } + { name: 'postgresql:9.5', alias: 'db', command: %w[cmd run], entrypoint: %w[/bin/sh run] } end describe '#valid?' do @@ -80,13 +80,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do describe '#command' do it "returns service's command" do - expect(entry.command).to eq %w(cmd run) + expect(entry.command).to eq %w[cmd run] end end describe '#entrypoint' do it "returns service's entrypoint" do - expect(entry.entrypoint).to eq %w(/bin/sh run) + expect(entry.entrypoint).to eq %w[/bin/sh run] end end @@ -99,7 +99,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do context 'when configuration has ports' do let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] } let(:config) do - { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports } + { name: 'postgresql:9.5', alias: 'db', command: %w[cmd run], entrypoint: %w[/bin/sh run], ports: ports } end let(:entry) { described_class.new(config, with_image_ports: image_ports) } @@ -198,7 +198,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do context 'when service has ports' do let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] } let(:config) do - { name: 'postgresql:9.5', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports } + { name: 'postgresql:9.5', command: %w[cmd run], entrypoint: %w[/bin/sh run], ports: ports } end it 'alias field is mandatory' do @@ -209,7 +209,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do context 'when service does not have ports' do let(:config) do - { name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run) } + { name: 'postgresql:9.5', alias: 'db', command: %w[cmd run], entrypoint: %w[/bin/sh run] } end it 'alias field is optional' do diff --git a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb index 69aa3bab77a..b57a56b8389 100644 --- a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb +++ b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb @@ -128,8 +128,7 @@ RSpec.describe Gitlab::Ci::Config::Extendable::Entry do it 'raises an error' do expect { subject.extend! } - .to raise_error(described_class::InvalidExtensionError, - /invalid base hash/) + .to raise_error(described_class::InvalidExtensionError, /invalid base hash/) end end @@ -140,8 +139,7 @@ RSpec.describe Gitlab::Ci::Config::Extendable::Entry do it 'raises an error' do expect { subject.extend! } - .to raise_error(described_class::InvalidExtensionError, - /unknown key/) + .to raise_error(described_class::InvalidExtensionError, /unknown key/) end end @@ -178,7 +176,7 @@ RSpec.describe Gitlab::Ci::Config::Extendable::Entry do { first: { script: 'my value', image: 'ubuntu' }, second: { image: 'alpine' }, - test: { extends: %w(first second) } + test: { extends: %w[first second] } } end @@ -186,7 +184,7 @@ RSpec.describe Gitlab::Ci::Config::Extendable::Entry do { first: { script: 'my value', image: 'ubuntu' }, second: { image: 'alpine' }, - test: { extends: %w(first second), script: 'my value', image: 'alpine' } + test: { extends: %w[first second], script: 'my value', image: 'alpine' } } end @@ -230,8 +228,7 @@ RSpec.describe Gitlab::Ci::Config::Extendable::Entry do it 'raises an error' do expect { subject.extend! } - .to raise_error(described_class::CircularDependencyError, - /circular dependency detected/) + .to raise_error(described_class::CircularDependencyError, /circular dependency detected/) end end diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb index 1415dbeb532..bcfab620bd9 100644 --- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe end context 'when location is not a string' do - let(:location) { %w(some/file.txt other/file.txt) } + let(:location) { %w[some/file.txt other/file.txt] } it { is_expected.to be_falsy } end diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index 56d1ddee4b8..5f28b45496f 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -406,8 +406,10 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper, feature_category: :pipeline end it 'includes the matched local files' do - expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Local), - an_instance_of(Gitlab::Ci::Config::External::File::Local)) + expect(subject).to contain_exactly( + an_instance_of(Gitlab::Ci::Config::External::File::Local), + an_instance_of(Gitlab::Ci::Config::External::File::Local) + ) expect(subject.map(&:location)).to contain_exactly('myfolder/file1.yml', 'myfolder/file2.yml') end @@ -424,8 +426,10 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper, feature_category: :pipeline let(:project_id) { project.id } it 'includes the file' do - expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Remote), - an_instance_of(Gitlab::Ci::Config::External::File::Local)) + expect(subject).to contain_exactly( + an_instance_of(Gitlab::Ci::Config::External::File::Remote), + an_instance_of(Gitlab::Ci::Config::External::File::Local) + ) end end diff --git a/spec/lib/gitlab/ci/config/header/input_spec.rb b/spec/lib/gitlab/ci/config/header/input_spec.rb index 5d1fa4a8e6e..df70d1fd7c8 100644 --- a/spec/lib/gitlab/ci/config/header/input_spec.rb +++ b/spec/lib/gitlab/ci/config/header/input_spec.rb @@ -40,12 +40,24 @@ RSpec.describe Gitlab::Ci::Config::Header::Input, feature_category: :pipeline_co end end - context 'when has a default value' do + context 'when has a string default value' do let(:input_hash) { { default: 'bar' } } it_behaves_like 'a valid input' end + context 'when has a numeric default value' do + let(:input_hash) { { default: 6.66 } } + + it_behaves_like 'a valid input' + end + + context 'when has a boolean default value' do + let(:input_hash) { { default: true } } + + it_behaves_like 'a valid input' + end + context 'when has a description value' do let(:input_hash) { { description: 'bar' } } @@ -103,4 +115,21 @@ RSpec.describe Gitlab::Ci::Config::Header::Input, feature_category: :pipeline_co it_behaves_like 'an invalid input' end + + context 'when the limit for allowed number of options is reached' do + let(:limit) { described_class::ALLOWED_OPTIONS_LIMIT } + let(:input_hash) { { default: 'value1', options: options } } + let(:options) { Array.new(limit.next) { |i| "value#{i}" } } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + it 'returns error about incorrect type' do + expect(config.errors).to contain_exactly( + "foo config cannot define more than #{limit} options") + end + end + end end diff --git a/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb b/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb index b0618081207..57ced4eab98 100644 --- a/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb +++ b/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb @@ -7,6 +7,90 @@ RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs, feature_category: :pip let(:specs) { { foo: { default: 'bar' } } } let(:args) { {} } + context 'when inputs are valid strings and have options' do + let(:specs) { { foo: { default: 'one', options: %w[one two three] } } } + + context 'and the value is selected' do + let(:args) { { foo: 'two' } } + + it 'assigns the selected value' do + expect(inputs).to be_valid + expect(inputs.to_hash).to eq({ foo: 'two' }) + end + end + + context 'and the value is not selected' do + it 'assigns the default value' do + expect(inputs).to be_valid + expect(inputs.to_hash).to eq({ foo: 'one' }) + end + end + end + + context 'when inputs options are valid integers' do + let(:specs) { { foo: { default: 1, options: [1, 2, 3, 4, 5], type: 'number' } } } + + context 'and a value of the wrong type is given' do + let(:args) { { foo: 'word' } } + + it 'returns an error' do + expect(inputs).not_to be_valid + expect(inputs.errors).to contain_exactly( + "`foo` input: `word` cannot be used because it is not in the list of the allowed options", + "`foo` input: provided value is not a number" + ) + end + end + + context 'and the value is selected' do + let(:args) { { foo: 2 } } + + it 'assigns the selected value' do + expect(inputs).to be_valid + expect(inputs.to_hash).to eq({ foo: 2 }) + end + end + + context 'and the value is not selected' do + it 'assigns the default value' do + expect(inputs).to be_valid + expect(inputs.to_hash).to eq({ foo: 1 }) + end + end + end + + context 'when inputs have invalid type options' do + let(:specs) { { foo: { default: true, options: [true, false], type: 'boolean' } } } + + it 'returns an error' do + expect(inputs).not_to be_valid + expect(inputs.errors).to contain_exactly("`foo` input: Options can only be used with string and number inputs") + end + end + + context 'when inputs are valid with options but the default value is not in the options' do + let(:specs) { { foo: { default: 'coop', options: %w[one two three] } } } + + it 'returns an error' do + expect(inputs).not_to be_valid + expect(inputs.errors).to contain_exactly( + '`foo` input: `coop` cannot be used because it is not in the list of allowed options' + ) + end + end + + context 'when inputs are valid with options but the value is not in the options' do + let(:specs) { { foo: { default: 'one', options: %w[one two three] } } } + let(:args) { { foo: 'niet' } } + + it 'returns an error' do + expect(inputs).not_to be_valid + expect(inputs.errors).to contain_exactly( + '`foo` input: `niet` cannot be used because it is not in the list of allowed options' + ) + end + end + context 'when given unrecognized inputs' do let(:specs) { { foo: nil } } let(:args) { { foo: 'bar', test: 'bar' } } @@ -164,7 +248,7 @@ RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs, feature_category: :pip context 'when the value is not a number' do let(:specs) { { number_input: { type: 'number' } } } - let(:args) { { number_input: 'NaN' } } + let(:args) { { number_input: false } } it 'is invalid' do expect(inputs).not_to be_valid diff --git a/spec/lib/gitlab/ci/jwt_v2_spec.rb b/spec/lib/gitlab/ci/jwt_v2_spec.rb index d45d8cacb88..c2ced10620b 100644 --- a/spec/lib/gitlab/ci/jwt_v2_spec.rb +++ b/spec/lib/gitlab/ci/jwt_v2_spec.rb @@ -33,14 +33,6 @@ RSpec.describe Gitlab::Ci::JwtV2, feature_category: :continuous_integration do describe '#payload' do subject(:payload) { ci_job_jwt_v2.payload } - it 'has correct values for the standard JWT attributes' do - aggregate_failures do - expect(payload[:iss]).to eq(Settings.gitlab.base_url) - expect(payload[:aud]).to eq(Settings.gitlab.base_url) - expect(payload[:sub]).to eq("project_path:#{project.full_path}:ref_type:branch:ref:#{pipeline.source_ref}") - end - end - it 'includes user identities when enabled' do expect(user).to receive(:pass_user_identities_to_ci_jwt).and_return(true) identities = payload[:user_identities].map { |identity| identity.slice(:extern_uid, :provider) } @@ -53,6 +45,34 @@ RSpec.describe Gitlab::Ci::JwtV2, feature_category: :continuous_integration do expect(payload).not_to include(:user_identities) end + context 'when oidc_issuer_url is disabled' do + before do + stub_feature_flags(oidc_issuer_url: false) + end + + it 'has correct values for the standard JWT attributes' do + aggregate_failures do + expect(payload[:iss]).to eq(Settings.gitlab.base_url) + expect(payload[:aud]).to eq(Settings.gitlab.base_url) + expect(payload[:sub]).to eq("project_path:#{project.full_path}:ref_type:branch:ref:#{pipeline.source_ref}") + end + end + end + + context 'when oidc_issuer_url is enabled' do + before do + stub_feature_flags(oidc_issuer_url: true) + end + + it 'has correct values for the standard JWT attributes' do + aggregate_failures do + expect(payload[:iss]).to eq(Gitlab.config.gitlab.url) + expect(payload[:aud]).to eq(Settings.gitlab.base_url) + expect(payload[:sub]).to eq("project_path:#{project.full_path}:ref_type:branch:ref:#{pipeline.source_ref}") + end + end + end + context 'when given an aud' do let(:aud) { 'AWS' } diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb index dacbe07c8b3..2c57106b07c 100644 --- a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb +++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb @@ -42,15 +42,16 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::CyclonedxProperties, feature_category: it { is_expected.to be_nil } end - context 'when no dependency_scanning properties are present' do + context 'when no dependency_scanning or container_scanning properties are present' do let(:properties) do [ { 'name' => 'gitlab:meta:schema_version', 'value' => '1' } ] end - it 'does not call dependency_scanning parser' do + it 'does not call source parsers' do expect(Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning).not_to receive(:source) + expect(Gitlab::Ci::Parsers::Sbom::Source::ContainerScanning).not_to receive(:source) parse_source_from_properties end @@ -85,4 +86,35 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::CyclonedxProperties, feature_category: parse_source_from_properties end end + + context 'when container_scanning properties are present' do + let(:properties) do + [ + { 'name' => 'gitlab:meta:schema_version', 'value' => '1' }, + { 'name' => 'gitlab:container_scanning:image:name', 'value' => 'photon' }, + { 'name' => 'gitlab:container_scanning:image:tag', 'value' => '5.0-20231007' }, + { 'name' => 'gitlab:container_scanning:operating_system:name', 'value' => 'Photon OS' }, + { 'name' => 'gitlab:container_scanning:operating_system:version', 'value' => '5.0' } + ] + end + + let(:expected_input) do + { + 'image' => { + 'name' => 'photon', + 'tag' => '5.0-20231007' + }, + 'operating_system' => { + 'name' => 'Photon OS', + 'version' => '5.0' + } + } + end + + it 'passes only supported properties to the container scanning parser' do + expect(Gitlab::Ci::Parsers::Sbom::Source::ContainerScanning).to receive(:source).with(expected_input) + + parse_source_from_properties + end + end end diff --git a/spec/lib/gitlab/ci/parsers/sbom/source/container_scanning_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/source/container_scanning_spec.rb new file mode 100644 index 00000000000..410b2c0098a --- /dev/null +++ b/spec/lib/gitlab/ci/parsers/sbom/source/container_scanning_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe Gitlab::Ci::Parsers::Sbom::Source::ContainerScanning, feature_category: :container_scanning do + subject { described_class.source(property_data) } + + context 'when required properties are present' do + let(:property_data) do + { + 'image' => { + 'name' => 'photon', + 'tag' => '5.0-20231007' + }, + 'operating_system' => { + 'name' => 'Photon OS', + 'version' => '5.0' + } + } + end + + it 'returns expected source data' do + is_expected.to have_attributes( + source_type: :container_scanning, + data: property_data + ) + end + end + + context 'when required properties are missing' do + let(:property_data) do + { + 'operating_system' => { + 'name' => 'Photon OS', + 'version' => '5.0' + } + } + end + + it { is_expected.to be_nil } + end + + context 'when some operating_system properties are missing' do + let(:property_data) do + { + 'image' => { + 'name' => 'photon', + 'tag' => '5.0-20231007' + }, + 'operating_system' => { + 'name' => 'Photon OS' + } + } + end + + it { is_expected.to be_nil } + end +end diff --git a/spec/lib/gitlab/ci/parsers/security/common_spec.rb b/spec/lib/gitlab/ci/parsers/security/common_spec.rb index 648b8ac2db9..431a6d94c48 100644 --- a/spec/lib/gitlab/ci/parsers/security/common_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/common_spec.rb @@ -335,7 +335,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common, feature_category: :vulnera expect(flags).to contain_exactly( have_attributes(type: 'flagged-as-likely-false-positive', origin: 'post analyzer X', description: 'static string to sink'), - have_attributes(type: 'flagged-as-likely-false-positive', origin: 'post analyzer Y', description: 'integer to sink') + have_attributes(type: 'flagged-as-likely-false-positive', origin: 'post analyzer Y', description: 'integer to sink') ) end end diff --git a/spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb b/spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb index 13999b2a9e5..640bed0d329 100644 --- a/spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/secret_detection_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do let(:created_at) { 2.weeks.ago } context "when parsing valid reports" do - where(report_format: %i(secret_detection)) + where(report_format: %i[secret_detection]) with_them do let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, created_at) } diff --git a/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb index ddd0de69d79..70d73a8095c 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb @@ -21,11 +21,11 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::TemplateUsage do expect(command).to( receive(:yaml_processor_result) .and_return( - double(included_templates: %w(Template-1 Template-2)) + double(included_templates: %w[Template-1 Template-2]) ) ) - %w(Template-1 Template-2).each do |expected_template| + %w[Template-1 Template-2].each do |expected_template| expect(Gitlab::UsageDataCounters::CiTemplateUniqueCounter).to( receive(:track_unique_project_event) .with(project: project, template: expected_template, config_source: pipeline.config_source, user: user) diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb index ab223ae41fa..eb71cc0f0bc 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb @@ -49,7 +49,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do context 'when left and right are equal' do where(:left_value, :right_value) do - [%w(string string)] + [%w[string string]] end with_them do diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 54e569f424b..ef9b8f2b82f 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -395,7 +395,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build, feature_category: :pipeline_co end context 'when root_variables_inheritance is an array' do - let(:root_variables_inheritance) { %w(VAR1 VAR2 VAR3) } + let(:root_variables_inheritance) { %w[VAR1 VAR2 VAR3] } it 'returns calculated yaml variables' do expect(subject[:yaml_variables]).to match_array( diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb index ad8f1dc11f8..6d8b472a240 100644 --- a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb +++ b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do "type" => "error", "typeCode" => 1, "message" => "Anchor element found with a valid href attribute, but no link content has been supplied.", - "context" => %{<a href="/" class="navbar-brand animated"><svg height="36" viewBox="0 0 1...</a>}, + "context" => %(<a href="/" class="navbar-brand animated"><svg height="36" viewBox="0 0 1...</a>), "selector" => "#main-nav > div:nth-child(1) > a", "runner" => "htmlcs", "runnerExtras" => {} @@ -29,7 +29,7 @@ RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do "type" => "error", "typeCode" => 1, "message" => "This element has insufficient contrast at this conformance level.", - "context" => %{<a href="/stages-devops-lifecycle/" class="main-nav-link">Product</a>}, + "context" => %(<a href="/stages-devops-lifecycle/" class="main-nav-link">Product</a>), "selector" => "#main-nav > div:nth-child(2) > ul > li:nth-child(1) > a", "runner" => "htmlcs", "runnerExtras" => {} diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb index af6844491ca..dff59474746 100644 --- a/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/accessibility_reports_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Gitlab::Ci::Reports::AccessibilityReports do "type": "error", "typeCode": 1, "message": "Anchor element found with a valid href attribute, but no link content has been supplied.", - "context": %{<a href="/customers/worldline"><svg viewBox="0 0 509 89" xmln...</a>}, + "context": %(<a href="/customers/worldline"><svg viewBox="0 0 509 89" xmln...</a>), "selector": "html > body > div:nth-child(9) > div:nth-child(2) > a:nth-child(17)", "runner": "htmlcs", "runnerExtras": {} @@ -22,7 +22,7 @@ RSpec.describe Gitlab::Ci::Reports::AccessibilityReports do "type": "error", "typeCode": 1, "message": "Anchor element found with a valid href attribute, but no link content has been supplied.", - "context": %{<a href="/customers/equinix"><svg xmlns="http://www.w3.org/...</a>}, + "context": %(<a href="/customers/equinix"><svg xmlns="http://www.w3.org/...</a>), "selector": "html > body > div:nth-child(9) > div:nth-child(2) > a:nth-child(18)", "runner": "htmlcs", "runnerExtras": {} diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb index 05f6a8a8cb6..46ab0802200 100644 --- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb +++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb @@ -192,7 +192,7 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do end context 'when there are multiple test cases' do - let(:status_ordered) { %w(error failed success skipped) } + let(:status_ordered) { %w[error failed success skipped] } before do test_suite.add_test_case(test_case_success) diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb index cbf0976c976..e46ad573235 100644 --- a/spec/lib/gitlab/ci/status/composite_spec.rb +++ b/spec/lib/gitlab/ci/status/composite_spec.rb @@ -43,28 +43,29 @@ RSpec.describe Gitlab::Ci::Status::Composite, feature_category: :continuous_inte context 'allow_failure: false' do where(:build_statuses, :dag, :result, :has_warnings) do - %i(skipped) | false | 'skipped' | false - %i(skipped success) | false | 'success' | false - %i(skipped success) | true | 'skipped' | false - %i(created) | false | 'created' | false - %i(preparing) | false | 'preparing' | false - %i(canceled success skipped) | false | 'canceled' | false - %i(canceled success skipped) | true | 'skipped' | false - %i(pending created skipped) | false | 'pending' | false - %i(pending created skipped success) | false | 'running' | false - %i(running created skipped success) | false | 'running' | false - %i(pending created skipped) | true | 'skipped' | false - %i(pending created skipped success) | true | 'skipped' | false - %i(running created skipped success) | true | 'skipped' | false - %i(success waiting_for_resource) | false | 'waiting_for_resource' | false - %i(success manual) | false | 'manual' | false - %i(success scheduled) | false | 'scheduled' | false - %i(created preparing) | false | 'preparing' | false - %i(created success pending) | false | 'running' | false - %i(skipped success failed) | false | 'failed' | false - %i(skipped success failed) | true | 'skipped' | false - %i(success manual) | true | 'manual' | false - %i(success failed created) | true | 'running' | false + %i[skipped] | false | 'skipped' | false + %i[skipped success] | false | 'success' | false + %i[skipped success] | true | 'skipped' | false + %i[created] | false | 'created' | false + %i[preparing] | false | 'preparing' | false + %i[canceled success skipped] | false | 'canceled' | false + %i[canceled success skipped] | true | 'skipped' | false + %i[pending created skipped] | false | 'pending' | false + %i[pending created skipped success] | false | 'running' | false + %i[running created skipped success] | false | 'running' | false + %i[pending created skipped] | true | 'skipped' | false + %i[pending created skipped success] | true | 'skipped' | false + %i[running created skipped success] | true | 'skipped' | false + %i[success waiting_for_resource] | false | 'waiting_for_resource' | false + %i[success waiting_for_callback] | false | 'waiting_for_callback' | false + %i[success manual] | false | 'manual' | false + %i[success scheduled] | false | 'scheduled' | false + %i[created preparing] | false | 'preparing' | false + %i[created success pending] | false | 'running' | false + %i[skipped success failed] | false | 'failed' | false + %i[skipped success failed] | true | 'skipped' | false + %i[success manual] | true | 'manual' | false + %i[success failed created] | true | 'running' | false end with_them do @@ -78,13 +79,13 @@ RSpec.describe Gitlab::Ci::Status::Composite, feature_category: :continuous_inte context 'allow_failure: true' do where(:build_statuses, :dag, :result, :has_warnings) do - %i(manual) | false | 'skipped' | false - %i(skipped failed) | false | 'success' | true - %i(skipped failed) | true | 'skipped' | true - %i(success manual) | true | 'skipped' | false - %i(success manual) | false | 'success' | false - %i(created failed) | false | 'created' | true - %i(preparing manual) | false | 'preparing' | false + %i[manual] | false | 'skipped' | false + %i[skipped failed] | false | 'success' | true + %i[skipped failed] | true | 'skipped' | true + %i[success manual] | true | 'skipped' | false + %i[success manual] | false | 'success' | false + %i[created failed] | false | 'created' | true + %i[preparing manual] | false | 'preparing' | false end with_them do diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index 34e430202c9..98fefea7bdf 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -22,7 +22,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory, feature_category: :continuous end context 'when stage has a core status' do - (Ci::HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status| + (Ci::HasStatus::AVAILABLE_STATUSES - %w[manual skipped scheduled]).each do |core_status| context "when core status is #{core_status}" do let(:stage) { create(:ci_stage, pipeline: pipeline, status: core_status) } diff --git a/spec/lib/gitlab/ci/status/waiting_for_callback_spec.rb b/spec/lib/gitlab/ci/status/waiting_for_callback_spec.rb new file mode 100644 index 00000000000..6c833e96137 --- /dev/null +++ b/spec/lib/gitlab/ci/status/waiting_for_callback_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Status::WaitingForCallback, feature_category: :deployment_management do + subject do + described_class.new(double, double) + end + + describe '#text' do + it { expect(subject.text).to eq 'Waiting' } + end + + describe '#label' do + it { expect(subject.label).to eq 'waiting for callback' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'status_pending' } + end + + describe '#favicon' do + it { expect(subject.favicon).to eq 'favicon_status_pending' } + end + + describe '#group' do + it { expect(subject.group).to eq 'waiting-for-callback' } + end + + describe '#name' do + it { expect(subject.name).to eq 'WAITING_FOR_CALLBACK' } + end + + describe '#details_path' do + it { expect(subject.details_path).to be_nil } + end +end diff --git a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb index 460ecbb05d0..c5125689207 100644 --- a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb +++ b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do subject(:service) { described_class.new(statuses) } describe 'gem version' do - let(:acceptable_version) { '9.0.1' } + let(:acceptable_version) { '10.0.0' } let(:error_message) do <<~MESSAGE diff --git a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb index acb296082b8..dc9999ab9e4 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb @@ -58,7 +58,7 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do context 'with no cluster or agent' do it 'does not create any kubernetes deployment jobs' do - expect(build_names).to eq %w(placeholder) + expect(build_names).to eq %w[placeholder] end end @@ -68,7 +68,7 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do end it 'does not create any kubernetes deployment jobs' do - expect(build_names).to eq %w(placeholder) + expect(build_names).to eq %w[placeholder] end end @@ -81,7 +81,7 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do it 'when CI_DEPLOY_FREEZE is present' do create(:ci_variable, project: project, key: 'CI_DEPLOY_FREEZE', value: 'true') - expect(build_names).to eq %w(placeholder) + expect(build_names).to eq %w[placeholder] end it 'when CANARY_ENABLED' do diff --git a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb index 2b9213ea921..86bc9259789 100644 --- a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb @@ -44,7 +44,7 @@ RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml', feature_category: :continuo it 'creates a pipeline with the expected jobs' do expect(pipeline).to be_merge_request_event expect(pipeline.errors.full_messages).to be_empty - expect(build_names).to match_array(%w(kics-iac-sast)) + expect(build_names).to match_array(%w[kics-iac-sast]) end end end diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb index 09ca2678de5..7471dc58e44 100644 --- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb @@ -94,7 +94,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml', feature_category: :auto_devops do project.repository.create_branch(pipeline_branch, default_branch) end - %w(review_ecs review_fargate).each do |job| + %w[review_ecs review_fargate].each do |job| it_behaves_like 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do let(:job_name) { job } end @@ -142,7 +142,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml', feature_category: :auto_devops do context 'when the project has no active cluster' do it 'only creates a build and a test stage' do - expect(pipeline.stages_names).to eq(%w(build test)) + expect(pipeline.stages_names).to eq(%w[build test]) end it_behaves_like 'no Kubernetes deployment job' @@ -273,25 +273,25 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml', feature_category: :auto_devops do using RSpec::Parameterized::TableSyntax where(:case_name, :files, :variables, :include_build_names, :not_include_build_names) do - 'No match' | { 'README.md' => '' } | {} | %w() | %w(build test) - 'Buildpack' | { 'README.md' => '' } | { 'BUILDPACK_URL' => 'http://example.com' } | %w(build test) | %w() - 'Explicit set' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '1' } | %w(build test) | %w() - 'Explicit unset' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '0' } | %w() | %w(build test) - 'DOCKERFILE_PATH' | { 'README.md' => '' } | { 'DOCKERFILE_PATH' => 'Docker.file' } | %w(build test) | %w() - 'Dockerfile' | { 'Dockerfile' => '' } | {} | %w(build test) | %w() - 'Clojure' | { 'project.clj' => '' } | {} | %w(build test) | %w() - 'Go modules' | { 'go.mod' => '' } | {} | %w(build test) | %w() - 'Go gb' | { 'src/gitlab.com/gopackage.go' => '' } | {} | %w(build test) | %w() - 'Gradle' | { 'gradlew' => '' } | {} | %w(build test) | %w() - 'Java' | { 'pom.xml' => '' } | {} | %w(build test) | %w() - 'Multi-buildpack' | { '.buildpacks' => '' } | {} | %w(build test) | %w() - 'NodeJS' | { 'package.json' => '' } | {} | %w(build test) | %w() - 'PHP' | { 'composer.json' => '' } | {} | %w(build test) | %w() - 'Play' | { 'conf/application.conf' => '' } | {} | %w(build test) | %w() - 'Python' | { 'Pipfile' => '' } | {} | %w(build test) | %w() - 'Ruby' | { 'Gemfile' => '' } | {} | %w(build test) | %w() - 'Scala' | { 'build.sbt' => '' } | {} | %w(build test) | %w() - 'Static' | { '.static' => '' } | {} | %w(build test) | %w() + 'No match' | { 'README.md' => '' } | {} | %w[] | %w[build test] + 'Buildpack' | { 'README.md' => '' } | { 'BUILDPACK_URL' => 'http://example.com' } | %w[build test] | %w[] + 'Explicit set' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '1' } | %w[build test] | %w[] + 'Explicit unset' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '0' } | %w[] | %w[build test] + 'DOCKERFILE_PATH' | { 'README.md' => '' } | { 'DOCKERFILE_PATH' => 'Docker.file' } | %w[build test] | %w[] + 'Dockerfile' | { 'Dockerfile' => '' } | {} | %w[build test] | %w[] + 'Clojure' | { 'project.clj' => '' } | {} | %w[build test] | %w[] + 'Go modules' | { 'go.mod' => '' } | {} | %w[build test] | %w[] + 'Go gb' | { 'src/gitlab.com/gopackage.go' => '' } | {} | %w[build test] | %w[] + 'Gradle' | { 'gradlew' => '' } | {} | %w[build test] | %w[] + 'Java' | { 'pom.xml' => '' } | {} | %w[build test] | %w[] + 'Multi-buildpack' | { '.buildpacks' => '' } | {} | %w[build test] | %w[] + 'NodeJS' | { 'package.json' => '' } | {} | %w[build test] | %w[] + 'PHP' | { 'composer.json' => '' } | {} | %w[build test] | %w[] + 'Play' | { 'conf/application.conf' => '' } | {} | %w[build test] | %w[] + 'Python' | { 'Pipfile' => '' } | {} | %w[build test] | %w[] + 'Ruby' | { 'Gemfile' => '' } | {} | %w[build test] | %w[] + 'Scala' | { 'build.sbt' => '' } | {} | %w[build test] | %w[] + 'Static' | { '.static' => '' } | {} | %w[build test] | %w[] end with_them do diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb index d96c8f1bd0c..aa612899f4b 100644 --- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb @@ -123,11 +123,11 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item, feature_category: :secre }, "simple variable reference": { variable: { key: 'VAR', value: 'something_$VAR2' }, - expected_depends_on: %w(VAR2) + expected_depends_on: %w[VAR2] }, "complex expansion": { variable: { key: 'VAR', value: 'something_${VAR2}_$VAR3' }, - expected_depends_on: %w(VAR2 VAR3) + expected_depends_on: %w[VAR2 VAR3] }, "complex expansion in raw variable": { variable: { key: 'VAR', value: 'something_${VAR2}_$VAR3', raw: true }, @@ -135,7 +135,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item, feature_category: :secre }, "complex expansions for Windows": { variable: { key: 'variable3', value: 'key%variable%%variable2%' }, - expected_depends_on: %w(variable variable2) + expected_depends_on: %w[variable variable2] } } end @@ -282,7 +282,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item, feature_category: :secre it '#depends_on contains names of dependencies' do runner_variable = described_class.new(key: 'CI_VAR', value: '${CI_VAR_2}-123-$CI_VAR_3') - expect(runner_variable.depends_on).to eq(%w(CI_VAR_2 CI_VAR_3)) + expect(runner_variable.depends_on).to eq(%w[CI_VAR_2 CI_VAR_3]) end end diff --git a/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb b/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb index 082febacbd7..496d89403d5 100644 --- a/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor/dag_spec.rb @@ -3,38 +3,48 @@ require 'fast_spec_helper' require 'tsort' -RSpec.describe Gitlab::Ci::YamlProcessor::Dag do +RSpec.describe Gitlab::Ci::YamlProcessor::Dag, feature_category: :pipeline_composition do let(:nodes) { {} } subject(:result) { described_class.new(nodes).tsort } context 'when it is a regular pipeline' do let(:nodes) do - { 'job_c' => %w(job_b job_d), 'job_d' => %w(job_a), 'job_b' => %w(job_a), 'job_a' => %w() } + { 'job_c' => %w[job_b job_d], 'job_d' => %w[job_a], 'job_b' => %w[job_a], 'job_a' => %w[] } end it 'returns ordered jobs' do - expect(result).to eq(%w(job_a job_b job_d job_c)) + expect(result).to eq(%w[job_a job_b job_d job_c]) end end context 'when there is a circular dependency' do let(:nodes) do - { 'job_a' => %w(job_c), 'job_b' => %w(job_a), 'job_c' => %w(job_b) } + { 'job_a' => %w[job_c], 'job_b' => %w[job_a], 'job_c' => %w[job_b] } end - it 'raises TSort::Cyclic' do + it 'raises TSort::Cyclic error' do expect { result }.to raise_error(TSort::Cyclic, /topological sort failed/) end + + context 'when a job has a self-dependency' do + let(:nodes) do + { 'job_a' => %w[job_a] } + end + + it 'raises TSort::Cyclic error' do + expect { result }.to raise_error(TSort::Cyclic, "self-dependency: job_a") + end + end end context 'when there are some missing jobs' do let(:nodes) do - { 'job_a' => %w(job_d job_f), 'job_b' => %w(job_a job_c job_e) } + { 'job_a' => %w[job_d job_f], 'job_b' => %w[job_a job_c job_e] } end it 'ignores the missing ones and returns in a valid order' do - expect(result).to eq(%w(job_d job_f job_a job_c job_e job_b)) + expect(result).to eq(%w[job_d job_f job_a job_c job_e job_b]) end end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 81bc8c7ab9a..f01c1c7d053 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1510,7 +1510,7 @@ module Gitlab it 'correctly extends rspec job' do expect(config_processor.builds).to be_one - expect(subject.dig(:options, :script)).to eq %w(test) + expect(subject.dig(:options, :script)).to eq %w[test] expect(subject.dig(:options, :image, :name)).to eq 'ruby:alpine' end end @@ -1595,7 +1595,7 @@ module Gitlab it 'correctly extends rspec job' do expect(config_processor.builds).to be_one expect(subject.dig(:options, :before_script)).to eq ["bundle install"] - expect(subject.dig(:options, :script)).to eq %w(rspec) + expect(subject.dig(:options, :script)).to eq %w[rspec] expect(subject.dig(:options, :image, :name)).to eq 'image:test' expect(subject.dig(:when)).to eq 'always' end @@ -2386,7 +2386,7 @@ module Gitlab end context 'dependencies to builds' do - let(:dependencies) { %w(build1 build2) } + let(:dependencies) { %w[build1 build2] } it { is_expected.to be_valid } end @@ -2457,7 +2457,7 @@ module Gitlab end context 'needs a job from the same stage' do - let(:needs) { %w(test2) } + let(:needs) { %w[test2] } it 'creates jobs with valid specifications' do expect(subject.builds.size).to eq(7) @@ -2494,7 +2494,7 @@ module Gitlab end context 'needs two builds' do - let(:needs) { %w(build1 build2) } + let(:needs) { %w[build1 build2] } it "does create jobs with valid specification" do expect(subject.builds.size).to eq(7) @@ -2578,7 +2578,7 @@ module Gitlab end context 'needs parallel job' do - let(:needs) { %w(parallel) } + let(:needs) { %w[parallel] } it "does create jobs with valid specification" do expect(subject.builds.size).to eq(7) @@ -2707,7 +2707,7 @@ module Gitlab context 'duplicate needs' do context 'when needs are specified in an array' do - let(:needs) { %w(build1 build1) } + let(:needs) { %w[build1 build1] } it_behaves_like 'returns errors', 'test1 has the following needs duplicated: build1.' end @@ -2736,8 +2736,8 @@ module Gitlab end context 'needs and dependencies that are mismatching' do - let(:needs) { %w(build1) } - let(:dependencies) { %w(build2) } + let(:needs) { %w[build1] } + let(:dependencies) { %w[build2] } it_behaves_like 'returns errors', 'jobs:test1 dependencies the build2 should be part of needs' end @@ -2750,13 +2750,13 @@ module Gitlab ] end - let(:dependencies) { %w(build3) } + let(:dependencies) { %w[build3] } it_behaves_like 'returns errors', 'jobs:test1 dependencies the build3 should be part of needs' end context 'needs with an array type and dependency with a string type' do - let(:needs) { %w(build1) } + let(:needs) { %w[build1] } let(:dependencies) { 'deploy' } it_behaves_like 'returns errors', 'jobs:test1 dependencies should be an array of strings' @@ -2764,7 +2764,7 @@ module Gitlab context 'needs with a string type and dependency with an array type' do let(:needs) { 'build1' } - let(:dependencies) { %w(deploy) } + let(:dependencies) { %w[deploy] } it_behaves_like 'returns errors', 'jobs:test1:needs config can only be a hash or an array' end @@ -3252,7 +3252,7 @@ module Gitlab end context 'returns errors if job stage is not a defined stage' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", stage: "acceptance" } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", stage: "acceptance" } }) } it_behaves_like 'returns errors', 'rspec job: chosen stage does not exist; available stages are .pre, build, test, .post' end @@ -3288,37 +3288,37 @@ module Gitlab end context 'returns errors if job artifacts:name is not an a string' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { name: 1 } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { name: 1 } } }) } it_behaves_like 'returns errors', 'jobs:rspec:artifacts name should be a string' end context 'returns errors if job artifacts:when is not an a predefined value' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { when: 1 } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { when: 1 } } }) } it_behaves_like 'returns errors', 'jobs:rspec:artifacts when should be one of: on_success, on_failure, always' end context 'returns errors if job artifacts:expire_in is not an a string' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { expire_in: 1 } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { expire_in: 1 } } }) } it_behaves_like 'returns errors', 'jobs:rspec:artifacts expire in should be a duration' end context 'returns errors if job artifacts:expire_in is not an a valid duration' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } }) } it_behaves_like 'returns errors', 'jobs:rspec:artifacts expire in should be a duration' end context 'returns errors if job artifacts:untracked is not an array of strings' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { untracked: "string" } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { untracked: "string" } } }) } it_behaves_like 'returns errors', 'jobs:rspec:artifacts untracked should be a boolean value' end context 'returns errors if job artifacts:paths is not an array of strings' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { paths: "string" } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", artifacts: { paths: "string" } } }) } it_behaves_like 'returns errors', 'jobs:rspec:artifacts paths should be an array of strings' end @@ -3342,49 +3342,49 @@ module Gitlab end context 'returns errors if job cache:key is not an a string' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { key: 1 } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { key: 1 } } }) } it_behaves_like 'returns errors', "jobs:rspec:cache:key should be a hash, a string or a symbol" end context 'returns errors if job cache:key:files is not an array of strings' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { key: { files: [1] } } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { key: { files: [1] } } } }) } it_behaves_like 'returns errors', 'jobs:rspec:cache:key:files config should be an array of strings' end context 'returns errors if job cache:key:files is an empty array' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { key: { files: [] } } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { key: { files: [] } } } }) } it_behaves_like 'returns errors', 'jobs:rspec:cache:key:files config requires at least 1 item' end context 'returns errors if job defines only cache:key:prefix' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { key: { prefix: 'prefix-key' } } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { key: { prefix: 'prefix-key' } } } }) } it_behaves_like 'returns errors', 'jobs:rspec:cache:key config missing required keys: files' end context 'returns errors if job cache:key:prefix is not an a string' do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { key: { prefix: 1, files: ['file'] } } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { key: { prefix: 1, files: ['file'] } } } }) } it_behaves_like 'returns errors', 'jobs:rspec:cache:key:prefix config should be a string or symbol' end context "returns errors if job cache:untracked is not an array of strings" do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { untracked: "string" } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { untracked: "string" } } }) } it_behaves_like 'returns errors', "jobs:rspec:cache:untracked config should be a boolean value" end context "returns errors if job cache:paths is not an array of strings" do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", cache: { paths: "string" } } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", cache: { paths: "string" } } }) } it_behaves_like 'returns errors', "jobs:rspec:cache:paths config should be an array of strings" end context "returns errors if job dependencies is not an array of strings" do - let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", dependencies: "string" } }) } + let(:config) { YAML.dump({ stages: %w[build test], rspec: { script: "test", dependencies: "string" } }) } it_behaves_like 'returns errors', "jobs:rspec dependencies should be an array of strings" end @@ -3433,7 +3433,24 @@ module Gitlab YAML end - it_behaves_like 'returns errors', 'The pipeline has circular dependencies' + it_behaves_like 'returns errors', 'The pipeline has circular dependencies: topological sort failed: ["job_a", "job_c", "job_b"]' + + context 'when a job has a self-dependency' do + let(:config) do + <<~YAML + job_0: + stage: test + script: build + + job: + stage: test + script: build + needs: [job_0, job] + YAML + end + + it_behaves_like 'returns errors', 'The pipeline has circular dependencies: self-dependency: job' + end end end @@ -3668,6 +3685,70 @@ module Gitlab it { is_expected.to be_valid } end end + + context 'for pages jobs', feature_category: :pages do + context 'on publish option' do + context 'when not in a pages job' do + let(:config) do + <<-EOYML + not-pages: + script: echo + publish: 'foo' + EOYML + end + + it_behaves_like 'returns errors', 'jobs:not-pages publish can only be used within a `pages` job' + end + + context 'when in a pages job' do + let(:config) do + <<-EOYML + pages: + script: echo + publish: 'foo' + EOYML + end + + it { is_expected.to be_valid } + + it 'sets the publish configuration' do + expect(subject.builds.first[:options][:publish]).to eq('foo') + end + end + end + + context 'on pages option' do + context 'when not in a pages job' do + let(:config) do + <<-EOYML + not-pages: + script: echo + pages: + path_prefix: 'foo' + EOYML + end + + it_behaves_like 'returns errors', 'jobs:not-pages pages can only be used within a `pages` job' + end + + context 'when in a pages job' do + let(:config) do + <<-EOYML + pages: + script: echo + pages: + path_prefix: 'foo' + EOYML + end + + it { is_expected.to be_valid } + + it 'sets the pages configuration' do + expect(subject.builds.first[:options][:pages]).to eq(path_prefix: 'foo') + end + end + end + end end end end diff --git a/spec/lib/gitlab/composer/version_index_spec.rb b/spec/lib/gitlab/composer/version_index_spec.rb index 63efa8cae95..c5bc6dc0195 100644 --- a/spec/lib/gitlab/composer/version_index_spec.rb +++ b/spec/lib/gitlab/composer/version_index_spec.rb @@ -83,32 +83,6 @@ RSpec.describe Gitlab::Composer::VersionIndex, feature_category: :package_regist it_behaves_like 'returns the packages json' end - - context 'with composer_use_ssh_source_urls disabled' do - before do - stub_feature_flags(composer_use_ssh_source_urls: false) - end - - context 'with a public project' do - it_behaves_like 'returns the packages json' - end - - context 'with an internal project' do - before do - project.update!(visibility: Gitlab::VisibilityLevel::INTERNAL) - end - - it_behaves_like 'returns the packages json' - end - - context 'with a private project' do - before do - project.update!(visibility: Gitlab::VisibilityLevel::PRIVATE) - end - - it_behaves_like 'returns the packages json' - end - end end describe '#sha' do diff --git a/spec/lib/gitlab/config/entry/factory_spec.rb b/spec/lib/gitlab/config/entry/factory_spec.rb index be4dfd31651..bbbba0cf7cd 100644 --- a/spec/lib/gitlab/config/entry/factory_spec.rb +++ b/spec/lib/gitlab/config/entry/factory_spec.rb @@ -21,16 +21,16 @@ RSpec.describe Gitlab::Config::Entry::Factory do context 'when setting a concrete value' do it 'creates entry with valid value' do entry = factory - .value(%w(ls pwd)) + .value(%w[ls pwd]) .create! - expect(entry.value).to eq %w(ls pwd) + expect(entry.value).to eq %w[ls pwd] end context 'when setting description' do before do factory - .value(%w(ls pwd)) + .value(%w[ls pwd]) .with(description: 'test description') end @@ -41,7 +41,7 @@ RSpec.describe Gitlab::Config::Entry::Factory do it 'creates entry with description' do entry = factory.create! - expect(entry.value).to eq %w(ls pwd) + expect(entry.value).to eq %w[ls pwd] expect(entry.description).to eq 'test description' end end @@ -49,7 +49,7 @@ RSpec.describe Gitlab::Config::Entry::Factory do context 'when setting inherit' do before do factory - .value(%w(ls pwd)) + .value(%w[ls pwd]) .with(inherit: true) end @@ -61,7 +61,7 @@ RSpec.describe Gitlab::Config::Entry::Factory do context 'when setting key' do it 'creates entry with custom key' do entry = factory - .value(%w(ls pwd)) + .value(%w[ls pwd]) .with(key: 'test key') .create! diff --git a/spec/lib/gitlab/config/entry/validators_spec.rb b/spec/lib/gitlab/config/entry/validators_spec.rb index 6fa9f9d0767..e13c09f97ca 100644 --- a/spec/lib/gitlab/config/entry/validators_spec.rb +++ b/spec/lib/gitlab/config/entry/validators_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Gitlab::Config::Entry::Validators, feature_category: :pipeline_co expect(instance.valid?).to be(valid_result) unless valid_result - expect(instance.errors.messages_for(:config)).to include /please use only one of the following keys: foo, bar/ + expect(instance.errors.messages_for(:config)).to include(/these keys cannot be used together: foo, bar/) end end end diff --git a/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb b/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb deleted file mode 100644 index afee3c5536e..00000000000 --- a/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::ConfigChecker::PumaRuggedChecker do - describe '#check' do - subject { described_class.check } - - context 'application is not puma' do - before do - allow(Gitlab::Runtime).to receive(:puma?).and_return(false) - end - - it { is_expected.to be_empty } - end - - context 'application is puma' do - let(:notice_multi_threaded_puma_with_rugged) do - { - type: 'warning', - message: 'Puma is running with a thread count above 1 and the Rugged '\ - 'service is enabled. This may decrease performance in some environments. '\ - 'See our <a href="https://docs.gitlab.com/ee/administration/operations/puma.html#performance-caveat-when-using-puma-with-rugged">documentation</a> '\ - 'for details of this issue.' - } - end - - before do - allow(Gitlab::Runtime).to receive(:puma?).and_return(true) - allow(described_class).to receive(:running_puma_with_multiple_threads?).and_return(multithreaded_puma) - allow(described_class).to receive(:rugged_enabled_through_feature_flag?).and_return(rugged_enabled) - end - - context 'not multithreaded_puma and rugged API enabled' do - let(:multithreaded_puma) { false } - let(:rugged_enabled) { true } - - it { is_expected.to be_empty } - end - - context 'not multithreaded_puma and rugged API is not enabled' do - let(:multithreaded_puma) { false } - let(:rugged_enabled) { false } - - it { is_expected.to be_empty } - end - - context 'multithreaded_puma and rugged API is not enabled' do - let(:multithreaded_puma) { true } - let(:rugged_enabled) { false } - - it { is_expected.to be_empty } - end - - context 'multithreaded_puma and rugged API is enabled' do - let(:multithreaded_puma) { true } - let(:rugged_enabled) { true } - - it 'report multi_threaded_puma_with_rugged notices' do - is_expected.to contain_exactly(notice_multi_threaded_puma_with_rugged) - end - end - end - end -end diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb index 6ea8e6c6706..49252a6537c 100644 --- a/spec/lib/gitlab/conflict/file_spec.rb +++ b/spec/lib/gitlab/conflict/file_spec.rb @@ -58,7 +58,7 @@ RSpec.describe Gitlab::Conflict::File do it 'returns a file containing only the chosen parts of the resolved sections' do expect(resolved_lines.chunk { |line| line.type || 'both' }.map(&:first)) - .to eq(%w(both new both old both new both)) + .to eq(%w[both new both old both new both]) end end @@ -193,7 +193,7 @@ RSpec.describe Gitlab::Conflict::File do it 'sets conflict to true for sections with only changed lines' do conflict_file.sections.select { |section| section[:conflict] }.each do |section| section[:lines].each do |line| - expect(line.type).to be_in(%w(new old)) + expect(line.type).to be_in(%w[new old]) end end end diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb index 66890315ee8..7afd16f53e5 100644 --- a/spec/lib/gitlab/data_builder/build_spec.rb +++ b/spec/lib/gitlab/data_builder/build_spec.rb @@ -49,7 +49,7 @@ RSpec.describe Gitlab::DataBuilder::Build, feature_category: :integrations do it { expect(data[:commit][:id]).to eq(ci_build.pipeline.id) } it { expect(data[:runner][:id]).to eq(ci_build.runner.id) } - it { expect(data[:runner][:tags]).to match_array(%w(tag1 tag2)) } + it { expect(data[:runner][:tags]).to match_array(%w[tag1 tag2]) } it { expect(data[:runner][:description]).to eq(ci_build.runner.description) } it { expect(data[:runner][:runner_type]).to eq(ci_build.runner.runner_type) } it { expect(data[:runner][:is_shared]).to eq(ci_build.runner.instance_type?) } diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb index 351872ffbc5..ad7cd2dc736 100644 --- a/spec/lib/gitlab/data_builder/pipeline_spec.rb +++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb @@ -66,7 +66,7 @@ RSpec.describe Gitlab::DataBuilder::Pipeline, feature_category: :continuous_inte end context 'build with runner' do - let_it_be(:tag_names) { %w(tag-1 tag-2) } + let_it_be(:tag_names) { %w[tag-1 tag-2] } let_it_be(:ci_runner) { create(:ci_runner, tag_list: tag_names.map { |n| ActsAsTaggableOn::Tag.create!(name: n) }) } let_it_be(:build) { create(:ci_build, pipeline: pipeline, runner: ci_runner) } diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb index a3dd4e49e83..02dc596c5eb 100644 --- a/spec/lib/gitlab/data_builder/push_spec.rb +++ b/spec/lib/gitlab/data_builder/push_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Gitlab::DataBuilder::Push do it 'returns commit hook data' do expect(subject[:project]).to eq(project.hook_attrs) - expect(subject[:commits].first.keys).to include(*%i(added removed modified)) + expect(subject[:commits].first.keys).to include(*%i[added removed modified]) end end @@ -35,7 +35,7 @@ RSpec.describe Gitlab::DataBuilder::Push do it 'returns commit hook data without include deltas' do expect(subject[:project]).to eq(project.hook_attrs) - expect(subject[:commits].first.keys).not_to include(*%i(added removed modified)) + expect(subject[:commits].first.keys).not_to include(*%i[added removed modified]) end end end diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb index d9b81a2be30..e1d1674d05c 100644 --- a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb @@ -25,7 +25,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d describe 'state machine' do let_it_be(:job) { create(:batched_background_migration_job, :failed) } - it { expect(described_class.state_machine.states.map(&:name)).to eql(%i(pending running failed succeeded)) } + it { expect(described_class.state_machine.states.map(&:name)).to eql(%i[pending running failed succeeded]) } context 'when a job is running' do it 'logs the transition' do diff --git a/spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb index 59f4f40c0ef..7cf7be8ffc2 100644 --- a/spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_job_transition_log_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJobTransitionLog, t it { is_expected.to validate_presence_of(:batched_job) } it { is_expected.to validate_length_of(:exception_class).is_at_most(100) } it { is_expected.to validate_length_of(:exception_message).is_at_most(1000) } - it { is_expected.to define_enum_for(:previous_status).with_values(%i(pending running failed succeeded)).with_prefix } - it { is_expected.to define_enum_for(:next_status).with_values(%i(pending running failed succeeded)).with_prefix } + it { is_expected.to define_enum_for(:previous_status).with_values(%i[pending running failed succeeded]).with_prefix } + it { is_expected.to define_enum_for(:next_status).with_values(%i[pending running failed succeeded]).with_prefix } end end diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb index 213dee0d19d..f70b38377d8 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb @@ -422,11 +422,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m describe '#create_batched_job!' do let(:batched_migration) do - create(:batched_background_migration, - batch_size: 999, - sub_batch_size: 99, - pause_ms: 250 - ) + create( + :batched_background_migration, + batch_size: 999, + sub_batch_size: 99, + pause_ms: 250 + ) end it 'creates a batched_job with the correct batch configuration' do diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb index 8d74d16f4e5..bbcb65109ce 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb @@ -32,17 +32,17 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, ' end it 'runs the migration job' do - expect(job_class).to receive(:new) - .with(start_id: 1, - end_id: 10, - batch_table: 'events', - batch_column: 'id', - sub_batch_size: 1, - pause_ms: pause_ms, - job_arguments: active_migration.job_arguments, - connection: connection, - sub_batch_exception: sub_batch_exception) - .and_return(job_instance) + expect(job_class).to receive(:new).with( + start_id: 1, + end_id: 10, + batch_table: 'events', + batch_column: 'id', + sub_batch_size: 1, + pause_ms: pause_ms, + job_arguments: active_migration.job_arguments, + connection: connection, + sub_batch_exception: sub_batch_exception + ).and_return(job_instance) expect(job_instance).to receive(:perform).with(no_args) diff --git a/spec/lib/gitlab/database/background_migration/prometheus_metrics_spec.rb b/spec/lib/gitlab/database/background_migration/prometheus_metrics_spec.rb index 1f256de35ec..8f380a8229c 100644 --- a/spec/lib/gitlab/database/background_migration/prometheus_metrics_spec.rb +++ b/spec/lib/gitlab/database/background_migration/prometheus_metrics_spec.rb @@ -5,11 +5,14 @@ require 'spec_helper' RSpec.describe Gitlab::Database::BackgroundMigration::PrometheusMetrics, :prometheus do describe '#track' do let(:job_record) do - build(:batched_background_migration_job, :succeeded, - started_at: Time.current - 2.minutes, - finished_at: Time.current - 1.minute, - updated_at: Time.current, - metrics: { 'timings' => { 'update_all' => [0.05, 0.2, 0.4, 0.9, 4] } }) + build( + :batched_background_migration_job, + :succeeded, + started_at: Time.current - 2.minutes, + finished_at: Time.current - 1.minute, + updated_at: Time.current, + metrics: { 'timings' => { 'update_all' => [0.05, 0.2, 0.4, 0.9, 4] } } + ) end let(:labels) { job_record.batched_migration.prometheus_labels } diff --git a/spec/lib/gitlab/database/bulk_update_spec.rb b/spec/lib/gitlab/database/bulk_update_spec.rb index fa519cffd6b..2f0859dba74 100644 --- a/spec/lib/gitlab/database/bulk_update_spec.rb +++ b/spec/lib/gitlab/database/bulk_update_spec.rb @@ -92,7 +92,7 @@ RSpec.describe Gitlab::Database::BulkUpdate do end context 'validates prepared_statements support', :reestablished_active_record_base, - :suppress_gitlab_schemas_validate_connection do + :suppress_gitlab_schemas_validate_connection do using RSpec::Parameterized::TableSyntax where(:prepared_statements) do diff --git a/spec/lib/gitlab/database/dictionary_spec.rb b/spec/lib/gitlab/database/dictionary_spec.rb new file mode 100644 index 00000000000..6d2de41468b --- /dev/null +++ b/spec/lib/gitlab/database/dictionary_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::Dictionary, feature_category: :database do + subject(:database_dictionary) { described_class.new(file_path) } + + context 'for a table' do + let(:file_path) { 'db/docs/application_settings.yml' } + + describe '#name_and_schema' do + it 'returns the name of the table and its gitlab schema' do + expect(database_dictionary.name_and_schema).to match_array(['application_settings', :gitlab_main_clusterwide]) + end + end + + describe '#table_name' do + it 'returns the name of the table' do + expect(database_dictionary.table_name).to eq('application_settings') + end + end + + describe '#view_name' do + it 'returns nil' do + expect(database_dictionary.view_name).to be_nil + end + end + + describe '#milestone' do + it 'returns the milestone in which the table was introduced' do + expect(database_dictionary.milestone).to eq('7.7') + end + end + + describe '#gitlab_schema' do + it 'returns the gitlab_schema of the table' do + expect(database_dictionary.table_name).to eq('application_settings') + end + end + + describe '#schema?' do + it 'checks if the given schema matches the schema of the table' do + expect(database_dictionary.schema?('gitlab_main')).to eq(false) + expect(database_dictionary.schema?('gitlab_main_clusterwide')).to eq(true) + end + end + + describe '#key_name' do + it 'returns the value of the name of the table' do + expect(database_dictionary.key_name).to eq('application_settings') + end + end + + describe '#validate!' do + it 'raises an error if the gitlab_schema is empty' do + allow(database_dictionary).to receive(:gitlab_schema).and_return(nil) + + expect { database_dictionary.validate! }.to raise_error(Gitlab::Database::GitlabSchema::UnknownSchemaError) + end + end + end + + context 'for a view' do + let(:file_path) { 'db/docs/views/postgres_constraints.yml' } + + describe '#table_name' do + it 'returns nil' do + expect(database_dictionary.table_name).to be_nil + end + end + + describe '#view_name' do + it 'returns the name of the view' do + expect(database_dictionary.view_name).to eq('postgres_constraints') + end + end + + describe '#key_name' do + it 'returns the value of the name of the view' do + expect(database_dictionary.key_name).to eq('postgres_constraints') + end + end + end +end diff --git a/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb b/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb index fe423b3639b..7ab50d47408 100644 --- a/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb +++ b/spec/lib/gitlab/database/dynamic_model_helpers_spec.rb @@ -2,28 +2,83 @@ require 'spec_helper' -RSpec.describe Gitlab::Database::DynamicModelHelpers do +RSpec.describe Gitlab::Database::DynamicModelHelpers, feature_category: :database do let(:including_class) { Class.new.include(described_class) } let(:table_name) { Project.table_name } let(:connection) { Project.connection } describe '#define_batchable_model' do - subject { including_class.new.define_batchable_model(table_name, connection: connection) } + subject(:model) { including_class.new.define_batchable_model(table_name, connection: connection) } it 'is an ActiveRecord model' do - expect(subject.ancestors).to include(ActiveRecord::Base) + expect(model.ancestors).to include(ActiveRecord::Base) end it 'includes EachBatch' do - expect(subject.included_modules).to include(EachBatch) + expect(model.included_modules).to include(EachBatch) end it 'has the correct table name' do - expect(subject.table_name).to eq(table_name) + expect(model.table_name).to eq(table_name) end it 'has the inheritance type column disable' do - expect(subject.inheritance_column).to eq('_type_disabled') + expect(model.inheritance_column).to eq('_type_disabled') + end + + context 'for primary key' do + subject(:model) do + including_class.new.define_batchable_model(table_name, connection: connection, primary_key: primary_key) + end + + context 'when table primary key is a single column' do + let(:primary_key) { nil } + + context 'when primary key is nil' do + it 'does not change the primary key from :id' do + expect(model.primary_key).to eq('id') + end + end + + context 'when primary key is not nil' do + let(:primary_key) { 'other_column' } + + it 'does not change the primary key from :id' do + expect(model.primary_key).to eq('id') + end + end + end + + context 'when table has composite primary key' do + let(:primary_key) { nil } + let(:table_name) { :_test_composite_primary_key } + + before do + connection.execute(<<~SQL) + DROP TABLE IF EXISTS #{table_name}; + + CREATE TABLE #{table_name} ( + id integer NOT NULL, + partition_id integer NOT NULL, + PRIMARY KEY (id, partition_id) + ); + SQL + end + + context 'when primary key is nil' do + it 'does not change the primary key from nil' do + expect(model.primary_key).to be_nil + end + end + + context 'when primary key is not nil' do + let(:primary_key) { 'id' } + + it 'changes the primary key' do + expect(model.primary_key).to eq('id') + end + end + end end end diff --git a/spec/lib/gitlab/database/gitlab_schema_spec.rb b/spec/lib/gitlab/database/gitlab_schema_spec.rb index a6de695c345..a47e53c18a5 100644 --- a/spec/lib/gitlab/database/gitlab_schema_spec.rb +++ b/spec/lib/gitlab/database/gitlab_schema_spec.rb @@ -95,10 +95,10 @@ RSpec.describe Gitlab::Database::GitlabSchema, feature_category: :database do # ignore gitlab_internal due to `ar_internal_metadata`, `schema_migrations` table_and_view_names = table_and_view_names - .reject { |_, gitlab_schema| gitlab_schema == :gitlab_internal } + .reject { |database_dictionary| database_dictionary.schema?('gitlab_internal') } duplicated_tables = table_and_view_names - .group_by(&:first) + .group_by(&:key_name) .select { |_, schemas| schemas.count > 1 } .keys diff --git a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb index 552df64096a..1824a50cb28 100644 --- a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb +++ b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb @@ -8,14 +8,14 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do it 'all definitions have assigned a known gitlab_schema and on_delete' do is_expected.to all(have_attributes( - options: a_hash_including( - column: be_a(String), - gitlab_schema: be_in(Gitlab::Database.schemas_to_base_models.symbolize_keys.keys), - on_delete: be_in([:async_delete, :async_nullify]) - ), - from_table: be_a(String), - to_table: be_a(String) - )) + options: a_hash_including( + column: be_a(String), + gitlab_schema: be_in(Gitlab::Database.schemas_to_base_models.symbolize_keys.keys), + on_delete: be_in([:async_delete, :async_nullify]) + ), + from_table: be_a(String), + to_table: be_a(String) + )) end context 'ensure keys are sorted' do diff --git a/spec/lib/gitlab/database/migration_helpers/cascading_namespace_settings_spec.rb b/spec/lib/gitlab/database/migration_helpers/cascading_namespace_settings_spec.rb index e11ffe53c61..fb3da38a7be 100644 --- a/spec/lib/gitlab/database/migration_helpers/cascading_namespace_settings_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers/cascading_namespace_settings_spec.rb @@ -27,7 +27,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::CascadingNamespaceSettings do it 'raises an error when some columns already exist' do expect do migration.add_cascading_namespace_setting(:cascading_setting, :integer) - end.to raise_error %r/Existing columns: namespace_settings.cascading_setting, application_settings.lock_cascading_setting/ + end.to raise_error %r{Existing columns: namespace_settings.cascading_setting, application_settings.lock_cascading_setting} end end end diff --git a/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb b/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb index 1ff157b51d4..b0384a37746 100644 --- a/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::MigrationHelpers::ConvertToBigint, feature_category: :database do let(:migration) do Class - .new + .new(Gitlab::Database::Migration[2.1]) .include(described_class) .include(Gitlab::Database::MigrationHelpers) .new @@ -73,4 +73,135 @@ RSpec.describe Gitlab::Database::MigrationHelpers::ConvertToBigint, feature_cate expect(migration.columns_swapped?(:test_table, :id)).to eq(false) end end + + describe '#add_bigint_column_indexes' do + let(:connection) { migration.connection } + + let(:table_name) { '_test_table_bigint_indexes' } + let(:int_column) { 'token' } + let(:bigint_column) { 'token_convert_to_bigint' } + + subject(:add_bigint_column_indexes) { migration.add_bigint_column_indexes(table_name, int_column) } + + before do + connection.execute(<<~SQL) + CREATE TABLE IF NOT EXISTS public.#{table_name} ( + name varchar(40), + #{int_column} integer + ); + SQL + + allow(migration).to receive(:transaction_open?).and_return(false) + allow(migration).to receive(:disable_statement_timeout).and_call_original + end + + after do + connection.execute("DROP TABLE IF EXISTS #{table_name}") + end + + context 'without corresponding bigint column' do + let(:error_msg) { "Bigint column '#{bigint_column}' does not exist on #{table_name}" } + + it { expect { subject }.to raise_error(RuntimeError, error_msg) } + end + + context 'with corresponding bigint column' do + let(:indexes) { connection.indexes(table_name) } + let(:int_column_indexes) { indexes.select { |i| i.columns.include?(int_column) } } + let(:bigint_column_indexes) { indexes.select { |i| i.columns.include?(bigint_column) } } + + before do + connection.execute("ALTER TABLE #{table_name} ADD COLUMN #{bigint_column} bigint") + end + + context 'without the integer column index' do + it 'does not create new bigint index' do + expect(int_column_indexes).to be_empty + + add_bigint_column_indexes + + expect(bigint_column_indexes).to be_empty + end + end + + context 'with integer column indexes' do + let(:bigint_index_name) { ->(int_index_name) { migration.bigint_index_name(int_index_name) } } + let(:expected_bigint_indexes) do + [ + { + name: bigint_index_name.call("hash_idx_#{table_name}"), + column: [bigint_column], + using: 'hash' + }, + { + name: bigint_index_name.call("idx_#{table_name}"), + column: [bigint_column], + using: 'btree' + }, + { + name: bigint_index_name.call("idx_#{table_name}_combined"), + column: "#{bigint_column}, lower((name)::text)", + where: "(#{bigint_column} IS NOT NULL)", + using: 'btree' + }, + { + name: bigint_index_name.call("idx_#{table_name}_functional"), + column: "#{bigint_column}, lower((name)::text)", + using: 'btree' + }, + { + name: bigint_index_name.call("idx_#{table_name}_ordered"), + column: [bigint_column], + order: 'DESC NULLS LAST', + using: 'btree' + }, + { + name: bigint_index_name.call("idx_#{table_name}_ordered_multiple"), + column: [bigint_column, 'name'], + order: { bigint_column => 'DESC NULLS LAST', 'name' => 'desc' }, + using: 'btree' + }, + { + name: bigint_index_name.call("idx_#{table_name}_partial"), + column: [bigint_column], + where: "(#{bigint_column} IS NOT NULL)", + using: 'btree' + }, + { + name: bigint_index_name.call("uniq_idx_#{table_name}"), + column: [bigint_column], + unique: true, + using: 'btree' + } + ] + end + + before do + connection.execute(<<~SQL) + CREATE INDEX "hash_idx_#{table_name}" ON #{table_name} USING hash (#{int_column}); + CREATE INDEX "idx_#{table_name}" ON #{table_name} USING btree (#{int_column}); + CREATE INDEX "idx_#{table_name}_combined" ON #{table_name} USING btree (#{int_column}, lower((name)::text)) WHERE (#{int_column} IS NOT NULL); + CREATE INDEX "idx_#{table_name}_functional" ON #{table_name} USING btree (#{int_column}, lower((name)::text)); + CREATE INDEX "idx_#{table_name}_ordered" ON #{table_name} USING btree (#{int_column} DESC NULLS LAST); + CREATE INDEX "idx_#{table_name}_ordered_multiple" ON #{table_name} USING btree (#{int_column} DESC NULLS LAST, name DESC); + CREATE INDEX "idx_#{table_name}_partial" ON #{table_name} USING btree (#{int_column}) WHERE (#{int_column} IS NOT NULL); + CREATE UNIQUE INDEX "uniq_idx_#{table_name}" ON #{table_name} USING btree (#{int_column}); + SQL + end + + it 'creates appropriate bigint indexes' do + expected_bigint_indexes.each do |bigint_index| + expect(migration).to receive(:add_concurrent_index).with( + table_name, + bigint_index[:column], + name: bigint_index[:name], + ** bigint_index.except(:name, :column) + ) + end + + add_bigint_column_indexes + end + end + end + end end diff --git a/spec/lib/gitlab/database/migration_helpers/wraparound_autovacuum_spec.rb b/spec/lib/gitlab/database/migration_helpers/wraparound_autovacuum_spec.rb index 1cc4ff6891c..b88d26100c9 100644 --- a/spec/lib/gitlab/database/migration_helpers/wraparound_autovacuum_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers/wraparound_autovacuum_spec.rb @@ -14,20 +14,30 @@ RSpec.describe Gitlab::Database::MigrationHelpers::WraparoundAutovacuum, feature describe '#can_execute_on?' do using RSpec::Parameterized::TableSyntax - where(:dot_com, :dev_or_test, :wraparound_prevention, :expectation) do - true | true | true | false - true | false | true | false - false | true | true | false - false | false | true | false - true | true | false | true - true | false | false | true - false | true | false | true - false | false | false | false + where(:dot_com, :jh, :dev_or_test, :wraparound_prevention, :expectation) do + true | true | true | true | false + true | true | false | true | false + false | true | true | true | false + false | true | false | true | false + true | true | true | false | true + true | true | false | false | false + false | true | true | false | true + false | true | false | false | false + + true | false | true | true | false + true | false | false | true | false + false | false | true | true | false + false | false | false | true | false + true | false | true | false | true + true | false | false | false | true + false | false | true | false | true + false | false | false | false | false end with_them do - it 'returns true for GitLab.com, dev, or test' do + it 'returns as expected for GitLab.com, dev, or test' do allow(Gitlab).to receive(:com?).and_return(dot_com) + allow(Gitlab).to receive(:jh?).and_return(jh) allow(Gitlab).to receive(:dev_or_test_env?).and_return(dev_or_test) allow(migration).to receive(:wraparound_prevention_on_tables?).with([:table]).and_return(wraparound_prevention) diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index dd51cca688c..8bf05f56b3f 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -118,7 +118,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d it 'cannot add unacceptable column names' do expect do model.add_timestamps_with_timezone(:foo, columns: [:bar]) - end.to raise_error %r/Illegal timestamp column name/ + end.to raise_error %r{Illegal timestamp column name} end end @@ -1753,8 +1753,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d describe '#indexes_for' do it 'returns the indexes for a column' do - idx1 = double(:idx, columns: %w(project_id)) - idx2 = double(:idx, columns: %w(user_id)) + idx1 = double(:idx, columns: %w[project_id]) + idx2 = double(:idx, columns: %w[user_id]) allow(model).to receive(:indexes).with('table').and_return([idx1, idx2]) @@ -1777,7 +1777,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d context 'when index name is too long' do it 'does not fail' do index = double(:index, - columns: %w(uuid), + columns: %w[uuid], name: 'index_vuln_findings_on_uuid_including_vuln_id_1', using: nil, where: nil, @@ -1791,7 +1791,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d expect(model).to receive(:add_concurrent_index) .with(:vulnerability_occurrences, - %w(tmp_undo_cleanup_column_8cbf300838), + %w[tmp_undo_cleanup_column_8cbf300838], { unique: true, name: 'idx_copy_191a1af1a0', @@ -1806,7 +1806,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d context 'using a regular index using a single column' do it 'copies the index' do index = double(:index, - columns: %w(project_id), + columns: %w[project_id], name: 'index_on_issues_project_id', using: nil, where: nil, @@ -1820,7 +1820,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d expect(model).to receive(:add_concurrent_index) .with(:issues, - %w(gl_project_id), + %w[gl_project_id], { unique: false, name: 'index_on_issues_gl_project_id', @@ -1835,7 +1835,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d context 'using a regular index with multiple columns' do it 'copies the index' do index = double(:index, - columns: %w(project_id foobar), + columns: %w[project_id foobar], name: 'index_on_issues_project_id_foobar', using: nil, where: nil, @@ -1849,7 +1849,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d expect(model).to receive(:add_concurrent_index) .with(:issues, - %w(gl_project_id foobar), + %w[gl_project_id foobar], { unique: false, name: 'index_on_issues_gl_project_id_foobar', @@ -1864,7 +1864,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d context 'using an index with a WHERE clause' do it 'copies the index' do index = double(:index, - columns: %w(project_id), + columns: %w[project_id], name: 'index_on_issues_project_id', using: nil, where: 'foo', @@ -1878,7 +1878,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d expect(model).to receive(:add_concurrent_index) .with(:issues, - %w(gl_project_id), + %w[gl_project_id], { unique: false, name: 'index_on_issues_gl_project_id', @@ -1894,7 +1894,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d context 'using an index with a USING clause' do it 'copies the index' do index = double(:index, - columns: %w(project_id), + columns: %w[project_id], name: 'index_on_issues_project_id', where: nil, using: 'foo', @@ -1908,7 +1908,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d expect(model).to receive(:add_concurrent_index) .with(:issues, - %w(gl_project_id), + %w[gl_project_id], { unique: false, name: 'index_on_issues_gl_project_id', @@ -1924,7 +1924,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d context 'using an index with custom operator classes' do it 'copies the index' do index = double(:index, - columns: %w(project_id), + columns: %w[project_id], name: 'index_on_issues_project_id', using: nil, where: nil, @@ -1938,7 +1938,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d expect(model).to receive(:add_concurrent_index) .with(:issues, - %w(gl_project_id), + %w[gl_project_id], { unique: false, name: 'index_on_issues_gl_project_id', @@ -1955,7 +1955,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d it 'copies the index' do index = double(:index, { - columns: %w(project_id foobar), + columns: %w[project_id foobar], name: 'index_on_issues_project_id_foobar', using: :gin, where: nil, @@ -1970,7 +1970,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d expect(model).to receive(:add_concurrent_index) .with(:issues, - %w(gl_project_id foobar), + %w[gl_project_id foobar], { unique: false, name: 'index_on_issues_gl_project_id_foobar', @@ -1988,7 +1988,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d it 'copies the index' do index = double(:index, { - columns: %w(project_id foobar), + columns: %w[project_id foobar], name: 'index_on_issues_project_id_foobar', using: :gin, where: nil, @@ -2003,7 +2003,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d expect(model).to receive(:add_concurrent_index) .with(:issues, - %w(gl_project_id foobar), + %w[gl_project_id foobar], { unique: false, name: 'index_on_issues_gl_project_id_foobar', @@ -2020,7 +2020,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d describe 'using an index of which the name does not contain the source column' do it 'raises RuntimeError' do index = double(:index, - columns: %w(project_id), + columns: %w[project_id], name: 'index_foobar_index', using: nil, where: nil, diff --git a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb index f1271f2434c..a81ccf9583a 100644 --- a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb @@ -443,10 +443,10 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers, describe '#ensure_batched_background_migration_is_finished' do let(:job_class_name) { 'CopyColumnUsingBackgroundMigrationJob' } - let(:table_name) { 'events' } + let(:table_name) { '_test_table' } let(:column_name) { :id } let(:job_arguments) { [["id"], ["id_convert_to_bigint"], nil] } - let(:gitlab_schema) { Gitlab::Database::GitlabSchema.table_schema!(table_name) } + let(:gitlab_schema) { :gitlab_main } let(:configuration) do { @@ -484,7 +484,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers, "\n\n" \ "Finalize it manually by running the following command in a `bash` or `sh` shell:" \ "\n\n" \ - "\tsudo gitlab-rake gitlab:background_migrations:finalize[CopyColumnUsingBackgroundMigrationJob,events,id,'[[\"id\"]\\,[\"id_convert_to_bigint\"]\\,null]']" \ + "\tsudo gitlab-rake gitlab:background_migrations:finalize[CopyColumnUsingBackgroundMigrationJob,_test_table,id,'[[\"id\"]\\,[\"id_convert_to_bigint\"]\\,null]']" \ "\n\n" \ "For more information, check the documentation" \ "\n\n" \ diff --git a/spec/lib/gitlab/database/migrations/constraints_helpers_spec.rb b/spec/lib/gitlab/database/migrations/constraints_helpers_spec.rb index 476b5f3a784..4d7c51a3fbf 100644 --- a/spec/lib/gitlab/database/migrations/constraints_helpers_spec.rb +++ b/spec/lib/gitlab/database/migrations/constraints_helpers_spec.rb @@ -13,9 +13,11 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do describe '#check_constraint_name' do it 'returns a valid constraint name' do - name = model.check_constraint_name(:this_is_a_very_long_table_name, - :with_a_very_long_column_name, - :with_a_very_long_type) + name = model.check_constraint_name( + :this_is_a_very_long_table_name, + :with_a_very_long_column_name, + :with_a_very_long_type + ) expect(name).to be_an_instance_of(String) expect(name).to start_with('check_') @@ -404,9 +406,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do describe '#add_text_limit' do context 'when it is called with the default options' do it 'calls add_check_constraint with an infered constraint name and validate: true' do - constraint_name = model.check_constraint_name(:test_table, - :name, - 'max_length') + constraint_name = model.check_constraint_name(:test_table, :name, 'max_length') check = "char_length(name) <= 255" expect(model).to receive(:check_constraint_name).and_call_original @@ -440,9 +440,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do describe '#validate_text_limit' do context 'when constraint_name is not provided' do it 'calls validate_check_constraint with an infered constraint name' do - constraint_name = model.check_constraint_name(:test_table, - :name, - 'max_length') + constraint_name = model.check_constraint_name(:test_table, :name, 'max_length') expect(model).to receive(:check_constraint_name).and_call_original expect(model).to receive(:validate_check_constraint) @@ -468,9 +466,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do describe '#remove_text_limit' do context 'when constraint_name is not provided' do it 'calls remove_check_constraint with an infered constraint name' do - constraint_name = model.check_constraint_name(:test_table, - :name, - 'max_length') + constraint_name = model.check_constraint_name(:test_table, :name, 'max_length') expect(model).to receive(:check_constraint_name).and_call_original expect(model).to receive(:remove_check_constraint) @@ -496,9 +492,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do describe '#check_text_limit_exists?' do context 'when constraint_name is not provided' do it 'calls check_constraint_exists? with an infered constraint name' do - constraint_name = model.check_constraint_name(:test_table, - :name, - 'max_length') + constraint_name = model.check_constraint_name(:test_table, :name, 'max_length') expect(model).to receive(:check_constraint_name).and_call_original expect(model).to receive(:check_constraint_exists?) @@ -524,9 +518,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do describe '#add_not_null_constraint' do context 'when it is called with the default options' do it 'calls add_check_constraint with an infered constraint name and validate: true' do - constraint_name = model.check_constraint_name(:test_table, - :name, - 'not_null') + constraint_name = model.check_constraint_name(:test_table, :name, 'not_null') check = "name IS NOT NULL" expect(model).to receive(:column_is_nullable?).and_return(true) @@ -571,9 +563,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do describe '#validate_not_null_constraint' do context 'when constraint_name is not provided' do it 'calls validate_check_constraint with an infered constraint name' do - constraint_name = model.check_constraint_name(:test_table, - :name, - 'not_null') + constraint_name = model.check_constraint_name(:test_table, :name, 'not_null') expect(model).to receive(:check_constraint_name).and_call_original expect(model).to receive(:validate_check_constraint) @@ -599,9 +589,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do describe '#remove_not_null_constraint' do context 'when constraint_name is not provided' do it 'calls remove_check_constraint with an infered constraint name' do - constraint_name = model.check_constraint_name(:test_table, - :name, - 'not_null') + constraint_name = model.check_constraint_name(:test_table, :name, 'not_null') expect(model).to receive(:check_constraint_name).and_call_original expect(model).to receive(:remove_check_constraint) @@ -627,9 +615,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do describe '#check_not_null_constraint_exists?' do context 'when constraint_name is not provided' do it 'calls check_constraint_exists? with an infered constraint name' do - constraint_name = model.check_constraint_name(:test_table, - :name, - 'not_null') + constraint_name = model.check_constraint_name(:test_table, :name, 'not_null') expect(model).to receive(:check_constraint_name).and_call_original expect(model).to receive(:check_constraint_exists?) diff --git a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb index a12e0909dc2..81368dde94d 100644 --- a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb +++ b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb @@ -75,8 +75,12 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do context 'on successful execution' do subject do - instrumentation.observe(version: migration_version, name: migration_name, - connection: connection, meta: migration_meta) {} + instrumentation.observe( + version: migration_version, + name: migration_name, + connection: connection, + meta: migration_meta + ) {} end it 'records a valid observation', :aggregate_failures do @@ -98,8 +102,12 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do with_them do subject(:observe) do - instrumentation.observe(version: migration_version, name: migration_name, - connection: connection, meta: migration_meta) { raise exception, error_message } + instrumentation.observe( + version: migration_version, + name: migration_name, + connection: connection, + meta: migration_meta + ) { raise exception, error_message } end context 'retrieving observations' do diff --git a/spec/lib/gitlab/database/migrations/milestone_mixin_spec.rb b/spec/lib/gitlab/database/migrations/milestone_mixin_spec.rb index e375af494a2..1ed5c846550 100644 --- a/spec/lib/gitlab/database/migrations/milestone_mixin_spec.rb +++ b/spec/lib/gitlab/database/migrations/milestone_mixin_spec.rb @@ -12,14 +12,11 @@ RSpec.describe Gitlab::Database::Migrations::MilestoneMixin, feature_category: : end let(:migration_mixin) do - Class.new(Gitlab::Database::Migration[2.1]) do - include Gitlab::Database::Migrations::MilestoneMixin - end + Class.new(Gitlab::Database::Migration[2.2]) end let(:migration_mixin_version) do - Class.new(Gitlab::Database::Migration[2.1]) do - include Gitlab::Database::Migrations::MilestoneMixin + Class.new(Gitlab::Database::Migration[2.2]) do milestone '16.4' end end @@ -44,5 +41,11 @@ RSpec.describe Gitlab::Database::Migrations::MilestoneMixin, feature_category: : expect { migration_mixin_version.new(4, 4, :regular) }.not_to raise_error end end + + context 'when initialize arguments are not provided' do + it "does not raise an error" do + expect { migration_mixin_version.new }.not_to raise_error + end + end end end diff --git a/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb b/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb index c6327de98d1..6e943307ae6 100644 --- a/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb +++ b/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Gitlab::Database::Migrations::ReestablishedConnectionStack do # establish connection ApplicationRecord.connection.select_one("SELECT 1 FROM projects LIMIT 1") - Ci::ApplicationRecord.connection.select_one("SELECT 1 FROM ci_builds LIMIT 1") + Ci::ApplicationRecord.connection.select_one("SELECT 1 FROM p_ci_builds LIMIT 1") end expect(new_handler).not_to eq(original_handler), "is reconnected" diff --git a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb index 31a194ae883..660c4347ffa 100644 --- a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb +++ b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb @@ -80,8 +80,11 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez end subject(:sample_migration) do - described_class.new(result_dir: result_dir, connection: connection, - from_id: from_id).run_jobs(for_duration: 1.minute) + described_class.new( + result_dir: result_dir, + connection: connection, + from_id: from_id + ).run_jobs(for_duration: 1.minute) end it 'runs sampled jobs from the batched background migration' do @@ -125,12 +128,19 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez calls << args end - queue_migration(migration_name, table_name, :id, - job_interval: 5.minutes, - batch_size: 100) + queue_migration( + migration_name, + table_name, + :id, + job_interval: 5.minutes, + batch_size: 100 + ) - described_class.new(result_dir: result_dir, connection: connection, - from_id: from_id).run_jobs(for_duration: 3.minutes) + described_class.new( + result_dir: result_dir, + connection: connection, + from_id: from_id + ).run_jobs(for_duration: 3.minutes) expect(calls).not_to be_empty end @@ -142,13 +152,19 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez calls << args end - queue_migration(migration_name, table_name, :id, - job_interval: 5.minutes, - batch_size: num_rows_in_table * 2, - sub_batch_size: num_rows_in_table * 2) + queue_migration( + migration_name, + table_name, :id, + job_interval: 5.minutes, + batch_size: num_rows_in_table * 2, + sub_batch_size: num_rows_in_table * 2 + ) - described_class.new(result_dir: result_dir, connection: connection, - from_id: from_id).run_jobs(for_duration: 3.minutes) + described_class.new( + result_dir: result_dir, + connection: connection, + from_id: from_id + ).run_jobs(for_duration: 3.minutes) expect(calls.size).to eq(1) end @@ -161,13 +177,20 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez calls << args end - queue_migration(migration_name, table_name, :id, - job_interval: 5.minutes, - batch_size: num_rows_in_table * 2, - sub_batch_size: num_rows_in_table * 2) - - described_class.new(result_dir: result_dir, connection: connection, - from_id: from_id).run_jobs(for_duration: 3.minutes) + queue_migration( + migration_name, + table_name, + :id, + job_interval: 5.minutes, + batch_size: num_rows_in_table * 2, + sub_batch_size: num_rows_in_table * 2 + ) + + described_class.new( + result_dir: result_dir, + connection: connection, + from_id: from_id + ).run_jobs(for_duration: 3.minutes) expect(calls.count).to eq(0) end @@ -181,26 +204,41 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez it 'runs all pending jobs based on the last migration id' do old_migration = define_background_migration(migration_name) - queue_migration(migration_name, table_name, :id, - job_interval: 5.minutes, - batch_size: 100) + queue_migration( + migration_name, + table_name, + :id, + job_interval: 5.minutes, + batch_size: 100 + ) last_id new_migration = define_background_migration('NewMigration') { travel 1.second } - queue_migration('NewMigration', table_name, :id, - job_interval: 5.minutes, - batch_size: 10, - sub_batch_size: 5) + queue_migration( + 'NewMigration', + table_name, + :id, + job_interval: 5.minutes, + batch_size: 10, + sub_batch_size: 5 + ) other_new_migration = define_background_migration('NewMigration2') { travel 2.seconds } - queue_migration('NewMigration2', table_name, :id, - job_interval: 5.minutes, - batch_size: 10, - sub_batch_size: 5) + queue_migration( + 'NewMigration2', + table_name, + :id, + job_interval: 5.minutes, + batch_size: 10, + sub_batch_size: 5 + ) expect_migration_runs(new_migration => 3, other_new_migration => 2, old_migration => 0) do - described_class.new(result_dir: result_dir, connection: connection, - from_id: last_id).run_jobs(for_duration: 5.seconds) + described_class.new( + result_dir: result_dir, + connection: connection, + from_id: last_id + ).run_jobs(for_duration: 5.seconds) end end end diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb index c6cd5e55754..c57b8bb5992 100644 --- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb +++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb @@ -11,6 +11,7 @@ RSpec.describe 'cross-database foreign keys' do # should be added as a comment along with the name of the column. let!(:allowed_cross_database_foreign_keys) do [ + 'events.author_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/429803 'gitlab_subscriptions.hosted_plan_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422012 'group_import_states.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/421210 'identities.saml_provider_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422010 @@ -18,11 +19,18 @@ RSpec.describe 'cross-database foreign keys' do 'issues.closed_by_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422154 'issues.updated_by_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422154 'issue_assignees.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422154 + 'lfs_file_locks.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/430838 'merge_requests.assignee_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080 'merge_requests.updated_by_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080 'merge_requests.merge_user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080 'merge_requests.author_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080 + 'namespace_commit_emails.email_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/429804 + 'namespace_commit_emails.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/429804 + 'path_locks.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/429380 'project_authorizations.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422044 + 'protected_branch_push_access_levels.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/431054 + 'protected_branch_merge_access_levels.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/431055 + 'security_orchestration_policy_configurations.bot_user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/429438 'user_group_callouts.user_id' # https://gitlab.com/gitlab-org/gitlab/-/issues/421287 ] end diff --git a/spec/lib/gitlab/database/no_new_tables_with_gitlab_main_schema_spec.rb b/spec/lib/gitlab/database/no_new_tables_with_gitlab_main_schema_spec.rb new file mode 100644 index 00000000000..338475fa9c4 --- /dev/null +++ b/spec/lib/gitlab/database/no_new_tables_with_gitlab_main_schema_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'new tables with gitlab_main schema', feature_category: :cell do + # During the development of Cells, we will be moving tables from the `gitlab_main` schema + # to either the `gitlab_main_clusterwide` or `gitlab_main_cell` schema. + # As part of this process, starting from milestone 16.7, it will be a mandatory requirement that + # all newly created tables are associated with one of these two schemas. + # Any attempt to set the `gitlab_main` schema for a new table will result in a failure of this spec. + + # Specific tables can be exempted from this requirement, and such tables must be added to the `exempted_tables` list. + let!(:exempted_tables) do + [] + end + + let!(:starting_from_milestone) { 16.7 } + + it 'only allows exempted tables to have `gitlab_main` as its schema, after milestone 16.7', :aggregate_failures do + tables_having_gitlab_main_schema(starting_from_milestone: starting_from_milestone).each do |table_name| + expect(exempted_tables).to include(table_name), error_message(table_name) + end + end + + it 'only allows tables having `gitlab_main` as its schema in `exempted_tables`', :aggregate_failures do + tables_having_gitlab_main_schema = gitlab_main_schema_tables.map(&:table_name) + + exempted_tables.each do |exempted_table| + expect(tables_having_gitlab_main_schema).to include(exempted_table), + "`#{exempted_table}` does not have `gitlab_main` as its schema. + Please remove this table from the `exempted_tables` list." + end + end + + private + + def error_message(table_name) + <<~HEREDOC + The table `#{table_name}` has been added with `gitlab_main` schema. + Starting from GitLab #{starting_from_milestone}, we expect new tables to use either the `gitlab_main_cell` or the + `gitlab_main_clusterwide` schema. + + To choose an appropriate schema for this table from among `gitlab_main_cell` and `gitlab_main_clusterwide`, please refer + to our guidelines at https://docs.gitlab.com/ee/development/database/multiple_databases.html#guidelines-on-choosing-between-gitlab_main_cell-and-gitlab_main_clusterwide-schema, or consult with the Tenant Scale group. + + Please see issue https://gitlab.com/gitlab-org/gitlab/-/issues/424990 to understand why this change is being enforced. + HEREDOC + end + + def tables_having_gitlab_main_schema(starting_from_milestone:) + selected_data = gitlab_main_schema_tables.select do |database_dictionary| + database_dictionary.milestone.to_f >= starting_from_milestone + end + + selected_data.map(&:table_name) + end + + def gitlab_main_schema_tables + ::Gitlab::Database::GitlabSchema.build_dictionary('').select do |database_dictionary| + database_dictionary.schema?('gitlab_main') + end + end +end diff --git a/spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb b/spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb index 04940028aee..eb78d836be0 100644 --- a/spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb +++ b/spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb @@ -57,17 +57,19 @@ RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do SQL end - Postgresql::DetachedPartition.create!(table_name: name, - drop_after: drop_after) + Postgresql::DetachedPartition.create!(table_name: name, drop_after: drop_after) end describe '#perform' do context 'when the partition should not be dropped yet' do it 'does not drop the partition' do - create_partition(name: :_test_partition, - from: 2.months.ago, to: 1.month.ago, - attached: false, - drop_after: 1.day.from_now) + create_partition( + name: :_test_partition, + from: 2.months.ago, + to: 1.month.ago, + attached: false, + drop_after: 1.day.from_now + ) dropper.perform @@ -77,11 +79,13 @@ RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do context 'with a partition to drop' do before do - create_partition(name: :_test_partition, - from: 2.months.ago, - to: 1.month.ago.beginning_of_month, - attached: false, - drop_after: 1.second.ago) + create_partition( + name: :_test_partition, + from: 2.months.ago, + to: 1.month.ago.beginning_of_month, + attached: false, + drop_after: 1.second.ago + ) end it 'drops the partition' do @@ -159,11 +163,13 @@ RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do context 'when the partition to drop is still attached to its table' do before do - create_partition(name: :_test_partition, - from: 2.months.ago, - to: 1.month.ago.beginning_of_month, - attached: true, - drop_after: 1.second.ago) + create_partition( + name: :_test_partition, + from: 2.months.ago, + to: 1.month.ago.beginning_of_month, + attached: true, + drop_after: 1.second.ago + ) end it 'does not drop the partition, but does remove the DetachedPartition entry' do @@ -192,17 +198,21 @@ RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do context 'with multiple partitions to drop' do before do - create_partition(name: :_test_partition_1, - from: 3.months.ago, - to: 2.months.ago, - attached: false, - drop_after: 1.second.ago) - - create_partition(name: :_test_partition_2, - from: 2.months.ago, - to: 1.month.ago, - attached: false, - drop_after: 1.second.ago) + create_partition( + name: :_test_partition_1, + from: 3.months.ago, + to: 2.months.ago, + attached: false, + drop_after: 1.second.ago + ) + + create_partition( + name: :_test_partition_2, + from: 2.months.ago, + to: 1.month.ago, + attached: false, + drop_after: 1.second.ago + ) end it 'drops both partitions' do diff --git a/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb b/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb index 3afa338fdf7..8b18e5b6d08 100644 --- a/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb +++ b/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb @@ -235,8 +235,12 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy, feature_category subject { described_class.new(model, partitioning_key, retain_for: 3.months).extra_partitions } it 'prunes the unbounded partition ending 2020-05-01' do - min_value_to_may = Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-05-01', - partition_name: '_test_partitioned_test_000000') + min_value_to_may = Gitlab::Database::Partitioning::TimePartition.new( + model.table_name, + nil, + '2020-05-01', + partition_name: '_test_partitioned_test_000000' + ) expect(subject).to contain_exactly(min_value_to_may) end @@ -247,8 +251,18 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy, feature_category it 'prunes the unbounded partition and the partition for May-June' do expect(subject).to contain_exactly( - Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-05-01', partition_name: '_test_partitioned_test_000000'), - Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01', partition_name: '_test_partitioned_test_202005') + Gitlab::Database::Partitioning::TimePartition.new( + model.table_name, + nil, + '2020-05-01', + partition_name: '_test_partitioned_test_000000' + ), + Gitlab::Database::Partitioning::TimePartition.new( + model.table_name, + '2020-05-01', + '2020-06-01', + partition_name: '_test_partitioned_test_202005' + ) ) end @@ -257,8 +271,18 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy, feature_category it 'prunes empty partitions' do expect(subject).to contain_exactly( - Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-05-01', partition_name: '_test_partitioned_test_000000'), - Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01', partition_name: '_test_partitioned_test_202005') + Gitlab::Database::Partitioning::TimePartition.new( + model.table_name, + nil, + '2020-05-01', + partition_name: '_test_partitioned_test_000000' + ), + Gitlab::Database::Partitioning::TimePartition.new( + model.table_name, + '2020-05-01', + '2020-06-01', + partition_name: '_test_partitioned_test_202005' + ) ) end diff --git a/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb b/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb index 80ffa708d8a..336cd2d912d 100644 --- a/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb +++ b/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager, feature_categor include ActiveSupport::Testing::TimeHelpers include Database::PartitioningHelpers include ExclusiveLeaseHelpers + using RSpec::Parameterized::TableSyntax let(:partitioned_table_name) { :_test_gitlab_main_my_model_example_table } @@ -107,14 +108,88 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager, feature_categor end end - before do - my_model.table_name = partitioned_table_name + context 'when single database is configured' do + before do + skip_if_database_exists(:ci) - create_partitioned_table(connection, partitioned_table_name) + my_model.table_name = partitioned_table_name + + create_partitioned_table(connection, partitioned_table_name) + end + + it 'creates partitions' do + expect { sync_partitions }.to change { find_partitions(my_model.table_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA).size }.from(0) + end end - it 'creates partitions' do - expect { sync_partitions }.to change { find_partitions(my_model.table_name, schema: Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA).size }.from(0) + context 'when multiple databases are configured' do + before do + skip_if_shared_database(:ci) + + my_model.table_name = partitioned_table_name + + create_partitioned_table(connection, partitioned_table_name) + + stub_feature_flags(automatic_lock_writes_on_partition_tables: ff_enabled) + + sync_partitions + end + + where(:gitlab_schema, :database, :expectation) do + :gitlab_main | :main | false + :gitlab_main | :ci | true + :gitlab_ci | :main | true + :gitlab_ci | :ci | false + end + with_them do + subject(:sync_partitions) { described_class.new(my_model, connection: connection).sync_partitions } + + let(:partitioned_table_name) { "_test_gitlab_#{database}_my_model_example_#{gitlab_schema}" } + let(:base_model) { Gitlab::Database.schemas_to_base_models[gitlab_schema].first } + let(:connection) { Gitlab::Database.database_base_models[database.to_s].connection } + + let(:my_model) do + Class.new(base_model) do + include PartitionedTable + + partitioned_by :created_at, strategy: :monthly + end + end + + let(:partitions) do + Gitlab::Database::PostgresPartition.using_connection(connection) { Gitlab::Database::PostgresPartition.for_parent_table(partitioned_table_name).to_a } + end + + let(:partitions_locked_for_writes?) do + partitions.map do |partition| + Gitlab::Database::LockWritesManager.new( + table_name: "#{partition.schema}.#{partition.name}", + connection: connection, + database_name: gitlab_schema + ).table_locked_for_writes? + end.all?(true) + end + + context 'when feature flag is enabled' do + let(:ff_enabled) { true } + + it "matches expectation" do + sync_partitions + + expect(partitions_locked_for_writes?).to eq(expectation) + end + end + + context 'when feature flag is disabled' do + let(:ff_enabled) { false } + + it "will not lock created partition" do + sync_partitions + + expect(partitions_locked_for_writes?).to eq(false) + end + end + end end end diff --git a/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb b/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb index ac4d345271e..9ca0a1b6e57 100644 --- a/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb +++ b/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb @@ -15,9 +15,12 @@ RSpec.describe Gitlab::Database::Partitioning::SlidingListStrategy, feature_cate let(:detach_partition_if) { double('detach_partition_if') } subject(:strategy) do - described_class.new(model, :partition, - next_partition_if: next_partition_if, - detach_partition_if: detach_partition_if) + described_class.new( + model, + :partition, + next_partition_if: next_partition_if, + detach_partition_if: detach_partition_if + ) end before do @@ -213,9 +216,9 @@ RSpec.describe Gitlab::Database::Partitioning::SlidingListStrategy, feature_cate include PartitionedTable partitioned_by :partition, - strategy: :sliding_list, - next_partition_if: proc { false }, - detach_partition_if: proc { false } + strategy: :sliding_list, + next_partition_if: proc { false }, + detach_partition_if: proc { false } end end.to raise_error(/ignored_columns/) end @@ -228,9 +231,9 @@ RSpec.describe Gitlab::Database::Partitioning::SlidingListStrategy, feature_cate self.ignored_columns = [:partition] partitioned_by :partition, - strategy: :sliding_list, - next_partition_if: proc { false }, - detach_partition_if: proc { false } + strategy: :sliding_list, + next_partition_if: proc { false }, + detach_partition_if: proc { false } end end.not_to raise_error end @@ -255,9 +258,9 @@ RSpec.describe Gitlab::Database::Partitioning::SlidingListStrategy, feature_cate detach_partition?(...) end partitioned_by :partition, - strategy: :sliding_list, - next_partition_if: method(:next_partition_if_wrapper), - detach_partition_if: method(:detach_partition_if_wrapper) + strategy: :sliding_list, + next_partition_if: method(:next_partition_if_wrapper), + detach_partition_if: method(:detach_partition_if_wrapper) def self.next_partition?(current_partition); end diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb index a81c8a5a49c..aa644885306 100644 --- a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb +++ b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb @@ -99,8 +99,11 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::IndexHelpers do expect(migration).to receive(:add_index) .with(table_name, column_name, { name: index_name, where: 'x > 0', unique: true }) - migration.add_concurrent_partitioned_index(table_name, column_name, - { name: index_name, where: 'x > 0', unique: true }) + migration.add_concurrent_partitioned_index( + table_name, + column_name, + { name: index_name, where: 'x > 0', unique: true } + ) end end diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb index 6a947044317..31c669ff330 100644 --- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb +++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb @@ -2,6 +2,173 @@ require 'spec_helper' +RSpec.shared_examples "a measurable object" do + context 'when the table is not allowed' do + let(:source_table) { :_test_this_table_is_not_allowed } + + it 'raises an error' do + expect(migration).to receive(:assert_table_is_allowed).with(source_table).and_call_original + + expect do + subject + end.to raise_error(/#{source_table} is not allowed for use/) + end + end + + context 'when run inside a transaction block' do + it 'raises an error' do + expect(migration).to receive(:transaction_open?).and_return(true) + + expect do + subject + end.to raise_error(/can not be run inside a transaction/) + end + end + + context 'when the given table does not have a primary key' do + it 'raises an error' do + migration.execute(<<~SQL) + ALTER TABLE #{source_table} + DROP CONSTRAINT #{source_table}_pkey + SQL + + expect do + subject + end.to raise_error(/primary key not defined for #{source_table}/) + end + end + + it 'creates the partitioned table with the same non-key columns' do + subject + + copied_columns = filter_columns_by_name(connection.columns(partitioned_table), new_primary_key) + original_columns = filter_columns_by_name(connection.columns(source_table), new_primary_key) + + expect(copied_columns).to match_array(original_columns) + end + + it 'removes the default from the primary key column' do + subject + + pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key } + + expect(pk_column.default_function).to be_nil + end + + describe 'constructing the partitioned table' do + it 'creates a table partitioned by the proper column' do + subject + + expect(connection.table_exists?(partitioned_table)).to be(true) + expect(connection.primary_key(partitioned_table)).to eq(new_primary_key) + + expect_table_partitioned_by(partitioned_table, [partition_column_name]) + end + + it 'requires the migration helper to be run in DDL mode' do + expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_ddl_mode!) + + subject + + expect(connection.table_exists?(partitioned_table)).to be(true) + expect(connection.primary_key(partitioned_table)).to eq(new_primary_key) + + expect_table_partitioned_by(partitioned_table, [partition_column_name]) + end + + it 'changes the primary key datatype to bigint' do + subject + + pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key } + + expect(pk_column.sql_type).to eq('bigint') + end + + it 'removes the default from the primary key column' do + subject + + pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key } + + expect(pk_column.default_function).to be_nil + end + + it 'creates the partitioned table with the same non-key columns' do + subject + + copied_columns = filter_columns_by_name(connection.columns(partitioned_table), new_primary_key) + original_columns = filter_columns_by_name(connection.columns(source_table), new_primary_key) + + expect(copied_columns).to match_array(original_columns) + end + end + + describe 'keeping data in sync with the partitioned table' do + before do + partitioned_model.primary_key = :id + partitioned_model.table_name = partitioned_table + end + + it 'creates a trigger function on the original table' do + expect_function_not_to_exist(function_name) + expect_trigger_not_to_exist(source_table, trigger_name) + + subject + + expect_function_to_exist(function_name) + expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update]) + end + + it 'syncs inserts to the partitioned tables' do + subject + + expect(partitioned_model.count).to eq(0) + + first_record = source_model.create!(name: 'Bob', age: 20, created_at: timestamp, external_id: 1, updated_at: timestamp) + second_record = source_model.create!(name: 'Alice', age: 30, created_at: timestamp, external_id: 2, updated_at: timestamp) + + expect(partitioned_model.count).to eq(2) + expect(partitioned_model.find(first_record.id).attributes).to eq(first_record.attributes) + expect(partitioned_model.find(second_record.id).attributes).to eq(second_record.attributes) + end + + it 'syncs updates to the partitioned tables' do + subject + + first_record = source_model.create!(name: 'Bob', age: 20, created_at: timestamp, external_id: 1, updated_at: timestamp) + second_record = source_model.create!(name: 'Alice', age: 30, created_at: timestamp, external_id: 2, updated_at: timestamp) + + expect(partitioned_model.count).to eq(2) + + first_copy = partitioned_model.find(first_record.id) + second_copy = partitioned_model.find(second_record.id) + + expect(first_copy.attributes).to eq(first_record.attributes) + expect(second_copy.attributes).to eq(second_record.attributes) + + first_record.update!(age: 21, updated_at: timestamp + 1.hour, external_id: 3) + + expect(partitioned_model.count).to eq(2) + expect(first_copy.reload.attributes).to eq(first_record.attributes) + expect(second_copy.reload.attributes).to eq(second_record.attributes) + end + + it 'syncs deletes to the partitioned tables' do + subject + + first_record = source_model.create!(name: 'Bob', age: 20, created_at: timestamp, external_id: 1, updated_at: timestamp) + second_record = source_model.create!(name: 'Alice', age: 30, created_at: timestamp, external_id: 2, updated_at: timestamp) + + expect(partitioned_model.count).to eq(2) + + first_record.destroy! + + expect(partitioned_model.count).to eq(1) + expect(partitioned_model.find_by_id(first_record.id)).to be_nil + expect(partitioned_model.find(second_record.id).attributes).to eq(second_record.attributes) + end + end +end + RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers, feature_category: :database do include Database::PartitioningHelpers include Database::TriggerHelpers @@ -18,6 +185,7 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe let(:partitioned_table) { :_test_migration_partitioned_table } let(:function_name) { :_test_migration_function_name } let(:trigger_name) { :_test_migration_trigger_name } + let(:partition_column2) { 'external_id' } let(:partition_column) { 'created_at' } let(:min_date) { Date.new(2019, 12) } let(:max_date) { Date.new(2020, 3) } @@ -29,6 +197,7 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe migration.create_table source_table do |t| t.string :name, null: false t.integer :age, null: false + t.integer partition_column2 t.datetime partition_column t.datetime :updated_at end @@ -51,13 +220,15 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe end it 'delegates to a method on List::ConvertTable' do - expect_next_instance_of(Gitlab::Database::Partitioning::List::ConvertTable, - migration_context: migration, - table_name: source_table, - parent_table_name: partitioned_table, - partitioning_column: partition_column, - zero_partition_value: min_date, - **extra_options) do |converter| + expect_next_instance_of( + Gitlab::Database::Partitioning::List::ConvertTable, + migration_context: migration, + table_name: source_table, + parent_table_name: partitioned_table, + partitioning_column: partition_column, + zero_partition_value: min_date, + **extra_options + ) do |converter| expect(converter).to receive(expected_method) end @@ -70,11 +241,13 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe let(:lock_tables) { [source_table] } let(:expected_method) { :partition } let(:migrate) do - migration.convert_table_to_first_list_partition(table_name: source_table, - partitioning_column: partition_column, - parent_table_name: partitioned_table, - initial_partitioning_value: min_date, - lock_tables: lock_tables) + migration.convert_table_to_first_list_partition( + table_name: source_table, + partitioning_column: partition_column, + parent_table_name: partitioned_table, + initial_partitioning_value: min_date, + lock_tables: lock_tables + ) end end end @@ -83,10 +256,12 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe it_behaves_like 'delegates to ConvertTable' do let(:expected_method) { :revert_partitioning } let(:migrate) do - migration.revert_converting_table_to_first_list_partition(table_name: source_table, - partitioning_column: partition_column, - parent_table_name: partitioned_table, - initial_partitioning_value: min_date) + migration.revert_converting_table_to_first_list_partition( + table_name: source_table, + partitioning_column: partition_column, + parent_table_name: partitioned_table, + initial_partitioning_value: min_date + ) end end end @@ -95,11 +270,13 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe it_behaves_like 'delegates to ConvertTable' do let(:expected_method) { :prepare_for_partitioning } let(:migrate) do - migration.prepare_constraint_for_list_partitioning(table_name: source_table, - partitioning_column: partition_column, - parent_table_name: partitioned_table, - initial_partitioning_value: min_date, - async: false) + migration.prepare_constraint_for_list_partitioning( + table_name: source_table, + partitioning_column: partition_column, + parent_table_name: partitioned_table, + initial_partitioning_value: min_date, + async: false + ) end end end @@ -108,41 +285,200 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe it_behaves_like 'delegates to ConvertTable' do let(:expected_method) { :revert_preparation_for_partitioning } let(:migrate) do - migration.revert_preparing_constraint_for_list_partitioning(table_name: source_table, - partitioning_column: partition_column, - parent_table_name: partitioned_table, - initial_partitioning_value: min_date) + migration.revert_preparing_constraint_for_list_partitioning( + table_name: source_table, + partitioning_column: partition_column, + parent_table_name: partitioned_table, + initial_partitioning_value: min_date + ) end end end end - describe '#partition_table_by_date' do - let(:partition_column) { 'created_at' } + describe '#partition_table_by_int_range' do let(:old_primary_key) { 'id' } - let(:new_primary_key) { [old_primary_key, partition_column] } + let(:new_primary_key) { ['id', partition_column2] } + let(:partition_column_name) { partition_column2 } + let(:partitioned_model) { Class.new(ActiveRecord::Base) } + let(:timestamp) { Time.utc(2019, 12, 1, 12).round } + let(:partition_size) { 500 } - context 'when the table is not allowed' do - let(:source_table) { :_test_this_table_is_not_allowed } + subject { migration.partition_table_by_int_range(source_table, partition_column2, partition_size: partition_size, primary_key: ['id', partition_column2]) } - it 'raises an error' do - expect(migration).to receive(:assert_table_is_allowed).with(source_table).and_call_original + include_examples "a measurable object" - expect do - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - end.to raise_error(/#{source_table} is not allowed for use/) + context 'simulates the merge_request_diff_commits migration' do + let(:table_name) { '_test_merge_request_diff_commits' } + let(:partition_column_name) { 'relative_order' } + let(:partition_size) { 2 } + let(:partitions) do + { + '1' => %w[1 3], + '3' => %w[3 5], + '5' => %w[5 7], + '7' => %w[7 9], + '9' => %w[9 11], + '11' => %w[11 13] + } + end + + let(:buffer_partitions) do + { + '13' => %w[13 15], + '15' => %w[15 17], + '17' => %w[17 19], + '19' => %w[19 21], + '21' => %w[21 23], + '23' => %w[23 25] + } + end + + let(:new_table_defition) do + { + new_path: { default: 'test', null: true, sql_type: 'text' }, + merge_request_diff_id: { default: nil, null: false, sql_type: 'bigint' }, + relative_order: { default: nil, null: false, sql_type: 'integer' } + } + end + + let(:primary_key) { %w[merge_request_diff_id relative_order] } + + before do + migration.create_table table_name, primary_key: primary_key do |t| + t.integer :merge_request_diff_id, null: false, default: 1 + t.integer :relative_order, null: false + t.text :new_path, null: true, default: 'test' + end + + source_model.table_name = table_name + end + + it 'creates the partitions' do + migration.partition_table_by_int_range(table_name, partition_column_name, partition_size: partition_size, primary_key: primary_key) + + expect_range_partitions_for(partitioned_table, partitions.merge(buffer_partitions)) + end + + it 'creates a composite primary key' do + migration.partition_table_by_int_range(table_name, partition_column_name, partition_size: partition_size, primary_key: primary_key) + + expect(connection.primary_key(:_test_migration_partitioned_table)).to eql(%w[merge_request_diff_id relative_order]) + end + + it 'applies the correct column schema for the new table' do + migration.partition_table_by_int_range(table_name, partition_column_name, partition_size: partition_size, primary_key: primary_key) + + columns = connection.columns(:_test_migration_partitioned_table) + + columns.each do |column| + column_name = column.name.to_sym + + expect(column.default).to eql(new_table_defition[column_name][:default]) + expect(column.null).to eql(new_table_defition[column_name][:null]) + expect(column.sql_type).to eql(new_table_defition[column_name][:sql_type]) + end + end + + it 'creates multiple partitions' do + migration.partition_table_by_int_range(table_name, partition_column_name, partition_size: 500, primary_key: primary_key) + + expect_range_partitions_for(partitioned_table, { + '1' => %w[1 501], + '501' => %w[501 1001], + '1001' => %w[1001 1501], + '1501' => %w[1501 2001], + '2001' => %w[2001 2501], + '2501' => %w[2501 3001], + '3001' => %w[3001 3501], + '3501' => %w[3501 4001], + '4001' => %w[4001 4501], + '4501' => %w[4501 5001], + '5001' => %w[5001 5501], + '5501' => %w[5501 6001] + }) + end + + context 'when the table is not empty' do + before do + source_model.create!(merge_request_diff_id: 1, relative_order: 7, new_path: 'new_path') + end + + let(:partition_size) { 2 } + + let(:partitions) do + { + '1' => %w[1 3], + '3' => %w[3 5], + '5' => %w[5 7] + } + end + + let(:buffer_partitions) do + { + '7' => %w[7 9], + '9' => %w[9 11], + '11' => %w[11 13], + '13' => %w[13 15], + '15' => %w[15 17], + '17' => %w[17 19] + } + end + + it 'defaults the min_id to 1 and the max_id to 7' do + migration.partition_table_by_int_range(table_name, partition_column_name, partition_size: partition_size, primary_key: primary_key) + + expect_range_partitions_for(partitioned_table, partitions.merge(buffer_partitions)) + end end end - context 'when run inside a transaction block' do + context 'when an invalid partition column is given' do + let(:invalid_column) { :_this_is_not_real } + it 'raises an error' do - expect(migration).to receive(:transaction_open?).and_return(true) + expect do + migration.partition_table_by_int_range(source_table, invalid_column, partition_size: partition_size, primary_key: ['id']) + end.to raise_error(/partition column #{invalid_column} does not exist/) + end + end + context 'when partition_size is less than 1' do + let(:partition_size) { 1 } + + it 'raises an error' do expect do - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - end.to raise_error(/can not be run inside a transaction/) + subject + end.to raise_error(/partition_size must be greater than 1/) + end + end + + context 'when the partitioned table already exists' do + before do + migration.send(:create_range_id_partitioned_copy, source_table, + migration.send(:make_partitioned_table_name, source_table), + connection.columns(source_table).find { |c| c.name == partition_column2 }, + connection.columns(source_table).select { |c| new_primary_key.include?(c.name) }) + end + + it 'raises an error' do + expect(Gitlab::AppLogger).to receive(:warn).with(/Partitioned table not created because it already exists/) + expect { subject }.not_to raise_error end end + end + + describe '#partition_table_by_date' do + let(:partition_column) { 'created_at' } + let(:old_primary_key) { 'id' } + let(:new_primary_key) { [old_primary_key, partition_column] } + let(:partition_column_name) { 'created_at' } + let(:partitioned_model) { Class.new(ActiveRecord::Base) } + let(:timestamp) { Time.utc(2019, 12, 1, 12).round } + + subject { migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date } + + include_examples "a measurable object" context 'when the the max_date is less than the min_date' do let(:max_date) { Time.utc(2019, 6) } @@ -164,19 +500,6 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe end end - context 'when the given table does not have a primary key' do - it 'raises an error' do - migration.execute(<<~SQL) - ALTER TABLE #{source_table} - DROP CONSTRAINT #{source_table}_pkey - SQL - - expect do - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - end.to raise_error(/primary key not defined for #{source_table}/) - end - end - context 'when an invalid partition column is given' do let(:invalid_column) { :_this_is_not_real } @@ -188,34 +511,6 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe end describe 'constructing the partitioned table' do - it 'creates a table partitioned by the proper column' do - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - - expect(connection.table_exists?(partitioned_table)).to be(true) - expect(connection.primary_key(partitioned_table)).to eq(new_primary_key) - - expect_table_partitioned_by(partitioned_table, [partition_column]) - end - - it 'requires the migration helper to be run in DDL mode' do - expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_ddl_mode!) - - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - - expect(connection.table_exists?(partitioned_table)).to be(true) - expect(connection.primary_key(partitioned_table)).to eq(new_primary_key) - - expect_table_partitioned_by(partitioned_table, [partition_column]) - end - - it 'changes the primary key datatype to bigint' do - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - - pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key } - - expect(pk_column.sql_type).to eq('bigint') - end - context 'with a non-integer primary key datatype' do before do connection.create_table non_int_table, id: false do |t| @@ -238,23 +533,6 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe end end - it 'removes the default from the primary key column' do - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - - pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key } - - expect(pk_column.default_function).to be_nil - end - - it 'creates the partitioned table with the same non-key columns' do - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - - copied_columns = filter_columns_by_name(connection.columns(partitioned_table), new_primary_key) - original_columns = filter_columns_by_name(connection.columns(source_table), new_primary_key) - - expect(copied_columns).to match_array(original_columns) - end - it 'creates a partition spanning over each month in the range given' do migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date @@ -340,75 +618,6 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe end end end - - describe 'keeping data in sync with the partitioned table' do - let(:partitioned_model) { Class.new(ActiveRecord::Base) } - let(:timestamp) { Time.utc(2019, 12, 1, 12).round } - - before do - partitioned_model.primary_key = :id - partitioned_model.table_name = partitioned_table - end - - it 'creates a trigger function on the original table' do - expect_function_not_to_exist(function_name) - expect_trigger_not_to_exist(source_table, trigger_name) - - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - - expect_function_to_exist(function_name) - expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update]) - end - - it 'syncs inserts to the partitioned tables' do - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - - expect(partitioned_model.count).to eq(0) - - first_record = source_model.create!(name: 'Bob', age: 20, created_at: timestamp, updated_at: timestamp) - second_record = source_model.create!(name: 'Alice', age: 30, created_at: timestamp, updated_at: timestamp) - - expect(partitioned_model.count).to eq(2) - expect(partitioned_model.find(first_record.id).attributes).to eq(first_record.attributes) - expect(partitioned_model.find(second_record.id).attributes).to eq(second_record.attributes) - end - - it 'syncs updates to the partitioned tables' do - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - - first_record = source_model.create!(name: 'Bob', age: 20, created_at: timestamp, updated_at: timestamp) - second_record = source_model.create!(name: 'Alice', age: 30, created_at: timestamp, updated_at: timestamp) - - expect(partitioned_model.count).to eq(2) - - first_copy = partitioned_model.find(first_record.id) - second_copy = partitioned_model.find(second_record.id) - - expect(first_copy.attributes).to eq(first_record.attributes) - expect(second_copy.attributes).to eq(second_record.attributes) - - first_record.update!(age: 21, updated_at: timestamp + 1.hour) - - expect(partitioned_model.count).to eq(2) - expect(first_copy.reload.attributes).to eq(first_record.attributes) - expect(second_copy.reload.attributes).to eq(second_record.attributes) - end - - it 'syncs deletes to the partitioned tables' do - migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date - - first_record = source_model.create!(name: 'Bob', age: 20, created_at: timestamp, updated_at: timestamp) - second_record = source_model.create!(name: 'Alice', age: 30, created_at: timestamp, updated_at: timestamp) - - expect(partitioned_model.count).to eq(2) - - first_record.destroy! - - expect(partitioned_model.count).to eq(1) - expect(partitioned_model.find_by_id(first_record.id)).to be_nil - expect(partitioned_model.find(second_record.id).attributes).to eq(second_record.attributes) - end - end end describe '#drop_partitioned_table_for' do diff --git a/spec/lib/gitlab/database/postgres_constraint_spec.rb b/spec/lib/gitlab/database/postgres_constraint_spec.rb index 75084a69115..140180540e0 100644 --- a/spec/lib/gitlab/database/postgres_constraint_spec.rb +++ b/spec/lib/gitlab/database/postgres_constraint_spec.rb @@ -51,9 +51,11 @@ RSpec.describe Gitlab::Database::PostgresConstraint, type: :model do subject(:check_constraints) { described_class.check_constraints.by_table_identifier(table_identifier) } it 'finds check constraints for the table' do - expect(check_constraints.map(&:name)).to contain_exactly(check_constraint_a_positive, - check_constraint_a_gt_b, - invalid_constraint_a) + expect(check_constraints.map(&:name)).to contain_exactly( + check_constraint_a_positive, + check_constraint_a_gt_b, + invalid_constraint_a + ) end it 'includes columns for the check constraints', :aggregate_failures do @@ -108,8 +110,12 @@ RSpec.describe Gitlab::Database::PostgresConstraint, type: :model do describe '#including_column' do it 'only matches constraints on the given column' do constraints_on_a = described_class.by_table_identifier(table_identifier).including_column('a').map(&:name) - expect(constraints_on_a).to contain_exactly(check_constraint_a_positive, check_constraint_a_gt_b, - unique_constraint_a, invalid_constraint_a) + expect(constraints_on_a).to contain_exactly( + check_constraint_a_positive, + check_constraint_a_gt_b, + unique_constraint_a, + invalid_constraint_a + ) end end diff --git a/spec/lib/gitlab/database/postgres_index_spec.rb b/spec/lib/gitlab/database/postgres_index_spec.rb index d8a2612caf3..2e654a33a58 100644 --- a/spec/lib/gitlab/database/postgres_index_spec.rb +++ b/spec/lib/gitlab/database/postgres_index_spec.rb @@ -40,7 +40,7 @@ RSpec.describe Gitlab::Database::PostgresIndex do it 'only btree and gist indexes' do types = described_class.reindexing_support.map(&:type).uniq - expect(types & %w(btree gist)).to eq(types) + expect(types & %w[btree gist]).to eq(types) end context 'with leftover indexes' do @@ -71,7 +71,7 @@ RSpec.describe Gitlab::Database::PostgresIndex do end it 'retrieves leftover indexes matching the /_ccnew[0-9]*$/ pattern' do - expect(subject.map(&:name)).to eq(%w(foobar_ccnew foobar_ccnew1)) + expect(subject.map(&:name)).to eq(%w[foobar_ccnew foobar_ccnew1]) end end diff --git a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb index f325060e592..1909e134e66 100644 --- a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb +++ b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana }, "for query accessing gitlab_ci and gitlab_main" => { model: ApplicationRecord, - sql: "SELECT 1 FROM projects LEFT JOIN ci_builds ON ci_builds.project_id=projects.id", + sql: "SELECT 1 FROM projects LEFT JOIN p_ci_builds ON p_ci_builds.project_id=projects.id", expectations: { gitlab_schemas: "gitlab_ci,gitlab_main_cell", db_config_name: "main" @@ -43,7 +43,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana }, "for query accessing gitlab_ci and gitlab_main the gitlab_schemas is always ordered" => { model: ApplicationRecord, - sql: "SELECT 1 FROM ci_builds LEFT JOIN projects ON ci_builds.project_id=projects.id", + sql: "SELECT 1 FROM p_ci_builds LEFT JOIN projects ON p_ci_builds.project_id=projects.id", expectations: { gitlab_schemas: "gitlab_ci,gitlab_main_cell", db_config_name: "main" @@ -51,7 +51,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana }, "for query accessing CI database" => { model: Ci::ApplicationRecord, - sql: "SELECT 1 FROM ci_builds", + sql: "SELECT 1 FROM p_ci_builds", expectations: { gitlab_schemas: "gitlab_ci", db_config_name: "ci" diff --git a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb index e3ff5ab4779..0664508fa8d 100644 --- a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb +++ b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb @@ -26,14 +26,14 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection }, "for query accessing gitlab_ci and gitlab_main" => { model: ApplicationRecord, - sql: "SELECT 1 FROM projects LEFT JOIN ci_builds ON ci_builds.project_id=projects.id", - expect_error: /The query tried to access \["projects", "ci_builds"\]/, + sql: "SELECT 1 FROM projects LEFT JOIN p_ci_builds ON p_ci_builds.project_id=projects.id", + expect_error: /The query tried to access \["projects", "p_ci_builds"\]/, setup: -> (_) { skip_if_shared_database(:ci) } }, "for query accessing gitlab_ci and gitlab_main the gitlab_schemas is always ordered" => { model: ApplicationRecord, - sql: "SELECT 1 FROM ci_builds LEFT JOIN projects ON ci_builds.project_id=projects.id", - expect_error: /The query tried to access \["ci_builds", "projects"\]/, + sql: "SELECT 1 FROM p_ci_builds LEFT JOIN projects ON p_ci_builds.project_id=projects.id", + expect_error: /The query tried to access \["p_ci_builds", "projects"\]/, setup: -> (_) { skip_if_shared_database(:ci) } }, "for query accessing main table from CI database" => { @@ -44,13 +44,13 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection }, "for query accessing CI database" => { model: Ci::ApplicationRecord, - sql: "SELECT 1 FROM ci_builds", + sql: "SELECT 1 FROM p_ci_builds", expect_error: nil }, "for query accessing CI table from main database" => { model: ::ApplicationRecord, - sql: "SELECT 1 FROM ci_builds", - expect_error: /The query tried to access \["ci_builds"\]/, + sql: "SELECT 1 FROM p_ci_builds", + expect_error: /The query tried to access \["p_ci_builds"\]/, setup: -> (_) { skip_if_shared_database(:ci) } }, "for query accessing unknown gitlab_schema" => { @@ -89,7 +89,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection it "throws an error when trying to access a table that belongs to the gitlab_ci schema from the main database" do expect do - ApplicationRecord.connection.execute("select * from ci_builds limit 1") + ApplicationRecord.connection.execute("select * from p_ci_builds limit 1") end.to raise_error(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection::CrossSchemaAccessError) end end diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/columns_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/columns_spec.rb new file mode 100644 index 00000000000..042bc297520 --- /dev/null +++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/columns_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::Columns, + feature_category: :cell do + include PreventSetOperatorMismatchHelper + + let_it_be(:namespace_columns) { Namespace.column_names.join(',') } + + describe '.types' do + let(:node) { sql_select_node(sql) } + let(:cte_refs) { {} } + let(:select_stmt) do + Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::SelectStmt.new(node, cte_refs) + end + + subject { described_class.types(select_stmt) } + + context 'when static column' do + let(:sql) { 'SELECT id FROM namespaces' } + + it do + expect(subject).to contain_exactly(Type::STATIC) + end + + context 'with dynamic reference' do + let(:sql) { 'SELECT id FROM (SELECT * FROM namespaces) AS xyz' } + + it do + expect(subject).to contain_exactly(Type::STATIC) + end + end + end + + context 'when dynamic column' do + let(:sql) { 'SELECT * FROM namespaces' } + + it do + expect(subject).to contain_exactly(Type::DYNAMIC) + end + + context 'with static reference' do + let(:sql) { 'SELECT * FROM (SELECT 1) AS xyz' } + + it do + expect(subject).to contain_exactly(Type::STATIC) + end + end + end + + context 'when reference has errors' do + let(:cte_refs) { { 'namespaces' => [Type::INVALID].to_set } } + let(:sql) { 'SELECT * FROM namespaces' } + + it 'forward through error state' do + expect(subject).to include(Type::INVALID) + end + end + + context 'when static and dynamic columns' do + let(:sql) { 'SELECT *, users.id FROM namespaces, users' } + + it do + expect(subject).to contain_exactly(Type::DYNAMIC, Type::STATIC) + end + end + + context 'when static column and error' do + let(:error_column) { "SELECT #{namespace_columns} FROM namespaces UNION SELECT * FROM namespaces" } + let(:sql) { "SELECT id, (#{error_column}) FROM namespaces" } + + it do + expect(subject).to contain_exactly(Type::STATIC, Type::INVALID) + end + end + + context 'when dynamic column and error' do + let(:error_column) { "SELECT #{namespace_columns} FROM namespaces UNION SELECT * FROM namespaces" } + let(:sql) { "SELECT *, (#{error_column}) FROM namespaces" } + + it do + # The sub-select is treated as a Type::STATIC column for now. This could do with some refinement. + expect(subject).to contain_exactly(Type::DYNAMIC, Type::STATIC, Type::INVALID) + end + end + end +end diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/common_table_expressions_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/common_table_expressions_spec.rb new file mode 100644 index 00000000000..eacaa643ba5 --- /dev/null +++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/common_table_expressions_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::CommonTableExpressions, + feature_category: :cell do + include PreventSetOperatorMismatchHelper + + describe '.references' do + let(:node) { sql_select_node(sql) } + let(:cte_refs) { {} } + + subject { described_class.references(node, cte_refs) } + + context 'when standard CTE' do + let(:sql) do + <<-SQL + WITH some_cte AS (#{cte}) + SELECT 1 + FROM some_cte + SQL + end + + context 'with static SELECT' do + let(:cte) { 'SELECT 1' } + + it do + exp = { "some_cte" => Set.new([Type::STATIC]) } + expect(subject).to eq(exp) + end + end + + context 'with dynamic SELECT' do + let(:cte) { 'SELECT * FROM namespaces' } + + it do + exp = { "some_cte" => Set.new([Type::DYNAMIC]) } + expect(subject).to eq(exp) + end + end + end + + context 'when recursive CTE' do + let(:sql) do + <<-SQL + WITH RECURSIVE some_cte AS (#{cte}) + SELECT 1 + FROM some_cte + SQL + end + + context 'with static SELECT' do + let(:cte) { 'SELECT 1 UNION SELECT 2' } + + it do + exp = { "some_cte" => Set.new([Type::STATIC]) } + expect(subject).to eq(exp) + end + end + + context 'with dynamic SELECT' do + let(:cte) { 'SELECT * FROM namespaces UNION SELECT * FROM namespaces' } + + it do + exp = { "some_cte" => Set.new([Type::DYNAMIC]) } + expect(subject).to eq(exp) + end + end + + context 'with error SELECT' do + let(:cte) { 'SELECT * FROM namespaces UNION SELECT id FROM namespaces' } + + it do + exp = { "some_cte" => Set.new([Type::DYNAMIC, Type::STATIC, Type::INVALID]) } + expect(subject).to eq(exp) + end + end + end + + context 'with inherited CTE references' do + let(:sql) do + <<-SQL + WITH some_cte AS (SELECT 1) + SELECT 1 + FROM some_cte + SQL + end + + let(:cte_refs) { { 'some_reference' => 123 } } + + it 'maintains inherited CTE references' do + subject_ref_names = subject.keys + expect(subject_ref_names).to eq(cte_refs.keys + ['some_cte']) + end + end + end +end diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/froms_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/froms_spec.rb new file mode 100644 index 00000000000..03c0a845e60 --- /dev/null +++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/froms_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::Froms, + feature_category: :cell do + include PreventSetOperatorMismatchHelper + + describe '.references' do + let(:node) { sql_select_node(sql) } + let(:cte_refs) { {} } + + subject { described_class.references(node, cte_refs) } + + context 'when node is nil' do + let(:node) { nil } + + it { is_expected.to eq({}) } + end + + context 'when range_var' do + let(:sql) { 'SELECT 1 FROM namespaces' } + + it { is_expected.to match({ 'namespaces' => an_instance_of(PgQuery::RangeVar) }) } + end + + context 'when range_var with alias' do + let(:sql) { 'SELECT 1 FROM namespaces ns' } + + it { is_expected.to match({ 'ns' => an_instance_of(PgQuery::RangeVar) }) } + end + + context 'when join expression' do + let(:sql) do + <<-SQL + SELECT 1 FROM namespaces + INNER JOIN organizations ON namespaces.organization_id = organization.id + SQL + end + + it do + is_expected.to match({ + 'namespaces' => an_instance_of(PgQuery::RangeVar), + 'organizations' => an_instance_of(PgQuery::RangeVar) + }) + end + end + + context 'when join expression with alias' do + let(:sql) do + <<-SQL + SELECT 1 FROM namespaces ns + INNER JOIN organizations o ON ns.organization_id = o.id + SQL + end + + it do + is_expected.to match({ + 'ns' => an_instance_of(PgQuery::RangeVar), + 'o' => an_instance_of(PgQuery::RangeVar) + }) + end + end + + context 'when sub-query' do + let(:sql) do + <<-SQL + SELECT 1 + FROM (SELECT 1) some_subquery + SQL + end + + it { is_expected.to match({ 'some_subquery' => [Type::STATIC].to_set }) } + end + end +end diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node_spec.rb new file mode 100644 index 00000000000..a8294376107 --- /dev/null +++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/node_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::Node, feature_category: :cell do + include PreventSetOperatorMismatchHelper + + let(:sql) { 'SELECT id FROM namespaces' } + let(:node) { sql_select_node(sql) } + + describe '.descendants' do + context 'with a block' do + it do + nodes = [] + described_class.descendants(node.from_clause) do |node| + nodes << node.class + end + expect(nodes).to match_array [PgQuery::Node, PgQuery::RangeVar] + end + end + + context 'without a block' do + subject { described_class.descendants(node) } + + it { is_expected.to be_instance_of Enumerator } + end + + context 'with a filter' do + let(:filter) { ->(field) { %i[from_clause target_list].include?(field) } } + + subject { described_class.descendants(node, filter: filter).count } + + it 'only traverse nodes that match the filter' do + is_expected.to eq 2 + end + end + end + + describe '.locate_descendant' do + subject { described_class.locate_descendant(node.target_list, :res_target) } + + it { is_expected.to be_instance_of PgQuery::ResTarget } + + context 'with a filter' do + subject { described_class.locate_descendant(node.target_list, :res_target, filter: ->(_) { false }) } + + it { is_expected.to be_nil } + end + end + + describe '.locate_descendants' do + subject { described_class.locate_descendants(node.target_list, :res_target) } + + it { is_expected.to be_instance_of Array } + + context 'with a filter' do + subject { described_class.locate_descendant(node.target_list, :res_target, filter: ->(_) { false }) } + + it { is_expected.to be_nil } + end + end + + describe '.dig' do + subject { described_class.dig(node.target_list[0], :res_target, :val, :column_ref) } + + it { is_expected.to be_instance_of PgQuery::ColumnRef } + end +end diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/references_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/references_spec.rb new file mode 100644 index 00000000000..0f0f92aa1f2 --- /dev/null +++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/references_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::References, + feature_category: :cell do + include PreventSetOperatorMismatchHelper + + let(:refs) do + { + 'resolved_reference' => Set.new, + 'unresolved_reference' => double, + 'table_reference' => PgQuery::RangeVar.new, + 'error_reference' => [Type::INVALID].to_set + } + end + + describe '.resolved' do + subject { described_class.resolved(refs) } + + it { is_expected.to eq refs.slice('resolved_reference', 'error_reference') } + end + + describe '.unresolved' do + subject { described_class.unresolved(refs) } + + it { is_expected.to eq refs.slice('unresolved_reference') } + end + + describe '.errors?' do + subject { described_class.errors?(refs) } + + it { is_expected.to be_truthy } + + context 'when no errors exist' do + subject { described_class.errors?(refs.except('error_reference')) } + + it { is_expected.to be_falsey } + end + end +end diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/select_stmt_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/select_stmt_spec.rb new file mode 100644 index 00000000000..52d6c9f1032 --- /dev/null +++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/select_stmt_spec.rb @@ -0,0 +1,361 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::SelectStmt, feature_category: :cell do + include PreventSetOperatorMismatchHelper + + let_it_be(:static_namespace_columns) { Namespace.column_names.join(', ') } + + let(:node) { sql_select_node(sql) } + + subject { described_class.new(node).types } + + shared_examples 'valid SQL' do + it { is_expected.not_to include(Type::INVALID) } + end + + shared_examples 'invalid SQL' do + it { is_expected.to include(Type::INVALID) } + end + + shared_context 'with basic set operator queries' do + let(:set_operator_queries) do + { + 'set operator with static columns' => <<-SQL, + SELECT id, name FROM namespaces WHERE name = 'test1' + #{set_operator} + SELECT id, name FROM namespaces WHERE name = 'test2' + SQL + 'set operator with static referenced columns' => <<-SQL, + SELECT namespaces.id, name FROM namespaces WHERE name = 'test1' + #{set_operator} + SELECT id, namespaces.name FROM namespaces WHERE name = 'test2' + SQL + 'set operator with static alias referenced columns' => <<-SQL, + SELECT namespaces.id, name FROM namespaces WHERE name = 'test1' + #{set_operator} + SELECT id, namespaces2.name FROM namespaces2 WHERE name = 'test2' + SQL + 'set operator with dynamic columns' => <<-SQL, + SELECT * FROM namespaces WHERE name = 'test1' + #{set_operator} + SELECT * FROM namespaces WHERE name = 'test2' + SQL + 'set operator with dynamic referenced columns' => <<-SQL, + SELECT namespaces.* FROM namespaces WHERE name = 'test1' + #{set_operator} + SELECT namespaces.* FROM namespaces WHERE name = 'test2' + SQL + 'set operator with dynamic referenced aliased columns' => <<-SQL, + SELECT namespaces.* FROM namespaces WHERE name = 'test1' + #{set_operator} + SELECT namespaces2.* FROM namespaces namespaces2 WHERE name = 'test2' + SQL + 'set operator with dynamic columns without using star' => <<-SQL, + SELECT namespaces FROM namespaces WHERE name = 'test1' + #{set_operator} + SELECT * FROM namespaces WHERE name = 'test2' + SQL + 'set operator with single dynamic referenced columns' => <<-SQL, + SELECT namespaces.* FROM namespaces WHERE name = 'test1' + #{set_operator} + SELECT * FROM namespaces WHERE name = 'test2' + SQL + 'set operator with static and dynamic columns' => <<-SQL, + SELECT #{static_namespace_columns} FROM namespaces WHERE name = 'test1' + #{set_operator} + SELECT * FROM namespaces WHERE name = 'test2' + SQL + 'set operator with static aliased columns and dynamic columns' => <<-SQL, + SELECT #{Namespace.column_names.map { |c| "namespaces2.#{c}" }.join(', ')} + FROM namespaces namespaces2 + WHERE name = 'test1' + #{set_operator} + SELECT * FROM namespaces WHERE name = 'test2' + SQL + 'set operator with static columns and dynamic aliased columns' => <<-SQL, + SELECT #{static_namespace_columns} FROM namespaces WHERE name = 'test1' + #{set_operator} + SELECT namespaces2.* FROM namespaces namespaces2 WHERE name = 'test2' + SQL + 'set operator with static and dynamic aliased columns' => <<-SQL, + SELECT #{Namespace.column_names.map { |c| "namespaces2.#{c}" }.join(', ')} + FROM namespaces namespaces2 + WHERE name = 'test1' + #{set_operator} + SELECT namespaces3.* FROM namespaces namespaces3 WHERE name = 'test2' + SQL + 'set operator with mixed dynamic and static columns' => <<-SQL, + SELECT namespaces.*, projects.id FROM namespaces, projects WHERE name = 'test1' + #{set_operator} + SELECT namespaces.*, projects.id FROM namespaces, projects WHERE name = 'test2' + SQL + 'set operator without references' => <<-SQL + SELECT 1 + #{set_operator} + SELECT 2 + SQL + } + end + + where(:query_name, :behavior) do + [ + ['set operator with static columns', 'valid SQL'], + ['set operator with static referenced columns', 'valid SQL'], + ['set operator with static alias referenced columns', 'valid SQL'], + ['set operator with dynamic columns', 'valid SQL'], + ['set operator with dynamic referenced columns', 'valid SQL'], + ['set operator with dynamic referenced aliased columns', 'valid SQL'], + ['set operator with dynamic columns without using star', 'invalid SQL'], + ['set operator with single dynamic referenced columns', 'valid SQL'], + ['set operator with static and dynamic columns', 'invalid SQL'], + ['set operator with static and dynamic aliased columns', 'invalid SQL'], + ['set operator with static aliased columns and dynamic columns', 'invalid SQL'], + ['set operator with static columns and dynamic aliased columns', 'invalid SQL'], + ['set operator with static and dynamic aliased columns', 'invalid SQL'], + ['set operator with mixed dynamic and static columns', 'valid SQL'], + ['set operator without references', 'valid SQL'] + ] + end + end + + %w[UNION INTERSECT EXCEPT].each do |set_operator| + context "with #{set_operator}" do + let(:set_operator) { set_operator } + + context "for basic #{set_operator} queries" do + include_context 'with basic set operator queries' + + with_them do + let(:sql) { set_operator_queries[query_name] } + + it_behaves_like params[:behavior] + end + end + + context 'for subquery' do + context "with #{set_operator}" do + where(:select_columns) do + [ + ['*'], + ['sub.*'], + ['sub'], + ['sub.id'] + ] + end + + with_them do + include_context 'with basic set operator queries' + + with_them do + let(:sql) do + <<-SQL + SELECT #{select_columns} + FROM ( + #{set_operator_queries[query_name]} + ) sub + SQL + end + + it_behaves_like params[:behavior] + end + end + end + + context "when used by one side of #{set_operator}" do + let(:sql) do + <<-SQL + SELECT #{union1} + FROM ( + SELECT #{subquery} + FROM namespaces + ) namespaces + + #{set_operator} + + SELECT #{union2} + FROM namespaces + SQL + end + + where(:union1, :union2, :subquery, :expected) do + [ + ['*', '*', '*', 'valid SQL'], + [ref(:static_namespace_columns), '*', '*', 'invalid SQL'], + ['*', ref(:static_namespace_columns), '*', 'invalid SQL'], + ['*', '*', ref(:static_namespace_columns), 'invalid SQL'], + [ref(:static_namespace_columns), ref(:static_namespace_columns), '*', 'valid SQL'], + [ref(:static_namespace_columns), '*', ref(:static_namespace_columns), 'invalid SQL'], + ['*', ref(:static_namespace_columns), ref(:static_namespace_columns), 'valid SQL'], + ['namespaces', 'namespaces', 'namespaces', 'valid SQL'], + # Used by our keyset pagination queries. + ['NULL :: namespaces', 'namespaces', 'id, name', 'valid SQL'], + ['NULL :: namespaces, id, name', 'namespaces, id, name', 'namespaces', 'valid SQL'] + ] + end + + with_them do + it_behaves_like params[:expected] + end + end + end + + context 'for CTE' do + context "when #{set_operator}" do + where(:select_columns) do + [ + ['*'], + ['namespaces_cte.*'], + ['namespaces_cte.id'] + ] + end + + with_them do + include_context 'with basic set operator queries' + + with_them do + let(:sql) do + <<-SQL + WITH namespaces_cte AS ( + #{set_operator_queries[query_name]} + ) + SELECT * + FROM namespaces_cte + SQL + end + + it_behaves_like params[:behavior] + end + end + end + + context "when used by one side of #{set_operator}" do + let(:sql) do + <<-SQL + WITH #{cte_name} AS ( + SELECT #{cte_select_columns} + FROM namespaces + ) + SELECT #{select_columns} + FROM #{cte_name} + + #{set_operator} + + SELECT * + FROM namespaces + SQL + end + + where(:cte_select_columns, :select_columns, :cte_name, :expected) do + [ + ['*', '*', 'some_cte', 'valid SQL'], + [ref(:static_namespace_columns), '*', 'some_cte', 'invalid SQL'], + ['*', ref(:static_namespace_columns), 'some_cte', 'invalid SQL'], + [ref(:static_namespace_columns), ref(:static_namespace_columns), 'some_cte', 'invalid SQL'], + ['*', '*', 'some_cte', 'valid SQL'], + # Same scenarios as above, but the CTE name matches the table name in the CTE. + ['*', '*', 'namespaces', 'valid SQL'], + [ref(:static_namespace_columns), '*', 'namespaces', 'valid SQL'], + ['*', ref(:static_namespace_columns), 'namespaces', 'invalid SQL'], + [ref(:static_namespace_columns), ref(:static_namespace_columns), 'namespaces', 'valid SQL'], + ['*', '*', 'namespaces', 'valid SQL'] + ] + end + + with_them do + it_behaves_like params[:expected] + end + end + + context 'when recursive' do + let(:sql) do + <<-SQL + WITH RECURSIVE namespaces_cte AS ( + ( + SELECT #{select1} + FROM namespaces + ) + UNION + ( + SELECT #{select2} + FROM namespaces_cte + ) + ) + SELECT * + FROM namespaces_cte + SQL + end + + where(:select1, :select2, :expected) do + [ + ['id', 'id', 'valid SQL'], + [ref(:static_namespace_columns), '*', 'valid SQL'], + ['*', ref(:static_namespace_columns), 'invalid SQL'] + ] + end + + with_them do + it_behaves_like params[:expected] + end + end + end + + context 'for subselect' do + context 'with set operator' do + let(:sql) do + <<-SQL + SELECT ( + SELECT id FROM namespaces + #{set_operator} + SELECT id FROM namespaces + ) AS namespace_id + SQL + end + + it_behaves_like 'valid SQL' + end + end + end + end + + context 'with lateral join' do + let(:sql) do + <<-SQL + SELECT namespaces.id + FROM + namespaces CROSS + JOIN LATERAL ( + SELECT + namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1) ] AS id + FROM + namespaces + WHERE + namespaces.type = 'Group' + AND namespaces.traversal_ids @ > ARRAY[members.source_id] + ) namespaces + SQL + end + + pending + end + + context 'when columns are not referenced' do + let(:sql) do + <<-SQL + SELECT + COUNT(1) + FROM ( + SELECT #{static_namespace_columns} + FROM namespaces + UNION + SELECT * + FROM namespaces + ) invalid_union + SQL + end + + # Error will bubble up even though the parent query does not reference any of the sub-query columns. + it_behaves_like 'invalid SQL' + end +end diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/targets_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/targets_spec.rb new file mode 100644 index 00000000000..2ea69f3726e --- /dev/null +++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch/targets_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::Targets, feature_category: :cell do + include PreventSetOperatorMismatchHelper + + let(:node) { sql_select_node(sql) } + let(:select_stmt) { Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch::SelectStmt.new(node) } + let(:target) { node.target_list[0].res_target } + + describe '.reference_names' do + subject { described_class.reference_names(target, select_stmt) } + + context 'with a literal target' do + let(:sql) { 'SELECT 1' } + + it { is_expected.to be_empty } + end + + context 'with a function target' do + let(:sql) { 'SELECT unnest(ARRAY[1,2]) FROM namespaces, users' } + + it { is_expected.to be_empty } + end + + context 'with a subselect target' do + let(:sql) { 'SELECT (SELECT 1) xyz FROM namespaces' } + + it { is_expected.to eq(%w[xyz_subselect]) } + + it 'updates all_references in the select statement' do + expect { subject }.to change { select_stmt.all_references } + .to include('xyz_subselect') + end + end + + context 'with an unqualified column name' do + let(:sql) { 'SELECT id FROM namespaces, users' } + + it { is_expected.to eq(%w[namespaces users]) } + end + + context 'with a qualified column name' do + let(:sql) { 'SELECT namespaces.id FROM namespaces, users' } + + it { is_expected.to eq(%w[namespaces]) } + end + + context 'with a table name' do + let(:sql) { 'SELECT namespaces FROM namespaces, users' } + + it { is_expected.to eq(%w[namespaces]) } + end + + context 'with a *' do + let(:sql) { 'SELECT * FROM namespaces, users' } + + it { is_expected.to eq(%w[namespaces users]) } + end + end + + describe '.a_star?' do + subject { described_class.a_star?(target) } + + context 'when * is used' do + let(:sql) { 'SELECT * FROM namespaces' } + + it { is_expected.to be_truthy } + end + + context 'when no * is used' do + let(:sql) { 'SELECT 1' } + + it { is_expected.to be_falsey } + end + end + + describe '.null?' do + subject { described_class.null?(target) } + + context 'when target is null' do + let(:sql) { 'SELECT NULL::namespaces FROM namespaces' } + + it { is_expected.to be_truthy } + end + + context 'when target is not null' do + let(:sql) { 'SELECT 1' } + + it { is_expected.to be_falsey } + end + end +end diff --git a/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch_spec.rb b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch_spec.rb new file mode 100644 index 00000000000..28c155c1eb1 --- /dev/null +++ b/spec/lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::QueryAnalyzers::PreventSetOperatorMismatch, query_analyzers: false, feature_category: :cell do + let(:analyzer) { described_class } + let_it_be(:static_namespace_columns) { Namespace.column_names.join(', ') } + + def process_sql(sql, model = ApplicationRecord) + Gitlab::Database::QueryAnalyzer.instance.within([analyzer]) do + # Skip load balancer and retrieve connection assigned to model + Gitlab::Database::QueryAnalyzer.instance.send(:process_sql, sql, model.retrieve_connection) + end + end + + shared_examples 'parses SQL' do + it do + expect_next_instance_of(described_class::SelectStmt) do |select_stmt| + expect(select_stmt).to receive(:types).and_return(Set.new) + end + + process_sql sql + end + end + + context 'when SQL includes a UNION' do + let(:sql) { 'SELECT 1 UNION SELECT 2' } + + include_examples 'parses SQL' + end + + context 'when SQL includes a INTERSECT' do + let(:sql) { 'SELECT 1 INTERSECT SELECT 2' } + + include_examples 'parses SQL' + end + + context 'when SQL includes a EXCEPT' do + let(:sql) { 'SELECT 1 EXCEPT SELECT 2' } + + include_examples 'parses SQL' + end + + context 'when SQL does not include a set operator' do + let(:sql) { 'SELECT 1' } + + it 'does not parse SQL' do + expect(described_class::SelectStmt).not_to receive(:new) + + process_sql sql + end + end + + context 'when SQL is invalid' do + it 'raises error' do + expect do + process_sql "SELECT #{static_namespace_columns} FROM namespaces UNION SELECT * FROM namespaces" + end.to raise_error(described_class::SetOperatorStarError) + end + end + + context 'when SQL is valid' do + it 'does not raise error' do + expect do + process_sql 'SELECT 1' + end.not_to raise_error + end + end + + context 'when SQL has many select statements' do + let(:sql) do + <<-SQL + SELECT 1 UNION SELECT 1; + SELECT #{static_namespace_columns} FROM namespaces UNION SELECT * FROM namespaces + SQL + end + + it 'raises error' do + expect do + process_sql sql + end.to raise_error(described_class::SetOperatorStarError) + end + end +end diff --git a/spec/lib/gitlab/database/reindexing_spec.rb b/spec/lib/gitlab/database/reindexing_spec.rb index 441f6476abe..2321f5d933d 100644 --- a/spec/lib/gitlab/database/reindexing_spec.rb +++ b/spec/lib/gitlab/database/reindexing_spec.rb @@ -231,7 +231,7 @@ RSpec.describe Gitlab::Database::Reindexing, feature_category: :database, time_t states = queued_actions.map(&:reload).map(&:state) - expect(states).to eq(%w(failed done queued)) + expect(states).to eq(%w[failed done queued]) end end diff --git a/spec/lib/gitlab/database/tables_locker_spec.rb b/spec/lib/gitlab/database/tables_locker_spec.rb index 0e7e929d54b..e3bd61b32a1 100644 --- a/spec/lib/gitlab/database/tables_locker_spec.rb +++ b/spec/lib/gitlab/database/tables_locker_spec.rb @@ -33,30 +33,40 @@ RSpec.describe Gitlab::Database::TablesLocker, :suppress_gitlab_schemas_validate FOR VALUES IN (0) SQL - ApplicationRecord.connection.execute(create_partition_sql) - Ci::ApplicationRecord.connection.execute(create_partition_sql) - create_detached_partition_sql = <<~SQL CREATE TABLE IF NOT EXISTS #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}._test_gitlab_main_part_202201 ( id bigserial primary key not null ) SQL - ApplicationRecord.connection.execute(create_detached_partition_sql) - Ci::ApplicationRecord.connection.execute(create_detached_partition_sql) + [ApplicationRecord, Ci::ApplicationRecord] + .map(&:connection) + .each do |conn| + conn.execute(create_partition_sql) + conn.execute( + "DROP TABLE IF EXISTS #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}._test_gitlab_main_part_202201" + ) + conn.execute(create_detached_partition_sql) + + Gitlab::Database::SharedModel.using_connection(conn) do + Postgresql::DetachedPartition.delete_all + Postgresql::DetachedPartition.create!( + table_name: '_test_gitlab_main_part_20220101', + drop_after: Time.current + ) + end + end + end - Gitlab::Database::SharedModel.using_connection(ApplicationRecord.connection) do - Postgresql::DetachedPartition.create!( - table_name: '_test_gitlab_main_part_20220101', - drop_after: Time.current - ) - end - Gitlab::Database::SharedModel.using_connection(Ci::ApplicationRecord.connection) do - Postgresql::DetachedPartition.create!( - table_name: '_test_gitlab_main_part_20220101', - drop_after: Time.current - ) - end + after(:all) do + [ApplicationRecord, Ci::ApplicationRecord] + .map(&:connection) + .each do |conn| + conn.execute( + "DROP TABLE IF EXISTS #{Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA}._test_gitlab_main_part_202201" + ) + Gitlab::Database::SharedModel.using_connection(conn) { Postgresql::DetachedPartition.delete_all } + end end shared_examples "lock tables" do |gitlab_schema, database_name| diff --git a/spec/lib/gitlab/database/tables_truncate_spec.rb b/spec/lib/gitlab/database/tables_truncate_spec.rb index e41c7d34378..352c2fff779 100644 --- a/spec/lib/gitlab/database/tables_truncate_spec.rb +++ b/spec/lib/gitlab/database/tables_truncate_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::TablesTruncate, :reestablished_active_record_base, - :suppress_gitlab_schemas_validate_connection, feature_category: :cell do + :suppress_gitlab_schemas_validate_connection, feature_category: :cell do include MigrationsHelpers let(:min_batch_size) { 1 } @@ -373,7 +373,9 @@ RSpec.describe Gitlab::Database::TablesTruncate, :reestablished_active_record_ba context 'with no main data in ci datatabase' do before do # Remove 'main' data in ci database - ci_connection.truncate_tables([:_test_gitlab_main_items, :_test_gitlab_main_references]) + ci_connection.execute( + "TRUNCATE TABLE _test_gitlab_main_items, _test_gitlab_main_references RESTART IDENTITY CASCADE;" + ) end it { is_expected.to eq(false) } diff --git a/spec/lib/gitlab/database/transaction/observer_spec.rb b/spec/lib/gitlab/database/transaction/observer_spec.rb index d1cb014a594..778212add66 100644 --- a/spec/lib/gitlab/database/transaction/observer_spec.rb +++ b/spec/lib/gitlab/database/transaction/observer_spec.rb @@ -25,7 +25,7 @@ RSpec.describe Gitlab::Database::Transaction::Observer, feature_category: :datab User.first expect(transaction_context).to be_a(::Gitlab::Database::Transaction::Context) - expect(context.keys).to match_array(%i(start_time depth savepoints queries backtraces external_http_count_start external_http_duration_start)) + expect(context.keys).to match_array(%i[start_time depth savepoints queries backtraces external_http_count_start external_http_duration_start]) expect(context[:depth]).to eq(2) expect(context[:savepoints]).to eq(1) expect(context[:queries].length).to eq(1) diff --git a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb index 2811bc859da..ef61a962279 100644 --- a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb @@ -46,8 +46,8 @@ RSpec.describe Gitlab::DependencyLinker::BaseLinker do 'target="_blank"' ] - attrs.unshift(%{href="#{url}"}) if url + attrs.unshift(%(href="#{url}")) if url - %{<a #{attrs.join(' ')}>#{text}</a>} + %(<a #{attrs.join(' ')}>#{text}</a>) end end diff --git a/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb index 7f6b3b86799..d147afbf3bd 100644 --- a/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb @@ -70,7 +70,7 @@ RSpec.describe Gitlab::DependencyLinker::CargoTomlLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'links dependencies' do diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb index 52ddba24458..eeb7471cd18 100644 --- a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb @@ -54,7 +54,7 @@ RSpec.describe Gitlab::DependencyLinker::CartfileLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'links dependencies' do diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb index 02fac96a02f..cf636a7f201 100644 --- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb @@ -50,7 +50,7 @@ RSpec.describe Gitlab::DependencyLinker::ComposerJsonLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'does not link the module name' do diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb index 00e95dea224..a5507fab3fe 100644 --- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Gitlab::DependencyLinker::GemfileLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'links sources' do diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb index ae82dd51c95..9f207459113 100644 --- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb @@ -42,7 +42,7 @@ RSpec.describe Gitlab::DependencyLinker::GemspecLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'does not link the gem name' do diff --git a/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb b/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb index 605b14bc923..fbd8b6477a2 100644 --- a/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/go_mod_linker_spec.rb @@ -55,7 +55,7 @@ RSpec.describe Gitlab::DependencyLinker::GoModLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'links the module name' do diff --git a/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb b/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb index 2836c0e9f29..559c27e91ba 100644 --- a/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/go_sum_linker_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Gitlab::DependencyLinker::GoSumLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'links modules' do diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb index c1ed030c548..3ac234f47d9 100644 --- a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb @@ -61,7 +61,7 @@ RSpec.describe Gitlab::DependencyLinker::GodepsJsonLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'links the package name' do diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb index cdfc0e89bc7..127f437dd54 100644 --- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb @@ -51,7 +51,7 @@ RSpec.describe Gitlab::DependencyLinker::PackageJsonLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'does not link the module name' do diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb index 8e536c00ea6..41c29278bda 100644 --- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Gitlab::DependencyLinker::PodfileLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'links sources' do diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb index 1f81049a41e..f8b782a7cda 100644 --- a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb @@ -66,7 +66,7 @@ RSpec.describe Gitlab::DependencyLinker::PodspecJsonLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'links the gem name' do diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb index 132b5b21d85..6f2653829e2 100644 --- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb @@ -41,7 +41,7 @@ RSpec.describe Gitlab::DependencyLinker::PodspecLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'does not link the pod name' do diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb index 86ebddc9681..fc3c57b7cff 100644 --- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb @@ -64,7 +64,7 @@ RSpec.describe Gitlab::DependencyLinker::RequirementsTxtLinker do subject { Gitlab::Highlight.highlight(file_name, file_content) } def link(name, url) - %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>} + %(<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>) end it 'links dependencies' do diff --git a/spec/lib/gitlab/diff/file_collection/compare_spec.rb b/spec/lib/gitlab/diff/file_collection/compare_spec.rb index c3f768db7f0..5469a43e46e 100644 --- a/spec/lib/gitlab/diff/file_collection/compare_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/compare_spec.rb @@ -10,9 +10,11 @@ RSpec.describe Gitlab::Diff::FileCollection::Compare do let(:start_commit) { sample_image_commit } let(:head_commit) { sample_commit } let(:raw_compare) do - Gitlab::Git::Compare.new(project.repository.raw_repository, - start_commit.id, - head_commit.id) + Gitlab::Git::Compare.new( + project.repository.raw_repository, + start_commit.id, + head_commit.id + ) end let(:diffable) { Compare.new(raw_compare, project) } diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb index 8e14f48ae29..65e96f8e936 100644 --- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb @@ -10,10 +10,7 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch, feature_cate let(:diff_files_relation) { diffable.merge_request_diff_files } subject do - described_class.new(diffable, - batch_page, - batch_size, - diff_options: nil) + described_class.new(diffable, batch_page, batch_size, diff_options: nil) end let(:diff_files) { subject.diff_files } @@ -87,10 +84,7 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch, feature_cate context 'last page' do it 'returns correct diff files' do last_page = diff_files_relation.count - batch_size - collection = described_class.new(diffable, - last_page, - batch_size, - diff_options: nil) + collection = described_class.new(diffable, last_page, batch_size, diff_options: nil) expected_batch_files = diff_files_relation.offset(last_page).limit(batch_size).map(&:new_path) @@ -101,10 +95,7 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch, feature_cate it_behaves_like 'unfoldable diff' do subject do - described_class.new(merge_request.merge_request_diff, - batch_page, - batch_size, - diff_options: nil) + described_class.new(merge_request.merge_request_diff, batch_page, batch_size, diff_options: nil) end end @@ -118,10 +109,7 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch, feature_cate let(:stub_path) { '.gitignore' } subject do - described_class.new(merge_request.merge_request_diff, - batch_page, - batch_size, - **collection_default_args) + described_class.new(merge_request.merge_request_diff, batch_page, batch_size, **collection_default_args) end end @@ -136,10 +124,7 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch, feature_cate end subject do - described_class.new(merge_request.merge_request_diff, - batch_page, - batch_size, - **collection_default_args) + described_class.new(merge_request.merge_request_diff, batch_page, batch_size, **collection_default_args) end end end diff --git a/spec/lib/gitlab/diff/file_collection/paginated_merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/paginated_merge_request_diff_spec.rb index ee956d04325..891336658ce 100644 --- a/spec/lib/gitlab/diff/file_collection/paginated_merge_request_diff_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/paginated_merge_request_diff_spec.rb @@ -11,9 +11,7 @@ RSpec.describe Gitlab::Diff::FileCollection::PaginatedMergeRequestDiff, feature_ let(:diff_files) { subject.diff_files } subject do - described_class.new(diffable, - page, - per_page) + described_class.new(diffable, page, per_page) end describe '#diff_files' do @@ -79,9 +77,7 @@ RSpec.describe Gitlab::Diff::FileCollection::PaginatedMergeRequestDiff, feature_ context 'when last page' do it 'returns correct diff files' do last_page = diff_files_relation.count - per_page - collection = described_class.new(diffable, - last_page, - per_page) + collection = described_class.new(diffable, last_page, per_page) expected_batch_files = diff_files_relation.page(last_page).per(per_page).map(&:new_path) @@ -92,9 +88,7 @@ RSpec.describe Gitlab::Diff::FileCollection::PaginatedMergeRequestDiff, feature_ it_behaves_like 'unfoldable diff' do subject do - described_class.new(merge_request.merge_request_diff, - page, - per_page) + described_class.new(merge_request.merge_request_diff, page, per_page) end end @@ -106,9 +100,7 @@ RSpec.describe Gitlab::Diff::FileCollection::PaginatedMergeRequestDiff, feature_ let(:diffable) { merge_request.merge_request_diff } subject do - described_class.new(merge_request.merge_request_diff, - page, - per_page) + described_class.new(merge_request.merge_request_diff, page, per_page) end end end diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index ad2524e40c5..bc4fc49b1b7 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -407,10 +407,7 @@ RSpec.describe Gitlab::Diff::File do context 'diff file stats' do let(:diff_file) do - described_class.new(diff, - diff_refs: commit.diff_refs, - repository: project.repository, - stats: stats) + described_class.new(diff, diff_refs: commit.diff_refs, repository: project.repository, stats: stats) end let(:raw_diff) do diff --git a/spec/lib/gitlab/diff/formatters/file_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/file_formatter_spec.rb index 32e5f17f7eb..fc77ed5d763 100644 --- a/spec/lib/gitlab/diff/formatters/file_formatter_spec.rb +++ b/spec/lib/gitlab/diff/formatters/file_formatter_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Gitlab::Diff::Formatters::FileFormatter, feature_category: :code_ let(:attrs) { base_attrs.merge(old_path: 'path.rb', new_path: 'path.rb') } it_behaves_like 'position formatter' do - # rubocop:disable Fips/SHA1 (This is used to match the existing class method) + # rubocop:disable Fips/SHA1 -- This is used to match the existing class method let(:key) do [123, 456, 789, Digest::SHA1.hexdigest(formatter.old_path), Digest::SHA1.hexdigest(formatter.new_path), diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb index c51eaa4fa18..94a5d30283c 100644 --- a/spec/lib/gitlab/diff/highlight_cache_spec.rb +++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb @@ -49,10 +49,12 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache, feature_ let(:diff_file) do diffs = merge_request.diffs raw_diff = diffs.diffable.raw_diffs(diffs.diff_options.merge(paths: ['CHANGELOG'])).first - Gitlab::Diff::File.new(raw_diff, - repository: diffs.project.repository, - diff_refs: diffs.diff_refs, - fallback_diff_refs: diffs.fallback_diff_refs) + Gitlab::Diff::File.new( + raw_diff, + repository: diffs.project.repository, + diff_refs: diffs.diff_refs, + fallback_diff_refs: diffs.fallback_diff_refs + ) end before do @@ -227,10 +229,12 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache, feature_ let(:diff_file) do diffs = merge_request.diffs raw_diff = diffs.diffable.raw_diffs(diffs.diff_options.merge(paths: ['CHANGELOG'])).first - Gitlab::Diff::File.new(raw_diff, - repository: diffs.project.repository, - diff_refs: diffs.diff_refs, - fallback_diff_refs: diffs.fallback_diff_refs) + Gitlab::Diff::File.new( + raw_diff, + repository: diffs.project.repository, + diff_refs: diffs.diff_refs, + fallback_diff_refs: diffs.fallback_diff_refs + ) end it "uses ActiveSupport::Gzip when reading from the cache" do diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index e39c15c8fd7..e65f5a618a5 100644 --- a/spec/lib/gitlab/diff/highlight_spec.rb +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -44,13 +44,13 @@ RSpec.describe Gitlab::Diff::Highlight, feature_category: :source_code_managemen end it 'highlights and marks removed lines' do - code = %{-<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n} + code = %(-<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n) expect(subject[4].rich_text).to eq(code) end it 'highlights and marks added lines' do - code = %{+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class="idiff left addition">RuntimeError</span></span><span class="p"><span class="idiff addition">,</span></span><span class="idiff right addition"> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n} + code = %(+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class="idiff left addition">RuntimeError</span></span><span class="p"><span class="idiff addition">,</span></span><span class="idiff right addition"> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n) expect(subject[5].rich_text).to eq(code) end @@ -86,14 +86,14 @@ RSpec.describe Gitlab::Diff::Highlight, feature_category: :source_code_managemen end it 'marks removed lines' do - code = %q{- raise "System commands must be given as an array of strings"} + code = %q(- raise "System commands must be given as an array of strings") expect(subject[4].text).to eq(code) expect(subject[4].text).not_to be_html_safe end it 'marks added lines' do - code = %q{+ raise <span class="idiff left right addition">RuntimeError, </span>"System commands must be given as an array of strings"} + code = %q(+ raise <span class="idiff left right addition">RuntimeError, </span>"System commands must be given as an array of strings") expect(subject[5].rich_text).to eq(code) expect(subject[5].rich_text).to be_html_safe @@ -107,7 +107,7 @@ RSpec.describe Gitlab::Diff::Highlight, feature_category: :source_code_managemen it 'keeps the original rich line' do allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception) - code = %q{+ raise RuntimeError, "System commands must be given as an array of strings"} + code = %q(+ raise RuntimeError, "System commands must be given as an array of strings") expect(subject[5].text).to eq(code) expect(subject[5].text).not_to be_html_safe diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb index 8ab2a7b64dd..602f1b1c2b2 100644 --- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb +++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb @@ -10,10 +10,10 @@ RSpec.describe Gitlab::Diff::InlineDiffMarker do subject { described_class.new(raw, rich).mark(inline_diffs) } context "when the rich text is html safe" do - let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">'def'</span>}.html_safe } + let(:rich) { %(<span class="abc">abc</span><span class="space"> </span><span class="def">'def'</span>).html_safe } it 'marks the range' do - expect(subject).to eq(%{<span class="abc">ab<span class="idiff left">c</span></span><span class="space"><span class="idiff"> </span></span><span class="def"><span class="idiff right">'d</span>ef'</span>}) + expect(subject).to eq(%(<span class="abc">ab<span class="idiff left">c</span></span><span class="space"><span class="idiff"> </span></span><span class="def"><span class="idiff right">'d</span>ef'</span>)) expect(subject).to be_html_safe end end @@ -22,7 +22,7 @@ RSpec.describe Gitlab::Diff::InlineDiffMarker do let(:rich) { "abc 'def' differs" } it 'marks the range' do - expect(subject).to eq(%{ab<span class="idiff left right">c 'd</span>ef' differs}) + expect(subject).to eq(%(ab<span class="idiff left right">c 'd</span>ef' differs)) expect(subject).to be_html_safe end end diff --git a/spec/lib/gitlab/diff/line_spec.rb b/spec/lib/gitlab/diff/line_spec.rb index 949def599ae..b23ba3a0f00 100644 --- a/spec/lib/gitlab/diff/line_spec.rb +++ b/spec/lib/gitlab/diff/line_spec.rb @@ -11,10 +11,16 @@ RSpec.describe Gitlab::Diff::Line do end let(:line) do - described_class.new('<input>', 'match', 0, 0, 1, - parent_file: double(:file), - line_code: double(:line_code), - rich_text: rich_text) + described_class.new( + '<input>', + 'match', + 0, + 0, + 1, + parent_file: double(:file), + line_code: double(:line_code), + rich_text: rich_text + ) end let(:rich_text) { nil } diff --git a/spec/lib/gitlab/diff/suggestion_diff_spec.rb b/spec/lib/gitlab/diff/suggestion_diff_spec.rb index 9546c581112..f9d56662753 100644 --- a/spec/lib/gitlab/diff/suggestion_diff_spec.rb +++ b/spec/lib/gitlab/diff/suggestion_diff_spec.rb @@ -22,9 +22,7 @@ RSpec.describe Gitlab::Diff::SuggestionDiff do end let(:suggestion) do - instance_double(Suggestion, from_line: 12, - from_content: from_content, - to_content: to_content) + instance_double(Suggestion, from_line: 12, from_content: from_content, to_content: to_content) end subject { described_class.new(suggestion).diff_lines } @@ -56,9 +54,12 @@ RSpec.describe Gitlab::Diff::SuggestionDiff do it 'returns a correct value if there is no newline at the end of the file' do from_content = "One line test" to_content = "Successful test!" - suggestion = instance_double(Suggestion, from_line: 1, - from_content: from_content, - to_content: to_content) + suggestion = instance_double( + Suggestion, + from_line: 1, + from_content: from_content, + to_content: to_content + ) diff_lines = described_class.new(suggestion).diff_lines diff --git a/spec/lib/gitlab/diff/suggestion_spec.rb b/spec/lib/gitlab/diff/suggestion_spec.rb index 40779faf917..9f654c44852 100644 --- a/spec/lib/gitlab/diff/suggestion_spec.rb +++ b/spec/lib/gitlab/diff/suggestion_spec.rb @@ -5,10 +5,12 @@ require 'spec_helper' RSpec.describe Gitlab::Diff::Suggestion do shared_examples 'correct suggestion raw content' do it 'returns correct raw data' do - expect(suggestion.to_hash).to include(from_content: expected_lines.join, - to_content: "#{text}\n", - lines_above: above, - lines_below: below) + expect(suggestion.to_hash).to include( + from_content: expected_lines.join, + to_content: "#{text}\n", + lines_above: above, + lines_below: below + ) end it 'returns diff lines with correct line numbers' do @@ -25,11 +27,13 @@ RSpec.describe Gitlab::Diff::Suggestion do let(:merge_request) { create(:merge_request) } let(:project) { merge_request.project } let(:position) do - Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", - new_path: "files/ruby/popen.rb", - old_line: nil, - new_line: 9, - diff_refs: merge_request.diff_refs) + Gitlab::Diff::Position.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 9, + diff_refs: merge_request.diff_refs + ) end let(:diff_file) do diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb index a00c55d4fb2..ef845dbdc4c 100644 --- a/spec/lib/gitlab/diff/suggestions_parser_spec.rb +++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb @@ -7,11 +7,13 @@ RSpec.describe Gitlab::Diff::SuggestionsParser do let(:merge_request) { create(:merge_request) } let(:project) { merge_request.project } let(:position) do - Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", - new_path: "files/ruby/popen.rb", - old_line: nil, - new_line: 9, - diff_refs: merge_request.diff_refs) + Gitlab::Diff::Position.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 9, + diff_refs: merge_request.diff_refs + ) end let(:diff_file) do @@ -19,8 +21,7 @@ RSpec.describe Gitlab::Diff::SuggestionsParser do end subject do - described_class.parse(markdown, project: merge_request.project, - position: position) + described_class.parse(markdown, project: merge_request.project, position: position) end def blob_lines_data(from_line, to_line) @@ -59,15 +60,19 @@ RSpec.describe Gitlab::Diff::SuggestionsParser do from_line = position.new_line to_line = position.new_line - expect(subject.first.to_hash).to include(from_content: blob_lines_data(from_line, to_line), - to_content: " foo\n bar\n", - lines_above: 0, - lines_below: 0) - - expect(subject.second.to_hash).to include(from_content: blob_lines_data(from_line, to_line), - to_content: " xpto\n baz\n", - lines_above: 0, - lines_below: 0) + expect(subject.first.to_hash).to include( + from_content: blob_lines_data(from_line, to_line), + to_content: " foo\n bar\n", + lines_above: 0, + lines_below: 0 + ) + + expect(subject.second.to_hash).to include( + from_content: blob_lines_data(from_line, to_line), + to_content: " xpto\n baz\n", + lines_above: 0, + lines_below: 0 + ) end end @@ -105,30 +110,36 @@ RSpec.describe Gitlab::Diff::SuggestionsParser do from_line = position.new_line - 2 to_line = position.new_line + 1 - expect(subject.first.to_hash).to include(from_content: blob_lines_data(from_line, to_line), - to_content: " # above and below\n", - lines_above: 2, - lines_below: 1) + expect(subject.first.to_hash).to include( + from_content: blob_lines_data(from_line, to_line), + to_content: " # above and below\n", + lines_above: 2, + lines_below: 1 + ) end it 'suggestion with above param has correct data' do from_line = position.new_line - 3 to_line = position.new_line - expect(subject.second.to_hash).to eq(from_content: blob_lines_data(from_line, to_line), - to_content: " # only above\n", - lines_above: 3, - lines_below: 0) + expect(subject.second.to_hash).to eq( + from_content: blob_lines_data(from_line, to_line), + to_content: " # only above\n", + lines_above: 3, + lines_below: 0 + ) end it 'suggestion with below param has correct data' do from_line = position.new_line to_line = position.new_line + 3 - expect(subject.third.to_hash).to eq(from_content: blob_lines_data(from_line, to_line), - to_content: " # only below\n", - lines_above: 0, - lines_below: 3) + expect(subject.third.to_hash).to eq( + from_content: blob_lines_data(from_line, to_line), + to_content: " # only below\n", + lines_above: 0, + lines_below: 3 + ) end end end diff --git a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb index f85a68ada15..a1d9a861355 100644 --- a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb +++ b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::DiscussionsDiff::FileCollection do +RSpec.describe Gitlab::DiscussionsDiff::FileCollection, :clean_gitlab_redis_shared_state do let(:merge_request) { create(:merge_request) } let!(:diff_note_a) { create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request) } let!(:diff_note_b) { create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request) } @@ -11,7 +11,18 @@ RSpec.describe Gitlab::DiscussionsDiff::FileCollection do subject { described_class.new([note_diff_file_a, note_diff_file_b]) } - describe '#load_highlight', :clean_gitlab_redis_shared_state do + describe '#load_highlight' do + it 'only takes into account for the specific diff note ids' do + file_a_caching_content = diff_note_a.diff_file.highlighted_diff_lines.map(&:to_hash) + + expect(Gitlab::DiscussionsDiff::HighlightCache) + .to receive(:write_multiple) + .with({ note_diff_file_a.id => file_a_caching_content }) + .and_call_original + + subject.load_highlight(diff_note_ids: [note_diff_file_a.diff_note_id]) + end + it 'writes uncached diffs highlight' do file_a_caching_content = diff_note_a.diff_file.highlighted_diff_lines.map(&:to_hash) file_b_caching_content = diff_note_b.diff_file.highlighted_diff_lines.map(&:to_hash) diff --git a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb index e6fff939632..f13fd0be4cd 100644 --- a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb @@ -8,12 +8,14 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se before do stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo") + stub_service_desk_email_setting(enabled: true, address: "contact+%{key}@example.com") stub_config_setting(host: 'localhost') end let(:email_raw) { email_fixture('emails/service_desk.eml') } let(:author_email) { 'jake@adventuretime.ooo' } let(:message_id) { 'CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com' } + let(:issue_email_participants_count) { 1 } let_it_be(:group) { create(:group, :private, :crm_enabled, name: "email") } @@ -22,7 +24,7 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se end context 'service desk is enabled for the project' do - let_it_be(:project) { create(:project, :repository, :private, group: group, path: 'test', service_desk_enabled: true) } + let_it_be_with_reload(:project) { create(:project, :repository, :private, group: group, path: 'test', service_desk_enabled: true) } before do allow(Gitlab::ServiceDesk).to receive(:supported?).and_return(true) @@ -50,6 +52,7 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se receiver.execute new_issue = Issue.last + expect(new_issue.issue_email_participants.count).to eq(issue_email_participants_count) expect(new_issue.issue_email_participants.first.email).to eq(author_email) end @@ -96,6 +99,72 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se expect(new_issue.issue_customer_relations_contacts.map(&:contact)).to contain_exactly(contact, contact2, contact3) end + context 'when add_external_participants_from_cc is true' do + shared_examples 'does not add CC address' do + it 'creates a new issue and adds issue_email_participant from From header' do + expect { receiver.execute }.to change { Issue.count }.by(1) + expect(Issue.last.issue_email_participants.map(&:email)).to match_array(%w[from@example.com]) + end + end + + let_it_be(:setting) { create(:service_desk_setting, project: project, add_external_participants_from_cc: true) } + + # Original email contains two CC email addresses + let(:issue_email_participants_count) { 3 } + let(:to_address) { project.service_desk_incoming_address } + + it_behaves_like 'a new issue request' + + context 'when no CC header is present' do + let(:email_raw) do + <<~EMAIL + From: from@example.com + To: #{to_address} + Subject: Issue title + + Issue description + EMAIL + end + + it_behaves_like 'does not add CC address' + end + + context 'when service desk system address is in CC' do + let(:cc_address) { project.service_desk_incoming_address } + let(:email_raw) do + <<~EMAIL + From: from@example.com + To: #{to_address} + Cc: #{cc_address} + Subject: Issue title + + Issue description + EMAIL + end + + it_behaves_like 'does not add CC address' + + context 'when service_desk_email is part of CC' do + let(:cc_address) { project.service_desk_alias_address } + + it_behaves_like 'does not add CC address' + end + + context 'when custom email is part of CC' do + let!(:credential) { create(:service_desk_custom_email_credential, project: project) } + let!(:verification) { create(:service_desk_custom_email_verification, :finished, project: project) } + let(:cc_address) { project.service_desk_custom_address } + + before do + project.reset + setting.update!(custom_email: 'support@example.com', custom_email_enabled: true) + end + + it_behaves_like 'does not add CC address' + end + end + end + context 'with legacy incoming email address' do let(:email_raw) { fixture_file('emails/service_desk_legacy.eml') } @@ -140,25 +209,26 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se subject end - context 'when issue_email_participants FF is enabled' do - it 'creates 2 issue_email_participants' do - subject + it 'creates issue_email_participants for author and reply author' do + subject - expect(Issue.last.issue_email_participants.map(&:email)) - .to match_array(%w(alan@adventuretime.ooo jake@adventuretime.ooo)) - end + # 1 from issue creation + # 1 from new note reply + expect(Issue.last.issue_email_participants.map(&:email)) + .to match_array(%w[alan@adventuretime.ooo jake@adventuretime.ooo]) end context 'when issue_email_participants FF is disabled' do before do + # Was turned off after issue creation stub_feature_flags(issue_email_participants: false) end - it 'creates only 1 issue_email_participant' do + it 'creates issue_email_participant for the author' do subject expect(Issue.last.issue_email_participants.map(&:email)) - .to match_array(%w(jake@adventuretime.ooo)) + .to match_array(%w[jake@adventuretime.ooo]) end end end @@ -182,11 +252,11 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se subject end - it 'creates 1 issue_email_participant' do + it 'creates issue_email_participant for the author' do subject expect(Issue.last.issue_email_participants.map(&:email)) - .to match_array(%w(alan@adventuretime.ooo)) + .to match_array(%w[alan@adventuretime.ooo]) end end end diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb index d3a4d77c58e..47907401a63 100644 --- a/spec/lib/gitlab/email/handler_spec.rb +++ b/spec/lib/gitlab/email/handler_spec.rb @@ -60,10 +60,10 @@ RSpec.describe Gitlab::Email::Handler do describe 'regexps are set properly' do let(:addresses) do - %W(sent_notification_key#{Gitlab::Email::Common::UNSUBSCRIBE_SUFFIX} sent_notification_key#{Gitlab::Email::Common::UNSUBSCRIBE_SUFFIX_LEGACY}) + - %w(sent_notification_key path-to-project-123-user_email_token-merge-request) + - %w(path-to-project-123-user_email_token-issue path-to-project-123-user_email_token-issue-123) + - %w(path/to/project+user_email_token path/to/project+merge-request+user_email_token some/project) + %W[sent_notification_key#{Gitlab::Email::Common::UNSUBSCRIBE_SUFFIX} sent_notification_key#{Gitlab::Email::Common::UNSUBSCRIBE_SUFFIX_LEGACY}] + + %w[sent_notification_key path-to-project-123-user_email_token-merge-request] + + %w[path-to-project-123-user_email_token-issue path-to-project-123-user_email_token-issue-123] + + %w[path/to/project+user_email_token path/to/project+merge-request+user_email_token some/project] end before do diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index f8084d24850..c86a83092a4 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -33,7 +33,7 @@ RSpec.describe Gitlab::Email::Receiver do metadata = receiver.mail_metadata - expect(metadata.keys).to match_array(%i(mail_uid from_address to_address mail_key references delivered_to envelope_to x_envelope_to meta received_recipients cc_address)) + expect(metadata.keys).to match_array(%i[mail_uid from_address to_address mail_key references delivered_to envelope_to x_envelope_to meta received_recipients cc_address]) expect(metadata[:meta]).to include(client_id: client_id, project: project.full_path) expect(metadata[meta_key]).to eq(meta_value) end diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index 1b7c11dfef6..db7961fc0c9 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -214,7 +214,7 @@ RSpec.describe Gitlab::EncodingHelper, feature_category: :shared do [nil, ""], ["", ""], [" ", " "], - %w(a1 a1), + %w[a1 a1], ["编码", "\xE7\xBC\x96\xE7\xA0\x81".b] ].each do |input, result| it "encodes #{input.inspect} to #{result.inspect}" do diff --git a/spec/lib/gitlab/endpoint_attributes_spec.rb b/spec/lib/gitlab/endpoint_attributes_spec.rb index a623070c3eb..34f4221b86a 100644 --- a/spec/lib/gitlab/endpoint_attributes_spec.rb +++ b/spec/lib/gitlab/endpoint_attributes_spec.rb @@ -11,19 +11,19 @@ RSpec.describe Gitlab::EndpointAttributes, feature_category: :api do let(:controller) do Class.new(base_controller) do - feature_category :foo, %w(update edit) - feature_category :bar, %w(index show) - feature_category :quux, %w(destroy) + feature_category :foo, %w[update edit] + feature_category :bar, %w[index show] + feature_category :quux, %w[destroy] - urgency :high, %w(do_a) - urgency :low, %w(do_b do_c) + urgency :high, %w[do_a] + urgency :low, %w[do_b do_c] end end let(:subclass) do Class.new(controller) do - feature_category :baz, %w(subclass_index) - urgency :high, %w(superclass_do_something) + feature_category :baz, %w[subclass_index] + urgency :high, %w[superclass_do_something] end end diff --git a/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb b/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb index a854adca32b..eae6186e789 100644 --- a/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb +++ b/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Gitlab::ErrorTracking::StackTraceHighlightDecorator do [11, '<span id="LC1" class="line" lang="ruby"><span class="k">class</span> <span class="nc">HelloWorld</span></span>'], [12, '<span id="LC1" class="line" lang="ruby"> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">message</span></span>'], [13, '<span id="LC1" class="line" lang="ruby"> <span class="vi">@name</span> <span class="o">=</span> <span class="s1">\'World\'</span></span>'], - [14, %[<span id="LC1" class="line" lang="ruby"> <span class="nb">puts</span> <span class="s2">"Hello </span><span class="si">\#{</span><span class="vi">@name</span><span class="si">}</span><span class="s2">"</span></span>]], + [14, %(<span id="LC1" class="line" lang="ruby"> <span class="nb">puts</span> <span class="s2">"Hello </span><span class="si">\#{</span><span class="vi">@name</span><span class="si">}</span><span class="s2">"</span></span>)], [15, '<span id="LC1" class="line" lang="ruby"> <span class="k">end</span></span>'], [16, '<span id="LC1" class="line" lang="ruby"><span class="k">end</span></span>'] ] diff --git a/spec/lib/gitlab/external_authorization/client_spec.rb b/spec/lib/gitlab/external_authorization/client_spec.rb index b907b0bb262..b507fe7bde8 100644 --- a/spec/lib/gitlab/external_authorization/client_spec.rb +++ b/spec/lib/gitlab/external_authorization/client_spec.rb @@ -109,7 +109,7 @@ RSpec.describe Gitlab::ExternalAuthorization::Client do describe 'for non-ldap users with identities' do before do - %w(twitter facebook).each do |provider| + %w[twitter facebook].each do |provider| create(:identity, provider: provider, extern_uid: "#{provider}_external_id", user: user) end end diff --git a/spec/lib/gitlab/favicon_spec.rb b/spec/lib/gitlab/favicon_spec.rb index 033fa5d1b42..62071293764 100644 --- a/spec/lib/gitlab/favicon_spec.rb +++ b/spec/lib/gitlab/favicon_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Gitlab::Favicon, :request_store do subject { described_class.available_status_names } it 'returns the available status names' do - expect(subject).to eq %w( + expect(subject).to eq %w[ favicon_status_canceled favicon_status_created favicon_status_failed @@ -73,7 +73,7 @@ RSpec.describe Gitlab::Favicon, :request_store do favicon_status_skipped favicon_status_success favicon_status_warning - ) + ] end end end diff --git a/spec/lib/gitlab/feature_categories_spec.rb b/spec/lib/gitlab/feature_categories_spec.rb index a35166a4499..11ddd08c968 100644 --- a/spec/lib/gitlab/feature_categories_spec.rb +++ b/spec/lib/gitlab/feature_categories_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::FeatureCategories do - let(:fake_categories) { %w(foo bar) } + let(:fake_categories) { %w[foo bar] } subject(:feature_categories) { described_class.new(fake_categories) } diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb index 208acf28cc4..55bb1804d86 100644 --- a/spec/lib/gitlab/file_detector_spec.rb +++ b/spec/lib/gitlab/file_detector_spec.rb @@ -1,17 +1,17 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Gitlab::FileDetector do describe '.types_in_paths' do it 'returns the file types for the given paths' do - expect(described_class.types_in_paths(%w(README.md CHANGELOG VERSION VERSION))) - .to eq(%i{readme changelog version}) + expect(described_class.types_in_paths(%w[README.md CHANGELOG VERSION VERSION])) + .to eq(%i[readme changelog version]) end it 'does not include unrecognized file paths' do - expect(described_class.types_in_paths(%w(README.md foo.txt))) - .to eq(%i{readme}) + expect(described_class.types_in_paths(%w[README.md foo.txt])) + .to eq(%i[readme]) end end @@ -25,7 +25,7 @@ RSpec.describe Gitlab::FileDetector do extensions = ['txt', *Gitlab::MarkupHelper::EXTENSIONS] extensions.each do |ext| - %w(index readme).each do |file| + %w[index readme].each do |file| expect(described_class.type_of("#{file}.#{ext}")).to eq(:readme) end end @@ -45,13 +45,13 @@ RSpec.describe Gitlab::FileDetector do end it 'returns the type of a changelog file' do - %w(CHANGELOG HISTORY CHANGES NEWS).each do |file| + %w[CHANGELOG HISTORY CHANGES NEWS].each do |file| expect(described_class.type_of(file)).to eq(:changelog) end end it 'returns the type of a license file' do - %w(LICENSE LICENCE COPYING UNLICENSE UNLICENCE).each do |file| + %w[LICENSE LICENCE COPYING UNLICENSE UNLICENCE].each do |file| expect(described_class.type_of(file)).to eq(:license) end end @@ -73,7 +73,7 @@ RSpec.describe Gitlab::FileDetector do end it 'returns the type of an avatar' do - %w(logo.gif logo.png logo.jpg).each do |file| + %w[logo.gif logo.png logo.jpg].each do |file| expect(described_class.type_of(file)).to eq(:avatar) end end diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index 6de7cab9c42..75427ac0402 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -78,13 +78,13 @@ RSpec.describe Gitlab::Gfm::ReferenceRewriter do context 'label referenced by id' do let(:text) { '#1 and ~123' } - it { is_expected.to eq %{#{old_project_ref}#1 and #{old_project_ref}~123} } + it { is_expected.to eq %(#{old_project_ref}#1 and #{old_project_ref}~123) } end context 'label referenced by text' do let(:text) { '#1 and ~"test"' } - it { is_expected.to eq %{#{old_project_ref}#1 and #{old_project_ref}~123} } + it { is_expected.to eq %(#{old_project_ref}#1 and #{old_project_ref}~123) } end end @@ -99,13 +99,13 @@ RSpec.describe Gitlab::Gfm::ReferenceRewriter do context 'label referenced by id' do let(:text) { '#1 and ~321' } - it { is_expected.to eq %{#{old_project_ref}#1 and #{old_project_ref}~321} } + it { is_expected.to eq %(#{old_project_ref}#1 and #{old_project_ref}~321) } end context 'label referenced by text' do let(:text) { '#1 and ~"group label"' } - it { is_expected.to eq %{#{old_project_ref}#1 and #{old_project_ref}~321} } + it { is_expected.to eq %(#{old_project_ref}#1 and #{old_project_ref}~321) } end end end @@ -149,7 +149,7 @@ RSpec.describe Gitlab::Gfm::ReferenceRewriter do let(:text) { 'milestone: %"9.0"' } - it { is_expected.to eq %[milestone: #{old_project_ref}%"9.0"] } + it { is_expected.to eq %(milestone: #{old_project_ref}%"9.0") } end context 'when referring to group milestone' do diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb index 77361b09857..751611be5d2 100644 --- a/spec/lib/gitlab/git/blame_spec.rb +++ b/spec/lib/gitlab/git/blame_spec.rb @@ -48,6 +48,14 @@ RSpec.describe Gitlab::Git::Blame, feature_category: :source_code_management do end end + context 'when path is missing' do + let(:path) { 'unknown_file' } + + it 'returns an empty array' do + expect(result).to eq([]) + end + end + context "ISO-8859 encoding" do let(:path) { 'encoding/iso8859.txt' } diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 5bb4b84835d..59cf87ddc7e 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -154,18 +154,6 @@ RSpec.describe Gitlab::Git::Blob do it_behaves_like '.find' end - describe '.find with Rugged enabled', :enable_rugged do - it 'calls out to the Rugged implementation' do - allow_next_instance_of(Rugged) do |instance| - allow(instance).to receive(:rev_parse).with(TestEnv::BRANCH_SHA['master']).and_call_original - end - - described_class.find(repository, TestEnv::BRANCH_SHA['master'], 'files/images/6049019_460s.jpg') - end - - it_behaves_like '.find' - end - describe '.raw' do let(:raw_blob) { described_class.raw(repository, SeedRepo::RubyBlob::ID) } let(:bad_blob) { described_class.raw(repository, SeedRepo::BigCommit::ID) } diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 5c4be1003c3..d8d62ac9670 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -160,18 +160,6 @@ RSpec.describe Gitlab::Git::Commit, feature_category: :source_code_management do it_behaves_like '.find' end - describe '.find with Rugged enabled', :enable_rugged do - it 'calls out to the Rugged implementation' do - allow_next_instance_of(Rugged) do |instance| - allow(instance).to receive(:rev_parse).with(SeedRepo::Commit::ID).and_call_original - end - - described_class.find(repository, SeedRepo::Commit::ID) - end - - it_behaves_like '.find' - end - describe '.last_for_path' do context 'no path' do subject { described_class.last_for_path(repository, 'master') } @@ -459,18 +447,6 @@ RSpec.describe Gitlab::Git::Commit, feature_category: :source_code_management do end end - describe '.batch_by_oid with Rugged enabled', :enable_rugged do - it_behaves_like '.batch_by_oid' - - it 'calls out to the Rugged implementation' do - allow_next_instance_of(Rugged) do |instance| - allow(instance).to receive(:rev_parse).with(SeedRepo::Commit::ID).and_call_original - end - - described_class.batch_by_oid(repository, [SeedRepo::Commit::ID]) - end - end - describe '.extract_signature_lazily' do subject { described_class.extract_signature_lazily(repository, commit_id).itself } diff --git a/spec/lib/gitlab/git/merge_base_spec.rb b/spec/lib/gitlab/git/merge_base_spec.rb index fda2232c2c3..cbe47aae852 100644 --- a/spec/lib/gitlab/git/merge_base_spec.rb +++ b/spec/lib/gitlab/git/merge_base_spec.rb @@ -11,13 +11,13 @@ RSpec.describe Gitlab::Git::MergeBase do shared_context 'existing refs with a merge base', :existing_refs do let(:refs) do - %w(304d257dcb821665ab5110318fc58a007bd104ed 0031876facac3f2b2702a0e53a26e89939a42209) + %w[304d257dcb821665ab5110318fc58a007bd104ed 0031876facac3f2b2702a0e53a26e89939a42209] end end shared_context 'when passing a missing ref', :missing_ref do let(:refs) do - %w(304d257dcb821665ab5110318fc58a007bd104ed aaaa) + %w[304d257dcb821665ab5110318fc58a007bd104ed aaaa] end end @@ -51,13 +51,13 @@ RSpec.describe Gitlab::Git::MergeBase do end it 'returns a merge base when passing 2 branch names' do - merge_base = described_class.new(repository, %w(master feature)) + merge_base = described_class.new(repository, %w[master feature]) expect(merge_base.sha).to be_present end it 'returns a merge base when passing a tag name' do - merge_base = described_class.new(repository, %w(master v1.0.0)) + merge_base = described_class.new(repository, %w[master v1.0.0]) expect(merge_base.sha).to be_present end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 47b5986cfd8..5791d9c524f 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -2435,7 +2435,7 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen end it 'deletes all refs except those with the specified prefixes' do - repository.delete_all_refs_except(%w(refs/keep refs/also-keep refs/heads)) + repository.delete_all_refs_except(%w[refs/keep refs/also-keep refs/heads]) expect(repository.ref_exists?("refs/delete/a")).to be(false) expect(repository.ref_exists?("refs/also-delete/b")).to be(false) expect(repository.ref_exists?("refs/keep/c")).to be(true) @@ -2722,15 +2722,15 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen describe '#check_objects_exist' do it 'returns hash specifying which object exists in repo' do - refs_exist = %w( + refs_exist = %w[ b83d6e391c22777fca1ed3012fce84f633d7fed0 498214de67004b1da3d820901307bed2a68a8ef6 1b12f15a11fc6e62177bef08f47bc7b5ce50b141 - ) - refs_dont_exist = %w( + ] + refs_dont_exist = %w[ 1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 - ) + ] object_existence_map = repository.check_objects_exist(refs_exist + refs_dont_exist) expect(object_existence_map).to eq({ 'b83d6e391c22777fca1ed3012fce84f633d7fed0' => true, diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb deleted file mode 100644 index d5a0ab3d5e0..00000000000 --- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb +++ /dev/null @@ -1,116 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, feature_category: :gitaly do - let(:project) { create(:project, :repository) } - let(:repository) { project.repository } - let(:feature_flag_name) { wrapper.rugged_feature_keys.first } - - subject(:wrapper) do - klazz = Class.new do - include Gitlab::Git::RuggedImpl::UseRugged - - def rugged_test(ref, test_number); end - end - - klazz.new - end - - describe '#execute_rugged_call', :request_store do - let(:args) { ['refs/heads/master', 1] } - - before do - allow(Gitlab::PerformanceBar).to receive(:enabled_for_request?).and_return(true) - end - - it 'instruments Rugged call' do - expect(subject).to receive(:rugged_test).with(args) - - subject.execute_rugged_call(:rugged_test, args) - - expect(Gitlab::RuggedInstrumentation.query_count).to eq(1) - expect(Gitlab::RuggedInstrumentation.list_call_details.count).to eq(1) - end - end - - describe '#use_rugged?' do - it 'returns false' do - expect(subject.use_rugged?(repository, feature_flag_name)).to be false - end - end - - describe '#running_puma_with_multiple_threads?' do - context 'when using Puma' do - before do - stub_const('::Puma', double('puma constant')) - allow(Gitlab::Runtime).to receive(:puma?).and_return(true) - end - - it "returns false when Puma doesn't support the cli_config method" do - allow(::Puma).to receive(:respond_to?).with(:cli_config).and_return(false) - - expect(subject.running_puma_with_multiple_threads?).to be_falsey - end - - it 'returns false for single thread Puma' do - allow(::Puma).to receive_message_chain(:cli_config, :options).and_return(max_threads: 1) - - expect(subject.running_puma_with_multiple_threads?).to be false - end - - it 'returns true for multi-threaded Puma' do - allow(::Puma).to receive_message_chain(:cli_config, :options).and_return(max_threads: 2) - - expect(subject.running_puma_with_multiple_threads?).to be true - end - end - - context 'when not using Puma' do - before do - allow(Gitlab::Runtime).to receive(:puma?).and_return(false) - end - - it 'returns false' do - expect(subject.running_puma_with_multiple_threads?).to be false - end - end - end - - describe '#rugged_enabled_through_feature_flag?' do - subject { wrapper.send(:rugged_enabled_through_feature_flag?) } - - before do - allow(Feature).to receive(:enabled?).with(:feature_key_1).and_return(true) - allow(Feature).to receive(:enabled?).with(:feature_key_2).and_return(true) - allow(Feature).to receive(:enabled?).with(:feature_key_3).and_return(false) - allow(Feature).to receive(:enabled?).with(:feature_key_4).and_return(false) - - stub_const('Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS', feature_keys) - end - - context 'no feature keys given' do - let(:feature_keys) { [] } - - it { is_expected.to be_falsey } - end - - context 'all features are enabled' do - let(:feature_keys) { [:feature_key_1, :feature_key_2] } - - it { is_expected.to be_falsey } - end - - context 'all features are not enabled' do - let(:feature_keys) { [:feature_key_3, :feature_key_4] } - - it { is_expected.to be_falsey } - end - - context 'some feature is enabled' do - let(:feature_keys) { [:feature_key_4, :feature_key_2] } - - it { is_expected.to be_falsey } - end - end -end diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 9675e48a77f..090f9af2620 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -192,122 +192,4 @@ RSpec.describe Gitlab::Git::Tree, feature_category: :source_code_management do end end end - - describe '.where with Rugged enabled', :enable_rugged do - it 'does not call to the Rugged implementation' do - allow_next_instance_of(Rugged) do |instance| - allow(instance).not_to receive(:lookup) - end - - described_class.where(repository, SeedRepo::Commit::ID, 'files', false, false) - end - - it_behaves_like 'repo' do - describe 'Pagination' do - context 'with restrictive limit' do - let(:pagination_params) { { limit: 3, page_token: nil } } - - it 'returns limited paginated list of tree objects' do - expect(entries.count).to eq(3) - expect(cursor.next_cursor).to be_present - end - end - - context 'when limit is equal to number of entries' do - let(:entries_count) { entries.count } - - it 'returns all entries with a cursor' do - result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: entries_count, page_token: nil }) - - expect(cursor).to eq(Gitaly::PaginationCursor.new) - expect(result.entries.count).to eq(entries_count) - end - end - - context 'when limit is 0' do - let(:pagination_params) { { limit: 0, page_token: nil } } - - it 'returns empty result' do - expect(entries).to eq([]) - expect(cursor).to be_nil - end - end - - context 'when limit is missing' do - let(:pagination_params) { { limit: nil, page_token: nil } } - - it 'returns all entries' do - expect(entries.count).to be < 20 - expect(cursor).to eq(Gitaly::PaginationCursor.new) - end - end - - context 'when limit is negative' do - let(:entries_count) { entries.count } - - it 'returns all entries' do - result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: -1, page_token: nil }) - - expect(result.count).to eq(entries_count) - expect(cursor).to eq(Gitaly::PaginationCursor.new) - end - - context 'when token is provided' do - let(:pagination_params) { { limit: 1000, page_token: nil } } - let(:token) { entries.second.id } - - it 'returns all entries after token' do - result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: -1, page_token: token }) - - expect(result.count).to eq(entries.count - 2) - expect(cursor).to eq(Gitaly::PaginationCursor.new) - end - end - end - - context 'when token does not exist' do - let(:pagination_params) { { limit: 5, page_token: 'aabbccdd' } } - - it 'raises a command error' do - expect { entries }.to raise_error(Gitlab::Git::CommandError, /could not find starting OID: aabbccdd/) - end - end - - context 'when limit is bigger than number of entries' do - let(:pagination_params) { { limit: 1000, page_token: nil } } - - it 'returns only available entries' do - expect(entries.count).to be < 20 - expect(cursor).to eq(Gitaly::PaginationCursor.new) - end - end - - it 'returns all tree entries in specific order during cursor pagination' do - collected_entries = [] - token = nil - - expected_entries = entries - - loop do - result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: 5, page_token: token }) - - collected_entries += result.entries - token = cursor&.next_cursor - - break if token.blank? - end - - expect(collected_entries.map(&:path)).to match_array(expected_entries.map(&:path)) - - expected_order = [ - collected_entries.select(&:dir?).map(&:path), - collected_entries.select(&:file?).map(&:path), - collected_entries.select(&:submodule?).map(&:path) - ].flatten - - expect(collected_entries.map(&:path)).to eq(expected_order) - end - end - end - end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 1b205aa5c85..975e8bdd3ac 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -957,7 +957,7 @@ RSpec.describe Gitlab::GitAccess, :aggregate_failures, feature_category: :system } } - [%w(feature exact), ['feat*', 'wildcard']].each do |protected_branch_name, protected_branch_type| + [%w[feature exact], ['feat*', 'wildcard']].each do |protected_branch_name, protected_branch_type| context do let(:who_can_action) { :maintainers_can_push } let(:protected_branch) { create(:protected_branch, who_can_action, name: protected_branch_name, project: project) } diff --git a/spec/lib/gitlab/git_audit_event_spec.rb b/spec/lib/gitlab/git_audit_event_spec.rb deleted file mode 100644 index c533b39f550..00000000000 --- a/spec/lib/gitlab/git_audit_event_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::GitAuditEvent, feature_category: :source_code_management do - let_it_be(:player) { create(:user) } - let_it_be(:group) { create(:group, :public) } - let_it_be(:project) { create(:project) } - - subject { described_class.new(player, project) } - - describe '#send_audit_event' do - let(:msg) { 'valid_msg' } - - context 'with successfully sending' do - let_it_be(:project) { create(:project, namespace: group) } - - before do - allow(::Gitlab::Audit::Auditor).to receive(:audit) - end - - context 'when player is a regular user' do - it 'sends git audit event' do - expect(::Gitlab::Audit::Auditor).to receive(:audit).with(a_hash_including( - name: 'repository_git_operation', - stream_only: true, - author: player, - scope: project, - target: project, - message: msg - )).once - - subject.send_audit_event(msg) - end - end - - context 'when player is ::API::Support::GitAccessActor' do - let_it_be(:user) { player } - let_it_be(:key) { create(:key, user: user) } - let_it_be(:git_access_actor) { ::API::Support::GitAccessActor.new(user: user, key: key) } - - subject { described_class.new(git_access_actor, project) } - - it 'sends git audit event' do - expect(::Gitlab::Audit::Auditor).to receive(:audit).with(a_hash_including( - name: 'repository_git_operation', - stream_only: true, - author: git_access_actor.deploy_key_or_user, - scope: project, - target: project, - message: msg - )).once - - subject.send_audit_event(msg) - end - end - end - - context 'when user is blank' do - let_it_be(:player) { nil } - - it 'does not send git audit event' do - expect(::Gitlab::Audit::Auditor).not_to receive(:audit) - - subject.send_audit_event(msg) - end - end - - context 'when project is blank' do - let_it_be(:project) { nil } - - it 'does not send git audit event' do - expect(::Gitlab::Audit::Auditor).not_to receive(:audit) - - subject.send_audit_event(msg) - end - end - end -end diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 2ee9d85c723..02c7abadd99 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -1041,9 +1041,11 @@ RSpec.describe Gitlab::GitalyClient::CommitService, feature_category: :gitaly do end describe '#raw_blame' do - let(:project) { create(:project, :test_repo) } + let_it_be(:project) { create(:project, :test_repo) } + let(:revision) { 'blame-on-renamed' } let(:path) { 'files/plain_text/renamed' } + let(:range) { nil } let(:blame_headers) do [ @@ -1073,6 +1075,31 @@ RSpec.describe Gitlab::GitalyClient::CommitService, feature_category: :gitaly do is_expected.not_to include(blame_headers[0], blame_headers[1], blame_headers[4]) end end + + context 'when out of range' do + let(:range) { '9999,99999' } + + it { expect { blame }.to raise_error(ArgumentError, 'range is outside of the file length') } + end + + context 'when a file path is not found' do + let(:path) { 'unknown/path' } + + it { expect { blame }.to raise_error(ArgumentError, 'path not found in revision') } + end + + context 'when an unknown exception is raised' do + let(:gitaly_exception) { GRPC::BadStatus.new(GRPC::Core::StatusCodes::NOT_FOUND) } + + before do + expect_any_instance_of(Gitaly::CommitService::Stub) + .to receive(:raw_blame) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_raise(gitaly_exception) + end + + it { expect { blame }.to raise_error(gitaly_exception) } + end end describe '#get_commit_signatures' do diff --git a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb index d0787d8b673..816b59b96ae 100644 --- a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb +++ b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb @@ -3,12 +3,12 @@ require 'spec_helper' RSpec.describe Gitlab::GitalyClient::ConflictFilesStitcher do + let_it_be(:target_project) { create(:project, :repository) } + let_it_be(:target_repository) { target_project.repository.raw } + let_it_be(:target_gitaly_repository) { target_repository.gitaly_repository } + describe 'enumeration' do it 'combines segregated ConflictFile messages together' do - target_project = create(:project, :repository) - target_repository = target_project.repository.raw - target_gitaly_repository = target_repository.gitaly_repository - ancestor_path_1 = 'ancestor/path/1' our_path_1 = 'our/path/1' their_path_1 = 'their/path/1' @@ -69,5 +69,49 @@ RSpec.describe Gitlab::GitalyClient::ConflictFilesStitcher do expect(conflict_files[1].repository).to eq(target_repository) expect(conflict_files[1].commit_oid).to eq(commit_oid_2) end + + it 'handles non-latin character names' do + ancestor_path_1_utf8 = "ancestor/テスト.txt" + our_path_1_utf8 = "our/テスト.txt" + their_path_1_utf8 = "their/テスト.txt" + + ancestor_path_1 = String.new('ancestor/テスト.txt', encoding: Encoding::US_ASCII) + our_path_1 = String.new('our/テスト.txt', encoding: Encoding::US_ASCII) + their_path_1 = String.new('their/テスト.txt', encoding: Encoding::US_ASCII) + our_mode_1 = 0744 + commit_oid_1 = 'f00' + content_1 = 'content of the first file' + + header_1 = double( + repository: target_gitaly_repository, + commit_oid: commit_oid_1, + ancestor_path: ancestor_path_1.dup, + our_path: our_path_1.dup, + their_path: their_path_1.dup, + our_mode: our_mode_1 + ) + + messages = [ + double(files: [double(header: header_1), double(header: nil, content: content_1[0..5])]), + double(files: [double(header: nil, content: content_1[6..])]) + ] + + conflict_files = described_class.new(messages, target_repository.gitaly_repository).to_a + + expect(conflict_files.size).to be(1) + + expect(conflict_files[0].content).to eq(content_1) + expect(conflict_files[0].ancestor_path).to eq(ancestor_path_1_utf8) + expect(conflict_files[0].their_path).to eq(their_path_1_utf8) + expect(conflict_files[0].our_path).to eq(our_path_1_utf8) + expect(conflict_files[0].our_mode).to be(our_mode_1) + expect(conflict_files[0].repository).to eq(target_repository) + expect(conflict_files[0].commit_oid).to eq(commit_oid_1) + + # Doesn't equal the ASCII version + expect(conflict_files[0].ancestor_path).not_to eq(ancestor_path_1) + expect(conflict_files[0].their_path).not_to eq(their_path_1) + expect(conflict_files[0].our_path).not_to eq(our_path_1) + end end end diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb index bd0341d51bf..f50675fee60 100644 --- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb @@ -1184,7 +1184,7 @@ RSpec.describe Gitlab::GitalyClient::OperationService, feature_category: :source patch_names.map { |name| File.read(File.join(patches_folder, name)) }.join("\n") end - let(:patch_names) { %w(0001-This-does-not-apply-to-the-feature-branch.patch) } + let(:patch_names) { %w[0001-This-does-not-apply-to-the-feature-branch.patch] } let(:branch_name) { 'branch-with-patches' } subject(:commit_patches) do @@ -1203,7 +1203,7 @@ RSpec.describe Gitlab::GitalyClient::OperationService, feature_category: :source end context 'when the patch could not be applied' do - let(:patch_names) { %w(0001-This-does-not-apply-to-the-feature-branch.patch) } + let(:patch_names) { %w[0001-This-does-not-apply-to-the-feature-branch.patch] } let(:branch_name) { 'feature' } it 'raises the correct error' do diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index ae9276cf90b..118b316f2d4 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -67,7 +67,7 @@ RSpec.describe Gitlab::GitalyClient::RefService, feature_category: :gitaly do .with(gitaly_request_with_params(merged_only: true, merged_branches: ['test']), kind_of(Hash)) .and_return([]) - client.merged_branches(%w(test)) + client.merged_branches(%w[test]) end end @@ -425,7 +425,7 @@ RSpec.describe Gitlab::GitalyClient::RefService, feature_category: :gitaly do end describe '#delete_refs' do - let(:prefixes) { %w(refs/heads refs/keep-around) } + let(:prefixes) { %w[refs/heads refs/keep-around] } subject(:delete_refs) { client.delete_refs(except_with_prefixes: prefixes) } diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb index 283a9cb45dc..727bf494ee6 100644 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb @@ -140,6 +140,44 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService, feature_category: :gital end end + describe '#fork_repository' do + let(:source_repository) { Gitlab::Git::Repository.new('default', 'repo/path', '', 'group/project') } + + context 'when branch is not provided' do + it 'sends a create_fork message' do + expected_request = gitaly_request_with_params( + source_repository: source_repository.gitaly_repository, + revision: "" + ) + + expect_any_instance_of(Gitaly::RepositoryService::Stub) + .to receive(:create_fork) + .with(expected_request, kind_of(Hash)) + .and_return(double(value: true)) + + client.fork_repository(source_repository) + end + end + + context 'when branch is provided' do + it 'sends a create_fork message including revision' do + branch = 'wip' + + expected_request = gitaly_request_with_params( + source_repository: source_repository.gitaly_repository, + revision: "refs/heads/#{branch}" + ) + + expect_any_instance_of(Gitaly::RepositoryService::Stub) + .to receive(:create_fork) + .with(expected_request, kind_of(Hash)) + .and_return(double(value: true)) + + client.fork_repository(source_repository, branch) + end + end + end + describe '#import_repository' do let(:source) { 'https://example.com/git/repo.git' } diff --git a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb index 0c4c8de52ae..7252f7d6afb 100644 --- a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb +++ b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::GitalyClient::StorageSettings do +RSpec.describe Gitlab::GitalyClient::StorageSettings, feature_category: :gitaly do describe "#initialize" do context 'when the storage contains no path' do it 'raises an error' do @@ -62,16 +62,16 @@ RSpec.describe Gitlab::GitalyClient::StorageSettings do end describe '.disk_access_denied?' do - context 'when Rugged is enabled', :enable_rugged do - it 'returns false' do - expect(described_class.disk_access_denied?).to be_falsey - end - end + subject { described_class.disk_access_denied? } - context 'when Rugged is disabled' do - it 'returns true' do - expect(described_class.disk_access_denied?).to be_truthy + it { is_expected.to be_truthy } + + context 'in case of an exception' do + before do + allow(described_class).to receive(:temporarily_allowed?).and_raise('boom') end + + it { is_expected.to be_falsey } end end end diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index 0073d2ebe80..00639d9574b 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -517,6 +517,44 @@ RSpec.describe Gitlab::GitalyClient, feature_category: :gitaly do end end + describe '.fetch_relative_path' do + subject { described_class.request_kwargs('default', timeout: 1)[:metadata]['relative-path-bin'] } + + let(:relative_path) { 'relative_path' } + + context 'when RequestStore is disabled' do + it 'does not set a relative path' do + is_expected.to be_nil + end + end + + context 'when RequestStore is enabled', :request_store do + context 'when RequestStore is empty' do + it 'does not set a relative path' do + is_expected.to be_nil + end + end + + context 'when RequestStore contains a relalive_path value' do + before do + Gitlab::SafeRequestStore[:gitlab_git_relative_path] = relative_path + end + + it 'sets a base64 encoded version of relative_path' do + is_expected.to eq(relative_path) + end + + context 'when relalive_path is empty' do + let(:relative_path) { '' } + + it 'does not set a relative path' do + is_expected.to be_nil + end + end + end + end + end + context 'gitlab_git_env' do let(:policy) { 'gitaly-route-repository-accessor-policy' } diff --git a/spec/lib/gitlab/github_import/attachments_downloader_spec.rb b/spec/lib/gitlab/github_import/attachments_downloader_spec.rb index 72d8a9c0403..65c5a7daeb2 100644 --- a/spec/lib/gitlab/github_import/attachments_downloader_spec.rb +++ b/spec/lib/gitlab/github_import/attachments_downloader_spec.rb @@ -94,9 +94,9 @@ RSpec.describe Gitlab::GithubImport::AttachmentsDownloader, feature_category: :i end end - context 'when attachment is behind a redirect' do - let_it_be(:file_url) { "https://github.com/test/project/assets/142635249/4b9f9c90-f060-4845-97cf-b24c558bcb11" } - let(:redirect_url) { "https://https://github-production-user-asset-6210df.s3.amazonaws.com/142635249/740edb05293e.jpg" } + context 'when attachment is behind a github asset endpoint' do + let(:file_url) { "https://github.com/test/project/assets/142635249/4b9f9c90-f060-4845-97cf-b24c558bcb11" } + let(:redirect_url) { "https://github-production-user-asset-6210df.s3.amazonaws.com/142635249/740edb05293e.jpg" } let(:sample_response) do instance_double(HTTParty::Response, redirection?: true, headers: { location: redirect_url }) end @@ -115,6 +115,8 @@ RSpec.describe Gitlab::GithubImport::AttachmentsDownloader, feature_category: :i end context 'when url is not a redirection' do + let(:file_url) { "https://github.com/test/project/assets/142635249/4b9f9c90-f060-4845-97cf-b24c558bcb11.jpg" } + let(:sample_response) do instance_double(HTTParty::Response, code: 200, redirection?: false) end @@ -125,8 +127,13 @@ RSpec.describe Gitlab::GithubImport::AttachmentsDownloader, feature_category: :i .and_return sample_response end - it 'raises upon unsuccessful redirection' do - expect { downloader.perform }.to raise_error("expected a redirect response, got #{sample_response.code}") + it 'queries with original file_url' do + expect(Gitlab::HTTP).to receive(:perform_request) + .with(Net::HTTP::Get, file_url, stream_body: true).and_yield(chunk_double) + + file = downloader.perform + + expect(File.exist?(file.path)).to eq(true) end end diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb index 5f321a15de9..c409ec6983f 100644 --- a/spec/lib/gitlab/github_import/client_spec.rb +++ b/spec/lib/gitlab/github_import/client_spec.rb @@ -278,7 +278,7 @@ RSpec.describe Gitlab::GithubImport::Client, feature_category: :importers do client.with_rate_limit do if retries == 0 retries += 1 - raise(Octokit::TooManyRequests) + raise(Octokit::TooManyRequests.new(body: 'primary')) end end @@ -306,6 +306,37 @@ RSpec.describe Gitlab::GithubImport::Client, feature_category: :importers do expect(client.with_rate_limit { 10 }).to eq(10) end + context 'when threshold is hit' do + it 'raises a RateLimitError with the appropriate message' do + expect(client).to receive(:requests_remaining?).and_return(false) + + expect { client.with_rate_limit } + .to raise_error(Gitlab::GithubImport::RateLimitError, 'Internal threshold reached') + end + end + + context 'when primary rate limit hit' do + let(:limited_block) { -> { raise(Octokit::TooManyRequests.new(body: 'primary')) } } + + it 're-raises a RateLimitError with the appropriate message' do + expect(client).to receive(:requests_remaining?).and_return(true) + + expect { client.with_rate_limit(&limited_block) } + .to raise_error(Gitlab::GithubImport::RateLimitError, 'primary') + end + end + + context 'when secondary rate limit hit' do + let(:limited_block) { -> { raise(Octokit::TooManyRequests.new(body: 'secondary')) } } + + it 're-raises a RateLimitError with the appropriate message' do + expect(client).to receive(:requests_remaining?).and_return(true) + + expect { client.with_rate_limit(&limited_block) } + .to raise_error(Gitlab::GithubImport::RateLimitError, 'secondary') + end + end + context 'when Faraday error received from octokit', :aggregate_failures do let(:error_class) { described_class::CLIENT_CONNECTION_ERROR } let(:info_params) { { 'error.class': error_class } } @@ -392,7 +423,7 @@ RSpec.describe Gitlab::GithubImport::Client, feature_category: :importers do describe '#raise_or_wait_for_rate_limit' do context 'when running in parallel mode' do it 'raises RateLimitError' do - expect { client.raise_or_wait_for_rate_limit } + expect { client.raise_or_wait_for_rate_limit('primary') } .to raise_error(Gitlab::GithubImport::RateLimitError) end end @@ -404,7 +435,7 @@ RSpec.describe Gitlab::GithubImport::Client, feature_category: :importers do expect(client).to receive(:rate_limit_resets_in).and_return(1) expect(client).to receive(:sleep).with(1) - client.raise_or_wait_for_rate_limit + client.raise_or_wait_for_rate_limit('primary') end it 'increments the rate limit counter' do @@ -420,7 +451,7 @@ RSpec.describe Gitlab::GithubImport::Client, feature_category: :importers do .to receive(:increment) .and_call_original - client.raise_or_wait_for_rate_limit + client.raise_or_wait_for_rate_limit('primary') end end end diff --git a/spec/lib/gitlab/github_import/importer/collaborators_importer_spec.rb b/spec/lib/gitlab/github_import/importer/collaborators_importer_spec.rb index dcb02f32a28..6f602531d23 100644 --- a/spec/lib/gitlab/github_import/importer/collaborators_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/collaborators_importer_spec.rb @@ -82,7 +82,7 @@ RSpec.describe Gitlab::GithubImport::Importer::CollaboratorsImporter, feature_ca it 'imports each collaborator in parallel' do expect(Gitlab::GithubImport::ImportCollaboratorWorker).to receive(:perform_in) - .with(1.second, project.id, an_instance_of(Hash), an_instance_of(String)) + .with(1, project.id, an_instance_of(Hash), an_instance_of(String)) waiter = importer.parallel_import diff --git a/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb b/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb index 945b742b025..4e8066ecb69 100644 --- a/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb @@ -98,7 +98,7 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNotesImporter, feature_catego .and_yield(github_comment) expect(Gitlab::GithubImport::ImportDiffNoteWorker).to receive(:perform_in) - .with(1.second, project.id, an_instance_of(Hash), an_instance_of(String)) + .with(1, project.id, an_instance_of(Hash), an_instance_of(String)) waiter = importer.parallel_import diff --git a/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb index 04b694dc0cb..9aba6a2b02c 100644 --- a/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb @@ -78,7 +78,7 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventsImporter, feature_cate allow(importer).to receive(:each_object_to_import).and_yield(issue_event) expect(Gitlab::GithubImport::ImportIssueEventWorker).to receive(:perform_in).with( - 1.second, project.id, an_instance_of(Hash), an_instance_of(String) + 1, project.id, an_instance_of(Hash), an_instance_of(String) ) waiter = importer.parallel_import diff --git a/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb index d6fd1a4739c..1bfdce04187 100644 --- a/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb @@ -92,7 +92,7 @@ RSpec.describe Gitlab::GithubImport::Importer::IssuesImporter, feature_category: expect(Gitlab::GithubImport::ImportIssueWorker) .to receive(:perform_in) - .with(1.second, project.id, an_instance_of(Hash), an_instance_of(String)) + .with(1, project.id, an_instance_of(Hash), an_instance_of(String)) waiter = importer.parallel_import diff --git a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb index fab9d26532d..3f5ee68d264 100644 --- a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb @@ -119,7 +119,7 @@ RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter, feature_categ end expect(Gitlab::GithubImport::ImportLfsObjectWorker).to receive(:perform_in) - .with(1.second, project.id, an_instance_of(Hash), an_instance_of(String)) + .with(1, project.id, an_instance_of(Hash), an_instance_of(String)) waiter = importer.parallel_import diff --git a/spec/lib/gitlab/github_import/importer/note_importer_spec.rb b/spec/lib/gitlab/github_import/importer/note_importer_spec.rb index 91311a8e90f..b5fe8c207c8 100644 --- a/spec/lib/gitlab/github_import/importer/note_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/note_importer_spec.rb @@ -99,7 +99,7 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter, feature_category: : end context 'when the note have invalid chars' do - let(:note_body) { %{There were an invalid char "\u0000" <= right here} } + let(:note_body) { %(There were an invalid char "\u0000" <= right here) } it 'removes invalid chars' do expect(importer.user_finder) diff --git a/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb b/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb index 841cc8178ea..8c93963f325 100644 --- a/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb @@ -84,7 +84,7 @@ RSpec.describe Gitlab::GithubImport::Importer::NotesImporter, feature_category: .and_yield(github_comment) expect(Gitlab::GithubImport::ImportNoteWorker).to receive(:perform_in) - .with(1.second, project.id, an_instance_of(Hash), an_instance_of(String)) + .with(1, project.id, an_instance_of(Hash), an_instance_of(String)) waiter = importer.parallel_import diff --git a/spec/lib/gitlab/github_import/importer/protected_branches_importer_spec.rb b/spec/lib/gitlab/github_import/importer/protected_branches_importer_spec.rb index 6a8b14a2690..8e99585109b 100644 --- a/spec/lib/gitlab/github_import/importer/protected_branches_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/protected_branches_importer_spec.rb @@ -144,7 +144,7 @@ RSpec.describe Gitlab::GithubImport::Importer::ProtectedBranchesImporter, featur it 'imports each protected branch in parallel' do expect(Gitlab::GithubImport::ImportProtectedBranchWorker) .to receive(:perform_in) - .with(1.second, project.id, an_instance_of(Hash), an_instance_of(String)) + .with(1, project.id, an_instance_of(Hash), an_instance_of(String)) expect(Gitlab::GithubImport::ObjectCounter) .to receive(:increment).with(project, :protected_branch, :fetched) diff --git a/spec/lib/gitlab/github_import/importer/pull_requests/review_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests/review_requests_importer_spec.rb index d0145ba1120..1977815e3a0 100644 --- a/spec/lib/gitlab/github_import/importer/pull_requests/review_requests_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_requests/review_requests_importer_spec.rb @@ -97,7 +97,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequests::ReviewRequestsImpor { id: 4, login: 'alice' }, { id: 5, login: 'bob' } ] - }, + }.deep_stringify_keys, instance_of(String) ], [ @@ -108,7 +108,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequests::ReviewRequestsImpor users: [ { id: 4, login: 'alice' } ] - }, + }.deep_stringify_keys, instance_of(String) ] ] @@ -116,10 +116,10 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequests::ReviewRequestsImpor it 'schedule import for each merge request reviewers' do expect(Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker) - .to receive(:perform_in).with(1.second, *expected_worker_payload.first) + .to receive(:perform_in).with(1, *expected_worker_payload.first) expect(Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker) - .to receive(:perform_in).with(1.second, *expected_worker_payload.second) + .to receive(:perform_in).with(1, *expected_worker_payload.second) expect(Gitlab::GithubImport::ObjectCounter) .to receive(:increment).twice.with(project, :pull_request_review_request, :fetched) @@ -137,7 +137,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequests::ReviewRequestsImpor it "doesn't schedule import this merge request reviewers" do expect(Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker) - .to receive(:perform_in).with(1.second, *expected_worker_payload.second) + .to receive(:perform_in).with(1, *expected_worker_payload.second) expect(Gitlab::GithubImport::ObjectCounter) .to receive(:increment).once.with(project, :pull_request_review_request, :fetched) diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb index cfd75fba849..10e413fdfe5 100644 --- a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb @@ -102,7 +102,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsImporter, feature_cat expect(Gitlab::GithubImport::ImportPullRequestWorker) .to receive(:perform_in) - .with(1.second, project.id, an_instance_of(Hash), an_instance_of(String)) + .with(1, project.id, an_instance_of(Hash), an_instance_of(String)) waiter = importer.parallel_import diff --git a/spec/lib/gitlab/github_import/issuable_finder_spec.rb b/spec/lib/gitlab/github_import/issuable_finder_spec.rb index d3236994cef..977fef95d64 100644 --- a/spec/lib/gitlab/github_import/issuable_finder_spec.rb +++ b/spec/lib/gitlab/github_import/issuable_finder_spec.rb @@ -2,40 +2,80 @@ require 'spec_helper' -RSpec.describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do - let(:project) { double(:project, id: 4, import_data: import_data) } +RSpec.describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache, feature_category: :importers do + let(:project) { build(:project, id: 20, import_data_attributes: import_data_attributes) } let(:single_endpoint_optional_stage) { false } - let(:import_data) do - instance_double( - ProjectImportData, + let(:import_data_attributes) do + { data: { optional_stages: { single_endpoint_notes_import: single_endpoint_optional_stage } - }.deep_stringify_keys - ) + } + } end - let(:issue) { double(:issue, issuable_type: MergeRequest, issuable_id: 1) } + let(:merge_request) { create(:merge_request, source_project: project) } + let(:issue) { double(:issue, issuable_type: 'MergeRequest', issuable_id: merge_request.iid) } let(:finder) { described_class.new(project, issue) } describe '#database_id' do - it 'returns nil when no cache is in place' do - expect(finder.database_id).to be_nil + it 'returns nil if object does not exist' do + missing_issue = double(:issue, issuable_type: 'MergeRequest', issuable_id: 999) + + expect(described_class.new(project, missing_issue).database_id).to be_nil + end + + it 'fetches object id from database if not in cache' do + expect(finder.database_id).to eq(merge_request.id) end - it 'returns the ID of an issuable when the cache is in place' do + it 'fetches object id from cache if present' do finder.cache_database_id(10) expect(finder.database_id).to eq(10) end + it 'returns nil and skips database read if cache has no record' do + finder.cache_database_id(-1) + + expect(finder.database_id).to be_nil + end + it 'raises TypeError when the object is not supported' do finder = described_class.new(project, double(:issue)) expect { finder.database_id }.to raise_error(TypeError) end + context 'with FF import_fallback_to_db_empty_cache disabled' do + before do + stub_feature_flags(import_fallback_to_db_empty_cache: false) + end + + it 'returns nil if object does not exist' do + missing_issue = double(:issue, issuable_type: 'MergeRequest', issuable_id: 999) + + expect(described_class.new(project, missing_issue).database_id).to be_nil + end + + it 'does not fetch object id from database if not in cache' do + expect(finder.database_id).to eq(nil) + end + + it 'fetches object id from cache if present' do + finder.cache_database_id(10) + + expect(finder.database_id).to eq(10) + end + + it 'returns -1 if cache is -1' do + finder.cache_database_id(-1) + + expect(finder.database_id).to eq(-1) + end + end + context 'when group is present' do context 'when settings single_endpoint_notes_import is enabled' do let(:single_endpoint_optional_stage) { true } @@ -65,7 +105,7 @@ RSpec.describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache d it 'caches the ID of a database row' do expect(Gitlab::Cache::Import::Caching) .to receive(:write) - .with('github-import/issuable-finder/4/MergeRequest/1', 10, timeout: 86400) + .with("github-import/issuable-finder/20/MergeRequest/#{merge_request.iid}", 10, timeout: 86400) finder.cache_database_id(10) end diff --git a/spec/lib/gitlab/github_import/label_finder_spec.rb b/spec/lib/gitlab/github_import/label_finder_spec.rb index 9905fce2a20..e46595974d1 100644 --- a/spec/lib/gitlab/github_import/label_finder_spec.rb +++ b/spec/lib/gitlab/github_import/label_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do +RSpec.describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache, feature_category: :importers do let_it_be(:project) { create(:project) } let_it_be(:finder) { described_class.new(project) } let_it_be(:bug) { create(:label, project: project, name: 'Bug') } @@ -18,23 +18,64 @@ RSpec.describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do expect(finder.id_for(feature.name)).to eq(feature.id) end - it 'returns nil for an empty cache key' do + it 'fetches object id from database if not in cache' do key = finder.cache_key_for(bug.name) Gitlab::Cache::Import::Caching.write(key, '') - expect(finder.id_for(bug.name)).to be_nil + expect(finder.id_for(bug.name)).to eq(bug.id) end it 'returns nil for a non existing label name' do expect(finder.id_for('kittens')).to be_nil end + + it 'returns nil and skips database read if cache has no record' do + key = finder.cache_key_for(bug.name) + + Gitlab::Cache::Import::Caching.write(key, -1) + + expect(finder.id_for(bug.name)).to be_nil + end end context 'without a cache in place' do - it 'returns nil for a label' do + it 'caches the ID of a database row and returns the ID' do + expect(Gitlab::Cache::Import::Caching) + .to receive(:write) + .with("github-import/label-finder/#{project.id}/#{feature.name}", feature.id) + .and_call_original + + expect(finder.id_for(feature.name)).to eq(feature.id) + end + end + + context 'with FF import_fallback_to_db_empty_cache disabled' do + before do + stub_feature_flags(import_fallback_to_db_empty_cache: false) + end + + it 'returns nil for a non existing label name' do + expect(finder.id_for('kittens')).to be_nil + end + + it 'does not fetch object id from database if not in cache' do expect(finder.id_for(feature.name)).to be_nil end + + it 'fetches object id from cache if present' do + finder.build_cache + + expect(finder.id_for(feature.name)).to eq(feature.id) + end + + it 'returns -1 if cache is -1' do + key = finder.cache_key_for(bug.name) + + Gitlab::Cache::Import::Caching.write(key, -1) + + expect(finder.id_for(bug.name)).to eq(-1) + end end end diff --git a/spec/lib/gitlab/github_import/milestone_finder_spec.rb b/spec/lib/gitlab/github_import/milestone_finder_spec.rb index e7f47d334e8..62886981de1 100644 --- a/spec/lib/gitlab/github_import/milestone_finder_spec.rb +++ b/spec/lib/gitlab/github_import/milestone_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do +RSpec.describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache, feature_category: :importers do let_it_be(:project) { create(:project) } let_it_be(:milestone) { create(:milestone, project: project) } @@ -20,23 +20,72 @@ RSpec.describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache expect(finder.id_for(issuable)).to eq(milestone.id) end - it 'returns nil for an empty cache key' do + it 'returns nil if object does not exist' do + missing_issuable = double(:issuable, milestone_number: 999) + + expect(finder.id_for(missing_issuable)).to be_nil + end + + it 'fetches object id from database if not in cache' do key = finder.cache_key_for(milestone.iid) Gitlab::Cache::Import::Caching.write(key, '') - expect(finder.id_for(issuable)).to be_nil + expect(finder.id_for(issuable)).to eq(milestone.id) end it 'returns nil for an issuable with a non-existing milestone' do expect(finder.id_for(double(:issuable, milestone_number: 5))).to be_nil end + + it 'returns nil and skips database read if cache has no record' do + key = finder.cache_key_for(milestone.iid) + + Gitlab::Cache::Import::Caching.write(key, -1) + + expect(finder.id_for(issuable)).to be_nil + end end context 'without a cache in place' do - it 'returns nil' do + it 'caches the ID of a database row and returns the ID' do + expect(Gitlab::Cache::Import::Caching) + .to receive(:write) + .with("github-import/milestone-finder/#{project.id}/1", milestone.id) + .and_call_original + + expect(finder.id_for(issuable)).to eq(milestone.id) + end + end + + context 'with FF import_fallback_to_db_empty_cache disabled' do + before do + stub_feature_flags(import_fallback_to_db_empty_cache: false) + end + + it 'returns nil if object does not exist' do + missing_issuable = double(:issuable, milestone_number: 999) + + expect(finder.id_for(missing_issuable)).to be_nil + end + + it 'does not fetch object id from database if not in cache' do expect(finder.id_for(issuable)).to be_nil end + + it 'fetches object id from cache if present' do + finder.build_cache + + expect(finder.id_for(issuable)).to eq(milestone.id) + end + + it 'returns -1 if cache is -1' do + key = finder.cache_key_for(milestone.iid) + + Gitlab::Cache::Import::Caching.write(key, -1) + + expect(finder.id_for(issuable)).to eq(-1) + end end end diff --git a/spec/lib/gitlab/github_import/object_counter_spec.rb b/spec/lib/gitlab/github_import/object_counter_spec.rb index e41a2cff989..964bdd6aad1 100644 --- a/spec/lib/gitlab/github_import/object_counter_spec.rb +++ b/spec/lib/gitlab/github_import/object_counter_spec.rb @@ -68,6 +68,16 @@ RSpec.describe Gitlab::GithubImport::ObjectCounter, :clean_gitlab_redis_cache, f 'imported' => { 'issue' => 8 } ) end + + it 'uses the same TTL as when incrementing' do + expect(Gitlab::Cache::Import::Caching) + .to receive(:read_integer) + .with(anything, timeout: described_class::IMPORT_CACHING_TIMEOUT) + .twice + .and_call_original + + described_class.summary(project) + end end context 'when import is in progress but cache expired' do diff --git a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb index 9de39a3ff7e..e0b1ff1bc33 100644 --- a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb +++ b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb @@ -296,11 +296,11 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling, feature_category: :impo expect(importer).to receive(:each_object_to_import) .and_yield(object).and_yield(object).and_yield(object) expect(worker_class).to receive(:perform_in) - .with(1.second, project.id, { title: 'One' }, 'waiter-key').ordered + .with(1, project.id, { 'title' => 'One' }, 'waiter-key').ordered expect(worker_class).to receive(:perform_in) - .with(1.second, project.id, { title: 'Two' }, 'waiter-key').ordered + .with(1, project.id, { 'title' => 'Two' }, 'waiter-key').ordered expect(worker_class).to receive(:perform_in) - .with(1.minute + 1.second, project.id, { title: 'Three' }, 'waiter-key').ordered + .with(61, project.id, { 'title' => 'Three' }, 'waiter-key').ordered job_waiter = importer.parallel_import @@ -325,11 +325,11 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling, feature_category: :impo expect(importer).to receive(:each_object_to_import).and_yield(object).and_yield(object).and_yield(object) expect(worker_class).to receive(:perform_in) - .with(1.second, project.id, { title: 'One' }, 'waiter-key').ordered + .with(1, project.id, { 'title' => 'One' }, 'waiter-key').ordered expect(worker_class).to receive(:perform_in) - .with(1.minute + 1.second, project.id, { title: 'Two' }, 'waiter-key').ordered + .with(61, project.id, { 'title' => 'Two' }, 'waiter-key').ordered expect(worker_class).to receive(:perform_in) - .with(2.minutes + 1.second, project.id, { title: 'Three' }, 'waiter-key').ordered + .with(121, project.id, { 'title' => 'Three' }, 'waiter-key').ordered job_waiter = importer.parallel_import diff --git a/spec/lib/gitlab/github_import/representation/to_hash_spec.rb b/spec/lib/gitlab/github_import/representation/to_hash_spec.rb index 739c832025c..52edffe586d 100644 --- a/spec/lib/gitlab/github_import/representation/to_hash_spec.rb +++ b/spec/lib/gitlab/github_import/representation/to_hash_spec.rb @@ -2,14 +2,14 @@ require 'fast_spec_helper' -RSpec.describe Gitlab::GithubImport::Representation::ToHash do +RSpec.describe Gitlab::GithubImport::Representation::ToHash, feature_category: :importers do describe '#to_hash' do let(:user) { double(:user, attributes: { login: 'alice' }) } let(:issue) do double( :issue, - attributes: { user: user, assignees: [user], number: 42 } + attributes: { user: user, assignees: [user], number: 42, created_at: 5.days.ago, status: :valid } ) end @@ -35,5 +35,13 @@ RSpec.describe Gitlab::GithubImport::Representation::ToHash do it 'keeps values as-is if they do not respond to #to_hash' do expect(issue_hash[:number]).to eq(42) end + + it 'converts Date value to String' do + expect(issue_hash[:created_at]).to be_an_instance_of(String) + end + + it 'converts Symbol value to String' do + expect(issue_hash[:status]).to be_an_instance_of(String) + end end end diff --git a/spec/lib/gitlab/graphql/known_operations_spec.rb b/spec/lib/gitlab/graphql/known_operations_spec.rb index c7bc47e1e6a..acb85bc737b 100644 --- a/spec/lib/gitlab/graphql/known_operations_spec.rb +++ b/spec/lib/gitlab/graphql/known_operations_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Gitlab::Graphql::KnownOperations do using RSpec::Parameterized::TableSyntax # Include duplicated operation names to test that we are unique-ifying them - let(:fake_operations) { %w(foo foo bar bar) } + let(:fake_operations) { %w[foo foo bar bar] } let(:fake_schema) do Class.new(GraphQL::Schema) do query Graphql::FakeQueryType @@ -34,7 +34,7 @@ RSpec.describe Gitlab::Graphql::KnownOperations do describe "#operations" do it "returns array of known operations" do - expect(subject.operations.map(&:name)).to match_array(%w(unknown foo bar)) + expect(subject.operations.map(&:name)).to match_array(%w[unknown foo bar]) end end diff --git a/spec/lib/gitlab/graphql/tracers/metrics_tracer_spec.rb b/spec/lib/gitlab/graphql/tracers/metrics_tracer_spec.rb index f0312293469..f077cff6875 100644 --- a/spec/lib/gitlab/graphql/tracers/metrics_tracer_spec.rb +++ b/spec/lib/gitlab/graphql/tracers/metrics_tracer_spec.rb @@ -6,7 +6,7 @@ require 'rspec-parameterized' RSpec.describe Gitlab::Graphql::Tracers::MetricsTracer do using RSpec::Parameterized::TableSyntax - let(:default_known_operations) { ::Gitlab::Graphql::KnownOperations.new(%w(lorem foo bar)) } + let(:default_known_operations) { ::Gitlab::Graphql::KnownOperations.new(%w[lorem foo bar]) } let(:fake_schema) do Class.new(GraphQL::Schema) do diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb index 84a2a0549d5..8466e8a1bb5 100644 --- a/spec/lib/gitlab/group_search_results_spec.rb +++ b/spec/lib/gitlab/group_search_results_spec.rb @@ -48,7 +48,7 @@ RSpec.describe Gitlab::GroupSearchResults, feature_category: :global_search do end include_examples 'search results filtered by state' - include_examples 'search results filtered by archived', 'search_merge_requests_hide_archived_projects' + include_examples 'search results filtered by archived' end describe 'milestones search' do diff --git a/spec/lib/gitlab/hashed_path_spec.rb b/spec/lib/gitlab/hashed_path_spec.rb index 051c5196748..cf31e891957 100644 --- a/spec/lib/gitlab/hashed_path_spec.rb +++ b/spec/lib/gitlab/hashed_path_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Gitlab::HashedPath do end context 'when path contains multiple values' do - let(:path) { %w(path1 path2) } + let(:path) { %w[path1 path2] } it 'returns the disk path' do expect(subject).to match(%r[\h{2}/\h{2}/\h{64}/path1/path2]) diff --git a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb index 64c4e92f80b..8f676d20c22 100644 --- a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb +++ b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb @@ -4,11 +4,6 @@ require 'fast_spec_helper' RSpec.describe Gitlab::HealthChecks::GitalyCheck do let(:result_class) { Gitlab::HealthChecks::Result } - let(:repository_storages) { ['default'] } - - before do - allow(described_class).to receive(:repository_storages) { repository_storages } - end describe '#readiness' do subject { described_class.readiness } diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index 173131b1d5c..ef3765e479f 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -43,7 +43,7 @@ RSpec.describe Gitlab::Highlight do it 'returns plain version for unknown lexer context' do result = described_class.highlight(plain_text_file_name, plain_text_content) - expect(result).to eq(%[<span id="LC1" class="line" lang="plaintext">plain text contents</span>]) + expect(result).to eq(%(<span id="LC1" class="line" lang="plaintext">plain text contents</span>)) end context 'when content is too long to be highlighted' do diff --git a/spec/lib/gitlab/i18n/translation_entry_spec.rb b/spec/lib/gitlab/i18n/translation_entry_spec.rb index df503e68cf1..3531314cf9c 100644 --- a/spec/lib/gitlab/i18n/translation_entry_spec.rb +++ b/spec/lib/gitlab/i18n/translation_entry_spec.rb @@ -108,7 +108,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do describe '#msgid_contains_newlines' do it 'is true when the msgid is an array' do - data = { msgid: %w(hello world) } + data = { msgid: %w[hello world] } entry = described_class.new(entry_data: data, nplurals: 2) expect(entry.msgid_has_multiple_lines?).to be_truthy @@ -117,7 +117,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do describe '#plural_id_contains_newlines' do it 'is true when the msgid is an array' do - data = { msgid_plural: %w(hello world) } + data = { msgid_plural: %w[hello world] } entry = described_class.new(entry_data: data, nplurals: 2) expect(entry.plural_id_has_multiple_lines?).to be_truthy @@ -126,7 +126,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do describe '#translations_contain_newlines' do it 'is true when the msgid is an array' do - data = { msgstr: %w(hello world) } + data = { msgstr: %w[hello world] } entry = described_class.new(entry_data: data, nplurals: 2) expect(entry.translations_have_multiple_lines?).to be_truthy diff --git a/spec/lib/gitlab/import/import_failure_service_spec.rb b/spec/lib/gitlab/import/import_failure_service_spec.rb index a4682a9495e..362d809bb56 100644 --- a/spec/lib/gitlab/import/import_failure_service_spec.rb +++ b/spec/lib/gitlab/import/import_failure_service_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures, featur let(:import_state) { nil } let(:fail_import) { false } let(:metrics) { false } - let(:external_identifiers) { {} } + let(:external_identifiers) { { foo: 'bar' } } let(:project_id) { project.id } let(:arguments) do @@ -90,13 +90,19 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures, featur ) service.execute - - expect(project.import_state.reload.status).to eq('failed') - - expect(project.import_failures).not_to be_empty - expect(project.import_failures.last.exception_class).to eq('StandardError') - expect(project.import_failures.last.exception_message).to eq('some error') - expect(project.import_failures.last.retry_count).to eq(0) + project.reload + + expect(project.import_state.status).to eq('failed') + expect(project.import_failures).to contain_exactly( + have_attributes( + retry_count: 0, + exception_class: 'StandardError', + exception_message: 'some error', + external_identifiers: external_identifiers.with_indifferent_access, + correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id, + source: 'SomeImporter' + ) + ) end end @@ -128,13 +134,19 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures, featur ) service.execute + project.reload expect(project.import_state.reload.status).to eq('started') - - expect(project.import_failures).not_to be_empty - expect(project.import_failures.last.exception_class).to eq('StandardError') - expect(project.import_failures.last.exception_message).to eq('some error') - expect(project.import_failures.last.retry_count).to eq(nil) + expect(project.import_failures).to contain_exactly( + have_attributes( + retry_count: nil, + exception_class: 'StandardError', + exception_message: 'some error', + external_identifiers: external_identifiers.with_indifferent_access, + correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id, + source: 'SomeImporter' + ) + ) end end diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb index fc794f11499..2046e1b5ae5 100644 --- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb +++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb @@ -100,7 +100,7 @@ RSpec.describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrat describe '#log_validation_errors' do it 'add the message to the shared context' do - errors = %w(test_message test_message2) + errors = %w[test_message test_message2] allow(service).to receive(:invalid?).and_return(true) allow(service.errors).to receive(:full_messages).and_return(errors) diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb index 297fe3ade07..0f9cbe8aea3 100644 --- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb +++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb @@ -27,7 +27,7 @@ RSpec.describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do describe 'validations' do it 'only POST and PUT method allowed' do - %w(POST post PUT put).each do |method| + %w[POST post PUT put].each do |method| expect(subject.new(url: example_url, http_method: method)).to be_valid end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index cd899a79451..722b47ac9b8 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -660,6 +660,7 @@ project: - pages_domains - pages_metadatum - pages_deployments +- active_pages_deployments - authorized_users - project_authorizations - remote_mirrors @@ -1061,6 +1062,7 @@ approval_rules: - users - groups - group_users + - group_members - security_orchestration_policy_configuration - protected_branches - approval_merge_request_rule_sources diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb index 272c2629b08..9d69e1fec05 100644 --- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb +++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb @@ -34,7 +34,7 @@ RSpec.describe Gitlab::ImportExport::AttributeCleaner do 'note_ids' => [1, 2, 3], 'remote_attachment_url' => 'http://something.dodgy', 'remote_attachment_request_header' => 'bad value', - 'remote_attachment_urls' => %w(http://something.dodgy http://something.okay), + 'remote_attachment_urls' => %w[http://something.dodgy http://something.okay], 'attributes' => { 'issue_ids' => [1, 2, 3], 'merge_request_ids' => [1, 2, 3], diff --git a/spec/lib/gitlab/import_export/attributes_permitter_spec.rb b/spec/lib/gitlab/import_export/attributes_permitter_spec.rb index 08abd7908d2..996b32ed341 100644 --- a/spec/lib/gitlab/import_export/attributes_permitter_spec.rb +++ b/spec/lib/gitlab/import_export/attributes_permitter_spec.rb @@ -33,7 +33,7 @@ RSpec.describe Gitlab::ImportExport::AttributesPermitter, feature_category: :imp EOF end - let(:file) { Tempfile.new(%w(import_export .yml)) } + let(:file) { Tempfile.new(%w[import_export .yml]) } let(:config_hash) { Gitlab::ImportExport::Config.new(config: file.path).to_h } before do diff --git a/spec/lib/gitlab/import_export/command_line_util_spec.rb b/spec/lib/gitlab/import_export/command_line_util_spec.rb index 42c3b170e4d..ab47de8f874 100644 --- a/spec/lib/gitlab/import_export/command_line_util_spec.rb +++ b/spec/lib/gitlab/import_export/command_line_util_spec.rb @@ -263,7 +263,11 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe context 'when exception occurs' do it 'raises an exception' do - expect { subject.gzip(dir: path, filename: 'test') }.to raise_error(Gitlab::ImportExport::Error) + expect { subject.gzip(dir: path, filename: 'test') } + .to raise_error( + Gitlab::ImportExport::Error, + %r{File compression or decompression failed. Command exited with error code 1: gzip} + ) end end end @@ -283,7 +287,11 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe context 'when exception occurs' do it 'raises an exception' do - expect { subject.gunzip(dir: path, filename: 'test') }.to raise_error(Gitlab::ImportExport::Error) + expect { subject.gunzip(dir: path, filename: 'test') } + .to raise_error( + Gitlab::ImportExport::Error, + %r{File compression or decompression failed. Command exited with error code 1: gzip} + ) end end end @@ -306,7 +314,7 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe include Gitlab::ImportExport::CommandLineUtil end.new - expect { klass.tar_cf(archive: 'test', dir: 'test') }.to raise_error(Gitlab::ImportExport::Error, 'command exited with error code 1: Error') + expect { klass.tar_cf(archive: 'test', dir: 'test') }.to raise_error(Gitlab::ImportExport::Error, 'Command exited with error code 1: Error') end end end @@ -363,7 +371,7 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe include Gitlab::ImportExport::CommandLineUtil end.new - expect { klass.untar_xf(archive: 'test', dir: 'test') }.to raise_error(Gitlab::ImportExport::Error, 'command exited with error code 1: Error') + expect { klass.untar_xf(archive: 'test', dir: 'test') }.to raise_error(Gitlab::ImportExport::Error, 'Command exited with error code 1: Error') end it 'returns false and includes error status' do @@ -378,7 +386,7 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe end.new expect(klass.tar_czf(archive: 'test', dir: 'test')).to eq(false) - expect(klass.shared.errors).to eq(['command exited with error code 1: Error']) + expect(klass.shared.errors).to eq(['Command exited with error code 1: Error']) end end end diff --git a/spec/lib/gitlab/import_export/error_spec.rb b/spec/lib/gitlab/import_export/error_spec.rb index 015133a399b..db16d0f1e45 100644 --- a/spec/lib/gitlab/import_export/error_spec.rb +++ b/spec/lib/gitlab/import_export/error_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::ImportExport::Error do +RSpec.describe Gitlab::ImportExport::Error, feature_category: :importers do describe '.permission_error' do subject(:error) do described_class.permission_error(user, importable) @@ -28,4 +28,12 @@ RSpec.describe Gitlab::ImportExport::Error do end end end + + describe '.file_compression_error' do + it 'adds error to exception message' do + message = described_class.file_compression_error('Error').message + + expect(message).to eq('File compression or decompression failed. Error') + end + end end diff --git a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb index dfc7202194d..f6ad3e47c30 100644 --- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb +++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb @@ -156,7 +156,7 @@ RSpec.describe Gitlab::ImportExport::FastHashSerializer, :with_license, feature_ it 'has project and group labels' do label_types = subject['issues'].first['label_links'].map { |link| link['label']['type'] } - expect(label_types).to match_array(%w(ProjectLabel GroupLabel)) + expect(label_types).to match_array(%w[ProjectLabel GroupLabel]) end it 'has priorities associated to labels' do diff --git a/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb b/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb index 486d179ae05..a7aeb9e8c3b 100644 --- a/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb +++ b/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb @@ -54,7 +54,7 @@ RSpec.describe Gitlab::ImportExport::Json::NdjsonWriter, feature_category: :impo describe "#write_relation_array" do it "writes json in correct files" do values = [{ "key" => "value_1", "key_1" => "value_1" }, { "key" => "value_2", "key_1" => "value_2" }] - relations = %w(relation1 relation2) + relations = %w[relation1 relation2] relations.each do |relation| subject.write_relation_array(exportable_path, relation, values.to_enum) end diff --git a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb index fe064c50b9e..042a49f9419 100644 --- a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb @@ -27,7 +27,7 @@ RSpec.describe Gitlab::ImportExport::LfsRestorer do # Use the LfsSaver to save data to be restored def save_lfs_data - %w(project wiki).each do |repository_type| + %w[project wiki].each do |repository_type| create( :lfs_objects_project, project: project, diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb index 5b6f50025ff..bd225265ef0 100644 --- a/spec/lib/gitlab/import_export/lfs_saver_spec.rb +++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Gitlab::ImportExport::LfsSaver do describe 'saving a json file' do before do # Create two more LfsObjectProject records with different `repository_type`s - %w(wiki design).each do |repository_type| + %w[wiki design].each do |repository_type| create( :lfs_objects_project, project: project, diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb index 1bf1e5b47e1..2e82351db10 100644 --- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb @@ -190,7 +190,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver, :with_license, feature_ it 'has project and group labels' do label_types = subject.first['label_links'].map { |link| link['label']['type'] } - expect(label_types).to match_array(%w(ProjectLabel GroupLabel)) + expect(label_types).to match_array(%w[ProjectLabel GroupLabel]) end it 'has priorities associated to labels' do diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb index a34e68ecd19..ba38a5d7960 100644 --- a/spec/lib/gitlab/import_export/saver_spec.rb +++ b/spec/lib/gitlab/import_export/saver_spec.rb @@ -33,7 +33,7 @@ RSpec.describe Gitlab::ImportExport::Saver do subject.save # rubocop:disable Rails/SaveBang expect(ImportExportUpload.find_by(project: project).export_file.url) - .to match(%r[/uploads/-/system/import_export_upload/export_file.*]) + .to match(%r{/uploads/-/system/import_export_upload/export_file.*}) end it 'logs metrics after saving' do diff --git a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb index d7b1b180e2e..97e3caba9b3 100644 --- a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Gitlab::ImportExport::SnippetRepoRestorer do expect(restorer.restore).to be_truthy end.to change { SnippetRepository.count }.by(1) - snippet.repository.expire_method_caches(%i(exists?)) + snippet.repository.expire_method_caches(%i[exists?]) expect(snippet.repository_exists?).to be_truthy blob = snippet.repository.blob_at(snippet.default_branch, snippet.file_name) diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb index db23e3b1fd4..19f17c9079d 100644 --- a/spec/lib/gitlab/import_sources_spec.rb +++ b/spec/lib/gitlab/import_sources_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Gitlab::ImportSources, feature_category: :importers do describe '.values' do it 'returns an array' do expected = - %w( + %w[ github bitbucket bitbucket_server @@ -33,7 +33,7 @@ RSpec.describe Gitlab::ImportSources, feature_category: :importers do gitlab_project gitea manifest - ) + ] expect(described_class.values).to eq(expected) end @@ -42,14 +42,14 @@ RSpec.describe Gitlab::ImportSources, feature_category: :importers do describe '.importer_names' do it 'returns an array of importer names' do expected = - %w( + %w[ github bitbucket bitbucket_server fogbugz gitlab_project gitea - ) + ] expect(described_class.importer_names).to eq(expected) end diff --git a/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb b/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb index ddb5245f825..ea5a32a25ff 100644 --- a/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_cluster_validator_spec.rb @@ -11,25 +11,25 @@ RSpec.describe Gitlab::Instrumentation::RedisClusterValidator, feature_category: using RSpec::Parameterized::TableSyntax where(:command, :arguments, :keys, :is_valid) do - :rename | %w(foo bar) | 2 | false - :RENAME | %w(foo bar) | 2 | false - 'rename' | %w(foo bar) | 2 | false - 'RENAME' | %w(foo bar) | 2 | false - :rename | %w(iaa ahy) | 2 | true # 'iaa' and 'ahy' hash to the same slot - :rename | %w({foo}:1 {foo}:2) | 2 | true - :rename | %w(foo foo bar) | 2 | true # This is not a valid command but should not raise here - :mget | %w(foo bar) | 2 | false - :mget | %w(foo foo bar) | 3 | false - :mget | %w(foo foo) | 2 | true - :blpop | %w(foo bar 1) | 2 | false - :blpop | %w(foo foo 1) | 2 | true - :mset | %w(foo a bar a) | 2 | false - :mset | %w(foo a foo a) | 2 | true - :del | %w(foo bar) | 2 | false - :del | [%w(foo bar)] | 2 | false # Arguments can be a nested array - :del | %w(foo foo) | 2 | true - :hset | %w(foo bar) | 1 | nil # Single key write - :get | %w(foo) | 1 | nil # Single key read + :rename | %w[foo bar] | 2 | false + :RENAME | %w[foo bar] | 2 | false + 'rename' | %w[foo bar] | 2 | false + 'RENAME' | %w[foo bar] | 2 | false + :rename | %w[iaa ahy] | 2 | true # 'iaa' and 'ahy' hash to the same slot + :rename | %w[{foo}:1 {foo}:2] | 2 | true + :rename | %w[foo foo bar] | 2 | true # This is not a valid command but should not raise here + :mget | %w[foo bar] | 2 | false + :mget | %w[foo foo bar] | 3 | false + :mget | %w[foo foo] | 2 | true + :blpop | %w[foo bar 1] | 2 | false + :blpop | %w[foo foo 1] | 2 | true + :mset | %w[foo a bar a] | 2 | false + :mset | %w[foo a foo a] | 2 | true + :del | %w[foo bar] | 2 | false + :del | [%w[foo bar]] | 2 | false # Arguments can be a nested array + :del | %w[foo foo] | 2 | true + :hset | %w[foo bar] | 1 | nil # Single key write + :get | %w[foo] | 1 | nil # Single key read :mget | [] | 0 | true # This is invalid, but not because it's a cross-slot command end diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb index 698c8a37d48..f8a4d8023c1 100644 --- a/spec/lib/gitlab/instrumentation_helper_spec.rb +++ b/spec/lib/gitlab/instrumentation_helper_spec.rb @@ -7,6 +7,7 @@ require 'support/helpers/rails_helpers' RSpec.describe Gitlab::InstrumentationHelper, :clean_gitlab_redis_repository_cache, :clean_gitlab_redis_cache, :use_null_store_as_repository_cache, feature_category: :scalability do using RSpec::Parameterized::TableSyntax + include RedisHelpers describe '.add_instrumentation_data', :request_store do let(:payload) { {} } @@ -39,11 +40,23 @@ RSpec.describe Gitlab::InstrumentationHelper, :clean_gitlab_redis_repository_cac end context 'when Redis calls are made' do - it 'adds Redis data and omits Gitaly data' do - stub_rails_env('staging') # to avoid raising CrossSlotError - Gitlab::Redis::Sessions.with { |redis| redis.mset('test-cache', 123, 'test-cache2', 123) } + let_it_be(:redis_store_class) { define_helper_redis_store_class } + + before do + redis_store_class.with(&:ping) + Gitlab::Redis::Queues.with(&:ping) + RequestStore.clear! + end + + it 'adds Redis data including cross slot calls' do + expect(Gitlab::Instrumentation::RedisBase) + .to receive(:raise_cross_slot_validation_errors?) + .once.and_return(false) + + redis_store_class.with { |redis| redis.mset('test-cache', 123, 'test-cache2', 123) } + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do - Gitlab::Redis::Sessions.with { |redis| redis.mget('cache-test', 'cache-test-2') } + redis_store_class.with { |redis| redis.mget('cache-test', 'cache-test-2') } end Gitlab::Redis::Queues.with { |redis| redis.set('test-queues', 321) } @@ -249,15 +262,12 @@ RSpec.describe Gitlab::InstrumentationHelper, :clean_gitlab_redis_repository_cac end end - describe 'duration calculations' do - where(:end_time, :start_time, :time_now, :expected_duration) do + describe '.queue_duration_for_job' do + where(:enqueued_at, :created_at, :time_now, :expected_duration) do "2019-06-01T00:00:00.000+0000" | nil | "2019-06-01T02:00:00.000+0000" | 2.hours.to_f - "2019-06-01T02:00:00.000+0000" | nil | "2019-06-01T02:00:00.001+0000" | 0.001 "2019-06-01T02:00:00.000+0000" | "2019-05-01T02:00:00.000+0000" | "2019-06-01T02:00:01.000+0000" | 1 - nil | "2019-06-01T02:00:00.000+0000" | "2019-06-01T02:00:00.001+0000" | 0.001 nil | nil | "2019-06-01T02:00:00.001+0000" | nil "2019-06-01T02:00:00.000+0200" | nil | "2019-06-01T02:00:00.000-0200" | 4.hours.to_f - 1571825569.998168 | nil | "2019-10-23T12:13:16.000+0200" | 26.001832 1571825569 | nil | "2019-10-23T12:13:16.000+0200" | 27 "invalid_date" | nil | "2019-10-23T12:13:16.000+0200" | nil "" | nil | "2019-10-23T12:13:16.000+0200" | nil @@ -267,27 +277,30 @@ RSpec.describe Gitlab::InstrumentationHelper, :clean_gitlab_redis_repository_cac Time.at(1571999233).utc | nil | "2019-10-25T12:29:16.000+0200" | 123 end - describe '.queue_duration_for_job' do - with_them do - let(:job) { { 'enqueued_at' => end_time, 'created_at' => start_time } } + with_them do + let(:job) { { 'enqueued_at' => enqueued_at, 'created_at' => created_at } } - it "returns the correct duration" do - travel_to(Time.iso8601(time_now)) do - expect(described_class.queue_duration_for_job(job)).to eq(expected_duration) - end + it "returns the correct duration" do + travel_to(Time.iso8601(time_now)) do + expect(described_class.queue_duration_for_job(job)).to eq(expected_duration) end end end + end - describe '.enqueue_latency_for_scheduled_job' do - with_them do - let(:job) { { 'enqueued_at' => end_time, 'scheduled_at' => start_time } } + describe '.enqueue_latency_for_scheduled_job' do + where(:scheduled_at, :enqueued_at, :expected_duration) do + "2019-06-01T02:00:00.000+0000" | "2019-06-01T02:00:00.001+0000" | 0.001 + "2019-06-01T02:00:00.000+0000" | "2019-06-01T02:00:01.000+0000" | 1 + "2019-06-01T02:00:00.000+0000" | nil | nil + nil | "2019-06-01T02:00:01.000+0000" | nil + end - it "returns the correct duration" do - travel_to(Time.iso8601(time_now)) do - expect(described_class.enqueue_latency_for_scheduled_job(job)).to eq(expected_duration) - end - end + with_them do + let(:job) { { 'enqueued_at' => enqueued_at, 'scheduled_at' => scheduled_at } } + + it "returns the correct duration" do + expect(described_class.enqueue_latency_for_scheduled_job(job)).to eq(expected_duration) end end end diff --git a/spec/lib/gitlab/issues/rebalancing/state_spec.rb b/spec/lib/gitlab/issues/rebalancing/state_spec.rb index 5adf1328b87..a0ea5fec8ec 100644 --- a/spec/lib/gitlab/issues/rebalancing/state_spec.rb +++ b/spec/lib/gitlab/issues/rebalancing/state_spec.rb @@ -67,11 +67,11 @@ RSpec.describe Gitlab::Issues::Rebalancing::State, :clean_gitlab_redis_shared_st end it 'returns array of issue ids' do - expect(rebalance_caching.get_cached_issue_ids(0, 100)).to eq(%w(1 2 3)) + expect(rebalance_caching.get_cached_issue_ids(0, 100)).to eq(%w[1 2 3]) end it 'limits returned values' do - expect(rebalance_caching.get_cached_issue_ids(0, 2)).to eq(%w(1 2)) + expect(rebalance_caching.get_cached_issue_ids(0, 2)).to eq(%w[1 2]) end context 'when caching duplicate issue_ids' do @@ -84,7 +84,7 @@ RSpec.describe Gitlab::Issues::Rebalancing::State, :clean_gitlab_redis_shared_st end it 'returns cached issues with latest scores' do - expect(rebalance_caching.get_cached_issue_ids(0, 100)).to eq(%w(3 2 1)) + expect(rebalance_caching.get_cached_issue_ids(0, 100)).to eq(%w[3 2 1]) end end end @@ -231,8 +231,16 @@ RSpec.describe Gitlab::Issues::Rebalancing::State, :clean_gitlab_redis_shared_st def check_existing_keys index = 0 - # spec only, we do not actually scan keys in the code - recently_finished_keys_count = Gitlab::Redis::SharedState.with { |redis| redis.scan(0, match: "#{described_class::RECENTLY_FINISHED_REBALANCE_PREFIX}:*") }.last.count + cursor = '0' + recently_finished_keys_count = 0 + + # loop to scan since it may run against a Redis Cluster + loop do + # spec only, we do not actually scan keys in the code + cursor, items = Gitlab::Redis::SharedState.with { |redis| redis.scan(cursor, match: "#{described_class::RECENTLY_FINISHED_REBALANCE_PREFIX}:*") } + recently_finished_keys_count += items.count + break if cursor == '0' + end index += 1 if rebalance_caching.get_current_index > 0 index += 1 if rebalance_caching.get_current_project_id.present? diff --git a/spec/lib/gitlab/jira/middleware_spec.rb b/spec/lib/gitlab/jira/middleware_spec.rb deleted file mode 100644 index 09cf67d0657..00000000000 --- a/spec/lib/gitlab/jira/middleware_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require 'fast_spec_helper' - -RSpec.describe Gitlab::Jira::Middleware do - let(:app) { double(:app) } - let(:middleware) { described_class.new(app) } - let(:jira_user_agent) { 'Jira DVCS Connector Vertigo/5.0.0-D20170810T012915' } - - describe '.jira_dvcs_connector?' do - it 'returns true when DVCS connector' do - expect(described_class.jira_dvcs_connector?('HTTP_USER_AGENT' => jira_user_agent)).to eq(true) - end - - it 'returns true if user agent starts with "Jira DVCS Connector"' do - expect(described_class.jira_dvcs_connector?('HTTP_USER_AGENT' => 'Jira DVCS Connector')).to eq(true) - end - - it 'returns false when not DVCS connector' do - expect(described_class.jira_dvcs_connector?('HTTP_USER_AGENT' => 'pokemon')).to eq(false) - end - end - - describe '#call' do - it 'adjusts HTTP_AUTHORIZATION env when request from Jira DVCS user agent' do - expect(app).to receive(:call).with({ 'HTTP_USER_AGENT' => jira_user_agent, - 'HTTP_AUTHORIZATION' => 'Bearer hash-123' }) - - middleware.call('HTTP_USER_AGENT' => jira_user_agent, 'HTTP_AUTHORIZATION' => 'token hash-123') - end - - it 'does not change HTTP_AUTHORIZATION env when request is not from Jira DVCS user agent' do - env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0', 'HTTP_AUTHORIZATION' => 'token hash-123' } - - expect(app).to receive(:call).with(env) - - middleware.call(env) - end - end -end diff --git a/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb b/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb index b8c0dc64581..82233641778 100644 --- a/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb +++ b/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Gitlab::JiraImport::HandleLabelsService do let_it_be(:other_project_label) { create(:label, title: 'feature') } let_it_be(:group_label) { create(:group_label, group: group, title: 'dev') } - let(:jira_labels) { %w(bug feature dev group::new) } + let(:jira_labels) { %w[bug feature dev group::new] } subject { described_class.new(project, jira_labels).execute } @@ -23,7 +23,7 @@ RSpec.describe Gitlab::JiraImport::HandleLabelsService do it 'creates the missing labels on the project level' do expect { subject }.to change { Label.count }.from(3).to(5) - expect(created_labels.map(&:title)).to match_array(%w(feature group::new)) + expect(created_labels.map(&:title)).to match_array(%w[feature group::new]) end it 'returns the id of all labels matching the title' do @@ -32,7 +32,7 @@ RSpec.describe Gitlab::JiraImport::HandleLabelsService do end context 'when no provided jira labels are missing' do - let(:jira_labels) { %w(bug dev) } + let(:jira_labels) { %w[bug dev] } it 'does not create any new labels' do expect { subject }.not_to change { Label.count }.from(3) diff --git a/spec/lib/gitlab/jira_import/issue_serializer_spec.rb b/spec/lib/gitlab/jira_import/issue_serializer_spec.rb index 30ad24472b4..98958d8a92e 100644 --- a/spec/lib/gitlab/jira_import/issue_serializer_spec.rb +++ b/spec/lib/gitlab/jira_import/issue_serializer_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Gitlab::JiraImport::IssueSerializer do end let(:priority_field) { { 'name' => 'Medium' } } - let(:labels_field) { %w(bug dev backend frontend) } + let(:labels_field) { %w[bug dev backend frontend] } let(:fields) do { @@ -101,7 +101,7 @@ RSpec.describe Gitlab::JiraImport::IssueSerializer do end context 'when there are no new labels' do - let(:labels_field) { %w(bug dev) } + let(:labels_field) { %w[bug dev] } it 'assigns the labels to the Issue hash' do expect(subject[:label_ids]).to match_array([project_label.id, group_label.id]) diff --git a/spec/lib/gitlab/jira_import/labels_importer_spec.rb b/spec/lib/gitlab/jira_import/labels_importer_spec.rb index 4fb5e363475..7579e2c65f4 100644 --- a/spec/lib/gitlab/jira_import/labels_importer_spec.rb +++ b/spec/lib/gitlab/jira_import/labels_importer_spec.rb @@ -36,8 +36,8 @@ RSpec.describe Gitlab::JiraImport::LabelsImporter do let_it_be(:jira_import_with_label) { create(:jira_import_state, label: label, project: project) } let_it_be(:issue_label) { create(:label, project: project, title: 'bug') } - let(:jira_labels_1) { { "maxResults" => 2, "startAt" => 0, "total" => 3, "isLast" => false, "values" => %w(backend bug) } } - let(:jira_labels_2) { { "maxResults" => 2, "startAt" => 2, "total" => 3, "isLast" => true, "values" => %w(feature) } } + let(:jira_labels_1) { { "maxResults" => 2, "startAt" => 0, "total" => 3, "isLast" => false, "values" => %w[backend bug] } } + let(:jira_labels_2) { { "maxResults" => 2, "startAt" => 2, "total" => 3, "isLast" => true, "values" => %w[feature] } } context 'when labels are returned from jira' do before do @@ -55,8 +55,8 @@ RSpec.describe Gitlab::JiraImport::LabelsImporter do end it 'calls Gitlab::JiraImport::HandleLabelsService' do - expect(Gitlab::JiraImport::HandleLabelsService).to receive(:new).with(project, %w(backend bug)).and_return(double(execute: [1, 2])) - expect(Gitlab::JiraImport::HandleLabelsService).to receive(:new).with(project, %w(feature)).and_return(double(execute: [3])) + expect(Gitlab::JiraImport::HandleLabelsService).to receive(:new).with(project, %w[backend bug]).and_return(double(execute: [1, 2])) + expect(Gitlab::JiraImport::HandleLabelsService).to receive(:new).with(project, %w[feature]).and_return(double(execute: [3])) subject end @@ -92,7 +92,7 @@ RSpec.describe Gitlab::JiraImport::LabelsImporter do end context 'when the isLast argument is missing' do - let(:jira_labels) { { "maxResults" => 2, "startAt" => 0, "total" => 3, "values" => %w(bug dev) } } + let(:jira_labels) { { "maxResults" => 2, "startAt" => 0, "total" => 3, "values" => %w[bug dev] } } it_behaves_like 'no labels handling' end diff --git a/spec/lib/gitlab/job_waiter_spec.rb b/spec/lib/gitlab/job_waiter_spec.rb index b000f55e739..e3d7d59df04 100644 --- a/spec/lib/gitlab/job_waiter_spec.rb +++ b/spec/lib/gitlab/job_waiter_spec.rb @@ -62,6 +62,10 @@ RSpec.describe Gitlab::JobWaiter, :redis, feature_category: :shared do before do allow_any_instance_of(described_class).to receive(:wait).and_call_original + stub_feature_flags( + use_primary_and_secondary_stores_for_shared_state: false, + use_primary_store_as_default_for_shared_state: false + ) end it 'returns when all jobs have been completed' do @@ -83,36 +87,54 @@ RSpec.describe Gitlab::JobWaiter, :redis, feature_category: :shared do expect(result).to contain_exactly('a') end - context 'when a label is provided' do - let(:waiter) { described_class.new(2, worker_label: 'Foo') } - let(:started_total) { double(:started_total) } - let(:timeouts_total) { double(:timeouts_total) } + context 'when migration is ongoing' do + let(:waiter) { described_class.new(3) } - before do - allow(Gitlab::Metrics).to receive(:counter) - .with(described_class::STARTED_METRIC, anything) - .and_return(started_total) + shared_examples 'returns all jobs' do + it 'returns all jobs' do + result = nil + expect { Timeout.timeout(6) { result = waiter.wait(5) } }.not_to raise_error - allow(Gitlab::Metrics).to receive(:counter) - .with(described_class::TIMEOUTS_METRIC, anything) - .and_return(timeouts_total) + expect(result).to contain_exactly('a', 'b', 'c') + end end - it 'increments just job_waiter_started_total when all jobs complete' do - expect(started_total).to receive(:increment).with(worker: 'Foo') - expect(timeouts_total).not_to receive(:increment) + context 'when using both stores' do + context 'with existing jobs in old store' do + before do + described_class.notify(waiter.key, 'a') + described_class.notify(waiter.key, 'b') + described_class.notify(waiter.key, 'c') + stub_feature_flags(use_primary_and_secondary_stores_for_shared_state: true) + end - described_class.notify(waiter.key, 'a') - described_class.notify(waiter.key, 'b') + it_behaves_like 'returns all jobs' + end - expect { Timeout.timeout(1) { waiter.wait(2) } }.not_to raise_error - end + context 'with jobs in both stores' do + before do + stub_feature_flags(use_primary_and_secondary_stores_for_shared_state: true) + described_class.notify(waiter.key, 'a') + described_class.notify(waiter.key, 'b') + described_class.notify(waiter.key, 'c') + end - it 'increments job_waiter_started_total and job_waiter_timeouts_total when it times out' do - expect(started_total).to receive(:increment).with(worker: 'Foo') - expect(timeouts_total).to receive(:increment).with(worker: 'Foo') + it_behaves_like 'returns all jobs' + end - expect { Timeout.timeout(2) { waiter.wait(1) } }.not_to raise_error + context 'when using primary store as default store' do + before do + stub_feature_flags( + use_primary_and_secondary_stores_for_shared_state: true, + use_primary_store_as_default_for_shared_state: true + ) + described_class.notify(waiter.key, 'a') + described_class.notify(waiter.key, 'b') + described_class.notify(waiter.key, 'c') + end + + it_behaves_like 'returns all jobs' + end end end end diff --git a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb index 3028e0a13aa..f88f7c4c108 100644 --- a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb +++ b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb @@ -7,7 +7,7 @@ require_relative '../../../../lib/gitlab/kubernetes/pod_cmd' RSpec.describe Gitlab::Kubernetes::KubectlCmd do describe '.delete' do it 'constructs string properly' do - args = %w(resource_type type --flag-1 --flag-2) + args = %w[resource_type type --flag-1 --flag-2] expected_command = 'kubectl delete resource_type type --flag-1 --flag-2' @@ -31,7 +31,7 @@ RSpec.describe Gitlab::Kubernetes::KubectlCmd do context 'with optional args' do it 'constructs command properly with many args' do - args = %w(arg-1 --flag-0-1 arg-2 --flag-0-2) + args = %w[arg-1 --flag-0-1 arg-2 --flag-0-2] expected_command = 'kubectl apply -f filename arg-1 --flag-0-1 arg-2 --flag-0-2' diff --git a/spec/lib/gitlab/kubernetes/role_spec.rb b/spec/lib/gitlab/kubernetes/role_spec.rb index acb9b5d4e8e..288a5406372 100644 --- a/spec/lib/gitlab/kubernetes/role_spec.rb +++ b/spec/lib/gitlab/kubernetes/role_spec.rb @@ -9,9 +9,9 @@ RSpec.describe Gitlab::Kubernetes::Role do let(:rules) do [{ - apiGroups: %w(hello.world), - resources: %w(oil diamonds coffee), - verbs: %w(say do walk run) + apiGroups: %w[hello.world], + resources: %w[oil diamonds coffee], + verbs: %w[say do walk run] }] end diff --git a/spec/lib/gitlab/language_data_spec.rb b/spec/lib/gitlab/language_data_spec.rb index bb4b0c3855c..828fd95f78e 100644 --- a/spec/lib/gitlab/language_data_spec.rb +++ b/spec/lib/gitlab/language_data_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Gitlab::LanguageData do expect(described_class.extensions).to be_a(Set) expect(described_class.extensions.count).to be > 0 # Sanity check for known extensions - expect(described_class.extensions).to include(*%w(.rb .yml .json)) + expect(described_class.extensions).to include(*%w[.rb .yml .json]) end end end diff --git a/spec/lib/gitlab/mail_room/mail_room_spec.rb b/spec/lib/gitlab/mail_room/mail_room_spec.rb index 7259b5e2484..d3ddf034cd3 100644 --- a/spec/lib/gitlab/mail_room/mail_room_spec.rb +++ b/spec/lib/gitlab/mail_room/mail_room_spec.rb @@ -245,7 +245,6 @@ RSpec.describe Gitlab::MailRoom, feature_category: :build do delivery_options: { redis_url: "localhost", redis_db: 99, - namespace: "resque:gitlab", queue: "default", worker: "EmailReceiverWorker", sentinels: [{ host: "localhost", port: 1234 }] @@ -258,7 +257,6 @@ RSpec.describe Gitlab::MailRoom, feature_category: :build do delivery_options: { redis_url: "localhost", redis_db: 99, - namespace: "resque:gitlab", queue: "default", worker: "ServiceDeskEmailReceiverWorker", sentinels: [{ host: "localhost", port: 1234 }] diff --git a/spec/lib/gitlab/markup_helper_spec.rb b/spec/lib/gitlab/markup_helper_spec.rb index 2bffd029568..a7508288f4e 100644 --- a/spec/lib/gitlab/markup_helper_spec.rb +++ b/spec/lib/gitlab/markup_helper_spec.rb @@ -4,8 +4,8 @@ require 'fast_spec_helper' RSpec.describe Gitlab::MarkupHelper do describe '#markup?' do - %w(textile rdoc org creole wiki - mediawiki rst adoc ad asciidoc mdown md markdown).each do |type| + %w[textile rdoc org creole wiki + mediawiki rst adoc ad asciidoc mdown md markdown].each do |type| it "returns true for #{type} files" do expect(described_class.markup?("README.#{type}")).to be_truthy end @@ -17,7 +17,7 @@ RSpec.describe Gitlab::MarkupHelper do end describe '#gitlab_markdown?' do - %w(mdown mkd mkdn md markdown).each do |type| + %w[mdown mkd mkdn md markdown].each do |type| it "returns true for #{type} files" do expect(described_class.gitlab_markdown?("README.#{type}")).to be_truthy end @@ -29,7 +29,7 @@ RSpec.describe Gitlab::MarkupHelper do end describe '#asciidoc?' do - %w(adoc ad asciidoc ADOC).each do |type| + %w[adoc ad asciidoc ADOC].each do |type| it "returns true for #{type} files" do expect(described_class.asciidoc?("README.#{type}")).to be_truthy end diff --git a/spec/lib/gitlab/memory/instrumentation_spec.rb b/spec/lib/gitlab/memory/instrumentation_spec.rb index f287edb7da3..059bcad37e7 100644 --- a/spec/lib/gitlab/memory/instrumentation_spec.rb +++ b/spec/lib/gitlab/memory/instrumentation_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Memory::Instrumentation, feature_category: :application_performance do +RSpec.describe Gitlab::Memory::Instrumentation, feature_category: :cloud_connector do include MemoryInstrumentationHelper before do diff --git a/spec/lib/gitlab/memory/reporter_spec.rb b/spec/lib/gitlab/memory/reporter_spec.rb index 1d19d7129cf..5ba429ae6bc 100644 --- a/spec/lib/gitlab/memory/reporter_spec.rb +++ b/spec/lib/gitlab/memory/reporter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Memory::Reporter, :aggregate_failures, feature_category: :application_performance do +RSpec.describe Gitlab::Memory::Reporter, :aggregate_failures, feature_category: :cloud_connector do let(:fake_report) do Class.new do def name diff --git a/spec/lib/gitlab/memory/reports/heap_dump_spec.rb b/spec/lib/gitlab/memory/reports/heap_dump_spec.rb index 4e235a71bdb..7888f8b0c79 100644 --- a/spec/lib/gitlab/memory/reports/heap_dump_spec.rb +++ b/spec/lib/gitlab/memory/reports/heap_dump_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Memory::Reports::HeapDump, feature_category: :application_performance do +RSpec.describe Gitlab::Memory::Reports::HeapDump, feature_category: :cloud_connector do # Copy this class so we do not mess with its state. let(:klass) { described_class.dup } diff --git a/spec/lib/gitlab/memory/watchdog/configurator_spec.rb b/spec/lib/gitlab/memory/watchdog/configurator_spec.rb index 035652abfe6..cd9ac0d7a8d 100644 --- a/spec/lib/gitlab/memory/watchdog/configurator_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/configurator_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Memory::Watchdog::Configurator, feature_category: :application_performance do +RSpec.describe Gitlab::Memory::Watchdog::Configurator, feature_category: :cloud_connector do shared_examples 'as configurator' do |handler_class, event_reporter_class, sleep_time_env, sleep_time| it 'configures the correct handler' do configurator.call(configuration) diff --git a/spec/lib/gitlab/memory/watchdog/event_reporter_spec.rb b/spec/lib/gitlab/memory/watchdog/event_reporter_spec.rb index f1d241249e2..e27f842bc71 100644 --- a/spec/lib/gitlab/memory/watchdog/event_reporter_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/event_reporter_spec.rb @@ -3,7 +3,7 @@ require 'fast_spec_helper' require 'prometheus/client' -RSpec.describe Gitlab::Memory::Watchdog::EventReporter, feature_category: :application_performance do +RSpec.describe Gitlab::Memory::Watchdog::EventReporter, feature_category: :cloud_connector do let(:logger) { instance_double(::Logger) } let(:violations_counter) { instance_double(::Prometheus::Client::Counter) } let(:violations_handled_counter) { instance_double(::Prometheus::Client::Counter) } diff --git a/spec/lib/gitlab/memory/watchdog/handlers/null_handler_spec.rb b/spec/lib/gitlab/memory/watchdog/handlers/null_handler_spec.rb index 09c76de9611..96cb02393f9 100644 --- a/spec/lib/gitlab/memory/watchdog/handlers/null_handler_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/handlers/null_handler_spec.rb @@ -2,7 +2,7 @@ require 'fast_spec_helper' -RSpec.describe Gitlab::Memory::Watchdog::Handlers::NullHandler, feature_category: :application_performance do +RSpec.describe Gitlab::Memory::Watchdog::Handlers::NullHandler, feature_category: :cloud_connector do subject(:handler) { described_class.instance } describe '#call' do diff --git a/spec/lib/gitlab/memory/watchdog/handlers/puma_handler_spec.rb b/spec/lib/gitlab/memory/watchdog/handlers/puma_handler_spec.rb index 7df95c1722e..4c2fd5a3283 100644 --- a/spec/lib/gitlab/memory/watchdog/handlers/puma_handler_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/handlers/puma_handler_spec.rb @@ -2,7 +2,7 @@ require 'fast_spec_helper' -RSpec.describe Gitlab::Memory::Watchdog::Handlers::PumaHandler, feature_category: :application_performance do +RSpec.describe Gitlab::Memory::Watchdog::Handlers::PumaHandler, feature_category: :cloud_connector do # rubocop: disable RSpec/VerifiedDoubles # In tests, the Puma constant is not loaded so we cannot make this an instance_double. let(:puma_worker_handle_class) { double('Puma::Cluster::WorkerHandle') } diff --git a/spec/lib/gitlab/memory/watchdog/handlers/sidekiq_handler_spec.rb b/spec/lib/gitlab/memory/watchdog/handlers/sidekiq_handler_spec.rb index d1f303e7731..68dd784fb7e 100644 --- a/spec/lib/gitlab/memory/watchdog/handlers/sidekiq_handler_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/handlers/sidekiq_handler_spec.rb @@ -3,7 +3,7 @@ require 'fast_spec_helper' require 'sidekiq' -RSpec.describe Gitlab::Memory::Watchdog::Handlers::SidekiqHandler, feature_category: :application_performance do +RSpec.describe Gitlab::Memory::Watchdog::Handlers::SidekiqHandler, feature_category: :cloud_connector do let(:sleep_time) { 3 } let(:shutdown_timeout_seconds) { 30 } let(:handler_iterations) { 0 } diff --git a/spec/lib/gitlab/memory/watchdog/monitor/rss_memory_limit_spec.rb b/spec/lib/gitlab/memory/watchdog/monitor/rss_memory_limit_spec.rb index 67d185fd2f1..552736a55ef 100644 --- a/spec/lib/gitlab/memory/watchdog/monitor/rss_memory_limit_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/monitor/rss_memory_limit_spec.rb @@ -4,7 +4,7 @@ require 'fast_spec_helper' require 'prometheus/client' require 'support/shared_examples/lib/gitlab/memory/watchdog/monitor_result_shared_examples' -RSpec.describe Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit, feature_category: :application_performance do +RSpec.describe Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit, feature_category: :cloud_connector do let(:max_rss_limit_gauge) { instance_double(::Prometheus::Client::Gauge) } let(:memory_limit_bytes) { 2_097_152_000 } let(:worker_memory_bytes) { 1_048_576_000 } diff --git a/spec/lib/gitlab/memory/watchdog/sidekiq_event_reporter_spec.rb b/spec/lib/gitlab/memory/watchdog/sidekiq_event_reporter_spec.rb index 48595c3f172..06b1646d418 100644 --- a/spec/lib/gitlab/memory/watchdog/sidekiq_event_reporter_spec.rb +++ b/spec/lib/gitlab/memory/watchdog/sidekiq_event_reporter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Memory::Watchdog::SidekiqEventReporter, feature_category: :application_performance do +RSpec.describe Gitlab::Memory::Watchdog::SidekiqEventReporter, feature_category: :cloud_connector do let(:counter) { instance_double(::Prometheus::Client::Counter) } before do diff --git a/spec/lib/gitlab/memory/watchdog_spec.rb b/spec/lib/gitlab/memory/watchdog_spec.rb index dd6bfb6da2c..c442208617f 100644 --- a/spec/lib/gitlab/memory/watchdog_spec.rb +++ b/spec/lib/gitlab/memory/watchdog_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, feature_category: :application_performance do +RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, feature_category: :cloud_connector do context 'watchdog' do let(:configuration) { instance_double(described_class::Configuration) } let(:handler) { instance_double(described_class::Handlers::NullHandler) } diff --git a/spec/lib/gitlab/merge_requests/mergeability/check_result_spec.rb b/spec/lib/gitlab/merge_requests/mergeability/check_result_spec.rb index 74aa3528328..cd84525f7e5 100644 --- a/spec/lib/gitlab/merge_requests/mergeability/check_result_spec.rb +++ b/spec/lib/gitlab/merge_requests/mergeability/check_result_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::MergeRequests::Mergeability::CheckResult do +RSpec.describe Gitlab::MergeRequests::Mergeability::CheckResult, feature_category: :code_review_workflow do subject(:check_result) { described_class } let(:time) { Time.current } @@ -63,6 +63,28 @@ RSpec.describe Gitlab::MergeRequests::Mergeability::CheckResult do end end + describe '.inactive' do + subject(:inactive) { check_result.inactive(payload: payload) } + + let(:payload) { {} } + + it 'creates a inactive result' do + expect(inactive.status).to eq described_class::INACTIVE_STATUS + end + + it 'uses the default payload' do + expect(inactive.payload).to eq described_class.default_payload + end + + context 'when given a payload' do + let(:payload) { { last_run_at: time + 1.day, test: 'test' } } + + it 'uses the payload passed' do + expect(inactive.payload).to eq payload + end + end + end + describe '.from_hash' do subject(:from_hash) { described_class.from_hash(hash) } diff --git a/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb index 6673cc50d67..4184c674823 100644 --- a/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb +++ b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Metrics::Exporter::BaseExporter, feature_category: :application_performance do +RSpec.describe Gitlab::Metrics::Exporter::BaseExporter, feature_category: :cloud_connector do let(:settings) { double('settings') } let(:log_enabled) { false } let(:exporter) { described_class.new(settings, log_enabled: log_enabled, log_file: 'test_exporter.log') } diff --git a/spec/lib/gitlab/metrics/rails_slis_spec.rb b/spec/lib/gitlab/metrics/rails_slis_spec.rb index ef996f61082..3050c769117 100644 --- a/spec/lib/gitlab/metrics/rails_slis_spec.rb +++ b/spec/lib/gitlab/metrics/rails_slis_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Metrics::RailsSlis, feature_category: :error_budgets do before do - allow(Gitlab::Graphql::KnownOperations).to receive(:default).and_return(Gitlab::Graphql::KnownOperations.new(%w(foo bar))) + allow(Gitlab::Graphql::KnownOperations).to receive(:default).and_return(Gitlab::Graphql::KnownOperations.new(%w[foo bar])) end describe '.initialize_request_slis!' do diff --git a/spec/lib/gitlab/metrics/samplers/threads_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/threads_sampler_spec.rb index 5dabafb7c0b..0a3648c8b9a 100644 --- a/spec/lib/gitlab/metrics/samplers/threads_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/threads_sampler_spec.rb @@ -40,11 +40,11 @@ RSpec.describe Gitlab::Metrics::Samplers::ThreadsSampler do context 'thread names', :aggregate_failures do where(:thread_names, :expected_names) do [ - [[nil], %w(unnamed)], + [[nil], %w[unnamed]], [['puma threadpool 1', 'puma threadpool 001', 'puma threadpool 002'], ['puma threadpool']], - [%w(sidekiq_worker_thread), %w(sidekiq_worker_thread)], - [%w(some_sampler some_exporter), %w(some_sampler some_exporter)], - [%w(unknown thing), %w(unrecognized)] + [%w[sidekiq_worker_thread], %w[sidekiq_worker_thread]], + [%w[some_sampler some_exporter], %w[some_sampler some_exporter]], + [%w[unknown thing], %w[unrecognized]] ] end diff --git a/spec/lib/gitlab/metrics/subscribers/action_cable_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_cable_spec.rb index 54868bb6ca4..8fbfcdc3bd5 100644 --- a/spec/lib/gitlab/metrics/subscribers/action_cable_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/action_cable_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Metrics::Subscribers::ActionCable, :request_store, feature_category: :application_performance do +RSpec.describe Gitlab::Metrics::Subscribers::ActionCable, :request_store, feature_category: :cloud_connector do let(:subscriber) { described_class.new } let(:counter) { double(:counter) } let(:transmitted_bytes_counter) { double(:counter) } diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index a3835f9eed0..4820f42ade3 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -206,7 +206,7 @@ RSpec.describe Gitlab::Middleware::Go, feature_category: :source_code_management expect(response[0]).to eq(404) expect(response[1]['Content-Type']).to eq('text/html') - expected_body = %{<html><body>go get #{Gitlab.config.gitlab.url}/#{project.full_path}</body></html>} + expected_body = %(<html><body>go get #{Gitlab.config.gitlab.url}/#{project.full_path}</body></html>) expect(response[2]).to eq([expected_body]) end end @@ -278,7 +278,7 @@ RSpec.describe Gitlab::Middleware::Go, feature_category: :source_code_management project_url = "http://#{Gitlab.config.gitlab.host}/#{path}" expect(response[0]).to eq(200) expect(response[1]['Content-Type']).to eq('text/html') - expected_body = %{<html><head><meta name="go-import" content="#{Gitlab.config.gitlab.host}/#{path} git #{repository_url}"><meta name="go-source" content="#{Gitlab.config.gitlab.host}/#{path} #{project_url} #{project_url}/-/tree/#{branch}{/dir} #{project_url}/-/blob/#{branch}{/dir}/{file}#L{line}"></head><body>go get #{Gitlab.config.gitlab.url}/#{path}</body></html>} + expected_body = %(<html><head><meta name="go-import" content="#{Gitlab.config.gitlab.host}/#{path} git #{repository_url}"><meta name="go-source" content="#{Gitlab.config.gitlab.host}/#{path} #{project_url} #{project_url}/-/tree/#{branch}{/dir} #{project_url}/-/blob/#{branch}{/dir}/{file}#L{line}"></head><body>go get #{Gitlab.config.gitlab.url}/#{path}</body></html>) expect(response[2]).to eq([expected_body]) end end diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb index 509a4bb921b..b857ed47d42 100644 --- a/spec/lib/gitlab/middleware/multipart_spec.rb +++ b/spec/lib/gitlab/middleware/multipart_spec.rb @@ -33,7 +33,7 @@ RSpec.describe Gitlab::Middleware::Multipart do let(:params) { upload_parameters_for(key: 'file', mode: mode, filename: filename, remote_id: remote_id).merge('file.path' => '/should/not/be/read') } it 'builds an UploadedFile' do - expect_uploaded_files(original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file)) + expect_uploaded_files(original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w[file]) subject end @@ -61,7 +61,7 @@ RSpec.describe Gitlab::Middleware::Multipart do let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode, filename: filename) } it 'builds an UploadedFile' do - expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, size: uploaded_file.size, params_path: %w(file)) + expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, size: uploaded_file.size, params_path: %w[file]) subject end diff --git a/spec/lib/gitlab/middleware/path_traversal_check_spec.rb b/spec/lib/gitlab/middleware/path_traversal_check_spec.rb index 3d334a60c49..91081cc88ea 100644 --- a/spec/lib/gitlab/middleware/path_traversal_check_spec.rb +++ b/spec/lib/gitlab/middleware/path_traversal_check_spec.rb @@ -55,6 +55,34 @@ RSpec.describe ::Gitlab::Middleware::PathTraversalCheck, feature_category: :shar end end + shared_examples 'excluded path' do + it 'measures and logs the execution time' do + expect(::Gitlab::PathTraversal) + .not_to receive(:check_path_traversal!) + expect(::Gitlab::AppLogger) + .to receive(:warn) + .with({ class_name: described_class.name, duration_ms: instance_of(Float) }) + .and_call_original + + expect(subject).to eq(fake_response) + end + + context 'with log_execution_time_path_traversal_middleware disabled' do + before do + stub_feature_flags(log_execution_time_path_traversal_middleware: false) + end + + it 'does nothing' do + expect(::Gitlab::PathTraversal) + .not_to receive(:check_path_traversal!) + expect(::Gitlab::AppLogger) + .not_to receive(:warn) + + expect(subject).to eq(fake_response) + end + end + end + shared_examples 'path traversal' do it 'logs the problem and measures the execution time' do expect(::Gitlab::PathTraversal) @@ -70,7 +98,8 @@ RSpec.describe ::Gitlab::Middleware::PathTraversalCheck, feature_category: :shar class_name: described_class.name, duration_ms: instance_of(Float), message: described_class::PATH_TRAVERSAL_MESSAGE, - fullpath: fullpath + fullpath: fullpath, + method: method.upcase }).and_call_original expect(subject).to eq(fake_response) @@ -94,7 +123,8 @@ RSpec.describe ::Gitlab::Middleware::PathTraversalCheck, feature_category: :shar .with({ class_name: described_class.name, message: described_class::PATH_TRAVERSAL_MESSAGE, - fullpath: fullpath + fullpath: fullpath, + method: method.upcase }).and_call_original expect(subject).to eq(fake_response) @@ -110,23 +140,90 @@ RSpec.describe ::Gitlab::Middleware::PathTraversalCheck, feature_category: :shar let(:method) { 'get' } where(:path, :query_params, :shared_example_name) do - '/foo/bar' | {} | 'no issue' - '/foo/../bar' | {} | 'path traversal' - '/foo%2Fbar' | {} | 'no issue' - '/foo%2F..%2Fbar' | {} | 'path traversal' - '/foo%252F..%252Fbar' | {} | 'no issue' - '/foo/bar' | { x: 'foo' } | 'no issue' - '/foo/bar' | { x: 'foo/../bar' } | 'path traversal' - '/foo/bar' | { x: 'foo%2Fbar' } | 'no issue' - '/foo/bar' | { x: 'foo%2F..%2Fbar' } | 'no issue' - '/foo/bar' | { x: 'foo%252F..%252Fbar' } | 'no issue' - '/foo%2F..%2Fbar' | { x: 'foo%252F..%252Fbar' } | 'path traversal' + '/foo/bar' | {} | 'no issue' + '/foo/../bar' | {} | 'path traversal' + '/foo%2Fbar' | {} | 'no issue' + '/foo%2F..%2Fbar' | {} | 'path traversal' + '/foo%252F..%252Fbar' | {} | 'no issue' + + '/foo/bar' | { x: 'foo' } | 'no issue' + '/foo/bar' | { x: 'foo/../bar' } | 'path traversal' + '/foo/bar' | { x: 'foo%2Fbar' } | 'no issue' + '/foo/bar' | { x: 'foo%2F..%2Fbar' } | 'no issue' + '/foo/bar' | { x: 'foo%252F..%252Fbar' } | 'no issue' + '/foo%2F..%2Fbar' | { x: 'foo%252F..%252Fbar' } | 'path traversal' end with_them do it_behaves_like params[:shared_example_name] end + context 'for global search excluded paths' do + excluded_paths = %w[ + /search + /search/count + /api/v4/search + /api/v4/search.json + /api/v4/projects/4/search + /api/v4/projects/4/search.json + /api/v4/projects/4/-/search + /api/v4/projects/4/-/search.json + /api/v4/projects/my%2Fproject/search + /api/v4/projects/my%2Fproject/search.json + /api/v4/projects/my%2Fproject/-/search + /api/v4/projects/my%2Fproject/-/search.json + /api/v4/groups/4/search + /api/v4/groups/4/search.json + /api/v4/groups/4/-/search + /api/v4/groups/4/-/search.json + /api/v4/groups/my%2Fgroup/search + /api/v4/groups/my%2Fgroup/search.json + /api/v4/groups/my%2Fgroup/-/search + /api/v4/groups/my%2Fgroup/-/search.json + ] + query_params_with_no_path_traversal = [ + {}, + { x: 'foo' }, + { x: 'foo%2F..%2Fbar' }, + { x: 'foo%2F..%2Fbar' }, + { x: 'foo%252F..%252Fbar' } + ] + query_params_with_path_traversal = [ + { x: 'foo/../bar' } + ] + + excluded_paths.each do |excluded_path| + [query_params_with_no_path_traversal + query_params_with_path_traversal].flatten.each do |qp| + context "for excluded path #{excluded_path} with query params #{qp}" do + let(:query_params) { qp } + let(:path) { excluded_path } + + it_behaves_like 'excluded path' + end + end + + non_excluded_path = excluded_path.gsub('search', 'searchtest') + + query_params_with_no_path_traversal.each do |qp| + context "for non excluded path #{non_excluded_path} with query params #{qp}" do + let(:query_params) { qp } + let(:path) { non_excluded_path } + + it_behaves_like 'no issue' + end + end + + query_params_with_path_traversal.each do |qp| + context "for non excluded path #{non_excluded_path} with query params #{qp}" do + let(:query_params) { qp } + let(:path) { non_excluded_path } + + it_behaves_like 'path traversal' + end + end + end + end + context 'with a issues search path' do let(:query_params) { {} } let(:path) do @@ -147,6 +244,7 @@ RSpec.describe ::Gitlab::Middleware::PathTraversalCheck, feature_category: :shar '/foo%2Fbar' | {} | 'no issue' '/foo%2F..%2Fbar' | {} | 'path traversal' '/foo%252F..%252Fbar' | {} | 'no issue' + '/foo/bar' | { x: 'foo' } | 'no issue' '/foo/bar' | { x: 'foo/../bar' } | 'no issue' '/foo/bar' | { x: 'foo%2Fbar' } | 'no issue' @@ -158,6 +256,59 @@ RSpec.describe ::Gitlab::Middleware::PathTraversalCheck, feature_category: :shar with_them do it_behaves_like params[:shared_example_name] end + + context 'for global search excluded paths' do + excluded_paths = %w[ + /search + /search/count + /api/v4/search + /api/v4/search.json + /api/v4/projects/4/search + /api/v4/projects/4/search.json + /api/v4/projects/4/-/search + /api/v4/projects/4/-/search.json + /api/v4/projects/my%2Fproject/search + /api/v4/projects/my%2Fproject/search.json + /api/v4/projects/my%2Fproject/-/search + /api/v4/projects/my%2Fproject/-/search.json + /api/v4/groups/4/search + /api/v4/groups/4/search.json + /api/v4/groups/4/-/search + /api/v4/groups/4/-/search.json + /api/v4/groups/my%2Fgroup/search + /api/v4/groups/my%2Fgroup/search.json + /api/v4/groups/my%2Fgroup/-/search + /api/v4/groups/my%2Fgroup/-/search.json + ] + all_query_params = [ + {}, + { x: 'foo' }, + { x: 'foo%2F..%2Fbar' }, + { x: 'foo%2F..%2Fbar' }, + { x: 'foo%252F..%252Fbar' }, + { x: 'foo/../bar' } + ] + + excluded_paths.each do |excluded_path| + all_query_params.each do |qp| + context "for excluded path #{excluded_path} with query params #{qp}" do + let(:query_params) { qp } + let(:path) { excluded_path } + + it_behaves_like 'excluded path' + end + + non_excluded_path = excluded_path.gsub('search', 'searchtest') + + context "for non excluded path #{non_excluded_path} with query params #{qp}" do + let(:query_params) { qp } + let(:path) { excluded_path.gsub('search', 'searchtest') } + + it_behaves_like 'no issue' + end + end + end + end end end @@ -177,6 +328,12 @@ RSpec.describe ::Gitlab::Middleware::PathTraversalCheck, feature_category: :shar '/foo/bar' | { x: 'foo%2Fbar' } '/foo/bar' | { x: 'foo%2F..%2Fbar' } '/foo/bar' | { x: 'foo%252F..%252Fbar' } + '/search' | { x: 'foo/../bar' } + '/search' | { x: 'foo%2F..%2Fbar' } + '/search' | { x: 'foo%252F..%252Fbar' } + '%2Fsearch' | { x: 'foo/../bar' } + '%2Fsearch' | { x: 'foo%2F..%2Fbar' } + '%2Fsearch' | { x: 'foo%252F..%252Fbar' } end with_them do diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb index 1c665ec6e18..e12c0f4e78b 100644 --- a/spec/lib/gitlab/omniauth_initializer_spec.rb +++ b/spec/lib/gitlab/omniauth_initializer_spec.rb @@ -2,7 +2,9 @@ require 'spec_helper' -RSpec.describe Gitlab::OmniauthInitializer do +RSpec.describe Gitlab::OmniauthInitializer, feature_category: :system_access do + include LoginHelpers + let(:devise_config) { class_double(Devise) } subject(:initializer) { described_class.new(devise_config) } @@ -171,7 +173,7 @@ RSpec.describe Gitlab::OmniauthInitializer do end it 'allows "args" array for app_id and app_secret' do - legacy_config = { 'name' => 'legacy', 'args' => %w(123 abc) } + legacy_config = { 'name' => 'legacy', 'args' => %w[123 abc] } expect(devise_config).to receive(:omniauth).with(:legacy, '123', 'abc') @@ -224,6 +226,119 @@ RSpec.describe Gitlab::OmniauthInitializer do subject.execute([shibboleth_config]) end + context 'when SAML providers are configured' do + it 'configures default args for a single SAML provider' do + stub_omniauth_config(providers: [{ name: 'saml', args: { idp_sso_service_url: 'https://saml.example.com' } }]) + + expect(devise_config).to receive(:omniauth).with( + :saml, + { + idp_sso_service_url: 'https://saml.example.com', + attribute_statements: ::Gitlab::Auth::Saml::Config.default_attribute_statements + } + ) + + initializer.execute(Gitlab.config.omniauth.providers) + end + + context 'when configuration provides matching keys' do + before do + stub_omniauth_config( + providers: [ + { + name: 'saml', + args: { idp_sso_service_url: 'https://saml.example.com', attribute_statements: { email: ['custom_attr'] } } + } + ] + ) + end + + it 'merges arguments with user configuration preference' do + expect(devise_config).to receive(:omniauth).with( + :saml, + { + idp_sso_service_url: 'https://saml.example.com', + attribute_statements: ::Gitlab::Auth::Saml::Config.default_attribute_statements + .merge({ email: ['custom_attr'] }) + } + ) + + initializer.execute(Gitlab.config.omniauth.providers) + end + + it 'merges arguments with defaults preference when invert_omniauth_args_merging is not enabled' do + stub_feature_flags(invert_omniauth_args_merging: false) + + expect(devise_config).to receive(:omniauth).with( + :saml, + { + idp_sso_service_url: 'https://saml.example.com', + attribute_statements: ::Gitlab::Auth::Saml::Config.default_attribute_statements + } + ) + + initializer.execute(Gitlab.config.omniauth.providers) + end + end + + it 'configures defaults args for multiple SAML providers' do + stub_omniauth_config( + providers: [ + { name: 'saml', args: { idp_sso_service_url: 'https://saml.example.com' } }, + { + name: 'saml2', + args: { strategy_class: 'OmniAuth::Strategies::SAML', idp_sso_service_url: 'https://saml2.example.com' } + } + ] + ) + + expect(devise_config).to receive(:omniauth).with( + :saml, + { + idp_sso_service_url: 'https://saml.example.com', + attribute_statements: ::Gitlab::Auth::Saml::Config.default_attribute_statements + } + ) + expect(devise_config).to receive(:omniauth).with( + :saml2, + { + idp_sso_service_url: 'https://saml2.example.com', + strategy_class: OmniAuth::Strategies::SAML, + attribute_statements: ::Gitlab::Auth::Saml::Config.default_attribute_statements + } + ) + + initializer.execute(Gitlab.config.omniauth.providers) + end + + it 'merges arguments with user configuration preference for custom SAML provider' do + stub_omniauth_config( + providers: [ + { + name: 'custom_saml', + args: { + strategy_class: 'OmniAuth::Strategies::SAML', + idp_sso_service_url: 'https://saml2.example.com', + attribute_statements: { email: ['custom_attr'] } + } + } + ] + ) + + expect(devise_config).to receive(:omniauth).with( + :custom_saml, + { + idp_sso_service_url: 'https://saml2.example.com', + strategy_class: OmniAuth::Strategies::SAML, + attribute_statements: ::Gitlab::Auth::Saml::Config.default_attribute_statements + .merge({ email: ['custom_attr'] }) + } + ) + + initializer.execute(Gitlab.config.omniauth.providers) + end + end + it 'configures defaults for google_oauth2' do google_config = { 'name' => 'google_oauth2', diff --git a/spec/lib/gitlab/other_markup_spec.rb b/spec/lib/gitlab/other_markup_spec.rb index 34f1e0cfbc5..266cb75a8ac 100644 --- a/spec/lib/gitlab/other_markup_spec.rb +++ b/spec/lib/gitlab/other_markup_spec.rb @@ -59,6 +59,31 @@ RSpec.describe Gitlab::OtherMarkup, feature_category: :wiki do end end + context 'when mediawiki content' do + links = { + 'p' => { + file: 'file.mediawiki', + input: 'Red Bridge (JRuby Embed)', + output: "\n<p>Red Bridge (JRuby Embed)</p>" + }, + 'h1' => { + file: 'file.mediawiki', + input: '= Red Bridge (JRuby Embed) =', + output: "\n\n<h1>\n<a name=\"Red_Bridge_JRuby_Embed\"></a><span>Red Bridge (JRuby Embed)</span>\n</h1>\n" + }, + 'h2' => { + file: 'file.mediawiki', + input: '== Red Bridge (JRuby Embed) ==', + output: "\n\n<h2>\n<a name=\"Red_Bridge_JRuby_Embed\"></a><span>Red Bridge (JRuby Embed)</span>\n</h2>\n" + } + } + links.each do |name, data| + it "does render into #{name} element" do + expect(render(data[:file], data[:input], context)).to eq(data[:output]) + end + end + end + context 'when rendering takes too long' do let_it_be(:file_name) { 'foo.bar' } let_it_be(:project) { create(:project, :repository) } @@ -86,6 +111,24 @@ RSpec.describe Gitlab::OtherMarkup, feature_category: :wiki do end end + context 'RedCloth markup' do + it 'renders textile correctly' do + test_text = '"This is *my* text."' + expected_res = "<p>“This is <strong>my</strong> text.”</p>" + expect(RedCloth.new(test_text).to_html).to eq(expected_res) + end + + it 'protects against malicious backtracking' do + test_text = '<A' + ('A' * 54773) + + expect do + Timeout.timeout(Gitlab::OtherMarkup::RENDER_TIMEOUT.seconds) do + RedCloth.new(test_text, [:sanitize_html]).to_html + end + end.not_to raise_error + end + end + def render(...) described_class.render(...) end diff --git a/spec/lib/gitlab/pages/deployment_update_spec.rb b/spec/lib/gitlab/pages/deployment_update_spec.rb index cf109248f36..9a7564ddd59 100644 --- a/spec/lib/gitlab/pages/deployment_update_spec.rb +++ b/spec/lib/gitlab/pages/deployment_update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Pages::DeploymentUpdate do +RSpec.describe Gitlab::Pages::DeploymentUpdate, feature_category: :pages do let_it_be(:project, refind: true) { create(:project, :repository) } let_it_be(:old_pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) } diff --git a/spec/lib/gitlab/pages/virtual_host_finder_spec.rb b/spec/lib/gitlab/pages/virtual_host_finder_spec.rb index 8c34968bbfc..bc3f9d89b5f 100644 --- a/spec/lib/gitlab/pages/virtual_host_finder_spec.rb +++ b/spec/lib/gitlab/pages/virtual_host_finder_spec.rb @@ -5,10 +5,6 @@ require 'spec_helper' RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do let_it_be(:project) { create(:project) } - before_all do - project.update_pages_deployment!(create(:pages_deployment, project: project)) - end - before do stub_pages_setting(host: 'example.com') end @@ -24,10 +20,6 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do subject(:virtual_domain) { described_class.new(pages_domain.domain).execute } context 'when there are no pages deployed for the project' do - before_all do - project.mark_pages_as_not_deployed - end - it 'returns nil' do expect(virtual_domain).to be_nil end @@ -35,7 +27,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do context 'when there are pages deployed for the project' do before_all do - project.mark_pages_as_deployed + create(:pages_deployment, project: project) end it 'returns the virual domain when there are pages deployed for the project' do @@ -48,10 +40,6 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do context 'when host is a namespace domain' do context 'when there are no pages deployed for the project' do - before_all do - project.mark_pages_as_not_deployed - end - it 'returns no result if the provided host is not subdomain of the Pages host' do virtual_domain = described_class.new("#{project.namespace.path}.something.io").execute @@ -68,7 +56,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do context 'when there are pages deployed for the project' do before_all do - project.mark_pages_as_deployed + create(:pages_deployment, project: project) project.namespace.update!(path: 'topNAMEspace') end @@ -109,10 +97,6 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do end context 'when there are no pages deployed for the project' do - before_all do - project.mark_pages_as_not_deployed - end - it 'returns nil' do expect(virtual_domain).to be_nil end @@ -120,7 +104,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do context 'when there are pages deployed for the project' do before_all do - project.mark_pages_as_deployed + create(:pages_deployment, project: project) end it 'returns the virual domain when there are pages deployed for the project' do @@ -133,9 +117,10 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do it 'prioritizes the unique domain project' do group = create(:group, path: 'unique-domain') other_project = build(:project, path: 'unique-domain.example.com', group: group) - other_project.save!(validate: false) - other_project.update_pages_deployment!(create(:pages_deployment, project: other_project)) - other_project.mark_pages_as_deployed + .tap { |project| project.save!(validate: false) } + + create(:pages_deployment, project: project) + create(:pages_deployment, project: other_project) expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain) expect(virtual_domain.lookup_paths.first.project_id).to eq(project.id) @@ -150,10 +135,6 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do end context 'when there are no pages deployed for the project' do - before_all do - project.mark_pages_as_not_deployed - end - it 'returns nil' do expect(virtual_domain).to be_nil end @@ -161,7 +142,7 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do context 'when there are pages deployed for the project' do before_all do - project.mark_pages_as_deployed + create(:pages_deployment, project: project) end it 'returns nil' do diff --git a/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb b/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb index effe767e41d..e5958549a81 100644 --- a/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb +++ b/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb @@ -28,7 +28,9 @@ RSpec.describe Gitlab::Pagination::CursorBasedKeyset do end describe '.enforced_for_type?' do - subject { described_class.enforced_for_type?(relation) } + let_it_be(:project) { create(:project) } + + subject { described_class.enforced_for_type?(project, relation) } context 'when relation is Group' do let(:relation) { Group.all } @@ -45,7 +47,21 @@ RSpec.describe Gitlab::Pagination::CursorBasedKeyset do context 'when relation is Ci::Build' do let(:relation) { Ci::Build.all } - it { is_expected.to be false } + before do + stub_feature_flags(enforce_ci_builds_pagination_limit: false) + end + + context 'when feature flag enforce_ci_builds_pagination_limit is enabled' do + before do + stub_feature_flags(enforce_ci_builds_pagination_limit: project) + end + + it { is_expected.to be true } + end + + context 'when feature fllag enforce_ci_builds_pagination_limit is disabled' do + it { is_expected.to be false } + end end end diff --git a/spec/lib/gitlab/pagination/keyset/order_spec.rb b/spec/lib/gitlab/pagination/keyset/order_spec.rb index 05bb0bb8b3a..52d2688c06e 100644 --- a/spec/lib/gitlab/pagination/keyset/order_spec.rb +++ b/spec/lib/gitlab/pagination/keyset/order_spec.rb @@ -726,7 +726,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Order do end describe '#attribute_names' do - let(:expected_attribute_names) { %w(id name) } + let(:expected_attribute_names) { %w[id name] } let(:order) do described_class.build( [ diff --git a/spec/lib/gitlab/pagination/offset_header_builder_spec.rb b/spec/lib/gitlab/pagination/offset_header_builder_spec.rb index a415bad5135..f43216702a8 100644 --- a/spec/lib/gitlab/pagination/offset_header_builder_spec.rb +++ b/spec/lib/gitlab/pagination/offset_header_builder_spec.rb @@ -15,11 +15,15 @@ RSpec.describe Gitlab::Pagination::OffsetHeaderBuilder do describe '#execute' do let(:basic_links) do - %{<http://localhost?page=1&per_page=5>; rel="prev", <http://localhost?page=3&per_page=5>; rel="next", <http://localhost?page=1&per_page=5>; rel="first"} + [ + %(<http://localhost?page=1&per_page=5>; rel="prev"), + %(<http://localhost?page=3&per_page=5>; rel="next"), + %(<http://localhost?page=1&per_page=5>; rel="first") + ].join(', ') end let(:last_link) do - %{, <http://localhost?page=3&per_page=5>; rel="last"} + %(, <http://localhost?page=3&per_page=5>; rel="last") end def expect_basic_headers diff --git a/spec/lib/gitlab/patch/sidekiq_scheduled_enq_spec.rb b/spec/lib/gitlab/patch/sidekiq_scheduled_enq_spec.rb index f57257cd1c0..cd3718f5dcc 100644 --- a/spec/lib/gitlab/patch/sidekiq_scheduled_enq_spec.rb +++ b/spec/lib/gitlab/patch/sidekiq_scheduled_enq_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Gitlab::Patch::SidekiqScheduledEnq, :clean_gitlab_redis_queues, f allow(Sidekiq).to receive(:load_json).and_return(payload) # stub data in both namespaces - Sidekiq.redis { |c| c.zadd('schedule', 100, 'dummy') } + Gitlab::Redis::Queues.with { |c| c.zadd('resque:gitlab:schedule', 100, 'dummy') } Gitlab::Redis::Queues.with { |c| c.zadd('schedule', 100, 'dummy') } end @@ -26,7 +26,7 @@ RSpec.describe Gitlab::Patch::SidekiqScheduledEnq, :clean_gitlab_redis_queues, f end Gitlab::Redis::Queues.with do |conn| - expect(conn.zcard('schedule')).to eq(0) + expect(conn.zcard('resque:gitlab:schedule')).to eq(0) end end @@ -45,29 +45,13 @@ RSpec.describe Gitlab::Patch::SidekiqScheduledEnq, :clean_gitlab_redis_queues, f end Gitlab::Redis::Queues.with do |conn| - expect(conn.zcard('schedule')).to eq(1) + expect(conn.zcard('resque:gitlab:schedule')).to eq(1) end end end - context 'when both envvar are enabled' do - around do |example| - # runs the zadd to ensure it goes into namespaced set - Sidekiq.redis { |c| c.zadd('schedule', 100, 'dummy') } - - holder = Sidekiq.redis_pool - - # forcibly replace Sidekiq.redis since this is set in config/initializer/sidekiq.rb - Sidekiq.redis = Gitlab::Redis::Queues.pool - - example.run - - ensure - Sidekiq.redis = holder - end - + context 'when SIDEKIQ_ENABLE_DUAL_NAMESPACE_POLLING is enabled' do before do - stub_env('SIDEKIQ_ENQUEUE_NON_NAMESPACED', 'true') stub_env('SIDEKIQ_ENABLE_DUAL_NAMESPACE_POLLING', 'true') end @@ -81,7 +65,7 @@ RSpec.describe Gitlab::Patch::SidekiqScheduledEnq, :clean_gitlab_redis_queues, f end Gitlab::Redis::Queues.with do |conn| - expect(conn.zcard('schedule')).to eq(0) + expect(conn.zcard('resque:gitlab:schedule')).to eq(0) end end end diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 53dc145dcc4..eee05c0bb15 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -103,21 +103,27 @@ RSpec.describe Gitlab::PathRegex do .concat(Array(API::API.prefix.to_s)) .concat(sitemap_words) .concat(deprecated_routes) + .concat(reserved_routes) .compact .uniq end let(:sitemap_words) do - %w(sitemap sitemap.xml sitemap.xml.gz) + %w[sitemap sitemap.xml sitemap.xml.gz] end let(:deprecated_routes) do # profile was deprecated in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51646 - %w(profile s) + %w[profile s] + end + + let(:reserved_routes) do + # login was reserved in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134791 + ['login'] end let(:ee_top_level_words) do - %w(unsubscribes v2) + %w[unsubscribes v2] end let(:files_in_public) do @@ -126,7 +132,7 @@ RSpec.describe Gitlab::PathRegex do .split("\n") .map { |entry| entry.start_with?('public/-/') ? '-' : entry.gsub('public/', '') } .uniq - tracked + %w(assets uploads) + tracked + %w[assets uploads] end # All routes that start with a namespaced path, that have 1 or more diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index 0a186b07d19..78455cb705f 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Gitlab::Popen do context 'zero status' do before do - @output, @status = @klass.new.popen(%w(ls), path) + @output, @status = @klass.new.popen(%w[ls], path) end it { expect(@status).to be_zero } @@ -33,7 +33,7 @@ RSpec.describe Gitlab::Popen do context 'non-zero status' do before do - @output, @status = @klass.new.popen(%w(cat NOTHING), path) + @output, @status = @klass.new.popen(%w[cat NOTHING], path) end it { expect(@status).to eq(1) } @@ -64,7 +64,7 @@ RSpec.describe Gitlab::Popen do it 'calls popen3 with the provided environment variables' do expect(Open3).to receive(:popen3).with(vars, 'ls', options) - @output, @status = @klass.new.popen(%w(ls), path, { 'foobar' => 123 }) + @output, @status = @klass.new.popen(%w[ls], path, { 'foobar' => 123 }) end end @@ -83,7 +83,7 @@ RSpec.describe Gitlab::Popen do context 'without a directory argument' do before do - @output, @status = @klass.new.popen(%w(ls)) + @output, @status = @klass.new.popen(%w[ls]) end it { expect(@status).to be_zero } diff --git a/spec/lib/gitlab/process_management_spec.rb b/spec/lib/gitlab/process_management_spec.rb index fbd39702efb..709e4611954 100644 --- a/spec/lib/gitlab/process_management_spec.rb +++ b/spec/lib/gitlab/process_management_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Gitlab::ProcessManagement do expect(described_class).to receive(:trap).ordered.with(:INT) expect(described_class).to receive(:trap).ordered.with(:HUP) - described_class.trap_signals(%i(INT HUP)) + described_class.trap_signals(%i[INT HUP]) end end @@ -17,7 +17,7 @@ RSpec.describe Gitlab::ProcessManagement do expect(described_class).to receive(:trap).ordered.with(:INT, 'DEFAULT') expect(described_class).to receive(:trap).ordered.with(:HUP, 'DEFAULT') - described_class.modify_signals(%i(INT HUP), 'DEFAULT') + described_class.modify_signals(%i[INT HUP], 'DEFAULT') end end diff --git a/spec/lib/gitlab/process_supervisor_spec.rb b/spec/lib/gitlab/process_supervisor_spec.rb index 18de5053362..94535e96843 100644 --- a/spec/lib/gitlab/process_supervisor_spec.rb +++ b/spec/lib/gitlab/process_supervisor_spec.rb @@ -2,7 +2,7 @@ require_relative '../../../lib/gitlab/process_supervisor' -RSpec.describe Gitlab::ProcessSupervisor, feature_category: :application_performance do +RSpec.describe Gitlab::ProcessSupervisor, feature_category: :cloud_connector do let(:health_check_interval_seconds) { 0.1 } let(:check_terminate_interval_seconds) { 1 } let(:forwarded_signals) { [] } @@ -152,13 +152,13 @@ RSpec.describe Gitlab::ProcessSupervisor, feature_category: :application_perform end context 'termination signals' do - let(:term_signals) { %i(INT TERM) } + let(:term_signals) { %i[INT TERM] } context 'when TERM results in timely shutdown of processes' do it 'forwards them to observed processes without waiting for grace period to expire' do allow(Gitlab::ProcessManagement).to receive(:any_alive?).and_return(false) - expect(Gitlab::ProcessManagement).to receive(:trap_signals).ordered.with(%i(INT TERM)).and_yield(:TERM) + expect(Gitlab::ProcessManagement).to receive(:trap_signals).ordered.with(%i[INT TERM]).and_yield(:TERM) expect(Gitlab::ProcessManagement).to receive(:signal_processes).ordered.with(process_ids, :TERM) expect(supervisor).not_to receive(:sleep).with(check_terminate_interval_seconds) @@ -168,7 +168,7 @@ RSpec.describe Gitlab::ProcessSupervisor, feature_category: :application_perform context 'when TERM does not result in timely shutdown of processes' do it 'issues a KILL signal after the grace period expires' do - expect(Gitlab::ProcessManagement).to receive(:trap_signals).with(%i(INT TERM)).and_yield(:TERM) + expect(Gitlab::ProcessManagement).to receive(:trap_signals).with(%i[INT TERM]).and_yield(:TERM) expect(Gitlab::ProcessManagement).to receive(:signal_processes).ordered.with(process_ids, :TERM) expect(supervisor).to receive(:sleep).ordered.with(check_terminate_interval_seconds).at_least(:once) expect(Gitlab::ProcessManagement).to receive(:signal_processes).ordered.with(process_ids, '-KILL') @@ -179,10 +179,10 @@ RSpec.describe Gitlab::ProcessSupervisor, feature_category: :application_perform end context 'forwarded signals' do - let(:forwarded_signals) { %i(USR1) } + let(:forwarded_signals) { %i[USR1] } it 'forwards given signals to the observed processes' do - expect(Gitlab::ProcessManagement).to receive(:trap_signals).with(%i(USR1)).and_yield(:USR1) + expect(Gitlab::ProcessManagement).to receive(:trap_signals).with(%i[USR1]).and_yield(:USR1) expect(Gitlab::ProcessManagement).to receive(:signal_processes).ordered.with(process_ids, :USR1) supervisor.supervise(process_ids) { [] } diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb index 998fff12e94..07cdbf97091 100644 --- a/spec/lib/gitlab/project_template_spec.rb +++ b/spec/lib/gitlab/project_template_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::ProjectTemplate do +RSpec.describe Gitlab::ProjectTemplate, feature_category: :source_code_management do include ProjectTemplateTestHelper describe '.all' do diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb index f91e8d2a7ef..063b416c514 100644 --- a/spec/lib/gitlab/quick_actions/extractor_spec.rb +++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb @@ -246,7 +246,7 @@ RSpec.describe Gitlab::QuickActions::Extractor, feature_category: :team_planning msg = %(hello\nworld\n/reopen\n/shrug this is great?\n/shrug meh) msg, commands = extractor.extract_commands(msg) - expect(commands).to eq [['reopen'], ['shrug', 'this is great?'], %w(shrug meh)] + expect(commands).to eq [['reopen'], ['shrug', 'this is great?'], %w[shrug meh]] expect(msg).to eq "hello\nworld\nthis is great? SHRUG\nmeh SHRUG" end diff --git a/spec/lib/gitlab/redis/multi_store_spec.rb b/spec/lib/gitlab/redis/multi_store_spec.rb index 1745a745ec3..6b1c0fb2e81 100644 --- a/spec/lib/gitlab/redis/multi_store_spec.rb +++ b/spec/lib/gitlab/redis/multi_store_spec.rb @@ -3,7 +3,6 @@ require 'spec_helper' RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do - using RSpec::Parameterized::TableSyntax include RedisHelpers let_it_be(:redis_store_class) { define_helper_redis_store_class } @@ -56,7 +55,6 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do context 'when primary_store is not a ::Redis instance' do before do allow(primary_store).to receive(:is_a?).with(::Redis).and_return(false) - allow(primary_store).to receive(:is_a?).with(::Redis::Namespace).and_return(false) end it 'fails with exception' do @@ -65,21 +63,9 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do end end - context 'when primary_store is a ::Redis::Namespace instance' do - before do - allow(primary_store).to receive(:is_a?).with(::Redis).and_return(false) - allow(primary_store).to receive(:is_a?).with(::Redis::Namespace).and_return(true) - end - - it 'fails with exception' do - expect { described_class.new(primary_store, secondary_store, instance_name) }.not_to raise_error - end - end - context 'when secondary_store is not a ::Redis instance' do before do allow(secondary_store).to receive(:is_a?).with(::Redis).and_return(false) - allow(secondary_store).to receive(:is_a?).with(::Redis::Namespace).and_return(false) end it 'fails with exception' do @@ -88,130 +74,22 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do end end - context 'when secondary_store is a ::Redis::Namespace instance' do - before do - allow(secondary_store).to receive(:is_a?).with(::Redis).and_return(false) - allow(secondary_store).to receive(:is_a?).with(::Redis::Namespace).and_return(true) - end - - it 'fails with exception' do - expect { described_class.new(primary_store, secondary_store, instance_name) }.not_to raise_error - end - end - # rubocop:disable RSpec/MultipleMemoizedHelpers context 'with READ redis commands' do subject do multi_store.send(name, *args, **kwargs) end - let_it_be(:key1) { "redis:{1}:key_a" } - let_it_be(:key2) { "redis:{1}:key_b" } - let_it_be(:value1) { "redis_value1" } - let_it_be(:value2) { "redis_value2" } - let_it_be(:skey) { "redis:set:key" } - let_it_be(:skey2) { "redis:set:key2" } - let_it_be(:smemberargs) { [skey, value1] } - let_it_be(:hkey) { "redis:hash:key" } - let_it_be(:hkey2) { "redis:hash:key2" } - let_it_be(:zkey) { "redis:sortedset:key" } - let_it_be(:zkey2) { "redis:sortedset:key2" } - let_it_be(:hitem1) { "item1" } - let_it_be(:hitem2) { "item2" } - let_it_be(:keys) { [key1, key2] } - let_it_be(:values) { [value1, value2] } - let_it_be(:svalues) { [value2, value1] } - let_it_be(:hgetargs) { [hkey, hitem1] } - let_it_be(:hmgetval) { [value1] } - let_it_be(:mhmgetargs) { [hkey, hitem1] } - let_it_be(:hvalmapped) { { "item1" => value1 } } - let_it_be(:sscanargs) { [skey2, 0] } - let_it_be(:sscanval) { ["0", [value1]] } - let_it_be(:scanargs) { ["0"] } - let_it_be(:scankwargs) { { match: '*:set:key2*' } } - let_it_be(:scanval) { ["0", [skey2]] } - let_it_be(:sscan_eachval) { [value1] } - let_it_be(:sscan_each_arg) { { match: '*1*' } } - let_it_be(:hscan_eachval) { [[hitem1, value1]] } - let_it_be(:zscan_eachval) { [[value1, 1.0]] } - let_it_be(:scan_each_arg) { { match: 'redis*' } } - let_it_be(:scan_each_val) { [key1, key2, skey, skey2, hkey, hkey2, zkey, zkey2] } - - # rubocop:disable Layout/LineLength - where(:case_name, :name, :args, :value, :kwargs, :block) do - 'execute :get command' | :get | ref(:key1) | ref(:value1) | {} | nil - 'execute :mget command' | :mget | ref(:keys) | ref(:values) | {} | nil - 'execute :mget with block' | :mget | ref(:keys) | ref(:values) | {} | ->(value) { value } - 'execute :smembers command' | :smembers | ref(:skey) | ref(:svalues) | {} | nil - 'execute :scard command' | :scard | ref(:skey) | 2 | {} | nil - 'execute :sismember command' | :sismember | ref(:smemberargs) | true | {} | nil - 'execute :exists command' | :exists | ref(:key1) | 1 | {} | nil - 'execute :exists? command' | :exists? | ref(:key1) | true | {} | nil - 'execute :hget command' | :hget | ref(:hgetargs) | ref(:value1) | {} | nil - 'execute :hlen command' | :hlen | ref(:hkey) | 1 | {} | nil - 'execute :hgetall command' | :hgetall | ref(:hkey) | ref(:hvalmapped) | {} | nil - 'execute :hexists command' | :hexists | ref(:hgetargs) | true | {} | nil - 'execute :hmget command' | :hmget | ref(:hgetargs) | ref(:hmgetval) | {} | nil - 'execute :mapped_hmget command' | :mapped_hmget | ref(:mhmgetargs) | ref(:hvalmapped) | {} | nil - 'execute :sscan command' | :sscan | ref(:sscanargs) | ref(:sscanval) | {} | nil - 'execute :scan command' | :scan | ref(:scanargs) | ref(:scanval) | ref(:scankwargs) | nil - - # we run *scan_each here as they are reads too - 'execute :scan_each command' | :scan_each | nil | ref(:scan_each_val) | ref(:scan_each_arg) | nil - 'execute :sscan_each command' | :sscan_each | ref(:skey2) | ref(:sscan_eachval) | {} | nil - 'execute :sscan_each w block' | :sscan_each | ref(:skey) | ref(:sscan_eachval) | ref(:sscan_each_arg) | nil - 'execute :hscan_each command' | :hscan_each | ref(:hkey) | ref(:hscan_eachval) | {} | nil - 'execute :hscan_each w block' | :hscan_each | ref(:hkey2) | ref(:hscan_eachval) | ref(:sscan_each_arg) | nil - 'execute :zscan_each command' | :zscan_each | ref(:zkey) | ref(:zscan_eachval) | {} | nil - 'execute :zscan_each w block' | :zscan_each | ref(:zkey2) | ref(:zscan_eachval) | ref(:sscan_each_arg) | nil - end - # rubocop:enable Layout/LineLength - - before do - primary_store.set(key1, value1) - primary_store.set(key2, value2) - primary_store.sadd?(skey, [value1, value2]) - primary_store.sadd?(skey2, [value1]) - primary_store.hset(hkey, hitem1, value1) - primary_store.hset(hkey2, hitem1, value1, hitem2, value2) - primary_store.zadd(zkey, 1, value1) - primary_store.zadd(zkey2, [[1, value1], [2, value2]]) - - secondary_store.set(key1, value1) - secondary_store.set(key2, value2) - secondary_store.sadd?(skey, [value1, value2]) - secondary_store.sadd?(skey2, [value1]) - secondary_store.hset(hkey, hitem1, value1) - secondary_store.hset(hkey2, hitem1, value1, hitem2, value2) - secondary_store.zadd(zkey, 1, value1) - secondary_store.zadd(zkey2, [[1, value1], [2, value2]]) - end - - after do - primary_store.flushdb - secondary_store.flushdb - end - - RSpec.shared_examples_for 'reads correct value' do - it 'returns the correct value' do - if value.is_a?(Array) - # :smembers does not guarantee the order it will return the values (unsorted set) - is_expected.to match_array(value) - else - is_expected.to eq(value) - end - end - end + let(:args) { 'args' } + let(:kwargs) { { match: '*:set:key2*' } } RSpec.shared_examples_for 'secondary store' do it 'execute on the secondary instance' do - expect(secondary_store).to receive(name).with(*expected_args).and_call_original + expect(secondary_store).to receive(name).with(*expected_args) subject end - include_examples 'reads correct value' - it 'does not execute on the primary store' do expect(primary_store).not_to receive(name) @@ -219,23 +97,22 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do end end - with_them do + described_class::READ_COMMANDS.each do |name| describe name.to_s do - let(:expected_args) { kwargs&.present? ? [*args, { **kwargs }] : Array(args) } + let(:expected_args) { [*args, { **kwargs }] } + let(:name) { name } before do - allow(primary_store).to receive(name).and_call_original - allow(secondary_store).to receive(name).and_call_original + allow(primary_store).to receive(name) + allow(secondary_store).to receive(name) end context 'when reading from the primary is successful' do it 'returns the correct value' do - expect(primary_store).to receive(name).with(*expected_args).and_call_original + expect(primary_store).to receive(name).with(*expected_args) subject end - - include_examples 'reads correct value' end context 'when reading from default instance is raising an exception' do @@ -246,23 +123,13 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do it 'logs the exception and re-raises the error' do expect(Gitlab::ErrorTracking).to receive(:log_exception).with(an_instance_of(StandardError), - hash_including(:multi_store_error_message, instance_name: instance_name, command_name: name)) + hash_including(:multi_store_error_message, + instance_name: instance_name, command_name: name)) expect { subject }.to raise_error(an_instance_of(StandardError)) end end - context 'when reading from empty default instance' do - before do - # this ensures a cache miss without having to stub the default store - multi_store.default_store.flushdb - end - - it 'does not call the non_default_store' do - expect(multi_store.non_default_store).not_to receive(name) - end - end - context 'when the command is executed within pipelined block' do subject do multi_store.pipelined do |pipeline| @@ -276,7 +143,7 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do 2.times do expect_next_instance_of(Redis::PipelinedConnection) do |pipeline| - expect(pipeline).to receive(name).with(*expected_args).once.and_call_original + expect(pipeline).to receive(name).with(*expected_args).once end end @@ -284,27 +151,16 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do end end - if params[:block] + context 'when block provided' do subject do - multi_store.send(name, *expected_args, &block) + multi_store.send(name, expected_args) { nil } end - context 'when block is provided' do - it 'only default store yields to the block' do - expect(primary_store).to receive(name).and_yield(value) - expect(secondary_store).not_to receive(name).and_yield(value) - - subject - end - - it 'only default store to execute' do - expect(primary_store).to receive(name).with(*expected_args).and_call_original - expect(secondary_store).not_to receive(name).with(*expected_args).and_call_original - - subject - end + it 'only default store to execute' do + expect(primary_store).to receive(:send).with(name, expected_args) + expect(secondary_store).not_to receive(:send) - include_examples 'reads correct value' + subject end end @@ -327,8 +183,8 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do end it 'executes only on secondary redis store', :aggregate_failures do - expect(secondary_store).to receive(name).with(*expected_args).and_call_original - expect(primary_store).not_to receive(name).with(*expected_args).and_call_original + expect(secondary_store).to receive(name).with(*expected_args) + expect(primary_store).not_to receive(name).with(*expected_args) subject end @@ -336,8 +192,8 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do context 'when using primary store as default' do it 'executes only on primary redis store', :aggregate_failures do - expect(primary_store).to receive(name).with(*expected_args).and_call_original - expect(secondary_store).not_to receive(name).with(*expected_args).and_call_original + expect(primary_store).to receive(name).with(*expected_args) + expect(secondary_store).not_to receive(name).with(*expected_args) subject end @@ -429,110 +285,24 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do end end - RSpec.shared_examples_for 'verify that store contains values' do |store| - it "#{store} redis store contains correct values", :aggregate_failures do - subject - - redis_store = multi_store.send(store) - - if expected_value.is_a?(Array) - # :smembers does not guarantee the order it will return the values - expect(redis_store.send(verification_name, *verification_args)).to match_array(expected_value) - else - expect(redis_store.send(verification_name, *verification_args)).to eq(expected_value) - end - end - end - - # rubocop:disable RSpec/MultipleMemoizedHelpers context 'with WRITE redis commands' do - let_it_be(:ikey1) { "counter1" } - let_it_be(:ikey2) { "counter2" } - let_it_be(:iargs) { [ikey2, 3] } - let_it_be(:ivalue1) { "1" } - let_it_be(:ivalue2) { "3" } - let_it_be(:key1) { "redis:{1}:key_a" } - let_it_be(:key2) { "redis:{1}:key_b" } - let_it_be(:key3) { "redis:{1}:key_c" } - let_it_be(:key4) { "redis:{1}:key_d" } - let_it_be(:value1) { "redis_value1" } - let_it_be(:value2) { "redis_value2" } - let_it_be(:key1_value1) { [key1, value1] } - let_it_be(:key1_value2) { [key1, value2] } - let_it_be(:ttl) { 10 } - let_it_be(:key1_ttl_value1) { [key1, ttl, value1] } - let_it_be(:skey) { "redis:set:key" } - let_it_be(:svalues1) { [value2, value1] } - let_it_be(:svalues2) { [value1] } - let_it_be(:skey_value1) { [skey, [value1]] } - let_it_be(:skey_value2) { [skey, [value2]] } - let_it_be(:script) { %(redis.call("set", "#{key1}", "#{value1}")) } - let_it_be(:hkey1) { "redis:{1}:hash_a" } - let_it_be(:hkey2) { "redis:{1}:hash_b" } - let_it_be(:item) { "item" } - let_it_be(:hdelarg) { [hkey1, item] } - let_it_be(:hsetarg) { [hkey2, item, value1] } - let_it_be(:mhsetarg) { [hkey2, { "item" => value1 }] } - let_it_be(:hgetarg) { [hkey2, item] } - let_it_be(:expireargs) { [key3, ttl] } - - # rubocop:disable Layout/LineLength - where(:case_name, :name, :args, :expected_value, :verification_name, :verification_args) do - 'execute :set command' | :set | ref(:key1_value1) | ref(:value1) | :get | ref(:key1) - 'execute :setnx command' | :setnx | ref(:key1_value2) | ref(:value1) | :get | ref(:key2) - 'execute :setex command' | :setex | ref(:key1_ttl_value1) | ref(:ttl) | :ttl | ref(:key1) - 'execute :sadd command' | :sadd | ref(:skey_value2) | ref(:svalues1) | :smembers | ref(:skey) - 'execute :sadd? command' | :sadd? | ref(:skey_value2) | ref(:svalues1) | :smembers | ref(:skey) - 'execute :srem command' | :srem | ref(:skey_value1) | [] | :smembers | ref(:skey) - 'execute :del command' | :del | ref(:key2) | nil | :get | ref(:key2) - 'execute :unlink command' | :unlink | ref(:key3) | nil | :get | ref(:key3) - 'execute :flushdb command' | :flushdb | nil | 0 | :dbsize | nil - 'execute :eval command' | :eval | ref(:script) | ref(:value1) | :get | ref(:key1) - 'execute :incr command' | :incr | ref(:ikey1) | ref(:ivalue1) | :get | ref(:ikey1) - 'execute :incrby command' | :incrby | ref(:iargs) | ref(:ivalue2) | :get | ref(:ikey2) - 'execute :hset command' | :hset | ref(:hsetarg) | ref(:value1) | :hget | ref(:hgetarg) - 'execute :hdel command' | :hdel | ref(:hdelarg) | nil | :hget | ref(:hdelarg) - 'execute :expire command' | :expire | ref(:expireargs) | ref(:ttl) | :ttl | ref(:key3) - 'execute :mapped_hmset command' | :mapped_hmset | ref(:mhsetarg) | ref(:value1) | :hget | ref(:hgetarg) - end - # rubocop:enable Layout/LineLength - - before do - primary_store.flushdb - secondary_store.flushdb - - primary_store.set(key2, value1) - primary_store.set(key3, value1) - primary_store.set(key4, value1) - primary_store.sadd?(skey, value1) - primary_store.hset(hkey2, item, value1) - - secondary_store.set(key2, value1) - secondary_store.set(key3, value1) - secondary_store.set(key4, value1) - secondary_store.sadd?(skey, value1) - secondary_store.hset(hkey2, item, value1) - end - - with_them do + described_class::WRITE_COMMANDS.each do |name| describe name.to_s do - let(:expected_args) { args || no_args } + let(:args) { "dummy_args" } + let(:name) { name } before do - allow(primary_store).to receive(name).and_call_original - allow(secondary_store).to receive(name).and_call_original + allow(primary_store).to receive(name) + allow(secondary_store).to receive(name) end context 'when executing on primary instance is successful' do it 'executes on both primary and secondary redis store', :aggregate_failures do - expect(primary_store).to receive(name).with(*expected_args).and_call_original - expect(secondary_store).to receive(name).with(*expected_args).and_call_original + expect(primary_store).to receive(name).with(*args) + expect(secondary_store).to receive(name).with(*args) subject end - - include_examples 'verify that store contains values', :primary_store - include_examples 'verify that store contains values', :secondary_store end context 'when use_primary_and_secondary_stores feature flag is disabled' do @@ -546,8 +316,8 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do end it 'executes only on secondary redis store', :aggregate_failures do - expect(secondary_store).to receive(name).with(*expected_args).and_call_original - expect(primary_store).not_to receive(name).with(*expected_args).and_call_original + expect(secondary_store).to receive(name).with(*args) + expect(primary_store).not_to receive(name).with(*args) subject end @@ -555,8 +325,8 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do context 'when using primary store as default' do it 'executes only on primary redis store', :aggregate_failures do - expect(primary_store).to receive(name).with(*expected_args).and_call_original - expect(secondary_store).not_to receive(name).with(*expected_args).and_call_original + expect(primary_store).to receive(name).with(*args) + expect(secondary_store).not_to receive(name).with(*args) subject end @@ -565,31 +335,30 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do context 'when executing on the default instance is raising an exception' do before do - allow(multi_store.default_store).to receive(name).with(*expected_args).and_raise(StandardError) + allow(multi_store.default_store).to receive(name).with(*args).and_raise(StandardError) allow(Gitlab::ErrorTracking).to receive(:log_exception) end it 'raises error and does not execute on non default instance', :aggregate_failures do - expect(multi_store.non_default_store).not_to receive(name).with(*expected_args) + expect(multi_store.non_default_store).not_to receive(name).with(*args) expect { subject }.to raise_error(StandardError) end end context 'when executing on the non default instance is raising an exception' do before do - allow(multi_store.non_default_store).to receive(name).with(*expected_args).and_raise(StandardError) + allow(multi_store.non_default_store).to receive(name).with(*args).and_raise(StandardError) allow(Gitlab::ErrorTracking).to receive(:log_exception) end it 'logs the exception and execute on default instance', :aggregate_failures do expect(Gitlab::ErrorTracking).to receive(:log_exception).with(an_instance_of(StandardError), - hash_including(:multi_store_error_message, command_name: name, instance_name: instance_name)) - expect(multi_store.default_store).to receive(name).with(*expected_args).and_call_original + hash_including(:multi_store_error_message, + command_name: name, instance_name: instance_name)) + expect(multi_store.default_store).to receive(name).with(*args) subject end - - include_examples 'verify that store contains values', :default_store end context 'when the command is executed within pipelined block' do @@ -602,24 +371,35 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do it 'is executed only 1 time on each instance', :aggregate_failures do expect(primary_store).to receive(:pipelined).and_call_original expect_next_instance_of(Redis::PipelinedConnection) do |pipeline| - expect(pipeline).to receive(name).with(*expected_args).once.and_call_original + expect(pipeline).to receive(name).with(*args).once end expect(secondary_store).to receive(:pipelined).and_call_original expect_next_instance_of(Redis::PipelinedConnection) do |pipeline| - expect(pipeline).to receive(name).with(*expected_args).once.and_call_original + expect(pipeline).to receive(name).with(*args).once end subject end - - include_examples 'verify that store contains values', :primary_store - include_examples 'verify that store contains values', :secondary_store end end end end - # rubocop:enable RSpec/MultipleMemoizedHelpers + + RSpec.shared_examples_for 'verify that store contains values' do |store| + it "#{store} redis store contains correct values", :aggregate_failures do + subject + + redis_store = multi_store.send(store) + + if expected_value.is_a?(Array) + # :smembers does not guarantee the order it will return the values + expect(redis_store.send(verification_name, *verification_args)).to match_array(expected_value) + else + expect(redis_store.send(verification_name, *verification_args)).to eq(expected_value) + end + end + end RSpec.shared_examples_for 'pipelined command' do |name| let_it_be(:key1) { "redis:{1}:key_a" } @@ -951,11 +731,7 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do describe '#close' do subject { multi_store.close } - context 'when using both stores' do - before do - allow(multi_store).to receive(:use_primary_and_secondary_stores?).and_return(true) - end - + context 'when using both stores are different' do it 'closes both stores' do expect(primary_store).to receive(:close) expect(secondary_store).to receive(:close) @@ -964,36 +740,72 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do end end - context 'when using only one store' do + context 'when using identical stores' do before do - allow(multi_store).to receive(:use_primary_and_secondary_stores?).and_return(false) + allow(multi_store).to receive(:same_redis_store?).and_return(true) end - context 'when using primary_store as default store' do - before do - allow(multi_store).to receive(:use_primary_store_as_default?).and_return(true) - end + it 'closes secondary store' do + expect(secondary_store).to receive(:close) + expect(primary_store).not_to receive(:close) - it 'closes primary store' do - expect(primary_store).to receive(:close) - expect(secondary_store).not_to receive(:close) + subject + end + end + end - subject - end + describe '#blpop' do + let_it_be(:key) { "mylist" } + + subject { multi_store.blpop(key, timeout: 0.1) } + + shared_examples 'calls blpop on default_store' do + it 'calls blpop on default_store' do + expect(multi_store.default_store).to receive(:blpop).with(key, { timeout: 0.1 }) + + subject end + end - context 'when using secondary_store as default store' do + shared_examples 'does not call lpop on non_default_store' do + it 'does not call blpop on non_default_store' do + expect(multi_store.non_default_store).not_to receive(:blpop) + + subject + end + end + + context 'when using both stores' do + before do + allow(multi_store).to receive(:use_primary_and_secondary_stores?).and_return(true) + end + + it_behaves_like 'calls blpop on default_store' + + context "when an element exists in the default_store" do before do - allow(multi_store).to receive(:use_primary_store_as_default?).and_return(false) + multi_store.default_store.lpush(key, 'abc') end - it 'closes secondary store' do - expect(primary_store).not_to receive(:close) - expect(secondary_store).to receive(:close) + it 'calls lpop on non_default_store' do + expect(multi_store.non_default_store).to receive(:blpop).with(key, { timeout: 1 }) subject end end + + context 'when the list is empty in default_store' do + it_behaves_like 'does not call lpop on non_default_store' + end + end + + context 'when using one store' do + before do + allow(multi_store).to receive(:use_primary_and_secondary_stores?).and_return(false) + end + + it_behaves_like 'calls blpop on default_store' + it_behaves_like 'does not call lpop on non_default_store' end end @@ -1279,4 +1091,19 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do end end end + + describe '*_COMMANDS' do + it 'checks if every command is only defined once' do + commands = [ + described_class::REDIS_CLIENT_COMMANDS, + described_class::PUBSUB_SUBSCRIBE_COMMANDS, + described_class::READ_COMMANDS, + described_class::WRITE_COMMANDS, + described_class::PIPELINED_COMMANDS + ].inject([], :concat) + duplicated_commands = commands.group_by { |c| c }.select { |k, v| v.size > 1 }.map(&:first) + + expect(duplicated_commands).to be_empty, "commands #{duplicated_commands} defined more than once" + end + end end diff --git a/spec/lib/gitlab/redis/pubsub_spec.rb b/spec/lib/gitlab/redis/pubsub_spec.rb deleted file mode 100644 index e196d02116e..00000000000 --- a/spec/lib/gitlab/redis/pubsub_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Redis::Pubsub, feature_category: :redis do - include_examples "redis_new_instance_shared_examples", 'pubsub', Gitlab::Redis::SharedState - include_examples "redis_shared_examples" -end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 37db13b76b9..9b8143f7bb8 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Gitlab::ReferenceExtractor do project.add_reporter(@u_foo) project.add_reporter(@u_bar) - subject.analyze(%{ + subject.analyze(%( Inline code: `@foo` Code block: @@ -43,7 +43,7 @@ RSpec.describe Gitlab::ReferenceExtractor do Quote: > @offteam - }) + )) expect(subject.users).to match_array([]) end diff --git a/spec/lib/gitlab/regex_requires_app_spec.rb b/spec/lib/gitlab/regex_requires_app_spec.rb index bea5d25dbc8..7a247e5e8cf 100644 --- a/spec/lib/gitlab/regex_requires_app_spec.rb +++ b/spec/lib/gitlab/regex_requires_app_spec.rb @@ -5,6 +5,18 @@ require 'spec_helper' # Only specs that *cannot* be run with fast_spec_helper only # See regex_spec for tests that do not require the full spec_helper RSpec.describe Gitlab::Regex, feature_category: :tooling do + shared_examples_for 'npm package name regex' do + it { is_expected.to match('@scope/package') } + it { is_expected.to match('unscoped-package') } + it { is_expected.not_to match('@first-scope@second-scope/package') } + it { is_expected.not_to match('scope-without-at-symbol/package') } + it { is_expected.not_to match('@not-a-scoped-package') } + it { is_expected.not_to match('@scope/sub/package') } + it { is_expected.not_to match('@scope/../../package') } + it { is_expected.not_to match('@scope%2e%2e%2fpackage') } + it { is_expected.not_to match('@%2e%2e%2f/package') } + end + describe '.debian_architecture_regex' do subject { described_class.debian_architecture_regex } @@ -37,15 +49,7 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do describe '.npm_package_name_regex' do subject { described_class.npm_package_name_regex } - it { is_expected.to match('@scope/package') } - it { is_expected.to match('unscoped-package') } - it { is_expected.not_to match('@first-scope@second-scope/package') } - it { is_expected.not_to match('scope-without-at-symbol/package') } - it { is_expected.not_to match('@not-a-scoped-package') } - it { is_expected.not_to match('@scope/sub/package') } - it { is_expected.not_to match('@scope/../../package') } - it { is_expected.not_to match('@scope%2e%2e%2fpackage') } - it { is_expected.not_to match('@%2e%2e%2f/package') } + it_behaves_like 'npm package name regex' context 'capturing group' do [ @@ -63,6 +67,24 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do end end + describe '.protection_rules_npm_package_name_pattern_regex' do + subject { described_class.protection_rules_npm_package_name_pattern_regex } + + it_behaves_like 'npm package name regex' + + it { is_expected.to match('@scope/package-*') } + it { is_expected.to match('@my-scope/*my-package-with-wildcard-inbetween') } + it { is_expected.to match('@my-scope/*my-package-with-wildcard-start') } + it { is_expected.to match('@my-scope/my-*package-*with-wildcard-multiple-*') } + it { is_expected.to match('@my-scope/my-package-with_____underscore') } + it { is_expected.to match('@my-scope/my-package-with-wildcard-end*') } + it { is_expected.to match('@my-scope/my-package-with-regex-characters.+') } + + it { is_expected.not_to match('@my-scope/my-package-with-percent-sign-%') } + it { is_expected.not_to match('*@my-scope/my-package-with-wildcard-start') } + it { is_expected.not_to match('@my-scope/my-package-with-backslash-\*') } + end + describe '.debian_distribution_regex' do subject { described_class.debian_distribution_regex } diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb index 71a20cc58fd..c35038b3b75 100644 --- a/spec/lib/gitlab/repository_cache_adapter_spec.rb +++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do include Gitlab::RepositoryCacheAdapter # can't use described_class here def letters - %w(b a c) + %w[b a c] end cache_method_as_redis_set(:letters) @@ -47,11 +47,11 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do expect(fake_repository).to receive(:_uncached_letters).once.and_call_original 2.times do - expect(fake_repository.letters).to eq(%w(a b c)) + expect(fake_repository.letters).to eq(%w[a b c]) end expect(redis_set_cache.exist?(:letters)).to eq(true) - expect(fake_repository.instance_variable_get(:@letters)).to eq(%w(a b c)) + expect(fake_repository.instance_variable_get(:@letters)).to eq(%w[a b c]) end context 'membership checks' do @@ -69,7 +69,7 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do context 'when the cache key exists' do before do - redis_set_cache.write(:letters, %w(b a c)) + redis_set_cache.write(:letters, %w[b a c]) end it 'calls #try_include? on the set cache' do @@ -300,7 +300,7 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do expect(redis_set_cache).to receive(:expire).with(:branch_names) expect(redis_hash_cache).to receive(:delete).with(:branch_names) - repository.expire_method_caches(%i(branch_names)) + repository.expire_method_caches(%i[branch_names]) end it 'does not expire caches for non-existent methods' do @@ -308,7 +308,7 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do expect(Gitlab::AppLogger).to( receive(:error).with("Requested to expire non-existent method 'nonexistent' for Repository")) - repository.expire_method_caches(%i(nonexistent)) + repository.expire_method_caches(%i[nonexistent]) end end end diff --git a/spec/lib/gitlab/repository_hash_cache_spec.rb b/spec/lib/gitlab/repository_hash_cache_spec.rb index e3cc6ed69fb..4b4a2092c98 100644 --- a/spec/lib/gitlab/repository_hash_cache_spec.rb +++ b/spec/lib/gitlab/repository_hash_cache_spec.rb @@ -88,7 +88,7 @@ RSpec.describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_repository_cache describe "#read_members" do subject { cache.read_members(:example, keys) } - let(:keys) { %w(test missing) } + let(:keys) { %w[test missing] } context "all data is cached" do before do @@ -140,7 +140,7 @@ RSpec.describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_repository_cache end end - let(:keys) { %w(test) } + let(:keys) { %w[test] } it "records metrics" do # Here we expect it to receive "test" as a missing key because we @@ -151,7 +151,7 @@ RSpec.describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_repository_cache end context "fully cached" do - let(:keys) { %w(test another) } + let(:keys) { %w[test another] } before do cache.write(:example, test_hash.merge({ "another" => "not_missing" })) @@ -169,7 +169,7 @@ RSpec.describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_repository_cache end context "partially cached" do - let(:keys) { %w(test missing) } + let(:keys) { %w[test missing] } before do cache.write(:example, test_hash) @@ -187,7 +187,7 @@ RSpec.describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_repository_cache end context "uncached" do - let(:keys) { %w(test missing) } + let(:keys) { %w[test missing] } it "returns a hash" do is_expected.to eq({ "test" => "was_missing", "missing" => "was_missing" }) diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb index 23b2a2b9493..42378a16046 100644 --- a/spec/lib/gitlab/repository_set_cache_spec.rb +++ b/spec/lib/gitlab/repository_set_cache_spec.rb @@ -90,7 +90,7 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_repository_cache, end context 'single key' do - let(:keys) { %w(foo) } + let(:keys) { %w[foo] } it { is_expected.to eq(1) } @@ -102,7 +102,7 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_repository_cache, end context 'multiple keys' do - let(:keys) { %w(foo bar) } + let(:keys) { %w[foo bar] } it { is_expected.to eq(2) } diff --git a/spec/lib/gitlab/rugged_instrumentation_spec.rb b/spec/lib/gitlab/rugged_instrumentation_spec.rb deleted file mode 100644 index 393bb957aba..00000000000 --- a/spec/lib/gitlab/rugged_instrumentation_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::RuggedInstrumentation, :request_store do - subject { described_class } - - describe '.query_time' do - it 'increments query times' do - subject.add_query_time(0.4510004) - subject.add_query_time(0.3220004) - - expect(subject.query_time).to eq(0.773001) - expect(subject.query_time_ms).to eq(773.0) - end - end - - describe '.increment_query_count' do - it 'tracks query counts' do - expect(subject.query_count).to eq(0) - - 2.times { subject.increment_query_count } - - expect(subject.query_count).to eq(2) - end - end -end diff --git a/spec/lib/gitlab/runtime_spec.rb b/spec/lib/gitlab/runtime_spec.rb index 01cfa91bb59..05bcdf2fc96 100644 --- a/spec/lib/gitlab/runtime_spec.rb +++ b/spec/lib/gitlab/runtime_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Runtime, feature_category: :application_performance do +RSpec.describe Gitlab::Runtime, feature_category: :cloud_connector do shared_examples "valid runtime" do |runtime, max_threads| it "identifies itself" do expect(subject.identify).to eq(runtime) diff --git a/spec/lib/gitlab/search/abuse_detection_spec.rb b/spec/lib/gitlab/search/abuse_detection_spec.rb index cbf20614ba5..d244234e763 100644 --- a/spec/lib/gitlab/search/abuse_detection_spec.rb +++ b/spec/lib/gitlab/search/abuse_detection_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Gitlab::Search::AbuseDetection, feature_category: :global_search end describe 'abusive character matching' do - refs = %w( + refs = %w[ main тест maiñ @@ -30,7 +30,7 @@ RSpec.describe Gitlab::Search::AbuseDetection, feature_category: :global_search feature/it_works really_important! 测试 - ) + ] refs.each do |ref| it "does match refs permitted by git refname: #{ref}" do diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 00e68f73d2d..a3acb8b92ed 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -99,7 +99,7 @@ RSpec.describe Gitlab::SearchResults, feature_category: :global_search do describe '#aggregations' do where(:scope) do - %w(projects issues merge_requests blobs commits wiki_blobs epics milestones users unknown) + %w[projects issues merge_requests blobs commits wiki_blobs epics milestones users unknown] end with_them do @@ -197,7 +197,7 @@ RSpec.describe Gitlab::SearchResults, feature_category: :global_search do let(:query) { 'foo' } include_examples 'search results filtered by state' - include_examples 'search results filtered by archived', 'search_merge_requests_hide_archived_projects' + include_examples 'search results filtered by archived' end context 'ordering' do @@ -244,7 +244,7 @@ RSpec.describe Gitlab::SearchResults, feature_category: :global_search do include_examples 'search results filtered by state' include_examples 'search results filtered by confidential' - include_examples 'search results filtered by archived', 'search_issues_hide_archived_projects' + include_examples 'search results filtered by archived' end context 'ordering' do diff --git a/spec/lib/gitlab/security/scan_configuration_spec.rb b/spec/lib/gitlab/security/scan_configuration_spec.rb index faa8a206d74..9151db3c5ff 100644 --- a/spec/lib/gitlab/security/scan_configuration_spec.rb +++ b/spec/lib/gitlab/security/scan_configuration_spec.rb @@ -15,7 +15,7 @@ RSpec.describe ::Gitlab::Security::ScanConfiguration do let(:configured) { true } context 'with a core scanner' do - where(type: %i(sast sast_iac secret_detection container_scanning)) + where(type: %i[sast sast_iac secret_detection container_scanning]) with_them do it { is_expected.to be_truthy } @@ -73,7 +73,7 @@ RSpec.describe ::Gitlab::Security::ScanConfiguration do let(:configured) { true } context 'with a core scanner' do - where(type: %i(sast sast_iac secret_detection)) + where(type: %i[sast sast_iac secret_detection]) with_them do it { is_expected.to be_truthy } diff --git a/spec/lib/gitlab/seeders/ci/catalog/resource_seeder_spec.rb b/spec/lib/gitlab/seeders/ci/catalog/resource_seeder_spec.rb new file mode 100644 index 00000000000..4bd4455d1bd --- /dev/null +++ b/spec/lib/gitlab/seeders/ci/catalog/resource_seeder_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Gitlab::Seeders::Ci::Catalog::ResourceSeeder, feature_category: :pipeline_composition do + let_it_be(:admin) { create(:admin) } + let_it_be_with_reload(:group) { create(:group) } + let_it_be(:seed_count) { 2 } + let_it_be(:last_resource_id) { seed_count - 1 } + + let(:group_path) { group.path } + + subject(:seeder) { described_class.new(group_path: group_path, seed_count: seed_count) } + + before_all do + group.add_owner(admin) + end + + describe '#seed' do + subject(:seed) { seeder.seed } + + context 'when the group does not exists' do + let(:group_path) { 'nonexistent_group' } + + it 'skips seeding' do + expect { seed }.not_to change { Project.count } + end + end + + context 'when project name already exists' do + before do + create(:project, namespace: group, name: "ci_seed_resource_0") + end + + it 'skips that project creation and keeps seeding' do + expect { seed }.to change { Project.count }.by(seed_count - 1) + end + end + + context 'when project.saved? fails' do + before do + project = build(:project, name: nil) + + allow_next_instance_of(::Projects::CreateService) do |service| + allow(service).to receive(:execute).and_return(project) + end + end + + it 'does not modify the projects count' do + expect { seed }.not_to change { Project.count } + end + end + + context 'when ci resource creation fails' do + before do + allow_next_instance_of(::Ci::Catalog::Resources::CreateService) do |service| + allow(service).to receive(:execute).and_return(ServiceResponse.error(message: 'error')) + end + end + + it 'does not add a catalog resource' do + expect { seed }.to change { Project.count }.by(seed_count) + + expect(group.projects.all?(&:catalog_resource)).to eq false + end + end + + it 'skips seeding a project if the project name already exists' do + # We call the same command twice, as it means it would try to recreate + # projects that were already created! + expect { seed }.to change { group.projects.count }.by(seed_count) + expect { seed }.to change { group.projects.count }.by(0) + end + + it 'creates as many projects as specific in the argument' do + expect { seed }.to change { + group.projects.count + }.by(seed_count) + + last_ci_resource = Project.last + + expect(last_ci_resource.name).to eq "ci_seed_resource_#{last_resource_id}" + end + + it 'adds a README and a template.yml file to the projects' do + seed + project = group.projects.last + default_branch = project.default_branch_or_main + + expect(project.repository.blob_at(default_branch, "README.md")).not_to be_nil + expect(project.repository.blob_at(default_branch, "template.yml")).not_to be_nil + end + + # This should be run again when fixing: https://gitlab.com/gitlab-org/gitlab/-/issues/429649 + xit 'creates projects with CI catalog resources' do + expect { seed }.to change { Project.count }.by(seed_count) + + expect(group.projects.all?(&:catalog_resource)).to eq true + end + end +end diff --git a/spec/lib/gitlab/shard_health_cache_spec.rb b/spec/lib/gitlab/shard_health_cache_spec.rb index 0c25cc7dab5..8d0eebbb23e 100644 --- a/spec/lib/gitlab/shard_health_cache_spec.rb +++ b/spec/lib/gitlab/shard_health_cache_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::ShardHealthCache, :clean_gitlab_redis_cache do - let(:shards) { %w(foo bar) } + let(:shards) { %w[foo bar] } before do described_class.update(shards) # rubocop:disable Rails/SaveBang @@ -23,7 +23,7 @@ RSpec.describe Gitlab::ShardHealthCache, :clean_gitlab_redis_cache do end it 'replaces the existing set' do - new_set = %w(test me more) + new_set = %w[test me more] described_class.update(new_set) # rubocop:disable Rails/SaveBang expect(described_class.cached_healthy_shards).to match_array(new_set) diff --git a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb index 576b36c1829..1145fd02891 100644 --- a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb +++ b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb @@ -173,7 +173,7 @@ RSpec.describe Gitlab::SidekiqConfig::CliMethods do end it 'returns the queue names of matched workers' do - expect(described_class.query_queues(query, worker_metadatas)).to match(%w(a a:2 c)) + expect(described_class.query_queues(query, worker_metadatas)).to match(%w[a a:2 c]) end end end diff --git a/spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb b/spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb index dfe9358f70b..08ead3282fd 100644 --- a/spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb +++ b/spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb @@ -51,61 +51,61 @@ RSpec.describe Gitlab::SidekiqConfig::WorkerMatcher do context 'with valid input' do where(:query, :expected_metadatas) do # worker_name - 'worker_name=WorkerA' | %w(WorkerA) - 'worker_name=WorkerA2' | %w(WorkerA2) - 'worker_name=WorkerB|worker_name=WorkerD' | %w(WorkerB) - 'worker_name!=WorkerA' | %w(WorkerA2 WorkerB WorkerC) + 'worker_name=WorkerA' | %w[WorkerA] + 'worker_name=WorkerA2' | %w[WorkerA2] + 'worker_name=WorkerB|worker_name=WorkerD' | %w[WorkerB] + 'worker_name!=WorkerA' | %w[WorkerA2 WorkerB WorkerC] # feature_category - 'feature_category=category_a' | %w(WorkerA WorkerA2) - 'feature_category=category_a,category_c' | %w(WorkerA WorkerA2 WorkerC) - 'feature_category=category_a|feature_category=category_c' | %w(WorkerA WorkerA2 WorkerC) - 'feature_category!=category_a' | %w(WorkerB WorkerC) + 'feature_category=category_a' | %w[WorkerA WorkerA2] + 'feature_category=category_a,category_c' | %w[WorkerA WorkerA2 WorkerC] + 'feature_category=category_a|feature_category=category_c' | %w[WorkerA WorkerA2 WorkerC] + 'feature_category!=category_a' | %w[WorkerB WorkerC] # has_external_dependencies - 'has_external_dependencies=true' | %w(WorkerB) - 'has_external_dependencies=false' | %w(WorkerA WorkerA2 WorkerC) - 'has_external_dependencies=true,false' | %w(WorkerA WorkerA2 WorkerB WorkerC) - 'has_external_dependencies=true|has_external_dependencies=false' | %w(WorkerA WorkerA2 WorkerB WorkerC) - 'has_external_dependencies!=true' | %w(WorkerA WorkerA2 WorkerC) + 'has_external_dependencies=true' | %w[WorkerB] + 'has_external_dependencies=false' | %w[WorkerA WorkerA2 WorkerC] + 'has_external_dependencies=true,false' | %w[WorkerA WorkerA2 WorkerB WorkerC] + 'has_external_dependencies=true|has_external_dependencies=false' | %w[WorkerA WorkerA2 WorkerB WorkerC] + 'has_external_dependencies!=true' | %w[WorkerA WorkerA2 WorkerC] # urgency - 'urgency=high' | %w(WorkerA2 WorkerB) - 'urgency=low' | %w(WorkerA) - 'urgency=high,low,throttled' | %w(WorkerA WorkerA2 WorkerB WorkerC) - 'urgency=low|urgency=throttled' | %w(WorkerA WorkerC) - 'urgency!=high' | %w(WorkerA WorkerC) + 'urgency=high' | %w[WorkerA2 WorkerB] + 'urgency=low' | %w[WorkerA] + 'urgency=high,low,throttled' | %w[WorkerA WorkerA2 WorkerB WorkerC] + 'urgency=low|urgency=throttled' | %w[WorkerA WorkerC] + 'urgency!=high' | %w[WorkerA WorkerC] # name - 'name=a' | %w(WorkerA) - 'name=a,b' | %w(WorkerA WorkerB) - 'name=a,a:2|name=b' | %w(WorkerA WorkerA2 WorkerB) - 'name!=a,a:2' | %w(WorkerB WorkerC) + 'name=a' | %w[WorkerA] + 'name=a,b' | %w[WorkerA WorkerB] + 'name=a,a:2|name=b' | %w[WorkerA WorkerA2 WorkerB] + 'name!=a,a:2' | %w[WorkerB WorkerC] # resource_boundary - 'resource_boundary=memory' | %w(WorkerB WorkerC) - 'resource_boundary=memory,cpu' | %w(WorkerA WorkerB WorkerC) - 'resource_boundary=memory|resource_boundary=cpu' | %w(WorkerA WorkerB WorkerC) - 'resource_boundary!=memory,cpu' | %w(WorkerA2) + 'resource_boundary=memory' | %w[WorkerB WorkerC] + 'resource_boundary=memory,cpu' | %w[WorkerA WorkerB WorkerC] + 'resource_boundary=memory|resource_boundary=cpu' | %w[WorkerA WorkerB WorkerC] + 'resource_boundary!=memory,cpu' | %w[WorkerA2] # tags - 'tags=no_disk_io' | %w(WorkerA WorkerB) - 'tags=no_disk_io,git_access' | %w(WorkerA WorkerA2 WorkerB) - 'tags=no_disk_io|tags=git_access' | %w(WorkerA WorkerA2 WorkerB) - 'tags=no_disk_io&tags=git_access' | %w(WorkerA) - 'tags!=no_disk_io' | %w(WorkerA2 WorkerC) - 'tags!=no_disk_io,git_access' | %w(WorkerC) + 'tags=no_disk_io' | %w[WorkerA WorkerB] + 'tags=no_disk_io,git_access' | %w[WorkerA WorkerA2 WorkerB] + 'tags=no_disk_io|tags=git_access' | %w[WorkerA WorkerA2 WorkerB] + 'tags=no_disk_io&tags=git_access' | %w[WorkerA] + 'tags!=no_disk_io' | %w[WorkerA2 WorkerC] + 'tags!=no_disk_io,git_access' | %w[WorkerC] 'tags=unknown_tag' | [] - 'tags!=no_disk_io' | %w(WorkerA2 WorkerC) - 'tags!=no_disk_io,git_access' | %w(WorkerC) - 'tags!=unknown_tag' | %w(WorkerA WorkerA2 WorkerB WorkerC) + 'tags!=no_disk_io' | %w[WorkerA2 WorkerC] + 'tags!=no_disk_io,git_access' | %w[WorkerC] + 'tags!=unknown_tag' | %w[WorkerA WorkerA2 WorkerB WorkerC] # combinations - 'feature_category=category_a&urgency=high' | %w(WorkerA2) - 'feature_category=category_a&urgency=high|feature_category=category_c' | %w(WorkerA2 WorkerC) + 'feature_category=category_a&urgency=high' | %w[WorkerA2] + 'feature_category=category_a&urgency=high|feature_category=category_c' | %w[WorkerA2 WorkerC] # Match all - '*' | %w(WorkerA WorkerA2 WorkerB WorkerC) + '*' | %w[WorkerA WorkerA2 WorkerB WorkerC] end with_them do diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb index 4550ccc2fff..2e07fa100e8 100644 --- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb +++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb @@ -181,7 +181,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do it 'logs without created_at and enqueued_at fields' do travel_to(timestamp) do - excluded_fields = %w(created_at enqueued_at args scheduling_latency_s) + excluded_fields = %w[created_at enqueued_at args scheduling_latency_s] expect(logger).to receive(:info).with(start_payload.except(*excluded_fields)).ordered expect(logger).to receive(:info).with(end_payload.except(*excluded_fields)).ordered @@ -238,13 +238,11 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do end end - context 'with Gitaly, Rugged, and Redis calls' do + context 'with Gitaly, and Redis calls' do let(:timing_data) do { gitaly_calls: 10, gitaly_duration_s: 10000, - rugged_calls: 1, - rugged_duration_s: 5000, redis_calls: 3, redis_duration_s: 1234 } @@ -261,7 +259,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do end end - it 'logs with Gitaly and Rugged timing data', :aggregate_failures do + it 'logs with Gitaly timing data', :aggregate_failures do travel_to(timestamp) do expect(logger).to receive(:info).with(start_payload).ordered expect(logger).to receive(:info).with(expected_end_payload).ordered diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb index 7138ad04f69..dbfab116479 100644 --- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb @@ -106,9 +106,21 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, end context 'when TTL option is not set' do - let(:expected_ttl) { described_class::DEFAULT_DUPLICATE_KEY_TTL } + context 'when reduce_duplicate_job_key_ttl is enabled' do + let(:expected_ttl) { described_class::SHORT_DUPLICATE_KEY_TTL } - it_behaves_like 'sets Redis keys with correct TTL' + it_behaves_like 'sets Redis keys with correct TTL' + end + + context 'when reduce_duplicate_job_key_ttl is disabled' do + before do + stub_feature_flags(reduce_duplicate_job_key_ttl: false) + end + + let(:expected_ttl) { described_class::DEFAULT_DUPLICATE_KEY_TTL } + + it_behaves_like 'sets Redis keys with correct TTL' + end end context 'when TTL option is set' do diff --git a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb index a27e723e392..9cf9901007c 100644 --- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' # rubocop: disable RSpec/MultipleMemoizedHelpers -RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do +RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics, feature_category: :shared do shared_examples "a metrics middleware" do context "with mocked prometheus" do include_context 'server metrics with mocked prometheus' @@ -452,11 +452,6 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do end context 'when emit_sidekiq_histogram_metrics FF is disabled' do - include_context 'server metrics with mocked prometheus' - include_context 'server metrics call' do - let(:stub_subject) { false } - end - subject(:middleware) { described_class.new } let(:job) { {} } @@ -484,16 +479,38 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do stub_feature_flags(emit_sidekiq_histogram_metrics: false) end + # include_context below must run after stubbing FF above because + # the middleware initialization depends on the FF and it's being initialized + # in the 'server metrics call' shared_context + include_context 'server metrics with mocked prometheus' + include_context 'server metrics call' + it 'does not emit histogram metrics' do expect(completion_seconds_metric).not_to receive(:observe) expect(queue_duration_seconds).not_to receive(:observe) expect(failed_total_metric).not_to receive(:increment) + expect(user_execution_seconds_metric).not_to receive(:observe) + expect(db_seconds_metric).not_to receive(:observe) + expect(gitaly_seconds_metric).not_to receive(:observe) + expect(redis_seconds_metric).not_to receive(:observe) + expect(elasticsearch_seconds_metric).not_to receive(:observe) middleware.call(worker, job, queue) { nil } end - it 'emits sidekiq_jobs_completion_seconds_sum metric' do + it 'emits sidekiq_jobs_completion_seconds sum and count metric' do expect(completion_seconds_sum_metric).to receive(:increment).with(labels, monotonic_time_duration) + expect(completion_count_metric).to receive(:increment).with(labels, 1) + + middleware.call(worker, job, queue) { nil } + end + + it 'emits resource usage sum metrics' do + expect(cpu_seconds_sum_metric).to receive(:increment).with(labels, thread_cputime_duration) + expect(db_seconds_sum_metric).to receive(:increment).with(labels, db_duration) + expect(gitaly_seconds_sum_metric).to receive(:increment).with(labels, gitaly_duration) + expect(redis_seconds_sum_metric).to receive(:increment).with(labels, redis_duration) + expect(elasticsearch_seconds_sum_metric).to receive(:increment).with(labels, elasticsearch_duration) middleware.call(worker, job, queue) { nil } end diff --git a/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb b/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb index 2fa0e44d44f..6df77c350e2 100644 --- a/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb @@ -185,6 +185,21 @@ RSpec.describe Gitlab::SidekiqMiddleware::SkipJobs, feature_category: :scalabili TestWorker.perform_async(*job['args']) end end + + context 'when a block is provided' do + before do + TestWorker.defer_on_database_health_signal(*health_signal_attrs.values) do + [:gitlab_ci, [:ci_pipelines]] + end + end + + it 'uses the lazy evaluated schema and tables returned by the block' do + expect(Gitlab::Database::HealthStatus::Context).to receive(:new) + .with(anything, anything, [:ci_pipelines], :gitlab_ci).and_call_original + + expect { |b| subject.call(TestWorker.new, job, queue, &b) }.to yield_control + end + end end end end diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb index 4fbc64a45d6..0f8d84d13ec 100644 --- a/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb @@ -73,7 +73,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do 'job2' => build_stubbed(:user, username: 'user-2') } TestWithContextWorker.bulk_perform_async_with_contexts( - %w(job1 job2), + %w[job1 job2], arguments_proc: -> (name) { [name, 1, 2, 3] }, context_proc: -> (name) { { user: user_per_job[name] } } ) @@ -88,7 +88,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do context 'when the feature category is set in the context_proc' do it 'takes the feature category from the worker, not the caller' do TestWithContextWorker.bulk_perform_async_with_contexts( - %w(job1 job2), + %w[job1 job2], arguments_proc: -> (name) { [name, 1, 2, 3] }, context_proc: -> (_) { { feature_category: 'code_review' } } ) @@ -102,7 +102,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do it 'takes the feature category from the caller if the worker is not owned' do TestNotOwnedWithContextWorker.bulk_perform_async_with_contexts( - %w(job1 job2), + %w[job1 job2], arguments_proc: -> (name) { [name, 1, 2, 3] }, context_proc: -> (_) { { feature_category: 'code_review' } } ) @@ -125,7 +125,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do it 'takes the feature category from the worker, not the caller' do Gitlab::ApplicationContext.with_context(feature_category: 'system_access') do TestWithContextWorker.bulk_perform_async_with_contexts( - %w(job1 job2), + %w[job1 job2], arguments_proc: -> (name) { [name, 1, 2, 3] }, context_proc: -> (_) { {} } ) @@ -141,7 +141,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do it 'takes the feature category from the caller if the worker is not owned' do Gitlab::ApplicationContext.with_context(feature_category: 'system_access') do TestNotOwnedWithContextWorker.bulk_perform_async_with_contexts( - %w(job1 job2), + %w[job1 job2], arguments_proc: -> (name) { [name, 1, 2, 3] }, context_proc: -> (_) { {} } ) diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb index 7f1504a8df9..a555e6a828a 100644 --- a/spec/lib/gitlab/sidekiq_status_spec.rb +++ b/spec/lib/gitlab/sidekiq_status_spec.rb @@ -55,13 +55,13 @@ RSpec.describe Gitlab::SidekiqStatus, :clean_gitlab_redis_queues, :clean_gitlab_ describe '.all_completed?' do it 'returns true if all jobs have been completed' do - expect(described_class.all_completed?(%w(123))).to eq(true) + expect(described_class.all_completed?(%w[123])).to eq(true) end it 'returns false if a job has not yet been completed' do described_class.set('123') - expect(described_class.all_completed?(%w(123 456))).to eq(false) + expect(described_class.all_completed?(%w[123 456])).to eq(false) end end @@ -79,40 +79,40 @@ RSpec.describe Gitlab::SidekiqStatus, :clean_gitlab_redis_queues, :clean_gitlab_ describe '.num_running' do it 'returns 0 if all jobs have been completed' do - expect(described_class.num_running(%w(123))).to eq(0) + expect(described_class.num_running(%w[123])).to eq(0) end it 'returns 2 if two jobs are still running' do described_class.set('123') described_class.set('456') - expect(described_class.num_running(%w(123 456 789))).to eq(2) + expect(described_class.num_running(%w[123 456 789])).to eq(2) end end describe '.num_completed' do it 'returns 1 if all jobs have been completed' do - expect(described_class.num_completed(%w(123))).to eq(1) + expect(described_class.num_completed(%w[123])).to eq(1) end it 'returns 1 if a job has not yet been completed' do described_class.set('123') described_class.set('456') - expect(described_class.num_completed(%w(123 456 789))).to eq(1) + expect(described_class.num_completed(%w[123 456 789])).to eq(1) end end describe '.completed_jids' do it 'returns the completed job' do - expect(described_class.completed_jids(%w(123))).to eq(['123']) + expect(described_class.completed_jids(%w[123])).to eq(['123']) end it 'returns only the jobs completed' do described_class.set('123') described_class.set('456') - expect(described_class.completed_jids(%w(123 456 789))).to eq(['789']) + expect(described_class.completed_jids(%w[123 456 789])).to eq(['789']) end end @@ -122,7 +122,7 @@ RSpec.describe Gitlab::SidekiqStatus, :clean_gitlab_redis_queues, :clean_gitlab_ described_class.set('456') described_class.unset('123') - expect(described_class.job_status(%w(123 456 789))).to eq([false, true, false]) + expect(described_class.job_status(%w[123 456 789])).to eq([false, true, false]) end it 'handles an empty array' do @@ -140,7 +140,7 @@ RSpec.describe Gitlab::SidekiqStatus, :clean_gitlab_redis_queues, :clean_gitlab_ expect(Gitlab::Redis::SidekiqStatus).to receive(:with).and_call_original expect(Sidekiq).not_to receive(:redis) - described_class.job_status(%w(123 456 789)) + described_class.job_status(%w[123 456 789]) end it_behaves_like 'tracking status in redis' @@ -160,7 +160,7 @@ RSpec.describe Gitlab::SidekiqStatus, :clean_gitlab_redis_queues, :clean_gitlab_ expect(Sidekiq).to receive(:redis).and_call_original expect(Gitlab::Redis::SidekiqStatus).not_to receive(:with) - described_class.job_status(%w(123 456 789)) + described_class.job_status(%w[123 456 789]) end it_behaves_like 'tracking status in redis' diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb index d4b0b1ea53b..df9f04eb7a0 100644 --- a/spec/lib/gitlab/ssh_public_key_spec.rb +++ b/spec/lib/gitlab/ssh_public_key_spec.rb @@ -87,28 +87,28 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true, fips_mode: false do describe '.supported_algorithms' do it 'returns all supported algorithms' do expect(described_class.supported_algorithms).to eq( - %w( + %w[ ssh-rsa ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 sk-ecdsa-sha2-nistp256@openssh.com sk-ssh-ed25519@openssh.com - ) + ] ) end context 'FIPS mode', :fips_mode do it 'returns all supported algorithms' do expect(described_class.supported_algorithms).to eq( - %w( + %w[ ssh-rsa ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 sk-ecdsa-sha2-nistp256@openssh.com sk-ssh-ed25519@openssh.com - ) + ] ) end end @@ -117,12 +117,12 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true, fips_mode: false do describe '.supported_algorithms_for_name' do where(:name, :algorithms) do [ - [:rsa, %w(ssh-rsa)], - [:dsa, %w(ssh-dss)], - [:ecdsa, %w(ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521)], - [:ed25519, %w(ssh-ed25519)], - [:ecdsa_sk, %w(sk-ecdsa-sha2-nistp256@openssh.com)], - [:ed25519_sk, %w(sk-ssh-ed25519@openssh.com)] + [:rsa, %w[ssh-rsa]], + [:dsa, %w[ssh-dss]], + [:ecdsa, %w[ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521]], + [:ed25519, %w[ssh-ed25519]], + [:ecdsa_sk, %w[sk-ecdsa-sha2-nistp256@openssh.com]], + [:ed25519_sk, %w[sk-ssh-ed25519@openssh.com]] ] end @@ -136,12 +136,12 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true, fips_mode: false do context 'FIPS mode', :fips_mode do where(:name, :algorithms) do [ - [:rsa, %w(ssh-rsa)], - [:dsa, %w(ssh-dss)], - [:ecdsa, %w(ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521)], - [:ed25519, %w(ssh-ed25519)], - [:ecdsa_sk, %w(sk-ecdsa-sha2-nistp256@openssh.com)], - [:ed25519_sk, %w(sk-ssh-ed25519@openssh.com)] + [:rsa, %w[ssh-rsa]], + [:dsa, %w[ssh-dss]], + [:ecdsa, %w[ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521]], + [:ed25519, %w[ssh-ed25519]], + [:ecdsa_sk, %w[sk-ecdsa-sha2-nistp256@openssh.com]], + [:ed25519_sk, %w[sk-ssh-ed25519@openssh.com]] ] end @@ -194,7 +194,7 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true, fips_mode: false do context 'with a valid SSH key' do where(:factory) do - %i(rsa_key_2048 + %i[rsa_key_2048 rsa_key_4096 rsa_key_5120 rsa_key_8192 @@ -202,7 +202,7 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true, fips_mode: false do ecdsa_key_256 ed25519_key_256 ecdsa_sk_key_256 - ed25519_sk_key_256) + ed25519_sk_key_256] end with_them do diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb index 2ababd6a938..fcee64bc01c 100644 --- a/spec/lib/gitlab/string_range_marker_spec.rb +++ b/spec/lib/gitlab/string_range_marker_spec.rb @@ -14,10 +14,10 @@ RSpec.describe Gitlab::StringRangeMarker do end context "when the rich text is html safe" do - let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def"><def></span>}.html_safe } + let(:rich) { %(<span class="abc">abc</span><span class="space"> </span><span class="def"><def></span>).html_safe } it 'marks the inline diffs' do - expect(mark_diff(rich)).to eq(%{<span class="abc">abLEFTcRIGHT</span><span class="space">LEFT RIGHT</span><span class="def">LEFT<dRIGHTef></span>}) + expect(mark_diff(rich)).to eq(%(<span class="abc">abLEFTcRIGHT</span><span class="space">LEFT RIGHT</span><span class="def">LEFT<dRIGHTef></span>)) expect(mark_diff(rich)).to be_html_safe end end @@ -25,7 +25,7 @@ RSpec.describe Gitlab::StringRangeMarker do context "when the rich text is not html safe" do context 'when rich text equals raw text' do it 'marks the inline diffs' do - expect(mark_diff).to eq(%{abLEFTc <dRIGHTef>}) + expect(mark_diff).to eq(%(abLEFTc <dRIGHTef>)) expect(mark_diff).not_to be_html_safe end end @@ -34,7 +34,7 @@ RSpec.describe Gitlab::StringRangeMarker do let(:rich) { "abc <def> differs" } it 'marks the inline diffs' do - expect(mark_diff(rich)).to eq(%{abLEFTc <dRIGHTef> differs}) + expect(mark_diff(rich)).to eq(%(abLEFTc <dRIGHTef> differs)) expect(mark_diff(rich)).to be_html_safe end end diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb index 393bfea7c6b..87df8b9baab 100644 --- a/spec/lib/gitlab/string_regex_marker_spec.rb +++ b/spec/lib/gitlab/string_regex_marker_spec.rb @@ -5,34 +5,34 @@ require 'fast_spec_helper' RSpec.describe Gitlab::StringRegexMarker do describe '#mark' do context 'with a single occurrence' do - let(:raw) { %{"name": "AFNetworking"} } - let(:rich) { %{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>}.html_safe } + let(:raw) { %("name": "AFNetworking") } + let(:rich) { %(<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>).html_safe } subject do described_class.new(raw, rich).mark(/"[^"]+":\s*"(?<name>[^"]+)"/, group: :name) do |text, left:, right:, mode:| - %{<a href="#">#{text}</a>}.html_safe + %(<a href="#">#{text}</a>).html_safe end end it 'marks the match' do - expect(subject).to eq(%{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"<a href="#">AFNetworking</a>"</span>}) + expect(subject).to eq(%(<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"<a href="#">AFNetworking</a>"</span>)) expect(subject).to be_html_safe end end context 'with multiple occurrences' do - let(:raw) { %{a <b> <c> d} } - let(:rich) { %{a <b> <c> d}.html_safe } + let(:raw) { %(a <b> <c> d) } + let(:rich) { %(a <b> <c> d).html_safe } let(:regexp) { /<[a-z]>/ } subject do described_class.new(raw, rich).mark(regexp) do |text, left:, right:, mode:| - %{<strong>#{text}</strong>}.html_safe + %(<strong>#{text}</strong>).html_safe end end it 'marks the matches' do - expect(subject).to eq(%{a <strong><b></strong> <strong><c></strong> d}) + expect(subject).to eq(%(a <strong><b></strong> <strong><c></strong> d)) expect(subject).to be_html_safe end @@ -40,7 +40,7 @@ RSpec.describe Gitlab::StringRegexMarker do let(:regexp) { Gitlab::UntrustedRegexp.new('<[a-z]>') } it 'marks the matches' do - expect(subject).to eq(%{a <strong><b></strong> <strong><c></strong> d}) + expect(subject).to eq(%(a <strong><b></strong> <strong><c></strong> d)) expect(subject).to be_html_safe end end diff --git a/spec/lib/gitlab/suggestions/suggestion_set_spec.rb b/spec/lib/gitlab/suggestions/suggestion_set_spec.rb index 469646986e1..298ade2e33f 100644 --- a/spec/lib/gitlab/suggestions/suggestion_set_spec.rb +++ b/spec/lib/gitlab/suggestions/suggestion_set_spec.rb @@ -114,7 +114,7 @@ RSpec.describe Gitlab::Suggestions::SuggestionSet do it 'returns an array of unique file paths associated with the suggestions' do suggestion_set = described_class.new([suggestion, suggestion2, suggestion3]) - expected_paths = %w(files/ruby/popen.rb files/ruby/regex.rb) + expected_paths = %w[files/ruby/popen.rb files/ruby/regex.rb] actual_paths = suggestion_set.file_paths diff --git a/spec/lib/gitlab/task_helpers_spec.rb b/spec/lib/gitlab/task_helpers_spec.rb index 0c43dd15e8c..448406dfb99 100644 --- a/spec/lib/gitlab/task_helpers_spec.rb +++ b/spec/lib/gitlab/task_helpers_spec.rb @@ -84,17 +84,17 @@ RSpec.describe Gitlab::TaskHelpers do describe '#run_command' do it 'runs command and return the output' do - expect(subject.run_command(%w(echo it works!))).to eq("it works!\n") + expect(subject.run_command(%w[echo it works!])).to eq("it works!\n") end it 'returns empty string when command doesnt exist' do - expect(subject.run_command(%w(nonexistentcommand with arguments))).to eq('') + expect(subject.run_command(%w[nonexistentcommand with arguments])).to eq('') end end describe '#run_command!' do it 'runs command and return the output' do - expect(subject.run_command!(%w(echo it works!))).to eq("it works!\n") + expect(subject.run_command!(%w[echo it works!])).to eq("it works!\n") end it 'returns and exception when command exit with non zero code' do diff --git a/spec/lib/gitlab/tracking/event_definition_spec.rb b/spec/lib/gitlab/tracking/event_definition_spec.rb index b27aaa35695..ab0660147e4 100644 --- a/spec/lib/gitlab/tracking/event_definition_spec.rb +++ b/spec/lib/gitlab/tracking/event_definition_spec.rb @@ -15,8 +15,8 @@ RSpec.describe Gitlab::Tracking::EventDefinition do product_stage: 'growth', product_section: 'dev', product_group: 'group::product analytics', - distribution: %w(ee ce), - tier: %w(free premium ultimate) + distribution: %w[ee ce], + tier: %w[free premium ultimate] } end @@ -49,8 +49,8 @@ RSpec.describe Gitlab::Tracking::EventDefinition do :product_stage | 1 :product_section | nil :product_group | nil - :distributions | %[be eb] - :tiers | %[pro] + :distributions | %(be eb) + :tiers | %(pro) end with_them do diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index 68eb38a1335..81b70f85c3a 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -30,6 +30,7 @@ RSpec.describe Gitlab::UrlBuilder do :project_snippet | ->(snippet) { "/#{snippet.project.full_path}/-/snippets/#{snippet.id}" } :project_wiki | ->(wiki) { "/#{wiki.container.full_path}/-/wikis/home" } :release | ->(release) { "/#{release.project.full_path}/-/releases/#{release.tag}" } + :organization | ->(organization) { "/-/organizations/#{organization.path}" } :ci_build | ->(build) { "/#{build.project.full_path}/-/jobs/#{build.id}" } :design | ->(design) { "/#{design.project.full_path}/-/design_management/designs/#{design.id}/raw_image" } diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 2c2ef8f13fb..6a1521d9d72 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -8,10 +8,10 @@ RSpec.describe Gitlab::UrlSanitizer do describe '.sanitize' do def sanitize_url(url) # We want to try with multi-line content because is how error messages are formatted - described_class.sanitize(%{ + described_class.sanitize(%( remote: Not Found fatal: repository `#{url}` not found - }) + )) end where(:input, :output) do @@ -50,7 +50,7 @@ RSpec.describe Gitlab::UrlSanitizer do false | '123://invalid:url' false | 'valid@project:url.git' false | 'valid:pass@project:url.git' - false | %w(test array) + false | %w[test array] true | 'ssh://example.com' true | 'ssh://:@example.com' true | 'ssh://foo@example.com' @@ -74,7 +74,7 @@ RSpec.describe Gitlab::UrlSanitizer do false | '123://invalid:url' false | 'valid@project:url.git' false | 'valid:pass@project:url.git' - false | %w(test array) + false | %w[test array] false | 'ssh://example.com' false | 'ssh://:@example.com' false | 'ssh://foo@example.com' diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb index 51d3090c825..08adc031631 100644 --- a/spec/lib/gitlab/usage/metric_definition_spec.rb +++ b/spec/lib/gitlab/usage/metric_definition_spec.rb @@ -16,8 +16,8 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping product_group: 'product_analytics', time_frame: 'none', data_source: 'database', - distribution: %w(ee ce), - tier: %w(free starter premium ultimate bronze silver gold), + distribution: %w[ee ce], + tier: %w[free starter premium ultimate bronze silver gold], data_category: 'standard', removed_by_url: 'http://gdk.test' } @@ -64,7 +64,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping it 'includes metrics that are not removed' do expect(described_class.not_removed.count).to eq(3) - expect(described_class.not_removed.keys).to match_array(%w(metric1 metric2 metric3)) + expect(described_class.not_removed.keys).to match_array(%w[metric1 metric2 metric3]) end end @@ -162,7 +162,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping :data_source | nil :distribution | nil :distribution | 'test' - :tier | %w(test ee) + :tier | %w[test ee] :repair_issue_url | nil :removed_by_url | 1 @@ -194,6 +194,156 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping described_class.new(path, attributes).validate! end end + + context 'when metric has removed status' do + before do + attributes[:status] = 'removed' + end + + it 'raise dev exception when removed_by_url is not provided' do + attributes.delete(:removed_by_url) + + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError)) + + described_class.new(path, attributes).validate! + end + + it 'raises dev exception when milestone_removed is not provided' do + attributes.delete(:milestone_removed) + + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError)) + + described_class.new(path, attributes).validate! + end + end + + context 'internal metric' do + before do + attributes[:data_source] = 'internal_events' + end + + where(:instrumentation_class, :options, :events, :is_valid) do + 'AnotherClass' | { events: ['a'] } | [{ name: 'a', unique: 'user.id' }] | false + nil | { events: ['a'] } | [{ name: 'a', unique: 'user.id' }] | false + 'RedisHLLMetric' | { events: ['a'] } | [{ name: 'a', unique: 'user.id' }] | true + 'RedisHLLMetric' | { events: ['a'] } | nil | false + 'RedisHLLMetric' | nil | [{ name: 'a', unique: 'user.id' }] | false + 'RedisHLLMetric' | { events: ['a'] } | [{ name: 'a', unique: 'a' }] | false + 'RedisHLLMetric' | { events: 'a' } | [{ name: 'a', unique: 'user.id' }] | false + 'RedisHLLMetric' | { events: [2] } | [{ name: 'a', unique: 'user.id' }] | false + 'RedisHLLMetric' | { events: ['a'], a: 'b' } | [{ name: 'a', unique: 'user.id' }] | false + 'RedisHLLMetric' | { events: ['a'] } | [{ name: 'a', unique: 'user.id', b: 'c' }] | false + 'RedisHLLMetric' | { events: ['a'] } | [{ name: 'a' }] | false + 'RedisHLLMetric' | { events: ['a'] } | [{ unique: 'user.id' }] | false + 'TotalCountMetric' | { events: ['a'] } | [{ name: 'a' }] | true + 'TotalCountMetric' | { events: ['a'] } | [{ name: 'a', unique: 'user.id' }] | false + 'TotalCountMetric' | { events: ['a'] } | nil | false + 'TotalCountMetric' | nil | [{ name: 'a' }] | false + 'TotalCountMetric' | { events: [2] } | [{ name: 'a' }] | false + 'TotalCountMetric' | { events: ['a'] } | [{}] | false + 'TotalCountMetric' | 'a' | [{ name: 'a' }] | false + 'TotalCountMetric' | { events: ['a'], a: 'b' } | [{ name: 'a' }] | false + end + + with_them do + it 'raises dev exception when invalid' do + attributes[:instrumentation_class] = instrumentation_class if instrumentation_class + attributes[:options] = options if options + attributes[:events] = events if events + + if is_valid + expect(Gitlab::ErrorTracking).not_to receive(:track_and_raise_for_dev_exception) + else + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError)) + end + + described_class.new(path, attributes).validate! + end + end + end + + context 'Redis metric' do + before do + attributes[:data_source] = 'redis' + end + + where(:instrumentation_class, :options, :is_valid) do + 'AnotherClass' | { event: 'a', widget: 'b' } | false + 'MergeRequestWidgetExtensionMetric' | { event: 'a', widget: 'b' } | true + 'MergeRequestWidgetExtensionMetric' | { event: 'a', widget: 2 } | false + 'MergeRequestWidgetExtensionMetric' | { event: 'a', widget: 'b', c: 'd' } | false + 'MergeRequestWidgetExtensionMetric' | { event: 'a' } | false + 'MergeRequestWidgetExtensionMetric' | { widget: 'b' } | false + 'RedisMetric' | { event: 'a', prefix: 'b', include_usage_prefix: true } | true + 'RedisMetric' | { event: 'a', prefix: nil, include_usage_prefix: true } | true + 'RedisMetric' | { event: 'a', prefix: 'b', include_usage_prefix: 2 } | false + 'RedisMetric' | { event: 'a', prefix: 'b', include_usage_prefix: true, c: 'd' } | false + 'RedisMetric' | { prefix: 'b', include_usage_prefix: true } | false + 'RedisMetric' | { event: 'a', include_usage_prefix: true } | false + 'RedisMetric' | { event: 'a', prefix: 'b' } | true + end + + with_them do + it 'validates properly' do + attributes[:instrumentation_class] = instrumentation_class + attributes[:options] = options + + if is_valid + expect(Gitlab::ErrorTracking).not_to receive(:track_and_raise_for_dev_exception) + else + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError)) + end + + described_class.new(path, attributes).validate! + end + end + end + + context 'RedisHLL metric' do + before do + attributes[:data_source] = 'redis_hll' + end + + where(:instrumentation_class, :options, :is_valid) do + 'AnotherClass' | { events: ['a'] } | false + 'RedisHLLMetric' | { events: ['a'] } | true + 'RedisHLLMetric' | nil | false + 'RedisHLLMetric' | {} | false + 'RedisHLLMetric' | { events: ['a'], b: 'c' } | false + 'RedisHLLMetric' | { events: [2] } | false + 'RedisHLLMetric' | { events: 'a' } | false + 'RedisHLLMetric' | { event: ['a'] } | false + 'AggregatedMetric' | { aggregate: { operator: 'OR', attribute: 'user_id' }, events: ['a'] } | true + 'AggregatedMetric' | { aggregate: { operator: 'AND', attribute: 'project_id' }, events: %w[b c] } | true + 'AggregatedMetric' | nil | false + 'AggregatedMetric' | {} | false + 'AggregatedMetric' | { aggregate: { operator: 'OR', attribute: 'user_id' }, events: ['a'], event: 'a' } | false + 'AggregatedMetric' | { aggregate: { operator: 'OR', attribute: 'user_id' } } | false + 'AggregatedMetric' | { events: ['a'] } | false + 'AggregatedMetric' | { aggregate: { operator: 'OR', attribute: 'user_id' }, events: 'a' } | false + 'AggregatedMetric' | { aggregate: 'a', events: ['a'] } | false + 'AggregatedMetric' | { aggregate: { operator: 'OR' }, events: ['a'] } | false + 'AggregatedMetric' | { aggregate: { attribute: 'user_id' }, events: ['a'] } | false + 'AggregatedMetric' | { aggregate: { operator: 'OR', attribute: 'user_id', a: 'b' }, events: ['a'] } | false + 'AggregatedMetric' | { aggregate: { operator: '???', attribute: 'user_id' }, events: ['a'] } | false + 'AggregatedMetric' | { aggregate: { operator: 'OR', attribute: ['user_id'] }, events: ['a'] } | false + end + + with_them do + it 'validates properly' do + attributes[:instrumentation_class] = instrumentation_class + attributes[:options] = options + + if is_valid + expect(Gitlab::ErrorTracking).not_to receive(:track_and_raise_for_dev_exception) + else + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError)) + end + + described_class.new(path, attributes).validate! + end + end + end end end @@ -213,10 +363,10 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping end context 'when metric is using new format' do - let(:attributes) { { events: [{ name: 'my_event', unique: 'user_id' }] } } + let(:attributes) { { events: [{ name: 'my_event', unique: 'user.id' }] } } it 'returns a correct hash' do - expect(definition.events).to eq({ 'my_event' => :user_id }) + expect(definition.events).to eq({ 'my_event' => :'user.id' }) end end @@ -309,8 +459,8 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping product_group: 'product_analytics', time_frame: 'none', data_source: 'database', - distribution: %w(ee ce), - tier: %w(free starter premium ultimate bronze silver gold), + distribution: %w[ee ce], + tier: %w[free starter premium ultimate bronze silver gold], data_category: 'optional' } end diff --git a/spec/lib/gitlab/usage/metric_spec.rb b/spec/lib/gitlab/usage/metric_spec.rb index a4135b143dd..42d2f394ce3 100644 --- a/spec/lib/gitlab/usage/metric_spec.rb +++ b/spec/lib/gitlab/usage/metric_spec.rb @@ -18,8 +18,8 @@ RSpec.describe Gitlab::Usage::Metric do time_frame: "all", data_source: "database", instrumentation_class: "CountIssuesMetric", - distribution: %w(ce ee), - tier: %w(free premium ultimate) + distribution: %w[ce ee], + tier: %w[free premium ultimate] } end diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/aggregated_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/aggregated_metric_spec.rb index 3e7b13e21c1..f6b9da68184 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/aggregated_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/aggregated_metric_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Gitlab::Usage::Metrics::Instrumentations::AggregatedMetric, :clean_gitlab_redis_shared_state do +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::AggregatedMetric, :clean_gitlab_redis_shared_state, + feature_category: :service_ping do using RSpec::Parameterized::TableSyntax before do # weekly AND 1 weekly OR 2 diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_dvcs_integration_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_dvcs_integration_metric_spec.rb deleted file mode 100644 index a2d86fc5044..00000000000 --- a/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_jira_dvcs_integration_metric_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountProjectsWithJiraDvcsIntegrationMetric, - feature_category: :integrations do - describe 'metric value and query' do - let_it_be_with_reload(:project_1) { create(:project) } - let_it_be_with_reload(:project_2) { create(:project) } - let_it_be_with_reload(:project_3) { create(:project) } - - before do - project_1.feature_usage.log_jira_dvcs_integration_usage(cloud: false) - project_2.feature_usage.log_jira_dvcs_integration_usage(cloud: false) - project_3.feature_usage.log_jira_dvcs_integration_usage(cloud: true) - end - - context 'when counting cloud integrations' do - let(:expected_value) { 1 } - let(:expected_query) do - 'SELECT COUNT("project_feature_usages"."project_id") FROM "project_feature_usages" ' \ - 'WHERE "project_feature_usages"."jira_dvcs_cloud_last_sync_at" IS NOT NULL' - end - - it_behaves_like 'a correct instrumented metric value and query', { time_frame: 'all', options: { cloud: true } } - end - - context 'when counting non-cloud integrations' do - let(:expected_value) { 2 } - let(:expected_query) do - 'SELECT COUNT("project_feature_usages"."project_id") FROM "project_feature_usages" ' \ - 'WHERE "project_feature_usages"."jira_dvcs_server_last_sync_at" IS NOT NULL' - end - - it_behaves_like 'a correct instrumented metric value and query', { time_frame: 'all', options: { cloud: false } } - end - end - - it "raises an exception if option is not present" do - expect do - described_class.new(options: {}, time_frame: 'all') - end.to raise_error(ArgumentError, %r{must be a boolean}) - end - - it "raises an exception if option has invalid value" do - expect do - described_class.new(options: { cloud: 'yes' }, time_frame: 'all') - end.to raise_error(ArgumentError, %r{must be a boolean}) - end -end diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb index 8ca42a6f007..9fcec56d019 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Usage::Metrics::Instrumentations::DatabaseMetric do +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::DatabaseMetric, feature_category: :service_ping do let(:database_metric_class) { Class.new(described_class) } subject do diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/generic_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/generic_metric_spec.rb index cc4df696b37..e65d5d30d9d 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/generic_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/generic_metric_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Usage::Metrics::Instrumentations::GenericMetric do +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::GenericMetric, feature_category: :service_ping do shared_examples 'custom fallback' do |custom_fallback| subject do Class.new(described_class) do diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/numbers_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/numbers_metric_spec.rb index 180c76d56f3..008e30eca9c 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/numbers_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/numbers_metric_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Usage::Metrics::Instrumentations::NumbersMetric do +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::NumbersMetric, feature_category: :service_ping do subject do described_class.tap do |metric_class| metric_class.operation :add diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric_spec.rb index 97306051533..33868d365a5 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Gitlab::Usage::Metrics::Instrumentations::RedisHLLMetric, :clean_gitlab_redis_shared_state do +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::RedisHLLMetric, :clean_gitlab_redis_shared_state, + feature_category: :service_ping do before do Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:i_quickactions_approve, values: 1, time: 1.week.ago) Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:i_quickactions_approve, values: 1, time: 2.weeks.ago) diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb index c4d6edd43e1..90568f4731e 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Gitlab::Usage::Metrics::Instrumentations::RedisMetric, :clean_gitlab_redis_shared_state do +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::RedisMetric, :clean_gitlab_redis_shared_state, + feature_category: :service_ping do before do 4.times do Gitlab::UsageDataCounters::SourceCodeCounter.count(:pushes) diff --git a/spec/lib/gitlab/usage/service_ping/instrumented_payload_spec.rb b/spec/lib/gitlab/usage/service_ping/instrumented_payload_spec.rb index 9d2711c49c6..51649e389e2 100644 --- a/spec/lib/gitlab/usage/service_ping/instrumented_payload_spec.rb +++ b/spec/lib/gitlab/usage/service_ping/instrumented_payload_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Gitlab::Usage::ServicePing::InstrumentedPayload do end context 'when building service ping with values' do - let(:metrics_key_paths) { %w(counts.boards uuid redis_hll_counters.search.i_search_total_monthly) } + let(:metrics_key_paths) { %w[counts.boards uuid redis_hll_counters.search.i_search_total_monthly] } let(:expected_payload) do { counts: { boards: 0 }, @@ -26,7 +26,7 @@ RSpec.describe Gitlab::Usage::ServicePing::InstrumentedPayload do end context 'when building service ping with instrumentations' do - let(:metrics_key_paths) { %w(counts.boards uuid redis_hll_counters.search.i_search_total_monthly) } + let(:metrics_key_paths) { %w[counts.boards uuid redis_hll_counters.search.i_search_total_monthly] } let(:expected_payload) do { counts: { boards: "SELECT COUNT(\"boards\".\"id\") FROM \"boards\"" }, diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb index 7bef14d5f7a..a7dc0b6a060 100644 --- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb @@ -113,73 +113,57 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s end end - context 'when usage_ping is disabled' do - it 'does not track the event' do - allow(::ServicePing::ServicePingSettings).to receive(:enabled?).and_return(false) + it 'tracks event when using symbol' do + expect(Gitlab::Redis::HLL).to receive(:add) - described_class.track_event(weekly_event, values: entity1, time: Date.current) - - expect(Gitlab::Redis::HLL).not_to receive(:add) - end + described_class.track_event(:g_analytics_contribution, values: entity1) end - context 'when usage_ping is enabled' do - before do - allow(::ServicePing::ServicePingSettings).to receive(:enabled?).and_return(true) - end + it 'tracks events with multiple values' do + values = [entity1, entity2] + expect(Gitlab::Redis::HLL).to receive(:add).with(key: /g_analytics_contribution/, value: values, + expiry: described_class::KEY_EXPIRY_LENGTH) - it 'tracks event when using symbol' do - expect(Gitlab::Redis::HLL).to receive(:add) - - described_class.track_event(:g_analytics_contribution, values: entity1) - end - - it 'tracks events with multiple values' do - values = [entity1, entity2] - expect(Gitlab::Redis::HLL).to receive(:add).with(key: /g_analytics_contribution/, value: values, - expiry: described_class::KEY_EXPIRY_LENGTH) + described_class.track_event(:g_analytics_contribution, values: values) + end - described_class.track_event(:g_analytics_contribution, values: values) - end + it 'raise error if metrics of unknown event' do + expect { described_class.track_event('unknown', values: entity1, time: Date.current) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent) + end - it 'raise error if metrics of unknown event' do - expect { described_class.track_event('unknown', values: entity1, time: Date.current) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent) + context 'when Rails environment is production' do + before do + allow(Rails.env).to receive(:development?).and_return(false) + allow(Rails.env).to receive(:test?).and_return(false) end - context 'when Rails environment is production' do - before do - allow(Rails.env).to receive(:development?).and_return(false) - allow(Rails.env).to receive(:test?).and_return(false) - end - - it 'reports only UnknownEvent exception' do - expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception) - .with(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent) - .once - .and_call_original + it 'reports only UnknownEvent exception' do + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception) + .with(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent) + .once + .and_call_original - expect { described_class.track_event('unknown', values: entity1, time: Date.current) }.not_to raise_error - end + expect { described_class.track_event('unknown', values: entity1, time: Date.current) }.not_to raise_error end + end - it 'reports an error if Feature.enabled raise an error' do - expect(Feature).to receive(:enabled?).and_raise(StandardError.new) - expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception) + it 'reports an error if Feature.enabled raise an error' do + expect(Feature).to receive(:enabled?).and_raise(StandardError.new) + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception) - described_class.track_event(:g_analytics_contribution, values: entity1, time: Date.current) - end + described_class.track_event(:g_analytics_contribution, values: entity1, time: Date.current) + end - context 'for weekly events' do - it 'sets the keys in Redis to expire' do - described_class.track_event("g_compliance_dashboard", values: entity1) + context 'for weekly events' do + it 'sets the keys in Redis to expire' do + described_class.track_event("g_compliance_dashboard", values: entity1) - Gitlab::Redis::SharedState.with do |redis| - keys = redis.scan_each(match: "{#{described_class::REDIS_SLOT}}_g_compliance_dashboard-*").to_a - expect(keys).not_to be_empty + Gitlab::Redis::SharedState.with do |redis| + keys = redis.scan_each(match: "{#{described_class::REDIS_SLOT}}_g_compliance_dashboard-*").to_a + expect(keys).not_to be_empty - keys.each do |key| - expect(redis.ttl(key)).to be_within(5.seconds).of(described_class::KEY_EXPIRY_LENGTH) - end + keys.each do |key| + expect(redis.ttl(key)).to be_within(5.seconds).of(described_class::KEY_EXPIRY_LENGTH) end end end diff --git a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb index 753e09731bf..39d48b7b938 100644 --- a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb @@ -7,51 +7,19 @@ RSpec.describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shar subject { Class.new.extend(described_class) } - before do - allow(::ServicePing::ServicePingSettings).to receive(:enabled?).and_return(service_ping_enabled) - end - describe '.increment' do - context 'when usage_ping is disabled' do - let(:service_ping_enabled) { false } - - it 'counter is not increased' do - expect do - subject.increment(redis_key) - end.not_to change { subject.total_count(redis_key) } - end - end - - context 'when usage_ping is enabled' do - let(:service_ping_enabled) { true } - - it 'counter is increased' do - expect do - subject.increment(redis_key) - end.to change { subject.total_count(redis_key) }.by(1) - end + it 'counter is increased' do + expect do + subject.increment(redis_key) + end.to change { subject.total_count(redis_key) }.by(1) end end describe '.increment_by' do - context 'when usage_ping is disabled' do - let(:service_ping_enabled) { false } - - it 'counter is not increased' do - expect do - subject.increment_by(redis_key, 3) - end.not_to change { subject.total_count(redis_key) } - end - end - - context 'when usage_ping is enabled' do - let(:service_ping_enabled) { true } - - it 'counter is increased' do - expect do - subject.increment_by(redis_key, 3) - end.to change { subject.total_count(redis_key) }.by(3) - end + it 'counter is increased' do + expect do + subject.increment_by(redis_key, 3) + end.to change { subject.total_count(redis_key) }.by(3) end end end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 6f188aa408e..a1564318408 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -42,7 +42,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic end it 'ensures recorded_at is set before any other usage data calculation' do - %i(alt_usage_data redis_usage_data distinct_count count).each do |method| + %i[alt_usage_data redis_usage_data distinct_count count].each do |method| expect(described_class).not_to receive(method) end expect(described_class).to receive(:recorded_at).and_raise(Exception.new('Stopped calculating recorded_at')) @@ -191,7 +191,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic omniauth: { providers: omniauth_providers } ) - allow(Devise).to receive(:omniauth_providers).and_return(%w(ldapmain ldapsecondary group_saml)) + allow(Devise).to receive(:omniauth_providers).and_return(%w[ldapmain ldapsecondary group_saml]) for_defined_days_back do user = create(:user) @@ -268,7 +268,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic for_defined_days_back do user = create(:user) - %w(gitlab_project github bitbucket bitbucket_server gitea git manifest fogbugz).each do |type| + %w[gitlab_project github bitbucket bitbucket_server gitea git manifest fogbugz].each do |type| create(:project, import_type: type, creator_id: user.id) end @@ -734,7 +734,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic subject { described_class.object_store_usage_data } it 'fetches object store config of five components' do - %w(artifacts external_diffs lfs uploads packages).each do |component| + %w[artifacts external_diffs lfs uploads packages].each do |component| expect(described_class).to receive(:object_store_config).with(component).and_return("#{component}_object_store_config") end diff --git a/spec/lib/gitlab/utils/log_limited_array_spec.rb b/spec/lib/gitlab/utils/log_limited_array_spec.rb index a55a176be48..23cca4fd791 100644 --- a/spec/lib/gitlab/utils/log_limited_array_spec.rb +++ b/spec/lib/gitlab/utils/log_limited_array_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Gitlab::Utils::LogLimitedArray do context 'when the argument is an array' do context 'when the array is under the limit' do it 'returns the array unchanged' do - expect(described_class.log_limited_array(%w(a b))).to eq(%w(a b)) + expect(described_class.log_limited_array(%w[a b])).to eq(%w[a b]) end end diff --git a/spec/lib/gitlab/webpack/graphql_known_operations_spec.rb b/spec/lib/gitlab/webpack/graphql_known_operations_spec.rb index 89cade82fe6..6c3e3b4eb69 100644 --- a/spec/lib/gitlab/webpack/graphql_known_operations_spec.rb +++ b/spec/lib/gitlab/webpack/graphql_known_operations_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Gitlab::Webpack::GraphqlKnownOperations do 2.times { ::Gitlab::Webpack::GraphqlKnownOperations.load } - expect(::Gitlab::Webpack::GraphqlKnownOperations.load).to eq(%w(hello world test)) + expect(::Gitlab::Webpack::GraphqlKnownOperations.load).to eq(%w[hello world test]) end end diff --git a/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb b/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb index 3152dc2ad2f..3d165f7d830 100644 --- a/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb +++ b/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Gitlab::WikiPages::FrontMatterParser do end def have_correct_front_matter - include(a: 1, b: 2, c: %w(foo bar)) + include(a: 1, b: 2, c: %w[foo bar]) end describe '#parse' do diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index cca18cb05c7..d77763f89be 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -226,7 +226,8 @@ RSpec.describe Gitlab::Workhorse, feature_category: :shared do GL_ID: "user-#{user.id}", GL_USERNAME: user.username, GL_REPOSITORY: "project-#{project.id}", - ShowAllRefs: false + ShowAllRefs: false, + NeedAudit: false } end @@ -277,6 +278,12 @@ RSpec.describe Gitlab::Workhorse, feature_category: :shared do it { is_expected.to include(ShowAllRefs: true) } end + context 'need_audit enabled' do + subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action, show_all_refs: true, need_audit: true) } + + it { is_expected.to include(NeedAudit: true) } + end + context 'when a feature flag is set for a single project' do before do stub_feature_flags(gitaly_mep_mep: project) diff --git a/spec/lib/object_storage/config_spec.rb b/spec/lib/object_storage/config_spec.rb index 412fcb9b6b8..bf9aeb51cda 100644 --- a/spec/lib/object_storage/config_spec.rb +++ b/spec/lib/object_storage/config_spec.rb @@ -155,7 +155,7 @@ RSpec.describe ObjectStorage::Config, feature_category: :shared do it { expect(subject.aws_server_side_encryption_enabled?).to be true } it { expect(subject.server_side_encryption).to eq('AES256') } it { expect(subject.server_side_encryption_kms_key_id).to eq('arn:aws:12345') } - it { expect(subject.fog_attributes.keys).to match_array(%w(x-amz-server-side-encryption x-amz-server-side-encryption-aws-kms-key-id)) } + it { expect(subject.fog_attributes.keys).to match_array(%w[x-amz-server-side-encryption x-amz-server-side-encryption-aws-kms-key-id]) } end context 'with only server side encryption enabled' do diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb index 3a42e6ebd09..5df295e73d7 100644 --- a/spec/lib/object_storage/direct_upload_spec.rb +++ b/spec/lib/object_storage/direct_upload_spec.rb @@ -123,7 +123,7 @@ RSpec.describe ObjectStorage::DirectUpload, feature_category: :shared do expect(s3_config[:Region]).to eq(region) expect(s3_config[:PathStyle]).to eq(path_style) expect(s3_config[:UseIamProfile]).to eq(use_iam_profile) - expect(s3_config.keys).not_to include(%i(ServerSideEncryption SSEKMSKeyID)) + expect(s3_config.keys).not_to include(%i[ServerSideEncryption SSEKMSKeyID]) end context 'when no region is specified' do diff --git a/spec/lib/peek/views/rugged_spec.rb b/spec/lib/peek/views/rugged_spec.rb deleted file mode 100644 index 31418b5fc81..00000000000 --- a/spec/lib/peek/views/rugged_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Peek::Views::Rugged, :request_store do - subject { described_class.new } - - let(:project) { create(:project) } - - before do - allow(Gitlab::PerformanceBar).to receive(:enabled_for_request?).and_return(true) - end - - it 'returns no results' do - expect(subject.results).to eq({}) - end - - it 'returns aggregated results' do - ::Gitlab::RuggedInstrumentation.add_query_time(1.234) - ::Gitlab::RuggedInstrumentation.increment_query_count - ::Gitlab::RuggedInstrumentation.increment_query_count - - ::Gitlab::RuggedInstrumentation.add_call_details(feature: :rugged_test, - args: [project.repository.raw, 'HEAD'], - duration: 0.123) - ::Gitlab::RuggedInstrumentation.add_call_details(feature: :rugged_test2, - args: [project.repository, 'refs/heads/master'], - duration: 0.456) - - results = subject.results - expect(results[:calls]).to eq(2) - expect(results[:duration]).to eq('1234.00ms') - expect(results[:details].count).to eq(2) - - expected = [ - [project.repository.raw.to_s, "HEAD"], - [project.repository.to_s, "refs/heads/master"] - ] - - expect(results[:details].map { |data| data[:args] }).to match_array(expected) - end -end diff --git a/spec/lib/result_spec.rb b/spec/lib/result_spec.rb index 2b88521fe14..170a2f5e777 100644 --- a/spec/lib/result_spec.rb +++ b/spec/lib/result_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' # NOTE: # This spec is intended to serve as documentation examples of idiomatic usage for the `Result` type. diff --git a/spec/lib/rouge/formatters/html_gitlab_spec.rb b/spec/lib/rouge/formatters/html_gitlab_spec.rb index 6fc1b395fc8..5e5075b72b8 100644 --- a/spec/lib/rouge/formatters/html_gitlab_spec.rb +++ b/spec/lib/rouge/formatters/html_gitlab_spec.rb @@ -15,14 +15,14 @@ RSpec.describe Rouge::Formatters::HTMLGitlab, feature_category: :source_code_man let(:options) { { tag: lang, ellipsis_indexes: [0], ellipsis_svg: "svg_icon" } } it 'returns highlighted ruby code with svg' do - code = %q{<span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">hello</span><span class="gl-px-2 gl-rounded-base gl-mx-2 gl-bg-gray-100 gl-cursor-help has-tooltip" title="Content has been trimmed">svg_icon</span></span>} + code = %q(<span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">hello</span><span class="gl-px-2 gl-rounded-base gl-mx-2 gl-bg-gray-100 gl-cursor-help has-tooltip" title="Content has been trimmed">svg_icon</span></span>) is_expected.to eq(code) end end it 'returns highlighted ruby code' do - code = %q{<span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">hello</span></span>} + code = %q(<span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">hello</span></span>) is_expected.to eq(code) end @@ -31,7 +31,7 @@ RSpec.describe Rouge::Formatters::HTMLGitlab, feature_category: :source_code_man let(:options) { {} } it 'returns highlighted code without language' do - code = %q{<span id="LC1" class="line" lang=""><span class="k">def</span> <span class="nf">hello</span></span>} + code = %q(<span id="LC1" class="line" lang=""><span class="k">def</span> <span class="nf">hello</span></span>) is_expected.to eq(code) end @@ -41,7 +41,7 @@ RSpec.describe Rouge::Formatters::HTMLGitlab, feature_category: :source_code_man let(:options) { { tag: lang, line_number: 10 } } it 'returns highlighted ruby code with correct line number' do - code = %q{<span id="LC10" class="line" lang="ruby"><span class="k">def</span> <span class="nf">hello</span></span>} + code = %q(<span id="LC10" class="line" lang="ruby"><span class="k">def</span> <span class="nf">hello</span></span>) is_expected.to eq(code) end @@ -64,7 +64,7 @@ RSpec.describe Rouge::Formatters::HTMLGitlab, feature_category: :source_code_man it 'highlights the control characters' do message = "Potentially unwanted character detected: Unicode BiDi Control" - is_expected.to include(%{<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{message}">}).exactly(4).times + is_expected.to include(%(<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{message}">)).exactly(4).times end end diff --git a/spec/lib/safe_zip/entry_spec.rb b/spec/lib/safe_zip/entry_spec.rb index 8d49e2bcece..9a068b255dd 100644 --- a/spec/lib/safe_zip/entry_spec.rb +++ b/spec/lib/safe_zip/entry_spec.rb @@ -4,8 +4,8 @@ require 'spec_helper' RSpec.describe SafeZip::Entry do let(:target_path) { Dir.mktmpdir('safe-zip') } - let(:directories) { %w(public folder/with/subfolder) } - let(:files) { %w(public/index.html public/assets/image.png) } + let(:directories) { %w[public folder/with/subfolder] } + let(:files) { %w[public/index.html public/assets/image.png] } let(:params) { SafeZip::ExtractParams.new(directories: directories, files: files, to: target_path) } let(:entry) { described_class.new(zip_archive, zip_entry, params) } @@ -52,7 +52,7 @@ RSpec.describe SafeZip::Entry do subject { entry.extract } context 'when entry does not match the filtered directories' do - let(:directories) { %w(public folder/with/subfolder) } + let(:directories) { %w[public folder/with/subfolder] } let(:files) { [] } using RSpec::Parameterized::TableSyntax @@ -76,7 +76,7 @@ RSpec.describe SafeZip::Entry do context 'when entry does not match the filtered files' do let(:directories) { [] } - let(:files) { %w(public/index.html public/assets/image.png) } + let(:files) { %w[public/index.html public/assets/image.png] } using RSpec::Parameterized::TableSyntax diff --git a/spec/lib/safe_zip/extract_params_spec.rb b/spec/lib/safe_zip/extract_params_spec.rb index 0ebfb7430c5..b0d787e09d5 100644 --- a/spec/lib/safe_zip/extract_params_spec.rb +++ b/spec/lib/safe_zip/extract_params_spec.rb @@ -6,8 +6,8 @@ RSpec.describe SafeZip::ExtractParams do let(:target_path) { Dir.mktmpdir("safe-zip") } let(:real_target_path) { File.realpath(target_path) } let(:params) { described_class.new(directories: directories, files: files, to: target_path) } - let(:directories) { %w(public folder/with/subfolder) } - let(:files) { %w(public/index.html public/assets/image.png) } + let(:directories) { %w[public folder/with/subfolder] } + let(:files) { %w[public/index.html public/assets/image.png] } after do FileUtils.remove_entry_secure(target_path) diff --git a/spec/lib/safe_zip/extract_spec.rb b/spec/lib/safe_zip/extract_spec.rb index c727475e271..fa8a922beef 100644 --- a/spec/lib/safe_zip/extract_spec.rb +++ b/spec/lib/safe_zip/extract_spec.rb @@ -4,8 +4,8 @@ require 'spec_helper' RSpec.describe SafeZip::Extract do let(:target_path) { Dir.mktmpdir('safe-zip') } - let(:directories) { %w(public) } - let(:files) { %w(public/index.html) } + let(:directories) { %w[public] } + let(:files) { %w[public/index.html] } let(:object) { described_class.new(archive) } let(:archive) { Rails.root.join('spec', 'fixtures', 'safe_zip', archive_name) } @@ -47,7 +47,7 @@ RSpec.describe SafeZip::Extract do end end - %w(valid-simple.zip valid-symlinks-first.zip valid-non-writeable.zip).each do |name| + %w[valid-simple.zip valid-symlinks-first.zip valid-non-writeable.zip].each do |name| context "when using #{name} archive" do let(:archive_name) { name } @@ -74,7 +74,7 @@ RSpec.describe SafeZip::Extract do context 'when no matching directories are found' do let(:archive_name) { 'valid-simple.zip' } - let(:directories) { %w(non/existing) } + let(:directories) { %w[non/existing] } let(:error_message) { 'No entries extracted' } subject { object.extract(directories: directories, to: target_path) } @@ -84,7 +84,7 @@ RSpec.describe SafeZip::Extract do context 'when no matching files are found' do let(:archive_name) { 'valid-simple.zip' } - let(:files) { %w(non/existing) } + let(:files) { %w[non/existing] } let(:error_message) { 'No entries extracted' } subject { object.extract(files: files, to: target_path) } diff --git a/spec/lib/sbom/purl_type/converter_spec.rb b/spec/lib/sbom/purl_type/converter_spec.rb index 2eb35c4d079..d0907bf253f 100644 --- a/spec/lib/sbom/purl_type/converter_spec.rb +++ b/spec/lib/sbom/purl_type/converter_spec.rb @@ -22,6 +22,7 @@ RSpec.describe Sbom::PurlType::Converter, feature_category: :dependency_manageme 'nuget' | 'nuget' 'pip' | 'pypi' 'pipenv' | 'pypi' + 'poetry' | 'pypi' 'setuptools' | 'pypi' 'Python (python-pkg)' | 'pypi' 'analyzer (gobinary)' | 'golang' diff --git a/spec/lib/security/ci_configuration/container_scanning_build_action_spec.rb b/spec/lib/security/ci_configuration/container_scanning_build_action_spec.rb index 5b1db66beb0..af61d9c8261 100644 --- a/spec/lib/security/ci_configuration/container_scanning_build_action_spec.rb +++ b/spec/lib/security/ci_configuration/container_scanning_build_action_spec.rb @@ -39,7 +39,7 @@ RSpec.describe Security::CiConfiguration::ContainerScanningBuildAction do context 'template includes are an array' do let(:gitlab_ci_content) do - { "stages" => %w(test security), + { "stages" => %w[test security], "variables" => { "RANDOM" => "make sure this persists" }, "include" => [{ "template" => "existing.yml" }] } end @@ -52,7 +52,7 @@ RSpec.describe Security::CiConfiguration::ContainerScanningBuildAction do context 'template include is not an array' do let(:gitlab_ci_content) do - { "stages" => %w(test security), + { "stages" => %w[test security], "variables" => { "RANDOM" => "make sure this persists" }, "include" => { "template" => "existing.yml" } } end @@ -91,7 +91,7 @@ RSpec.describe Security::CiConfiguration::ContainerScanningBuildAction do context 'container_scanning template include are an array' do let(:gitlab_ci_content) do - { "stages" => %w(test), + { "stages" => %w[test], "variables" => { "RANDOM" => "make sure this persists" }, "include" => [{ "template" => "Jobs/Container-Scanning.gitlab-ci.yml" }] } end @@ -104,7 +104,7 @@ RSpec.describe Security::CiConfiguration::ContainerScanningBuildAction do context 'container_scanning template include is not an array' do let(:gitlab_ci_content) do - { "stages" => %w(test), + { "stages" => %w[test], "variables" => { "RANDOM" => "make sure this persists" }, "include" => { "template" => "Jobs/Container-Scanning.gitlab-ci.yml" } } end diff --git a/spec/lib/security/ci_configuration/sast_build_action_spec.rb b/spec/lib/security/ci_configuration/sast_build_action_spec.rb index 381ea60e7f5..fe504e2b278 100644 --- a/spec/lib/security/ci_configuration/sast_build_action_spec.rb +++ b/spec/lib/security/ci_configuration/sast_build_action_spec.rb @@ -218,47 +218,47 @@ RSpec.describe Security::CiConfiguration::SastBuildAction do end def existing_gitlab_ci_and_template_array_without_sast - { "stages" => %w(test security), + { "stages" => %w[test security], "variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" }, "sast" => { "variables" => { "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" }, "include" => [{ "template" => "existing.yml" }] } end def existing_gitlab_ci_and_single_template_with_sast_and_default_stage - { "stages" => %w(test), + { "stages" => %w[test], "variables" => { "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" }, "sast" => { "variables" => { "SEARCH_MAX_DEPTH" => 1 }, "stage" => "test" }, "include" => { "template" => "Security/SAST.gitlab-ci.yml" } } end def existing_gitlab_ci_and_single_template_without_sast - { "stages" => %w(test security), + { "stages" => %w[test security], "variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" }, "sast" => { "variables" => { "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" }, "include" => { "template" => "existing.yml" } } end def existing_gitlab_ci_with_no_variables - { "stages" => %w(test security), + { "stages" => %w[test security], "sast" => { "variables" => { "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" }, "include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] } end def existing_gitlab_ci_with_no_sast_section - { "stages" => %w(test security), + { "stages" => %w[test security], "variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" }, "include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] } end def existing_gitlab_ci_with_no_sast_variables - { "stages" => %w(test security), + { "stages" => %w[test security], "variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "localhost:5000/analyzers" }, "sast" => { "stage" => "security" }, "include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] } end def existing_gitlab_ci - { "stages" => %w(test security), + { "stages" => %w[test security], "variables" => { "RANDOM" => "make sure this persists", "SECURE_ANALYZERS_PREFIX" => "bad_prefix" }, "sast" => { "variables" => { "SEARCH_MAX_DEPTH" => 1 }, "stage" => "security" }, "include" => [{ "template" => "Security/SAST.gitlab-ci.yml" }] } diff --git a/spec/lib/security/ci_configuration/sast_iac_build_action_spec.rb b/spec/lib/security/ci_configuration/sast_iac_build_action_spec.rb index 7b2a0d22918..fcee34d833b 100644 --- a/spec/lib/security/ci_configuration/sast_iac_build_action_spec.rb +++ b/spec/lib/security/ci_configuration/sast_iac_build_action_spec.rb @@ -34,7 +34,7 @@ RSpec.describe Security::CiConfiguration::SastIacBuildAction do context 'template includes are an array' do let(:gitlab_ci_content) do - { "stages" => %w(test security), + { "stages" => %w[test security], "variables" => { "RANDOM" => "make sure this persists" }, "include" => [{ "template" => "existing.yml" }] } end @@ -47,7 +47,7 @@ RSpec.describe Security::CiConfiguration::SastIacBuildAction do context 'template include is not an array' do let(:gitlab_ci_content) do - { "stages" => %w(test security), + { "stages" => %w[test security], "variables" => { "RANDOM" => "make sure this persists" }, "include" => { "template" => "existing.yml" } } end @@ -80,7 +80,7 @@ RSpec.describe Security::CiConfiguration::SastIacBuildAction do context 'secret_detection template include are an array' do let(:gitlab_ci_content) do - { "stages" => %w(test), + { "stages" => %w[test], "variables" => { "RANDOM" => "make sure this persists" }, "include" => [{ "template" => "Security/SAST-IaC.latest.gitlab-ci.yml" }] } end @@ -93,7 +93,7 @@ RSpec.describe Security::CiConfiguration::SastIacBuildAction do context 'secret_detection template include is not an array' do let(:gitlab_ci_content) do - { "stages" => %w(test), + { "stages" => %w[test], "variables" => { "RANDOM" => "make sure this persists" }, "include" => { "template" => "Security/SAST-IaC.latest.gitlab-ci.yml" } } end diff --git a/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb b/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb index 4d9860ca4a5..64323ce71f3 100644 --- a/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb +++ b/spec/lib/security/ci_configuration/secret_detection_build_action_spec.rb @@ -33,7 +33,7 @@ RSpec.describe Security::CiConfiguration::SecretDetectionBuildAction do context 'template includes are an array' do let(:gitlab_ci_content) do - { "stages" => %w(test security), + { "stages" => %w[test security], "variables" => { "RANDOM" => "make sure this persists" }, "include" => [{ "template" => "existing.yml" }] } end @@ -46,7 +46,7 @@ RSpec.describe Security::CiConfiguration::SecretDetectionBuildAction do context 'template include is not an array' do let(:gitlab_ci_content) do - { "stages" => %w(test security), + { "stages" => %w[test security], "variables" => { "RANDOM" => "make sure this persists" }, "include" => { "template" => "existing.yml" } } end @@ -79,7 +79,7 @@ RSpec.describe Security::CiConfiguration::SecretDetectionBuildAction do context 'secret_detection template include are an array' do let(:gitlab_ci_content) do - { "stages" => %w(test), + { "stages" => %w[test], "variables" => { "RANDOM" => "make sure this persists" }, "include" => [{ "template" => "Security/Secret-Detection.gitlab-ci.yml" }] } end @@ -92,7 +92,7 @@ RSpec.describe Security::CiConfiguration::SecretDetectionBuildAction do context 'secret_detection template include is not an array' do let(:gitlab_ci_content) do - { "stages" => %w(test), + { "stages" => %w[test], "variables" => { "RANDOM" => "make sure this persists" }, "include" => { "template" => "Security/Secret-Detection.gitlab-ci.yml" } } end diff --git a/spec/lib/sidebars/explore/menus/catalog_menu_spec.rb b/spec/lib/sidebars/explore/menus/catalog_menu_spec.rb new file mode 100644 index 00000000000..2c4c4c48eae --- /dev/null +++ b/spec/lib/sidebars/explore/menus/catalog_menu_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Sidebars::Explore::Menus::CatalogMenu, feature_category: :navigation do + let_it_be(:current_user) { build(:user) } + let_it_be(:user) { build(:user) } + + let(:context) { Sidebars::Context.new(current_user: current_user, container: user) } + + subject { described_class.new(context) } + + context 'when `global_ci_catalog` is enabled`' do + it 'renders' do + expect(subject.render?).to be(true) + end + + it 'renders the correct link' do + expect(subject.link).to match "explore/catalog" + end + + it 'renders the correct title' do + expect(subject.title).to eq "CI/CD Catalog" + end + + it 'renders the correct icon' do + expect(subject.sprite_icon).to eq "catalog-checkmark" + end + end + + context 'when `global_ci_catalog` FF is disabled' do + before do + stub_feature_flags(global_ci_catalog: false) + end + + it 'does not render' do + expect(subject.render?).to be(false) + end + end +end diff --git a/spec/lib/sidebars/menu_spec.rb b/spec/lib/sidebars/menu_spec.rb index e59a8cd2163..aa3b754f17e 100644 --- a/spec/lib/sidebars/menu_spec.rb +++ b/spec/lib/sidebars/menu_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do describe '#all_active_routes' do it 'gathers all active routes of items and the current menu' do - menu.add_item(Sidebars::MenuItem.new(title: 'foo1', link: 'foo1', active_routes: { path: %w(bar test) })) + menu.add_item(Sidebars::MenuItem.new(title: 'foo1', link: 'foo1', active_routes: { path: %w[bar test] })) menu.add_item(Sidebars::MenuItem.new(title: 'foo2', link: 'foo2', active_routes: { controller: 'fooc' })) menu.add_item(Sidebars::MenuItem.new(title: 'foo3', link: 'foo3', active_routes: { controller: 'barc' })) menu.add_item(nil_menu_item) @@ -18,7 +18,7 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do allow(menu).to receive(:active_routes).and_return({ path: 'foo' }) expect(menu).to receive(:renderable_items).and_call_original - expect(menu.all_active_routes).to eq({ path: %w(foo bar test), controller: %w(fooc barc) }) + expect(menu.all_active_routes).to eq({ path: %w[foo bar test], controller: %w[fooc barc] }) end end @@ -53,6 +53,7 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do { title: "Title", icon: nil, + id: 'menu', avatar: nil, avatar_shape: 'rect', entity_id: nil, @@ -94,6 +95,7 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do { title: "Title", icon: nil, + id: 'menu', avatar: nil, avatar_shape: 'rect', entity_id: nil, diff --git a/spec/lib/sidebars/organizations/menus/manage_menu_spec.rb b/spec/lib/sidebars/organizations/menus/manage_menu_spec.rb index 08fc352a6cd..87346176a4c 100644 --- a/spec/lib/sidebars/organizations/menus/manage_menu_spec.rb +++ b/spec/lib/sidebars/organizations/menus/manage_menu_spec.rb @@ -24,5 +24,11 @@ RSpec.describe Sidebars::Organizations::Menus::ManageMenu, feature_category: :na it { is_expected.not_to be_nil } end + + describe 'Users' do + let(:item_id) { :organization_users } + + it { is_expected.not_to be_nil } + end end end diff --git a/spec/lib/sidebars/projects/menus/scope_menu_spec.rb b/spec/lib/sidebars/projects/menus/scope_menu_spec.rb index 1c2d159950a..108a98e28a4 100644 --- a/spec/lib/sidebars/projects/menus/scope_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/scope_menu_spec.rb @@ -23,7 +23,7 @@ RSpec.describe Sidebars::Projects::Menus::ScopeMenu, feature_category: :navigati describe '#container_html_options' do subject { described_class.new(context).container_html_options } - specify { is_expected.to match(hash_including(class: 'shortcuts-project rspec-project-link')) } + specify { is_expected.to match(hash_including(class: 'shortcuts-project')) } end describe '#extra_nav_link_html_options' do diff --git a/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb b/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb index 4b4706bd311..c7c0586c2f1 100644 --- a/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb @@ -21,9 +21,12 @@ RSpec.describe Sidebars::Projects::Menus::SecurityComplianceMenu do context 'when user is authenticated' do context 'when the Security and Compliance is disabled' do + let_it_be(:project) { create(:project, :security_and_compliance_disabled) } + before do allow(Ability).to receive(:allowed?).with(user, :access_security_and_compliance, project).and_return(false) allow(Ability).to receive(:allowed?).with(user, :read_security_resource, project).and_return(false) + allow(project).to receive(:security_and_compliance_enabled?).and_return(false) end it { is_expected.to be_falsey } diff --git a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb index 81ca9670ac6..605cec8be5e 100644 --- a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb @@ -59,18 +59,6 @@ RSpec.describe Sidebars::Projects::Menus::SettingsMenu, feature_category: :navig let(:item_id) { :access_tokens } it_behaves_like 'access rights checks' - - describe 'when the user is not an admin but has manage_resource_access_tokens' do - before do - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(user, :admin_project, project).and_return(false) - allow(Ability).to receive(:allowed?).with(user, :manage_resource_access_tokens, project).and_return(true) - end - - it 'includes access token menu item' do - expect(subject.title).to eql('Access Tokens') - end - end end describe 'Repository' do diff --git a/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb b/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb index e5c5204e0b4..3f8a146f040 100644 --- a/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb +++ b/spec/lib/sidebars/projects/super_sidebar_menus/monitor_menu_spec.rb @@ -16,6 +16,7 @@ RSpec.describe Sidebars::Projects::SuperSidebarMenus::MonitorMenu, feature_categ expect(items.map(&:class).uniq).to eq([Sidebars::NilMenuItem]) expect(items.map(&:item_id)).to eq([ :tracing, + :metrics, :error_tracking, :alert_management, :incidents, diff --git a/spec/lib/sidebars/user_settings/menus/comment_templates_menu_spec.rb b/spec/lib/sidebars/user_settings/menus/comment_templates_menu_spec.rb index 37a383cfd9d..9a093efe6ba 100644 --- a/spec/lib/sidebars/user_settings/menus/comment_templates_menu_spec.rb +++ b/spec/lib/sidebars/user_settings/menus/comment_templates_menu_spec.rb @@ -15,10 +15,6 @@ RSpec.describe Sidebars::UserSettings::Menus::CommentTemplatesMenu, feature_cate let_it_be(:user) { build(:user) } context 'when comment templates are enabled' do - before do - allow(subject).to receive(:saved_replies_enabled?).and_return(true) - end - context 'when user is logged in' do let(:context) { Sidebars::Context.new(current_user: user, container: nil) } @@ -37,29 +33,5 @@ RSpec.describe Sidebars::UserSettings::Menus::CommentTemplatesMenu, feature_cate end end end - - context 'when comment templates are disabled' do - before do - allow(subject).to receive(:saved_replies_enabled?).and_return(false) - end - - context 'when user is logged in' do - let(:context) { Sidebars::Context.new(current_user: user, container: nil) } - - it 'renders' do - expect(subject.render?).to be false - end - end - - context 'when user is not logged in' do - let(:context) { Sidebars::Context.new(current_user: nil, container: nil) } - - subject { described_class.new(context) } - - it 'does not render' do - expect(subject.render?).to be false - end - end - end end end diff --git a/spec/lib/system_check/orphans/namespace_check_spec.rb b/spec/lib/system_check/orphans/namespace_check_spec.rb index e764c2313cd..3964068b20c 100644 --- a/spec/lib/system_check/orphans/namespace_check_spec.rb +++ b/spec/lib/system_check/orphans/namespace_check_spec.rb @@ -12,10 +12,10 @@ RSpec.describe SystemCheck::Orphans::NamespaceCheck, :silence_stdout do describe '#multi_check' do context 'all orphans' do - let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 repos/@hashed) } + let(:disk_namespaces) { %w[/repos/orphan1 /repos/orphan2 repos/@hashed] } it 'prints list of all orphaned namespaces except @hashed' do - expect_list_of_orphans(%w(orphan1 orphan2)) + expect_list_of_orphans(%w[orphan1 orphan2]) subject.multi_check end @@ -23,10 +23,10 @@ RSpec.describe SystemCheck::Orphans::NamespaceCheck, :silence_stdout do context 'few orphans with existing namespace' do let!(:first_level) { create(:group, path: 'my-namespace') } - let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/@hashed) } + let(:disk_namespaces) { %w[/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/@hashed] } it 'prints list of orphaned namespaces' do - expect_list_of_orphans(%w(orphan1 orphan2)) + expect_list_of_orphans(%w[orphan1 orphan2]) subject.multi_check end @@ -35,17 +35,17 @@ RSpec.describe SystemCheck::Orphans::NamespaceCheck, :silence_stdout do context 'few orphans with existing namespace and parents with same name as orphans' do let!(:first_level) { create(:group, path: 'my-namespace') } let!(:second_level) { create(:group, path: 'second-level', parent: first_level) } - let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/second-level /repos/@hashed) } + let(:disk_namespaces) { %w[/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/second-level /repos/@hashed] } it 'prints list of orphaned namespaces ignoring parents with same namespace as orphans' do - expect_list_of_orphans(%w(orphan1 orphan2 second-level)) + expect_list_of_orphans(%w[orphan1 orphan2 second-level]) subject.multi_check end end context 'no orphans' do - let(:disk_namespaces) { %w(@hashed) } + let(:disk_namespaces) { %w[@hashed] } it 'prints an empty list ignoring @hashed' do expect_list_of_orphans([]) diff --git a/spec/lib/system_check/orphans/repository_check_spec.rb b/spec/lib/system_check/orphans/repository_check_spec.rb index 91b48969cc1..0504e133ab9 100644 --- a/spec/lib/system_check/orphans/repository_check_spec.rb +++ b/spec/lib/system_check/orphans/repository_check_spec.rb @@ -13,11 +13,11 @@ RSpec.describe SystemCheck::Orphans::RepositoryCheck, :silence_stdout do describe '#multi_check' do context 'all orphans' do - let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 repos/@hashed) } - let(:disk_repositories) { %w(repo1.git repo2.git) } + let(:disk_namespaces) { %w[/repos/orphan1 /repos/orphan2 repos/@hashed] } + let(:disk_repositories) { %w[repo1.git repo2.git] } it 'prints list of all orphaned namespaces except @hashed' do - expect_list_of_orphans(%w(orphan1/repo1.git orphan1/repo2.git orphan2/repo1.git orphan2/repo2.git)) + expect_list_of_orphans(%w[orphan1/repo1.git orphan1/repo2.git orphan2/repo1.git orphan2/repo2.git]) subject.multi_check end @@ -26,11 +26,11 @@ RSpec.describe SystemCheck::Orphans::RepositoryCheck, :silence_stdout do context 'few orphans with existing namespace' do let!(:first_level) { create(:group, path: 'my-namespace') } let!(:project) { create(:project, path: 'repo', namespace: first_level) } - let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/@hashed) } - let(:disk_repositories) { %w(repo.git) } + let(:disk_namespaces) { %w[/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/@hashed] } + let(:disk_repositories) { %w[repo.git] } it 'prints list of orphaned namespaces' do - expect_list_of_orphans(%w(orphan1/repo.git orphan2/repo.git)) + expect_list_of_orphans(%w[orphan1/repo.git orphan2/repo.git]) subject.multi_check end @@ -40,19 +40,19 @@ RSpec.describe SystemCheck::Orphans::RepositoryCheck, :silence_stdout do let!(:first_level) { create(:group, path: 'my-namespace') } let!(:second_level) { create(:group, path: 'second-level', parent: first_level) } let!(:project) { create(:project, path: 'repo', namespace: first_level) } - let(:disk_namespaces) { %w(/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/second-level /repos/@hashed) } - let(:disk_repositories) { %w(repo.git) } + let(:disk_namespaces) { %w[/repos/orphan1 /repos/orphan2 /repos/my-namespace /repos/second-level /repos/@hashed] } + let(:disk_repositories) { %w[repo.git] } it 'prints list of orphaned namespaces ignoring parents with same namespace as orphans' do - expect_list_of_orphans(%w(orphan1/repo.git orphan2/repo.git second-level/repo.git)) + expect_list_of_orphans(%w[orphan1/repo.git orphan2/repo.git second-level/repo.git]) subject.multi_check end end context 'no orphans' do - let(:disk_namespaces) { %w(@hashed) } - let(:disk_repositories) { %w(repo.git) } + let(:disk_namespaces) { %w[@hashed] } + let(:disk_repositories) { %w[repo.git] } it 'prints an empty list ignoring @hashed' do expect_list_of_orphans([]) diff --git a/spec/lib/system_check/sidekiq_check_spec.rb b/spec/lib/system_check/sidekiq_check_spec.rb index ff4eece8f7c..efd5414294a 100644 --- a/spec/lib/system_check/sidekiq_check_spec.rb +++ b/spec/lib/system_check/sidekiq_check_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe SystemCheck::SidekiqCheck do describe '#multi_check' do def stub_ps_output(output) - allow(Gitlab::Popen).to receive(:popen).with(%w(ps uxww)).and_return([output, nil]) + allow(Gitlab::Popen).to receive(:popen).with(%w[ps uxww]).and_return([output, nil]) end def expect_check_output(matcher) diff --git a/spec/lib/unnested_in_filters/dsl_spec.rb b/spec/lib/unnested_in_filters/dsl_spec.rb index bce4c88f94c..9f1552b02ec 100644 --- a/spec/lib/unnested_in_filters/dsl_spec.rb +++ b/spec/lib/unnested_in_filters/dsl_spec.rb @@ -12,7 +12,7 @@ RSpec.describe UnnestedInFilters::Dsl do end describe '#exists?' do - let(:states) { %w(active banned) } + let(:states) { %w[active banned] } subject { test_model.where(state: states).use_unnested_filters.exists? } diff --git a/spec/lib/unnested_in_filters/rewriter_spec.rb b/spec/lib/unnested_in_filters/rewriter_spec.rb index ea561c42993..945a50ce2e8 100644 --- a/spec/lib/unnested_in_filters/rewriter_spec.rb +++ b/spec/lib/unnested_in_filters/rewriter_spec.rb @@ -26,7 +26,7 @@ RSpec.describe UnnestedInFilters::Rewriter do context 'when the given relation has an `IN` predicate' do context 'when there is no index coverage for the used columns' do - let(:relation) { User.where(username: %w(user_1 user_2), state: :active) } + let(:relation) { User.where(username: %w[user_1 user_2], state: :active) } it { is_expected.to be_falsey } end @@ -37,7 +37,7 @@ RSpec.describe UnnestedInFilters::Rewriter do it { is_expected.to be_truthy } context 'when there is an ordering' do - let(:relation) { User.where(state: %w(active blocked banned)).order(order).limit(2) } + let(:relation) { User.where(state: %w[active blocked banned]).order(order).limit(2) } context 'when the order is an Arel node' do let(:order) { { user_type: :desc } } @@ -67,7 +67,7 @@ RSpec.describe UnnestedInFilters::Rewriter do describe '#rewrite' do let(:recorded_queries) { ActiveRecord::QueryRecorder.new { rewriter.rewrite.load } } - let(:relation) { User.where(state: :active, user_type: %i(support_bot alert_bot)).limit(2) } + let(:relation) { User.where(state: :active, user_type: %i[support_bot alert_bot]).limit(2) } let(:users_select) { 'SELECT "users".*' } let(:users_select_with_ignored_columns) { 'SELECT ("users"."\w+", )+("users"."\w+")' } @@ -101,7 +101,7 @@ RSpec.describe UnnestedInFilters::Rewriter do end context 'when the relation has a subquery' do - let(:relation) { User.where(state: User.select(:state), user_type: %i(support_bot alert_bot)).limit(1) } + let(:relation) { User.where(state: User.select(:state), user_type: %i[support_bot alert_bot]).limit(1) } let(:users_unnest) do 'FROM @@ -127,7 +127,7 @@ RSpec.describe UnnestedInFilters::Rewriter do end context 'when there is an order' do - let(:relation) { User.where(state: %w(active blocked banned)).order(order).limit(2) } + let(:relation) { User.where(state: %w[active blocked banned]).order(order).limit(2) } let(:users_unnest) do 'FROM @@ -177,7 +177,7 @@ RSpec.describe UnnestedInFilters::Rewriter do end context 'when the combined attributes include the primary key' do - let(:relation) { User.where(user_type: %i(support_bot alert_bot)).order(id: :desc).limit(2) } + let(:relation) { User.where(user_type: %i[support_bot alert_bot]).order(id: :desc).limit(2) } let(:users_where) do 'FROM |