diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-20 09:07:57 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-20 09:07:57 +0000 |
commit | 7881eb30eaa8b01dbcfe87faa09927c75c7d6e45 (patch) | |
tree | 298bc8d2c62b2f2c29cb8ecbcf3de3eaaa6466d9 /spec/lib | |
parent | 64b66e0cb6d1bfd27abf24e06653f00bddb60597 (diff) | |
download | gitlab-ce-7881eb30eaa8b01dbcfe87faa09927c75c7d6e45.tar.gz |
Add latest changes from gitlab-org/gitlab@12-6-stable-ee
Diffstat (limited to 'spec/lib')
324 files changed, 5852 insertions, 1424 deletions
diff --git a/spec/lib/api/entities/release_spec.rb b/spec/lib/api/entities/release_spec.rb new file mode 100644 index 00000000000..729a69347cb --- /dev/null +++ b/spec/lib/api/entities/release_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Entities::Release do + let_it_be(:project) { create(:project) } + let_it_be(:release) { create(:release, :with_evidence, project: project) } + let(:user) { create(:user) } + let(:entity) { described_class.new(release, current_user: user) } + + subject { entity.as_json } + + describe 'evidence' do + context 'when the current user can download code' do + it 'exposes the evidence sha and the json path' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(user, :download_code, project).and_return(true) + + expect(subject[:evidence_sha]).to eq(release.evidence_sha) + expect(subject[:assets][:evidence_file_path]).to eq( + Gitlab::Routing.url_helpers.evidence_project_release_url(project, + release.tag, + format: :json) + ) + end + end + + context 'when the current user cannot download code' do + it 'does not expose any evidence data' do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(user, :download_code, project).and_return(false) + + expect(subject.keys).not_to include(:evidence_sha) + expect(subject[:assets].keys).not_to include(:evidence_file_path) + end + end + end +end diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb index 040ff1a8ebe..2d5bec2e752 100644 --- a/spec/lib/api/helpers/pagination_spec.rb +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -5,10 +5,16 @@ require 'spec_helper' describe API::Helpers::Pagination do subject { Class.new.include(described_class).new } + let(:expected_result) { double("result", to_a: double) } + let(:relation) { double("relation") } + let(:params) { {} } + + before do + allow(subject).to receive(:params).and_return(params) + end + describe '#paginate' do - let(:relation) { double("relation") } let(:offset_pagination) { double("offset pagination") } - let(:expected_result) { double("result") } it 'delegates to OffsetPagination' do expect(::Gitlab::Pagination::OffsetPagination).to receive(:new).with(subject).and_return(offset_pagination) @@ -19,4 +25,50 @@ describe API::Helpers::Pagination do expect(result).to eq(expected_result) end end + + describe '#paginate_and_retrieve!' do + context 'for offset pagination' do + before do + allow(Gitlab::Pagination::Keyset).to receive(:available?).and_return(false) + end + + it 'delegates to paginate' do + expect(subject).to receive(:paginate).with(relation).and_return(expected_result) + + result = subject.paginate_and_retrieve!(relation) + + expect(result).to eq(expected_result.to_a) + end + end + + context 'for keyset pagination' do + let(:params) { { pagination: 'keyset' } } + let(:request_context) { double('request context') } + + before do + allow(Gitlab::Pagination::Keyset::RequestContext).to receive(:new).with(subject).and_return(request_context) + end + + context 'when keyset pagination is available' do + it 'delegates to KeysetPagination' do + expect(Gitlab::Pagination::Keyset).to receive(:available?).and_return(true) + expect(Gitlab::Pagination::Keyset).to receive(:paginate).with(request_context, relation).and_return(expected_result) + + result = subject.paginate_and_retrieve!(relation) + + expect(result).to eq(expected_result.to_a) + end + end + + context 'when keyset pagination is not available' do + it 'renders a 501 error if keyset pagination isnt available yet' do + expect(Gitlab::Pagination::Keyset).to receive(:available?).with(request_context, relation).and_return(false) + expect(Gitlab::Pagination::Keyset).not_to receive(:paginate) + expect(subject).to receive(:error!).with(/not yet available/, 405) + + subject.paginate_and_retrieve!(relation) + end + end + end + end end diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb index fb26cc417e8..eeeb22abd10 100644 --- a/spec/lib/api/helpers/related_resources_helpers_spec.rb +++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb @@ -43,6 +43,7 @@ describe API::Helpers::RelatedResourcesHelpers do describe '#expose_url' do let(:path) { '/api/v4/awesome_endpoint' } + subject(:url) { helpers.expose_url(path) } def stub_default_url_options(protocol: 'http', host: 'example.com', port: nil, script_name: '') diff --git a/spec/lib/api/support/git_access_actor_spec.rb b/spec/lib/api/support/git_access_actor_spec.rb index 63f5966faea..69637947c79 100644 --- a/spec/lib/api/support/git_access_actor_spec.rb +++ b/spec/lib/api/support/git_access_actor_spec.rb @@ -9,17 +9,26 @@ describe API::Support::GitAccessActor do subject { described_class.new(user: user, key: key) } describe '.from_params' do + let(:key) { create(:key) } + context 'with params that are valid' do it 'returns an instance of API::Support::GitAccessActor' do - params = { key_id: create(:key).id } + params = { key_id: key.id } expect(described_class.from_params(params)).to be_instance_of(described_class) end end context 'with params that are invalid' do - it 'returns nil' do - expect(described_class.from_params({})).to be_nil + it "returns an instance of #{described_class}" do + expect(described_class.from_params({})).to be_instance_of(described_class) + end + end + + context 'when passing an identifier used gitaly' do + it 'finds the user based on an identifier' do + expect(described_class).to receive(:identify).and_call_original + expect(described_class.from_params(identifier: "key-#{key.id}").user).to eq(key.user) end end end diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb index 35594cd2fb8..06ad0557e37 100644 --- a/spec/lib/backup/manager_spec.rb +++ b/spec/lib/backup/manager_spec.rb @@ -326,7 +326,7 @@ describe Backup::Manager do context 'target path' do it 'uses the tar filename by default' do expect_any_instance_of(Fog::Collection).to receive(:create) - .with(hash_including(key: backup_filename)) + .with(hash_including(key: backup_filename, public: false)) .and_return(true) Dir.chdir(Gitlab.config.backup.path) do @@ -338,7 +338,39 @@ describe Backup::Manager do stub_env('DIRECTORY', 'daily') expect_any_instance_of(Fog::Collection).to receive(:create) - .with(hash_including(key: "daily/#{backup_filename}")) + .with(hash_including(key: "daily/#{backup_filename}", public: false)) + .and_return(true) + + Dir.chdir(Gitlab.config.backup.path) do + subject.upload + end + end + end + + context 'with Google provider' do + before do + stub_backup_setting( + upload: { + connection: { + provider: 'Google', + google_storage_access_key_id: 'test-access-id', + google_storage_secret_access_key: 'secret' + }, + remote_directory: 'directory', + multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size, + encryption: nil, + encryption_key: nil, + storage_class: nil + } + ) + + connection = ::Fog::Storage.new(Gitlab.config.backup.upload.connection.symbolize_keys) + connection.directories.create(key: Gitlab.config.backup.upload.remote_directory) + end + + it 'does not attempt to set ACL' do + expect_any_instance_of(Fog::Collection).to receive(:create) + .with(hash_excluding(public: false)) .and_return(true) Dir.chdir(Gitlab.config.backup.path) do diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index 5f120f258cd..32e718d4b3b 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe Backup::Repository do let(:progress) { StringIO.new } let!(:project) { create(:project, :wiki_repo) } + subject { described_class.new(progress) } before do @@ -12,7 +13,9 @@ describe Backup::Repository do allow(progress).to receive(:print) allow(FileUtils).to receive(:mv).and_return(true) - allow_any_instance_of(described_class).to receive(:progress).and_return(progress) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:progress).and_return(progress) + end end describe '#dump' do @@ -47,7 +50,9 @@ describe Backup::Repository do describe 'command failure' do before do - allow_any_instance_of(Gitlab::Shell).to receive(:create_repository).and_return(false) + allow_next_instance_of(Gitlab::Shell) do |instance| + allow(instance).to receive(:create_repository).and_return(false) + end end context 'hashed storage' do diff --git a/spec/lib/backup/uploads_spec.rb b/spec/lib/backup/uploads_spec.rb index 55b69f29812..1f49baeff69 100644 --- a/spec/lib/backup/uploads_spec.rb +++ b/spec/lib/backup/uploads_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Backup::Uploads do let(:progress) { StringIO.new } + subject(:backup) { described_class.new(progress) } describe '#initialize' do diff --git a/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb b/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb new file mode 100644 index 00000000000..317ac7ef854 --- /dev/null +++ b/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::Filter::BroadcastMessageSanitizationFilter do + include FilterSpecHelper + + it_behaves_like 'default whitelist' + + describe 'custom whitelist' do + it_behaves_like 'XSS prevention' + it_behaves_like 'sanitize link' + + subject { filter(exp).to_html } + + context 'allows `a` elements' do + let(:exp) { %q{<a href="/">Link</a>} } + + it { is_expected.to eq(exp) } + end + + context 'allows `br` elements' do + let(:exp) { %q{Hello<br>World} } + + it { is_expected.to eq(exp) } + end + + context 'when `a` elements have `style` attribute' do + let(:whitelisted_style) { 'color: red; border: blue; background: green; padding: 10px; margin: 10px; text-decoration: underline;' } + + context 'allows specific properties' do + let(:exp) { %{<a href="#" style="#{whitelisted_style}">Stylish Link</a>} } + + it { is_expected.to eq(exp) } + end + + it 'disallows other properties in `style` attribute on `a` elements' do + style = [whitelisted_style, 'position: fixed'].join(';') + doc = filter(%{<a href="#" style="#{style}">Stylish Link</a>}) + + expect(doc.at_css('a')['style']).to eq(whitelisted_style) + end + end + + context 'allows `class` on `a` elements' do + let(:exp) { %q{<a href="#" class="btn">Button Link</a>} } + + it { is_expected.to eq(exp) } + end + end +end diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb index 326703eea05..63ec597a0ba 100644 --- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -60,7 +60,9 @@ describe Banzai::Filter::CommitReferenceFilter do end it 'escapes the title attribute' do - allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="}) + allow_next_instance_of(Commit) do |instance| + allow(instance).to receive(:title).and_return(%{"></a>whatever<a title="}) + end doc = reference_filter("See #{reference}") expect(doc.text).to eq "See #{commit.short_id}" diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb index 745b9133529..e2615ea5069 100644 --- a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb @@ -55,11 +55,29 @@ describe Banzai::Filter::InlineMetricsRedactorFilter do it_behaves_like 'a supported metrics dashboard url' end - context 'for an internal non-dashboard url' do - let(:url) { urls.project_url(project) } + context 'the user has requisite permissions' do + let(:user) { create(:user) } + let(:doc) { filter(input, current_user: user) } - it 'leaves the placeholder' do - expect(doc.to_s).to be_empty + before do + project.add_maintainer(user) + end + + context 'for an internal non-dashboard url' do + let(:url) { urls.project_url(project) } + + it 'leaves the placeholder' do + expect(doc.to_s).to be_empty + end + end + + context 'with over 100 embeds' do + let(:embed) { %(<div class="js-render-metrics" data-dashboard-url="#{url}"></div>) } + let(:input) { embed * 150 } + + it 'redacts ill-advised embeds' do + expect(doc.to_s.length).to eq(embed.length * 100) + end end end end diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb index 06df67facf9..d0a43564903 100644 --- a/spec/lib/banzai/filter/markdown_filter_spec.rb +++ b/spec/lib/banzai/filter/markdown_filter_spec.rb @@ -7,13 +7,17 @@ describe Banzai::Filter::MarkdownFilter do describe 'markdown engine from context' do it 'defaults to CommonMark' do - expect_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark).to receive(:render).and_return('test') + expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance| + expect(instance).to receive(:render).and_return('test') + end filter('test') end it 'uses CommonMark' do - expect_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark).to receive(:render).and_return('test') + expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance| + expect(instance).to receive(:render).and_return('test') + end filter('test', { markdown_engine: :common_mark }) end diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index ab0c2c383c5..2fe8c9074df 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -214,7 +214,9 @@ describe Banzai::Filter::MilestoneReferenceFilter do end it 'escapes the name attribute' do - allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="}) + allow_next_instance_of(Milestone) do |instance| + allow(instance).to receive(:title).and_return(%{"></a>whatever<a title="}) + end doc = reference_filter("See #{reference}") @@ -251,7 +253,9 @@ describe Banzai::Filter::MilestoneReferenceFilter do end it 'escapes the name attribute' do - allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="}) + allow_next_instance_of(Milestone) do |instance| + allow(instance).to receive(:title).and_return(%{"></a>whatever<a title="}) + end doc = reference_filter("See #{reference}") @@ -288,7 +292,9 @@ describe Banzai::Filter::MilestoneReferenceFilter do end it 'escapes the name attribute' do - allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="}) + allow_next_instance_of(Milestone) do |instance| + allow(instance).to receive(:title).and_return(%{"></a>whatever<a title="}) + end doc = reference_filter("See #{reference}") diff --git a/spec/lib/banzai/filter/reference_redactor_filter_spec.rb b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb index dc888a47988..9739afd3d57 100644 --- a/spec/lib/banzai/filter/reference_redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb @@ -42,7 +42,9 @@ describe Banzai::Filter::ReferenceRedactorFilter do context 'valid projects' do before do - allow_any_instance_of(Banzai::ReferenceParser::BaseParser).to receive(:can_read_reference?).and_return(true) + allow_next_instance_of(Banzai::ReferenceParser::BaseParser) do |instance| + allow(instance).to receive(:can_read_reference?).and_return(true) + end end it 'allows permitted Project references' do @@ -59,7 +61,9 @@ describe Banzai::Filter::ReferenceRedactorFilter do context 'invalid projects' do before do - allow_any_instance_of(Banzai::ReferenceParser::BaseParser).to receive(:can_read_reference?).and_return(false) + allow_next_instance_of(Banzai::ReferenceParser::BaseParser) do |instance| + allow(instance).to receive(:can_read_reference?).and_return(false) + end end it 'removes unpermitted references' do diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index 371c7a2347c..a17a645d4d0 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -96,21 +96,25 @@ describe Banzai::Filter::RelativeLinkFilter do context 'with a project_wiki' do let(:project_wiki) { double('ProjectWiki') } + include_examples :preserve_unchanged end context 'without a repository' do let(:project) { create(:project) } + include_examples :preserve_unchanged end context 'with an empty repository' do let(:project) { create(:project_empty_repo) } + include_examples :preserve_unchanged end context 'without project repository access' do let(:project) { create(:project, :repository, repository_access_level: ProjectFeature::PRIVATE) } + include_examples :preserve_unchanged end @@ -269,6 +273,7 @@ describe Banzai::Filter::RelativeLinkFilter do context 'when requested path is a file in the repo' do let(:requested_path) { 'doc/api/README.md' } + it 'rebuilds URL relative to the containing directory' do doc = filter(link('users.md')) expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md" @@ -277,6 +282,7 @@ describe Banzai::Filter::RelativeLinkFilter do context 'when requested path is a directory in the repo' do let(:requested_path) { 'doc/api/' } + it 'rebuilds URL relative to the directory' do doc = filter(link('users.md')) expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md" @@ -287,6 +293,7 @@ describe Banzai::Filter::RelativeLinkFilter do let(:ref) { '100%branch' } let(:commit) { project.commit('1b12f15a11fc6e62177bef08f47bc7b5ce50b141') } let(:requested_path) { 'foo/bar/' } + it 'correctly escapes the ref' do doc = filter(link('.gitkeep')) expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/foo/bar/.gitkeep" @@ -316,6 +323,7 @@ describe Banzai::Filter::RelativeLinkFilter do let(:ref) { 'master' } let(:commit) { project.commit('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e') } let(:requested_path) { 'with space/' } + it 'does not escape the space twice' do doc = filter(link('README.md')) expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/with%20space/README.md" @@ -328,7 +336,9 @@ describe Banzai::Filter::RelativeLinkFilter do end context 'with a valid ref' do - let(:commit) { nil } # force filter to use ref instead of commit + # force filter to use ref instead of commit + let(:commit) { nil } + include_examples :valid_repository end diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb index 8a4b819e4d6..607dc3fda47 100644 --- a/spec/lib/banzai/filter/sanitization_filter_spec.rb +++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb @@ -5,48 +5,12 @@ require 'spec_helper' describe Banzai::Filter::SanitizationFilter do include FilterSpecHelper - describe 'default whitelist' do - it 'sanitizes tags that are not whitelisted' do - act = %q{<textarea>no inputs</textarea> and <blink>no blinks</blink>} - exp = 'no inputs and no blinks' - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes tag attributes' do - act = %q{<a href="http://example.com/bar.html" onclick="bar">Text</a>} - exp = %q{<a href="http://example.com/bar.html">Text</a>} - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes javascript in attributes' do - act = %q(<a href="javascript:alert('foo')">Text</a>) - exp = '<a>Text</a>' - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes mixed-cased javascript in attributes' do - act = %q(<a href="javaScript:alert('foo')">Text</a>) - exp = '<a>Text</a>' - expect(filter(act).to_html).to eq exp - end - - it 'allows whitelisted HTML tags from the user' do - exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>" - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes `class` attribute on any element' do - act = %q{<strong class="foo">Strong</strong>} - expect(filter(act).to_html).to eq %q{<strong>Strong</strong>} - end - - it 'sanitizes `id` attribute on any element' do - act = %q{<em id="foo">Emphasis</em>} - expect(filter(act).to_html).to eq %q{<em>Emphasis</em>} - end - end + it_behaves_like 'default whitelist' describe 'custom whitelist' do + it_behaves_like 'XSS prevention' + it_behaves_like 'sanitize link' + it 'customizes the whitelist only once' do instance = described_class.new('Foo') control_count = instance.whitelist[:transformers].size @@ -167,142 +131,6 @@ describe Banzai::Filter::SanitizationFilter do expect(filter(html).to_html).to eq(output) end - it 'removes `rel` attribute from `a` elements' do - act = %q{<a href="#" rel="nofollow">Link</a>} - exp = %q{<a href="#">Link</a>} - - expect(filter(act).to_html).to eq exp - end - - # Adapted from the Sanitize test suite: http://git.io/vczrM - protocols = { - 'protocol-based JS injection: simple, no spaces' => { - input: '<a href="javascript:alert(\'XSS\');">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: simple, spaces before' => { - input: '<a href="javascript :alert(\'XSS\');">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: simple, spaces after' => { - input: '<a href="javascript: alert(\'XSS\');">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: simple, spaces before and after' => { - input: '<a href="javascript : alert(\'XSS\');">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: preceding colon' => { - input: '<a href=":javascript:alert(\'XSS\');">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: UTF-8 encoding' => { - input: '<a href="javascript:">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: long UTF-8 encoding' => { - input: '<a href="javascript:">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: long UTF-8 encoding without semicolons' => { - input: '<a href=javascript:alert('XSS')>foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: hex encoding' => { - input: '<a href="javascript:">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: long hex encoding' => { - input: '<a href="javascript:">foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: hex encoding without semicolons' => { - input: '<a href=javascript:alert('XSS')>foo</a>', - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: null char' => { - input: "<a href=java\0script:alert(\"XSS\")>foo</a>", - output: '<a href="java"></a>' - }, - - 'protocol-based JS injection: invalid URL char' => { - input: '<img src=java\script:alert("XSS")>', - output: '<img>' - }, - - 'protocol-based JS injection: Unicode' => { - input: %Q(<a href="\u0001java\u0003script:alert('XSS')">foo</a>), - output: '<a>foo</a>' - }, - - 'protocol-based JS injection: spaces and entities' => { - input: '<a href="  javascript:alert(\'XSS\');">foo</a>', - output: '<a href="">foo</a>' - }, - - 'protocol whitespace' => { - input: '<a href=" http://example.com/"></a>', - output: '<a href="http://example.com/"></a>' - } - } - - protocols.each do |name, data| - it "disallows #{name}" do - doc = filter(data[:input]) - - expect(doc.to_html).to eq data[:output] - end - end - - it 'disallows data links' do - input = '<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">XSS</a>' - output = filter(input) - - expect(output.to_html).to eq '<a>XSS</a>' - end - - it 'disallows vbscript links' do - input = '<a href="vbscript:alert(document.domain)">XSS</a>' - output = filter(input) - - expect(output.to_html).to eq '<a>XSS</a>' - end - - it 'disallows invalid URIs' do - expect(Addressable::URI).to receive(:parse).with('foo://example.com') - .and_raise(Addressable::URI::InvalidURIError) - - input = '<a href="foo://example.com">Foo</a>' - output = filter(input) - - expect(output.to_html).to eq '<a>Foo</a>' - end - - it 'allows non-standard anchor schemes' do - exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>} - act = filter(exp) - - expect(act.to_html).to eq exp - end - - it 'allows relative links' do - exp = %q{<a href="foo/bar.md">foo/bar.md</a>} - act = filter(exp) - - expect(act.to_html).to eq exp - end - it 'allows the `data-sourcepos` attribute globally' do exp = %q{<p data-sourcepos="1:1-1:10">foo/bar.md</p>} act = filter(exp) diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index f220ccecee1..5a844fb61e3 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -92,7 +92,9 @@ describe Banzai::Filter::SyntaxHighlightFilter do context "when Rouge lexing fails" do before do - allow_any_instance_of(Rouge::Lexers::Ruby).to receive(:stream_tokens).and_raise(StandardError) + allow_next_instance_of(Rouge::Lexers::Ruby) do |instance| + allow(instance).to receive(:stream_tokens).and_raise(StandardError) + end end it "highlights as plaintext" do @@ -106,7 +108,9 @@ describe Banzai::Filter::SyntaxHighlightFilter do context "when Rouge lexing fails after a retry" do before do - allow_any_instance_of(Rouge::Lexers::PlainText).to receive(:stream_tokens).and_raise(StandardError) + allow_next_instance_of(Rouge::Lexers::PlainText) do |instance| + allow(instance).to receive(:stream_tokens).and_raise(StandardError) + end end it "does not add highlighting classes" do diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index a523608fa50..aef11775e60 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -25,7 +25,9 @@ describe Banzai::ObjectRenderer do end it 'calls Banzai::ReferenceRedactor to perform redaction' do - expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original + expect_next_instance_of(Banzai::ReferenceRedactor) do |instance| + expect(instance).to receive(:redact).and_call_original + end renderer.render([object], :note) end @@ -85,7 +87,9 @@ describe Banzai::ObjectRenderer do end it 'calls Banzai::ReferenceRedactor to perform redaction' do - expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original + expect_next_instance_of(Banzai::ReferenceRedactor) do |instance| + expect(instance).to receive(:redact).and_call_original + end renderer.render([cacheless_thing], :title) end diff --git a/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb b/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb new file mode 100644 index 00000000000..9832b132b58 --- /dev/null +++ b/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::Pipeline::BroadcastMessagePipeline do + before do + stub_commonmark_sourcepos_disabled + end + + subject { described_class.to_html(exp, project: spy) } + + context "allows `a` elements" do + let(:exp) { "<a>Link</a>" } + + it { is_expected.to eq("<p>#{exp}</p>") } + end + + context "allows `br` elements" do + let(:exp) { "Hello<br>World" } + + it { is_expected.to eq("<p>#{exp}</p>") } + end +end diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index 26f2b0b0acf..e1814ea403e 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -72,14 +72,14 @@ describe Banzai::Pipeline::WikiPipeline do markdown = "[Page](./page)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/page\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/page\"") end it "rewrites file links to be at the scope of the current directory" do markdown = "[Link to Page](./page.md)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/page.md\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/page.md\"") end end @@ -88,14 +88,14 @@ describe Banzai::Pipeline::WikiPipeline do markdown = "[Link to Page](../page)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/page\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/page\"") end it "rewrites file links to be at the scope of the parent directory" do markdown = "[Link to Page](../page.md)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/page.md\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/page.md\"") end end @@ -104,14 +104,14 @@ describe Banzai::Pipeline::WikiPipeline do markdown = "[Link to Page](./subdirectory/page)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/subdirectory/page\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/subdirectory/page\"") end it "rewrites file links to be at the scope of the sub-directory" do markdown = "[Link to Page](./subdirectory/page.md)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/subdirectory/page.md\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/subdirectory/page.md\"") end end @@ -120,35 +120,35 @@ describe Banzai::Pipeline::WikiPipeline do markdown = "[Link to Page](page)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/page\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/page\"") end it 'rewrites non-file links (with spaces) to be at the scope of the wiki root' do markdown = "[Link to Page](page slug)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/page%20slug\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/page%20slug\"") end it "rewrites file links to be at the scope of the current directory" do markdown = "[Link to Page](page.md)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/page.md\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/page.md\"") end it 'rewrites links with anchor' do markdown = '[Link to Header](start-page#title)' output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/start-page#title\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/start-page#title\"") end it 'rewrites links (with spaces) with anchor' do markdown = '[Link to Header](start page#title)' output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/start%20page#title\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/start%20page#title\"") end end @@ -157,14 +157,14 @@ describe Banzai::Pipeline::WikiPipeline do markdown = "[Link to Page](/page)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/page\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/page\"") end it 'rewrites file links to be at the scope of the wiki root' do markdown = "[Link to Page](/page.md)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/page.md\"") + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/page.md\"") end end end @@ -270,28 +270,28 @@ describe Banzai::Pipeline::WikiPipeline do markdown = "![video_file](video_file_name.mp4)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/video_file_name.mp4"') + expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/video_file_name.mp4"') end it 'rewrites and replaces video links names with white spaces to %20' do markdown = "![video file](video file name.mp4)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/video%20file%20name.mp4"') + expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/video%20file%20name.mp4"') end it 'generates audio html structure' do markdown = "![audio_file](audio_file_name.wav)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include('<audio src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/audio_file_name.wav"') + expect(output).to include('<audio src="/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/audio_file_name.wav"') end it 'rewrites and replaces audio links names with white spaces to %20' do markdown = "![audio file](audio file name.wav)" output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) - expect(output).to include('<audio src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/audio%20file%20name.wav"') + expect(output).to include('<audio src="/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/audio%20file%20name.wav"') end end end diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb index eac1cf16a8f..7f7c750fe74 100644 --- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb @@ -35,8 +35,9 @@ describe Banzai::ReferenceParser::CommitParser do it 'returns an Array of commits' do commit = double(:commit) - allow_any_instance_of(Project).to receive(:valid_repo?) - .and_return(true) + allow_next_instance_of(Project) do |instance| + allow(instance).to receive(:valid_repo?).and_return(true) + end expect(subject).to receive(:find_commits) .with(project, ['123']) @@ -46,8 +47,9 @@ describe Banzai::ReferenceParser::CommitParser do end it 'returns an empty Array when the commit could not be found' do - allow_any_instance_of(Project).to receive(:valid_repo?) - .and_return(true) + allow_next_instance_of(Project) do |instance| + allow(instance).to receive(:valid_repo?).and_return(true) + end expect(subject).to receive(:find_commits) .with(project, ['123']) @@ -57,8 +59,9 @@ describe Banzai::ReferenceParser::CommitParser do end it 'skips projects without valid repositories' do - allow_any_instance_of(Project).to receive(:valid_repo?) - .and_return(false) + allow_next_instance_of(Project) do |instance| + allow(instance).to receive(:valid_repo?).and_return(false) + end expect(subject.referenced_by([link])).to eq([]) end @@ -66,8 +69,9 @@ describe Banzai::ReferenceParser::CommitParser do context 'when the link does not have a data-commit attribute' do it 'returns an empty Array' do - allow_any_instance_of(Project).to receive(:valid_repo?) - .and_return(true) + allow_next_instance_of(Project) do |instance| + allow(instance).to receive(:valid_repo?).and_return(true) + end expect(subject.referenced_by([link])).to eq([]) end @@ -76,8 +80,9 @@ describe Banzai::ReferenceParser::CommitParser do context 'when the link does not have a data-project attribute' do it 'returns an empty Array' do - allow_any_instance_of(Project).to receive(:valid_repo?) - .and_return(true) + allow_next_instance_of(Project) do |instance| + allow(instance).to receive(:valid_repo?).and_return(true) + end expect(subject.referenced_by([link])).to eq([]) end diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb index a925d294b1b..ac321aca5e9 100644 --- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb @@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::IssueParser do let(:user) { create(:user) } let(:issue) { create(:issue, project: project) } let(:link) { empty_html_link } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/mentioned_users_by_group_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb index 99d607629eb..30b99f3eda7 100644 --- a/spec/lib/banzai/reference_parser/mentioned_users_by_group_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Banzai::ReferenceParser::MentionedUsersByGroupParser do +describe Banzai::ReferenceParser::MentionedGroupParser do include ReferenceParserHelpers let(:group) { create(:group, :private) } diff --git a/spec/lib/banzai/reference_parser/mentioned_users_by_project_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb index 155f2189d9e..154f7c4dc36 100644 --- a/spec/lib/banzai/reference_parser/mentioned_users_by_project_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Banzai::ReferenceParser::MentionedUsersByProjectParser do +describe Banzai::ReferenceParser::MentionedProjectParser do include ReferenceParserHelpers let(:group) { create(:group, :private) } diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb index 05dc1cb4d2d..6581ed0d7c3 100644 --- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb @@ -200,6 +200,7 @@ describe Banzai::ReferenceParser::SnippetParser do describe '#referenced_by' do let(:snippet) { create(:snippet, project: project) } + describe 'when the link has a data-snippet attribute' do context 'using an existing snippet ID' do it 'returns an Array of snippets' do diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb index 931fb1e3953..71d2e1de3b6 100644 --- a/spec/lib/banzai/reference_parser/user_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb @@ -19,15 +19,23 @@ describe Banzai::ReferenceParser::UserParser do link['data-group'] = project.group.id.to_s end - it 'returns the users of the group' do - create(:group_member, group: group, user: user) - - expect(subject.referenced_by([link])).to eq([user]) - end - it 'returns an empty Array when the group has no users' do expect(subject.referenced_by([link])).to eq([]) end + + context 'when group has members' do + let!(:group_member) { create(:group_member, group: group, user: user) } + + it 'returns the users of the group' do + expect(subject.referenced_by([link])).to eq([user]) + end + + it 'returns an empty Array when the group has mentions disabled' do + group.update!(mentions_disabled: true) + + expect(subject.referenced_by([link])).to eq([]) + end + end end context 'using a non-existing group ID' do diff --git a/spec/lib/banzai/reference_redactor_spec.rb b/spec/lib/banzai/reference_redactor_spec.rb index c30a194a0b3..0dec6395fb3 100644 --- a/spec/lib/banzai/reference_redactor_spec.rb +++ b/spec/lib/banzai/reference_redactor_spec.rb @@ -36,6 +36,7 @@ describe Banzai::ReferenceRedactor do context 'when data-original attribute provided' do let(:original_content) { '<code>foo</code>' } + it 'replaces redacted reference with original content' do doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-original='#{original_content}'>bar</a>") redactor.redact([doc]) @@ -173,10 +174,11 @@ describe Banzai::ReferenceRedactor do doc = Nokogiri::HTML.fragment('<a data-reference-type="issue"></a>') node = doc.children[0] - expect_any_instance_of(Banzai::ReferenceParser::IssueParser) - .to receive(:nodes_visible_to_user) - .with(user, [node]) - .and_return([node]) + expect_next_instance_of(Banzai::ReferenceParser::IssueParser) do |instance| + expect(instance).to receive(:nodes_visible_to_user) + .with(user, [node]) + .and_return([node]) + end expect(redactor.nodes_visible_to_user([node])).to eq(Set.new([node])) end diff --git a/spec/lib/bitbucket/connection_spec.rb b/spec/lib/bitbucket/connection_spec.rb index ec8eac232cd..5aca93767dc 100644 --- a/spec/lib/bitbucket/connection_spec.rb +++ b/spec/lib/bitbucket/connection_spec.rb @@ -4,12 +4,16 @@ require 'spec_helper' describe Bitbucket::Connection do before do - allow_any_instance_of(described_class).to receive(:provider).and_return(double(app_id: '', app_secret: '')) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:provider).and_return(double(app_id: '', app_secret: '')) + end end describe '#get' do it 'calls OAuth2::AccessToken::get' do - expect_any_instance_of(OAuth2::AccessToken).to receive(:get).and_return(double(parsed: true)) + expect_next_instance_of(OAuth2::AccessToken) do |instance| + expect(instance).to receive(:get).and_return(double(parsed: true)) + end connection = described_class.new({}) @@ -19,7 +23,9 @@ describe Bitbucket::Connection do describe '#expired?' do it 'calls connection.expired?' do - expect_any_instance_of(OAuth2::AccessToken).to receive(:expired?).and_return(true) + expect_next_instance_of(OAuth2::AccessToken) do |instance| + expect(instance).to receive(:expired?).and_return(true) + end expect(described_class.new({}).expired?).to be_truthy end @@ -29,7 +35,9 @@ describe Bitbucket::Connection do it 'calls connection.refresh!' do response = double(token: nil, expires_at: nil, expires_in: nil, refresh_token: nil) - expect_any_instance_of(OAuth2::AccessToken).to receive(:refresh!).and_return(response) + expect_next_instance_of(OAuth2::AccessToken) do |instance| + expect(instance).to receive(:refresh!).and_return(response) + end described_class.new({}).refresh! end diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb index ac3221ecab7..963e1d5b8e0 100644 --- a/spec/lib/constraints/project_url_constrainer_spec.rb +++ b/spec/lib/constraints/project_url_constrainer_spec.rb @@ -37,11 +37,13 @@ describe Constraints::ProjectUrlConstrainer do context 'and is a GET request' do let(:request) { build_request(namespace.full_path, old_project_path) } + it { expect(subject.matches?(request)).to be_truthy } end context 'and is NOT a GET request' do let(:request) { build_request(namespace.full_path, old_project_path, 'POST') } + it { expect(subject.matches?(request)).to be_falsey } end end diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb index 15ef930420c..4f665def3bf 100644 --- a/spec/lib/constraints/user_url_constrainer_spec.rb +++ b/spec/lib/constraints/user_url_constrainer_spec.rb @@ -24,11 +24,13 @@ describe Constraints::UserUrlConstrainer do context 'and is a GET request' do let(:request) { build_request(redirect_route.path) } + it { expect(subject.matches?(request)).to be_truthy } end context 'and is NOT a GET request' do let(:request) { build_request(redirect_route.path, 'POST') } + it { expect(subject.matches?(request)).to be_falsey } end end diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb index 3115dfe852f..9447112e4a8 100644 --- a/spec/lib/container_registry/tag_spec.rb +++ b/spec/lib/container_registry/tag_spec.rb @@ -97,6 +97,29 @@ describe ContainerRegistry::Tag do end end + context 'image is a helm chart' do + before do + stub_request(:get, 'http://registry.gitlab/v2/group/test/manifests/tag') + .with(headers: headers) + .to_return( + status: 200, + body: File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest_helm.json'), + headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v2+json' }) + + stub_request(:get, 'http://registry.gitlab/v2/group/test/blobs/sha256:65a07b841ece031e6d0ec5eb948eacb17aa6d7294cdeb01d5348e86242951487') + .with(headers: { 'Accept' => 'application/vnd.cncf.helm.config.v1+json' }) + .to_return( + status: 200, + body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob_helm.json')) + end + + context '#created_at' do + subject { tag.created_at } + + it { is_expected.to be_nil } + end + end + context 'schema v2' do before do stub_request(:get, 'http://registry.gitlab/v2/group/test/manifests/tag') diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index ffe7584a019..861ef79b2f8 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -88,7 +88,9 @@ describe ExtractsPath do context 'subclass overrides get_id' do it 'uses ref returned by get_id' do - allow_any_instance_of(self.class).to receive(:get_id) { '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } + allow_next_instance_of(self.class) do |instance| + allow(instance).to receive(:get_id) { '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } + end assign_ref_vars diff --git a/spec/lib/gitaly/server_spec.rb b/spec/lib/gitaly/server_spec.rb index 12dfad6698d..184d049d1fb 100644 --- a/spec/lib/gitaly/server_spec.rb +++ b/spec/lib/gitaly/server_spec.rb @@ -65,4 +65,26 @@ describe Gitaly::Server do end end end + + describe '#expected_version?' do + using RSpec::Parameterized::TableSyntax + + where(:expected_version, :server_version, :result) do + '1.1.1' | '1.1.1' | true + '1.1.2' | '1.1.1' | false + '1.73.0' | '1.73.0-18-gf756ebe2' | false + '594c3ea3e0e5540e5915bd1c49713a0381459dd6' | '1.55.6-45-g594c3ea3' | true + '594c3ea3e0e5540e5915bd1c49713a0381459dd6' | '1.55.6-46-gabc123ff' | false + '594c3ea3e0e5540e5915bd1c49713a0381459dd6' | '1.55.6' | false + end + + with_them do + it do + allow(Gitlab::GitalyClient).to receive(:expected_server_version).and_return(expected_version) + allow(server).to receive(:server_version).and_return(server_version) + + expect(server.expected_version?).to eq(result) + end + end + end end diff --git a/spec/lib/gitlab/action_rate_limiter_spec.rb b/spec/lib/gitlab/application_rate_limiter_spec.rb index 8b510a475d2..f1a0163d91c 100644 --- a/spec/lib/gitlab/action_rate_limiter_spec.rb +++ b/spec/lib/gitlab/application_rate_limiter_spec.rb @@ -2,30 +2,40 @@ require 'spec_helper' -describe Gitlab::ActionRateLimiter, :clean_gitlab_redis_cache do +describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_cache do let(:redis) { double('redis') } let(:user) { create(:user) } let(:project) { create(:project) } + let(:rate_limits) do + { + test_action: { + threshold: 1, + interval: 2.minutes + } + } + end + let(:key) { rate_limits.keys[0] } - subject { described_class.new(action: :test_action, expiry_time: 100) } + subject { described_class } before do allow(Gitlab::Redis::Cache).to receive(:with).and_yield(redis) + allow(described_class).to receive(:rate_limits).and_return(rate_limits) end shared_examples 'action rate limiter' do it 'increases the throttle count and sets the expiration time' do expect(redis).to receive(:incr).with(cache_key).and_return(1) - expect(redis).to receive(:expire).with(cache_key, 100) + expect(redis).to receive(:expire).with(cache_key, 120) - expect(subject.throttled?(key, 1)).to be_falsy + expect(subject.throttled?(key, scope: scope)).to be_falsy end it 'returns true if the key is throttled' do expect(redis).to receive(:incr).with(cache_key).and_return(2) expect(redis).not_to receive(:expire) - expect(subject.throttled?(key, 1)).to be_truthy + expect(subject.throttled?(key, scope: scope)).to be_truthy end context 'when throttling is disabled' do @@ -33,16 +43,16 @@ describe Gitlab::ActionRateLimiter, :clean_gitlab_redis_cache do expect(redis).not_to receive(:incr) expect(redis).not_to receive(:expire) - expect(subject.throttled?(key, 0)).to be_falsy + expect(subject.throttled?(key, scope: scope, threshold: 0)).to be_falsy end end end context 'when the key is an array of only ActiveRecord models' do - let(:key) { [user, project] } + let(:scope) { [user, project] } let(:cache_key) do - "action_rate_limiter:test_action:user:#{user.id}:project:#{project.id}" + "application_rate_limiter:test_action:user:#{user.id}:project:#{project.id}" end it_behaves_like 'action rate limiter' @@ -52,10 +62,10 @@ describe Gitlab::ActionRateLimiter, :clean_gitlab_redis_cache do let(:project) { create(:project, :public, :repository) } let(:commit) { project.repository.commit } let(:path) { 'app/controllers/groups_controller.rb' } - let(:key) { [project, commit, path] } + let(:scope) { [project, commit, path] } let(:cache_key) do - "action_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}:#{path}" + "application_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}:#{path}" end it_behaves_like 'action rate limiter' @@ -72,7 +82,7 @@ describe Gitlab::ActionRateLimiter, :clean_gitlab_redis_cache do let(:base_attributes) do { - message: 'Action_Rate_Limiter_Request', + message: 'Application_Rate_Limiter_Request', env: type, remote_ip: '127.0.0.1', request_method: 'GET', diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 415a6e62374..38ec04ebe81 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -451,6 +451,7 @@ module Gitlab context 'with path to a binary file' do let(:blob) { fake_blob(path: 'dk.png', binary: true) } + include_examples :invalid_include end @@ -500,6 +501,7 @@ module Gitlab context 'without a commit (only ref)' do let(:commit) { nil } + include_examples :valid_include end end @@ -511,6 +513,7 @@ module Gitlab context 'without a commit (only ref)' do let(:commit) { nil } + include_examples :valid_include end end diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb index dd8070c1240..82ff8e7f76c 100644 --- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/auth_finders_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Gitlab::Auth::UserAuthFinders do +describe Gitlab::Auth::AuthFinders do include described_class let(:user) { create(:user) } @@ -116,9 +116,9 @@ describe Gitlab::Auth::UserAuthFinders do end describe '#find_user_from_static_object_token' do - context 'when request format is archive' do + shared_examples 'static object request' do before do - env['SCRIPT_NAME'] = 'project/-/archive/master.zip' + env['SCRIPT_NAME'] = path end context 'when token header param is present' do @@ -126,7 +126,7 @@ describe Gitlab::Auth::UserAuthFinders do it 'returns the user' do request.headers['X-Gitlab-Static-Object-Token'] = user.static_object_token - expect(find_user_from_static_object_token(:archive)).to eq(user) + expect(find_user_from_static_object_token(format)).to eq(user) end end @@ -134,7 +134,7 @@ describe Gitlab::Auth::UserAuthFinders do it 'returns the user' do request.headers['X-Gitlab-Static-Object-Token'] = 'foobar' - expect { find_user_from_static_object_token(:archive) }.to raise_error(Gitlab::Auth::UnauthorizedError) + expect { find_user_from_static_object_token(format) }.to raise_error(Gitlab::Auth::UnauthorizedError) end end end @@ -144,7 +144,7 @@ describe Gitlab::Auth::UserAuthFinders do it 'returns the user' do set_param(:token, user.static_object_token) - expect(find_user_from_static_object_token(:archive)).to eq(user) + expect(find_user_from_static_object_token(format)).to eq(user) end end @@ -152,13 +152,27 @@ describe Gitlab::Auth::UserAuthFinders do it 'returns the user' do set_param(:token, 'foobar') - expect { find_user_from_static_object_token(:archive) }.to raise_error(Gitlab::Auth::UnauthorizedError) + expect { find_user_from_static_object_token(format) }.to raise_error(Gitlab::Auth::UnauthorizedError) end end end end - context 'when request format is not archive' do + context 'when request format is archive' do + it_behaves_like 'static object request' do + let_it_be(:path) { 'project/-/archive/master.zip' } + let_it_be(:format) { :archive } + end + end + + context 'when request format is blob' do + it_behaves_like 'static object request' do + let_it_be(:path) { 'project/raw/master/README.md' } + let_it_be(:format) { :blob } + end + end + + context 'when request format is not archive nor blob' do before do env['script_name'] = 'url' end @@ -182,13 +196,13 @@ describe Gitlab::Auth::UserAuthFinders do context 'when validate_access_token! returns valid' do it 'returns user' do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect(find_user_from_access_token).to eq user end it 'returns exception if token has no user' do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token allow_any_instance_of(PersonalAccessToken).to receive(:user).and_return(nil) expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError) @@ -214,7 +228,7 @@ describe Gitlab::Auth::UserAuthFinders do let(:personal_access_token) { create(:personal_access_token, user: user) } before do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token end it 'returns exception if token has no user' do @@ -265,7 +279,7 @@ describe Gitlab::Auth::UserAuthFinders do context 'passed as header' do it 'returns token if valid personal_access_token' do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect(find_personal_access_token).to eq personal_access_token end @@ -273,7 +287,7 @@ describe Gitlab::Auth::UserAuthFinders do context 'passed as param' do it 'returns token if valid personal_access_token' do - set_param(Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_PARAM, personal_access_token.token) + set_param(described_class::PRIVATE_TOKEN_PARAM, personal_access_token.token) expect(find_personal_access_token).to eq personal_access_token end @@ -284,7 +298,7 @@ describe Gitlab::Auth::UserAuthFinders do end it 'returns exception if invalid personal_access_token' do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = 'invalid_token' + env[described_class::PRIVATE_TOKEN_HEADER] = 'invalid_token' expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError) end @@ -321,6 +335,72 @@ describe Gitlab::Auth::UserAuthFinders do end end + describe '#find_user_from_basic_auth_job' do + def basic_http_auth(username, password) + ActionController::HttpAuthentication::Basic.encode_credentials(username, password) + end + + def set_auth(username, password) + env['HTTP_AUTHORIZATION'] = basic_http_auth(username, password) + end + + subject { find_user_from_basic_auth_job } + + context 'when the request does not have AUTHORIZATION header' do + it { is_expected.to be_nil } + end + + context 'with wrong credentials' do + it 'returns nil without user and password' do + set_auth(nil, nil) + + is_expected.to be_nil + end + + it 'returns nil without password' do + set_auth('some-user', nil) + + is_expected.to be_nil + end + + it 'returns nil without user' do + set_auth(nil, 'password') + + is_expected.to be_nil + end + + it 'returns nil without CI username' do + set_auth('user', 'password') + + is_expected.to be_nil + end + end + + context 'with CI username' do + let(:username) { ::Ci::Build::CI_REGISTRY_USER } + let(:user) { create(:user) } + let(:build) { create(:ci_build, user: user) } + + it 'returns nil without password' do + set_auth(username, nil) + + is_expected.to be_nil + end + + it 'returns user with valid token' do + set_auth(username, build.token) + + is_expected.to eq user + end + + it 'raises error with invalid token' do + set_auth(username, 'token') + + expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) + end + end + end + describe '#validate_access_token!' do let(:personal_access_token) { create(:personal_access_token, user: user) } @@ -365,4 +445,58 @@ describe Gitlab::Auth::UserAuthFinders do end end end + + describe '#find_runner_from_token' do + let(:runner) { create(:ci_runner) } + + context 'with API requests' do + before do + env['SCRIPT_NAME'] = '/api/endpoint' + end + + it 'returns the runner if token is valid' do + set_param(:token, runner.token) + + expect(find_runner_from_token).to eq(runner) + end + + it 'returns nil if token is not present' do + expect(find_runner_from_token).to be_nil + end + + it 'returns nil if token is blank' do + set_param(:token, '') + + expect(find_runner_from_token).to be_nil + end + + it 'returns exception if invalid token' do + set_param(:token, 'invalid_token') + + expect { find_runner_from_token }.to raise_error(Gitlab::Auth::UnauthorizedError) + end + end + + context 'without API requests' do + before do + env['SCRIPT_NAME'] = 'url.ics' + end + + it 'returns nil if token is valid' do + set_param(:token, runner.token) + + expect(find_runner_from_token).to be_nil + end + + it 'returns nil if token is blank' do + expect(find_runner_from_token).to be_nil + end + + it 'returns nil if invalid token' do + set_param(:token, 'invalid_token') + + expect(find_runner_from_token).to be_nil + end + end + end end diff --git a/spec/lib/gitlab/auth/current_user_mode_spec.rb b/spec/lib/gitlab/auth/current_user_mode_spec.rb index b93d460cf48..3b3db0f7315 100644 --- a/spec/lib/gitlab/auth/current_user_mode_spec.rb +++ b/spec/lib/gitlab/auth/current_user_mode_spec.rb @@ -62,69 +62,90 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do context 'when the user is an admin' do let(:user) { build(:user, :admin) } - it 'is false by default' do - expect(subject.admin_mode?).to be(false) - end - - it 'cannot be enabled with an invalid password' do - subject.enable_admin_mode!(password: nil) - - expect(subject.admin_mode?).to be(false) - end + context 'when admin mode not requested' do + it 'is false by default' do + expect(subject.admin_mode?).to be(false) + end - it 'can be enabled with a valid password' do - subject.enable_admin_mode!(password: user.password) + it 'raises exception if we try to enable it' do + expect do + subject.enable_admin_mode!(password: user.password) + end.to raise_error(::Gitlab::Auth::CurrentUserMode::NotRequestedError) - expect(subject.admin_mode?).to be(true) + expect(subject.admin_mode?).to be(false) + end end - it 'can be disabled' do - subject.enable_admin_mode!(password: user.password) - subject.disable_admin_mode! - - expect(subject.admin_mode?).to be(false) - end + context 'when admin mode requested first' do + before do + subject.request_admin_mode! + end - it 'will expire in the future' do - subject.enable_admin_mode!(password: user.password) - expect(subject.admin_mode?).to be(true), 'admin mode is not active in the present' + it 'is false by default' do + expect(subject.admin_mode?).to be(false) + end - Timecop.freeze(Gitlab::Auth::CurrentUserMode::MAX_ADMIN_MODE_TIME.from_now) do - # in the future this will be a new request, simulate by clearing the RequestStore - Gitlab::SafeRequestStore.clear! + it 'cannot be enabled with an invalid password' do + subject.enable_admin_mode!(password: nil) - expect(subject.admin_mode?).to be(false), 'admin mode did not expire in the future' + expect(subject.admin_mode?).to be(false) end - end - context 'skipping password validation' do it 'can be enabled with a valid password' do - subject.enable_admin_mode!(password: user.password, skip_password_validation: true) + subject.enable_admin_mode!(password: user.password) expect(subject.admin_mode?).to be(true) end - it 'can be enabled with an invalid password' do - subject.enable_admin_mode!(skip_password_validation: true) + it 'can be disabled' do + subject.enable_admin_mode!(password: user.password) + subject.disable_admin_mode! - expect(subject.admin_mode?).to be(true) + expect(subject.admin_mode?).to be(false) end - end - context 'with two independent sessions' do - let(:another_session) { {} } - let(:another_subject) { described_class.new(user) } + it 'will expire in the future' do + subject.enable_admin_mode!(password: user.password) + expect(subject.admin_mode?).to be(true), 'admin mode is not active in the present' - before do - allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session, another_session]) + Timecop.freeze(Gitlab::Auth::CurrentUserMode::MAX_ADMIN_MODE_TIME.from_now) do + # in the future this will be a new request, simulate by clearing the RequestStore + Gitlab::SafeRequestStore.clear! + + expect(subject.admin_mode?).to be(false), 'admin mode did not expire in the future' + end end - it 'can be enabled in one and seen in the other' do - Gitlab::Session.with_session(another_session) do - another_subject.enable_admin_mode!(password: user.password) + context 'skipping password validation' do + it 'can be enabled with a valid password' do + subject.enable_admin_mode!(password: user.password, skip_password_validation: true) + + expect(subject.admin_mode?).to be(true) end - expect(subject.admin_mode?).to be(true) + it 'can be enabled with an invalid password' do + subject.enable_admin_mode!(skip_password_validation: true) + + expect(subject.admin_mode?).to be(true) + end + end + + context 'with two independent sessions' do + let(:another_session) { {} } + let(:another_subject) { described_class.new(user) } + + before do + allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session, another_session]) + end + + it 'can be enabled in one and seen in the other' do + Gitlab::Session.with_session(another_session) do + another_subject.request_admin_mode! + another_subject.enable_admin_mode!(password: user.password) + end + + expect(subject.admin_mode?).to be(true) + end end end end @@ -134,16 +155,28 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do let(:user) { build(:user, :admin) } it 'creates a timestamp in the session' do + subject.request_admin_mode! subject.enable_admin_mode!(password: user.password) expect(session).to include(expected_session_entry(be_within(1.second).of Time.now)) end end + describe '#enable_sessionless_admin_mode!' do + let(:user) { build(:user, :admin) } + + it 'enabled admin mode without password' do + subject.enable_sessionless_admin_mode! + + expect(subject.admin_mode?).to be(true) + end + end + describe '#disable_admin_mode!' do let(:user) { build(:user, :admin) } it 'sets the session timestamp to nil' do + subject.request_admin_mode! subject.disable_admin_mode! expect(session).to include(expected_session_entry(be_nil)) diff --git a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb index 8d6bf45ab30..aea1b2921b6 100644 --- a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb +++ b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb @@ -62,4 +62,36 @@ describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_caching do it_behaves_like 'whitelisted IPs' end end + + shared_examples 'skips the rate limiter' do + it 'does not call Rack::Attack::Allow2Ban.reset!' do + expect(Rack::Attack::Allow2Ban).not_to receive(:reset!) + + subject.reset! + end + + it 'does not call Rack::Attack::Allow2Ban.banned?' do + expect(Rack::Attack::Allow2Ban).not_to receive(:banned?) + + subject.banned? + end + + it 'does not call Rack::Attack::Allow2Ban.filter' do + expect(Rack::Attack::Allow2Ban).not_to receive(:filter) + + subject.register_fail! + end + end + + context 'when IP is whitlisted' do + let(:ip) { '127.0.0.1' } + + it_behaves_like 'skips the rate limiter' + end + + context 'when rate limiter is disabled' do + let(:options) { { enabled: false } } + + it_behaves_like 'skips the rate limiter' + end end diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb index ecdd5b29986..f9eb4a30190 100644 --- a/spec/lib/gitlab/auth/ldap/access_spec.rb +++ b/spec/lib/gitlab/auth/ldap/access_spec.rb @@ -136,7 +136,9 @@ describe Gitlab::Auth::LDAP::Access do context 'without ActiveDirectory enabled' do before do allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true) - allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive(:active_directory).and_return(false) + allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance| + allow(instance).to receive(:active_directory).and_return(false) + end end it 'returns true' do diff --git a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb index adb8e138ca7..f1050b9f830 100644 --- a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb +++ b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb @@ -58,7 +58,9 @@ describe Gitlab::Auth::LDAP::AuthHash do end before do - allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive(:attributes).and_return(attributes) + allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance| + allow(instance).to receive(:attributes).and_return(attributes) + end end it "has the correct username" do diff --git a/spec/lib/gitlab/auth/ldap/authentication_spec.rb b/spec/lib/gitlab/auth/ldap/authentication_spec.rb index e68e83e4617..ebaf8383ce5 100644 --- a/spec/lib/gitlab/auth/ldap/authentication_spec.rb +++ b/spec/lib/gitlab/auth/ldap/authentication_spec.rb @@ -18,8 +18,9 @@ describe Gitlab::Auth::LDAP::Authentication do # try only to fake the LDAP call adapter = double('adapter', dn: dn).as_null_object - allow_any_instance_of(described_class) - .to receive(:adapter).and_return(adapter) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:adapter).and_return(adapter) + end expect(described_class.login(login, password)).to be_truthy end @@ -27,8 +28,9 @@ describe Gitlab::Auth::LDAP::Authentication do it "is false if the user does not exist" do # try only to fake the LDAP call adapter = double('adapter', dn: dn).as_null_object - allow_any_instance_of(described_class) - .to receive(:adapter).and_return(adapter) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:adapter).and_return(adapter) + end expect(described_class.login(login, password)).to be_falsey end @@ -38,8 +40,9 @@ describe Gitlab::Auth::LDAP::Authentication do # try only to fake the LDAP call adapter = double('adapter', bind_as: nil).as_null_object - allow_any_instance_of(described_class) - .to receive(:adapter).and_return(adapter) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:adapter).and_return(adapter) + end expect(described_class.login(login, password)).to be_falsey end diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb index 1e3da4f7c2d..c621c0aa935 100644 --- a/spec/lib/gitlab/auth/o_auth/user_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb @@ -253,6 +253,7 @@ describe Gitlab::Auth::OAuth::User do context "and LDAP user has an account already" do let!(:existing_user) { create(:omniauth_user, name: 'John Doe', email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') } + it "adds the omniauth identity to the LDAP account" do allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) @@ -396,7 +397,9 @@ describe Gitlab::Auth::OAuth::User do context "and no account for the LDAP user" do context 'dont block on create (LDAP)' do before do - allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: false) + allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance| + allow(instance).to receive_messages(block_auto_created_users: false) + end end it do @@ -408,7 +411,9 @@ describe Gitlab::Auth::OAuth::User do context 'block on create (LDAP)' do before do - allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: true) + allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance| + allow(instance).to receive_messages(block_auto_created_users: true) + end end it do @@ -424,7 +429,9 @@ describe Gitlab::Auth::OAuth::User do context 'dont block on create (LDAP)' do before do - allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: false) + allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance| + allow(instance).to receive_messages(block_auto_created_users: false) + end end it do @@ -436,7 +443,9 @@ describe Gitlab::Auth::OAuth::User do context 'block on create (LDAP)' do before do - allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: true) + allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance| + allow(instance).to receive_messages(block_auto_created_users: true) + end end it do @@ -480,7 +489,9 @@ describe Gitlab::Auth::OAuth::User do context 'dont block on create (LDAP)' do before do - allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: false) + allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance| + allow(instance).to receive_messages(block_auto_created_users: false) + end end it do @@ -492,7 +503,9 @@ describe Gitlab::Auth::OAuth::User do context 'block on create (LDAP)' do before do - allow_any_instance_of(Gitlab::Auth::LDAP::Config).to receive_messages(block_auto_created_users: true) + allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance| + allow(instance).to receive_messages(block_auto_created_users: true) + end end it do diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb index f7fff389d88..4dbcd0df302 100644 --- a/spec/lib/gitlab/auth/request_authenticator_spec.rb +++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb @@ -66,4 +66,28 @@ describe Gitlab::Auth::RequestAuthenticator do expect(subject.find_sessionless_user([:api])).to be_blank end end + + describe '#runner' do + let!(:runner) { build(:ci_runner) } + + it 'returns the runner using #find_runner_from_token' do + expect_any_instance_of(described_class) + .to receive(:find_runner_from_token) + .and_return(runner) + + expect(subject.runner).to eq runner + end + + it 'returns nil if no runner is found' do + expect(subject.runner).to be_blank + end + + it 'rescue Gitlab::Auth::AuthenticationError exceptions' do + expect_any_instance_of(described_class) + .to receive(:find_runner_from_token) + .and_raise(Gitlab::Auth::UnauthorizedError) + + expect(subject.runner).to be_blank + end + end end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index dc4b0b5b1b6..311cbd4dd7e 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' -describe Gitlab::Auth do +describe Gitlab::Auth, :use_clean_rails_memory_store_caching do let(:gl_auth) { described_class } + set(:project) { create(:project) } describe 'constants' do @@ -79,6 +80,66 @@ describe Gitlab::Auth do end describe 'find_for_git_client' do + describe 'rate limiting' do + before do + stub_rack_attack_setting(enabled: true, ip_whitelist: []) + end + + context 'when IP is already banned' do + subject { gl_auth.find_for_git_client('username', 'password', project: nil, ip: 'ip') } + + before do + expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter| + expect(rate_limiter).to receive(:banned?).and_return(true) + end + end + + it 'raises an IpBlacklisted exception' do + expect { subject }.to raise_error(Gitlab::Auth::IpBlacklisted) + end + end + + context 'for CI registry user' do + let_it_be(:build) { create(:ci_build, :running) } + + it 'skips rate limiting for successful auth' do + expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter| + expect(rate_limiter).not_to receive(:reset!) + end + + gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: build.project, ip: 'ip') + end + + it 'skips rate limiting for failed auth' do + expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter| + 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') + end + end + + context 'for other users' do + let_it_be(:user) { create(:user) } + + it 'resets rate limit for successful auth' do + expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter| + expect(rate_limiter).to receive(:reset!) + end + + gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip') + end + + it 'registers failure for failed auth' do + expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter| + expect(rate_limiter).to receive(:register_fail!) + end + + gl_auth.find_for_git_client(user.username, 'wrong_password', project: nil, ip: 'ip') + end + end + end + context 'build token' do subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: project, ip: 'ip') } @@ -86,10 +147,6 @@ describe Gitlab::Auth do let!(:build) { create(:ci_build, :running) } let(:project) { build.project } - before do - expect(gl_auth).not_to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token') - end - it 'recognises user-less build' do expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, described_class.build_authentication_abilities)) end @@ -106,10 +163,6 @@ describe Gitlab::Auth do let!(:build) { create(:ci_build, status: build_status) } let(:project) { build.project } - before do - expect(gl_auth).not_to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token') - end - it 'denies authentication' do expect(subject).to eq(Gitlab::Auth::Result.new) end @@ -121,14 +174,12 @@ describe Gitlab::Auth do project.create_drone_ci_service(active: true) project.drone_ci_service.update(token: 'token') - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'drone-ci-token') expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, project, :ci, described_class.build_authentication_abilities)) end it 'recognizes master passwords' do user = create(:user, password: 'password') - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username) expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities)) end @@ -145,7 +196,6 @@ describe Gitlab::Auth do user = create(:user) token = Gitlab::LfsToken.new(user).token - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username) expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, described_class.read_write_project_authentication_abilities)) end @@ -153,7 +203,6 @@ describe Gitlab::Auth do key = create(:deploy_key) token = Gitlab::LfsToken.new(key).token - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, described_class.read_only_authentication_abilities)) end @@ -171,7 +220,6 @@ describe Gitlab::Auth do create(:deploy_keys_project, :write_access, deploy_key: key, project: project) token = Gitlab::LfsToken.new(key).token - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, described_class.read_write_authentication_abilities)) end @@ -179,7 +227,6 @@ describe Gitlab::Auth do key = create(:deploy_key) token = Gitlab::LfsToken.new(key).token - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: project, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, described_class.read_only_authentication_abilities)) end end @@ -190,14 +237,12 @@ describe Gitlab::Auth do let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) } it 'succeeds for OAuth tokens with the `api` scope' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2') expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, described_class.full_authentication_abilities)) end it 'fails for OAuth tokens with other scopes' do token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'read_user') - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'oauth2') expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil)) end @@ -257,7 +302,7 @@ describe Gitlab::Auth do end context 'while using regular user and password' do - it 'falls through lfs authentication' do + it 'goes through lfs authentication' do user = create( :user, username: 'normal_user', @@ -268,7 +313,7 @@ describe Gitlab::Auth do .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, described_class.full_authentication_abilities)) end - it 'fails through oauth authentication when the username is oauth2' do + it 'goes through oauth authentication when the username is oauth2' do user = create( :user, username: 'oauth2', @@ -283,7 +328,6 @@ describe Gitlab::Auth do it 'returns double nil for invalid credentials' do login = 'foo' - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login) expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new) end @@ -301,10 +345,6 @@ describe Gitlab::Auth do let(:user) { create(:user, username: username, password: 'my-secret') } let(:deploy_token) { create(:deploy_token, username: username, read_registry: false, projects: [project]) } - before do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: username) - end - it 'succeeds for the token' do auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:download_code]) @@ -328,13 +368,11 @@ describe Gitlab::Auth do it 'succeeds for the right token' do auth_success = Gitlab::Auth::Result.new(read_repository, project, :deploy_token, [:download_code]) - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'deployer') expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, ip: 'ip')) .to eq(auth_success) end it 'fails for the wrong token' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deployer') expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip')) .to eq(auth_failure) end @@ -347,13 +385,11 @@ describe Gitlab::Auth do it 'succeeds for the right token' do auth_success = Gitlab::Auth::Result.new(read_repository, project, :deploy_token, [:download_code]) - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'deployer') expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, ip: 'ip')) .to eq(auth_success) end it 'fails for the wrong token' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deployer') expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip')) .to eq(auth_failure) end @@ -367,7 +403,6 @@ describe Gitlab::Auth do it 'succeeds when login and token are valid' do auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:download_code]) - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: login) expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip')) .to eq(auth_success) end @@ -376,32 +411,27 @@ describe Gitlab::Auth do deploy_token = create(:deploy_token, username: 'deployer', read_registry: false, projects: [project]) auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:download_code]) - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'deployer') expect(gl_auth.find_for_git_client('deployer', deploy_token.token, project: project, ip: 'ip')) .to eq(auth_success) end it 'fails when login is not valid' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'random_login') expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip')) .to eq(auth_failure) end it 'fails when token is not valid' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login) expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip')) .to eq(auth_failure) end it 'fails if token is nil' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login) expect(gl_auth.find_for_git_client(login, nil, project: project, ip: 'ip')) .to eq(auth_failure) end it 'fails if token is not related to project' do another_deploy_token = create(:deploy_token) - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login) expect(gl_auth.find_for_git_client(login, another_deploy_token.token, project: project, ip: 'ip')) .to eq(auth_failure) end @@ -410,7 +440,6 @@ describe Gitlab::Auth do deploy_token.revoke! expect(deploy_token.revoked?).to be_truthy - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deploy-token') expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: project, ip: 'ip')) .to eq(auth_failure) end @@ -428,31 +457,26 @@ describe Gitlab::Auth do it 'succeeds when login and token are valid' do auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:read_container_image]) - expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: login) expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip')) .to eq(auth_success) end it 'fails when login is not valid' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'random_login') expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip')) .to eq(auth_failure) end it 'fails when token is not valid' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login) expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip')) .to eq(auth_failure) end it 'fails if token is nil' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login) expect(gl_auth.find_for_git_client(login, nil, project: nil, ip: 'ip')) .to eq(auth_failure) end it 'fails if token is not related to project' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login) expect(gl_auth.find_for_git_client(login, 'abcdef', project: nil, ip: 'ip')) .to eq(auth_failure) end @@ -461,7 +485,6 @@ describe Gitlab::Auth do deploy_token.revoke! expect(deploy_token.revoked?).to be_truthy - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deploy-token') expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: nil, ip: 'ip')) .to eq(auth_failure) end @@ -473,7 +496,6 @@ describe Gitlab::Auth do end it 'fails when login and token are valid' do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login) expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip')) .to eq(auth_failure) end @@ -586,7 +608,6 @@ describe Gitlab::Auth do private def expect_results_with_abilities(personal_access_token, abilities, success = true) - expect(gl_auth).to receive(:rate_limit!).with('ip', success: success, login: '') expect(gl_auth.find_for_git_client('', personal_access_token&.token, project: nil, ip: 'ip')) .to eq(Gitlab::Auth::Result.new(personal_access_token&.user, nil, personal_access_token.nil? ? nil : :personal_access_token, abilities)) end diff --git a/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb index c66d7cd6148..5cad479ff05 100644 --- a/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb @@ -10,6 +10,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig, :migr describe described_class::Storage::HashedProject do let(:project) { double(id: 555) } + subject(:project_storage) { described_class.new(project) } it 'has the correct disk_path' do @@ -19,6 +20,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig, :migr describe described_class::Storage::LegacyProject do let(:project) { double(full_path: 'this/is/the/full/path') } + subject(:project_storage) { described_class.new(project) } it 'has the correct disk_path' do @@ -28,6 +30,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig, :migr describe described_class::Project do let(:project_record) { projects.create!(namespace_id: subgroup.id, name: 'baz', path: 'baz') } + subject(:project) { described_class.find(project_record.id) } describe '#full_path' do diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb index ddb1d3cea21..75a23d4f49e 100644 --- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb @@ -75,7 +75,9 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do end it 'does not schedule an import' do - expect_any_instance_of(Project).not_to receive(:import_schedule) + expect_next_instance_of(Project) do |instance| + expect(instance).not_to receive(:import_schedule) + end importer.create_project_if_needed end diff --git a/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb b/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb index 1e969542975..a27f14cd621 100644 --- a/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb +++ b/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb @@ -49,6 +49,7 @@ describe Gitlab::BranchPushMergeCommitAnalyzer do context 'when relevant_commit_ids is provided' do let(:relevant_commit_id) { '8a994512e8c8f0dfcf22bb16df6e876be7a61036' } + subject { described_class.new(commits, relevant_commit_ids: [relevant_commit_id]) } it 'returns correct merge commit' do diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb index 91e7edaf704..c2816f35cec 100644 --- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb +++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb @@ -9,7 +9,9 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do describe '.load_for_project' do it "loads the status" do - expect_any_instance_of(described_class).to receive(:load_status) + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:load_status) + end described_class.load_for_project(project) end diff --git a/spec/lib/gitlab/chat/command_spec.rb b/spec/lib/gitlab/chat/command_spec.rb index 46d23ab2b62..f7f344bf786 100644 --- a/spec/lib/gitlab/chat/command_spec.rb +++ b/spec/lib/gitlab/chat/command_spec.rb @@ -44,7 +44,7 @@ describe Gitlab::Chat::Command do let(:pipeline) { command.create_pipeline } before do - stub_repository_ci_yaml_file(sha: project.commit.id) + stub_ci_pipeline_yaml_file(gitlab_ci_yaml) project.add_developer(chat_name.user) end diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb index 71b64a3b9df..7cc1722dfd4 100644 --- a/spec/lib/gitlab/checks/branch_check_spec.rb +++ b/spec/lib/gitlab/checks/branch_check_spec.rb @@ -32,7 +32,9 @@ describe Gitlab::Checks::BranchCheck do end it 'raises an error if the user is not allowed to merge to protected branches' do - expect_any_instance_of(Gitlab::Checks::MatchingMergeRequest).to receive(:match?).and_return(true) + expect_next_instance_of(Gitlab::Checks::MatchingMergeRequest) do |instance| + expect(instance).to receive(:match?).and_return(true) + end expect(user_access).to receive(:can_merge_to_branch?).and_return(false) expect(user_access).to receive(:can_push_to_branch?).and_return(false) diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb index 3a8e8f67e16..dfc8c59fd74 100644 --- a/spec/lib/gitlab/checks/change_access_spec.rb +++ b/spec/lib/gitlab/checks/change_access_spec.rb @@ -14,31 +14,41 @@ describe Gitlab::Checks::ChangeAccess do end it 'calls pushes checks' do - expect_any_instance_of(Gitlab::Checks::PushCheck).to receive(:validate!) + expect_next_instance_of(Gitlab::Checks::PushCheck) do |instance| + expect(instance).to receive(:validate!) + end subject.exec end it 'calls branches checks' do - expect_any_instance_of(Gitlab::Checks::BranchCheck).to receive(:validate!) + expect_next_instance_of(Gitlab::Checks::BranchCheck) do |instance| + expect(instance).to receive(:validate!) + end subject.exec end it 'calls tags checks' do - expect_any_instance_of(Gitlab::Checks::TagCheck).to receive(:validate!) + expect_next_instance_of(Gitlab::Checks::TagCheck) do |instance| + expect(instance).to receive(:validate!) + end subject.exec end it 'calls lfs checks' do - expect_any_instance_of(Gitlab::Checks::LfsCheck).to receive(:validate!) + expect_next_instance_of(Gitlab::Checks::LfsCheck) do |instance| + expect(instance).to receive(:validate!) + end subject.exec end it 'calls diff checks' do - expect_any_instance_of(Gitlab::Checks::DiffCheck).to receive(:validate!) + expect_next_instance_of(Gitlab::Checks::DiffCheck) do |instance| + expect(instance).to receive(:validate!) + end subject.exec end diff --git a/spec/lib/gitlab/ci/ansi2json/result_spec.rb b/spec/lib/gitlab/ci/ansi2json/result_spec.rb new file mode 100644 index 00000000000..5b7b5481400 --- /dev/null +++ b/spec/lib/gitlab/ci/ansi2json/result_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Ansi2json::Result do + let(:stream) { StringIO.new('hello') } + let(:state) { Gitlab::Ci::Ansi2json::State.new(nil, stream.size) } + let(:offset) { 0 } + let(:params) do + { lines: [], state: state, append: false, truncated: false, offset: offset, stream: stream } + end + + subject { described_class.new(params) } + + describe '#size' do + before do + stream.seek(5) # move stream cursor to the end + end + + context 'when offset is at the start' do + let(:offset) { 0 } + + it 'returns the full size' do + expect(subject.size).to eq(5) + end + end + + context 'when offset is not zero' do + let(:offset) { 2 } + + it 'returns the remaining size' do + expect(subject.size).to eq(3) + end + end + end + + describe '#total' do + it 'returns size of stread' do + expect(subject.total).to eq(5) + end + end +end diff --git a/spec/lib/gitlab/ci/ansi2json/style_spec.rb b/spec/lib/gitlab/ci/ansi2json/style_spec.rb index 5110c215415..ad05aa03e83 100644 --- a/spec/lib/gitlab/ci/ansi2json/style_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json/style_spec.rb @@ -147,6 +147,10 @@ describe Gitlab::Ci::Ansi2json::Style do [%w[1], %w[0], '', 'resets style from format bold'], [%w[1 3], %w[0], '', 'resets style from format bold and italic'], [%w[1 3 term-fg-l-red term-bg-yellow], %w[0], '', 'resets all formats and colors'], + # default foreground + [%w[31 42], %w[39], 'term-bg-green', 'set foreground from red to default leaving background unchanged'], + # default background + [%w[31 42], %w[49], 'term-fg-red', 'set background from green to default leaving foreground unchanged'], # misc [[], %w[1 30 42 3], 'term-fg-l-black term-bg-green term-bold term-italic', 'adds fg color, bg color and formats from no style'], [%w[3 31], %w[23 1 43], 'term-fg-l-red term-bg-yellow term-bold', 'replaces format italic with bold and adds a yellow background'] 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 73c3cad88bc..243c6f06324 100644 --- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb @@ -152,6 +152,7 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do describe '#blob' do let(:file_entry) { |example| path(example) } + subject { file_entry.blob } it 'returns a blob representing the entry data' do diff --git a/spec/lib/gitlab/ci/build/context/build_spec.rb b/spec/lib/gitlab/ci/build/context/build_spec.rb index 3adde213f59..1b73b9a083d 100644 --- a/spec/lib/gitlab/ci/build/context/build_spec.rb +++ b/spec/lib/gitlab/ci/build/context/build_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Ci::Build::Context::Build do diff --git a/spec/lib/gitlab/ci/build/context/global_spec.rb b/spec/lib/gitlab/ci/build/context/global_spec.rb index 6bc8f862779..65cc41ed3f9 100644 --- a/spec/lib/gitlab/ci/build/context/global_spec.rb +++ b/spec/lib/gitlab/ci/build/context/global_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Ci::Build::Context::Global do diff --git a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb index 9148c0d579e..848adb2e6e5 100644 --- a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb +++ b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb @@ -12,12 +12,16 @@ describe Gitlab::Ci::Build::Credentials::Factory do end before do - allow_any_instance_of(described_class).to receive(:providers).and_return([TestProvider]) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:providers).and_return([TestProvider]) + end end context 'when provider is valid' do before do - allow_any_instance_of(TestProvider).to receive(:valid?).and_return(true) + allow_next_instance_of(TestProvider) do |instance| + allow(instance).to receive(:valid?).and_return(true) + end end it 'generates an array of credentials objects' do @@ -29,7 +33,9 @@ describe Gitlab::Ci::Build::Credentials::Factory do context 'when provider is not valid' do before do - allow_any_instance_of(TestProvider).to receive(:valid?).and_return(false) + allow_next_instance_of(TestProvider) do |instance| + allow(instance).to receive(:valid?).and_return(false) + end end it 'generates an array without specific credential object' do diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index c7a5ac783b3..2493855f851 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -38,13 +38,45 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do .and_return(double(execute: kubernetes_namespace)) end - it { is_expected.to be_falsey } - - context 'and the service_account_token is blank' do - let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: nil) } + context 'and the knative-serving namespace is missing' do + before do + allow(Clusters::KnativeServingNamespaceFinder).to receive(:new) + .and_return(double(execute: false)) + end it { is_expected.to be_truthy } end + + context 'and the knative-serving namespace exists' do + before do + allow(Clusters::KnativeServingNamespaceFinder).to receive(:new) + .and_return(double(execute: true)) + end + + context 'and the knative version role binding is missing' do + before do + allow(Clusters::KnativeVersionRoleBindingFinder).to receive(:new) + .and_return(double(execute: nil)) + end + + it { is_expected.to be_truthy } + end + + context 'and the knative version role binding already exists' do + before do + allow(Clusters::KnativeVersionRoleBindingFinder).to receive(:new) + .and_return(double(execute: true)) + end + + it { is_expected.to be_falsey } + + context 'and the service_account_token is blank' do + let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: nil) } + + it { is_expected.to be_truthy } + end + end + end end end @@ -96,6 +128,47 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do subject end + + context 'the build has a namespace configured via CI template' do + let(:kubernetes_namespace) { double(namespace: existing_namespace) } + + before do + allow(build).to receive(:expanded_kubernetes_namespace) + .and_return(requested_namespace) + end + + context 'the requested namespace matches the default' do + let(:requested_namespace) { 'production' } + let(:existing_namespace) { requested_namespace } + + it 'creates a namespace' do + expect(Clusters::BuildKubernetesNamespaceService) + .to receive(:new) + .with(cluster, environment: deployment.environment) + .and_return(namespace_builder) + + expect(Clusters::Kubernetes::CreateOrUpdateNamespaceService) + .to receive(:new) + .with(cluster: cluster, kubernetes_namespace: kubernetes_namespace) + .and_return(service) + + expect(service).to receive(:execute).once + + subject + end + end + + context 'the requested namespace differs from the default' do + let(:requested_namespace) { 'production' } + let(:existing_namespace) { 'other-namespace' } + + it 'does not create a namespace' do + expect(Clusters::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new) + + subject + end + end + end end context 'kubernetes namespace exists (but has no service_account_token)' do @@ -115,6 +188,24 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do subject end end + + context 'knative version role binding is missing' do + before do + allow(Clusters::KubernetesNamespaceFinder).to receive(:new) + .and_return(double(execute: kubernetes_namespace)) + allow(Clusters::KnativeVersionRoleBindingFinder).to receive(:new) + .and_return(double(execute: nil)) + end + + it 'creates the knative version role binding' do + expect(Clusters::Kubernetes::CreateOrUpdateNamespaceService) + .to receive(:new) + .with(cluster: cluster, kubernetes_namespace: kubernetes_namespace) + .and_return(service) + + subject + end + end end context 'completion is not required' do diff --git a/spec/lib/gitlab/ci/config/entry/default_spec.rb b/spec/lib/gitlab/ci/config/entry/default_spec.rb index dad4f408e50..23c62bbf92a 100644 --- a/spec/lib/gitlab/ci/config/entry/default_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/default_spec.rb @@ -26,7 +26,8 @@ describe Gitlab::Ci::Config::Entry::Default do it 'contains the expected node names' do expect(described_class.nodes.keys) .to match_array(%i[before_script image services - after_script cache interruptible]) + after_script cache interruptible + timeout retry tags artifacts]) end end end diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb index 7b72b45fd8d..8c21d5342cc 100644 --- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb @@ -206,6 +206,35 @@ describe Gitlab::Ci::Config::Entry::Environment do end end + context 'when auto_stop_in is specified' do + let(:config) do + { + name: 'review/$CI_COMMIT_REF_NAME', + url: 'https://$CI_COMMIT_REF_NAME.review.gitlab.com', + on_stop: 'stop_review', + auto_stop_in: auto_stop_in + } + end + + context 'when auto_stop_in is correct format' do + let(:auto_stop_in) { '2 days' } + + it 'becomes valid' do + expect(entry).to be_valid + expect(entry.auto_stop_in).to eq(auto_stop_in) + end + end + + context 'when auto_stop_in is invalid format' do + let(:auto_stop_in) { 'invalid' } + + it 'becomes invalid' do + expect(entry).not_to be_valid + expect(entry.errors).to include 'environment auto stop in should be a duration' + end + end + end + context 'when configuration is invalid' do context 'when configuration is an array' do let(:config) { ['env'] } @@ -241,4 +270,28 @@ describe Gitlab::Ci::Config::Entry::Environment do end end end + + describe 'kubernetes' do + let(:config) do + { name: 'production', kubernetes: kubernetes_config } + end + + context 'is a string' do + let(:kubernetes_config) { 'production' } + + it { expect(entry).not_to be_valid } + end + + context 'is a hash' do + let(:kubernetes_config) { Hash(namespace: 'production') } + + it { expect(entry).to be_valid } + end + + context 'is nil' do + let(:kubernetes_config) { nil } + + it { expect(entry).to be_valid } + end + end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index fe83171c57a..cc1ee63ff04 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -24,7 +24,7 @@ describe Gitlab::Ci::Config::Entry::Job do let(:result) do %i[before_script script stage type after_script cache image services only except rules needs variables artifacts - environment coverage retry interruptible] + environment coverage retry interruptible timeout tags] end it { is_expected.to match_array result } @@ -93,7 +93,7 @@ describe Gitlab::Ci::Config::Entry::Job do context 'when delayed job' do context 'when start_in is specified' do - let(:config) { { script: 'echo', when: 'delayed', start_in: '1 day' } } + let(:config) { { script: 'echo', when: 'delayed', start_in: '1 week' } } it { expect(entry).to be_valid } end @@ -232,11 +232,9 @@ describe Gitlab::Ci::Config::Entry::Job do context 'when delayed job' do context 'when start_in is specified' do - let(:config) { { script: 'echo', when: 'delayed', start_in: '1 day' } } + let(:config) { { script: 'echo', when: 'delayed', start_in: '1 week' } } - it 'returns error about invalid type' do - expect(entry).to be_valid - end + it { expect(entry).to be_valid } end context 'when start_in is empty' do @@ -257,8 +255,8 @@ describe Gitlab::Ci::Config::Entry::Job do end end - context 'when start_in is longer than one day' do - let(:config) { { when: 'delayed', start_in: '2 days' } } + context 'when start_in is longer than one week' do + let(:config) { { when: 'delayed', start_in: '8 days' } } it 'returns error about exceeding the limit' do expect(entry).not_to be_valid @@ -417,21 +415,21 @@ describe Gitlab::Ci::Config::Entry::Job do context 'when timeout value is not correct' do context 'when it is higher than instance wide timeout' do - let(:config) { { timeout: '3 months' } } + let(:config) { { timeout: '3 months', script: 'test' } } it 'returns error about value too high' do expect(entry).not_to be_valid expect(entry.errors) - .to include "job timeout should not exceed the limit" + .to include "timeout config should not exceed the limit" end end context 'when it is not a duration' do - let(:config) { { timeout: 100 } } + let(:config) { { timeout: 100, script: 'test' } } it 'returns error about wrong value' do expect(entry).not_to be_valid - expect(entry.errors).to include 'job timeout should be a duration' + expect(entry.errors).to include 'timeout config should be a duration' end end end @@ -463,7 +461,8 @@ describe Gitlab::Ci::Config::Entry::Job do let(:unspecified) { double('unspecified', 'specified?' => false) } let(:default) { double('default', '[]' => unspecified) } - let(:deps) { double('deps', 'default' => default, '[]' => unspecified) } + let(:workflow) { double('workflow', 'has_rules?' => false) } + let(:deps) { double('deps', 'default' => default, '[]' => unspecified, 'workflow' => workflow) } context 'when job config overrides default config' do before do @@ -494,6 +493,49 @@ describe Gitlab::Ci::Config::Entry::Job do expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push') end end + + context 'with workflow rules' do + using RSpec::Parameterized::TableSyntax + + where(:name, :has_workflow_rules?, :only, :rules, :result) do + "uses default only" | false | nil | nil | { refs: %w[branches tags] } + "uses user only" | false | %w[branches] | nil | { refs: %w[branches] } + "does not define only" | false | nil | [] | nil + "does not define only" | true | nil | nil | nil + "uses user only" | true | %w[branches] | nil | { refs: %w[branches] } + "does not define only" | true | nil | [] | nil + end + + with_them do + let(:config) { { script: 'ls', rules: rules, only: only }.compact } + + it "#{name}" do + expect(workflow).to receive(:has_rules?) { has_workflow_rules? } + + entry.compose!(deps) + + expect(entry.only_value).to eq(result) + end + end + end + + context 'when workflow rules is used' do + context 'when rules are used' do + let(:config) { { script: 'ls', cache: { key: 'test' }, rules: [] } } + + it 'does not define only' do + expect(entry).not_to be_only_defined + end + end + + context 'when rules are not used' do + let(:config) { { script: 'ls', cache: { key: 'test' }, only: [] } } + + it 'does not define only' do + expect(entry).not_to be_only_defined + end + end + end end context 'when composed' do diff --git a/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb b/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb new file mode 100644 index 00000000000..468e83ec506 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::Kubernetes do + subject { described_class.new(config) } + + describe 'attributes' do + it { is_expected.to respond_to(:namespace) } + it { is_expected.to respond_to(:has_namespace?) } + end + + describe 'validations' do + describe 'config' do + context 'is a hash containing known keys' do + let(:config) { Hash(namespace: 'namespace') } + + it { is_expected.to be_valid } + end + + context 'is a hash containing an unknown key' do + let(:config) { Hash(unknown: 'attribute') } + + it { is_expected.not_to be_valid } + end + + context 'is a string' do + let(:config) { 'config' } + + it { is_expected.not_to be_valid } + end + end + + describe 'namespace' do + let(:config) { Hash(namespace: namespace) } + + context 'is a string' do + let(:namespace) { 'namespace' } + + it { is_expected.to be_valid } + end + + context 'is a hash' do + let(:namespace) { Hash(key: 'namespace') } + + it { is_expected.not_to be_valid } + end + + context 'is not present' do + let(:namespace) { '' } + + it { is_expected.not_to be_valid } + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/need_spec.rb b/spec/lib/gitlab/ci/config/entry/need_spec.rb index d119e604900..92b71c5f6cc 100644 --- a/spec/lib/gitlab/ci/config/entry/need_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/need_spec.rb @@ -5,31 +5,177 @@ require 'spec_helper' describe ::Gitlab::Ci::Config::Entry::Need do subject(:need) { described_class.new(config) } - context 'when job is specified' do - let(:config) { 'job_name' } + shared_examples 'job type' do + describe '#type' do + subject(:need_type) { need.type } - describe '#valid?' do - it { is_expected.to be_valid } + it { is_expected.to eq(:job) } + end + end + + context 'with simple config' do + context 'when job is specified' do + let(:config) { 'job_name' } + + describe '#valid?' do + it { is_expected.to be_valid } + end + + describe '#value' do + it 'returns job needs configuration' do + expect(need.value).to eq(name: 'job_name', artifacts: true) + end + end + + it_behaves_like 'job type' + end + + context 'when need is empty' do + let(:config) { '' } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + it 'is returns an error about an empty config' do + expect(need.errors) + .to contain_exactly("job string config can't be blank") + end + end + + it_behaves_like 'job type' end + end + + context 'with complex config' do + context 'with job name and artifacts true' do + let(:config) { { job: 'job_name', artifacts: true } } + + describe '#valid?' do + it { is_expected.to be_valid } + end + + describe '#value' do + it 'returns job needs configuration' do + expect(need.value).to eq(name: 'job_name', artifacts: true) + end + end + + it_behaves_like 'job type' + end + + context 'with job name and artifacts false' do + let(:config) { { job: 'job_name', artifacts: false } } + + describe '#valid?' do + it { is_expected.to be_valid } + end + + describe '#value' do + it 'returns job needs configuration' do + expect(need.value).to eq(name: 'job_name', artifacts: false) + end + end + + it_behaves_like 'job type' + end + + context 'with job name and artifacts nil' do + let(:config) { { job: 'job_name', artifacts: nil } } - describe '#value' do - it 'returns job needs configuration' do - expect(need.value).to eq(name: 'job_name') + describe '#valid?' do + it { is_expected.to be_valid } end + + describe '#value' do + it 'returns job needs configuration' do + expect(need.value).to eq(name: 'job_name', artifacts: true) + end + end + + it_behaves_like 'job type' + end + + context 'without artifacts key' do + let(:config) { { job: 'job_name' } } + + describe '#valid?' do + it { is_expected.to be_valid } + end + + describe '#value' do + it 'returns job needs configuration' do + expect(need.value).to eq(name: 'job_name', artifacts: true) + end + end + + it_behaves_like 'job type' + end + + context 'when job name is empty' do + let(:config) { { job: '', artifacts: true } } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + it 'is returns an error about an empty config' do + expect(need.errors) + .to contain_exactly("job hash job can't be blank") + end + end + + it_behaves_like 'job type' + end + + context 'when job name is not a string' do + let(:config) { { job: :job_name, artifacts: false } } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + it 'is returns an error about job type' do + expect(need.errors) + .to contain_exactly('job hash job should be a string') + end + end + + it_behaves_like 'job type' + end + + context 'when job has unknown keys' do + let(:config) { { job: 'job_name', artifacts: false, some: :key } } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + it 'is returns an error about job type' do + expect(need.errors) + .to contain_exactly('job hash config contains unknown keys: some') + end + end + + it_behaves_like 'job type' end end - context 'when need is empty' do - let(:config) { '' } + context 'when need config is not a string or a hash' do + let(:config) { :job_name } describe '#valid?' do it { is_expected.not_to be_valid } end describe '#errors' do - it 'is returns an error about an empty config' do + it 'is returns an error about job type' do expect(need.errors) - .to contain_exactly("job config can't be blank") + .to contain_exactly('unknown strategy has an unsupported type') end end end diff --git a/spec/lib/gitlab/ci/config/entry/needs_spec.rb b/spec/lib/gitlab/ci/config/entry/needs_spec.rb index f4a76b52d30..b8b84b5efd2 100644 --- a/spec/lib/gitlab/ci/config/entry/needs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/needs_spec.rb @@ -51,9 +51,34 @@ describe ::Gitlab::Ci::Config::Entry::Needs do end end end + + context 'when wrong needs type is used' do + let(:config) { [{ job: 'job_name', artifacts: true, some: :key }] } + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + it 'returns error about incorrect type' do + expect(needs.errors).to contain_exactly( + 'need config contains unknown keys: some') + end + end + end end describe '.compose!' do + shared_examples 'entry with descendant nodes' do + describe '#descendants' do + it 'creates valid descendant nodes' do + expect(needs.descendants.count).to eq 2 + expect(needs.descendants) + .to all(be_an_instance_of(::Gitlab::Ci::Config::Entry::Need)) + end + end + end + context 'when valid job entries composed' do let(:config) { %w[first_job_name second_job_name] } @@ -65,18 +90,80 @@ describe ::Gitlab::Ci::Config::Entry::Needs do it 'returns key value' do expect(needs.value).to eq( job: [ - { name: 'first_job_name' }, - { name: 'second_job_name' } + { name: 'first_job_name', artifacts: true }, + { name: 'second_job_name', artifacts: true } ] ) end end - describe '#descendants' do - it 'creates valid descendant nodes' do - expect(needs.descendants.count).to eq 2 - expect(needs.descendants) - .to all(be_an_instance_of(::Gitlab::Ci::Config::Entry::Need)) + it_behaves_like 'entry with descendant nodes' + end + + context 'with complex job entries composed' do + let(:config) do + [ + { job: 'first_job_name', artifacts: true }, + { job: 'second_job_name', artifacts: false } + ] + end + + before do + needs.compose! + end + + describe '#value' do + it 'returns key value' do + expect(needs.value).to eq( + job: [ + { name: 'first_job_name', artifacts: true }, + { name: 'second_job_name', artifacts: false } + ] + ) + end + end + + it_behaves_like 'entry with descendant nodes' + end + + context 'with mixed job entries composed' do + let(:config) do + [ + 'first_job_name', + { job: 'second_job_name', artifacts: false } + ] + end + + before do + needs.compose! + end + + describe '#value' do + it 'returns key value' do + expect(needs.value).to eq( + job: [ + { name: 'first_job_name', artifacts: true }, + { name: 'second_job_name', artifacts: false } + ] + ) + end + end + + it_behaves_like 'entry with descendant nodes' + end + + context 'with empty config' do + let(:config) do + [] + end + + before do + needs.compose! + end + + describe '#value' do + it 'returns empty value' do + expect(needs.value).to eq({}) end end end diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb index dd869c227a1..0aea3a59b33 100644 --- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb @@ -15,8 +15,9 @@ describe Gitlab::Ci::Config::External::File::Project do before do project.add_developer(user) - allow_any_instance_of(Gitlab::Ci::Config::External::Context) - .to receive(:check_execution_time!) + allow_next_instance_of(Gitlab::Ci::Config::External::Context) do |instance| + allow(instance).to receive(:check_execution_time!) + end end describe '#matching?' do @@ -159,8 +160,8 @@ describe Gitlab::Ci::Config::External::File::Project do private def stub_project_blob(ref, path) - allow_any_instance_of(Repository) - .to receive(:blob_data_at) - .with(ref, path) { yield } + allow_next_instance_of(Repository) do |instance| + allow(instance).to receive(:blob_data_at).with(ref, path) { yield } + end end end diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb index 08db00dda9d..a23cce9b757 100644 --- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb @@ -21,8 +21,9 @@ describe Gitlab::Ci::Config::External::File::Remote do end before do - allow_any_instance_of(Gitlab::Ci::Config::External::Context) - .to receive(:check_execution_time!) + allow_next_instance_of(Gitlab::Ci::Config::External::Context) do |instance| + allow(instance).to receive(:check_execution_time!) + end end describe '#matching?' do diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb index 164b5800abf..ee1660e4dfd 100644 --- a/spec/lib/gitlab/ci/config/external/file/template_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb @@ -14,8 +14,9 @@ describe Gitlab::Ci::Config::External::File::Template do let(:template_file) { described_class.new(params, context) } before do - allow_any_instance_of(Gitlab::Ci::Config::External::Context) - .to receive(:check_execution_time!) + allow_next_instance_of(Gitlab::Ci::Config::External::Context) do |instance| + allow(instance).to receive(:check_execution_time!) + end end describe '#matching?' do diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index 8d09aa47f12..2a5f62f7e74 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -23,8 +23,9 @@ describe Gitlab::Ci::Config::External::Mapper do before do stub_full_request(remote_url).to_return(body: file_content) - allow_any_instance_of(Gitlab::Ci::Config::External::Context) - .to receive(:check_execution_time!) + allow_next_instance_of(Gitlab::Ci::Config::External::Context) do |instance| + allow(instance).to receive(:check_execution_time!) + end end describe '#process' do diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb index bf880478387..db62fb7524d 100644 --- a/spec/lib/gitlab/ci/config/normalizer_spec.rb +++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb @@ -105,7 +105,7 @@ describe Gitlab::Ci::Config::Normalizer do context 'for needs' do let(:expanded_job_attributes) do expanded_job_names.map do |job_name| - { name: job_name } + { name: job_name, extra: :key } end end @@ -117,7 +117,7 @@ describe Gitlab::Ci::Config::Normalizer do script: 'echo 1', needs: { job: [ - { name: job_name.to_s } + { name: job_name.to_s, extra: :key } ] } } @@ -140,8 +140,8 @@ describe Gitlab::Ci::Config::Normalizer do script: 'echo 1', needs: { job: [ - { name: job_name.to_s }, - { name: "other_job" } + { name: job_name.to_s, extra: :key }, + { name: "other_job", extra: :key } ] } } @@ -153,7 +153,7 @@ describe Gitlab::Ci::Config::Normalizer do end it "includes the regular job in dependencies" do - expect(subject.dig(:final_job, :needs, :job)).to include(name: 'other_job') + expect(subject.dig(:final_job, :needs, :job)).to include(name: 'other_job', extra: :key) end end end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index b254f9af2f1..63a36995284 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -8,8 +8,9 @@ describe Gitlab::Ci::Config do set(:user) { create(:user) } before do - allow_any_instance_of(Gitlab::Ci::Config::External::Context) - .to receive(:check_execution_time!) + allow_next_instance_of(Gitlab::Ci::Config::External::Context) do |instance| + allow(instance).to receive(:check_execution_time!) + end end let(:config) do @@ -156,7 +157,7 @@ describe Gitlab::Ci::Config do describe '.new' do it 'raises error' do - expect(Gitlab::Sentry).to receive(:track_exception) + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception) expect { config }.to raise_error( described_class::ConfigError, @@ -358,22 +359,15 @@ describe Gitlab::Ci::Config do context "when it takes too long to evaluate includes" do before do - allow_any_instance_of(Gitlab::Ci::Config::External::Context) - .to receive(:check_execution_time!) - .and_call_original - - allow_any_instance_of(Gitlab::Ci::Config::External::Context) - .to receive(:set_deadline) - .with(described_class::TIMEOUT_SECONDS) - .and_call_original - - allow_any_instance_of(Gitlab::Ci::Config::External::Context) - .to receive(:execution_expired?) - .and_return(true) + allow_next_instance_of(Gitlab::Ci::Config::External::Context) do |instance| + allow(instance).to receive(:check_execution_time!).and_call_original + allow(instance).to receive(:set_deadline).with(described_class::TIMEOUT_SECONDS).and_call_original + allow(instance).to receive(:execution_expired?).and_return(true) + end end it 'raises error TimeoutError' do - expect(Gitlab::Sentry).to receive(:track_exception) + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception) expect { config }.to raise_error( described_class::ConfigError, @@ -384,9 +378,9 @@ describe Gitlab::Ci::Config do context 'when context expansion timeout is disabled' do before do - allow_any_instance_of(Gitlab::Ci::Config::External::Context) - .to receive(:check_execution_time!) - .and_call_original + allow_next_instance_of(Gitlab::Ci::Config::External::Context) do |instance| + allow(instance).to receive(:check_execution_time!).and_call_original + end allow(Feature) .to receive(:enabled?) diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index af4e9d687c4..385df72fa41 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -152,6 +152,22 @@ describe Gitlab::Ci::CronParser do end end end + + context 'when time crosses a Daylight Savings boundary' do + let(:cron) { '* 0 1 12 *'} + + # Note this previously only failed if the time zone is set + # to a zone that observes Daylight Savings + # (e.g. America/Chicago) at the start of the test. Stubbing + # TZ doesn't appear to be enough. + it 'generates day without TZInfo::AmbiguousTime error' do + Timecop.freeze(Time.utc(2020, 1, 1)) do + expect(subject.year).to eq(2020) + expect(subject.month).to eq(12) + expect(subject.day).to eq(1) + end + end + end end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb index a631cd2777b..b81094f8b4a 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do let(:step) { described_class.new(pipeline, command) } before do - stub_repository_ci_yaml_file(sha: anything) + stub_ci_pipeline_yaml_file(gitlab_ci_yaml) end it 'never breaks the chain' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb new file mode 100644 index 00000000000..7ebe5842fd0 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb @@ -0,0 +1,221 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::Config::Content do + let(:project) { create(:project, ci_config_path: ci_config_path) } + let(:pipeline) { build(:ci_pipeline, project: project) } + let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project) } + + subject { described_class.new(pipeline, command) } + + describe '#perform!' do + context 'when feature flag is disabled' do + before do + stub_feature_flags(ci_root_config_content: false) + end + + context 'when config is defined in a custom path in the repository' do + let(:ci_config_path) { 'path/to/config.yml' } + + before do + expect(project.repository) + .to receive(:gitlab_ci_yml_for) + .with(pipeline.sha, ci_config_path) + .and_return('the-content') + end + + it 'returns the content of the YAML file' do + subject.perform! + + expect(pipeline.config_source).to eq 'repository_source' + expect(command.config_content).to eq('the-content') + end + end + + context 'when config is defined remotely' do + let(:ci_config_path) { 'http://example.com/path/to/ci/config.yml' } + + it 'does not support URLs and default to AutoDevops' do + subject.perform! + + expect(pipeline.config_source).to eq 'auto_devops_source' + template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps') + expect(command.config_content).to eq(template.content) + end + end + + context 'when config is defined in a separate repository' do + let(:ci_config_path) { 'path/to/.gitlab-ci.yml@another-group/another-repo' } + + it 'does not support YAML from external repository and default to AutoDevops' do + subject.perform! + + expect(pipeline.config_source).to eq 'auto_devops_source' + template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps') + expect(command.config_content).to eq(template.content) + end + end + + context 'when config is defined in the default .gitlab-ci.yml' do + let(:ci_config_path) { nil } + + before do + expect(project.repository) + .to receive(:gitlab_ci_yml_for) + .with(pipeline.sha, '.gitlab-ci.yml') + .and_return('the-content') + end + + it 'returns the content of the canonical config file' do + subject.perform! + + expect(pipeline.config_source).to eq 'repository_source' + expect(command.config_content).to eq('the-content') + end + end + + context 'when config is the Auto-Devops template' do + let(:ci_config_path) { nil } + + before do + expect(project).to receive(:auto_devops_enabled?).and_return(true) + end + + it 'returns the content of AutoDevops template' do + subject.perform! + + expect(pipeline.config_source).to eq 'auto_devops_source' + template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps') + expect(command.config_content).to eq(template.content) + end + end + + context 'when config is not defined anywhere' do + let(:ci_config_path) { nil } + + before do + expect(project).to receive(:auto_devops_enabled?).and_return(false) + end + + it 'builds root config including the auto-devops template' do + subject.perform! + + expect(pipeline.config_source).to eq('unknown_source') + expect(command.config_content).to be_nil + expect(pipeline.errors.full_messages).to include('Missing CI config file') + end + end + end + + context 'when config is defined in a custom path in the repository' do + let(:ci_config_path) { 'path/to/config.yml' } + + before do + expect(project.repository) + .to receive(:gitlab_ci_yml_for) + .with(pipeline.sha, ci_config_path) + .and_return('the-content') + end + + it 'builds root config including the local custom file' do + subject.perform! + + expect(pipeline.config_source).to eq 'repository_source' + expect(command.config_content).to eq(<<~EOY) + --- + include: + - local: #{ci_config_path} + EOY + end + end + + context 'when config is defined remotely' do + let(:ci_config_path) { 'http://example.com/path/to/ci/config.yml' } + + it 'builds root config including the remote config' do + subject.perform! + + expect(pipeline.config_source).to eq 'remote_source' + expect(command.config_content).to eq(<<~EOY) + --- + include: + - remote: #{ci_config_path} + EOY + end + end + + context 'when config is defined in a separate repository' do + let(:ci_config_path) { 'path/to/.gitlab-ci.yml@another-group/another-repo' } + + it 'builds root config including the path to another repository' do + subject.perform! + + expect(pipeline.config_source).to eq 'external_project_source' + expect(command.config_content).to eq(<<~EOY) + --- + include: + - project: another-group/another-repo + file: path/to/.gitlab-ci.yml + EOY + end + end + + context 'when config is defined in the default .gitlab-ci.yml' do + let(:ci_config_path) { nil } + + before do + expect(project.repository) + .to receive(:gitlab_ci_yml_for) + .with(pipeline.sha, '.gitlab-ci.yml') + .and_return('the-content') + end + + it 'builds root config including the canonical CI config file' do + subject.perform! + + expect(pipeline.config_source).to eq 'repository_source' + expect(command.config_content).to eq(<<~EOY) + --- + include: + - local: ".gitlab-ci.yml" + EOY + end + end + + context 'when config is the Auto-Devops template' do + let(:ci_config_path) { nil } + + before do + expect(project).to receive(:auto_devops_enabled?).and_return(true) + end + + it 'builds root config including the auto-devops template' do + subject.perform! + + expect(pipeline.config_source).to eq 'auto_devops_source' + expect(command.config_content).to eq(<<~EOY) + --- + include: + - template: Auto-DevOps.gitlab-ci.yml + EOY + end + end + + context 'when config is not defined anywhere' do + let(:ci_config_path) { nil } + + before do + expect(project).to receive(:auto_devops_enabled?).and_return(false) + end + + it 'builds root config including the auto-devops template' do + subject.perform! + + expect(pipeline.config_source).to eq('unknown_source') + expect(command.config_content).to be_nil + expect(pipeline.errors.full_messages).to include('Missing CI config file') + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb new file mode 100644 index 00000000000..f2a0b93ef28 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::Validate::External do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:pipeline) { build(:ci_empty_pipeline, user: user, project: project) } + let!(:step) { described_class.new(pipeline, command) } + + let(:ci_yaml) do + <<-CI_YAML + stages: + - first_stage + - second_stage + + first_stage_job_name: + stage: first_stage + image: hello_world + script: + - echo 'hello' + + second_stage_job_name: + stage: second_stage + services: + - postgres + before_script: + - echo 'first hello' + script: + - echo 'second hello' + CI_YAML + end + + let(:yaml_processor) do + ::Gitlab::Ci::YamlProcessor.new( + ci_yaml, { + project: project, + sha: pipeline.sha, + user: user + } + ) + end + + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new( + project: project, current_user: user, config_processor: yaml_processor + ) + end + + describe '#perform!' do + subject(:perform!) { step.perform! } + + context 'when validation returns true' do + before do + allow(step).to receive(:validate_external).and_return(true) + end + + it 'does not drop the pipeline' do + perform! + + expect(pipeline.status).not_to eq('failed') + expect(pipeline.errors).to be_empty + end + + it 'does not break the chain' do + perform! + + expect(step.break?).to be false + end + end + + context 'when validation return false' do + before do + allow(step).to receive(:validate_external).and_return(false) + end + + it 'drops the pipeline' do + perform! + + expect(pipeline.status).to eq('failed') + expect(pipeline.errors.to_a).to include('External validation failed') + end + + it 'breaks the chain' do + perform! + + expect(step.break?).to be true + end + end + end + + describe '#validation_service_payload' do + subject(:validation_service_payload) { step.send(:validation_service_payload, pipeline, command.config_processor.stages_attributes) } + + it 'respects the defined schema' do + expect(validation_service_payload).to match_schema('/external_validation') + end + + it 'does not fire sql queries' do + expect { validation_service_payload }.not_to exceed_query_limit(1) + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 53dcb6359fe..2ae513aea1b 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -852,7 +852,7 @@ describe Gitlab::Ci::Pipeline::Seed::Build do it "returns an error" do expect(subject.errors).to contain_exactly( - "rspec: one job can only need 5 others, but you have listed 6. See needs keyword documentation for more details") + "rspec: one job can only need 10 others, but you have listed 11. See needs keyword documentation for more details") end end diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb index a13335f63d5..a978084876f 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb @@ -81,7 +81,9 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do context 'when a ref is protected' do before do - allow_any_instance_of(Project).to receive(:protected_for?).and_return(true) + allow_next_instance_of(Project) do |instance| + allow(instance).to receive(:protected_for?).and_return(true) + end end it 'returns protected builds' do @@ -91,7 +93,9 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do context 'when a ref is not protected' do before do - allow_any_instance_of(Project).to receive(:protected_for?).and_return(false) + allow_next_instance_of(Project) do |instance| + allow(instance).to receive(:protected_for?).and_return(false) + end end it 'returns unprotected builds' do diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index de489fa4664..11be17bfc53 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -31,10 +31,10 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'passed' + expect(status.text).to eq s_('CiStatusText|passed') expect(status.icon).to eq 'status_success' expect(status.favicon).to eq 'favicon_status_success' - expect(status.label).to eq 'passed' + expect(status.label).to eq s_('CiStatusLabel|passed') expect(status).to have_details expect(status).to have_action end @@ -58,10 +58,10 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'passed' + expect(status.text).to eq s_('CiStatusText|passed') expect(status.icon).to eq 'status_success' expect(status.favicon).to eq 'favicon_status_success' - expect(status.label).to eq 'passed' + expect(status.label).to eq s_('CiStatusLabel|passed') expect(status).to have_details expect(status).to have_action end @@ -86,11 +86,11 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'failed' + expect(status.text).to eq s_('CiStatusText|failed') expect(status.icon).to eq 'status_failed' expect(status.favicon).to eq 'favicon_status_failed' - expect(status.label).to eq 'failed' - expect(status.status_tooltip).to eq 'failed - (unknown failure)' + expect(status.label).to eq s_('CiStatusLabel|failed') + expect(status.status_tooltip).to eq "#{s_('CiStatusText|failed')} - (unknown failure)" expect(status).to have_details expect(status).to have_action end @@ -115,7 +115,7 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'failed' + expect(status.text).to eq s_('CiStatusText|failed') expect(status.icon).to eq 'status_warning' expect(status.favicon).to eq 'favicon_status_failed' expect(status.label).to eq 'failed (allowed to fail)' @@ -144,10 +144,10 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'failed' + expect(status.text).to eq s_('CiStatusText|failed') expect(status.icon).to eq 'status_failed' expect(status.favicon).to eq 'favicon_status_failed' - expect(status.label).to eq 'failed' + expect(status.label).to eq s_('CiStatusLabel|failed') expect(status).to have_details expect(status).to have_action expect(status.action_title).to include 'Retry' @@ -173,11 +173,11 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'canceled' + expect(status.text).to eq s_('CiStatusText|canceled') expect(status.icon).to eq 'status_canceled' expect(status.favicon).to eq 'favicon_status_canceled' expect(status.illustration).to include(:image, :size, :title) - expect(status.label).to eq 'canceled' + expect(status.label).to eq s_('CiStatusLabel|canceled') expect(status).to have_details expect(status).to have_action end @@ -200,10 +200,10 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'running' + expect(status.text).to eq s_('CiStatus|running') expect(status.icon).to eq 'status_running' expect(status.favicon).to eq 'favicon_status_running' - expect(status.label).to eq 'running' + expect(status.label).to eq s_('CiStatus|running') expect(status).to have_details expect(status).to have_action end @@ -226,11 +226,11 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'pending' + expect(status.text).to eq s_('CiStatusText|pending') expect(status.icon).to eq 'status_pending' expect(status.favicon).to eq 'favicon_status_pending' expect(status.illustration).to include(:image, :size, :title, :content) - expect(status.label).to eq 'pending' + expect(status.label).to eq s_('CiStatusLabel|pending') expect(status).to have_details expect(status).to have_action end @@ -252,11 +252,11 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'skipped' + expect(status.text).to eq s_('CiStatusText|skipped') expect(status.icon).to eq 'status_skipped' expect(status.favicon).to eq 'favicon_status_skipped' expect(status.illustration).to include(:image, :size, :title) - expect(status.label).to eq 'skipped' + expect(status.label).to eq s_('CiStatusLabel|skipped') expect(status).to have_details expect(status).not_to have_action end @@ -282,7 +282,7 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'manual' + expect(status.text).to eq s_('CiStatusText|manual') expect(status.group).to eq 'manual' expect(status.icon).to eq 'status_manual' expect(status.favicon).to eq 'favicon_status_manual' @@ -339,7 +339,7 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'manual' + expect(status.text).to eq s_('CiStatusText|manual') expect(status.group).to eq 'manual' expect(status.icon).to eq 'status_manual' expect(status.favicon).to eq 'favicon_status_manual' @@ -370,7 +370,7 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'delayed' + expect(status.text).to eq s_('CiStatusText|delayed') expect(status.group).to eq 'scheduled' expect(status.icon).to eq 'status_scheduled' expect(status.favicon).to eq 'favicon_status_scheduled' diff --git a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb index 876ba712d05..664915ba552 100644 --- a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb @@ -23,6 +23,7 @@ describe Gitlab::Ci::Status::Pipeline::Blocked do describe '.matches?' do let(:user) { double('user') } + subject { described_class.matches?(pipeline, user) } context 'when pipeline is blocked' do diff --git a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb index 90b797965b3..aba403de712 100644 --- a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb @@ -23,6 +23,7 @@ describe Gitlab::Ci::Status::Pipeline::Delayed do describe '.matches?' do let(:user) { double('user') } + subject { described_class.matches?(pipeline, user) } context 'when pipeline is scheduled' do 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 new file mode 100644 index 00000000000..c2f9930056a --- /dev/null +++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Auto-DevOps.gitlab-ci.yml' do + subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps') } + + describe 'the created pipeline' do + let(:user) { create(:admin) } + let(:default_branch) { 'master' } + let(:pipeline_branch) { default_branch } + let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } + let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } + let(:pipeline) { service.execute!(:push) } + let(:build_names) { pipeline.builds.pluck(:name) } + + before do + stub_ci_pipeline_yaml_file(template.content) + allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true) + allow(project).to receive(:default_branch).and_return(default_branch) + end + + it 'creates a build and a test job' do + expect(build_names).to include('build', 'test') + end + + 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)) + end + + it 'does not create any deployment-related builds' do + expect(build_names).not_to include('production') + expect(build_names).not_to include('production_manual') + expect(build_names).not_to include('staging') + expect(build_names).not_to include('canary') + expect(build_names).not_to include('review') + expect(build_names).not_to include(a_string_matching(/rollout \d+%/)) + end + end + + context 'when the project has an active cluster' do + let(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) } + + before do + allow(cluster).to receive(:active?).and_return(true) + end + + describe 'deployment-related builds' do + context 'on default branch' do + it 'does not include rollout jobs besides production' do + expect(build_names).to include('production') + expect(build_names).not_to include('production_manual') + expect(build_names).not_to include('staging') + expect(build_names).not_to include('canary') + expect(build_names).not_to include('review') + expect(build_names).not_to include(a_string_matching(/rollout \d+%/)) + end + + context 'when STAGING_ENABLED=1' do + before do + create(:ci_variable, project: project, key: 'STAGING_ENABLED', value: '1') + end + + it 'includes a staging job and a production_manual job' do + expect(build_names).not_to include('production') + expect(build_names).to include('production_manual') + expect(build_names).to include('staging') + expect(build_names).not_to include('canary') + expect(build_names).not_to include('review') + expect(build_names).not_to include(a_string_matching(/rollout \d+%/)) + end + end + + context 'when CANARY_ENABLED=1' do + before do + create(:ci_variable, project: project, key: 'CANARY_ENABLED', value: '1') + end + + it 'includes a canary job and a production_manual job' do + expect(build_names).not_to include('production') + expect(build_names).to include('production_manual') + expect(build_names).not_to include('staging') + expect(build_names).to include('canary') + expect(build_names).not_to include('review') + expect(build_names).not_to include(a_string_matching(/rollout \d+%/)) + end + end + end + + context 'outside of default branch' do + let(:pipeline_branch) { 'patch-1' } + + before do + project.repository.create_branch(pipeline_branch) + end + + it 'does not include rollout jobs besides review' do + expect(build_names).not_to include('production') + expect(build_names).not_to include('production_manual') + expect(build_names).not_to include('staging') + expect(build_names).not_to include('canary') + expect(build_names).to include('review') + expect(build_names).not_to include(a_string_matching(/rollout \d+%/)) + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb new file mode 100644 index 00000000000..2a6314755ef --- /dev/null +++ b/spec/lib/gitlab/ci/templates/managed_cluster_applications_gitlab_ci_yaml_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Managed-Cluster-Applications.gitlab-ci.yml' do + subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Managed-Cluster-Applications') } + + describe 'the created pipeline' do + let_it_be(:user) { create(:user) } + + let(:project) { create(:project, :custom_repo, namespace: user.namespace, files: { 'README.md' => '' }) } + let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } + let(:pipeline) { service.execute!(:push) } + let(:build_names) { pipeline.builds.pluck(:name) } + let(:pipeline_branch) { 'master' } + + before do + stub_ci_pipeline_yaml_file(template.content) + end + + context 'for a default branch' do + it 'creates a apply job' do + expect(build_names).to match_array('apply') + end + end + + context 'outside of default branch' do + let(:pipeline_branch) { 'a_branch' } + + before do + project.repository.create_branch(pipeline_branch) + end + + it 'has no jobs' do + expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError, 'No stages / jobs for this pipeline.') + end + end + end +end diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb index e0077a5280a..795e8e51276 100644 --- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb +++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb @@ -112,8 +112,9 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do end it 'calls get_chunk only once' do - expect_any_instance_of(Gitlab::Ci::Trace::ChunkedIO) - .to receive(:current_chunk).once.and_call_original + expect_next_instance_of(Gitlab::Ci::Trace::ChunkedIO) do |instance| + expect(instance).to receive(:current_chunk).once.and_call_original + end chunked_io.each_line { |line| } end diff --git a/spec/lib/gitlab/ci/trace/section_parser_spec.rb b/spec/lib/gitlab/ci/trace/section_parser_spec.rb index 5e2efe083be..6e8504a1584 100644 --- a/spec/lib/gitlab/ci/trace/section_parser_spec.rb +++ b/spec/lib/gitlab/ci/trace/section_parser_spec.rb @@ -21,6 +21,7 @@ describe Gitlab::Ci::Trace::SectionParser do end let(:lines) { build_lines('') } + subject { described_class.new(lines) } describe '#sections' do diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 4b1c7483b11..8f9c5c74260 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -28,6 +28,7 @@ module Gitlab stage: "test", stage_idx: 2, name: "rspec", + only: { refs: %w[branches tags] }, options: { before_script: ["pwd"], script: ["rspec"] @@ -120,6 +121,7 @@ module Gitlab stage: "test", stage_idx: 2, name: "rspec", + only: { refs: %w[branches tags] }, options: { script: ["rspec"] }, interruptible: true, allow_failure: false, @@ -149,6 +151,28 @@ module Gitlab expect(subject[:options]).not_to have_key(:retry) end end + + context 'when retry count is specified by default' do + let(:config) do + YAML.dump(default: { retry: { max: 1 } }, + rspec: { script: 'rspec' }) + end + + it 'does use the default value' do + expect(subject[:options]).to include(retry: { max: 1 }) + end + end + + context 'when retry count default value is overridden' do + let(:config) do + YAML.dump(default: { retry: { max: 1 } }, + rspec: { script: 'rspec', retry: { max: 2 } }) + end + + it 'does use the job value' do + expect(subject[:options]).to include(retry: { max: 2 }) + end + end end describe 'allow failure entry' do @@ -244,8 +268,7 @@ module Gitlab when: "on_success", yaml_variables: [], options: { script: ["rspec"] }, - only: { refs: ["branches"] }, - except: {} }] }, + only: { refs: ["branches"] } }] }, { name: "deploy", index: 3, builds: @@ -256,8 +279,7 @@ module Gitlab when: "on_success", yaml_variables: [], options: { script: ["cap prod"] }, - only: { refs: ["tags"] }, - except: {} }] }, + only: { refs: ["tags"] } }] }, { name: ".post", index: 4, builds: [] }] @@ -594,6 +616,7 @@ module Gitlab stage: "test", stage_idx: 2, name: "rspec", + only: { refs: %w[branches tags] }, options: { before_script: ["pwd"], script: ["rspec"], @@ -625,6 +648,7 @@ module Gitlab stage: "test", stage_idx: 2, name: "rspec", + only: { refs: %w[branches tags] }, options: { before_script: ["pwd"], script: ["rspec"], @@ -654,6 +678,7 @@ module Gitlab stage: "test", stage_idx: 2, name: "rspec", + only: { refs: %w[branches tags] }, options: { before_script: ["pwd"], script: ["rspec"], @@ -679,6 +704,7 @@ module Gitlab stage: "test", stage_idx: 2, name: "rspec", + only: { refs: %w[branches tags] }, options: { before_script: ["pwd"], script: ["rspec"], @@ -1193,6 +1219,7 @@ module Gitlab stage: "test", stage_idx: 2, name: "rspec", + only: { refs: %w[branches tags] }, options: { before_script: ["pwd"], script: ["rspec"], @@ -1375,7 +1402,7 @@ module Gitlab end it 'raises an error for invalid number' do - expect { builds }.to raise_error('jobs:deploy_to_production timeout should be a duration') + expect { builds }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:deploy_to_production:timeout config should be a duration') end end @@ -1490,6 +1517,7 @@ module Gitlab stage: "build", stage_idx: 1, name: "build1", + only: { refs: %w[branches tags] }, options: { script: ["test"] }, @@ -1501,10 +1529,53 @@ module Gitlab stage: "test", stage_idx: 2, name: "test1", + only: { refs: %w[branches tags] }, options: { script: ["test"] }, needs_attributes: [ - { name: "build1" }, - { name: "build2" } + { name: "build1", artifacts: true }, + { name: "build2", artifacts: true } + ], + when: "on_success", + allow_failure: false, + yaml_variables: [] + ) + end + end + + context 'needs two builds' do + let(:needs) do + [ + { job: 'parallel', artifacts: false }, + { job: 'build1', artifacts: true }, + 'build2' + ] + end + + it "does create jobs with valid specification" do + expect(subject.builds.size).to eq(7) + expect(subject.builds[0]).to eq( + stage: "build", + stage_idx: 1, + name: "build1", + only: { refs: %w[branches tags] }, + options: { + script: ["test"] + }, + when: "on_success", + allow_failure: false, + yaml_variables: [] + ) + expect(subject.builds[4]).to eq( + stage: "test", + stage_idx: 2, + name: "test1", + only: { refs: %w[branches tags] }, + options: { script: ["test"] }, + needs_attributes: [ + { name: "parallel 1/2", artifacts: false }, + { name: "parallel 2/2", artifacts: false }, + { name: "build1", artifacts: true }, + { name: "build2", artifacts: true } ], when: "on_success", allow_failure: false, @@ -1522,10 +1593,41 @@ module Gitlab stage: "test", stage_idx: 2, name: "test1", + only: { refs: %w[branches tags] }, options: { script: ["test"] }, needs_attributes: [ - { name: "parallel 1/2" }, - { name: "parallel 2/2" } + { name: "parallel 1/2", artifacts: true }, + { name: "parallel 2/2", artifacts: true } + ], + when: "on_success", + allow_failure: false, + yaml_variables: [] + ) + end + end + + context 'needs dependencies artifacts' do + let(:needs) do + [ + "build1", + { job: "build2" }, + { job: "parallel", artifacts: true } + ] + end + + it "does create jobs with valid specification" do + expect(subject.builds.size).to eq(7) + expect(subject.builds[4]).to eq( + stage: "test", + stage_idx: 2, + name: "test1", + only: { refs: %w[branches tags] }, + options: { script: ["test"] }, + needs_attributes: [ + { name: "build1", artifacts: true }, + { name: "build2", artifacts: true }, + { name: "parallel 1/2", artifacts: true }, + { name: "parallel 2/2", artifacts: true } ], when: "on_success", allow_failure: false, @@ -1617,6 +1719,7 @@ module Gitlab describe "Hidden jobs" do let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) } + subject { config_processor.stage_builds_attributes("test") } shared_examples 'hidden_job_handling' do @@ -1626,6 +1729,7 @@ module Gitlab stage: "test", stage_idx: 2, name: "normal_job", + only: { refs: %w[branches tags] }, options: { script: ["test"] }, @@ -1661,6 +1765,7 @@ module Gitlab describe "YAML Alias/Anchor" do let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) } + subject { config_processor.stage_builds_attributes("build") } shared_examples 'job_templates_handling' do @@ -1670,6 +1775,7 @@ module Gitlab stage: "build", stage_idx: 1, name: "job1", + only: { refs: %w[branches tags] }, options: { script: ["execute-script-for-job"] }, @@ -1681,6 +1787,7 @@ module Gitlab stage: "build", stage_idx: 1, name: "job2", + only: { refs: %w[branches tags] }, options: { script: ["execute-script-for-job"] }, @@ -1758,7 +1865,7 @@ module Gitlab config = YAML.dump({ rspec: { script: "test", tags: "mysql" } }) expect do Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec tags should be an array of strings") + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:tags config should be an array of strings") end it "returns errors if before_script parameter is invalid" do @@ -2106,7 +2213,7 @@ module Gitlab context "when the tags parameter is invalid" do let(:content) { YAML.dump({ rspec: { script: "test", tags: "mysql" } }) } - it { is_expected.to eq "jobs:rspec tags should be an array of strings" } + it { is_expected.to eq "jobs:rspec:tags config should be an array of strings" } end context "when YAML content is empty" do @@ -2127,6 +2234,70 @@ module Gitlab it { is_expected.to be_nil } end end + + describe '.new_with_validation_errors' do + subject { Gitlab::Ci::YamlProcessor.new_with_validation_errors(content) } + + context 'when the YAML could not be parsed' do + let(:content) { YAML.dump('invalid: yaml: test') } + + it 'returns errors and empty configuration' do + expect(subject.valid?).to eq(false) + expect(subject.errors).to eq(['Invalid configuration format']) + expect(subject.content).to be_blank + end + end + + context 'when the tags parameter is invalid' do + let(:content) { YAML.dump({ rspec: { script: 'test', tags: 'mysql' } }) } + + it 'returns errors and empty configuration' do + expect(subject.valid?).to eq(false) + expect(subject.errors).to eq(['jobs:rspec:tags config should be an array of strings']) + expect(subject.content).to be_blank + end + end + + context 'when the configuration contains multiple keyword-syntax errors' do + let(:content) { YAML.dump({ rspec: { script: 'test', bad_tags: 'mysql', rules: { wrong: 'format' } } }) } + + it 'returns errors and empty configuration' do + expect(subject.valid?).to eq(false) + expect(subject.errors).to eq(['jobs:rspec config contains unknown keys: bad_tags', 'jobs:rspec rules should be an array of hashes']) + expect(subject.content).to be_blank + end + end + + context 'when YAML content is empty' do + let(:content) { '' } + + it 'returns errors and empty configuration' do + expect(subject.valid?).to eq(false) + expect(subject.errors).to eq(['Please provide content of .gitlab-ci.yml']) + expect(subject.content).to be_blank + end + end + + context 'when the YAML contains an unknown alias' do + let(:content) { 'steps: *bad_alias' } + + it 'returns errors and empty configuration' do + expect(subject.valid?).to eq(false) + expect(subject.errors).to eq(['Unknown alias: bad_alias']) + expect(subject.content).to be_blank + end + end + + context 'when the YAML is valid' do + let(:content) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) } + + it 'returns errors and empty configuration' do + expect(subject.valid?).to eq(true) + expect(subject.errors).to be_empty + expect(subject.content).to be_present + end + end + end end end end diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb index fc9792e16d7..75ef75fccc9 100644 --- a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb +++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::Cleanup::OrphanJobArtifactFiles do let(:null_logger) { Logger.new('/dev/null') } + subject(:cleanup) { described_class.new(logger: null_logger) } before do diff --git a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb index c5b17aafdd2..9c16fb6f6dc 100644 --- a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb @@ -9,7 +9,9 @@ shared_examples 'base stage' do before do allow(stage).to receive(:project_median).and_return(1.12) - allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({}) + allow_next_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher) do |instance| + allow(instance).to receive(:event_result).and_return({}) + end end it 'has the median data value' do diff --git a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb index d4ab9bc225b..41ce9355708 100644 --- a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb @@ -17,7 +17,9 @@ describe Gitlab::CycleAnalytics::UsageData do projects.each_with_index do |project, time| issue = create(:issue, project: project, created_at: (time + 1).hour.ago) - allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue]) + allow_next_instance_of(Gitlab::ReferenceExtractor) do |instance| + allow(instance).to receive(:issues).and_return([issue]) + end milestone = create(:milestone, project: project) mr = create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") diff --git a/spec/lib/gitlab/danger/changelog_spec.rb b/spec/lib/gitlab/danger/changelog_spec.rb new file mode 100644 index 00000000000..888094eaf6e --- /dev/null +++ b/spec/lib/gitlab/danger/changelog_spec.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'rspec-parameterized' +require_relative 'danger_spec_helper' + +require 'gitlab/danger/changelog' + +describe Gitlab::Danger::Changelog do + using RSpec::Parameterized::TableSyntax + include DangerSpecHelper + + let(:added_files) { nil } + let(:fake_git) { double('fake-git', added_files: added_files) } + + let(:mr_labels) { nil } + let(:mr_json) { nil } + let(:fake_gitlab) { double('fake-gitlab', mr_labels: mr_labels, mr_json: mr_json) } + + let(:changes_by_category) { nil } + let(:ee?) { false } + let(:fake_helper) { double('fake-helper', changes_by_category: changes_by_category, ee?: ee?) } + + let(:fake_danger) { new_fake_danger.include(described_class) } + + subject(:changelog) { fake_danger.new(git: fake_git, gitlab: fake_gitlab, helper: fake_helper) } + + describe '#needed?' do + subject { changelog.needed? } + + [ + { docs: nil }, + { none: nil }, + { docs: nil, none: nil } + ].each do |categories| + let(:changes_by_category) { categories } + it "is falsy when categories don't require a changelog" do + is_expected.to be_falsy + end + end + + where(:categories, :labels) do + { backend: nil } | %w[backend backstage] + { frontend: nil, docs: nil } | ['ci-build'] + { engineering_productivity: nil, none: nil } | ['meta'] + end + + with_them do + let(:changes_by_category) { categories } + let(:mr_labels) { labels } + + it "is falsy when labels require no changelog" do + is_expected.to be_falsy + end + end + + where(:categories, :labels) do + { frontend: nil, docs: nil } | ['database::review pending', 'feature'] + { backend: nil } | ['backend', 'technical debt'] + { engineering_productivity: nil, none: nil } | ['frontend'] + end + + with_them do + let(:changes_by_category) { categories } + let(:mr_labels) { labels } + + it "is truthy when categories and labels require a changelog" do + is_expected.to be_truthy + end + end + end + + describe '#found' do + subject { changelog.found } + + context 'added files contain a changelog' do + [ + 'changelogs/unreleased/entry.md', + 'ee/changelogs/unreleased/entry.md', + 'changelogs/unreleased-ee/entry.md', + 'ee/changelogs/unreleased-ee/entry.md' + ].each do |file_path| + let(:added_files) { [file_path] } + + it { is_expected.to be_truthy } + end + end + + context 'added files do not contain a changelog' do + [ + 'app/models/model.rb', + 'app/assets/javascripts/file.js' + ].each do |file_path| + let(:added_files) { [file_path] } + it { is_expected.to eq(nil) } + end + end + end + + describe '#presented_no_changelog_labels' do + subject { changelog.presented_no_changelog_labels } + + it 'returns the labels formatted' do + is_expected.to eq('~backstage, ~ci-build, ~meta') + end + end + + describe '#sanitized_mr_title' do + subject { changelog.sanitized_mr_title } + + [ + 'WIP: My MR title', + 'My MR title' + ].each do |mr_title| + let(:mr_json) { { "title" => mr_title } } + it { is_expected.to eq("My MR title") } + end + end + + describe '#ee_changelog?' do + context 'is ee changelog' do + [ + 'changelogs/unreleased-ee/entry.md', + 'ee/changelogs/unreleased-ee/entry.md' + ].each do |file_path| + subject { changelog.ee_changelog?(file_path) } + + it { is_expected.to be_truthy } + end + end + + context 'is not ee changelog' do + [ + 'changelogs/unreleased/entry.md', + 'ee/changelogs/unreleased/entry.md' + ].each do |file_path| + subject { changelog.ee_changelog?(file_path) } + + it { is_expected.to be_falsy } + end + end + end + + describe '#ce_port_changelog?' do + where(:helper_ee?, :file_path, :expected) do + true | 'changelogs/unreleased-ee/entry.md' | false + true | 'ee/changelogs/unreleased-ee/entry.md' | false + false | 'changelogs/unreleased-ee/entry.md' | false + false | 'ee/changelogs/unreleased-ee/entry.md' | false + true | 'changelogs/unreleased/entry.md' | true + true | 'ee/changelogs/unreleased/entry.md' | true + false | 'changelogs/unreleased/entry.md' | false + false | 'ee/changelogs/unreleased/entry.md' | false + end + + with_them do + let(:ee?) { helper_ee? } + subject { changelog.ce_port_changelog?(file_path) } + + it { is_expected.to eq(expected) } + end + end +end diff --git a/spec/lib/gitlab/danger/danger_spec_helper.rb b/spec/lib/gitlab/danger/danger_spec_helper.rb new file mode 100644 index 00000000000..b1e84b3c13d --- /dev/null +++ b/spec/lib/gitlab/danger/danger_spec_helper.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module DangerSpecHelper + def new_fake_danger + Class.new do + attr_reader :git, :gitlab, :helper + + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def initialize(git: nil, gitlab: nil, helper: nil) + @git = git + @gitlab = gitlab + @helper = helper + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + end + end +end diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb index 8056418e697..d7e67444fca 100644 --- a/spec/lib/gitlab/danger/helper_spec.rb +++ b/spec/lib/gitlab/danger/helper_spec.rb @@ -2,29 +2,22 @@ require 'fast_spec_helper' require 'rspec-parameterized' +require_relative 'danger_spec_helper' require 'gitlab/danger/helper' describe Gitlab::Danger::Helper do using RSpec::Parameterized::TableSyntax - - class FakeDanger - include Gitlab::Danger::Helper - - attr_reader :git, :gitlab - - def initialize(git:, gitlab:) - @git = git - @gitlab = gitlab - end - end + include DangerSpecHelper let(:fake_git) { double('fake-git') } let(:mr_author) { nil } let(:fake_gitlab) { double('fake-gitlab', mr_author: mr_author) } - subject(:helper) { FakeDanger.new(git: fake_git, gitlab: fake_gitlab) } + let(:fake_danger) { new_fake_danger.include(described_class) } + + subject(:helper) { fake_danger.new(git: fake_git, gitlab: fake_gitlab) } describe '#gitlab_helper' do context 'when gitlab helper is not available' do diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb index 35edfa08a63..bf6152ff3c2 100644 --- a/spec/lib/gitlab/danger/teammate_spec.rb +++ b/spec/lib/gitlab/danger/teammate_spec.rb @@ -33,8 +33,8 @@ describe Gitlab::Danger::Teammate do context 'when labels contain devops::create and the category is test' do let(:labels) { ['devops::create'] } - context 'when role is Test Automation Engineer, Create' do - let(:role) { 'Test Automation Engineer, Create' } + context 'when role is Software Engineer in Test, Create' do + let(:role) { 'Software Engineer in Test, Create' } it '#reviewer? returns true' do expect(subject.reviewer?(project, :test, labels)).to be_truthy @@ -45,7 +45,7 @@ describe Gitlab::Danger::Teammate do end context 'when hyperlink is mangled in the role' do - let(:role) { '<a href="#">Test Automation Engineer</a>, Create' } + let(:role) { '<a href="#">Software Engineer in Test</a>, Create' } it '#reviewer? returns true' do expect(subject.reviewer?(project, :test, labels)).to be_truthy @@ -53,16 +53,16 @@ describe Gitlab::Danger::Teammate do end end - context 'when role is Test Automation Engineer' do - let(:role) { 'Test Automation Engineer' } + context 'when role is Software Engineer in Test' do + let(:role) { 'Software Engineer in Test' } it '#reviewer? returns false' do expect(subject.reviewer?(project, :test, labels)).to be_falsey end end - context 'when role is Test Automation Engineer, Manage' do - let(:role) { 'Test Automation Engineer, Manage' } + context 'when role is Software Engineer in Test, Manage' do + let(:role) { 'Software Engineer in Test, Manage' } it '#reviewer? returns false' do expect(subject.reviewer?(project, :test, labels)).to be_falsey diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb index b170ef788d9..fdb855de786 100644 --- a/spec/lib/gitlab/data_builder/build_spec.rb +++ b/spec/lib/gitlab/data_builder/build_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe Gitlab::DataBuilder::Build do - let(:build) { create(:ci_build) } + let(:runner) { create(:ci_runner, :instance) } + let(:build) { create(:ci_build, :running, runner: runner) } describe '.build' do let(:data) do @@ -20,6 +21,10 @@ describe Gitlab::DataBuilder::Build do it { expect(data[:build_failure_reason]).to eq(build.failure_reason) } it { expect(data[:project_id]).to eq(build.project.id) } it { expect(data[:project_name]).to eq(build.project.full_name) } + it { expect(data[:pipeline_id]).to eq(build.pipeline.id) } + it { expect(data[:commit][:id]).to eq(build.pipeline.id) } + it { expect(data[:runner][:id]).to eq(build.runner.id) } + it { expect(data[:runner][:description]).to eq(build.runner.description) } context 'commit author_url' do context 'when no commit present' do diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb index 931477d19c2..635bf56b72e 100644 --- a/spec/lib/gitlab/data_builder/pipeline_spec.rb +++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb @@ -34,6 +34,7 @@ describe Gitlab::DataBuilder::Pipeline do expect(build_data).to be_a(Hash) expect(build_data[:id]).to eq(build.id) expect(build_data[:status]).to eq(build.status) + expect(build_data[:allow_failure]).to eq(build.allow_failure) expect(project_data).to eq(project.hook_attrs(backward: false)) expect(data[:merge_request]).to be_nil end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 449eee7a371..cac6908f4b4 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -212,44 +212,118 @@ describe Gitlab::Database::MigrationHelpers do allow(model).to receive(:transaction_open?).and_return(false) end - it 'creates a concurrent foreign key and validates it' do - expect(model).to receive(:disable_statement_timeout).and_call_original - expect(model).to receive(:execute).with(/statement_timeout/) - expect(model).to receive(:execute).ordered.with(/NOT VALID/) - expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) - expect(model).to receive(:execute).with(/RESET ALL/) + context 'ON DELETE statements' do + context 'on_delete: :nullify' do + it 'appends ON DELETE SET NULL statement' do + expect(model).to receive(:disable_statement_timeout).and_call_original + expect(model).to receive(:execute).with(/statement_timeout/) + expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) + expect(model).to receive(:execute).with(/RESET ALL/) + + expect(model).to receive(:execute).with(/ON DELETE SET NULL/) + + model.add_concurrent_foreign_key(:projects, :users, + column: :user_id, + on_delete: :nullify) + end + end - model.add_concurrent_foreign_key(:projects, :users, column: :user_id) - end + context 'on_delete: :cascade' do + it 'appends ON DELETE CASCADE statement' do + expect(model).to receive(:disable_statement_timeout).and_call_original + expect(model).to receive(:execute).with(/statement_timeout/) + expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) + expect(model).to receive(:execute).with(/RESET ALL/) + + expect(model).to receive(:execute).with(/ON DELETE CASCADE/) + + model.add_concurrent_foreign_key(:projects, :users, + column: :user_id, + on_delete: :cascade) + end + end - it 'appends a valid ON DELETE statement' do - expect(model).to receive(:disable_statement_timeout).and_call_original - expect(model).to receive(:execute).with(/statement_timeout/) - expect(model).to receive(:execute).with(/ON DELETE SET NULL/) - expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) - expect(model).to receive(:execute).with(/RESET ALL/) + context 'on_delete: nil' do + it 'appends no ON DELETE statement' do + expect(model).to receive(:disable_statement_timeout).and_call_original + expect(model).to receive(:execute).with(/statement_timeout/) + expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) + expect(model).to receive(:execute).with(/RESET ALL/) - model.add_concurrent_foreign_key(:projects, :users, - column: :user_id, - on_delete: :nullify) + expect(model).not_to receive(:execute).with(/ON DELETE/) + + model.add_concurrent_foreign_key(:projects, :users, + column: :user_id, + on_delete: nil) + end + end end - it 'does not create a foreign key if it exists already' do - expect(model).to receive(:foreign_key_exists?).with(:projects, :users, column: :user_id).and_return(true) - expect(model).not_to receive(:execute).with(/ADD CONSTRAINT/) - expect(model).to receive(:execute).with(/VALIDATE CONSTRAINT/) + context 'when no custom key name is supplied' do + it 'creates a concurrent foreign key and validates it' do + expect(model).to receive(:disable_statement_timeout).and_call_original + expect(model).to receive(:execute).with(/statement_timeout/) + expect(model).to receive(:execute).ordered.with(/NOT VALID/) + expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) + expect(model).to receive(:execute).with(/RESET ALL/) + + model.add_concurrent_foreign_key(:projects, :users, column: :user_id) + end + + it 'does not create a foreign key if it exists already' do + name = model.concurrent_foreign_key_name(:projects, :user_id) + expect(model).to receive(:foreign_key_exists?).with(:projects, :users, + column: :user_id, + on_delete: :cascade, + name: name).and_return(true) + + expect(model).not_to receive(:execute).with(/ADD CONSTRAINT/) + expect(model).to receive(:execute).with(/VALIDATE CONSTRAINT/) - model.add_concurrent_foreign_key(:projects, :users, column: :user_id) + model.add_concurrent_foreign_key(:projects, :users, column: :user_id) + end end - it 'allows the use of a custom key name' do - expect(model).to receive(:disable_statement_timeout).and_call_original - expect(model).to receive(:execute).with(/statement_timeout/) - expect(model).to receive(:execute).ordered.with(/NOT VALID/) - expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+foo/) - expect(model).to receive(:execute).with(/RESET ALL/) + context 'when a custom key name is supplied' do + context 'for creating a new foreign key for a column that does not presently exist' do + it 'creates a new foreign key' do + expect(model).to receive(:disable_statement_timeout).and_call_original + expect(model).to receive(:execute).with(/statement_timeout/) + expect(model).to receive(:execute).ordered.with(/NOT VALID/) + expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+foo/) + expect(model).to receive(:execute).with(/RESET ALL/) + + model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo) + end + end + + context 'for creating a duplicate foreign key for a column that presently exists' do + context 'when the supplied key name is the same as the existing foreign key name' do + it 'does not create a new foreign key' do + expect(model).to receive(:foreign_key_exists?).with(:projects, :users, + name: :foo, + on_delete: :cascade, + column: :user_id).and_return(true) + + expect(model).not_to receive(:execute).with(/ADD CONSTRAINT/) + expect(model).to receive(:execute).with(/VALIDATE CONSTRAINT/) + + model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo) + end + end + + context 'when the supplied key name is different from the existing foreign key name' do + it 'creates a new foreign key' do + expect(model).to receive(:disable_statement_timeout).and_call_original + expect(model).to receive(:execute).with(/statement_timeout/) + expect(model).to receive(:execute).ordered.with(/NOT VALID/) + expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+bar/) + expect(model).to receive(:execute).with(/RESET ALL/) - model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo) + model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :bar) + end + end + end end end end @@ -266,23 +340,61 @@ describe Gitlab::Database::MigrationHelpers do describe '#foreign_key_exists?' do before do - key = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(:projects, :users, { column: :non_standard_id }) + key = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(:projects, :users, { column: :non_standard_id, name: :fk_projects_users_non_standard_id, on_delete: :cascade }) allow(model).to receive(:foreign_keys).with(:projects).and_return([key]) end - it 'finds existing foreign keys by column' do - expect(model.foreign_key_exists?(:projects, :users, column: :non_standard_id)).to be_truthy + shared_examples_for 'foreign key checks' do + it 'finds existing foreign keys by column' do + expect(model.foreign_key_exists?(:projects, target_table, column: :non_standard_id)).to be_truthy + end + + it 'finds existing foreign keys by name' do + expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id)).to be_truthy + end + + it 'finds existing foreign_keys by name and column' do + expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id)).to be_truthy + end + + it 'finds existing foreign_keys by name, column and on_delete' do + expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id, on_delete: :cascade)).to be_truthy + end + + it 'finds existing foreign keys by target table only' do + expect(model.foreign_key_exists?(:projects, target_table)).to be_truthy + end + + it 'compares by column name if given' do + expect(model.foreign_key_exists?(:projects, target_table, column: :user_id)).to be_falsey + end + + it 'compares by foreign key name if given' do + expect(model.foreign_key_exists?(:projects, target_table, name: :non_existent_foreign_key_name)).to be_falsey + end + + it 'compares by foreign key name and column if given' do + expect(model.foreign_key_exists?(:projects, target_table, name: :non_existent_foreign_key_name, column: :non_standard_id)).to be_falsey + end + + it 'compares by foreign key name, column and on_delete if given' do + expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id, on_delete: :nullify)).to be_falsey + end end - it 'finds existing foreign keys by target table only' do - expect(model.foreign_key_exists?(:projects, :users)).to be_truthy + context 'without specifying a target table' do + let(:target_table) { nil } + + it_behaves_like 'foreign key checks' end - it 'compares by column name if given' do - expect(model.foreign_key_exists?(:projects, :users, column: :user_id)).to be_falsey + context 'specifying a target table' do + let(:target_table) { :users } + + it_behaves_like 'foreign key checks' end - it 'compares by target if no column given' do + it 'compares by target table if no column given' do expect(model.foreign_key_exists?(:projects, :other_table)).to be_falsey end end @@ -1328,4 +1440,17 @@ describe Gitlab::Database::MigrationHelpers do end end end + + describe '#create_or_update_plan_limit' do + it 'creates or updates plan limits' do + expect(model).to receive(:execute).with <<~SQL + INSERT INTO plan_limits (plan_id, "project_hooks") + VALUES + ((SELECT id FROM plans WHERE name = 'free' LIMIT 1), '10') + ON CONFLICT (plan_id) DO UPDATE SET "project_hooks" = EXCLUDED."project_hooks"; + SQL + + model.create_or_update_plan_limit('project_hooks', 'free', 10) + end + end end diff --git a/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb b/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb index 6d38f7f1b95..0f68201a153 100644 --- a/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb +++ b/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb @@ -4,25 +4,34 @@ require 'spec_helper' describe Gitlab::Database::ObsoleteIgnoredColumns do module Testing + # Used a fixed date to prevent tests failing across date boundaries + REMOVE_DATE = Date.new(2019, 12, 16) + class MyBase < ApplicationRecord end class SomeAbstract < MyBase + include IgnorableColumns + self.abstract_class = true self.table_name = 'projects' - self.ignored_columns += %i[unused] + ignore_column :unused, remove_after: '2019-01-01', remove_with: '12.0' end class B < MyBase + include IgnorableColumns + self.table_name = 'issues' - self.ignored_columns += %i[id other] + ignore_column :id, :other, remove_after: '2019-01-01', remove_with: '12.0' + ignore_column :not_used_but_still_ignored, remove_after: REMOVE_DATE.to_s, remove_with: '12.1' end class A < SomeAbstract - self.ignored_columns += %i[id also_unused] + ignore_column :also_unused, remove_after: '2019-02-01', remove_with: '12.1' + ignore_column :not_used_but_still_ignored, remove_after: REMOVE_DATE.to_s, remove_with: '12.1' end class C < MyBase @@ -34,10 +43,17 @@ describe Gitlab::Database::ObsoleteIgnoredColumns do describe '#execute' do it 'returns a list of class names and columns pairs' do - expect(subject.execute).to eq([ - ['Testing::A', %w(unused also_unused)], - ['Testing::B', %w(other)] - ]) + Timecop.freeze(Testing::REMOVE_DATE) do + expect(subject.execute).to eq([ + ['Testing::A', { + 'unused' => IgnorableColumns::ColumnIgnore.new(Date.parse('2019-01-01'), '12.0'), + 'also_unused' => IgnorableColumns::ColumnIgnore.new(Date.parse('2019-02-01'), '12.1') + }], + ['Testing::B', { + 'other' => IgnorableColumns::ColumnIgnore.new(Date.parse('2019-01-01'), '12.0') + }] + ]) + end end end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index 612c418e8bb..7b8437e4874 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -83,6 +83,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete describe '#rename_path_for_routable' do context 'for namespaces' do let(:namespace) { create(:namespace, path: 'the-path') } + it "renames namespaces called the-path" do subject.rename_path_for_routable(migration_namespace(namespace)) @@ -159,6 +160,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete describe '#perform_rename' do describe 'for namespaces' do let(:namespace) { create(:namespace, path: 'the-path') } + it 'renames the path' do subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed') diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb index 8c4d7e323fa..46fc48ab3fc 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb @@ -95,6 +95,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : describe '#move_repositories' do let(:namespace) { create(:group, name: 'hello-group') } + it 'moves a project for a namespace' do create(:project, :repository, :legacy_storage, namespace: namespace, path: 'hello-project') expected_path = File.join(TestEnv.repos_path, 'bye-group', 'hello-project.git') diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb index 5b1a17e734d..ee3c99afdf1 100644 --- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb +++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb @@ -279,5 +279,11 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do end end end + + it "tracks successful install" do + expect(Gitlab::Tracking).to receive(:event).with("self_monitoring", "project_created") + + result + end end end diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 15fb1503529..3db8900ed8e 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -228,6 +228,7 @@ describe Gitlab::Database do describe '.bulk_insert' do before do allow(described_class).to receive(:connection).and_return(connection) + allow(described_class).to receive(:version).and_return(version) allow(connection).to receive(:quote_column_name, &:itself) allow(connection).to receive(:quote, &:itself) allow(connection).to receive(:execute) @@ -242,6 +243,8 @@ describe Gitlab::Database do ] end + let_it_be(:version) { 9.6 } + it 'does nothing with empty rows' do expect(connection).not_to receive(:execute) @@ -307,6 +310,30 @@ describe Gitlab::Database do expect(ids).to eq([10]) end + + context 'with version >= 9.5' do + it 'allows setting the upsert to do nothing' do + expect(connection) + .to receive(:execute) + .with(/ON CONFLICT DO NOTHING/) + + described_class + .bulk_insert('test', [{ number: 10 }], on_conflict: :do_nothing) + end + end + + context 'with version < 9.5' do + let(:version) { 9.4 } + + it 'refuses setting the upsert' do + expect(connection) + .not_to receive(:execute) + .with(/ON CONFLICT/) + + described_class + .bulk_insert('test', [{ number: 10 }], on_conflict: :do_nothing) + end + end end end diff --git a/spec/lib/gitlab/diff/deprecated_highlight_cache_spec.rb b/spec/lib/gitlab/diff/deprecated_highlight_cache_spec.rb new file mode 100644 index 00000000000..7e46632ea77 --- /dev/null +++ b/spec/lib/gitlab/diff/deprecated_highlight_cache_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Diff::DeprecatedHighlightCache do + let(:merge_request) { create(:merge_request_with_diffs) } + + subject(:cache) { described_class.new(merge_request.diffs, backend: backend) } + + describe '#decorate' do + let(:backend) { double('backend').as_null_object } + + # Manually creates a Diff::File object to avoid triggering the cache on + # the FileCollection::MergeRequestDiff + 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) + end + + it 'does not calculate highlighting when reading from cache' do + cache.write_if_empty + cache.decorate(diff_file) + + expect_any_instance_of(Gitlab::Diff::Highlight).not_to receive(:highlight) + + diff_file.highlighted_diff_lines + end + + it 'assigns highlighted diff lines to the DiffFile' do + cache.write_if_empty + cache.decorate(diff_file) + + expect(diff_file.highlighted_diff_lines.size).to be > 5 + end + + it 'submits a single reading from the cache' do + cache.decorate(diff_file) + cache.decorate(diff_file) + + expect(backend).to have_received(:read).with(cache.key).once + end + end + + describe '#write_if_empty' do + let(:backend) { double('backend', read: {}).as_null_object } + + it 'submits a single writing to the cache' do + cache.write_if_empty + cache.write_if_empty + + expect(backend).to have_received(:write).with(cache.key, + hash_including('CHANGELOG-false-false-false'), + expires_in: 1.week).once + end + end + + describe '#clear' do + let(:backend) { double('backend').as_null_object } + + it 'clears cache' do + cache.clear + + expect(backend).to have_received(:delete).with(cache.key) + end + end +end diff --git a/spec/lib/gitlab/diff/diff_refs_spec.rb b/spec/lib/gitlab/diff/diff_refs_spec.rb index e12b46c15ad..33a7cf5ae12 100644 --- a/spec/lib/gitlab/diff/diff_refs_spec.rb +++ b/spec/lib/gitlab/diff/diff_refs_spec.rb @@ -7,6 +7,7 @@ describe Gitlab::Diff::DiffRefs do describe '#==' do let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') } + subject { commit.diff_refs } context 'when shas are missing' do @@ -63,6 +64,7 @@ describe Gitlab::Diff::DiffRefs do describe '#compare_in' do context 'with diff refs for the initial commit' do let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') } + subject { commit.diff_refs } it 'returns an appropriate comparison' do @@ -74,6 +76,7 @@ describe Gitlab::Diff::DiffRefs do context 'with diff refs for a commit' do let(:commit) { project.commit('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } + subject { commit.diff_refs } it 'returns an appropriate comparison' do 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 265c6260ca9..7e945d1d140 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 @@ -123,4 +123,8 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do collection_default_args) end end + + it_behaves_like 'cacheable diff collection' do + let(:cacheable_files_count) { batch_size } + end end diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb index d89be6fef4e..a4f74ddc8c2 100644 --- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb @@ -4,22 +4,31 @@ require 'spec_helper' describe Gitlab::Diff::FileCollection::MergeRequestDiff do let(:merge_request) { create(:merge_request) } - let(:subject) { described_class.new(merge_request.merge_request_diff, diff_options: nil) } + let(:diffable) { merge_request.merge_request_diff } + let(:subject) { described_class.new(diffable, diff_options: nil) } let(:diff_files) { subject.diff_files } describe '#diff_files' do it 'does not highlight binary files' do - allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(false) + allow_next_instance_of(Gitlab::Diff::File) do |instance| + allow(instance).to receive(:text?).and_return(false) + end - expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) + expect_next_instance_of(Gitlab::Diff::File) do |instance| + expect(instance).not_to receive(:highlighted_diff_lines) + end diff_files end it 'does not highlight files marked as undiffable in .gitattributes' do - allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(false) + allow_next_instance_of(Gitlab::Diff::File) do |instance| + allow(instance).to receive(:diffable?).and_return(false) + end - expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) + expect_next_instance_of(Gitlab::Diff::File) do |instance| + expect(instance).not_to receive(:highlighted_diff_lines) + end diff_files end @@ -29,13 +38,19 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do let(:diffable) { merge_request.merge_request_diff } end - it 'uses a different cache key if diff line keys change' do - mr_diff = described_class.new(merge_request.merge_request_diff, diff_options: nil) - key = mr_diff.cache_key + context 'using Gitlab::Diff::DeprecatedHighlightCache' do + before do + stub_feature_flags(hset_redis_diff_caching: false) + end + + it 'uses a different cache key if diff line keys change' do + mr_diff = described_class.new(merge_request.merge_request_diff, diff_options: nil) + key = mr_diff.cache_key - stub_const('Gitlab::Diff::Line::SERIALIZE_KEYS', [:foo]) + stub_const('Gitlab::Diff::Line::SERIALIZE_KEYS', [:foo]) - expect(mr_diff.cache_key).not_to eq(key) + expect(mr_diff.cache_key).not_to eq(key) + end end it_behaves_like 'diff statistics' do @@ -46,6 +61,10 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do let(:stub_path) { '.gitignore' } end + it_behaves_like 'cacheable diff collection' do + let(:cacheable_files_count) { diffable.size.to_i } + end + it 'returns a valid instance of a DiffCollection' do expect(diff_files).to be_a(Gitlab::Git::DiffCollection) end diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb index bfcfed4231f..c73ec84e332 100644 --- a/spec/lib/gitlab/diff/highlight_cache_spec.rb +++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb @@ -2,14 +2,46 @@ require 'spec_helper' -describe Gitlab::Diff::HighlightCache do +describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do let(:merge_request) { create(:merge_request_with_diffs) } + let(:diff_hash) do + { ".gitignore-false-false-false" => + [{ line_code: nil, rich_text: nil, text: "@@ -17,3 +17,4 @@ rerun.txt", type: "match", index: 0, old_pos: 17, new_pos: 17 }, + { line_code: "a5cc2925ca8258af241be7e5b0381edf30266302_17_17", + rich_text: " <span id=\"LC17\" class=\"line\" lang=\"plaintext\">pickle-email-*.html</span>\n", + text: " pickle-email-*.html", + type: nil, + index: 1, + old_pos: 17, + new_pos: 17 }, + { line_code: "a5cc2925ca8258af241be7e5b0381edf30266302_18_18", + rich_text: " <span id=\"LC18\" class=\"line\" lang=\"plaintext\">.project</span>\n", + text: " .project", + type: nil, + index: 2, + old_pos: 18, + new_pos: 18 }, + { line_code: "a5cc2925ca8258af241be7e5b0381edf30266302_19_19", + rich_text: " <span id=\"LC19\" class=\"line\" lang=\"plaintext\">config/initializers/secret_token.rb</span>\n", + text: " config/initializers/secret_token.rb", + type: nil, + index: 3, + old_pos: 19, + new_pos: 19 }, + { line_code: "a5cc2925ca8258af241be7e5b0381edf30266302_20_20", + rich_text: "+<span id=\"LC20\" class=\"line\" lang=\"plaintext\">.DS_Store</span>", + text: "+.DS_Store", + type: "new", + index: 4, + old_pos: 20, + new_pos: 20 }] } + end - subject(:cache) { described_class.new(merge_request.diffs, backend: backend) } + let(:cache_key) { cache.key } - describe '#decorate' do - let(:backend) { double('backend').as_null_object } + subject(:cache) { described_class.new(merge_request.diffs) } + describe '#decorate' do # Manually creates a Diff::File object to avoid triggering the cache on # the FileCollection::MergeRequestDiff let(:diff_file) do @@ -37,34 +69,89 @@ describe Gitlab::Diff::HighlightCache do expect(diff_file.highlighted_diff_lines.size).to be > 5 end - it 'submits a single reading from the cache' do - cache.decorate(diff_file) + it 'assigns highlighted diff lines which rich_text are HTML-safe' do + cache.write_if_empty cache.decorate(diff_file) - expect(backend).to have_received(:read).with(cache.key).once + rich_texts = diff_file.highlighted_diff_lines.map(&:rich_text) + + expect(rich_texts).to all(be_html_safe) end end - describe '#write_if_empty' do - let(:backend) { double('backend', read: {}).as_null_object } + shared_examples 'caches missing entries' do + it 'filters the key/value list of entries to be caches for each invocation' do + expect(cache).to receive(:write_to_redis_hash) + .with(hash_including(*paths)) + .once + .and_call_original + + 2.times { cache.write_if_empty } + end + + it 'reads from cache once' do + expect(cache).to receive(:read_cache).once.and_call_original - it 'submits a single writing to the cache' do - cache.write_if_empty cache.write_if_empty + end + end - expect(backend).to have_received(:write).with(cache.key, - hash_including('CHANGELOG-false-false-false'), - expires_in: 1.week).once + describe '#write_if_empty' do + it_behaves_like 'caches missing entries' do + let(:paths) { merge_request.diffs.raw_diff_files.select(&:text?).map(&:file_path) } + end + + context 'different diff_collections for the same diffable' do + before do + cache.write_if_empty + end + + it 'writes an uncached files in the collection to the same redis hash' do + Gitlab::Redis::Cache.with { |r| r.hdel(cache_key, "files/whitespace") } + + expect { cache.write_if_empty } + .to change { Gitlab::Redis::Cache.with { |r| r.hgetall(cache_key) } } + end + end + + context 'when cache initialized with MergeRequestDiffBatch' do + let(:merge_request_diff_batch) do + Gitlab::Diff::FileCollection::MergeRequestDiffBatch.new( + merge_request.merge_request_diff, + 1, + 10, + diff_options: nil) + end + + it_behaves_like 'caches missing entries' do + let(:cache) { described_class.new(merge_request_diff_batch) } + let(:paths) { merge_request_diff_batch.raw_diff_files.select(&:text?).map(&:file_path) } + end end end - describe '#clear' do - let(:backend) { double('backend').as_null_object } + describe '#write_to_redis_hash' do + it 'creates or updates a Redis hash' do + expect { cache.send(:write_to_redis_hash, diff_hash) } + .to change { Gitlab::Redis::Cache.with { |r| r.hgetall(cache_key) } } + end + # Note that this spec and the code it confirms can be removed when + # :hset_redis_diff_caching is fully launched. + # + it 'attempts to clear deprecated cache entries' do + expect_any_instance_of(Gitlab::Diff::DeprecatedHighlightCache) + .to receive(:clear).and_call_original + + cache.send(:write_to_redis_hash, diff_hash) + end + end + + describe '#clear' do it 'clears cache' do - cache.clear + expect_any_instance_of(Redis).to receive(:del).with(cache_key) - expect(backend).to have_received(:delete).with(cache.key) + cache.clear end end end diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index f5d3d14ccc5..ff4ec75358e 100644 --- a/spec/lib/gitlab/diff/highlight_spec.rb +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -105,7 +105,7 @@ describe Gitlab::Diff::Highlight do end it 'keeps the original rich line' do - allow(Gitlab::Sentry).to receive(:track_exception) + 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"} @@ -114,7 +114,7 @@ describe Gitlab::Diff::Highlight do end it 'reports to Sentry if configured' do - expect(Gitlab::Sentry).to receive(:track_exception).and_call_original + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).and_call_original expect { subject }. to raise_exception(RangeError) end diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb index 1739bcd14a8..6a86f885c3b 100644 --- a/spec/lib/gitlab/diff/line_mapper_spec.rb +++ b/spec/lib/gitlab/diff/line_mapper_spec.rb @@ -11,6 +11,7 @@ describe Gitlab::Diff::LineMapper do let(:diffs) { commit.raw_diffs } let(:diff) { diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) } + subject { described_class.new(diff_file) } describe '#old_to_new' do diff --git a/spec/lib/gitlab/diff/line_spec.rb b/spec/lib/gitlab/diff/line_spec.rb index 29b9951ba4c..7961bec9d57 100644 --- a/spec/lib/gitlab/diff/line_spec.rb +++ b/spec/lib/gitlab/diff/line_spec.rb @@ -1,18 +1,48 @@ # frozen_string_literal: true +require 'spec_helper' + describe Gitlab::Diff::Line do - describe '.init_from_hash' do + shared_examples 'line object initialized by hash' do it 'round-trips correctly with to_hash' do - line = described_class.new('<input>', 'match', 0, 0, 1, - parent_file: double(:file), - line_code: double(:line_code), - rich_text: '<input>') - - expect(described_class.init_from_hash(line.to_hash).to_hash) + expect(described_class.safe_init_from_hash(line.to_hash).to_hash) .to eq(line.to_hash) end 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) + end + + describe '.init_from_hash' do + let(:rich_text) { '<input>' } + + it_behaves_like 'line object initialized by hash' + end + + describe '.safe_init_from_hash' do + let(:rich_text) { '<input>' } + + it_behaves_like 'line object initialized by hash' + + it 'ensures rich_text is HTML-safe' do + expect(line.rich_text).not_to be_html_safe + + new_line = described_class.safe_init_from_hash(line.to_hash) + + expect(new_line.rich_text).to be_html_safe + end + + context 'when given hash has no rich_text' do + it_behaves_like 'line object initialized by hash' do + let(:rich_text) { nil } + end + end + end + context "when setting rich text" do it 'escapes any HTML special characters in the diff chunk header' do subject = described_class.new("<input>", "", 0, 0, 0) diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb index 7540da71086..d275bf2c223 100644 --- a/spec/lib/gitlab/diff/parallel_diff_spec.rb +++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb @@ -11,6 +11,7 @@ describe Gitlab::Diff::ParallelDiff do let(:diffs) { commit.raw_diffs } let(:diff) { diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) } + subject { described_class.new(diff_file) } describe '#parallelize' do diff --git a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb index 15ee8c40b55..97d3a49ea90 100644 --- a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb +++ b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb @@ -62,6 +62,15 @@ describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do expect(found.second.size).to eq(2) expect(found.second).to all(be_a(Gitlab::Diff::Line)) end + + it 'returns lines which rich_text are HTML-safe' do + described_class.write_multiple(mapping) + + found = described_class.read_multiple(mapping.keys) + rich_texts = found.flatten.map(&:rich_text) + + expect(rich_texts).to all(be_html_safe) + end end describe '#clear_multiple' do diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb index 2fa86b2b46f..9f5413f9607 100644 --- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb @@ -95,7 +95,9 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do context "something is wrong" do context "when the merge request could not be saved" do before do - allow_any_instance_of(MergeRequest).to receive(:save).and_return(false) + allow_next_instance_of(MergeRequest) do |instance| + allow(instance).to receive(:save).and_return(false) + end end it "raises an InvalidMergeRequestError" do @@ -189,6 +191,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do describe '#patch_attachments' do let(:email_raw) { email_fixture('emails/merge_request_multiple_patches.eml') } let(:mail) { Mail::Message.new(email_raw) } + subject(:handler) { described_class.new(mail, mail_key) } it 'orders attachments ending in `.patch` by name' do diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb index d2920b08956..5229b778ccf 100644 --- a/spec/lib/gitlab/email/handler_spec.rb +++ b/spec/lib/gitlab/email/handler_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' describe Gitlab::Email::Handler do describe '.for' do - it 'picks issue handler if there is not merge request prefix' do + it 'picks issue handler if there is no merge request prefix' do expect(described_class.for('email', 'project+key')).to be_an_instance_of(Gitlab::Email::Handler::CreateIssueHandler) 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 new file mode 100644 index 00000000000..04ef5ba516e --- /dev/null +++ b/spec/lib/gitlab/error_tracking/stack_trace_highlight_decorator_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ErrorTracking::StackTraceHighlightDecorator do + let(:error_event) { build(:error_tracking_error_event) } + + describe '.decorate' do + subject(:decorate) { described_class.decorate(error_event) } + + it 'does not change issue_id' do + expect(decorate.issue_id).to eq(error_event.issue_id) + end + + it 'does not change date_received' do + expect(decorate.date_received).to eq(error_event.date_received) + end + + it 'decorates the stack trace context' do + expect(decorate.stack_trace_entries).to eq( + [ + { + 'function' => 'puts', + 'lineNo' => 14, + 'filename' => 'hello_world.rb', + 'context' => [ + [10, '<span id="LC1" class="line" lang="ruby"><span class="c1"># Ruby example</span></span>'], + [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, %Q[<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>'] + ] + }, + { + 'function' => 'print', + 'lineNo' => 6, + 'filename' => 'HelloWorld.swift', + 'context' => [ + [1, '<span id="LC1" class="line" lang="swift"><span class="c1">// Swift example</span></span>'], + [2, '<span id="LC1" class="line" lang="swift"><span class="kd">struct</span> <span class="kt">HelloWorld</span> <span class="p">{</span></span>'], + [3, '<span id="LC1" class="line" lang="swift"> <span class="k">let</span> <span class="nv">name</span> <span class="o">=</span> <span class="s">"World"</span></span>'], + [4, '<span id="LC1" class="line" lang="swift"></span>'], + [5, '<span id="LC1" class="line" lang="swift"> <span class="kd">static</span> <span class="kd">func</span> <span class="nf">message</span><span class="p">()</span> <span class="p">{</span></span>'], + [6, '<span id="LC1" class="line" lang="swift"> <span class="nf">print</span><span class="p">(</span><span class="s">"Hello, </span><span class="se">\\(</span><span class="k">self</span><span class="o">.</span><span class="n">name</span><span class="se">)</span><span class="s">"</span><span class="p">)</span></span>'], + [7, '<span id="LC1" class="line" lang="swift"> <span class="p">}</span></span>'], + [8, '<span id="LC1" class="line" lang="swift"><span class="p">}</span></span>'] + ] + }, + { + 'filename' => 'blank.txt' + } + ] + ) + end + end +end diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb new file mode 100644 index 00000000000..08718bc92a1 --- /dev/null +++ b/spec/lib/gitlab/error_tracking_spec.rb @@ -0,0 +1,172 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ErrorTracking do + let(:exception) { RuntimeError.new('boom') } + let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' } + + let(:expected_payload_includes) do + [ + { 'exception.class' => 'RuntimeError' }, + { 'exception.message' => 'boom' }, + { 'tags.correlation_id' => 'cid' }, + { 'extra.some_other_info' => 'info' }, + { 'extra.issue_url' => 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' } + ] + end + + before do + stub_sentry_settings + + allow(described_class).to receive(:sentry_dsn).and_return(Gitlab.config.sentry.dsn) + allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid') + + described_class.configure + end + + describe '.with_context' do + it 'adds the expected tags' do + described_class.with_context {} + + expect(Raven.tags_context[:locale].to_s).to eq(I18n.locale.to_s) + expect(Raven.tags_context[Labkit::Correlation::CorrelationId::LOG_KEY.to_sym].to_s) + .to eq('cid') + end + end + + describe '.track_and_raise_for_dev_exception' do + context 'when exceptions for dev should be raised' do + before do + expect(described_class).to receive(:should_raise_for_dev?).and_return(true) + end + + it 'raises the exception' do + expect(Raven).to receive(:capture_exception) + + expect { described_class.track_and_raise_for_dev_exception(exception) } + .to raise_error(RuntimeError) + end + end + + context 'when exceptions for dev should not be raised' do + before do + expect(described_class).to receive(:should_raise_for_dev?).and_return(false) + end + + it 'logs the exception with all attributes passed' do + expected_extras = { + some_other_info: 'info', + issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' + } + + expected_tags = { + correlation_id: 'cid' + } + + expect(Raven).to receive(:capture_exception) + .with(exception, + tags: a_hash_including(expected_tags), + extra: a_hash_including(expected_extras)) + + described_class.track_and_raise_for_dev_exception( + exception, + issue_url: issue_url, + some_other_info: 'info' + ) + end + + it 'calls Gitlab::ErrorTracking::Logger.error with formatted payload' do + expect(Gitlab::ErrorTracking::Logger).to receive(:error) + .with(a_hash_including(*expected_payload_includes)) + + described_class.track_and_raise_for_dev_exception( + exception, + issue_url: issue_url, + some_other_info: 'info' + ) + end + end + end + + describe '.track_and_raise_exception' do + it 'always raises the exception' do + expect(Raven).to receive(:capture_exception) + + expect { described_class.track_and_raise_exception(exception) } + .to raise_error(RuntimeError) + end + + it 'calls Gitlab::ErrorTracking::Logger.error with formatted payload' do + expect(Gitlab::ErrorTracking::Logger).to receive(:error) + .with(a_hash_including(*expected_payload_includes)) + + expect do + described_class.track_and_raise_exception( + exception, + issue_url: issue_url, + some_other_info: 'info' + ) + end.to raise_error(RuntimeError) + end + end + + describe '.track_exception' do + it 'calls Raven.capture_exception' do + expected_extras = { + some_other_info: 'info', + issue_url: issue_url + } + + expected_tags = { + correlation_id: 'cid' + } + + expect(Raven).to receive(:capture_exception) + .with(exception, + tags: a_hash_including(expected_tags), + extra: a_hash_including(expected_extras)) + + described_class.track_exception( + exception, + issue_url: issue_url, + some_other_info: 'info' + ) + end + + it 'calls Gitlab::ErrorTracking::Logger.error with formatted payload' do + expect(Gitlab::ErrorTracking::Logger).to receive(:error) + .with(a_hash_including(*expected_payload_includes)) + + described_class.track_exception( + exception, + issue_url: issue_url, + some_other_info: 'info' + ) + end + + context 'the exception implements :sentry_extra_data' do + let(:extra_info) { { event: 'explosion', size: :massive } } + let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info, backtrace: caller) } + + it 'includes the extra data from the exception in the tracking information' do + expect(Raven).to receive(:capture_exception) + .with(exception, a_hash_including(extra: a_hash_including(extra_info))) + + described_class.track_exception(exception) + end + end + + context 'the exception implements :sentry_extra_data, which returns nil' do + let(:exception) { double(message: 'bang!', sentry_extra_data: nil, backtrace: caller) } + + it 'just includes the other extra info' do + extra_info = { issue_url: issue_url } + expect(Raven).to receive(:capture_exception) + .with(exception, a_hash_including(extra: a_hash_including(extra_info))) + + described_class.track_exception(exception, extra_info) + end + end + end +end diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb index e7734c6f9f6..24df67b3058 100644 --- a/spec/lib/gitlab/etag_caching/middleware_spec.rb +++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb @@ -38,8 +38,9 @@ describe Gitlab::EtagCaching::Middleware do end it 'generates ETag' do - expect_any_instance_of(Gitlab::EtagCaching::Store) - .to receive(:touch).and_return('123') + expect_next_instance_of(Gitlab::EtagCaching::Store) do |instance| + expect(instance).to receive(:touch).and_return('123') + end middleware.call(build_request(path, if_none_match)) end @@ -177,9 +178,9 @@ describe Gitlab::EtagCaching::Middleware do 'SCRIPT_NAME' => '/relative-gitlab' } - expect_any_instance_of(Gitlab::EtagCaching::Store) - .to receive(:get).with("/relative-gitlab#{enabled_path}") - .and_return(nil) + expect_next_instance_of(Gitlab::EtagCaching::Store) do |instance| + expect(instance).to receive(:get).with("/relative-gitlab#{enabled_path}").and_return(nil) + end middleware.call(env) end @@ -190,8 +191,9 @@ describe Gitlab::EtagCaching::Middleware do end def mock_value_in_store(value) - allow_any_instance_of(Gitlab::EtagCaching::Store) - .to receive(:get).and_return(value) + allow_next_instance_of(Gitlab::EtagCaching::Store) do |instance| + allow(instance).to receive(:get).and_return(value) + end end def build_request(path, if_none_match) diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb index 8fcd4eb3c21..e25ce4df4aa 100644 --- a/spec/lib/gitlab/etag_caching/router_spec.rb +++ b/spec/lib/gitlab/etag_caching/router_spec.rb @@ -12,6 +12,15 @@ describe Gitlab::EtagCaching::Router do expect(result.name).to eq 'issue_notes' end + it 'matches MR notes endpoint' do + result = described_class.match( + '/my-group/and-subgroup/here-comes-the-project/noteable/merge_request/1/notes' + ) + + expect(result).to be_present + expect(result.name).to eq 'merge_request_notes' + end + it 'matches issue title endpoint' do result = described_class.match( '/my-group/my-project/issues/123/realtime_changes' diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb index 9be6ace3be5..b8be72cf8d7 100644 --- a/spec/lib/gitlab/experimentation_spec.rb +++ b/spec/lib/gitlab/experimentation_spec.rb @@ -158,7 +158,9 @@ describe Gitlab::Experimentation do context 'the user is part of the control group' do before do - allow_any_instance_of(described_class).to receive(:experiment_enabled?).with(:test_experiment).and_return(false) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:experiment_enabled?).with(:test_experiment).and_return(false) + end end it 'pushes the right parameters to gon' do diff --git a/spec/lib/gitlab/external_authorization/client_spec.rb b/spec/lib/gitlab/external_authorization/client_spec.rb index a17d933e3bb..e28a155a47f 100644 --- a/spec/lib/gitlab/external_authorization/client_spec.rb +++ b/spec/lib/gitlab/external_authorization/client_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe Gitlab::ExternalAuthorization::Client do let(:user) { build(:user, email: 'dummy_user@example.com') } let(:dummy_url) { 'https://dummy.net/' } + subject(:client) { described_class.new(user, 'dummy_label') } before do diff --git a/spec/lib/gitlab/external_authorization/response_spec.rb b/spec/lib/gitlab/external_authorization/response_spec.rb index e1f6e9ac1fa..5ce3325ef77 100644 --- a/spec/lib/gitlab/external_authorization/response_spec.rb +++ b/spec/lib/gitlab/external_authorization/response_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::ExternalAuthorization::Response do let(:excon_response) { double } + subject(:response) { described_class.new(excon_response) } describe '#valid?' do diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb index f3a9f706e86..23f7deba7f7 100644 --- a/spec/lib/gitlab/file_detector_spec.rb +++ b/spec/lib/gitlab/file_detector_spec.rb @@ -82,5 +82,21 @@ describe Gitlab::FileDetector do it 'returns nil for an unknown file' do expect(described_class.type_of('foo.txt')).to be_nil end + + it 'returns the type of an OpenAPI spec if file name is correct' do + openapi_types = [ + 'openapi.yml', 'openapi.yaml', 'openapi.json', + 'swagger.yml', 'swagger.yaml', 'swagger.json', + 'gitlab_swagger.yml', 'openapi_gitlab.yml', + 'OpenAPI.YML', 'openapi.Yaml', 'openapi.JSON', + 'openapi.gitlab.yml', 'gitlab.openapi.yml' + ] + + openapi_types.each do |type_name| + expect(described_class.type_of(type_name)).to eq(:openapi) + end + + expect(described_class.type_of('openapiyml')).to be_nil + end end end diff --git a/spec/lib/gitlab/file_finder_spec.rb b/spec/lib/gitlab/file_finder_spec.rb index 7ea9d43c9f7..6cc5141a6fe 100644 --- a/spec/lib/gitlab/file_finder_spec.rb +++ b/spec/lib/gitlab/file_finder_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe Gitlab::FileFinder do describe '#find' do let(:project) { create(:project, :public, :repository) } + subject { described_class.new(project, project.default_branch) } it_behaves_like 'file finder' do diff --git a/spec/lib/gitlab/fogbugz_import/client_spec.rb b/spec/lib/gitlab/fogbugz_import/client_spec.rb index 676511211c8..ca6f374476c 100644 --- a/spec/lib/gitlab/fogbugz_import/client_spec.rb +++ b/spec/lib/gitlab/fogbugz_import/client_spec.rb @@ -20,6 +20,8 @@ describe Gitlab::FogbugzImport::Client do end def stub_api(users) - allow_any_instance_of(::Fogbugz::Interface).to receive(:command).with(:listPeople).and_return(users) + allow_next_instance_of(::Fogbugz::Interface) do |instance| + allow(instance).to receive(:command).with(:listPeople).and_return(users) + end end end diff --git a/spec/lib/gitlab/fogbugz_import/importer_spec.rb b/spec/lib/gitlab/fogbugz_import/importer_spec.rb new file mode 100644 index 00000000000..9e67047eeda --- /dev/null +++ b/spec/lib/gitlab/fogbugz_import/importer_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::FogbugzImport::Importer do + let(:project) { create(:project_empty_repo) } + let(:importer) { described_class.new(project) } + let(:repo) do + instance_double(Gitlab::FogbugzImport::Repository, + safe_name: 'vim', + path: 'vim', + raw_data: '') + end + let(:import_data) { { 'repo' => repo } } + let(:credentials) do + { + 'fb_session' => { + 'uri' => 'https://testing.fogbugz.com', + 'token' => 'token' + } + } + end + + let(:closed_bug) do + { + fOpen: 'false', + sTitle: 'Closed bug', + sLatestTextSummary: "", + dtOpened: Time.now.to_s, + dtLastUpdated: Time.now.to_s, + events: { event: [] } + }.with_indifferent_access + end + + let(:opened_bug) do + { + fOpen: 'true', + sTitle: 'Opened bug', + sLatestTextSummary: "", + dtOpened: Time.now.to_s, + dtLastUpdated: Time.now.to_s, + events: { event: [] } + }.with_indifferent_access + end + + let(:fogbugz_bugs) { [opened_bug, closed_bug] } + + before do + project.create_import_data(data: import_data, credentials: credentials) + allow_any_instance_of(::Fogbugz::Interface).to receive(:command).with(:listCategories).and_return([]) + allow_any_instance_of(Gitlab::FogbugzImport::Client).to receive(:cases).and_return(fogbugz_bugs) + end + + it 'imports bugs' do + expect { importer.execute }.to change { Issue.count }.by(2) + end + + it 'imports opened bugs' do + importer.execute + + issue = Issue.where(project_id: project.id).find_by_title(opened_bug[:sTitle]) + + expect(issue.state_id).to eq(Issue.available_states[:opened]) + end + + it 'imports closed bugs' do + importer.execute + + issue = Issue.where(project_id: project.id).find_by_title(closed_bug[:sTitle]) + + expect(issue.state_id).to eq(Issue.available_states[:closed]) + end +end diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index 026fd1fedde..d16f34af325 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -54,11 +54,13 @@ describe Gitlab::Gfm::ReferenceRewriter do context 'code' do let(:text) { "#1, but not `[#1]`" } + it { is_expected.to eq "#{issue_first.to_reference(new_project)}, but not `[#1]`" } end context 'code reverse' do let(:text) { "not `#1`, but #1" } + it { is_expected.to eq "not `#1`, but #{issue_first.to_reference(new_project)}" } end @@ -74,11 +76,13 @@ describe Gitlab::Gfm::ReferenceRewriter do context 'label referenced by id' do let(:text) { '#1 and ~123' } + it { is_expected.to eq %Q{#{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 %Q{#{old_project_ref}#1 and #{old_project_ref}~123} } end end @@ -93,11 +97,13 @@ describe Gitlab::Gfm::ReferenceRewriter do context 'label referenced by id' do let(:text) { '#1 and ~321' } + it { is_expected.to eq %Q{#{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 %Q{#{old_project_ref}#1 and #{old_project_ref}~321} } end end diff --git a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb index 134bd5657e7..6c4f650fa83 100644 --- a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb +++ b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::AttributesAtRefParser, :seed_helper do diff --git a/spec/lib/gitlab/git/attributes_parser_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb index f431d4e2a53..94b7a086e59 100644 --- a/spec/lib/gitlab/git/attributes_parser_spec.rb +++ b/spec/lib/gitlab/git/attributes_parser_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::AttributesParser, :seed_helper do diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb index ac085e2c266..9b2d6fa3bcb 100644 --- a/spec/lib/gitlab/git/blame_spec.rb +++ b/spec/lib/gitlab/git/blame_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Blame, :seed_helper do diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 7f680071969..a659af3d22e 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Blob, :seed_helper do @@ -132,7 +134,9 @@ describe Gitlab::Git::Blob, :seed_helper do describe '.find with Rugged enabled', :enable_rugged do it 'calls out to the Rugged implementation' do - allow_any_instance_of(Rugged).to receive(:rev_parse).with(SeedRepo::Commit::ID).and_call_original + 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, 'files/images/6049019_460s.jpg') end diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index 02ef7b92538..cc26b7e7fcd 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Branch, :seed_helper do @@ -77,7 +79,7 @@ describe Gitlab::Git::Branch, :seed_helper do tree = parents.first.tree { - message: 'commit message', + message: +'commit message', author: committer, committer: committer, tree: tree, @@ -126,7 +128,7 @@ describe Gitlab::Git::Branch, :seed_helper do it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) } def create_commit - params[:message].delete!("\r") + params[:message].delete!(+"\r") Rugged::Commit.create(rugged, params.merge(committer: committer.merge(time: Time.now))) end end diff --git a/spec/lib/gitlab/git/bundle_file_spec.rb b/spec/lib/gitlab/git/bundle_file_spec.rb index ff7c981dadd..e88e163a03f 100644 --- a/spec/lib/gitlab/git/bundle_file_spec.rb +++ b/spec/lib/gitlab/git/bundle_file_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::BundleFile do diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index cdab7127748..7ec655eb113 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Commit, :seed_helper do @@ -15,13 +17,13 @@ describe Gitlab::Git::Commit, :seed_helper do @committer = { email: 'mike@smith.com', name: "Mike Smith", - time: Time.now + time: Time.new(2000, 1, 1, 0, 0, 0, "+08:00") } @author = { email: 'john@smith.com', name: "John Smith", - time: Time.now + time: Time.new(2000, 1, 1, 0, 0, 0, "-08:00") } @parents = [rugged_repo.head.target] @@ -46,7 +48,7 @@ describe Gitlab::Git::Commit, :seed_helper do it { expect(@commit.id).to eq(@raw_commit.oid) } it { expect(@commit.sha).to eq(@raw_commit.oid) } it { expect(@commit.safe_message).to eq(@raw_commit.message) } - it { expect(@commit.created_at).to eq(@raw_commit.author[:time]) } + it { expect(@commit.created_at).to eq(@raw_commit.committer[:time]) } it { expect(@commit.date).to eq(@raw_commit.committer[:time]) } it { expect(@commit.author_email).to eq(@author[:email]) } it { expect(@commit.author_name).to eq(@author[:name]) } @@ -64,8 +66,8 @@ describe Gitlab::Git::Commit, :seed_helper do end describe "Commit info from gitaly commit" do - let(:subject) { "My commit".force_encoding('ASCII-8BIT') } - let(:body) { subject + "My body".force_encoding('ASCII-8BIT') } + let(:subject) { (+"My commit").force_encoding('ASCII-8BIT') } + let(:body) { subject + (+"My body").force_encoding('ASCII-8BIT') } let(:body_size) { body.length } let(:gitaly_commit) { build(:gitaly_commit, subject: subject, body: body, body_size: body_size) } let(:id) { gitaly_commit.id } @@ -77,15 +79,29 @@ describe Gitlab::Git::Commit, :seed_helper do it { expect(commit.id).to eq(id) } it { expect(commit.sha).to eq(id) } it { expect(commit.safe_message).to eq(body) } - it { expect(commit.created_at).to eq(Time.at(committer.date.seconds)) } + it { expect(commit.created_at).to eq(Time.at(committer.date.seconds).utc) } it { expect(commit.author_email).to eq(author.email) } it { expect(commit.author_name).to eq(author.name) } it { expect(commit.committer_name).to eq(committer.name) } it { expect(commit.committer_email).to eq(committer.email) } it { expect(commit.parent_ids).to eq(gitaly_commit.parent_ids) } + context 'non-UTC dates' do + let(:seconds) { Time.now.to_i } + + it 'sets timezones correctly' do + gitaly_commit.author.date.seconds = seconds + gitaly_commit.author.timezone = '-0800' + gitaly_commit.committer.date.seconds = seconds + gitaly_commit.committer.timezone = '+0800' + + expect(commit.authored_date).to eq(Time.at(seconds, in: '-08:00')) + expect(commit.committed_date).to eq(Time.at(seconds, in: '+08:00')) + end + end + context 'body_size != body.size' do - let(:body) { "".force_encoding('ASCII-8BIT') } + let(:body) { (+"").force_encoding('ASCII-8BIT') } context 'zero body_size' do it { expect(commit.safe_message).to eq(subject) } @@ -160,7 +176,9 @@ describe Gitlab::Git::Commit, :seed_helper do describe '.find with Rugged enabled', :enable_rugged do it 'calls out to the Rugged implementation' do - allow_any_instance_of(Rugged).to receive(:rev_parse).with(SeedRepo::Commit::ID).and_call_original + 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 @@ -422,7 +440,9 @@ describe Gitlab::Git::Commit, :seed_helper do it_should_behave_like '.batch_by_oid' it 'calls out to the Rugged implementation' do - allow_any_instance_of(Rugged).to receive(:rev_parse).with(SeedRepo::Commit::ID).and_call_original + 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 @@ -522,6 +542,7 @@ describe Gitlab::Git::Commit, :seed_helper do skip 'move this test to gitaly-ruby' do describe '#init_from_rugged' do let(:gitlab_commit) { described_class.new(repository, rugged_commit) } + subject { gitlab_commit } describe '#id' do @@ -533,6 +554,7 @@ describe Gitlab::Git::Commit, :seed_helper do describe '#init_from_hash' do let(:commit) { described_class.new(repository, sample_commit_hash) } + subject { commit } describe '#id' do @@ -588,6 +610,7 @@ describe Gitlab::Git::Commit, :seed_helper do describe '#to_hash' do let(:hash) { commit.to_hash } + subject { hash } it { is_expected.to be_kind_of Hash } @@ -609,6 +632,7 @@ describe Gitlab::Git::Commit, :seed_helper do describe '#ref_names' do let(:commit) { described_class.find(repository, 'master') } + subject { commit.ref_names(repository) } it 'has 2 element' do diff --git a/spec/lib/gitlab/git/compare_spec.rb b/spec/lib/gitlab/git/compare_spec.rb index 65dfb93d0db..6136df57acb 100644 --- a/spec/lib/gitlab/git/compare_spec.rb +++ b/spec/lib/gitlab/git/compare_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Compare, :seed_helper do diff --git a/spec/lib/gitlab/git/conflict/file_spec.rb b/spec/lib/gitlab/git/conflict/file_spec.rb index a6cabd4966a..0ee9ff93e87 100644 --- a/spec/lib/gitlab/git/conflict/file_spec.rb +++ b/spec/lib/gitlab/git/conflict/file_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::Conflict::File do let(:conflict) { { theirs: { path: 'foo', mode: 33188 }, ours: { path: 'foo', mode: 33188 } } } - let(:invalid_content) { described_class.new(nil, nil, conflict, "a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) } - let(:valid_content) { described_class.new(nil, nil, conflict, "Espa\xC3\xB1a".force_encoding(Encoding::ASCII_8BIT)) } + let(:invalid_content) { described_class.new(nil, nil, conflict, (+"a\xC4\xFC").force_encoding(Encoding::ASCII_8BIT)) } + let(:valid_content) { described_class.new(nil, nil, conflict, (+"Espa\xC3\xB1a").force_encoding(Encoding::ASCII_8BIT)) } describe '#lines' do context 'when the content contains non-UTF-8 characters' do diff --git a/spec/lib/gitlab/git/conflict/parser_spec.rb b/spec/lib/gitlab/git/conflict/parser_spec.rb index 29a1702a1c6..600c870acd4 100644 --- a/spec/lib/gitlab/git/conflict/parser_spec.rb +++ b/spec/lib/gitlab/git/conflict/parser_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::Conflict::Parser do @@ -208,7 +210,7 @@ CONFLICT # these strings. context 'when the file contains UTF-8 characters' do it 'does not raise' do - expect { parse_text("Espa\xC3\xB1a".force_encoding(Encoding::ASCII_8BIT)) } + expect { parse_text((+"Espa\xC3\xB1a").force_encoding(Encoding::ASCII_8BIT)) } .not_to raise_error end end diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb index ce45d6e24ba..0d19d35bc52 100644 --- a/spec/lib/gitlab/git/diff_collection_spec.rb +++ b/spec/lib/gitlab/git/diff_collection_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::DiffCollection, :seed_helper do diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 9ab669ad488..ac606da5cc1 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Diff, :seed_helper do @@ -147,6 +149,7 @@ EOT describe '.between' do let(:diffs) { described_class.between(repository, 'feature', 'master') } + subject { diffs } it { is_expected.to be_kind_of Gitlab::Git::DiffCollection } diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb index de81dcd227d..58d1d2c71da 100644 --- a/spec/lib/gitlab/git/gitmodules_parser_spec.rb +++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::GitmodulesParser do diff --git a/spec/lib/gitlab/git/hook_env_spec.rb b/spec/lib/gitlab/git/hook_env_spec.rb index 5e49ea6da7a..22b016cee3e 100644 --- a/spec/lib/gitlab/git/hook_env_spec.rb +++ b/spec/lib/gitlab/git/hook_env_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::HookEnv do @@ -55,6 +57,7 @@ describe Gitlab::Git::HookEnv do using RSpec::Parameterized::TableSyntax let(:key) { 'GIT_OBJECT_DIRECTORY_RELATIVE' } + subject { described_class.to_env_hash(gl_repository) } where(:input, :output) do diff --git a/spec/lib/gitlab/git/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb index d035df7e0c2..a99e8c4f60c 100644 --- a/spec/lib/gitlab/git/lfs_changes_spec.rb +++ b/spec/lib/gitlab/git/lfs_changes_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::LfsChanges do diff --git a/spec/lib/gitlab/git/lfs_pointer_file_spec.rb b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb index d7f76737f3f..8bb26ed4854 100644 --- a/spec/lib/gitlab/git/lfs_pointer_file_spec.rb +++ b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::LfsPointerFile do diff --git a/spec/lib/gitlab/git/merge_base_spec.rb b/spec/lib/gitlab/git/merge_base_spec.rb index dbb4e3d0b3e..fa95a1664ea 100644 --- a/spec/lib/gitlab/git/merge_base_spec.rb +++ b/spec/lib/gitlab/git/merge_base_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe Gitlab::Git::MergeBase do set(:project) { create(:project, :repository) } let(:repository) { project.repository } + subject(:merge_base) { described_class.new(repository, refs) } shared_context 'existing refs with a merge base', :existing_refs do diff --git a/spec/lib/gitlab/git/pre_receive_error_spec.rb b/spec/lib/gitlab/git/pre_receive_error_spec.rb index cb030e38032..cb539261671 100644 --- a/spec/lib/gitlab/git/pre_receive_error_spec.rb +++ b/spec/lib/gitlab/git/pre_receive_error_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::PreReceiveError do diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb index 566c8209504..32c4c1c82d4 100644 --- a/spec/lib/gitlab/git/push_spec.rb +++ b/spec/lib/gitlab/git/push_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::Push do diff --git a/spec/lib/gitlab/git/raw_diff_change_spec.rb b/spec/lib/gitlab/git/raw_diff_change_spec.rb index a0bb37fd84a..79b2fc21011 100644 --- a/spec/lib/gitlab/git/raw_diff_change_spec.rb +++ b/spec/lib/gitlab/git/raw_diff_change_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::RawDiffChange do diff --git a/spec/lib/gitlab/git/remote_mirror_spec.rb b/spec/lib/gitlab/git/remote_mirror_spec.rb index dc63eef7814..9744562b51b 100644 --- a/spec/lib/gitlab/git/remote_mirror_spec.rb +++ b/spec/lib/gitlab/git/remote_mirror_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::RemoteMirror do diff --git a/spec/lib/gitlab/git/remote_repository_spec.rb b/spec/lib/gitlab/git/remote_repository_spec.rb index e166628d4ca..b53eee293f0 100644 --- a/spec/lib/gitlab/git/remote_repository_spec.rb +++ b/spec/lib/gitlab/git/remote_repository_spec.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::RemoteRepository, :seed_helper do let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } + subject { described_class.new(repository) } describe '#empty?' do diff --git a/spec/lib/gitlab/git/repository_cleaner_spec.rb b/spec/lib/gitlab/git/repository_cleaner_spec.rb index 7bba0107e58..b387d1033d3 100644 --- a/spec/lib/gitlab/git/repository_cleaner_spec.rb +++ b/spec/lib/gitlab/git/repository_cleaner_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::RepositoryCleaner do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 44c41da7560..6854d514dcc 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Repository, :seed_helper do @@ -210,6 +212,7 @@ describe Gitlab::Git::Repository, :seed_helper do describe '#ref_names' do let(:ref_names) { repository.ref_names } + subject { ref_names } it { is_expected.to be_kind_of Array } diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb index 4c0291f64f0..87db3f588ad 100644 --- a/spec/lib/gitlab/git/tag_spec.rb +++ b/spec/lib/gitlab/git/tag_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Tag, :seed_helper do diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 7e169cfe270..d82acad866c 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Tree, :seed_helper do @@ -143,7 +145,9 @@ describe Gitlab::Git::Tree, :seed_helper do describe '.where with Rugged enabled', :enable_rugged do it 'calls out to the Rugged implementation' do - allow_any_instance_of(Rugged).to receive(:lookup).with(SeedRepo::Commit::ID) + allow_next_instance_of(Rugged) do |instance| + allow(instance).to receive(:lookup).with(SeedRepo::Commit::ID) + end described_class.where(repository, SeedRepo::Commit::ID, 'files', false) end diff --git a/spec/lib/gitlab/git/user_spec.rb b/spec/lib/gitlab/git/user_spec.rb index d9d338206f8..6761413320a 100644 --- a/spec/lib/gitlab/git/user_spec.rb +++ b/spec/lib/gitlab/git/user_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::User do @@ -24,6 +26,7 @@ describe Gitlab::Git::User do describe '.from_gitlab' do context 'when no commit_email has been set' do let(:user) { build(:user, email: 'alice@example.com', commit_email: nil) } + subject { described_class.from_gitlab(user) } it { expect(subject).to eq(described_class.new(user.username, user.name, user.email, 'user-')) } @@ -31,6 +34,7 @@ describe Gitlab::Git::User do context 'when commit_email has been set' do let(:user) { build(:user, email: 'alice@example.com', commit_email: 'bob@example.com') } + subject { described_class.from_gitlab(user) } it { expect(subject).to eq(described_class.new(user.username, user.name, user.commit_email, 'user-')) } diff --git a/spec/lib/gitlab/git/util_spec.rb b/spec/lib/gitlab/git/util_spec.rb index 88c871855df..81918f036f9 100644 --- a/spec/lib/gitlab/git/util_spec.rb +++ b/spec/lib/gitlab/git/util_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::Util do diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb index 1e577392949..8bae2e8125e 100644 --- a/spec/lib/gitlab/git/wiki_spec.rb +++ b/spec/lib/gitlab/git/wiki_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::Wiki do diff --git a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb index bcf4814edb6..a4489cca443 100644 --- a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb +++ b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::WrapsGitalyErrors do diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index f74cc5623c9..36bde9de12d 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -730,7 +730,9 @@ describe Gitlab::GitAccess do it 'checks LFS integrity only for first change' do allow(project).to receive(:lfs_enabled?).and_return(true) - expect_any_instance_of(Gitlab::Checks::LfsIntegrity).to receive(:objects_missing?).exactly(1).times + expect_next_instance_of(Gitlab::Checks::LfsIntegrity) do |instance| + expect(instance).to receive(:objects_missing?).exactly(1).times + end push_access_check end diff --git a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb index 3430fbb71f1..07f53797b2a 100644 --- a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb @@ -14,10 +14,11 @@ describe Gitlab::GitalyClient::CleanupService do end it 'sends an apply_bfg_object_map_stream message' do - expect_any_instance_of(Gitaly::CleanupService::Stub) - .to receive(:apply_bfg_object_map_stream) - .with(kind_of(Enumerator), kind_of(Hash)) - .and_return([]) + expect_next_instance_of(Gitaly::CleanupService::Stub) do |instance| + expect(instance).to receive(:apply_bfg_object_map_stream) + .with(kind_of(Enumerator), kind_of(Hash)) + .and_return([]) + end client.apply_bfg_object_map_stream(StringIO.new) end diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 1abdabe17bb..820578dfc6e 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -188,6 +188,7 @@ describe Gitlab::GitalyClient::CommitService do describe '#find_commit' do let(:revision) { Gitlab::Git::EMPTY_TREE_ID } + it 'sends an RPC request' do request = Gitaly::FindCommitRequest.new( repository: repository_message, revision: revision diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 2b4fe2ea5c0..d4a7f6e6df9 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -22,6 +22,7 @@ describe Gitlab::GitalyClient::RefService do describe '#remote_branches' do let(:remote_name) { 'my_remote' } + subject { client.remote_branches(remote_name) } it 'sends a find_all_remote_branches message' do diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index b6c0c0ad523..4b69b4734f1 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -55,7 +55,9 @@ describe Gitlab::GitalyClient do it 'returns an empty string when the storage is not found in the response' do response = double("response") allow(response).to receive(:storage_statuses).and_return([]) - allow_any_instance_of(Gitlab::GitalyClient::ServerService).to receive(:info).and_return(response) + allow_next_instance_of(Gitlab::GitalyClient::ServerService) do |instance| + allow(instance).to receive(:info).and_return(response) + end expect(described_class.filesystem_id('default')).to eq(nil) end @@ -84,12 +86,11 @@ describe Gitlab::GitalyClient do describe '.stub_certs' do it 'skips certificates if OpenSSLError is raised and report it' do - expect(Rails.logger).to receive(:error).at_least(:once) - expect(Gitlab::Sentry) - .to receive(:track_exception) + expect(Gitlab::ErrorTracking) + .to receive(:track_and_raise_for_dev_exception) .with( a_kind_of(OpenSSL::X509::CertificateError), - extra: { cert_file: a_kind_of(String) }).at_least(:once) + cert_file: a_kind_of(String)).at_least(:once) expect(OpenSSL::X509::Certificate) .to receive(:new) diff --git a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb index 484458289af..66909976b43 100644 --- a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb @@ -144,9 +144,9 @@ describe Gitlab::GithubImport::Importer::DiffNoteImporter do describe '#find_merge_request_id' do it 'returns a merge request ID' do - expect_any_instance_of(Gitlab::GithubImport::IssuableFinder) - .to receive(:database_id) - .and_return(10) + expect_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |instance| + expect(instance).to receive(:database_id).and_return(10) + end expect(importer.find_merge_request_id).to eq(10) end diff --git a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb index a003ad7e091..0f2ba99f816 100644 --- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb @@ -99,7 +99,6 @@ describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cach project_id: project.id, description: 'This is my issue', milestone_id: milestone.id, - state: :opened, state_id: 1, created_at: created_at, updated_at: updated_at @@ -129,7 +128,6 @@ describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cach project_id: project.id, description: "*Created by: alice*\n\nThis is my issue", milestone_id: milestone.id, - state: :opened, state_id: 1, created_at: created_at, updated_at: updated_at diff --git a/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb b/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb index 19d40b2f380..9c02b0e280f 100644 --- a/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/label_links_importer_spec.rb @@ -74,9 +74,9 @@ describe Gitlab::GithubImport::Importer::LabelLinksImporter do describe '#find_target_id' do it 'returns the ID of the issuable to create the label link for' do - expect_any_instance_of(Gitlab::GithubImport::IssuableFinder) - .to receive(:database_id) - .and_return(10) + expect_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |instance| + expect(instance).to receive(:database_id).and_return(10) + end expect(importer.find_target_id).to eq(10) end diff --git a/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb b/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb index 2dcf1433154..16326da9ca4 100644 --- a/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb @@ -50,8 +50,9 @@ describe Gitlab::GithubImport::Importer::LabelsImporter, :clean_gitlab_redis_cac describe '#build_labels_cache' do it 'builds the labels cache' do - expect_any_instance_of(Gitlab::GithubImport::LabelFinder) - .to receive(:build_cache) + expect_next_instance_of(Gitlab::GithubImport::LabelFinder) do |instance| + expect(instance).to receive(:build_cache) + end importer.build_labels_cache end diff --git a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb index eaf63e0e11b..294599c02f4 100644 --- a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb @@ -80,8 +80,9 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis describe '#build_milestones_cache' do it 'builds the milestones cache' do - expect_any_instance_of(Gitlab::GithubImport::MilestoneFinder) - .to receive(:build_cache) + expect_next_instance_of(Gitlab::GithubImport::MilestoneFinder) do |instance| + expect(instance).to receive(:build_cache) + end importer.build_milestones_cache end 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 d2b8ba186c8..816041b771b 100644 --- a/spec/lib/gitlab/github_import/importer/note_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/note_importer_spec.rb @@ -143,9 +143,9 @@ describe Gitlab::GithubImport::Importer::NoteImporter do describe '#find_noteable_id' do it 'returns the ID of the noteable' do - expect_any_instance_of(Gitlab::GithubImport::IssuableFinder) - .to receive(:database_id) - .and_return(10) + expect_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |instance| + expect(instance).to receive(:database_id).and_return(10) + end expect(importer.find_noteable_id).to eq(10) end diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb index 50c27e7f4b7..877b4d4bbaf 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb @@ -94,7 +94,6 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi target_project_id: project.id, source_branch: 'github/fork/alice/feature', target_branch: 'master', - state: :merged, state_id: 3, milestone_id: milestone.id, author_id: user.id, @@ -140,7 +139,6 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi target_project_id: project.id, source_branch: 'github/fork/alice/feature', target_branch: 'master', - state: :merged, state_id: 3, milestone_id: milestone.id, author_id: project.creator_id, @@ -187,7 +185,6 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi target_project_id: project.id, source_branch: 'master-42', target_branch: 'master', - state: :merged, state_id: 3, milestone_id: milestone.id, author_id: user.id, @@ -304,7 +301,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi it 'ignores Git command errors when creating a branch' do expect(project.repository).to receive(:add_branch).and_raise(Gitlab::Git::CommandError) - expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original + expect(Gitlab::ErrorTracking).to receive(:track_exception).and_call_original mr = insert_git_data diff --git a/spec/lib/gitlab/github_import/sequential_importer_spec.rb b/spec/lib/gitlab/github_import/sequential_importer_spec.rb index 8b1e8fbf3b7..256155dea03 100644 --- a/spec/lib/gitlab/github_import/sequential_importer_spec.rb +++ b/spec/lib/gitlab/github_import/sequential_importer_spec.rb @@ -9,8 +9,9 @@ describe Gitlab::GithubImport::SequentialImporter do project = double(:project, id: 1, repository: repository) importer = described_class.new(project, token: 'foo') - expect_any_instance_of(Gitlab::GithubImport::Importer::RepositoryImporter) - .to receive(:execute) + expect_next_instance_of(Gitlab::GithubImport::Importer::RepositoryImporter) do |instance| + expect(instance).to receive(:execute) + end described_class::SEQUENTIAL_IMPORTERS.each do |klass| instance = double(:instance) diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb index 0f1745fcc02..246ef6c02f2 100644 --- a/spec/lib/gitlab/gitlab_import/client_spec.rb +++ b/spec/lib/gitlab/gitlab_import/client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitlabImport::Client do @@ -19,18 +21,24 @@ describe Gitlab::GitlabImport::Client do it 'uses membership and simple flags' do stub_request('/api/v4/projects?membership=true&page=1&per_page=100&simple=true') - expect_any_instance_of(OAuth2::Response).to receive(:parsed).and_return([]) + expect_next_instance_of(OAuth2::Response) do |instance| + expect(instance).to receive(:parsed).and_return([]) + end expect(client.projects.to_a).to eq [] end shared_examples 'pagination params' do before do - allow_any_instance_of(OAuth2::Response).to receive(:parsed).and_return([]) + allow_next_instance_of(OAuth2::Response) do |instance| + allow(instance).to receive(:parsed).and_return([]) + end end it 'allows page_limit param' do - allow_any_instance_of(OAuth2::Response).to receive(:parsed).and_return(element_list) + allow_next_instance_of(OAuth2::Response) do |instance| + allow(instance).to receive(:parsed).and_return(element_list) + end expect(client).to receive(:lazy_page_iterator).with(hash_including(page_limit: 2)).and_call_original diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb index 200edceca8c..2db1ddcfd0a 100644 --- a/spec/lib/gitlab/gitlab_import/importer_spec.rb +++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitlabImport::Importer do diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb index b814f5fc76c..c7ef978df37 100644 --- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb +++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitlabImport::ProjectCreator do diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb index 37985c062b4..2e929a62ebc 100644 --- a/spec/lib/gitlab/google_code_import/client_spec.rb +++ b/spec/lib/gitlab/google_code_import/client_spec.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::GoogleCodeImport::Client do let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) } + subject { described_class.new(raw_data) } describe "#valid?" do diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 031f57dbc65..7055df89c09 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::GoogleCodeImport::Importer do @@ -37,7 +39,7 @@ describe Gitlab::GoogleCodeImport::Importer do Performance Usability Maintainability Component-Panel Component-Taskbar Component-Battery Component-Systray Component-Clock Component-Launcher Component-Tint2conf Component-Docs Component-New ).each do |label| - label.sub!("-", ": ") + label = label.sub("-", ": ") expect(project.labels.find_by(name: label)).not_to be_nil end end diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb index b959e006292..2353c24f77b 100644 --- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb +++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GoogleCodeImport::ProjectCreator do diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb index 8401b683fd5..ea0a6e1b967 100644 --- a/spec/lib/gitlab/gpg/commit_spec.rb +++ b/spec/lib/gitlab/gpg/commit_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Gpg::Commit do diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb index da307754243..c1516a48b80 100644 --- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb +++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb index 52d6a86f7d0..8600ef223c6 100644 --- a/spec/lib/gitlab/gpg_spec.rb +++ b/spec/lib/gitlab/gpg_spec.rb @@ -177,6 +177,25 @@ describe Gitlab::Gpg do end.not_to raise_error end + it 'tracks an exception when cleaning up the tmp dir fails' do + expected_exception = described_class::CleanupError.new('cleanup failed') + expected_tmp_dir = nil + + expect(described_class).to receive(:cleanup_tmp_dir).and_raise(expected_exception) + allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception) + + described_class.using_tmp_keychain do + expected_tmp_dir = described_class.current_home_dir + FileUtils.touch(File.join(expected_tmp_dir, 'dummy.file')) + end + + expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception).with( + expected_exception, + issue_url: 'https://gitlab.com/gitlab-org/gitlab/issues/20918', + tmp_dir: expected_tmp_dir, contents: ['dummy.file'] + ) + end + shared_examples 'multiple deletion attempts of the tmp-dir' do |seconds| let(:tmp_dir) do tmp_dir = Dir.mktmpdir @@ -211,15 +230,6 @@ describe Gitlab::Gpg do expect(File.exist?(tmp_dir)).to be false end - - it 'does not retry when the feature flag is disabled' do - stub_feature_flags(gpg_cleanup_retries: false) - - expect(FileUtils).to receive(:remove_entry).with(tmp_dir, true).and_call_original - expect(Retriable).not_to receive(:retriable) - - described_class.using_tmp_keychain {} - end end it_behaves_like 'multiple deletion attempts of the tmp-dir', described_class::FG_CLEANUP_RUNTIME_S diff --git a/spec/lib/gitlab/grafana_embed_usage_data_spec.rb b/spec/lib/gitlab/grafana_embed_usage_data_spec.rb new file mode 100644 index 00000000000..162db46719b --- /dev/null +++ b/spec/lib/gitlab/grafana_embed_usage_data_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::GrafanaEmbedUsageData do + describe '#issue_count' do + subject { described_class.issue_count } + + let(:project) { create(:project) } + let(:description_with_embed) { "Some comment\n\nhttps://grafana.example.com/d/xvAk4q0Wk/go-processes?orgId=1&from=1573238522762&to=1573240322762&var-job=prometheus&var-interval=10m&panelId=1&fullscreen" } + let(:description_with_unintegrated_embed) { "Some comment\n\nhttps://grafana.exp.com/d/xvAk4q0Wk/go-processes?orgId=1&from=1573238522762&to=1573240322762&var-job=prometheus&var-interval=10m&panelId=1&fullscreen" } + let(:description_with_non_grafana_inline_metric) { "Some comment\n\n#{Gitlab::Routing.url_helpers.metrics_namespace_project_environment_url(*['foo', 'bar', 12])}" } + + shared_examples "zero count" do + it "does not count the issue" do + expect(subject).to eq(0) + end + end + + context 'with project grafana integration enabled' do + before do + create(:grafana_integration, project: project, enabled: true) + end + + context 'with valid and invalid embeds' do + before do + # Valid + create(:issue, project: project, description: description_with_embed) + create(:issue, project: project, description: description_with_embed) + # In-Valid + create(:issue, project: project, description: description_with_unintegrated_embed) + create(:issue, project: project, description: description_with_non_grafana_inline_metric) + create(:issue, project: project, description: nil) + create(:issue, project: project, description: '') + create(:issue, project: project) + end + + it 'counts only the issues with embeds' do + expect(subject).to eq(2) + end + end + end + + context 'with project grafana integration disabled' do + before do + create(:grafana_integration, project: project, enabled: false) + end + + context 'with one issue having a grafana link in the description and one without' do + before do + create(:issue, project: project, description: description_with_embed) + create(:issue, project: project) + end + + it_behaves_like('zero count') + end + end + + context 'with an un-integrated project' do + context 'with one issue having a grafana link in the description and one without' do + before do + create(:issue, project: project, description: description_with_embed) + create(:issue, project: project) + end + + it_behaves_like('zero count') + end + end + end +end diff --git a/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb index e21af023bb8..0cfda80b854 100644 --- a/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb +++ b/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GrapeLogging::Loggers::ExceptionLogger do diff --git a/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb index f47b9dd3498..c0762e9892b 100644 --- a/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb +++ b/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GrapeLogging::Loggers::QueueDurationLogger do diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb index aada9285b31..98659dbed57 100644 --- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb @@ -25,6 +25,7 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do end let(:current_user) { double(:current_user) } + subject(:service) { described_class.new(field) } describe '#authorized_resolve' do @@ -34,6 +35,7 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)} let(:query_context) { OpenStruct.new(schema: schema) } let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema, context: query_context), values: { current_user: current_user }, object: nil) } + subject(:resolved) { service.authorized_resolve.call(presented_type, {}, context) } context 'scalar types' do diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb index 23762666ba8..67cb064b966 100644 --- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Graphql::Authorize::AuthorizeResource do @@ -25,6 +27,7 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do let(:user) { build(:user) } let(:project) { build(:project) } + subject(:loading_resource) { fake_class.new(user, project) } context 'when the user is allowed to perform the action' do diff --git a/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb b/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb index 1fda84f777e..20e87daa0d6 100644 --- a/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb +++ b/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb @@ -6,6 +6,7 @@ describe Gitlab::Graphql::Connections::FilterableArrayConnection do let(:callback) { proc { |nodes| nodes } } let(:all_nodes) { Gitlab::Graphql::FilterableArray.new(callback, 1, 2, 3, 4, 5) } let(:arguments) { {} } + subject(:connection) do described_class.new(all_nodes, arguments, max_page_size: 3) end diff --git a/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb index 9dda2a41ec6..bd0fcbbdeb2 100644 --- a/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb +++ b/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe Gitlab::Graphql::Connections::Keyset::Connection do let(:nodes) { Project.all.order(id: :asc) } let(:arguments) { {} } + subject(:connection) do described_class.new(nodes, arguments, max_page_size: 3) end @@ -218,23 +219,11 @@ describe Gitlab::Graphql::Connections::Keyset::Connection do end end - # TODO Enable this as part of below issue - # https://gitlab.com/gitlab-org/gitlab/issues/32933 - # context 'when an invalid cursor is provided' do - # let(:arguments) { { before: 'invalidcursor' } } - # - # it 'raises an error' do - # expect { expect(subject.sliced_nodes) }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) - # end - # end - - # TODO Remove this as part of below issue - # https://gitlab.com/gitlab-org/gitlab/issues/32933 - context 'when an old style cursor is provided' do - let(:arguments) { { before: Base64Bp.urlsafe_encode64(projects[1].id.to_s, padding: false) } } + context 'when an invalid cursor is provided' do + let(:arguments) { { before: Base64Bp.urlsafe_encode64('invalidcursor', padding: false) } } - it 'only returns the project before the selected one' do - expect(subject.sliced_nodes).to contain_exactly(projects.first) + it 'raises an error' do + expect { subject.sliced_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) end end end diff --git a/spec/lib/gitlab/graphql/connections/keyset/legacy_keyset_connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset/legacy_keyset_connection_spec.rb deleted file mode 100644 index aaf28fed684..00000000000 --- a/spec/lib/gitlab/graphql/connections/keyset/legacy_keyset_connection_spec.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true - -# TODO https://gitlab.com/gitlab-org/gitlab/issues/35104 -require 'spec_helper' - -describe Gitlab::Graphql::Connections::Keyset::LegacyKeysetConnection do - describe 'old keyset_connection' do - let(:described_class) { Gitlab::Graphql::Connections::Keyset::Connection } - let(:nodes) { Project.all.order(id: :asc) } - let(:arguments) { {} } - subject(:connection) do - described_class.new(nodes, arguments, max_page_size: 3) - end - - before do - stub_feature_flags(graphql_keyset_pagination: false) - end - - def encoded_property(value) - Base64Bp.urlsafe_encode64(value.to_s, padding: false) - end - - describe '#cursor_from_nodes' do - let(:project) { create(:project) } - - it 'returns an encoded ID' do - expect(connection.cursor_from_node(project)) - .to eq(encoded_property(project.id)) - end - - context 'when an order was specified' do - let(:nodes) { Project.order(:updated_at) } - - it 'returns the encoded value of the order' do - expect(connection.cursor_from_node(project)) - .to eq(encoded_property(project.updated_at)) - end - end - end - - describe '#sliced_nodes' do - let(:projects) { create_list(:project, 4) } - - context 'when before is passed' do - let(:arguments) { { before: encoded_property(projects[1].id) } } - - it 'only returns the project before the selected one' do - expect(subject.sliced_nodes).to contain_exactly(projects.first) - end - - context 'when the sort order is descending' do - let(:nodes) { Project.all.order(id: :desc) } - - it 'returns the correct nodes' do - expect(subject.sliced_nodes).to contain_exactly(*projects[2..-1]) - end - end - end - - context 'when after is passed' do - let(:arguments) { { after: encoded_property(projects[1].id) } } - - it 'only returns the project before the selected one' do - expect(subject.sliced_nodes).to contain_exactly(*projects[2..-1]) - end - - context 'when the sort order is descending' do - let(:nodes) { Project.all.order(id: :desc) } - - it 'returns the correct nodes' do - expect(subject.sliced_nodes).to contain_exactly(projects.first) - end - end - end - - context 'when both before and after are passed' do - let(:arguments) do - { - after: encoded_property(projects[1].id), - before: encoded_property(projects[3].id) - } - end - - it 'returns the expected set' do - expect(subject.sliced_nodes).to contain_exactly(projects[2]) - end - end - end - - describe '#paged_nodes' do - let!(:projects) { create_list(:project, 5) } - - it 'returns the collection limited to max page size' do - expect(subject.paged_nodes.size).to eq(3) - end - - it 'is a loaded memoized array' do - expect(subject.paged_nodes).to be_an(Array) - expect(subject.paged_nodes.object_id).to eq(subject.paged_nodes.object_id) - end - - context 'when `first` is passed' do - let(:arguments) { { first: 2 } } - - it 'returns only the first elements' do - expect(subject.paged_nodes).to contain_exactly(projects.first, projects.second) - end - end - - context 'when `last` is passed' do - let(:arguments) { { last: 2 } } - - it 'returns only the last elements' do - expect(subject.paged_nodes).to contain_exactly(projects[3], projects[4]) - end - end - - context 'when both are passed' do - let(:arguments) { { first: 2, last: 2 } } - - it 'raises an error' do - expect { subject.paged_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) - end - end - end - end -end diff --git a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb index 22d8aa4274a..1e8de144b8d 100644 --- a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb +++ b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do diff --git a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb index a4bbd868558..79f9ecb39cf 100644 --- a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb +++ b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Graphql::Loaders::BatchModelLoader do diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb index 09654e0439e..f92c7fb11a1 100644 --- a/spec/lib/gitlab/graphs/commits_spec.rb +++ b/spec/lib/gitlab/graphs/commits_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Graphs::Commits do diff --git a/spec/lib/gitlab/health_checks/db_check_spec.rb b/spec/lib/gitlab/health_checks/db_check_spec.rb index 33c6c24449c..3c1c1e3818d 100644 --- a/spec/lib/gitlab/health_checks/db_check_spec.rb +++ b/spec/lib/gitlab/health_checks/db_check_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require_relative './simple_check_shared' diff --git a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb index 36e2fd04aeb..d4ce16ce6fc 100644 --- a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb +++ b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::HealthChecks::GitalyCheck do diff --git a/spec/lib/gitlab/health_checks/master_check_spec.rb b/spec/lib/gitlab/health_checks/master_check_spec.rb index 91441a7ddc3..cb20c1188af 100644 --- a/spec/lib/gitlab/health_checks/master_check_spec.rb +++ b/spec/lib/gitlab/health_checks/master_check_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require_relative './simple_check_shared' diff --git a/spec/lib/gitlab/health_checks/probes/collection_spec.rb b/spec/lib/gitlab/health_checks/probes/collection_spec.rb index 33efc640257..d8c411fa27b 100644 --- a/spec/lib/gitlab/health_checks/probes/collection_spec.rb +++ b/spec/lib/gitlab/health_checks/probes/collection_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Gitlab::HealthChecks::Probes::Collection do let(:readiness) { described_class.new(*checks) } - describe '#call' do + describe '#execute' do subject { readiness.execute } context 'with all checks' do diff --git a/spec/lib/gitlab/health_checks/puma_check_spec.rb b/spec/lib/gitlab/health_checks/puma_check_spec.rb index 71b6386b174..dd052a4dd2c 100644 --- a/spec/lib/gitlab/health_checks/puma_check_spec.rb +++ b/spec/lib/gitlab/health_checks/puma_check_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::HealthChecks::PumaCheck do diff --git a/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb b/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb index 3693f52b51b..aaf474d7eeb 100644 --- a/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb +++ b/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require_relative '../simple_check_shared' diff --git a/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb b/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb index c69443d205d..f4b5e18da2a 100644 --- a/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb +++ b/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require_relative '../simple_check_shared' diff --git a/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb index 03afc1cd761..ae7ee0d0859 100644 --- a/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb +++ b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require_relative '../simple_check_shared' diff --git a/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb b/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb index b72e152bbe2..3e92b072254 100644 --- a/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb +++ b/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require_relative '../simple_check_shared' diff --git a/spec/lib/gitlab/health_checks/simple_check_shared.rb b/spec/lib/gitlab/health_checks/simple_check_shared.rb index 03a7cf249cf..3d0f9b3cf7a 100644 --- a/spec/lib/gitlab/health_checks/simple_check_shared.rb +++ b/spec/lib/gitlab/health_checks/simple_check_shared.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'simple_check' do |metrics_prefix, check_name, success_result| describe '#metrics' do subject { described_class.metrics } diff --git a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb index c02d0c37738..931b61cb168 100644 --- a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb +++ b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::HealthChecks::UnicornCheck do diff --git a/spec/lib/gitlab/hook_data/base_builder_spec.rb b/spec/lib/gitlab/hook_data/base_builder_spec.rb index e3c5ee3b905..4c3fd854c09 100644 --- a/spec/lib/gitlab/hook_data/base_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/base_builder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::HookData::BaseBuilder do @@ -13,6 +15,7 @@ describe Gitlab::HookData::BaseBuilder do context 'with an upload prefix specified' do let(:project_with_path) { double(full_path: 'baz/bar') } let(:object_with_project) { double(project: project_with_path) } + subject { subclass.new(object_with_project) } where do diff --git a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb index 97a89b319ea..5135c84df19 100644 --- a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::HookData::IssuableBuilder do diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb index ebd7feb0055..8008f3d72b2 100644 --- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::HookData::IssueBuilder do diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb index 39f80f92fa6..506354e370c 100644 --- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::HookData::MergeRequestBuilder do diff --git a/spec/lib/gitlab/http_io_spec.rb b/spec/lib/gitlab/http_io_spec.rb index f30528916dc..4bb5fb7c198 100644 --- a/spec/lib/gitlab/http_io_spec.rb +++ b/spec/lib/gitlab/http_io_spec.rb @@ -109,7 +109,9 @@ describe Gitlab::HttpIO do end it 'calls get_chunk only once' do - expect_any_instance_of(Net::HTTP).to receive(:request).once.and_call_original + expect_next_instance_of(Net::HTTP) do |instance| + expect(instance).to receive(:request).once.and_call_original + end http_io.each_line { |line| } end diff --git a/spec/lib/gitlab/i18n/metadata_entry_spec.rb b/spec/lib/gitlab/i18n/metadata_entry_spec.rb index a399517cc04..2d8bb538681 100644 --- a/spec/lib/gitlab/i18n/metadata_entry_spec.rb +++ b/spec/lib/gitlab/i18n/metadata_entry_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::I18n::MetadataEntry do diff --git a/spec/lib/gitlab/i18n/po_linter_spec.rb b/spec/lib/gitlab/i18n/po_linter_spec.rb index 3dbc23d2aaf..2ab363ee45c 100644 --- a/spec/lib/gitlab/i18n/po_linter_spec.rb +++ b/spec/lib/gitlab/i18n/po_linter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'simple_po_parser' diff --git a/spec/lib/gitlab/i18n/translation_entry_spec.rb b/spec/lib/gitlab/i18n/translation_entry_spec.rb index b301e6ea443..880da38052e 100644 --- a/spec/lib/gitlab/i18n/translation_entry_spec.rb +++ b/spec/lib/gitlab/i18n/translation_entry_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::I18n::TranslationEntry do @@ -134,6 +136,7 @@ describe Gitlab::I18n::TranslationEntry do describe '#contains_unescaped_chars' do let(:data) { { msgid: '' } } let(:entry) { described_class.new(data, 2) } + it 'is true when the msgid is an array' do string = '「100%確定」' diff --git a/spec/lib/gitlab/import/merge_request_helpers_spec.rb b/spec/lib/gitlab/import/merge_request_helpers_spec.rb index cc0f2baf905..42515888d4f 100644 --- a/spec/lib/gitlab/import/merge_request_helpers_spec.rb +++ b/spec/lib/gitlab/import/merge_request_helpers_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::Import::MergeRequestHelpers, type: :helper do target_project_id: project.id, source_branch: 'master-42', target_branch: 'master', - state: :merged, + state_id: 3, author_id: user.id, assignee_id: user.id } 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 a3d2880182d..86ceb97b250 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 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do 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 21a227335cd..95c47d15f8f 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 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do diff --git a/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb index bf727285a9f..9fe9e2eb73d 100644 --- a/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb +++ b/spec/lib/gitlab/import_export/after_export_strategy_builder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::AfterExportStrategyBuilder do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 8f627fcc24d..2ea563c50b6 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -8,6 +8,7 @@ issues: - milestone - notes - resource_label_events +- sentry_issue - label_links - labels - last_edited_by @@ -15,6 +16,7 @@ issues: - user_agent_detail - moved_to - duplicated_to +- promoted_to_epic - events - merge_requests_closing_issues - metrics @@ -32,6 +34,7 @@ issues: - zoom_meetings - vulnerability_links - related_vulnerabilities +- user_mentions events: - author - project @@ -80,6 +83,7 @@ snippets: - notes - award_emoji - user_agent_detail +- user_mentions releases: - author - project @@ -138,6 +142,9 @@ merge_requests: - blocking_merge_requests - blocked_merge_requests - description_versions +- deployment_merge_requests +- deployments +- user_mentions external_pull_requests: - project merge_request_diff: @@ -287,6 +294,7 @@ project: - microsoft_teams_service - mattermost_service - hangouts_chat_service +- unify_circuit_service - buildkite_service - bamboo_service - teamcity_service @@ -364,6 +372,7 @@ project: - root_of_fork_network - fork_network_member - fork_network +- fork_network_projects - custom_attributes - lfs_file_locks - project_badges @@ -432,6 +441,9 @@ project: - downstream_projects - upstream_project_subscriptions - downstream_project_subscriptions +- service_desk_setting +- import_failures +- container_expiration_policy award_emoji: - awardable - user @@ -531,14 +543,18 @@ design: &design - actions - versions - notes +- user_mentions designs: *design actions: - design - version versions: &version +- author - issue - designs - actions zoom_meetings: - issue +sentry_issue: +- issue design_versions: *version diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb index 1b28e26a7e8..44192c4639d 100644 --- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb +++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::AttributeCleaner do diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb index cc8ca1d87e3..58da25bbedb 100644 --- a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb +++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' # Part of the test security suite for the Import/Export feature @@ -10,21 +12,11 @@ require 'spec_helper' describe 'Import/Export attribute configuration' do include ConfigurationHelper - let(:config_hash) { Gitlab::ImportExport::Config.new.to_h.deep_stringify_keys } - let(:relation_names) do - names = names_from_tree(config_hash.dig('tree', 'project')) - - # Remove duplicated or add missing models - # - project is not part of the tree, so it has to be added manually. - # - milestone, labels have both singular and plural versions in the tree, so remove the duplicates. - names.flatten.uniq - %w(milestones labels) + ['project'] - end - let(:safe_attributes_file) { 'spec/lib/gitlab/import_export/safe_model_attributes.yml' } let(:safe_model_attributes) { YAML.load_file(safe_attributes_file) } it 'has no new columns' do - relation_names.each do |relation_name| + relation_names_for(:project).each do |relation_name| relation_class = relation_class_for_name(relation_name) relation_attributes = relation_class.new.attributes.keys - relation_class.encrypted_attributes.keys.map(&:to_s) diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb index e44ff6bbcbd..662e1a5eaab 100644 --- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::AvatarRestorer do @@ -12,8 +14,9 @@ describe Gitlab::ImportExport::AvatarRestorer do context 'with avatar' do before do - allow_any_instance_of(described_class).to receive(:avatar_export_file) - .and_return(uploaded_image_temp_path) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:avatar_export_file).and_return(uploaded_image_temp_path) + end end it 'restores a project avatar' do @@ -31,8 +34,9 @@ describe Gitlab::ImportExport::AvatarRestorer do Dir.mktmpdir do |tmpdir| FileUtils.mkdir_p("#{tmpdir}/a/b") - allow_any_instance_of(described_class).to receive(:avatar_export_path) - .and_return("#{tmpdir}/a") + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:avatar_export_path).and_return("#{tmpdir}/a") + end expect(described_class.new(project: project, shared: shared).restore).to be true end diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb index 2bd1b9924c6..d2349e47c0a 100644 --- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::AvatarSaver do @@ -8,7 +10,9 @@ describe Gitlab::ImportExport::AvatarSaver do before do FileUtils.mkdir_p("#{shared.export_path}/avatar/") - allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:export_path).and_return(export_path) + allow_next_instance_of(Gitlab::ImportExport::Shared) do |instance| + allow(instance).to receive(:export_path).and_return(export_path) + end end after do 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 b190a1007a0..6f90798f815 100644 --- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb +++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::FastHashSerializer do diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb index fbc9bcd2df5..7c54c5f2da1 100644 --- a/spec/lib/gitlab/import_export/file_importer_spec.rb +++ b/spec/lib/gitlab/import_export/file_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::FileImporter do @@ -16,9 +18,15 @@ describe Gitlab::ImportExport::FileImporter do stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) stub_uploads_object_storage(FileUploader) - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(storage_path) - allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true) - allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test') + allow_next_instance_of(Gitlab::ImportExport) do |instance| + allow(instance).to receive(:storage_path).and_return(storage_path) + end + allow_next_instance_of(Gitlab::ImportExport::CommandLineUtil) do |instance| + allow(instance).to receive(:untar_zxf).and_return(true) + end + allow_next_instance_of(Gitlab::ImportExport::Shared) do |instance| + allow(instance).to receive(:relative_archive_path).and_return('test') + end allow(SecureRandom).to receive(:hex).and_return('abcd') setup_files end @@ -29,7 +37,7 @@ describe Gitlab::ImportExport::FileImporter do context 'normal run' do before do - described_class.import(project: build(:project), archive_file: '', shared: shared) + described_class.import(importable: build(:project), archive_file: '', shared: shared) end it 'removes symlinks in root folder' do @@ -67,8 +75,10 @@ describe Gitlab::ImportExport::FileImporter do context 'error' do before do - allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError) - described_class.import(project: build(:project), archive_file: '', shared: shared) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:wait_for_archived_file).and_raise(StandardError) + end + described_class.import(importable: build(:project), archive_file: '', shared: shared) end it 'removes symlinks in root folder' do diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb index 5752fd8fa0d..09e4f62c686 100644 --- a/spec/lib/gitlab/import_export/fork_spec.rb +++ b/spec/lib/gitlab/import_export/fork_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'forked project import' do @@ -30,7 +32,9 @@ describe 'forked project import' do end before do - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + allow_next_instance_of(Gitlab::ImportExport) do |instance| + allow(instance).to receive(:storage_path).and_return(export_path) + end saver.save repo_saver.save diff --git a/spec/lib/gitlab/import_export/group_project_object_builder_spec.rb b/spec/lib/gitlab/import_export/group_project_object_builder_spec.rb index 1a5cb7806a3..0d0a2df4423 100644 --- a/spec/lib/gitlab/import_export/group_project_object_builder_spec.rb +++ b/spec/lib/gitlab/import_export/group_project_object_builder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::GroupProjectObjectBuilder do diff --git a/spec/lib/gitlab/import_export/hash_util_spec.rb b/spec/lib/gitlab/import_export/hash_util_spec.rb index 366582dece3..ddd874ddecf 100644 --- a/spec/lib/gitlab/import_export/hash_util_spec.rb +++ b/spec/lib/gitlab/import_export/hash_util_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::HashUtil do diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb index a6b0dc758cd..2ece0dd4b56 100644 --- a/spec/lib/gitlab/import_export/import_export_spec.rb +++ b/spec/lib/gitlab/import_export/import_export_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport do diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb index 898e4d07760..942af4084e5 100644 --- a/spec/lib/gitlab/import_export/importer_spec.rb +++ b/spec/lib/gitlab/import_export/importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::Importer do diff --git a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb index 2b0bdb909ae..a932dc3ee4e 100644 --- a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::LfsRestorer do @@ -7,6 +9,7 @@ describe Gitlab::ImportExport::LfsRestorer do let(:project) { create(:project) } let(:shared) { project.import_export_shared } let(:saver) { Gitlab::ImportExport::LfsSaver.new(project: project, shared: shared) } + subject(:restorer) { described_class.new(project: project, shared: shared) } before do diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb index c3c88486e16..a8ff7867410 100644 --- a/spec/lib/gitlab/import_export/lfs_saver_spec.rb +++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::LfsSaver do @@ -8,7 +10,9 @@ describe Gitlab::ImportExport::LfsSaver do subject(:saver) { described_class.new(project: project, shared: shared) } before do - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + allow_next_instance_of(Gitlab::ImportExport) do |instance| + allow(instance).to receive(:storage_path).and_return(export_path) + end FileUtils.mkdir_p(shared.export_path) end diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb index a9e8431acba..01a7901062a 100644 --- a/spec/lib/gitlab/import_export/members_mapper_spec.rb +++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::MembersMapper do @@ -25,7 +27,7 @@ describe Gitlab::ImportExport::MembersMapper do "email" => user2.email, "username" => 'test' }, - "user_id" => 19 + "user_id" => 19 }, { "id" => 3, @@ -45,7 +47,7 @@ describe Gitlab::ImportExport::MembersMapper do let(:members_mapper) do described_class.new( - exported_members: exported_members, user: user, project: project) + exported_members: exported_members, user: user, importable: project) end it 'includes the exported user ID in the map' do @@ -81,7 +83,8 @@ describe Gitlab::ImportExport::MembersMapper do end it 'removes old user_id from member_hash to avoid conflict with user key' do - expect(ProjectMember).to receive(:create) + expect(ProjectMember) + .to receive(:create) .twice .with(hash_excluding('user_id')) .and_call_original @@ -115,7 +118,7 @@ describe Gitlab::ImportExport::MembersMapper do let(:project) { create(:project, :public, name: 'searchable_project', namespace: group) } let(:members_mapper) do described_class.new( - exported_members: exported_members, user: user2, project: project) + exported_members: exported_members, user: user2, importable: project) end before do @@ -138,7 +141,7 @@ describe Gitlab::ImportExport::MembersMapper do let(:project) { create(:project, namespace: group) } let(:members_mapper) do described_class.new( - exported_members: exported_members, user: user, project: project) + exported_members: exported_members, user: user, importable: project) end before do @@ -161,7 +164,7 @@ describe Gitlab::ImportExport::MembersMapper do it 'includes importer specific error message' do expect(ProjectMember).to receive(:create!).and_raise(StandardError.new(exception_message)) - expect { members_mapper.map }.to raise_error(StandardError, "Error adding importer user to project members. #{exception_message}") + expect { members_mapper.map }.to raise_error(StandardError, "Error adding importer user to Project members. #{exception_message}") end end end diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb index 4b234411a44..c437efede4c 100644 --- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb +++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::MergeRequestParser do @@ -33,9 +35,11 @@ describe Gitlab::ImportExport::MergeRequestParser do end it 'parses a MR that has no source branch' do - allow_any_instance_of(described_class).to receive(:branch_exists?).and_call_original - allow_any_instance_of(described_class).to receive(:branch_exists?).with(merge_request.source_branch).and_return(false) - allow_any_instance_of(described_class).to receive(:fork_merge_request?).and_return(true) + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:branch_exists?).and_call_original + allow(instance).to receive(:branch_exists?).with(merge_request.source_branch).and_return(false) + allow(instance).to receive(:fork_merge_request?).and_return(true) + end allow(Gitlab::GitalyClient).to receive(:migrate).and_call_original allow(Gitlab::GitalyClient).to receive(:migrate).with(:fetch_ref).and_return([nil, 0]) diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb index 4426e68b474..cfbfe244988 100644 --- a/spec/lib/gitlab/import_export/model_configuration_spec.rb +++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' # Part of the test security suite for the Import/Export feature @@ -6,19 +8,10 @@ require 'spec_helper' describe 'Import/Export model configuration' do include ConfigurationHelper - let(:config_hash) { Gitlab::ImportExport::Config.new.to_h.deep_stringify_keys } - let(:model_names) do - names = names_from_tree(config_hash.dig('tree', 'project')) - - # Remove duplicated or add missing models - # - project is not part of the tree, so it has to be added manually. - # - milestone, labels, merge_request have both singular and plural versions in the tree, so remove the duplicates. - # - User, Author... Models we do not care about for checking models - names.flatten.uniq - %w(milestones labels user author merge_request) + ['project'] - end let(:all_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' } let(:all_models_hash) { YAML.load_file(all_models_yml) } let(:current_models) { setup_models } + let(:model_names) { relation_names_for(:project) } it 'has no new models' do model_names.each do |model_name| diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 64a648ca1f8..ec1b935ad63 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' include ImportExport::CommonUtil @@ -29,9 +31,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project) - expect(Gitlab::ImportExport::RelationFactory).to receive(:create).with(hash_including(excluded_keys: ['whatever'])).and_call_original.at_least(:once) - allow(project_tree_restorer).to receive(:excluded_keys_for_relation).and_return(['whatever']) - @restored_project_json = project_tree_restorer.restore end end @@ -235,6 +234,22 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect(meetings.first.url).to eq('https://zoom.us/j/123456789') end + it 'restores sentry issues' do + sentry_issue = @project.issues.first.sentry_issue + + expect(sentry_issue.sentry_issue_identifier).to eq(1234567891) + end + + it 'restores container_expiration_policy' do + policy = Project.find_by_path('project').container_expiration_policy + + aggregate_failures do + expect(policy).to be_an_instance_of(ContainerExpirationPolicy) + expect(policy).to be_persisted + expect(policy.cadence).to eq('3month') + end + end + context 'Merge requests' do it 'always has the new project as a target' do expect(MergeRequest.find_by_title('MR1').target_project).to eq(@project) @@ -360,7 +375,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect(restored_project_json).to eq(true) end - it_behaves_like 'restores project correctly', + it_behaves_like 'restores project successfully', issues: 1, labels: 2, label_with_priorities: 'A project label', @@ -373,7 +388,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do create(:ci_build, token: 'abcd') end - it_behaves_like 'restores project correctly', + it_behaves_like 'restores project successfully', issues: 1, labels: 2, label_with_priorities: 'A project label', @@ -450,7 +465,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect(restored_project_json).to eq(true) end - it_behaves_like 'restores project correctly', + it_behaves_like 'restores project successfully', issues: 2, labels: 2, label_with_priorities: 'A project label', @@ -555,8 +570,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'Minimal JSON' do let(:project) { create(:project) } + let(:user) { create(:user) } let(:tree_hash) { { 'visibility_level' => visibility } } - let(:restorer) { described_class.new(user: nil, shared: shared, project: project) } + let(:restorer) { described_class.new(user: user, shared: shared, project: project) } before do expect(restorer).to receive(:read_tree_hash) { tree_hash } @@ -631,4 +647,44 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end end end + + context 'JSON with invalid records' do + subject(:restored_project_json) { project_tree_restorer.restore } + + let(:user) { create(:user) } + let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') } + let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) } + let(:correlation_id) { 'my-correlation-id' } + + before do + setup_import_export_config('with_invalid_records') + + # Import is running from the rake task, `correlation_id` is not assigned + expect(Labkit::Correlation::CorrelationId).to receive(:new_id).and_return(correlation_id) + subject + end + + context 'when failures occur because a relation fails to be processed' do + it_behaves_like 'restores project successfully', + issues: 0, + labels: 0, + label_with_priorities: nil, + milestones: 1, + first_issue_labels: 0, + services: 0, + import_failures: 1 + + it 'records the failures in the database' do + import_failure = ImportFailure.last + + expect(import_failure.project_id).to eq(project.id) + expect(import_failure.relation_key).to eq('milestones') + expect(import_failure.relation_index).to be_present + expect(import_failure.exception_class).to eq('ActiveRecord::RecordInvalid') + expect(import_failure.exception_message).to be_present + expect(import_failure.correlation_id_value).to eq('my-correlation-id') + expect(import_failure.created_at).to be_present + end + end + end end 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 97d8b155826..29d0099d5c1 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::ProjectTreeSaver do diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb index 87f665bd995..e37ad281eb5 100644 --- a/spec/lib/gitlab/import_export/reader_spec.rb +++ b/spec/lib/gitlab/import_export/reader_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::Reader do @@ -7,19 +9,18 @@ describe Gitlab::ImportExport::Reader do subject { described_class.new(shared: shared).project_tree } it 'delegates to AttributesFinder#find_root' do - expect_any_instance_of(Gitlab::ImportExport::AttributesFinder) - .to receive(:find_root) - .with(:project) + expect_next_instance_of(Gitlab::ImportExport::AttributesFinder) do |instance| + expect(instance).to receive(:find_root).with(:project) + end subject end context 'when exception raised' do before do - expect_any_instance_of(Gitlab::ImportExport::AttributesFinder) - .to receive(:find_root) - .with(:project) - .and_raise(StandardError) + expect_next_instance_of(Gitlab::ImportExport::AttributesFinder) do |instance| + expect(instance).to receive(:find_root).with(:project).and_raise(StandardError) + end end it { is_expected.to be false } @@ -36,9 +37,9 @@ describe Gitlab::ImportExport::Reader do subject { described_class.new(shared: shared).group_members_tree } it 'delegates to AttributesFinder#find_root' do - expect_any_instance_of(Gitlab::ImportExport::AttributesFinder) - .to receive(:find_root) - .with(:group_members) + expect_next_instance_of(Gitlab::ImportExport::AttributesFinder) do |instance| + expect(instance).to receive(:find_root).with(:group_members) + end subject end diff --git a/spec/lib/gitlab/import_export/references_configuration_spec.rb b/spec/lib/gitlab/import_export/references_configuration_spec.rb new file mode 100644 index 00000000000..91cf9f964c0 --- /dev/null +++ b/spec/lib/gitlab/import_export/references_configuration_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# Part of the test security suite for the Import/Export feature +# Checks whether there are new reference attributes ending with _id in models that are currently being exported as part of the +# project Import/Export feature. +# If there are new references (foreign keys), these will have to either be replaced with actual relation +# or to be blacklisted by using the import_export.yml configuration file. +# Likewise, new models added to import_export.yml, will need to be added with their correspondent relations +# to this spec. +describe 'Import/Export Project configuration' do + include ConfigurationHelper + + where(:relation_path, :relation_name) do + relation_paths_for(:project).map do |relation_names| + next if relation_names.last == :author + + [relation_names.join("."), relation_names.last] + end.compact + end + + with_them do + context "where relation #{params[:relation_path]}" do + it 'does not have prohibited keys' do + relation_class = relation_class_for_name(relation_name) + relation_attributes = relation_class.new.attributes.keys - relation_class.encrypted_attributes.keys.map(&:to_s) + current_attributes = parsed_attributes(relation_name, relation_attributes) + prohibited_keys = current_attributes.select do |attribute| + prohibited_key?(attribute) || !relation_class.attribute_method?(attribute) + end + expect(prohibited_keys).to be_empty, failure_message(relation_class.to_s, prohibited_keys) + end + end + end + + def failure_message(relation_class, prohibited_keys) + <<-MSG + It looks like #{relation_class}, which is exported using the project Import/Export, has references: #{prohibited_keys.join(',')} + + Please replace it with actual relation in IMPORT_EXPORT_CONFIG if you consider this can be exported. + Please blacklist the attribute(s) in IMPORT_EXPORT_CONFIG by adding it to its correspondent + model in the +excluded_attributes+ section. + + IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file} + MSG + end +end diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb index a23e68a8f00..41d6e6f24fc 100644 --- a/spec/lib/gitlab/import_export/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::RelationFactory do @@ -201,7 +203,7 @@ describe Gitlab::ImportExport::RelationFactory do Gitlab::ImportExport::MembersMapper.new( exported_members: [exported_member], user: user, - project: project) + importable: project) end it 'maps the right author to the imported note' do diff --git a/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb new file mode 100644 index 00000000000..c761f9652ab --- /dev/null +++ b/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# This spec is a lightweight version of: +# * project_tree_restorer_spec.rb +# +# In depth testing is being done in the above specs. +# This spec tests that restore project works +# but does not have 100% relation coverage. + +require 'spec_helper' + +describe Gitlab::ImportExport::RelationTreeRestorer do + include ImportExport::CommonUtil + + let(:user) { create(:user) } + let(:shared) { Gitlab::ImportExport::Shared.new(importable) } + let(:members_mapper) { Gitlab::ImportExport::MembersMapper.new(exported_members: {}, user: user, importable: importable) } + + let(:importable_hash) do + json = IO.read(path) + ActiveSupport::JSON.decode(json) + end + + let(:relation_tree_restorer) do + described_class.new( + user: user, + shared: shared, + tree_hash: tree_hash, + importable: importable, + members_mapper: members_mapper, + relation_factory: relation_factory, + reader: reader + ) + end + + subject { relation_tree_restorer.restore } + + context 'when restoring a project' do + let(:path) { 'spec/fixtures/lib/gitlab/import_export/complex/project.json' } + let(:importable) { create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') } + let(:relation_factory) { Gitlab::ImportExport::RelationFactory } + let(:reader) { Gitlab::ImportExport::Reader.new(shared: shared) } + let(:tree_hash) { importable_hash } + + it 'restores project tree' do + expect(subject).to eq(true) + end + + describe 'imported project' do + let(:project) { Project.find_by_path('project') } + + before do + subject + end + + it 'has the project attributes and relations' do + expect(project.description).to eq('Nisi et repellendus ut enim quo accusamus vel magnam.') + expect(project.labels.count).to eq(3) + expect(project.boards.count).to eq(1) + expect(project.project_feature).not_to be_nil + expect(project.custom_attributes.count).to eq(2) + expect(project.project_badges.count).to eq(2) + expect(project.snippets.count).to eq(1) + end + end + end +end diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb index e2ffb2adb9b..a61d966bdfa 100644 --- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::RepoRestorer do @@ -18,7 +20,9 @@ describe Gitlab::ImportExport::RepoRestorer do end before do - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + allow_next_instance_of(Gitlab::ImportExport) do |instance| + allow(instance).to receive(:storage_path).and_return(export_path) + end bundler.save end diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb index c3df371af43..fc1f782bfdd 100644 --- a/spec/lib/gitlab/import_export/repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::RepoSaver do @@ -10,7 +12,9 @@ describe Gitlab::ImportExport::RepoSaver do before do project.add_maintainer(user) - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + allow_next_instance_of(Gitlab::ImportExport) do |instance| + allow(instance).to receive(:storage_path).and_return(export_path) + end end after do diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 04fe985cdb5..79442c35797 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -20,6 +20,7 @@ Issue: - due_date - moved_to_id - duplicated_to_id +- promoted_to_epic_id - lock_version - milestone_id - weight @@ -32,7 +33,6 @@ Issue: Event: - id - target_type -- target_id - project_id - group_id - created_at @@ -59,7 +59,6 @@ Note: - attachment - line_code - commit_id -- noteable_id - system - st_diff - updated_by_id @@ -72,11 +71,8 @@ Note: - resolved_by_push - discussion_id - original_discussion_id -- review_id LabelLink: - id -- label_id -- target_id - target_type - created_at - updated_at @@ -129,13 +125,11 @@ Release: - released_at Evidence: - id -- release_id - summary - created_at - updated_at Releases::Link: - id -- release_id - url - name - created_at @@ -143,7 +137,6 @@ Releases::Link: ProjectMember: - id - access_level -- source_id - source_type - user_id - notification_level @@ -442,6 +435,7 @@ Service: - note_events - pipeline_events - job_events +- comment_on_event_enabled - category - default - wiki_page_events @@ -598,7 +592,6 @@ AwardEmoji: LabelPriority: - id - project_id -- label_id - priority - created_at - updated_at @@ -606,7 +599,6 @@ Timelog: - id - time_spent - merge_request_id -- issue_id - user_id - spent_at - created_at @@ -621,7 +613,6 @@ ProjectAutoDevops: - updated_at IssueAssignee: - user_id -- issue_id ProjectCustomAttribute: - id - created_at @@ -650,6 +641,7 @@ PrometheusAlert: - prometheus_metric_id Badge: - id +- name - link_url - image_url - project_id @@ -676,7 +668,6 @@ ProtectedEnvironment::DeployAccessLevel: ResourceLabelEvent: - id - action -- issue_id - merge_request_id - label_id - user_id @@ -686,9 +677,11 @@ ErrorTracking::ProjectErrorTrackingSetting: - project_id - project_name - organization_name +SentryIssue: +- id +- sentry_issue_identifier Suggestion: - id -- note_id - relative_order - applied - commit_id @@ -726,6 +719,7 @@ List: - milestone_id - user_id - max_issue_count +- max_issue_weight ExternalPullRequest: - id - created_at @@ -742,23 +736,31 @@ ExternalPullRequest: DesignManagement::Design: - id - project_id -- issue_id - filename DesignManagement::Action: -- design_id - event -- version_id DesignManagement::Version: - id - created_at - sha -- issue_id - author_id ZoomMeeting: - id -- issue_id - project_id - issue_status - url - created_at - updated_at +ServiceDeskSetting: +- project_id +- issue_template_key +ContainerExpirationPolicy: +- created_at +- updated_at +- next_run_at +- project_id +- name_regex +- cadence +- older_than +- keep_n +- enabled diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb index aca63953677..a59cf7a1260 100644 --- a/spec/lib/gitlab/import_export/saver_spec.rb +++ b/spec/lib/gitlab/import_export/saver_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'fileutils' @@ -5,10 +7,13 @@ describe Gitlab::ImportExport::Saver do let!(:project) { create(:project, :public, name: 'project') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:shared) { project.import_export_shared } + subject { described_class.new(exportable: project, shared: shared) } before do - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + allow_next_instance_of(Gitlab::ImportExport) do |instance| + allow(instance).to receive(:storage_path).and_return(export_path) + end FileUtils.mkdir_p(shared.export_path) FileUtils.touch("#{shared.export_path}/tmp.bundle") diff --git a/spec/lib/gitlab/import_export/shared_spec.rb b/spec/lib/gitlab/import_export/shared_spec.rb index fc011f7e1be..8c16243576d 100644 --- a/spec/lib/gitlab/import_export/shared_spec.rb +++ b/spec/lib/gitlab/import_export/shared_spec.rb @@ -1,8 +1,11 @@ +# frozen_string_literal: true + require 'spec_helper' require 'fileutils' describe Gitlab::ImportExport::Shared do let(:project) { build(:project) } + subject { project.import_export_shared } context 'with a repository on disk' do @@ -46,24 +49,9 @@ describe Gitlab::ImportExport::Shared do it 'updates the import JID' do import_state = create(:import_state, project: project, jid: 'jid-test') - expect_next_instance_of(Gitlab::Import::Logger) do |logger| - expect(logger).to receive(:error).with(hash_including(import_jid: import_state.jid)) - end - - subject.error(error) - end - - it 'calls the error logger without a backtrace' do - expect(subject).to receive(:log_error).with(message: error.message) - - subject.error(error) - end - - it 'calls the error logger with the full message' do - backtrace = caller - allow(error).to receive(:backtrace).and_return(caller) - - expect(subject).to receive(:log_error).with(message: error.message, error_backtrace: Gitlab::Profiler.clean_backtrace(backtrace)) + expect(Gitlab::ErrorTracking) + .to receive(:track_exception) + .with(error, hash_including(import_jid: import_state.jid)) subject.error(error) end diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb index f13f639d6b7..e6d6ba840be 100644 --- a/spec/lib/gitlab/import_export/uploads_manager_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::UploadsManager do @@ -10,7 +12,9 @@ describe Gitlab::ImportExport::UploadsManager do subject(:manager) { described_class.new(project: project, shared: shared) } before do - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + allow_next_instance_of(Gitlab::ImportExport) do |instance| + allow(instance).to receive(:storage_path).and_return(export_path) + end FileUtils.mkdir_p(shared.export_path) end diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb index e2e8204b2fa..077ece87b31 100644 --- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::UploadsRestorer do @@ -6,7 +8,9 @@ describe Gitlab::ImportExport::UploadsRestorer do let(:shared) { project.import_export_shared } before do - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + allow_next_instance_of(Gitlab::ImportExport) do |instance| + allow(instance).to receive(:storage_path).and_return(export_path) + end FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/random')) FileUtils.touch(File.join(shared.export_path, 'uploads/random', 'dummy.txt')) end diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb index 24993460e51..8a36caef316 100644 --- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::UploadsSaver do @@ -7,7 +9,9 @@ describe Gitlab::ImportExport::UploadsSaver do let(:shared) { project.import_export_shared } before do - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + allow_next_instance_of(Gitlab::ImportExport) do |instance| + allow(instance).to receive(:storage_path).and_return(export_path) + end end after do diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb index 76f8253ec9b..befbd1b4c19 100644 --- a/spec/lib/gitlab/import_export/version_checker_spec.rb +++ b/spec/lib/gitlab/import_export/version_checker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' include ImportExport::CommonUtil @@ -6,10 +8,20 @@ describe Gitlab::ImportExport::VersionChecker do describe 'bundle a project Git repo' do let(:version) { Gitlab::ImportExport.version } + let(:version_file) { Tempfile.new('VERSION') } before do allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('') - allow(File).to receive(:open).and_return(version) + + version_file.write(version) + version_file.rewind + + allow_any_instance_of(described_class).to receive(:version_file).and_return(version_file.path) + end + + after do + version_file.close + version_file.unlink end it 'returns true if Import/Export have the same version' do diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb index 249afbd23d1..59a59223d8d 100644 --- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::WikiRepoSaver do @@ -11,7 +13,9 @@ describe Gitlab::ImportExport::WikiRepoSaver do before do project.add_maintainer(user) - allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + allow_next_instance_of(Gitlab::ImportExport) do |instance| + allow(instance).to receive(:storage_path).and_return(export_path) + end project_wiki.wiki project_wiki.create_page("index", "test content") end diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb index f99f198da33..33cd3e55393 100644 --- a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportExport::WikiRestorer do diff --git a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb index 7f20ae98b06..8d0422bae9f 100644 --- a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb +++ b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb @@ -11,10 +11,17 @@ describe Gitlab::InsecureKeyFingerprint do end let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" } + let(:fingerprint_sha256) { "MQHWhS9nhzUezUdD42ytxubZoBKrZLbyBZzxCkmnxXc" } describe "#fingerprint" do it "generates the key's fingerprint" do - expect(described_class.new(key.split[1]).fingerprint).to eq(fingerprint) + expect(described_class.new(key.split[1]).fingerprint_md5).to eq(fingerprint) + end + end + + describe "#fingerprint" do + it "generates the key's fingerprint" do + expect(described_class.new(key.split[1]).fingerprint_sha256).to eq(fingerprint_sha256) end end end diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb index 39cdd42088e..9d986abb8dd 100644 --- a/spec/lib/gitlab/json_cache_spec.rb +++ b/spec/lib/gitlab/json_cache_spec.rb @@ -7,6 +7,7 @@ describe Gitlab::JsonCache do let(:namespace) { 'geo' } let(:key) { 'foo' } let(:expanded_key) { "#{namespace}:#{key}:#{Gitlab::VERSION}:#{Rails.version}" } + set(:broadcast_message) { create(:broadcast_message) } subject(:cache) { described_class.new(namespace: namespace, backend: backend) } @@ -378,6 +379,12 @@ describe Gitlab::JsonCache do expect(result).to eq(broadcast_message) end + it 'decodes enums correctly' do + result = cache.fetch(key, as: BroadcastMessage) { 'block result' } + + expect(result.broadcast_type).to eq(broadcast_message.broadcast_type) + end + context 'when the cached value is an instance of ActiveRecord::Base' do it 'returns a persisted record when id is set' do backend.write(expanded_key, broadcast_message.to_json) diff --git a/spec/lib/gitlab/kubernetes/config_map_spec.rb b/spec/lib/gitlab/kubernetes/config_map_spec.rb index 911d6024804..0203772e069 100644 --- a/spec/lib/gitlab/kubernetes/config_map_spec.rb +++ b/spec/lib/gitlab/kubernetes/config_map_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Kubernetes::ConfigMap do @@ -16,6 +18,7 @@ describe Gitlab::Kubernetes::ConfigMap do describe '#generate' do let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: application.files) } + subject { config_map.generate } it 'builds a Kubeclient Resource' do diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb index 0de809833e6..5d9beec093a 100644 --- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Kubernetes::Helm::Api do diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb index 78a4eb44e38..c59078449b8 100644 --- a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Kubernetes::Helm::BaseCommand do diff --git a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb index 7e9853cf9ea..82e15864687 100644 --- a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb @@ -13,40 +13,57 @@ describe Gitlab::Kubernetes::Helm::DeleteCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --upgrade - for i in $(seq 1 30); do helm version && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only helm delete --purge app-name EOS end end - let(:tls_flags) do - <<~EOS.squish - --tls - --tls-ca-cert /data/helm/app-name/config/ca.pem - --tls-cert /data/helm/app-name/config/cert.pem - --tls-key /data/helm/app-name/config/key.pem - EOS - end - - context 'when there is a ca.pem file' do - let(:files) { { 'ca.pem': 'some file content' } } + context 'tillerless feature disabled' do + before do + stub_feature_flags(managed_apps_local_tiller: false) + end it_behaves_like 'helm commands' do let(:commands) do <<~EOS helm init --upgrade - for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) - #{helm_delete_command} + for i in $(seq 1 30); do helm version && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) + helm delete --purge app-name EOS end + end - let(:helm_delete_command) do + context 'when there is a ca.pem file' do + let(:files) { { 'ca.pem': 'some file content' } } + + let(:tls_flags) do <<~EOS.squish - helm delete --purge app-name - #{tls_flags} + --tls + --tls-ca-cert /data/helm/app-name/config/ca.pem + --tls-cert /data/helm/app-name/config/cert.pem + --tls-key /data/helm/app-name/config/key.pem EOS end + + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + helm init --upgrade + for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) + #{helm_delete_command} + EOS + end + + let(:helm_delete_command) do + <<~EOS.squish + helm delete --purge app-name + #{tls_flags} + EOS + end + end end end diff --git a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb index 4a3b9d4bf6a..f87ceb45766 100644 --- a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Kubernetes::Helm::InitCommand do diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb index e5a361bdab3..9c04e101e78 100644 --- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Kubernetes::Helm::InstallCommand do @@ -21,22 +23,14 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do ) end - let(:tls_flags) do - <<~EOS.squish - --tls - --tls-ca-cert /data/helm/app-name/config/ca.pem - --tls-cert /data/helm/app-name/config/cert.pem - --tls-key /data/helm/app-name/config/key.pem - EOS - end - subject { install_command } it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --upgrade - for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only helm repo add app-name https://repository.example.com helm repo update #{helm_install_comand} @@ -48,7 +42,6 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do helm upgrade app-name chart-name --install --reset-values - #{tls_flags} --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps @@ -57,8 +50,19 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do end end - context 'when rbac is true' do - let(:rbac) { true } + context 'tillerless feature disabled' do + before do + stub_feature_flags(managed_apps_local_tiller: false) + end + + let(:tls_flags) do + <<~EOS.squish + --tls + --tls-ca-cert /data/helm/app-name/config/ca.pem + --tls-cert /data/helm/app-name/config/cert.pem + --tls-key /data/helm/app-name/config/key.pem + EOS + end it_behaves_like 'helm commands' do let(:commands) do @@ -67,6 +71,36 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) helm repo add app-name https://repository.example.com helm repo update + #{helm_install_comand} + EOS + end + + let(:helm_install_comand) do + <<~EOS.squish + helm upgrade app-name chart-name + --install + --reset-values + #{tls_flags} + --version 1.2.3 + --set rbac.create\\=false,rbac.enabled\\=false + --namespace gitlab-managed-apps + -f /data/helm/app-name/config/values.yaml + EOS + end + end + end + + context 'when rbac is true' do + let(:rbac) { true } + + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only + helm repo add app-name https://repository.example.com + helm repo update #{helm_install_command} EOS end @@ -76,7 +110,6 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do helm upgrade app-name chart-name --install --reset-values - #{tls_flags} --version 1.2.3 --set rbac.create\\=true,rbac.enabled\\=true --namespace gitlab-managed-apps @@ -92,8 +125,9 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --upgrade - for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only helm repo add app-name https://repository.example.com helm repo update /bin/date @@ -107,7 +141,6 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do helm upgrade app-name chart-name --install --reset-values - #{tls_flags} --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps @@ -123,8 +156,9 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --upgrade - for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only helm repo add app-name https://repository.example.com helm repo update #{helm_install_command} @@ -138,7 +172,6 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do helm upgrade app-name chart-name --install --reset-values - #{tls_flags} --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps @@ -154,8 +187,9 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --upgrade - for i in $(seq 1 30); do helm version && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only helm repo add app-name https://repository.example.com helm repo update #{helm_install_command} @@ -182,8 +216,9 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --upgrade - for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only helm repo add app-name https://repository.example.com helm repo update #{helm_install_command} @@ -195,7 +230,6 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do helm upgrade app-name chart-name --install --reset-values - #{tls_flags} --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml diff --git a/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb new file mode 100644 index 00000000000..064efebdb96 --- /dev/null +++ b/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb @@ -0,0 +1,218 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Kubernetes::Helm::PatchCommand do + let(:files) { { 'ca.pem': 'some file content' } } + let(:repository) { 'https://repository.example.com' } + let(:rbac) { false } + let(:version) { '1.2.3' } + + subject(:patch_command) do + described_class.new( + name: 'app-name', + chart: 'chart-name', + rbac: rbac, + files: files, + version: version, + repository: repository + ) + end + + context 'when local tiller feature is disabled' do + before do + stub_feature_flags(managed_apps_local_tiller: false) + end + + let(:tls_flags) do + <<~EOS.squish + --tls + --tls-ca-cert /data/helm/app-name/config/ca.pem + --tls-cert /data/helm/app-name/config/cert.pem + --tls-key /data/helm/app-name/config/key.pem + EOS + end + + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + helm init --upgrade + for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) + helm repo add app-name https://repository.example.com + helm repo update + #{helm_upgrade_comand} + EOS + end + + let(:helm_upgrade_comand) do + <<~EOS.squish + helm upgrade app-name chart-name + --reuse-values + #{tls_flags} + --version 1.2.3 + --namespace gitlab-managed-apps + -f /data/helm/app-name/config/values.yaml + EOS + end + end + end + + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only + helm repo add app-name https://repository.example.com + helm repo update + #{helm_upgrade_comand} + EOS + end + + let(:helm_upgrade_comand) do + <<~EOS.squish + helm upgrade app-name chart-name + --reuse-values + --version 1.2.3 + --namespace gitlab-managed-apps + -f /data/helm/app-name/config/values.yaml + EOS + end + end + + context 'when rbac is true' do + let(:rbac) { true } + + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only + helm repo add app-name https://repository.example.com + helm repo update + #{helm_upgrade_command} + EOS + end + + let(:helm_upgrade_command) do + <<~EOS.squish + helm upgrade app-name chart-name + --reuse-values + --version 1.2.3 + --namespace gitlab-managed-apps + -f /data/helm/app-name/config/values.yaml + EOS + end + end + end + + context 'when there is no ca.pem file' do + let(:files) { { 'file.txt': 'some content' } } + + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only + helm repo add app-name https://repository.example.com + helm repo update + #{helm_upgrade_command} + EOS + end + + let(:helm_upgrade_command) do + <<~EOS.squish + helm upgrade app-name chart-name + --reuse-values + --version 1.2.3 + --namespace gitlab-managed-apps + -f /data/helm/app-name/config/values.yaml + EOS + end + end + end + + describe '#pod_name' do + subject { patch_command.pod_name } + + it { is_expected.to eq 'install-app-name' } + end + + context 'when there is no version' do + let(:version) { nil } + + it { expect { patch_command }.to raise_error(ArgumentError, 'version is required') } + end + + describe '#rbac?' do + subject { patch_command.rbac? } + + context 'rbac is enabled' do + let(:rbac) { true } + + it { is_expected.to be_truthy } + end + + context 'rbac is not enabled' do + let(:rbac) { false } + + it { is_expected.to be_falsey } + end + end + + describe '#pod_resource' do + subject { patch_command.pod_resource } + + context 'rbac is enabled' do + let(:rbac) { true } + + it 'generates a pod that uses the tiller serviceAccountName' do + expect(subject.spec.serviceAccountName).to eq('tiller') + end + end + + context 'rbac is not enabled' do + let(:rbac) { false } + + it 'generates a pod that uses the default serviceAccountName' do + expect(subject.spec.serviceAcccountName).to be_nil + end + end + end + + describe '#config_map_resource' do + let(:metadata) do + { + name: "values-content-configuration-app-name", + namespace: 'gitlab-managed-apps', + labels: { name: "values-content-configuration-app-name" } + } + end + + let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: files) } + + subject { patch_command.config_map_resource } + + it 'returns a KubeClient resource with config map content for the application' do + is_expected.to eq(resource) + end + end + + describe '#service_account_resource' do + subject { patch_command.service_account_resource } + + it 'returns nothing' do + is_expected.to be_nil + end + end + + describe '#cluster_role_binding_resource' do + subject { patch_command.cluster_role_binding_resource } + + it 'returns nothing' do + is_expected.to be_nil + end + end +end diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb index e1b4bd0b664..24a734a2915 100644 --- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Kubernetes::Helm::Pod do diff --git a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb index f24ab5579df..b65d7b9fdc6 100644 --- a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb +++ b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb @@ -45,4 +45,20 @@ describe Gitlab::Kubernetes::KubectlCmd do end end end + + describe '.api_resources' do + it 'constructs string properly' do + expected_command = 'kubectl api-resources -o name --api-group foo' + + expect(described_class.api_resources("-o", "name", "--api-group", "foo")).to eq expected_command + end + end + + describe '.delete_crds_from_group' do + it 'constructs string properly' do + expected_command = 'kubectl api-resources -o name --api-group foo | xargs kubectl delete --ignore-not-found crd' + + expect(described_class.delete_crds_from_group("foo")).to eq expected_command + end + end end diff --git a/spec/lib/gitlab/kubernetes/namespace_spec.rb b/spec/lib/gitlab/kubernetes/namespace_spec.rb index e91a755aa03..16634cc48e6 100644 --- a/spec/lib/gitlab/kubernetes/namespace_spec.rb +++ b/spec/lib/gitlab/kubernetes/namespace_spec.rb @@ -1,8 +1,11 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Kubernetes::Namespace do let(:name) { 'a_namespace' } let(:client) { double('kubernetes client') } + subject { described_class.new(name, client) } it { expect(subject.name).to eq(name) } diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb index 31bfd20449d..40c3e7d0b3c 100644 --- a/spec/lib/gitlab/kubernetes_spec.rb +++ b/spec/lib/gitlab/kubernetes_spec.rb @@ -27,6 +27,7 @@ describe Gitlab::Kubernetes do context 'with a path prefix in the API URL' do let(:api_url) { 'https://example.com/prefix/' } + it { expect(result.path).to eq('/prefix/api/v1/namespaces/default/pods/pod1/exec') } end diff --git a/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb index 48655851140..e96745f5fbe 100644 --- a/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/branch_formatter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::LegacyGithubImport::BranchFormatter do diff --git a/spec/lib/gitlab/legacy_github_import/client_spec.rb b/spec/lib/gitlab/legacy_github_import/client_spec.rb index 80b767abce0..194518a1f36 100644 --- a/spec/lib/gitlab/legacy_github_import/client_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::LegacyGithubImport::Client do diff --git a/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb index 413654e108c..0f03db312ce 100644 --- a/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/comment_formatter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::LegacyGithubImport::CommentFormatter do diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb index 9163019514b..c6ee0a3c094 100644 --- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::LegacyGithubImport::Importer do @@ -263,6 +265,7 @@ describe Gitlab::LegacyGithubImport::Importer do context 'when importing a GitHub project' do let(:api_root) { 'https://api.github.com' } let(:repo_root) { 'https://github.com' } + subject { described_class.new(project) } it_behaves_like 'Gitlab::LegacyGithubImport::Importer#execute' @@ -285,6 +288,7 @@ describe Gitlab::LegacyGithubImport::Importer do context 'when importing a Gitea project' do let(:api_root) { 'https://try.gitea.io/api/v1' } let(:repo_root) { 'https://try.gitea.io' } + subject { described_class.new(project) } before do diff --git a/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb index 3b5d8945344..f5bfc379e89 100644 --- a/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/issuable_formatter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::LegacyGithubImport::IssuableFormatter do diff --git a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb index 1a4d5dbfb70..9a7a34afbe7 100644 --- a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::LegacyGithubImport::IssueFormatter do diff --git a/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb index 0d1d04f1bf6..e56e2772f6a 100644 --- a/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/label_formatter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::LegacyGithubImport::LabelFormatter do diff --git a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb index 1db4bbb568c..f5d71888ac9 100644 --- a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::LegacyGithubImport::MilestoneFormatter do @@ -87,6 +89,7 @@ describe Gitlab::LegacyGithubImport::MilestoneFormatter do context 'when importing a Gitea project' do let(:iid_attr) { :id } + before do project.update(import_type: 'gitea') end diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb index 8675d8691c8..b0687474c80 100644 --- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::LegacyGithubImport::ProjectCreator do diff --git a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb index 267a41e3f32..622210508b9 100644 --- a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::LegacyGithubImport::PullRequestFormatter do diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb index cbb862cb0c9..e86de04b5cf 100644 --- a/spec/lib/gitlab/lets_encrypt/client_spec.rb +++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb @@ -86,6 +86,7 @@ describe ::Gitlab::LetsEncrypt::Client do describe '#load_order' do let(:url) { 'https://example.com/order' } + subject { client.load_order(url) } before do @@ -102,6 +103,7 @@ describe ::Gitlab::LetsEncrypt::Client do describe '#load_challenge' do let(:url) { 'https://example.com/challenge' } + subject { client.load_challenge(url) } before do diff --git a/spec/lib/gitlab/mail_room/mail_room_spec.rb b/spec/lib/gitlab/mail_room/mail_room_spec.rb new file mode 100644 index 00000000000..cb3e214d38b --- /dev/null +++ b/spec/lib/gitlab/mail_room/mail_room_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::MailRoom do + let(:default_port) { 143 } + let(:default_config) do + { + enabled: false, + port: default_port, + ssl: false, + start_tls: false, + mailbox: 'inbox', + idle_timeout: 60, + log_path: Rails.root.join('log', 'mail_room_json.log').to_s + } + end + + before do + described_class.reset_config! + allow(File).to receive(:exist?).and_return true + end + + describe '#config' do + context 'if the yml file cannot be found' do + before do + allow(File).to receive(:exist?).and_return false + end + + it 'returns an empty hash' do + expect(described_class.config).to be_empty + end + end + + before do + allow(described_class).to receive(:load_from_yaml).and_return(default_config) + end + + it 'sets up config properly' do + expected_result = default_config + + expect(described_class.config).to match expected_result + end + + context 'when a config value is missing from the yml file' do + it 'overwrites missing values with the default' do + stub_config(port: nil) + + expect(described_class.config[:port]).to eq default_port + end + end + + describe 'setting up redis settings' do + let(:fake_redis_queues) { double(url: "localhost", sentinels: "yes, them", sentinels?: true) } + + before do + allow(Gitlab::Redis::Queues).to receive(:new).and_return(fake_redis_queues) + end + + target_proc = proc { described_class.config[:redis_url] } + + it_behaves_like 'only truthy if both enabled and address are truthy', target_proc + end + + describe 'setting up the log path' do + context 'if the log path is a relative path' do + it 'expands the log path to an absolute value' do + stub_config(log_path: 'tiny_log.log') + + new_path = Pathname.new(described_class.config[:log_path]) + expect(new_path.absolute?).to be_truthy + end + end + + context 'if the log path is absolute path' do + it 'leaves the path as-is' do + new_path = '/dev/null' + stub_config(log_path: new_path) + + expect(described_class.config[:log_path]).to eq new_path + end + end + end + end + + describe '#enabled?' do + target_proc = proc { described_class.enabled? } + + it_behaves_like 'only truthy if both enabled and address are truthy', target_proc + end + + describe '#reset_config?' do + it 'resets config' do + described_class.instance_variable_set(:@config, { some_stuff: 'hooray' }) + + described_class.reset_config! + + expect(described_class.instance_variable_get(:@config)).to be_nil + end + end + + def stub_config(override_values) + modified_config = default_config.merge(override_values) + allow(described_class).to receive(:load_from_yaml).and_return(modified_config) + end +end diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb index c5fc74afea5..5b6c769d6eb 100644 --- a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb +++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb @@ -73,11 +73,11 @@ describe Gitlab::MarkdownCache::ActiveRecord::Extension do let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) } before do - thing.state = 'closed' + thing.state_id = 2 thing.save end - it { expect(thing.state).to eq('closed') } + it { expect(thing.state_id).to eq(2) } it { expect(thing.title).to eq(markdown) } it { expect(thing.title_html).to eq(html) } it { expect(thing.cached_markdown_version).to eq(cache_version) } @@ -126,6 +126,7 @@ describe Gitlab::MarkdownCache::ActiveRecord::Extension do describe '#cached_html_up_to_date?' do let(:thing) { klass.create(title: updated_markdown, title_html: html, cached_markdown_version: nil) } + subject { thing.cached_html_up_to_date?(:title) } it 'returns false if markdown has been changed but html has not' do diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb index 0d4562f78f1..e0c8133994b 100644 --- a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb @@ -22,6 +22,12 @@ describe Gitlab::Metrics::Dashboard::ServiceSelector do it { is_expected.to be Metrics::Dashboard::SystemDashboardService } end + + context 'when the path is for the pod dashboard' do + let(:arguments) { { dashboard_path: pod_dashboard_path } } + + it { is_expected.to be Metrics::Dashboard::PodDashboardService } + end end context 'when the embedded flag is provided' do diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index 0e2f274f157..bf84a476df9 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -87,7 +87,9 @@ describe Gitlab::Metrics::Instrumentation do allow(described_class).to receive(:transaction) .and_return(transaction) - expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure) + expect_next_instance_of(Gitlab::Metrics::MethodCall) do |instance| + expect(instance).to receive(:measure) + end @dummy.foo end @@ -165,7 +167,9 @@ describe Gitlab::Metrics::Instrumentation do allow(described_class).to receive(:transaction) .and_return(transaction) - expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure) + expect_next_instance_of(Gitlab::Metrics::MethodCall) do |instance| + expect(instance).to receive(:measure) + end @dummy.new.bar end diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb index 335670278c4..1fc6fdcf622 100644 --- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::Metrics::RequestsRackMiddleware do let(:app) { double('app') } + subject { described_class.new(app) } describe '#call' do diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb index cdfd95e3885..a64aae73d43 100644 --- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb @@ -52,6 +52,7 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do context 'unicorn listens on tcp sockets' do let(:tcp_socket_address) { '0.0.0.0:8080' } let(:tcp_sockets) { [tcp_socket_address] } + before do allow(unicorn).to receive(:listener_names).and_return(tcp_sockets) end diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index 16595102375..2b90035d148 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Middleware::Go do @@ -25,18 +27,18 @@ describe Gitlab::Middleware::Go do describe 'when go-get=1' do before do env['QUERY_STRING'] = 'go-get=1' - env['PATH_INFO'] = "/#{path}" + env['PATH_INFO'] = +"/#{path}" end shared_examples 'go-get=1' do |enabled_protocol:| context 'with simple 2-segment project path' do - let!(:project) { create(:project, :private) } + let!(:project) { create(:project, :private, :repository) } context 'with subpackages' do let(:path) { "#{project.full_path}/subpackage" } it 'returns the full project path' do - expect_response_with_path(go, enabled_protocol, project.full_path) + expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch) end end @@ -44,19 +46,19 @@ describe Gitlab::Middleware::Go do let(:path) { project.full_path } it 'returns the full project path' do - expect_response_with_path(go, enabled_protocol, project.full_path) + expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch) end end end context 'with a nested project path' do let(:group) { create(:group, :nested) } - let!(:project) { create(:project, :public, namespace: group) } + let!(:project) { create(:project, :public, :repository, namespace: group) } shared_examples 'a nested project' do context 'when the project is public' do it 'returns the full project path' do - expect_response_with_path(go, enabled_protocol, project.full_path) + expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch) end end @@ -67,7 +69,7 @@ describe Gitlab::Middleware::Go do shared_examples 'unauthorized' do it 'returns the 2-segment group path' do - expect_response_with_path(go, enabled_protocol, group.full_path) + expect_response_with_path(go, enabled_protocol, group.full_path, project.default_branch) end end @@ -85,7 +87,7 @@ describe Gitlab::Middleware::Go do shared_examples 'authenticated' do context 'with access to the project' do it 'returns the full project path' do - expect_response_with_path(go, enabled_protocol, project.full_path) + expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch) end end @@ -160,6 +162,36 @@ describe Gitlab::Middleware::Go do go end end + + context 'with a public project without a repository' do + let!(:project) { create(:project, :public) } + let(:path) { project.full_path } + + it 'returns 404' do + response = go + 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>} + expect(response[2].body).to eq([expected_body]) + end + end + + context 'with a non-standard head' do + let(:user) { create(:user) } + let!(:project) { create(:project, :public, :repository) } + let(:path) { project.full_path } + let(:default_branch) { 'default_branch' } + + before do + project.add_maintainer(user) + project.repository.add_branch(user, default_branch, 'master') + project.change_head(default_branch) + end + + it 'returns the full project path' do + expect_response_with_path(go, enabled_protocol, project.full_path, default_branch) + end + end end context 'with SSH disabled' do @@ -199,16 +231,17 @@ describe Gitlab::Middleware::Go do middleware.call(env) end - def expect_response_with_path(response, protocol, path) + def expect_response_with_path(response, protocol, path, branch) repository_url = case protocol when :ssh "ssh://#{Gitlab.config.gitlab.user}@#{Gitlab.config.gitlab.host}/#{path}.git" when :http, nil "http://#{Gitlab.config.gitlab.host}/#{path}.git" end + 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}" /></head></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].body).to eq([expected_body]) end end diff --git a/spec/lib/gitlab/pages_spec.rb b/spec/lib/gitlab/pages_spec.rb index affa2ebab2a..aecbc74385e 100644 --- a/spec/lib/gitlab/pages_spec.rb +++ b/spec/lib/gitlab/pages_spec.rb @@ -3,10 +3,10 @@ require 'spec_helper' describe Gitlab::Pages do - let(:pages_shared_secret) { SecureRandom.random_bytes(Gitlab::Pages::SECRET_LENGTH) } + let(:pages_secret) { SecureRandom.random_bytes(Gitlab::Pages::SECRET_LENGTH) } before do - allow(described_class).to receive(:secret).and_return(pages_shared_secret) + allow(described_class).to receive(:secret).and_return(pages_secret) end describe '.verify_api_request' do diff --git a/spec/lib/gitlab/pagination/keyset/page_spec.rb b/spec/lib/gitlab/pagination/keyset/page_spec.rb new file mode 100644 index 00000000000..5c03224c05a --- /dev/null +++ b/spec/lib/gitlab/pagination/keyset/page_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Pagination::Keyset::Page do + describe '#per_page' do + it 'limits to a maximum of 100 records per page' do + per_page = described_class.new(per_page: 101).per_page + + expect(per_page).to eq(described_class::MAXIMUM_PAGE_SIZE) + end + + it 'uses default value when given 0' do + per_page = described_class.new(per_page: 0).per_page + + expect(per_page).to eq(described_class::DEFAULT_PAGE_SIZE) + end + + it 'uses default value when given negative values' do + per_page = described_class.new(per_page: -1).per_page + + expect(per_page).to eq(described_class::DEFAULT_PAGE_SIZE) + end + + it 'uses the given value if it is within range' do + per_page = described_class.new(per_page: 10).per_page + + expect(per_page).to eq(10) + end + end + + describe '#next' do + let(:page) { described_class.new(order_by: order_by, lower_bounds: lower_bounds, per_page: per_page, end_reached: end_reached) } + subject { page.next(new_lower_bounds, new_end_reached) } + + let(:order_by) { { id: :desc } } + let(:lower_bounds) { { id: 42 } } + let(:per_page) { 10 } + let(:end_reached) { false } + + let(:new_lower_bounds) { { id: 21 } } + let(:new_end_reached) { true } + + it 'copies over order_by' do + expect(subject.order_by).to eq(page.order_by) + end + + it 'copies over per_page' do + expect(subject.per_page).to eq(page.per_page) + end + + it 'dups the instance' do + expect(subject).not_to eq(page) + end + + it 'sets lower_bounds only on new instance' do + expect(subject.lower_bounds).to eq(new_lower_bounds) + expect(page.lower_bounds).to eq(lower_bounds) + end + + it 'sets end_reached only on new instance' do + expect(subject.end_reached?).to eq(new_end_reached) + expect(page.end_reached?).to eq(end_reached) + end + end +end diff --git a/spec/lib/gitlab/pagination/keyset/pager_spec.rb b/spec/lib/gitlab/pagination/keyset/pager_spec.rb new file mode 100644 index 00000000000..6d23fe2adcc --- /dev/null +++ b/spec/lib/gitlab/pagination/keyset/pager_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Pagination::Keyset::Pager do + let(:relation) { Project.all.order(id: :asc) } + let(:request) { double('request', page: page, apply_headers: nil) } + let(:page) { Gitlab::Pagination::Keyset::Page.new(order_by: { id: :asc }, per_page: 3) } + let(:next_page) { double('next page') } + + before_all do + create_list(:project, 7) + end + + describe '#paginate' do + subject { described_class.new(request).paginate(relation) } + + it 'loads the result relation only once' do + expect do + subject + end.not_to exceed_query_limit(1) + end + + it 'passes information about next page to request' do + lower_bounds = relation.limit(page.per_page).last.slice(:id) + expect(page).to receive(:next).with(lower_bounds, false).and_return(next_page) + expect(request).to receive(:apply_headers).with(next_page) + + subject + end + + context 'when retrieving the last page' do + let(:relation) { Project.where('id > ?', Project.maximum(:id) - page.per_page).order(id: :asc) } + + it 'indicates this is the last page' do + expect(request).to receive(:apply_headers) do |next_page| + expect(next_page.end_reached?).to be_truthy + end + + subject + end + end + + context 'when retrieving an empty page' do + let(:relation) { Project.where('id > ?', Project.maximum(:id) + 1).order(id: :asc) } + + it 'indicates this is the last page' do + expect(request).to receive(:apply_headers) do |next_page| + expect(next_page.end_reached?).to be_truthy + end + + subject + end + end + + it 'returns an array with the loaded records' do + expect(subject).to eq(relation.limit(page.per_page).to_a) + end + + context 'validating the order clause' do + let(:page) { Gitlab::Pagination::Keyset::Page.new(order_by: { created_at: :asc }, per_page: 3) } + + it 'raises an error if has a different order clause than the page' do + expect { subject }.to raise_error(ArgumentError, /order_by does not match/) + end + end + end +end diff --git a/spec/lib/gitlab/pagination/keyset/request_context_spec.rb b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb new file mode 100644 index 00000000000..344ef90efa3 --- /dev/null +++ b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Pagination::Keyset::RequestContext do + let(:request) { double('request', params: params) } + + describe '#page' do + subject { described_class.new(request).page } + + context 'with only order_by given' do + let(:params) { { order_by: :id } } + + it 'extracts order_by/sorting information' do + page = subject + + expect(page.order_by).to eq(id: :desc) + end + end + + context 'with order_by and sort given' do + let(:params) { { order_by: :created_at, sort: :desc } } + + it 'extracts order_by/sorting information and adds tie breaker' do + page = subject + + expect(page.order_by).to eq(created_at: :desc, id: :desc) + end + end + + context 'with no order_by information given' do + let(:params) { {} } + + it 'defaults to tie breaker' do + page = subject + + expect(page.order_by).to eq({ id: :desc }) + end + end + + context 'with per_page params given' do + let(:params) { { per_page: 10 } } + + it 'extracts per_page information' do + page = subject + + expect(page.per_page).to eq(params[:per_page]) + end + end + end + + describe '#apply_headers' do + let(:request) { double('request', url: "http://#{Gitlab.config.gitlab.host}/api/v4/projects?foo=bar") } + let(:params) { { foo: 'bar' } } + let(:request_context) { double('request context', params: params, request: request) } + let(:next_page) { double('next page', order_by: { id: :asc }, lower_bounds: { id: 42 }, end_reached?: false) } + + subject { described_class.new(request_context).apply_headers(next_page) } + + it 'sets Links header with same host/path as the original request' do + orig_uri = URI.parse(request_context.request.url) + + expect(request_context).to receive(:header) do |name, header| + expect(name).to eq('Links') + + first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures + + uri = URI.parse(first_link) + + expect(uri.host).to eq(orig_uri.host) + expect(uri.path).to eq(orig_uri.path) + end + + subject + end + + it 'sets Links header with a link to the next page' do + orig_uri = URI.parse(request_context.request.url) + + expect(request_context).to receive(:header) do |name, header| + expect(name).to eq('Links') + + first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures + + query = CGI.parse(URI.parse(first_link).query) + + expect(query.except('id_after')).to eq(CGI.parse(orig_uri.query).except('id_after')) + expect(query['id_after']).to eq(['42']) + end + + subject + end + + context 'with descending order' do + let(:next_page) { double('next page', order_by: { id: :desc }, lower_bounds: { id: 42 }, end_reached?: false) } + + it 'sets Links header with a link to the next page' do + orig_uri = URI.parse(request_context.request.url) + + expect(request_context).to receive(:header) do |name, header| + expect(name).to eq('Links') + + first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures + + query = CGI.parse(URI.parse(first_link).query) + + expect(query.except('id_before')).to eq(CGI.parse(orig_uri.query).except('id_before')) + expect(query['id_before']).to eq(['42']) + end + + subject + end + end + end +end diff --git a/spec/lib/gitlab/pagination/keyset_spec.rb b/spec/lib/gitlab/pagination/keyset_spec.rb new file mode 100644 index 00000000000..5c2576d7b45 --- /dev/null +++ b/spec/lib/gitlab/pagination/keyset_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Pagination::Keyset do + describe '.paginate' do + subject { described_class.paginate(request_context, relation) } + + let(:request_context) { double } + let(:relation) { double } + let(:pager) { double } + let(:result) { double } + + it 'uses Pager to paginate the relation' do + expect(Gitlab::Pagination::Keyset::Pager).to receive(:new).with(request_context).and_return(pager) + expect(pager).to receive(:paginate).with(relation).and_return(result) + + expect(subject).to eq(result) + end + end + + describe '.available?' do + subject { described_class } + + let(:request_context) { double("request context", page: page)} + let(:page) { double("page", order_by: order_by) } + + shared_examples_for 'keyset pagination is available' do + it 'returns true for Project' do + expect(subject.available?(request_context, Project.all)).to be_truthy + end + + it 'return false for other types of relations' do + expect(subject.available?(request_context, User.all)).to be_falsey + end + end + + context 'with order-by id asc' do + let(:order_by) { { id: :asc } } + + it_behaves_like 'keyset pagination is available' + end + + context 'with order-by id desc' do + let(:order_by) { { id: :desc } } + + it_behaves_like 'keyset pagination is available' + end + + context 'with other order-by columns' do + let(:order_by) { { created_at: :desc, id: :desc } } + + it 'returns false for Project' do + expect(subject.available?(request_context, Project.all)).to be_falsey + end + + it 'return false for other types of relations' do + expect(subject.available?(request_context, User.all)).to be_falsey + end + end + end +end diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb index b6629fad453..14a5d40d445 100644 --- a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb +++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do set(:project) { create(:project) } let(:redis) { Gitlab::Redis::Cache } + subject(:map) { described_class.new(project) } describe '#get_gitlab_model' do diff --git a/spec/lib/gitlab/phabricator_import/importer_spec.rb b/spec/lib/gitlab/phabricator_import/importer_spec.rb index 99a6e4dad6b..2715b785379 100644 --- a/spec/lib/gitlab/phabricator_import/importer_spec.rb +++ b/spec/lib/gitlab/phabricator_import/importer_spec.rb @@ -11,6 +11,7 @@ describe Gitlab::PhabricatorImport::Importer do describe '#execute' do let(:project) { create(:project, :import_scheduled) } + subject(:importer) { described_class.new(project) } it 'sets a custom jid that will be kept up to date' do diff --git a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb index fd17284eea2..0b6a71290ed 100644 --- a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb +++ b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb @@ -8,6 +8,7 @@ describe Gitlab::PhabricatorImport::ProjectCreator do phabricator_server_url: 'http://phab.example.com', api_token: 'the-token' } end + subject(:creator) { described_class.new(user, params) } describe '#execute' do diff --git a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb index 918ff28c8f5..14a00deeb16 100644 --- a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb +++ b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::PhabricatorImport::UserFinder, :clean_gitlab_redis_cache do let(:project) { create(:project, namespace: create(:group)) } + subject(:finder) { described_class.new(project, ['first-phid', 'second-phid']) } before do diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index 99078f19361..6f4844d4543 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -53,6 +53,7 @@ describe Gitlab::ProjectSearchResults do context "when #{entity_type} is disabled" do let(:project) { disabled_project } + it "hides #{blob_kind} from members" do project.add_reporter(user) diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb index 5559b1e4291..a2e3e2146f3 100644 --- a/spec/lib/gitlab/project_template_spec.rb +++ b/spec/lib/gitlab/project_template_spec.rb @@ -23,6 +23,7 @@ describe Gitlab::ProjectTemplate do described_class.new('nfplainhtml', 'Netlify/Plain HTML', _('A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfplain-html'), described_class.new('nfgitbook', 'Netlify/GitBook', _('A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfgitbook'), described_class.new('nfhexo', 'Netlify/Hexo', _('A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhexo'), + described_class.new('salesforcedx', 'SalesforceDX', _('A project boilerplate for Salesforce App development with Salesforce Developer tools.'), 'https://gitlab.com/gitlab-org/project-templates/salesforcedx'), described_class.new('serverless_framework', 'Serverless Framework/JS', _('A basic page and serverless function that uses AWS Lambda, AWS API Gateway, and GitLab Pages'), 'https://gitlab.com/gitlab-org/project-templates/serverless-framework', 'illustrations/logos/serverless_framework.svg') ] diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb index 0ad2de218fe..d82b7665f85 100644 --- a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb @@ -5,8 +5,8 @@ require 'spec_helper' describe Gitlab::Prometheus::Queries::DeploymentQuery do let(:environment) { create(:environment, slug: 'environment-slug') } let(:deployment) { create(:deployment, environment: environment) } - let(:client) { double('prometheus_client') } + subject { described_class.new(client) } around do |example| diff --git a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb index 6361893c53c..fa2dccc7c92 100644 --- a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb @@ -7,8 +7,8 @@ describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do let(:project) { create(:project) } let(:serverless_func) { Serverless::Function.new(project, 'test-name', 'test-ns') } - let(:client) { double('prometheus_client') } + subject { described_class.new(client) } context 'verify queries' do @@ -16,12 +16,12 @@ describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do create(:prometheus_metric, :common, identifier: :system_metrics_knative_function_invocation_count, - query: 'sum(ceil(rate(istio_requests_total{destination_service_namespace="%{kube_namespace}", destination_app=~"%{function_name}.*"}[1m])*60))') + query: 'sum(ceil(rate(istio_requests_total{destination_service_namespace="%{kube_namespace}", destination_service=~"%{function_name}.*"}[1m])*60))') end it 'has the query, but no data' do expect(client).to receive(:query_range).with( - 'sum(ceil(rate(istio_requests_total{destination_service_namespace="test-ns", destination_app=~"test-name.*"}[1m])*60))', + 'sum(ceil(rate(istio_requests_total{destination_service_namespace="test-ns", destination_service=~"test-name.*"}[1m])*60))', hash_including(:start, :stop) ) diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb index 3f9b245a3fb..849265de513 100644 --- a/spec/lib/gitlab/prometheus/query_variables_spec.rb +++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Prometheus::QueryVariables do it do is_expected.to include(environment_filter: - %{container_name!="POD",environment="#{slug}"}) + %Q[container_name!="POD",environment="#{slug}"]) end context 'without deployment platform' do diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb index 86a1c14ed3f..4f9315e28e9 100644 --- a/spec/lib/gitlab/prometheus_client_spec.rb +++ b/spec/lib/gitlab/prometheus_client_spec.rb @@ -327,6 +327,7 @@ describe Gitlab::PrometheusClient do context "without response code" do let(:response_error) { Gitlab::HTTP::ResponseError } + it 'raises PrometheusClient::Error' do expect { subject.proxy('query', { query: prometheus_query }) }.to( raise_error(Gitlab::PrometheusClient::Error, 'Network connection error') diff --git a/spec/lib/gitlab/puma_logging/json_formatter_spec.rb b/spec/lib/gitlab/puma_logging/json_formatter_spec.rb new file mode 100644 index 00000000000..f7f5b99d5e5 --- /dev/null +++ b/spec/lib/gitlab/puma_logging/json_formatter_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::PumaLogging::JSONFormatter do + it "generate json format with timestamp and pid" do + Timecop.freeze( Time.utc(2019, 12, 04, 9, 10, 11, 123456)) do + expect(subject.call('log message')).to eq "{\"timestamp\":\"2019-12-04T09:10:11.123Z\",\"pid\":#{Process.pid},\"message\":\"log message\"}" + end + end +end diff --git a/spec/lib/gitlab/query_limiting/middleware_spec.rb b/spec/lib/gitlab/query_limiting/middleware_spec.rb index fb1c30118c2..f996ea38bb9 100644 --- a/spec/lib/gitlab/query_limiting/middleware_spec.rb +++ b/spec/lib/gitlab/query_limiting/middleware_spec.rb @@ -7,8 +7,9 @@ describe Gitlab::QueryLimiting::Middleware do it 'runs the application with query limiting in place' do middleware = described_class.new(-> (env) { env }) - expect_any_instance_of(Gitlab::QueryLimiting::Transaction) - .to receive(:act_upon_results) + expect_next_instance_of(Gitlab::QueryLimiting::Transaction) do |instance| + expect(instance).to receive(:act_upon_results) + end expect(middleware.call({ number: 10 })) .to eq({ number: 10 }) diff --git a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb index e4f25bc35a9..d0bb032f776 100644 --- a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb +++ b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb @@ -9,6 +9,7 @@ Hello! Let's do this! /sub_name I like this stuff EOF end + subject do described_class.new(:sub_name, action_block: proc { |text| "#{text} foo" }) end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 1397add9f5a..c580b46cf8d 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -61,6 +61,12 @@ describe Gitlab::Regex do it { is_expected.to match('my/image') } it { is_expected.to match('my/awesome/image-1') } it { is_expected.to match('my/awesome/image.test') } + it { is_expected.to match('my/awesome/image--test') } + # docker distribution allows for infinite `-` + # https://github.com/docker/distribution/blob/master/reference/regexp.go#L13 + # but we have a range of 0,10 to add a reasonable limit. + it { is_expected.not_to match('my/image-----------test') } + it { is_expected.not_to match('my/image-.test') } it { is_expected.not_to match('.my/image') } it { is_expected.not_to match('my/image.') } end diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb index cde12d4b310..87b8029de2e 100644 --- a/spec/lib/gitlab/request_context_spec.rb +++ b/spec/lib/gitlab/request_context_spec.rb @@ -43,7 +43,9 @@ describe Gitlab::RequestContext do let(:ip) { '192.168.1.11' } before do - allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip) + allow_next_instance_of(Rack::Request) do |instance| + allow(instance).to receive(:ip).and_return(ip) + end described_class.new(app).call(env) end diff --git a/spec/lib/gitlab/sanitizers/svg_spec.rb b/spec/lib/gitlab/sanitizers/svg_spec.rb index a8c7495376d..18fa96a2914 100644 --- a/spec/lib/gitlab/sanitizers/svg_spec.rb +++ b/spec/lib/gitlab/sanitizers/svg_spec.rb @@ -14,7 +14,9 @@ describe Gitlab::Sanitizers::SVG do let(:sanitized) { File.read(sanitized_svg_path) } it 'delegates sanitization to scrubber' do - expect_any_instance_of(Gitlab::Sanitizers::SVG::Scrubber).to receive(:scrub).at_least(:once) + expect_next_instance_of(Gitlab::Sanitizers::SVG::Scrubber) do |instance| + expect(instance).to receive(:scrub).at_least(:once) + end described_class.clean(data) end diff --git a/spec/lib/gitlab/sentry_spec.rb b/spec/lib/gitlab/sentry_spec.rb deleted file mode 100644 index 024ac733a07..00000000000 --- a/spec/lib/gitlab/sentry_spec.rb +++ /dev/null @@ -1,123 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Sentry do - describe '.context' do - it 'adds the expected tags' do - expect(described_class).to receive(:enabled?).and_return(true) - allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid') - - described_class.context(nil) - - expect(Raven.tags_context[:locale].to_s).to eq(I18n.locale.to_s) - expect(Raven.tags_context[Labkit::Correlation::CorrelationId::LOG_KEY.to_sym].to_s) - .to eq('cid') - end - end - - describe '.track_exception' do - let(:exception) { RuntimeError.new('boom') } - - before do - allow(described_class).to receive(:enabled?).and_return(true) - end - - it 'raises the exception if it should' do - expect(described_class).to receive(:should_raise_for_dev?).and_return(true) - expect { described_class.track_exception(exception) } - .to raise_error(RuntimeError) - end - - context 'when exceptions should not be raised' do - before do - allow(described_class).to receive(:should_raise_for_dev?).and_return(false) - allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid') - end - - it 'logs the exception with all attributes passed' do - expected_extras = { - some_other_info: 'info', - issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' - } - - expected_tags = { - correlation_id: 'cid' - } - - expect(Raven).to receive(:capture_exception) - .with(exception, - tags: a_hash_including(expected_tags), - extra: a_hash_including(expected_extras)) - - described_class.track_exception( - exception, - issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1', - extra: { some_other_info: 'info' } - ) - end - - it 'sets the context' do - expect(described_class).to receive(:context) - - described_class.track_exception(exception) - end - end - end - - context '.track_acceptable_exception' do - let(:exception) { RuntimeError.new('boom') } - let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' } - - before do - allow(described_class).to receive(:enabled?).and_return(true) - allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid') - end - - it 'calls Raven.capture_exception' do - expected_extras = { - some_other_info: 'info', - issue_url: issue_url - } - - expected_tags = { - correlation_id: 'cid' - } - - expect(Raven).to receive(:capture_exception) - .with(exception, - tags: a_hash_including(expected_tags), - extra: a_hash_including(expected_extras)) - - described_class.track_acceptable_exception( - exception, - issue_url: issue_url, - extra: { some_other_info: 'info' } - ) - end - - context 'the exception implements :sentry_extra_data' do - let(:extra_info) { { event: 'explosion', size: :massive } } - let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info) } - - it 'includes the extra data from the exception in the tracking information' do - expect(Raven).to receive(:capture_exception) - .with(exception, a_hash_including(extra: a_hash_including(extra_info))) - - described_class.track_acceptable_exception(exception) - end - end - - context 'the exception implements :sentry_extra_data, which returns nil' do - let(:exception) { double(message: 'bang!', sentry_extra_data: nil) } - - it 'just includes the other extra info' do - extra_info = { issue_url: issue_url } - expect(Raven).to receive(:capture_exception) - .with(exception, a_hash_including(extra: a_hash_including(extra_info))) - - described_class.track_acceptable_exception(exception, extra_info) - end - end - end -end diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb index 2245c3ee8e2..728c44df4f3 100644 --- a/spec/lib/gitlab/sherlock/transaction_spec.rb +++ b/spec/lib/gitlab/sherlock/transaction_spec.rb @@ -167,8 +167,9 @@ describe Gitlab::Sherlock::Transaction do allow(Gitlab::Sherlock).to receive(:enable_line_profiler?) .and_return(true) - allow_any_instance_of(Gitlab::Sherlock::LineProfiler) - .to receive(:profile).and_return('cats are amazing', []) + allow_next_instance_of(Gitlab::Sherlock::LineProfiler) do |instance| + allow(instance).to receive(:profile).and_return('cats are amazing', []) + end retval = transaction.profile_lines { 'cats are amazing' } diff --git a/spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb b/spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb index 0ff694d409b..d5ed939485a 100644 --- a/spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb @@ -28,7 +28,9 @@ describe Gitlab::SidekiqMiddleware::CorrelationInjector do end it 'injects into payload the correlation id' do - expect_any_instance_of(described_class).to receive(:call).and_call_original + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:call).and_call_original + end Labkit::Correlation::CorrelationId.use_id('new-correlation-id') do TestWorker.perform_async(1234) diff --git a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb index 0d8cff3a295..36c6f377bde 100644 --- a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb @@ -3,106 +3,201 @@ require 'fast_spec_helper' describe Gitlab::SidekiqMiddleware::Metrics do - let(:middleware) { described_class.new } - let(:concurrency_metric) { double('concurrency metric') } - - let(:queue_duration_seconds) { double('queue duration seconds metric') } - let(:completion_seconds_metric) { double('completion seconds metric') } - let(:user_execution_seconds_metric) { double('user execution seconds metric') } - let(:failed_total_metric) { double('failed total metric') } - let(:retried_total_metric) { double('retried total metric') } - let(:running_jobs_metric) { double('running jobs metric') } - - before do - allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_queue_duration_seconds, anything, anything, anything).and_return(queue_duration_seconds) - allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything, anything).and_return(completion_seconds_metric) - allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_cpu_seconds, anything, anything, anything).and_return(user_execution_seconds_metric) - allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric) - allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric) - allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :all).and_return(running_jobs_metric) - allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_concurrency, anything, {}, :all).and_return(concurrency_metric) - - allow(concurrency_metric).to receive(:set) - end + context "with worker attribution" do + subject { described_class.new } - describe '#initialize' do - it 'sets general metrics' do - expect(concurrency_metric).to receive(:set).with({}, Sidekiq.options[:concurrency].to_i) + let(:queue) { :test } + let(:worker_class) { worker.class } + let(:job) { {} } + let(:job_status) { :done } + let(:labels_with_job_status) { labels.merge(job_status: job_status.to_s) } + let(:default_labels) { { queue: queue.to_s, boundary: "", external_dependencies: "no", feature_category: "", latency_sensitive: "no" } } + + shared_examples "a metrics middleware" do + context "with mocked prometheus" do + let(:concurrency_metric) { double('concurrency metric') } + + let(:queue_duration_seconds) { double('queue duration seconds metric') } + let(:completion_seconds_metric) { double('completion seconds metric') } + let(:user_execution_seconds_metric) { double('user execution seconds metric') } + let(:failed_total_metric) { double('failed total metric') } + let(:retried_total_metric) { double('retried total metric') } + let(:running_jobs_metric) { double('running jobs metric') } + + before do + allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_queue_duration_seconds, anything, anything, anything).and_return(queue_duration_seconds) + allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything, anything).and_return(completion_seconds_metric) + allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_cpu_seconds, anything, anything, anything).and_return(user_execution_seconds_metric) + allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric) + allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric) + allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :all).and_return(running_jobs_metric) + allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_concurrency, anything, {}, :all).and_return(concurrency_metric) + + allow(concurrency_metric).to receive(:set) + end + + describe '#initialize' do + it 'sets concurrency metrics' do + expect(concurrency_metric).to receive(:set).with({}, Sidekiq.options[:concurrency].to_i) + + subject + end + end + + describe '#call' do + let(:thread_cputime_before) { 1 } + let(:thread_cputime_after) { 2 } + let(:thread_cputime_duration) { thread_cputime_after - thread_cputime_before } + + let(:monotonic_time_before) { 11 } + let(:monotonic_time_after) { 20 } + let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before } + + let(:queue_duration_for_job) { 0.01 } + + before do + allow(subject).to receive(:get_thread_cputime).and_return(thread_cputime_before, thread_cputime_after) + allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after) + allow(Gitlab::InstrumentationHelper).to receive(:queue_duration_for_job).with(job).and_return(queue_duration_for_job) + + expect(running_jobs_metric).to receive(:increment).with(labels, 1) + expect(running_jobs_metric).to receive(:increment).with(labels, -1) + + expect(queue_duration_seconds).to receive(:observe).with(labels, queue_duration_for_job) if queue_duration_for_job + expect(user_execution_seconds_metric).to receive(:observe).with(labels_with_job_status, thread_cputime_duration) + expect(completion_seconds_metric).to receive(:observe).with(labels_with_job_status, monotonic_time_duration) + end + + it 'yields block' do + expect { |b| subject.call(worker, job, :test, &b) }.to yield_control.once + end + + it 'sets queue specific metrics' do + subject.call(worker, job, :test) { nil } + end + + context 'when job_duration is not available' do + let(:queue_duration_for_job) { nil } + + it 'does not set the queue_duration_seconds histogram' do + expect(queue_duration_seconds).not_to receive(:observe) + + subject.call(worker, job, :test) { nil } + end + end + + context 'when error is raised' do + let(:job_status) { :fail } + + it 'sets sidekiq_jobs_failed_total and reraises' do + expect(failed_total_metric).to receive(:increment).with(labels, 1) + + expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed") + end + end + + context 'when job is retried' do + let(:job) { { 'retry_count' => 1 } } + + it 'sets sidekiq_jobs_retried_total metric' do + expect(retried_total_metric).to receive(:increment) + + subject.call(worker, job, :test) { nil } + end + end + end + end - middleware - end - end + context "with prometheus integrated" do + describe '#call' do + it 'yields block' do + expect { |b| subject.call(worker, job, :test, &b) }.to yield_control.once + end - it 'ignore user execution when measured 0' do - allow(completion_seconds_metric).to receive(:observe) + context 'when error is raised' do + let(:job_status) { :fail } - expect(user_execution_seconds_metric).not_to receive(:observe) - end + it 'sets sidekiq_jobs_failed_total and reraises' do + expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed") + end + end + end + end + end - describe '#call' do - let(:worker) { double(:worker) } + context "when workers are not attributed" do + class TestNonAttributedWorker + include Sidekiq::Worker + end + let(:worker) { TestNonAttributedWorker.new } + let(:labels) { default_labels } - let(:job) { {} } - let(:job_status) { :done } - let(:labels) { { queue: :test } } - let(:labels_with_job_status) { { queue: :test, job_status: job_status } } + it_behaves_like "a metrics middleware" + end - let(:thread_cputime_before) { 1 } - let(:thread_cputime_after) { 2 } - let(:thread_cputime_duration) { thread_cputime_after - thread_cputime_before } + context "when workers are attributed" do + def create_attributed_worker_class(latency_sensitive, external_dependencies, resource_boundary, category) + Class.new do + include Sidekiq::Worker + include WorkerAttributes + + latency_sensitive_worker! if latency_sensitive + worker_has_external_dependencies! if external_dependencies + worker_resource_boundary resource_boundary unless resource_boundary == :unknown + feature_category category unless category.nil? + end + end - let(:monotonic_time_before) { 11 } - let(:monotonic_time_after) { 20 } - let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before } + let(:latency_sensitive) { false } + let(:external_dependencies) { false } + let(:resource_boundary) { :unknown } + let(:feature_category) { nil } + let(:worker_class) { create_attributed_worker_class(latency_sensitive, external_dependencies, resource_boundary, feature_category) } + let(:worker) { worker_class.new } - let(:queue_duration_for_job) { 0.01 } + context "latency sensitive" do + let(:latency_sensitive) { true } + let(:labels) { default_labels.merge(latency_sensitive: "yes") } - before do - allow(middleware).to receive(:get_thread_cputime).and_return(thread_cputime_before, thread_cputime_after) - allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after) - allow(Gitlab::InstrumentationHelper).to receive(:queue_duration_for_job).with(job).and_return(queue_duration_for_job) + it_behaves_like "a metrics middleware" + end - expect(running_jobs_metric).to receive(:increment).with(labels, 1) - expect(running_jobs_metric).to receive(:increment).with(labels, -1) + context "external dependencies" do + let(:external_dependencies) { true } + let(:labels) { default_labels.merge(external_dependencies: "yes") } - expect(queue_duration_seconds).to receive(:observe).with(labels, queue_duration_for_job) if queue_duration_for_job - expect(user_execution_seconds_metric).to receive(:observe).with(labels_with_job_status, thread_cputime_duration) - expect(completion_seconds_metric).to receive(:observe).with(labels_with_job_status, monotonic_time_duration) - end + it_behaves_like "a metrics middleware" + end - it 'yields block' do - expect { |b| middleware.call(worker, job, :test, &b) }.to yield_control.once - end + context "cpu boundary" do + let(:resource_boundary) { :cpu } + let(:labels) { default_labels.merge(boundary: "cpu") } - it 'sets queue specific metrics' do - middleware.call(worker, job, :test) { nil } - end + it_behaves_like "a metrics middleware" + end - context 'when job_duration is not available' do - let(:queue_duration_for_job) { nil } + context "memory boundary" do + let(:resource_boundary) { :memory } + let(:labels) { default_labels.merge(boundary: "memory") } - it 'does not set the queue_duration_seconds histogram' do - middleware.call(worker, job, :test) { nil } + it_behaves_like "a metrics middleware" end - end - context 'when job is retried' do - let(:job) { { 'retry_count' => 1 } } + context "feature category" do + let(:feature_category) { :authentication } + let(:labels) { default_labels.merge(feature_category: "authentication") } - it 'sets sidekiq_jobs_retried_total metric' do - expect(retried_total_metric).to receive(:increment) - - middleware.call(worker, job, :test) { nil } + it_behaves_like "a metrics middleware" end - end - - context 'when error is raised' do - let(:job_status) { :fail } - it 'sets sidekiq_jobs_failed_total and reraises' do - expect(failed_total_metric).to receive(:increment).with(labels, 1) + context "combined" do + let(:latency_sensitive) { true } + let(:external_dependencies) { true } + let(:resource_boundary) { :cpu } + let(:feature_category) { :authentication } + let(:labels) { default_labels.merge(latency_sensitive: "yes", external_dependencies: "yes", boundary: "cpu", feature_category: "authentication") } - expect { middleware.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed") + it_behaves_like "a metrics middleware" end end end diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb new file mode 100644 index 00000000000..aef472e0648 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'sidekiq/testing' + +describe Gitlab::SidekiqMiddleware do + class TestWorker + include Sidekiq::Worker + + def perform(_arg) + end + end + + around do |example| + Sidekiq::Testing.inline! { example.run } + end + + let(:worker_class) { TestWorker } + let(:job_args) { [0.01] } + + # The test sets up a new server middleware stack, ensuring that the + # appropriate middlewares, as passed into server_configurator, + # are invoked. + # Additionally the test ensure that each middleware is + # 1) not failing + # 2) yielding exactly once + describe '.server_configurator' do + around do |example| + original = Sidekiq::Testing.server_middleware.dup + + example.run + + Sidekiq::Testing.instance_variable_set :@server_chain, original + end + + let(:middleware_expected_args) { [a_kind_of(worker_class), hash_including({ 'args' => job_args }), anything] } + let(:all_sidekiq_middlewares) do + [ + Gitlab::SidekiqMiddleware::Monitor, + Gitlab::SidekiqMiddleware::BatchLoader, + Gitlab::SidekiqMiddleware::CorrelationLogger, + Gitlab::SidekiqMiddleware::InstrumentationLogger, + Gitlab::SidekiqStatus::ServerMiddleware, + Gitlab::SidekiqMiddleware::Metrics, + Gitlab::SidekiqMiddleware::ArgumentsLogger, + Gitlab::SidekiqMiddleware::MemoryKiller, + Gitlab::SidekiqMiddleware::RequestStoreMiddleware + ] + end + let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares } + + before do + Sidekiq::Testing.server_middleware.clear + Sidekiq::Testing.server_middleware(&described_class.server_configurator( + metrics: metrics, + arguments_logger: arguments_logger, + memory_killer: memory_killer, + request_store: request_store + )) + + enabled_sidekiq_middlewares.each do |middleware| + expect_any_instance_of(middleware).to receive(:call).with(*middleware_expected_args).once.and_call_original + end + + disabled_sidekiq_middlewares.each do |middleware| + expect_any_instance_of(Gitlab::SidekiqMiddleware::ArgumentsLogger).not_to receive(:call) + end + end + + context "all optional middlewares off" do + let(:metrics) { false } + let(:arguments_logger) { false } + let(:memory_killer) { false } + let(:request_store) { false } + let(:disabled_sidekiq_middlewares) do + [ + Gitlab::SidekiqMiddleware::Metrics, + Gitlab::SidekiqMiddleware::ArgumentsLogger, + Gitlab::SidekiqMiddleware::MemoryKiller, + Gitlab::SidekiqMiddleware::RequestStoreMiddleware + ] + end + + it "passes through server middlewares" do + worker_class.perform_async(*job_args) + end + end + + context "all optional middlewares on" do + let(:metrics) { true } + let(:arguments_logger) { true } + let(:memory_killer) { true } + let(:request_store) { true } + let(:disabled_sidekiq_middlewares) { [] } + + it "passes through server middlewares" do + worker_class.perform_async(*job_args) + end + end + end + + # The test sets up a new client middleware stack. The test ensures + # that each middleware is: + # 1) not failing + # 2) yielding exactly once + describe '.client_configurator' do + let(:chain) { Sidekiq::Middleware::Chain.new } + let(:job) { { 'args' => job_args } } + let(:queue) { 'default' } + let(:redis_pool) { Sidekiq.redis_pool } + let(:middleware_expected_args) { [worker_class_arg, job, queue, redis_pool] } + + before do + described_class.client_configurator.call(chain) + end + + shared_examples "a client middleware chain" do + # Its possible that a middleware could accidentally omit a yield call + # this will prevent the full middleware chain from being executed. + # This test ensures that this does not happen + it "invokes the chain" do + expect_any_instance_of(Gitlab::SidekiqStatus::ClientMiddleware).to receive(:call).with(*middleware_expected_args).once.and_call_original + expect_any_instance_of(Gitlab::SidekiqMiddleware::CorrelationInjector).to receive(:call).with(*middleware_expected_args).once.and_call_original + + expect { |b| chain.invoke(worker_class_arg, job, queue, redis_pool, &b) }.to yield_control.once + end + end + + # Sidekiq documentation states that the worker class could be a string + # or a class reference. We should test for both + context "handles string worker_class values" do + let(:worker_class_arg) { worker_class.to_s } + + it_behaves_like "a client middleware chain" + end + + context "handles string worker_class values" do + let(:worker_class_arg) { worker_class } + + it_behaves_like "a client middleware chain" + end + end +end diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb index 5a8c721a634..73b93589fac 100644 --- a/spec/lib/gitlab/slash_commands/command_spec.rb +++ b/spec/lib/gitlab/slash_commands/command_spec.rb @@ -113,11 +113,13 @@ describe Gitlab::SlashCommands::Command do context 'IssueMove is triggered' do let(:params) { { text: 'issue move #78291 to gitlab/gitlab-ci' } } + it { is_expected.to eq(Gitlab::SlashCommands::IssueMove) } end context 'IssueComment is triggered' do let(:params) { { text: "issue comment #503\ncomment body" } } + it { is_expected.to eq(Gitlab::SlashCommands::IssueComment) } end end diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb index b766a9a1361..a142c8e4c92 100644 --- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb @@ -42,6 +42,7 @@ describe Gitlab::SlashCommands::IssueSearch do describe 'self.match' do let(:query) { "my search keywords" } + it 'matches the query' do match = described_class.match("issue search #{query}") diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb index b5ef417cb93..3741563a744 100644 --- a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb +++ b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb @@ -10,6 +10,7 @@ describe Gitlab::SlashCommands::Presenters::IssueComment do describe '#present' do let(:attachment) { subject[:attachments].first } + subject { described_class.new(note).present } it { is_expected.to be_a(Hash) } diff --git a/spec/lib/gitlab/slash_commands/run_spec.rb b/spec/lib/gitlab/slash_commands/run_spec.rb index 900fae05719..32a23129e3c 100644 --- a/spec/lib/gitlab/slash_commands/run_spec.rb +++ b/spec/lib/gitlab/slash_commands/run_spec.rb @@ -56,13 +56,13 @@ describe Gitlab::SlashCommands::Run do context 'when a pipeline could not be scheduled' do it 'returns an error' do - expect_any_instance_of(Gitlab::Chat::Command) - .to receive(:try_create_pipeline) - .and_return(nil) + expect_next_instance_of(Gitlab::Chat::Command) do |instance| + expect(instance).to receive(:try_create_pipeline).and_return(nil) + end - expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run) - .to receive(:failed_to_schedule) - .with('foo') + expect_next_instance_of(Gitlab::SlashCommands::Presenters::Run) do |instance| + expect(instance).to receive(:failed_to_schedule).with('foo') + end command.execute(command: 'foo', arguments: '') end @@ -77,17 +77,18 @@ describe Gitlab::SlashCommands::Run do persisted?: true ) - expect_any_instance_of(Gitlab::Chat::Command) - .to receive(:try_create_pipeline) - .and_return(pipeline) + expect_next_instance_of(Gitlab::Chat::Command) do |instance| + expect(instance).to receive(:try_create_pipeline).and_return(pipeline) + end expect(Gitlab::Chat::Responder) .to receive(:responder_for) .with(build) .and_return(nil) - expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run) - .to receive(:unsupported_chat_service) + expect_next_instance_of(Gitlab::SlashCommands::Presenters::Run) do |instance| + expect(instance).to receive(:unsupported_chat_service) + end command.execute(command: 'foo', arguments: '') end @@ -103,18 +104,18 @@ describe Gitlab::SlashCommands::Run do persisted?: true ) - expect_any_instance_of(Gitlab::Chat::Command) - .to receive(:try_create_pipeline) - .and_return(pipeline) + expect_next_instance_of(Gitlab::Chat::Command) do |instance| + expect(instance).to receive(:try_create_pipeline).and_return(pipeline) + end expect(Gitlab::Chat::Responder) .to receive(:responder_for) .with(build) .and_return(responder) - expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run) - .to receive(:in_channel_response) - .with(responder.scheduled_output) + expect_next_instance_of(Gitlab::SlashCommands::Presenters::Run) do |instance| + expect(instance).to receive(:in_channel_response).with(responder.scheduled_output) + end command.execute(command: 'foo', arguments: '') end diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb index 31944d51b3c..38b93913f6d 100644 --- a/spec/lib/gitlab/sql/pattern_spec.rb +++ b/spec/lib/gitlab/sql/pattern_spec.rb @@ -207,5 +207,15 @@ describe Gitlab::SQL::Pattern do expect(fuzzy_arel_match.to_sql).to match(/title.+I?LIKE '\%foo\%' AND .*title.*I?LIKE '\%baz\%' AND .*title.*I?LIKE '\%really bar\%'/) end end + + context 'when passing an Arel column' do + let(:query) { 'foo' } + + subject(:fuzzy_arel_match) { Project.fuzzy_arel_match(Route.arel_table[:path], query) } + + it 'returns a condition with the table and column name' do + expect(fuzzy_arel_match.to_sql).to match(/"routes"."path".*ILIKE '\%foo\%'/) + end + end end end diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb index f8becb0c796..08e008c82d9 100644 --- a/spec/lib/gitlab/ssh_public_key_spec.rb +++ b/spec/lib/gitlab/ssh_public_key_spec.rb @@ -183,6 +183,34 @@ describe Gitlab::SSHPublicKey, lib: true do end end + describe '#fingerprint in SHA256 format' do + subject { public_key.fingerprint("SHA256").gsub("SHA256:", "") if public_key.fingerprint("SHA256") } + + where(:factory, :fingerprint_sha256) do + [ + [:rsa_key_2048, 'GdtgO0eHbwLB+mK47zblkoXujkqKRZjgMQrHH6Kks3E'], + [:rsa_key_4096, 'ByDU7hQ1JB95l6p53rHrffc4eXvEtqGUtQhS+Dhyy7g'], + [:rsa_key_5120, 'PCCupLbFHScm4AbEufbGDvhBU27IM0MVAor715qKQK8'], + [:rsa_key_8192, 'CtHFQAS+9Hb8z4vrv4gVQPsHjNN0WIZhWODaB1mQLs4'], + [:dsa_key_2048, '+a3DQ7cU5GM+gaYOfmc0VWNnykHQSuth3VRcCpWuYNI'], + [:ecdsa_key_256, 'C+I5k3D+IGeM6k5iBR1ZsphqTKV+7uvL/XZ5hcrTr7g'], + [:ed25519_key_256, 'DCKAjzxWrdOTjaGKBBjtCW8qY5++GaiAJflrHPmp6W0'] + ] + end + + with_them do + let(:key) { attributes_for(factory)[:key] } + + it { is_expected.to eq(fingerprint_sha256) } + end + + context 'with an invalid SSH key' do + let(:key) { 'this is not a key' } + + it { is_expected.to be_nil } + end + end + describe '#key_text' do let(:key) { 'this is not a key' } diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb index 7ed43db3d10..ef9be7cd992 100644 --- a/spec/lib/gitlab/string_range_marker_spec.rb +++ b/spec/lib/gitlab/string_range_marker_spec.rb @@ -9,7 +9,7 @@ describe Gitlab::StringRangeMarker do inline_diffs = [2..5] described_class.new(raw, rich).mark(inline_diffs) do |text, left:, right:| - "LEFT#{text}RIGHT" + "LEFT#{text}RIGHT".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 2b19edbe7f9..2ab1ccc447b 100644 --- a/spec/lib/gitlab/string_regex_marker_spec.rb +++ b/spec/lib/gitlab/string_regex_marker_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::StringRegexMarker do subject do described_class.new(raw, rich).mark(/"[^"]+":\s*"(?<name>[^"]+)"/, group: :name) do |text, left:, right:| - %{<a href="#">#{text}</a>} + %{<a href="#">#{text}</a>}.html_safe end end @@ -26,7 +26,7 @@ describe Gitlab::StringRegexMarker do subject do described_class.new(raw, rich).mark(/<[a-z]>/) do |text, left:, right:| - %{<strong>#{text}</strong>} + %{<strong>#{text}</strong>}.html_safe end end diff --git a/spec/lib/gitlab/throttle_spec.rb b/spec/lib/gitlab/throttle_spec.rb new file mode 100644 index 00000000000..674646a5f06 --- /dev/null +++ b/spec/lib/gitlab/throttle_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Throttle do + describe '.protected_paths_enabled?' do + subject { described_class.protected_paths_enabled? } + + context 'when omnibus protected paths throttle should be used' do + before do + expect(described_class).to receive(:should_use_omnibus_protected_paths?).and_return(true) + end + + it { is_expected.to be_falsey } + end + + context 'when omnibus protected paths throttle should not be used' do + before do + expect(described_class).to receive(:should_use_omnibus_protected_paths?).and_return(false) + end + + it 'returns Application Settings throttle_protected_paths_enabled?' do + expect(Gitlab::CurrentSettings.current_application_settings).to receive(:throttle_protected_paths_enabled?) + + subject + end + end + end + + describe '.should_use_omnibus_protected_paths?' do + subject { described_class.should_use_omnibus_protected_paths? } + + context 'when rack_attack.admin_area_protected_paths_enabled config is unspecified' do + context 'when the omnibus protected paths throttle has been recently used (it has data)' do + before do + expect(described_class).to receive(:omnibus_protected_paths_present?).and_return(true) + end + + it { is_expected.to be_truthy } + end + + context 'when the omnibus protected paths throttle has not been recently used' do + before do + expect(described_class).to receive(:omnibus_protected_paths_present?).and_return(false) + end + + it { is_expected.to be_falsey } + end + end + + context 'when rack_attack.admin_area_protected_paths_enabled config is false' do + before do + stub_config(rack_attack: { + admin_area_protected_paths_enabled: false + }) + end + + context 'when the omnibus protected paths throttle has been recently used (it has data)' do + before do + expect(described_class).to receive(:omnibus_protected_paths_present?).and_return(true) + end + + it { is_expected.to be_truthy } + end + + context 'when the omnibus protected paths throttle has not been recently used' do + before do + expect(described_class).to receive(:omnibus_protected_paths_present?).and_return(false) + end + + it { is_expected.to be_falsey } + end + end + + context 'when rack_attack.admin_area_protected_paths_enabled config is true' do + before do + stub_config(rack_attack: { + admin_area_protected_paths_enabled: true + }) + + expect(described_class).not_to receive(:omnibus_protected_paths_present?) + end + + it { is_expected.to be_falsey } + end + end +end diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb index dc877f20cae..efb07d9dc95 100644 --- a/spec/lib/gitlab/tracking_spec.rb +++ b/spec/lib/gitlab/tracking_spec.rb @@ -97,7 +97,7 @@ describe Gitlab::Tracking do '_property_', '_value_', nil, - timestamp.to_i + (timestamp.to_f * 1000).to_i ) track_event @@ -130,7 +130,7 @@ describe Gitlab::Tracking do expect(tracker).to receive(:track_self_describing_event).with( '_event_json_', nil, - timestamp.to_i + (timestamp.to_f * 1000).to_i ) track_event diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index 08d3c638f9e..0aab02b6c4c 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -59,6 +59,26 @@ describe Gitlab::UrlBuilder do end end + context 'when passing a ProjectSnippet' do + it 'returns a proper URL' do + project_snippet = create(:project_snippet) + + url = described_class.build(project_snippet) + + expect(url).to eq "#{Settings.gitlab['url']}/#{project_snippet.project.full_path}/snippets/#{project_snippet.id}" + end + end + + context 'when passing a PersonalSnippet' do + it 'returns a proper URL' do + personal_snippet = create(:personal_snippet) + + url = described_class.build(personal_snippet) + + expect(url).to eq "#{Settings.gitlab['url']}/snippets/#{personal_snippet.id}" + end + end + context 'when passing a Note' do context 'on a Commit' do it 'returns a proper URL' do diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 484684eeb65..6ab23b00d5c 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -27,6 +27,7 @@ describe Gitlab::UsageData do create_list(:zoom_meeting, 2, project: projects[0], issue: projects[0].issues[1], issue_status: :removed) create(:zoom_meeting, project: projects[0], issue: projects[0].issues[2], issue_status: :added) create_list(:zoom_meeting, 2, project: projects[0], issue: projects[0].issues[2], issue_status: :removed) + create(:sentry_issue, issue: projects[0].issues[0]) # Enabled clusters gcp_cluster = create(:cluster_provider_gcp, :created).cluster @@ -53,6 +54,8 @@ describe Gitlab::UsageData do create(:grafana_integration, project: projects[1], enabled: true) create(:grafana_integration, project: projects[2], enabled: false) + allow(Gitlab::GrafanaEmbedUsageData).to receive(:issue_count).and_return(2) + ProjectFeature.first.update_attribute('repository_access_level', 0) end @@ -150,8 +153,10 @@ describe Gitlab::UsageData do grafana_integrated_projects groups issues + issues_created_from_gitlab_error_tracking_ui issues_with_associated_zoom_link issues_using_zoom_quick_actions + issues_with_embedded_grafana_charts_approx keys label_lists labels @@ -209,8 +214,10 @@ describe Gitlab::UsageData do expect(count_data[:projects_mattermost_active]).to eq(1) expect(count_data[:projects_with_repositories_enabled]).to eq(3) expect(count_data[:projects_with_error_tracking_enabled]).to eq(1) + expect(count_data[:issues_created_from_gitlab_error_tracking_ui]).to eq(1) expect(count_data[:issues_with_associated_zoom_link]).to eq(2) expect(count_data[:issues_using_zoom_quick_actions]).to eq(3) + expect(count_data[:issues_with_embedded_grafana_charts_approx]).to eq(2) expect(count_data[:clusters_enabled]).to eq(4) expect(count_data[:project_clusters_enabled]).to eq(3) @@ -293,6 +300,24 @@ describe Gitlab::UsageData do end end + describe '#ingress_modsecurity_usage' do + subject { described_class.ingress_modsecurity_usage } + + it 'gathers variable data' do + allow_any_instance_of( + ::Clusters::Applications::IngressModsecurityUsageService + ).to receive(:execute).and_return( + { + ingress_modsecurity_blocking: 1, + ingress_modsecurity_disabled: 2 + } + ) + + expect(subject[:ingress_modsecurity_blocking]).to eq(1) + expect(subject[:ingress_modsecurity_disabled]).to eq(2) + end + end + describe '#license_usage_data' do subject { described_class.license_usage_data } diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb index 75dc7d8e6d1..16a05af2216 100644 --- a/spec/lib/gitlab/visibility_level_spec.rb +++ b/spec/lib/gitlab/visibility_level_spec.rb @@ -95,4 +95,28 @@ describe Gitlab::VisibilityLevel do expect(described_class.valid_level?(described_class::PUBLIC)).to be_truthy end end + + describe '#visibility_level_decreased?' do + let(:project) { create(:project, :internal) } + + context 'when visibility level decreases' do + before do + project.update!(visibility_level: described_class::PRIVATE) + end + + it 'returns true' do + expect(project.visibility_level_decreased?).to be(true) + end + end + + context 'when visibility level does not decrease' do + before do + project.update!(visibility_level: described_class::PUBLIC) + end + + it 'returns false' do + expect(project.visibility_level_decreased?).to be(false) + end + end + end end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 88bc5034da5..89381057f6b 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -116,6 +116,7 @@ describe Gitlab::Workhorse do describe '.send_git_patch' do let(:diff_refs) { double(base_sha: "base", head_sha: "head") } + subject { described_class.send_git_patch(repository, diff_refs) } it 'sets the header correctly' do @@ -178,6 +179,7 @@ describe Gitlab::Workhorse do describe '.send_git_diff' do let(:diff_refs) { double(base_sha: "base", head_sha: "head") } + subject { described_class.send_git_diff(repository, diff_refs) } it 'sets the header correctly' do diff --git a/spec/lib/google_api/auth_spec.rb b/spec/lib/google_api/auth_spec.rb index a25004ac385..719e98c5fdf 100644 --- a/spec/lib/google_api/auth_spec.rb +++ b/spec/lib/google_api/auth_spec.rb @@ -30,8 +30,9 @@ describe GoogleApi::Auth do end before do - allow_any_instance_of(OAuth2::Strategy::AuthCode) - .to receive(:get_token).and_return(token) + allow_next_instance_of(OAuth2::Strategy::AuthCode) do |instance| + allow(instance).to receive(:get_token).and_return(token) + end end it 'returns token and expires_at' do diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb index 473ad639ead..bd063648ca1 100644 --- a/spec/lib/google_api/cloud_platform/client_spec.rb +++ b/spec/lib/google_api/cloud_platform/client_spec.rb @@ -92,7 +92,12 @@ describe GoogleApi::CloudPlatform::Client do name: cluster_name, initial_node_count: cluster_size, node_config: { - machine_type: machine_type + machine_type: machine_type, + oauth_scopes: [ + "https://www.googleapis.com/auth/devstorage.read_only", + "https://www.googleapis.com/auth/logging.write", + "https://www.googleapis.com/auth/monitoring" + ] }, master_auth: { username: 'admin', diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb index a127c787e28..62fddbb97c7 100644 --- a/spec/lib/json_web_token/rsa_token_spec.rb +++ b/spec/lib/json_web_token/rsa_token_spec.rb @@ -41,6 +41,7 @@ describe JSONWebToken::RSAToken do context 'for invalid key to raise an exception' do let(:new_key) { OpenSSL::PKey::RSA.generate(512) } + subject { JWT.decode(rsa_encoded, new_key, true, { algorithm: 'RS256' }) } it { expect {subject}.to raise_error(JWT::DecodeError) } diff --git a/spec/lib/marginalia_spec.rb b/spec/lib/marginalia_spec.rb new file mode 100644 index 00000000000..5dc54af99ce --- /dev/null +++ b/spec/lib/marginalia_spec.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Marginalia spec' do + class MarginaliaTestController < ActionController::Base + def first_user + User.first + render body: nil + end + end + + class MarginaliaTestJob + include Sidekiq::Worker + + def perform + User.first + end + end + + class MarginaliaTestMailer < BaseMailer + def first_user + User.first + end + end + + def add_sidekiq_middleware + # Reference: https://github.com/mperham/sidekiq/wiki/Testing#testing-server-middlewaresidekiq + # Sidekiq test harness fakes worker without its server middlewares, so include instrumentation to 'Sidekiq::Testing' server middleware. + Sidekiq::Testing.server_middleware do |chain| + chain.add Marginalia::SidekiqInstrumentation::Middleware + end + end + + def remove_sidekiq_middleware + Sidekiq::Testing.server_middleware do |chain| + chain.remove Marginalia::SidekiqInstrumentation::Middleware + end + end + + def stub_feature(value) + allow(Gitlab::Marginalia).to receive(:cached_feature_enabled?).and_return(value) + end + + def make_request(correlation_id) + request_env = Rack::MockRequest.env_for('/') + + ::Labkit::Correlation::CorrelationId.use_id(correlation_id) do + MarginaliaTestController.action(:first_user).call(request_env) + end + end + + describe 'For rails web requests' do + let(:correlation_id) { SecureRandom.uuid } + let(:recorded) { ActiveRecord::QueryRecorder.new { make_request(correlation_id) } } + + let(:component_map) do + { + "application" => "test", + "controller" => "marginalia_test", + "action" => "first_user", + "line" => "/spec/support/helpers/query_recorder.rb", + "correlation_id" => correlation_id + } + end + + context 'when the feature is enabled' do + before do + stub_feature(true) + end + + it 'generates a query that includes the component and value' do + component_map.each do |component, value| + expect(recorded.log.last).to include("#{component}:#{value}") + end + end + end + + context 'when the feature is disabled' do + before do + stub_feature(false) + end + + it 'excludes annotations in generated queries' do + expect(recorded.log.last).not_to include("/*") + expect(recorded.log.last).not_to include("*/") + end + end + end + + describe 'for Sidekiq worker jobs' do + before(:all) do + add_sidekiq_middleware + + # Because of faking, 'Sidekiq.server?' does not work so implicitly set application name which is done in config/initializers/0_marginalia.rb + Marginalia.application_name = "sidekiq" + end + + after(:all) do + MarginaliaTestJob.clear + remove_sidekiq_middleware + end + + around do |example| + Sidekiq::Testing.fake! { example.run } + end + + before do + MarginaliaTestJob.perform_async + end + + let(:sidekiq_job) { MarginaliaTestJob.jobs.first } + let(:recorded) { ActiveRecord::QueryRecorder.new { MarginaliaTestJob.drain } } + + let(:component_map) do + { + "application" => "sidekiq", + "job_class" => "MarginaliaTestJob", + "line" => "/spec/support/sidekiq_middleware.rb", + "correlation_id" => sidekiq_job['correlation_id'], + "jid" => sidekiq_job['jid'] + } + end + + context 'when the feature is enabled' do + before do + stub_feature(true) + end + + it 'generates a query that includes the component and value' do + component_map.each do |component, value| + expect(recorded.log.last).to include("#{component}:#{value}") + end + end + + describe 'for ActionMailer delivery jobs' do + let(:delivery_job) { MarginaliaTestMailer.first_user.deliver_later } + + let(:recorded) do + ActiveRecord::QueryRecorder.new do + delivery_job.perform_now + end + end + + let(:component_map) do + { + "application" => "sidekiq", + "line" => "/lib/gitlab/i18n.rb", + "jid" => delivery_job.job_id, + "job_class" => delivery_job.arguments.first + } + end + + it 'generates a query that includes the component and value' do + component_map.each do |component, value| + expect(recorded.log.last).to include("#{component}:#{value}") + end + end + end + end + + context 'when the feature is disabled' do + before do + stub_feature(false) + end + + it 'excludes annotations in generated queries' do + expect(recorded.log.last).not_to include("/*") + expect(recorded.log.last).not_to include("*/") + end + end + end +end diff --git a/spec/lib/omni_auth/strategies/saml_spec.rb b/spec/lib/omni_auth/strategies/saml_spec.rb index 73e86872308..447800bd93c 100644 --- a/spec/lib/omni_auth/strategies/saml_spec.rb +++ b/spec/lib/omni_auth/strategies/saml_spec.rb @@ -15,7 +15,9 @@ describe OmniAuth::Strategies::SAML, type: :strategy do it 'stores request ID during request phase' do request_id = double - allow_any_instance_of(OneLogin::RubySaml::Authrequest).to receive(:uuid).and_return(request_id) + allow_next_instance_of(OneLogin::RubySaml::Authrequest) do |instance| + allow(instance).to receive(:uuid).and_return(request_id) + end post '/users/auth/saml' expect(session['last_authn_request_id']).to eq(request_id) diff --git a/spec/lib/quality/helm_client_spec.rb b/spec/lib/quality/helm_client_spec.rb index da5ba4c4d99..795aa43b849 100644 --- a/spec/lib/quality/helm_client_spec.rb +++ b/spec/lib/quality/helm_client_spec.rb @@ -3,7 +3,8 @@ require 'fast_spec_helper' RSpec.describe Quality::HelmClient do - let(:namespace) { 'review-apps-ee' } + let(:tiller_namespace) { 'review-apps-ee' } + let(:namespace) { tiller_namespace } let(:release_name) { 'my-release' } let(:raw_helm_list_page1) do <<~OUTPUT @@ -30,12 +31,12 @@ RSpec.describe Quality::HelmClient do OUTPUT end - subject { described_class.new(namespace: namespace) } + subject { described_class.new(tiller_namespace: tiller_namespace, namespace: namespace) } describe '#releases' do it 'raises an error if the Helm command fails' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)]) + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json)]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) expect { subject.releases.to_a }.to raise_error(described_class::CommandFailedError) @@ -43,7 +44,7 @@ RSpec.describe Quality::HelmClient do it 'calls helm list with default arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)]) + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json)]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) subject.releases.to_a @@ -51,7 +52,7 @@ RSpec.describe Quality::HelmClient do it 'calls helm list with extra arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --deployed)]) + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json --deployed)]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) subject.releases(args: ['--deployed']).to_a @@ -59,7 +60,7 @@ RSpec.describe Quality::HelmClient do it 'returns a list of Release objects' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --deployed)]) + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json --deployed)]) .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true))) releases = subject.releases(args: ['--deployed']).to_a @@ -78,10 +79,10 @@ RSpec.describe Quality::HelmClient do it 'automatically paginates releases' do expect(Gitlab::Popen).to receive(:popen_with_detail).ordered - .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)]) + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json)]) .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page1, '', double(success?: true))) expect(Gitlab::Popen).to receive(:popen_with_detail).ordered - .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --offset review-6709-group-t40qbv)]) + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{tiller_namespace}" --output json --offset review-6709-group-t40qbv)]) .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true))) releases = subject.releases.to_a @@ -94,7 +95,7 @@ RSpec.describe Quality::HelmClient do describe '#delete' do it 'raises an error if the Helm command fails' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name})]) + .with([%(helm delete --tiller-namespace "#{tiller_namespace}" --purge #{release_name})]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError) @@ -102,7 +103,7 @@ RSpec.describe Quality::HelmClient do it 'calls helm delete with default arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name})]) + .with([%(helm delete --tiller-namespace "#{tiller_namespace}" --purge #{release_name})]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) expect(subject.delete(release_name: release_name)).to eq('') @@ -113,7 +114,7 @@ RSpec.describe Quality::HelmClient do it 'raises an error if the Helm command fails' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name.join(' ')})]) + .with([%(helm delete --tiller-namespace "#{tiller_namespace}" --purge #{release_name.join(' ')})]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError) @@ -121,7 +122,7 @@ RSpec.describe Quality::HelmClient do it 'calls helm delete with multiple release names' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name.join(' ')})]) + .with([%(helm delete --tiller-namespace "#{tiller_namespace}" --purge #{release_name.join(' ')})]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) expect(subject.delete(release_name: release_name)).to eq('') diff --git a/spec/lib/quality/kubernetes_client_spec.rb b/spec/lib/quality/kubernetes_client_spec.rb index 5bac102ac41..59d4a977d5e 100644 --- a/spec/lib/quality/kubernetes_client_spec.rb +++ b/spec/lib/quality/kubernetes_client_spec.rb @@ -5,15 +5,27 @@ require 'fast_spec_helper' RSpec.describe Quality::KubernetesClient do let(:namespace) { 'review-apps-ee' } let(:release_name) { 'my-release' } + let(:pod_for_release) { "pod-my-release-abcd" } + let(:raw_resource_names_str) { "NAME\nfoo\n#{pod_for_release}\nbar" } + let(:raw_resource_names) { raw_resource_names_str.lines.map(&:strip) } subject { described_class.new(namespace: namespace) } + describe 'RESOURCE_LIST' do + it 'returns the correct list of resources separated by commas' do + expect(described_class::RESOURCE_LIST).to eq('ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa,crd') + end + end + describe '#cleanup' do + before do + allow(subject).to receive(:raw_resource_names).and_return(raw_resource_names) + end + it 'raises an error if the Kubernetes command fails' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(kubectl --namespace "#{namespace}" delete ) \ - 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \ - "--now --ignore-not-found --include-uninitialized --wait=true -l release=\"#{release_name}\""]) + .with(["kubectl delete #{described_class::RESOURCE_LIST} " + + %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l release="#{release_name}")]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) expect { subject.cleanup(release_name: release_name) }.to raise_error(described_class::CommandFailedError) @@ -21,9 +33,12 @@ RSpec.describe Quality::KubernetesClient do it 'calls kubectl with the correct arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(kubectl --namespace "#{namespace}" delete ) \ - 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \ - "--now --ignore-not-found --include-uninitialized --wait=true -l release=\"#{release_name}\""]) + .with(["kubectl delete #{described_class::RESOURCE_LIST} " + + %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l release="#{release_name}")]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) + + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with([%(kubectl delete --namespace "#{namespace}" #{pod_for_release})]) .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) # We're not verifying the output here, just silencing it @@ -35,20 +50,22 @@ RSpec.describe Quality::KubernetesClient do it 'raises an error if the Kubernetes command fails' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(kubectl --namespace "#{namespace}" delete ) \ - 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \ - "--now --ignore-not-found --include-uninitialized --wait=true -l 'release in (#{release_name.join(', ')})'"]) - .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) + .with(["kubectl delete #{described_class::RESOURCE_LIST} " + + %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l 'release in (#{release_name.join(', ')})')]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) expect { subject.cleanup(release_name: release_name) }.to raise_error(described_class::CommandFailedError) end it 'calls kubectl with the correct arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(kubectl --namespace "#{namespace}" delete ) \ - 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \ - "--now --ignore-not-found --include-uninitialized --wait=true -l 'release in (#{release_name.join(', ')})'"]) - .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) + .with(["kubectl delete #{described_class::RESOURCE_LIST} " + + %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=true -l 'release in (#{release_name.join(', ')})')]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) + + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with([%(kubectl delete --namespace "#{namespace}" #{pod_for_release})]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) # We're not verifying the output here, just silencing it expect { subject.cleanup(release_name: release_name) }.to output.to_stdout @@ -58,24 +75,37 @@ RSpec.describe Quality::KubernetesClient do context 'with `wait: false`' do it 'raises an error if the Kubernetes command fails' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(kubectl --namespace "#{namespace}" delete ) \ - 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \ - "--now --ignore-not-found --include-uninitialized --wait=false -l release=\"#{release_name}\""]) - .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) + .with(["kubectl delete #{described_class::RESOURCE_LIST} " + + %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=false -l release="#{release_name}")]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) expect { subject.cleanup(release_name: release_name, wait: false) }.to raise_error(described_class::CommandFailedError) end it 'calls kubectl with the correct arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(kubectl --namespace "#{namespace}" delete ) \ - 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \ - "--now --ignore-not-found --include-uninitialized --wait=false -l release=\"#{release_name}\""]) - .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) + .with(["kubectl delete #{described_class::RESOURCE_LIST} " + + %(--namespace "#{namespace}" --now --ignore-not-found --include-uninitialized --wait=false -l release="#{release_name}")]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) + + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with([%(kubectl delete --namespace "#{namespace}" #{pod_for_release})]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) # We're not verifying the output here, just silencing it expect { subject.cleanup(release_name: release_name, wait: false) }.to output.to_stdout end end end + + describe '#raw_resource_names' do + it 'calls kubectl to retrieve the resource names' do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with(["kubectl get #{described_class::RESOURCE_LIST} " + + %(--namespace "#{namespace}" -o custom-columns=NAME:.metadata.name)]) + .and_return(Gitlab::Popen::Result.new([], raw_resource_names_str, '', double(success?: true))) + + expect(subject.__send__(:raw_resource_names)).to eq(raw_resource_names) + end + end end diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb index c85994402dd..13817bdcc72 100644 --- a/spec/lib/quality/test_level_spec.rb +++ b/spec/lib/quality/test_level_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Quality::TestLevel do context 'when level is unit' do it 'returns a pattern' do expect(subject.pattern(:unit)) - .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb") + .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb") end end @@ -82,7 +82,7 @@ RSpec.describe Quality::TestLevel do context 'when level is unit' do it 'returns a regexp' do expect(subject.regexp(:unit)) - .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)}) + .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)}) end end diff --git a/spec/lib/sentry/client/projects_spec.rb b/spec/lib/sentry/client/projects_spec.rb new file mode 100644 index 00000000000..462f74eaac9 --- /dev/null +++ b/spec/lib/sentry/client/projects_spec.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Sentry::Client::Projects do + include SentryClientHelpers + + let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' } + let(:token) { 'test-token' } + let(:client) { Sentry::Client.new(sentry_url, token) } + let(:projects_sample_response) do + Gitlab::Utils.deep_indifferent_access( + JSON.parse(fixture_file('sentry/list_projects_sample_response.json')) + ) + end + + shared_examples 'has correct return type' do |klass| + it "returns objects of type #{klass}" do + expect(subject).to all( be_a(klass) ) + end + end + + shared_examples 'has correct length' do |length| + it { expect(subject.length).to eq(length) } + end + + describe '#projects' do + let(:sentry_list_projects_url) { 'https://sentrytest.gitlab.com/api/0/projects/' } + let(:sentry_api_response) { projects_sample_response } + let!(:sentry_api_request) { stub_sentry_request(sentry_list_projects_url, body: sentry_api_response) } + + subject { client.projects } + + it_behaves_like 'calls sentry api' + + it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project + it_behaves_like 'has correct length', 2 + + context 'essential keys missing in API response' do + let(:sentry_api_response) do + projects_sample_response[0...1].map do |project| + project.except(:slug) + end + end + + it 'raises exception' do + expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "slug"') + end + end + + context 'optional keys missing in sentry response' do + let(:sentry_api_response) do + projects_sample_response[0...1].map do |project| + project[:organization].delete(:id) + project.delete(:id) + project.except(:status) + end + end + + it_behaves_like 'calls sentry api' + + it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project + it_behaves_like 'has correct length', 1 + end + + context 'error object created from sentry response' do + using RSpec::Parameterized::TableSyntax + + where(:sentry_project_object, :sentry_response) do + :id | :id + :name | :name + :status | :status + :slug | :slug + :organization_name | [:organization, :name] + :organization_id | [:organization, :id] + :organization_slug | [:organization, :slug] + end + + with_them do + it do + expect(subject[0].public_send(sentry_project_object)).to( + eq(sentry_api_response[0].dig(*sentry_response)) + ) + end + end + end + + context 'redirects' do + let(:sentry_api_url) { sentry_list_projects_url } + + it_behaves_like 'no Sentry redirects' + end + + # Sentry API returns 404 if there are extra slashes in the URL! + context 'extra slashes in URL' do + let(:sentry_url) { 'https://sentrytest.gitlab.com/api//0/projects//' } + let!(:valid_req_stub) do + stub_sentry_request(sentry_list_projects_url) + end + + it 'removes extra slashes in api url' do + expect(Gitlab::HTTP).to receive(:get).with( + URI(sentry_list_projects_url), + anything + ).and_call_original + + subject + + expect(valid_req_stub).to have_been_requested + end + end + + context 'when exception is raised' do + let(:sentry_request_url) { sentry_list_projects_url } + + it_behaves_like 'maps Sentry exceptions' + end + end +end diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb index 8101664d34f..cff06bf4a5f 100644 --- a/spec/lib/sentry/client_spec.rb +++ b/spec/lib/sentry/client_spec.rb @@ -3,8 +3,16 @@ require 'spec_helper' describe Sentry::Client do + include SentryClientHelpers + let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' } let(:token) { 'test-token' } + let(:default_httparty_options) do + { + follow_redirects: false, + headers: { "Authorization" => "Bearer test-token" } + } + end let(:issues_sample_response) do Gitlab::Utils.deep_indifferent_access( @@ -12,102 +20,60 @@ describe Sentry::Client do ) end - let(:projects_sample_response) do - Gitlab::Utils.deep_indifferent_access( - JSON.parse(fixture_file('sentry/list_projects_sample_response.json')) - ) - end - subject(:client) { described_class.new(sentry_url, token) } - # Requires sentry_api_url and subject to be defined - shared_examples 'no redirects' do - let(:redirect_to) { 'https://redirected.example.com' } - let(:other_url) { 'https://other.example.org' } - - let!(:redirected_req_stub) { stub_sentry_request(other_url) } - - let!(:redirect_req_stub) do - stub_sentry_request( - sentry_api_url, - status: 302, - headers: { location: redirect_to } - ) - end - - it 'does not follow redirects' do - expect { subject }.to raise_exception(Sentry::Client::Error, 'Sentry response status code: 302') - expect(redirect_req_stub).to have_been_requested - expect(redirected_req_stub).not_to have_been_requested - end - end - - shared_examples 'has correct return type' do |klass| + shared_examples 'issues has correct return type' do |klass| it "returns objects of type #{klass}" do - expect(subject).to all( be_a(klass) ) + expect(subject[:issues]).to all( be_a(klass) ) end end - shared_examples 'has correct length' do |length| - it { expect(subject.length).to eq(length) } - end - - # Requires sentry_api_request and subject to be defined - shared_examples 'calls sentry api' do - it 'calls sentry api' do - subject - - expect(sentry_api_request).to have_been_requested - end - end - - shared_examples 'maps exceptions' do - exceptions = { - Gitlab::HTTP::Error => 'Error when connecting to Sentry', - Net::OpenTimeout => 'Connection to Sentry timed out', - SocketError => 'Received SocketError when trying to connect to Sentry', - OpenSSL::SSL::SSLError => 'Sentry returned invalid SSL data', - Errno::ECONNREFUSED => 'Connection refused', - StandardError => 'Sentry request failed due to StandardError' - } - - exceptions.each do |exception, message| - context "#{exception}" do - before do - stub_request(:get, sentry_request_url).to_raise(exception) - end - - it do - expect { subject } - .to raise_exception(Sentry::Client::Error, message) - end - end - end + shared_examples 'issues has correct length' do |length| + it { expect(subject[:issues].length).to eq(length) } end describe '#list_issues' do let(:issue_status) { 'unresolved' } let(:limit) { 20 } + let(:search_term) { '' } + let(:cursor) { nil } + let(:sort) { 'last_seen' } let(:sentry_api_response) { issues_sample_response } let(:sentry_request_url) { sentry_url + '/issues/?limit=20&query=is:unresolved' } let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) } - subject { client.list_issues(issue_status: issue_status, limit: limit) } + subject { client.list_issues(issue_status: issue_status, limit: limit, search_term: search_term, sort: sort, cursor: cursor) } it_behaves_like 'calls sentry api' - it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error - it_behaves_like 'has correct length', 1 + it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error + it_behaves_like 'issues has correct length', 1 shared_examples 'has correct external_url' do context 'external_url' do it 'is constructed correctly' do - expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11') + expect(subject[:issues][0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11') end end end + context 'when response has a pagination info' do + let(:headers) do + { + link: '<https://sentrytest.gitlab.com>; rel="previous"; results="true"; cursor="1573556671000:0:1", <https://sentrytest.gitlab.com>; rel="next"; results="true"; cursor="1572959139000:0:0"' + } + end + let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response, headers: headers) } + + it 'parses the pagination' do + expect(subject[:pagination]).to eq( + 'previous' => { 'cursor' => '1573556671000:0:1' }, + 'next' => { 'cursor' => '1572959139000:0:0' } + ) + end + end + context 'error object created from sentry response' do using RSpec::Parameterized::TableSyntax @@ -130,7 +96,7 @@ describe Sentry::Client do end with_them do - it { expect(subject[0].public_send(error_object)).to eq(sentry_api_response[0].dig(*sentry_response)) } + it { expect(subject[:issues][0].public_send(error_object)).to eq(sentry_api_response[0].dig(*sentry_response)) } end it_behaves_like 'has correct external_url' @@ -139,7 +105,7 @@ describe Sentry::Client do context 'redirects' do let(:sentry_api_url) { sentry_url + '/issues/?limit=20&query=is:unresolved' } - it_behaves_like 'no redirects' + it_behaves_like 'no Sentry redirects' end # Sentry API returns 404 if there are extra slashes in the URL! @@ -164,6 +130,35 @@ describe Sentry::Client do end end + context 'requests with sort parameter in sentry api' do + let(:sentry_request_url) do + 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \ + 'issues/?limit=20&query=is:unresolved&sort=freq' + end + let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) } + + subject { client.list_issues(issue_status: issue_status, limit: limit, sort: 'frequency') } + + it 'calls the sentry api with sort params' do + expect(Gitlab::HTTP).to receive(:get).with( + URI("#{sentry_url}/issues/"), + default_httparty_options.merge(query: { limit: 20, query: "is:unresolved", sort: "freq" }) + ).and_call_original + + subject + + expect(sentry_api_request).to have_been_requested + end + end + + context 'with invalid sort params' do + subject { client.list_issues(issue_status: issue_status, limit: limit, sort: 'fish') } + + it 'throws an error' do + expect { subject }.to raise_error(Sentry::Client::BadRequestError, 'Invalid value for sort param') + end + end + context 'Older sentry versions where keys are not present' do let(:sentry_api_response) do issues_sample_response[0...1].map do |issue| @@ -174,8 +169,8 @@ describe Sentry::Client do it_behaves_like 'calls sentry api' - it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error - it_behaves_like 'has correct length', 1 + it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error + it_behaves_like 'issues has correct length', 1 it_behaves_like 'has correct external_url' end @@ -201,114 +196,26 @@ describe Sentry::Client do end end - it_behaves_like 'maps exceptions' - end - - describe '#list_projects' do - let(:sentry_list_projects_url) { 'https://sentrytest.gitlab.com/api/0/projects/' } - - let(:sentry_api_response) { projects_sample_response } - - let!(:sentry_api_request) { stub_sentry_request(sentry_list_projects_url, body: sentry_api_response) } - - subject { client.list_projects } - - it_behaves_like 'calls sentry api' - - it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project - it_behaves_like 'has correct length', 2 - - context 'essential keys missing in API response' do - let(:sentry_api_response) do - projects_sample_response[0...1].map do |project| - project.except(:slug) - end - end - - it 'raises exception' do - expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "slug"') - end - end + it_behaves_like 'maps Sentry exceptions' - context 'optional keys missing in sentry response' do - let(:sentry_api_response) do - projects_sample_response[0...1].map do |project| - project[:organization].delete(:id) - project.delete(:id) - project.except(:status) - end - end + context 'when search term is present' do + let(:search_term) { 'NoMethodError' } + let(:sentry_request_url) { "#{sentry_url}/issues/?limit=20&query=is:unresolved NoMethodError" } it_behaves_like 'calls sentry api' - it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project - it_behaves_like 'has correct length', 1 + it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error + it_behaves_like 'issues has correct length', 1 end - context 'error object created from sentry response' do - using RSpec::Parameterized::TableSyntax - - where(:sentry_project_object, :sentry_response) do - :id | :id - :name | :name - :status | :status - :slug | :slug - :organization_name | [:organization, :name] - :organization_id | [:organization, :id] - :organization_slug | [:organization, :slug] - end + context 'when cursor is present' do + let(:cursor) { '1572959139000:0:0' } + let(:sentry_request_url) { "#{sentry_url}/issues/?limit=20&cursor=#{cursor}&query=is:unresolved" } - with_them do - it do - expect(subject[0].public_send(sentry_project_object)).to( - eq(sentry_api_response[0].dig(*sentry_response)) - ) - end - end - end - - context 'redirects' do - let(:sentry_api_url) { sentry_list_projects_url } - - it_behaves_like 'no redirects' - end - - # Sentry API returns 404 if there are extra slashes in the URL! - context 'extra slashes in URL' do - let(:sentry_url) { 'https://sentrytest.gitlab.com/api//0/projects//' } - let(:client) { described_class.new(sentry_url, token) } - - let!(:valid_req_stub) do - stub_sentry_request(sentry_list_projects_url) - end - - it 'removes extra slashes in api url' do - expect(Gitlab::HTTP).to receive(:get).with( - URI(sentry_list_projects_url), - anything - ).and_call_original - - subject - - expect(valid_req_stub).to have_been_requested - end - end - - context 'when exception is raised' do - let(:sentry_request_url) { sentry_list_projects_url } + it_behaves_like 'calls sentry api' - it_behaves_like 'maps exceptions' + it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error + it_behaves_like 'issues has correct length', 1 end end - - private - - def stub_sentry_request(url, body: {}, status: 200, headers: {}) - stub_request(:get, url) - .to_return( - status: status, - headers: { 'Content-Type' => 'application/json' }.merge(headers), - body: body.to_json - ) - end end diff --git a/spec/lib/sentry/pagination_parser_spec.rb b/spec/lib/sentry/pagination_parser_spec.rb new file mode 100644 index 00000000000..1be6f9f4163 --- /dev/null +++ b/spec/lib/sentry/pagination_parser_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'support/helpers/fixture_helpers' + +describe Sentry::PaginationParser do + include FixtureHelpers + + describe '.parse' do + subject { described_class.parse(headers) } + + context 'when headers do not have "link" param' do + let(:headers) { {} } + + it 'returns empty hash' do + is_expected.to eq({}) + end + end + + context 'when headers.link has previous and next pages' do + let(:headers) do + { + 'link' => '<https://sentrytest.gitlab.com>; rel="previous"; results="true"; cursor="1573556671000:0:1", <https://sentrytest.gitlab.com>; rel="next"; results="true"; cursor="1572959139000:0:0"' + } + end + + it 'returns info about both pages' do + is_expected.to eq( + 'previous' => { 'cursor' => '1573556671000:0:1' }, + 'next' => { 'cursor' => '1572959139000:0:0' } + ) + end + end + + context 'when headers.link has only next page' do + let(:headers) do + { + 'link' => '<https://sentrytest.gitlab.com>; rel="previous"; results="false"; cursor="1573556671000:0:1", <https://sentrytest.gitlab.com>; rel="next"; results="true"; cursor="1572959139000:0:0"' + } + end + + it 'returns only info about the next page' do + is_expected.to eq( + 'next' => { 'cursor' => '1572959139000:0:0' } + ) + end + end + + context 'when headers.link has only previous page' do + let(:headers) do + { + 'link' => '<https://sentrytest.gitlab.com>; rel="previous"; results="true"; cursor="1573556671000:0:1", <https://sentrytest.gitlab.com>; rel="next"; results="false"; cursor="1572959139000:0:0"' + } + end + + it 'returns only info about the previous page' do + is_expected.to eq( + 'previous' => { 'cursor' => '1573556671000:0:1' } + ) + end + end + end +end |