diff options
Diffstat (limited to 'spec/lib')
234 files changed, 5387 insertions, 1523 deletions
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb index b57adb46385..040ff1a8ebe 100644 --- a/spec/lib/api/helpers/pagination_spec.rb +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -3,399 +3,20 @@ require 'spec_helper' describe API::Helpers::Pagination do - let(:resource) { Project.all } - let(:custom_port) { 8080 } - let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:#{custom_port}/api/v4/projects" } + subject { Class.new.include(described_class).new } - before do - stub_config_setting(port: custom_port) - end - - subject do - Class.new.include(described_class).new - end - - describe '#paginate (keyset pagination)' do - let(:value) { spy('return value') } - let(:base_query) do - { - pagination: 'keyset', - foo: 'bar', - bar: 'baz' - } - end - let(:query) { base_query } - - before do - allow(subject).to receive(:header).and_return(value) - allow(subject).to receive(:params).and_return(query) - allow(subject).to receive(:request).and_return(double(url: "#{incoming_api_projects_url}?#{query.to_query}")) - end - - context 'when resource can be paginated' do - let!(:projects) do - [ - create(:project, name: 'One'), - create(:project, name: 'Two'), - create(:project, name: 'Three') - ].sort_by { |e| -e.id } # sort by id desc (this is the default sort order for the API) - end - - describe 'first page' do - let(:query) { base_query.merge(per_page: 2) } - - it 'returns appropriate amount of resources' do - expect(subject.paginate(resource).count).to eq 2 - end - - it 'returns the first two records (by id desc)' do - expect(subject.paginate(resource)).to eq(projects[0..1]) - end - - it 'adds appropriate headers' do - expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}") - - expect_header('Link', anything) do |_key, val| - expect(val).to include('rel="next"') - end - - subject.paginate(resource) - end - end - - describe 'second page' do - let(:query) { base_query.merge(per_page: 2, ks_prev_id: projects[1].id) } - - it 'returns appropriate amount of resources' do - expect(subject.paginate(resource).count).to eq 1 - end - - it 'returns the third record' do - expect(subject.paginate(resource)).to eq(projects[2..2]) - end - - it 'adds appropriate headers' do - expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}") - - expect_header('Link', anything) do |_key, val| - expect(val).to include('rel="next"') - end - - subject.paginate(resource) - end - end - - describe 'third page' do - let(:query) { base_query.merge(per_page: 2, ks_prev_id: projects[2].id) } - - it 'returns appropriate amount of resources' do - expect(subject.paginate(resource).count).to eq 0 - end - - it 'adds appropriate headers' do - expect_header('X-Per-Page', '2') - expect_no_header('X-Next-Page') - expect(subject).not_to receive(:header).with('Link') - - subject.paginate(resource) - end - end - - context 'if order' do - context 'is not present' do - let(:query) { base_query.merge(per_page: 2) } - - it 'is not present it adds default order(:id) desc' do - resource.order_values = [] - - paginated_relation = subject.paginate(resource) - - expect(resource.order_values).to be_empty - expect(paginated_relation.order_values).to be_present - expect(paginated_relation.order_values.size).to eq(1) - expect(paginated_relation.order_values.first).to be_descending - expect(paginated_relation.order_values.first.expr.name).to eq 'id' - end - end - - context 'is present' do - let(:resource) { Project.all.order(name: :desc) } - let!(:projects) do - [ - create(:project, name: 'One'), - create(:project, name: 'Two'), - create(:project, name: 'Three'), - create(:project, name: 'Three'), # Note the duplicate name - create(:project, name: 'Four'), - create(:project, name: 'Five'), - create(:project, name: 'Six') - ] - - # if we sort this by name descending, id descending, this yields: - # { - # 2 => "Two", - # 4 => "Three", - # 3 => "Three", - # 7 => "Six", - # 1 => "One", - # 5 => "Four", - # 6 => "Five" - # } - # - # (key is the id) - end - - it 'also orders by primary key' do - paginated_relation = subject.paginate(resource) - - expect(paginated_relation.order_values).to be_present - expect(paginated_relation.order_values.size).to eq(2) - expect(paginated_relation.order_values.first).to be_descending - expect(paginated_relation.order_values.first.expr.name).to eq 'name' - expect(paginated_relation.order_values.second).to be_descending - expect(paginated_relation.order_values.second.expr.name).to eq 'id' - end - - it 'returns the right records (first page)' do - result = subject.paginate(resource) - - expect(result.first).to eq(projects[1]) - expect(result.second).to eq(projects[3]) - end - - describe 'second page' do - let(:query) { base_query.merge(ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2) } - - it 'returns the right records (second page)' do - result = subject.paginate(resource) - - expect(result.first).to eq(projects[2]) - expect(result.second).to eq(projects[6]) - end - - it 'returns the right link to the next page' do - expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}") - expect_header('Link', anything) do |_key, val| - expect(val).to include('rel="next"') - end - - subject.paginate(resource) - end - end - - describe 'third page' do - let(:query) { base_query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name, per_page: 5) } - - it 'returns the right records (third page), note increased per_page' do - result = subject.paginate(resource) - - expect(result.size).to eq(3) - expect(result.first).to eq(projects[0]) - expect(result.second).to eq(projects[4]) - expect(result.last).to eq(projects[5]) - end - end - end - end - end - end - - describe '#paginate (default offset-based pagination)' do - let(:value) { spy('return value') } - let(:base_query) { { foo: 'bar', bar: 'baz' } } - let(:query) { base_query } - - before do - allow(subject).to receive(:header).and_return(value) - allow(subject).to receive(:params).and_return(query) - allow(subject).to receive(:request).and_return(double(url: "#{incoming_api_projects_url}?#{query.to_query}")) - end - - context 'when resource can be paginated' do - before do - create_list(:project, 3) - end - - describe 'first page' do - shared_examples 'response with pagination headers' do - it 'adds appropriate headers' do - expect_header('X-Total', '3') - expect_header('X-Total-Pages', '2') - expect_header('X-Per-Page', '2') - expect_header('X-Page', '1') - expect_header('X-Next-Page', '2') - expect_header('X-Prev-Page', '') - - expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) - expect(val).not_to include('rel="prev"') - end - - subject.paginate(resource) - end - end - - shared_examples 'paginated response' do - it 'returns appropriate amount of resources' do - expect(subject.paginate(resource).count).to eq 2 - end - - it 'executes only one SELECT COUNT query' do - expect { subject.paginate(resource) }.to make_queries_matching(/SELECT COUNT/, 1) - end - end - - let(:query) { base_query.merge(page: 1, per_page: 2) } - - context 'when the api_kaminari_count_with_limit feature flag is unset' do - it_behaves_like 'paginated response' - it_behaves_like 'response with pagination headers' - end - - context 'when the api_kaminari_count_with_limit feature flag is disabled' do - before do - stub_feature_flags(api_kaminari_count_with_limit: false) - end - - it_behaves_like 'paginated response' - it_behaves_like 'response with pagination headers' - end - - context 'when the api_kaminari_count_with_limit feature flag is enabled' do - before do - stub_feature_flags(api_kaminari_count_with_limit: true) - end - - context 'when resources count is less than MAX_COUNT_LIMIT' do - before do - stub_const("::Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT", 4) - end - - it_behaves_like 'paginated response' - it_behaves_like 'response with pagination headers' - end - - context 'when resources count is more than MAX_COUNT_LIMIT' do - before do - stub_const("::Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT", 2) - end - - it_behaves_like 'paginated response' - - it 'does not return the X-Total and X-Total-Pages headers' do - expect_no_header('X-Total') - expect_no_header('X-Total-Pages') - expect_header('X-Per-Page', '2') - expect_header('X-Page', '1') - expect_header('X-Next-Page', '2') - expect_header('X-Prev-Page', '') + describe '#paginate' do + let(:relation) { double("relation") } + let(:offset_pagination) { double("offset pagination") } + let(:expected_result) { double("result") } - expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) - expect(val).not_to include('rel="last"') - expect(val).not_to include('rel="prev"') - end + it 'delegates to OffsetPagination' do + expect(::Gitlab::Pagination::OffsetPagination).to receive(:new).with(subject).and_return(offset_pagination) + expect(offset_pagination).to receive(:paginate).with(relation).and_return(expected_result) - subject.paginate(resource) - end - end - end - end + result = subject.paginate(relation) - describe 'second page' do - let(:query) { base_query.merge(page: 2, per_page: 2) } - - it 'returns appropriate amount of resources' do - expect(subject.paginate(resource).count).to eq 1 - end - - it 'adds appropriate headers' do - expect_header('X-Total', '3') - expect_header('X-Total-Pages', '2') - expect_header('X-Per-Page', '2') - expect_header('X-Page', '2') - expect_header('X-Next-Page', '') - expect_header('X-Prev-Page', '1') - - expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev")) - expect(val).not_to include('rel="next"') - end - - subject.paginate(resource) - end - end - - context 'if order' do - it 'is not present it adds default order(:id) if no order is present' do - resource.order_values = [] - - paginated_relation = subject.paginate(resource) - - expect(resource.order_values).to be_empty - expect(paginated_relation.order_values).to be_present - expect(paginated_relation.order_values.first).to be_ascending - expect(paginated_relation.order_values.first.expr.name).to eq 'id' - end - - it 'is present it does not add anything' do - paginated_relation = subject.paginate(resource.order(created_at: :desc)) - - expect(paginated_relation.order_values).to be_present - expect(paginated_relation.order_values.first).to be_descending - expect(paginated_relation.order_values.first.expr.name).to eq 'created_at' - end - end + expect(result).to eq(expected_result) end - - context 'when resource empty' do - describe 'first page' do - let(:query) { base_query.merge(page: 1, per_page: 2) } - - it 'returns appropriate amount of resources' do - expect(subject.paginate(resource).count).to eq 0 - end - - it 'adds appropriate headers' do - expect_header('X-Total', '0') - expect_header('X-Total-Pages', '1') - expect_header('X-Per-Page', '2') - expect_header('X-Page', '1') - expect_header('X-Next-Page', '') - expect_header('X-Prev-Page', '') - - expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last")) - expect(val).not_to include('rel="prev"') - expect(val).not_to include('rel="next"') - expect(val).not_to include('page=0') - end - - subject.paginate(resource) - end - end - end - end - - def expect_header(*args, &block) - expect(subject).to receive(:header).with(*args, &block) - end - - def expect_no_header(*args, &block) - expect(subject).not_to receive(:header).with(*args) - end - - def expect_message(method) - expect(subject).to receive(method) - .at_least(:once).and_return(value) end end diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb index 0624c25e734..81c4563feb6 100644 --- a/spec/lib/api/helpers_spec.rb +++ b/spec/lib/api/helpers_spec.rb @@ -174,4 +174,18 @@ describe API::Helpers do end end end + + describe '#track_event' do + it "creates a gitlab tracking event" do + expect(Gitlab::Tracking).to receive(:event).with('foo', 'my_event', {}) + + subject.track_event('my_event', category: 'foo') + end + + it "logs an exception" do + expect(Rails.logger).to receive(:warn).with(/Tracking event failed/) + + subject.track_event('my_event', category: nil) + end + end end diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index bf827fb3914..5f120f258cd 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -70,7 +70,7 @@ describe Backup::Repository do end context 'restoring object pools' do - it 'schedules restoring of the pool' do + it 'schedules restoring of the pool', :sidekiq_might_not_need_inline do pool_repository = create(:pool_repository, :failed) pool_repository.delete_object_pool diff --git a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb index 0c4ccbf28f4..ff2346fe1ba 100644 --- a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb +++ b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Banzai::Filter::AssetProxyFilter do diff --git a/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb new file mode 100644 index 00000000000..fd6f8816b63 --- /dev/null +++ b/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::Filter::InlineGrafanaMetricsFilter do + include FilterSpecHelper + + let_it_be(:project) { create(:project) } + let_it_be(:grafana_integration) { create(:grafana_integration, project: project) } + + let(:input) { %(<a href="#{url}">example</a>) } + let(:doc) { filter(input) } + + let(:url) { grafana_integration.grafana_url + dashboard_path } + let(:dashboard_path) do + '/d/XDaNK6amz/gitlab-omnibus-redis' \ + '?from=1570397739557&to=1570484139557' \ + '&var-instance=All&panelId=14' + end + + it 'appends a metrics charts placeholder with dashboard url after metrics links' do + node = doc.at_css('.js-render-metrics') + expect(node).to be_present + + dashboard_url = urls.project_grafana_api_metrics_dashboard_url( + project, + embedded: true, + grafana_url: url, + start: "2019-10-06T21:35:39Z", + end: "2019-10-07T21:35:39Z" + ) + + expect(node.attribute('data-dashboard-url').to_s).to eq(dashboard_url) + end + + context 'when the dashboard link is part of a paragraph' do + let(:paragraph) { %(This is an <a href="#{url}">example</a> of metrics.) } + let(:input) { %(<p>#{paragraph}</p>) } + + it 'appends the charts placeholder after the enclosing paragraph' do + expect(unescape(doc.at_css('p').to_s)).to include(paragraph) + expect(doc.at_css('.js-render-metrics')).to be_present + end + end + + context 'when grafana is not configured' do + before do + allow(project).to receive(:grafana_integration).and_return(nil) + end + + it 'leaves the markdown unchanged' do + expect(unescape(doc.to_s)).to eq(input) + end + end + + context 'when parameters are missing' do + let(:dashboard_path) { '/d/XDaNK6amz/gitlab-omnibus-redis' } + + it 'leaves the markdown unchanged' do + expect(unescape(doc.to_s)).to eq(input) + end + end + + private + + # Nokogiri escapes the URLs, but we don't care about that + # distinction for the purposes of this filter + def unescape(html) + CGI.unescapeHTML(html) + end +end 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 a99cd7d6076..745b9133529 100644 --- a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb @@ -18,30 +18,48 @@ describe Banzai::Filter::InlineMetricsRedactorFilter do end context 'with a metrics charts placeholder' do - let(:input) { %(<div class="js-render-metrics" data-dashboard-url="#{url}"></div>) } + shared_examples_for 'a supported metrics dashboard url' do + context 'no user is logged in' do + it 'redacts the placeholder' do + expect(doc.to_s).to be_empty + end + end - context 'no user is logged in' do - it 'redacts the placeholder' do - expect(doc.to_s).to be_empty + context 'the user does not have permission do see charts' do + let(:doc) { filter(input, current_user: build(:user)) } + + it 'redacts the placeholder' do + expect(doc.to_s).to be_empty + end end - end - context 'the user does not have permission do see charts' do - let(:doc) { filter(input, current_user: build(:user)) } + context 'the user has requisite permissions' do + let(:user) { create(:user) } + let(:doc) { filter(input, current_user: user) } - it 'redacts the placeholder' do - expect(doc.to_s).to be_empty + it 'leaves the placeholder' do + project.add_maintainer(user) + + expect(doc.to_s).to eq input + end end end - context 'the user has requisite permissions' do - let(:user) { create(:user) } - let(:doc) { filter(input, current_user: user) } + let(:input) { %(<div class="js-render-metrics" data-dashboard-url="#{url}"></div>) } - it 'leaves the placeholder' do - project.add_maintainer(user) + it_behaves_like 'a supported metrics dashboard url' + + context 'for a grafana dashboard' do + let(:url) { urls.project_grafana_api_metrics_dashboard_url(project, embedded: true) } + + it_behaves_like 'a supported metrics dashboard url' + end - expect(doc.to_s).to eq input + 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 end diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb index a395b021f32..c324c36fe4d 100644 --- a/spec/lib/banzai/filter/video_link_filter_spec.rb +++ b/spec/lib/banzai/filter/video_link_filter_spec.rb @@ -32,7 +32,7 @@ describe Banzai::Filter::VideoLinkFilter do expect(video.name).to eq 'video' expect(video['src']).to eq src - expect(video['width']).to eq "100%" + expect(video['width']).to eq "400" expect(paragraph.name).to eq 'p' diff --git a/spec/lib/bitbucket/representation/pull_request_spec.rb b/spec/lib/bitbucket/representation/pull_request_spec.rb index 70b51b8efec..6a9df0e5099 100644 --- a/spec/lib/bitbucket/representation/pull_request_spec.rb +++ b/spec/lib/bitbucket/representation/pull_request_spec.rb @@ -20,6 +20,7 @@ describe Bitbucket::Representation::PullRequest do describe '#state' do it { expect(described_class.new({ 'state' => 'MERGED' }).state).to eq('merged') } it { expect(described_class.new({ 'state' => 'DECLINED' }).state).to eq('closed') } + it { expect(described_class.new({ 'state' => 'SUPERSEDED' }).state).to eq('closed') } it { expect(described_class.new({}).state).to eq('opened') } end diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb index 3782c30e88a..a493b96b1e4 100644 --- a/spec/lib/container_registry/client_spec.rb +++ b/spec/lib/container_registry/client_spec.rb @@ -99,8 +99,8 @@ describe ContainerRegistry::Client do stub_upload('path', 'content', 'sha256:123', 400) end - it 'returns nil' do - expect(subject).to be nil + it 'returns a failure' do + expect(subject).not_to be_success end end end @@ -125,6 +125,14 @@ describe ContainerRegistry::Client do expect(subject).to eq(result_manifest) end + + context 'when upload fails' do + before do + stub_upload('path', "{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3', 500) + end + + it { is_expected.to be nil } + end end describe '#put_tag' do diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 7c65525b8dc..415a6e62374 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -58,7 +58,7 @@ module Gitlab }, 'image with onerror' => { input: 'image:https://localhost.com/image.png[Alt text" onerror="alert(7)]', - output: "<div>\n<p><span><img src=\"https://localhost.com/image.png\" alt='Alt text\" onerror=\"alert(7)'></span></p>\n</div>" + output: "<div>\n<p><span><a class=\"no-attachment-icon\" href=\"https://localhost.com/image.png\" target=\"_blank\" rel=\"noopener noreferrer\"><img src=\"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt='Alt text\" onerror=\"alert(7)' class=\"lazy\" data-src=\"https://localhost.com/image.png\"></a></span></p>\n</div>" }, 'fenced code with inline script' => { input: '```mypre"><script>alert(3)</script>', @@ -73,6 +73,20 @@ module Gitlab end end + context "images" do + it "does lazy load and link image" do + input = 'image:https://localhost.com/image.png[]' + output = "<div>\n<p><span><a class=\"no-attachment-icon\" href=\"https://localhost.com/image.png\" target=\"_blank\" rel=\"noopener noreferrer\"><img src=\"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"image\" class=\"lazy\" data-src=\"https://localhost.com/image.png\"></a></span></p>\n</div>" + expect(render(input, context)).to include(output) + end + + it "does not automatically link image if link is explicitly defined" do + input = 'image:https://localhost.com/image.png[link=https://gitlab.com]' + output = "<div>\n<p><span><a href=\"https://gitlab.com\" rel=\"nofollow noreferrer noopener\" target=\"_blank\"><img src=\"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"image\" class=\"lazy\" data-src=\"https://localhost.com/image.png\"></a></span></p>\n</div>" + expect(render(input, context)).to include(output) + end + end + context 'with admonition' do it 'preserves classes' do input = <<~ADOC @@ -107,7 +121,7 @@ module Gitlab ADOC output = <<~HTML - <h2>Title</h2> + <h2>Title</h2> HTML expect(render(input, context)).to include(output.strip) @@ -149,15 +163,15 @@ module Gitlab ADOC output = <<~HTML - <div> - <p>This paragraph has a footnote.<sup>[<a id="_footnoteref_1" href="#_footnotedef_1" title="View footnote.">1</a>]</sup></p> - </div> - <div> - <hr> - <div id="_footnotedef_1"> - <a href="#_footnoteref_1">1</a>. This is the text of the footnote. - </div> - </div> + <div> + <p>This paragraph has a footnote.<sup>[<a id="_footnoteref_1" href="#_footnotedef_1" title="View footnote.">1</a>]</sup></p> + </div> + <div> + <hr> + <div id="_footnotedef_1"> + <a href="#_footnoteref_1">1</a>. This is the text of the footnote. + </div> + </div> HTML expect(render(input, context)).to include(output.strip) @@ -183,34 +197,34 @@ module Gitlab ADOC output = <<~HTML - <h1>Title</h1> - <div> - <h2 id="user-content-first-section"> - <a class="anchor" href="#user-content-first-section"></a>First section</h2> - <div> - <div> - <p>This is the first section.</p> - </div> - </div> - </div> - <div> - <h2 id="user-content-second-section"> - <a class="anchor" href="#user-content-second-section"></a>Second section</h2> - <div> - <div> - <p>This is the second section.</p> - </div> - </div> - </div> - <div> - <h2 id="user-content-thunder"> - <a class="anchor" href="#user-content-thunder"></a>Thunder โก !</h2> - <div> - <div> - <p>This is the third section.</p> - </div> - </div> - </div> + <h1>Title</h1> + <div> + <h2 id="user-content-first-section"> + <a class="anchor" href="#user-content-first-section"></a>First section</h2> + <div> + <div> + <p>This is the first section.</p> + </div> + </div> + </div> + <div> + <h2 id="user-content-second-section"> + <a class="anchor" href="#user-content-second-section"></a>Second section</h2> + <div> + <div> + <p>This is the second section.</p> + </div> + </div> + </div> + <div> + <h2 id="user-content-thunder"> + <a class="anchor" href="#user-content-thunder"></a>Thunder โก !</h2> + <div> + <div> + <p>This is the third section.</p> + </div> + </div> + </div> HTML expect(render(input, context)).to include(output.strip) diff --git a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb index 05541972f87..adb8e138ca7 100644 --- a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb +++ b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Auth::LDAP::AuthHash do @@ -91,7 +93,7 @@ describe Gitlab::Auth::LDAP::AuthHash do let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' } before do - raw_info[:uid] = ['JOHN'] + raw_info[:uid] = [+'JOHN'] end it 'enabled the username attribute is lower cased' do diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb index 577dfe51949..e4a90d4018d 100644 --- a/spec/lib/gitlab/auth/ldap/config_spec.rb +++ b/spec/lib/gitlab/auth/ldap/config_spec.rb @@ -535,4 +535,23 @@ AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK end end end + + describe 'sign_in_enabled?' do + using RSpec::Parameterized::TableSyntax + + where(:enabled, :prevent_ldap_sign_in, :result) do + true | false | true + 'true' | false | true + true | true | false + false | nil | false + end + + with_them do + it do + stub_ldap_setting(enabled: enabled, prevent_ldap_sign_in: prevent_ldap_sign_in) + + expect(described_class.sign_in_enabled?).to eq(result) + end + end + end end diff --git a/spec/lib/gitlab/auth/ldap/person_spec.rb b/spec/lib/gitlab/auth/ldap/person_spec.rb index 1527fe60fb9..985732e69f9 100644 --- a/spec/lib/gitlab/auth/ldap/person_spec.rb +++ b/spec/lib/gitlab/auth/ldap/person_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Auth::LDAP::Person do @@ -135,7 +137,7 @@ describe Gitlab::Auth::LDAP::Person do let(:username_attribute) { 'uid' } before do - entry[username_attribute] = 'JOHN' + entry[username_attribute] = +'JOHN' @person = described_class.new(entry, 'ldapmain') end diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb index c1eaf1d3433..f2de73d5aea 100644 --- a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb +++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb @@ -91,15 +91,26 @@ describe Gitlab::BackgroundMigration::LegacyUploadMover do end end - context 'when no model found for the upload' do + context 'when no note found for the upload' do before do - legacy_upload.model = nil + legacy_upload.model_id = nil + legacy_upload.model_type = 'Note' expect_error_log end it_behaves_like 'legacy upload deletion' end + context 'when upload does not belong to a note' do + before do + legacy_upload.model = create(:appearance) + end + + it 'does not remove the upload' do + expect { described_class.new(legacy_upload).execute }.not_to change { Upload.count } + end + end + context 'when the upload move fails' do before do expect(FileUploader).to receive(:copy_to).and_raise('failed') diff --git a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb index cabca3dbef9..85187d039c1 100644 --- a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb +++ b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb @@ -35,6 +35,8 @@ describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do let!(:legacy_upload_no_file) { create_upload(note2, false) } let!(:legacy_upload_legacy_project) { create_upload(note_legacy) } + let!(:appearance) { create(:appearance, :with_logo) } + let(:start_id) { 1 } let(:end_id) { 10000 } @@ -52,12 +54,18 @@ describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do expect(File.exist?(legacy_upload_legacy_project.absolute_path)).to be_falsey end - it 'removes all AttachmentUploader records' do - expect { subject }.to change { Upload.where(uploader: 'AttachmentUploader').count }.from(3).to(0) + it 'removes all Note AttachmentUploader records' do + expect { subject }.to change { Upload.where(uploader: 'AttachmentUploader').count }.from(4).to(1) end it 'creates new uploads for successfully migrated records' do expect { subject }.to change { Upload.where(uploader: 'FileUploader').count }.from(0).to(2) end + + it 'does not remove appearance uploads' do + subject + + expect(appearance.logo.file).to exist + end end # rubocop: enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb index f877e8cc1b8..399db4ac259 100644 --- a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb +++ b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb @@ -33,7 +33,7 @@ describe ScheduleCalculateWikiSizes, :migration, :sidekiq do end end - it 'calculates missing wiki sizes' do + it 'calculates missing wiki sizes', :sidekiq_might_not_need_inline do expect(project_statistics.find_by(id: 2).wiki_size).to be_nil expect(project_statistics.find_by(id: 3).wiki_size).to be_nil diff --git a/spec/lib/gitlab/badge/pipeline/status_spec.rb b/spec/lib/gitlab/badge/pipeline/status_spec.rb index 684c6829879..ab8d1f0ec5b 100644 --- a/spec/lib/gitlab/badge/pipeline/status_spec.rb +++ b/spec/lib/gitlab/badge/pipeline/status_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Badge::Pipeline::Status do end end - context 'pipeline exists' do + context 'pipeline exists', :sidekiq_might_not_need_inline do let!(:pipeline) { create_pipeline(project, sha, branch) } context 'pipeline success' do diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb index 2fb9f1a0a08..ddb1d3cea21 100644 --- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb @@ -90,7 +90,7 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do hook_path = File.join(repo_path, 'hooks') expect(gitlab_shell.repository_exists?(project.repository_storage, repo_path)).to be(true) - expect(gitlab_shell.exists?(project.repository_storage, hook_path)).to be(true) + expect(TestEnv.storage_dir_exists?(project.repository_storage, hook_path)).to be(true) end context 'hashed storage enabled' do diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 7f7a285c453..b0d07c6e0b0 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -158,6 +158,7 @@ describe Gitlab::BitbucketImport::Importer do expect { subject.execute }.to change { MergeRequest.count }.by(1) merge_request = MergeRequest.first + expect(merge_request.state).to eq('merged') expect(merge_request.notes.count).to eq(2) expect(merge_request.notes.map(&:discussion_id).uniq.count).to eq(1) diff --git a/spec/lib/gitlab/checks/lfs_integrity_spec.rb b/spec/lib/gitlab/checks/lfs_integrity_spec.rb index 88e8f5d74d1..505f117034e 100644 --- a/spec/lib/gitlab/checks/lfs_integrity_spec.rb +++ b/spec/lib/gitlab/checks/lfs_integrity_spec.rb @@ -58,7 +58,7 @@ describe Gitlab::Checks::LfsIntegrity do end end - context 'for forked project' do + context 'for forked project', :sidekiq_might_not_need_inline do let(:parent_project) { create(:project, :repository) } let(:project) { fork_project(parent_project, nil, repository: true) } diff --git a/spec/lib/gitlab/ci/ansi2json/style_spec.rb b/spec/lib/gitlab/ci/ansi2json/style_spec.rb index 88a0ca35859..5110c215415 100644 --- a/spec/lib/gitlab/ci/ansi2json/style_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json/style_spec.rb @@ -143,6 +143,7 @@ describe Gitlab::Ci::Ansi2json::Style do [[], %w[106], 'term-bg-l-cyan', 'sets bg color light cyan'], [[], %w[107], 'term-bg-l-white', 'sets bg color light white'], # reset + [%w[1], %w[], '', 'resets style from format bold'], [%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'], diff --git a/spec/lib/gitlab/ci/ansi2json_spec.rb b/spec/lib/gitlab/ci/ansi2json_spec.rb index 3c6bc46436b..124379fa321 100644 --- a/spec/lib/gitlab/ci/ansi2json_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json_spec.rb @@ -12,11 +12,26 @@ describe Gitlab::Ci::Ansi2json do ]) end - it 'adds new line in a separate element' do - expect(convert_json("Hello\nworld")).to eq([ - { offset: 0, content: [{ text: 'Hello' }] }, - { offset: 6, content: [{ text: 'world' }] } - ]) + context 'new lines' do + it 'adds new line when encountering \n' do + expect(convert_json("Hello\nworld")).to eq([ + { offset: 0, content: [{ text: 'Hello' }] }, + { offset: 6, content: [{ text: 'world' }] } + ]) + end + + it 'adds new line when encountering \r\n' do + expect(convert_json("Hello\r\nworld")).to eq([ + { offset: 0, content: [{ text: 'Hello' }] }, + { offset: 7, content: [{ text: 'world' }] } + ]) + end + + it 'replace the current line when encountering \r' do + expect(convert_json("Hello\rworld")).to eq([ + { offset: 0, content: [{ text: 'world' }] } + ]) + end end it 'recognizes color changing ANSI sequences' do @@ -113,10 +128,6 @@ describe Gitlab::Ci::Ansi2json do content: [], section_duration: '01:03', section: 'prepare-script' - }, - { - offset: 63, - content: [] } ]) end @@ -134,10 +145,6 @@ describe Gitlab::Ci::Ansi2json do content: [], section: 'prepare-script', section_duration: '01:03' - }, - { - offset: 56, - content: [] } ]) end @@ -157,7 +164,7 @@ describe Gitlab::Ci::Ansi2json do section_duration: '01:03' }, { - offset: 49, + offset: 91, content: [{ text: 'world' }] } ]) @@ -198,7 +205,7 @@ describe Gitlab::Ci::Ansi2json do expect(convert_json("#{section_start}hello")).to eq([ { offset: 0, - content: [{ text: "#{section_start.gsub("\033[0K", '')}hello" }] + content: [{ text: 'hello' }] } ]) end @@ -211,30 +218,26 @@ describe Gitlab::Ci::Ansi2json do expect(convert_json("#{section_start}hello")).to eq([ { offset: 0, - content: [{ text: "#{section_start.gsub("\033[0K", '').gsub('<', '<')}hello" }] + content: [{ text: 'hello' }] } ]) end end - it 'prevents XSS injection' do - trace = "#{section_start}section_end:1:2<script>alert('XSS Hack!');</script>#{section_end}" + it 'prints HTML tags as is' do + trace = "#{section_start}section_end:1:2<div>hello</div>#{section_end}" expect(convert_json(trace)).to eq([ { offset: 0, - content: [{ text: "section_end:1:2<script>alert('XSS Hack!');</script>" }], + content: [{ text: "section_end:1:2<div>hello</div>" }], section: 'prepare-script', section_header: true }, { - offset: 95, + offset: 75, content: [], section: 'prepare-script', section_duration: '01:03' - }, - { - offset: 95, - content: [] } ]) end @@ -274,7 +277,7 @@ describe Gitlab::Ci::Ansi2json do section_duration: '00:02' }, { - offset: 106, + offset: 155, content: [{ text: 'baz' }], section: 'prepare-script' }, @@ -285,7 +288,7 @@ describe Gitlab::Ci::Ansi2json do section_duration: '01:03' }, { - offset: 158, + offset: 200, content: [{ text: 'world' }] } ]) @@ -318,14 +321,10 @@ describe Gitlab::Ci::Ansi2json do section_duration: '00:02' }, { - offset: 115, + offset: 164, content: [], section: 'prepare-script', section_duration: '01:03' - }, - { - offset: 164, - content: [] } ]) end @@ -380,7 +379,7 @@ describe Gitlab::Ci::Ansi2json do ] end - it 'returns the full line' do + it 'returns the line since last partially processed line' do expect(pass2.lines).to eq(lines) expect(pass2.append).to be_truthy end @@ -399,7 +398,7 @@ describe Gitlab::Ci::Ansi2json do ] end - it 'returns the full line' do + it 'returns the line since last partially processed line' do expect(pass2.lines).to eq(lines) expect(pass2.append).to be_falsey end @@ -416,7 +415,7 @@ describe Gitlab::Ci::Ansi2json do ] end - it 'returns the full line' do + it 'returns a blank line and the next line' do expect(pass2.lines).to eq(lines) expect(pass2.append).to be_falsey end @@ -502,10 +501,6 @@ describe Gitlab::Ci::Ansi2json do content: [], section: 'prepare-script', section_duration: '01:03' - }, - { - offset: 77, - content: [] } ] end diff --git a/spec/lib/gitlab/ci/build/context/build_spec.rb b/spec/lib/gitlab/ci/build/context/build_spec.rb new file mode 100644 index 00000000000..3adde213f59 --- /dev/null +++ b/spec/lib/gitlab/ci/build/context/build_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe Gitlab::Ci::Build::Context::Build do + let(:pipeline) { create(:ci_pipeline) } + let(:seed_attributes) { { 'name' => 'some-job' } } + + let(:context) { described_class.new(pipeline, seed_attributes) } + + describe '#variables' do + subject { context.variables } + + it { is_expected.to include('CI_COMMIT_REF_NAME' => 'master') } + it { is_expected.to include('CI_PIPELINE_IID' => pipeline.iid.to_s) } + it { is_expected.to include('CI_PROJECT_PATH' => pipeline.project.full_path) } + it { is_expected.to include('CI_JOB_NAME' => 'some-job') } + it { is_expected.to include('CI_BUILD_REF_NAME' => 'master') } + + context 'without passed build-specific attributes' do + let(:context) { described_class.new(pipeline) } + + it { is_expected.to include('CI_JOB_NAME' => nil) } + it { is_expected.to include('CI_BUILD_REF_NAME' => 'master') } + it { is_expected.to include('CI_PROJECT_PATH' => pipeline.project.full_path) } + end + end +end diff --git a/spec/lib/gitlab/ci/build/context/global_spec.rb b/spec/lib/gitlab/ci/build/context/global_spec.rb new file mode 100644 index 00000000000..6bc8f862779 --- /dev/null +++ b/spec/lib/gitlab/ci/build/context/global_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Gitlab::Ci::Build::Context::Global do + let(:pipeline) { create(:ci_pipeline) } + let(:yaml_variables) { {} } + + let(:context) { described_class.new(pipeline, yaml_variables: yaml_variables) } + + describe '#variables' do + subject { context.variables } + + it { is_expected.to include('CI_COMMIT_REF_NAME' => 'master') } + it { is_expected.to include('CI_PIPELINE_IID' => pipeline.iid.to_s) } + it { is_expected.to include('CI_PROJECT_PATH' => pipeline.project.full_path) } + + it { is_expected.not_to have_key('CI_JOB_NAME') } + it { is_expected.not_to have_key('CI_BUILD_REF_NAME') } + + context 'with passed yaml variables' do + let(:yaml_variables) { [{ key: 'SUPPORTED', value: 'parsed', public: true }] } + + it { is_expected.to include('SUPPORTED' => 'parsed') } + end + end +end diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb index 7140c14facb..66f2cb640b9 100644 --- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb @@ -16,7 +16,7 @@ describe Gitlab::Ci::Build::Policy::Variables do let(:seed) do double('build seed', to_resource: ci_build, - scoped_variables_hash: ci_build.scoped_variables_hash + variables: ci_build.scoped_variables_hash ) end @@ -91,7 +91,7 @@ describe Gitlab::Ci::Build::Policy::Variables do let(:seed) do double('bridge seed', to_resource: bridge, - scoped_variables_hash: ci_build.scoped_variables_hash + variables: ci_build.scoped_variables_hash ) end diff --git a/spec/lib/gitlab/ci/build/rules/rule_spec.rb b/spec/lib/gitlab/ci/build/rules/rule_spec.rb index 99852bd4228..04cdaa9d0ae 100644 --- a/spec/lib/gitlab/ci/build/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Ci::Build::Rules::Rule do let(:seed) do double('build seed', to_resource: ci_build, - scoped_variables_hash: ci_build.scoped_variables_hash + variables: ci_build.scoped_variables_hash ) end diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb index d7793ebc806..1ebcc4f9414 100644 --- a/spec/lib/gitlab/ci/build/rules_spec.rb +++ b/spec/lib/gitlab/ci/build/rules_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Ci::Build::Rules do @@ -7,11 +9,11 @@ describe Gitlab::Ci::Build::Rules do let(:seed) do double('build seed', to_resource: ci_build, - scoped_variables_hash: ci_build.scoped_variables_hash + variables: ci_build.scoped_variables_hash ) end - let(:rules) { described_class.new(rule_list) } + let(:rules) { described_class.new(rule_list, default_when: 'on_success') } describe '.new' do let(:rules_ivar) { rules.instance_variable_get :@rule_list } @@ -60,7 +62,7 @@ describe Gitlab::Ci::Build::Rules do context 'with a specified default when:' do let(:rule_list) { [{ if: '$VAR == null', when: 'always' }] } - let(:rules) { described_class.new(rule_list, 'manual') } + let(:rules) { described_class.new(rule_list, default_when: 'manual') } it 'sets @rule_list to an array of a single rule' do expect(rules_ivar).to be_an(Array) @@ -81,7 +83,7 @@ describe Gitlab::Ci::Build::Rules do it { is_expected.to eq(described_class::Result.new('on_success')) } context 'and when:manual set as the default' do - let(:rules) { described_class.new(rule_list, 'manual') } + let(:rules) { described_class.new(rule_list, default_when: 'manual') } it { is_expected.to eq(described_class::Result.new('manual')) } end @@ -93,7 +95,7 @@ describe Gitlab::Ci::Build::Rules do it { is_expected.to eq(described_class::Result.new('never')) } context 'and when:manual set as the default' do - let(:rules) { described_class.new(rule_list, 'manual') } + let(:rules) { described_class.new(rule_list, default_when: 'manual') } it { is_expected.to eq(described_class::Result.new('never')) } end @@ -157,7 +159,7 @@ describe Gitlab::Ci::Build::Rules do it { is_expected.to eq(described_class::Result.new('never')) } context 'and when:manual set as the default' do - let(:rules) { described_class.new(rule_list, 'manual') } + let(:rules) { described_class.new(rule_list, default_when: 'manual') } it 'does not return the default when:' do expect(subject).to eq(described_class::Result.new('never')) diff --git a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb index a7f457e0f5e..513a9b8f2b4 100644 --- a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb @@ -28,6 +28,14 @@ describe Gitlab::Ci::Config::Entry::Artifacts do expect(entry.value).to eq config end end + + context "when value includes 'expose_as' keyword" do + let(:config) { { paths: %w[results.txt], expose_as: "Test results" } } + + it 'returns general artifact and report-type artifacts configuration' do + expect(entry.value).to eq config + end + end end context 'when entry value is not correct' do @@ -58,6 +66,84 @@ describe Gitlab::Ci::Config::Entry::Artifacts do .to include 'artifacts reports should be a hash' end end + + context "when 'expose_as' is not a string" do + let(:config) { { paths: %w[results.txt], expose_as: 1 } } + + it 'reports error' do + expect(entry.errors) + .to include 'artifacts expose as should be a string' + end + end + + context "when 'expose_as' is too long" do + let(:config) { { paths: %w[results.txt], expose_as: 'A' * 101 } } + + it 'reports error' do + expect(entry.errors) + .to include 'artifacts expose as is too long (maximum is 100 characters)' + end + end + + context "when 'expose_as' is an empty string" do + let(:config) { { paths: %w[results.txt], expose_as: '' } } + + it 'reports error' do + expect(entry.errors) + .to include 'artifacts expose as ' + Gitlab::Ci::Config::Entry::Artifacts::EXPOSE_AS_ERROR_MESSAGE + end + end + + context "when 'expose_as' contains invalid characters" do + let(:config) do + { paths: %w[results.txt], expose_as: '<script>alert("xss");</script>' } + end + + it 'reports error' do + expect(entry.errors) + .to include 'artifacts expose as ' + Gitlab::Ci::Config::Entry::Artifacts::EXPOSE_AS_ERROR_MESSAGE + end + end + + context "when 'expose_as' is used without 'paths'" do + let(:config) { { expose_as: 'Test results' } } + + it 'reports error' do + expect(entry.errors) + .to include "artifacts paths can't be blank" + end + end + + context "when 'paths' includes '*' and 'expose_as' is defined" do + let(:config) { { expose_as: 'Test results', paths: ['test.txt', 'test*.txt'] } } + + it 'reports error' do + expect(entry.errors) + .to include "artifacts paths can't contain '*' when used with 'expose_as'" + end + end + end + + context 'when feature flag :ci_expose_arbitrary_artifacts_in_mr is disabled' do + before do + stub_feature_flags(ci_expose_arbitrary_artifacts_in_mr: false) + end + + context 'when syntax is correct' do + let(:config) { { expose_as: 'Test results', paths: ['test.txt'] } } + + it 'is valid' do + expect(entry.errors).to be_empty + end + end + + context 'when syntax for :expose_as is incorrect' do + let(:config) { { paths: %w[results.txt], expose_as: '' } } + + it 'is valid' do + expect(entry.errors).to be_empty + end + end end end end diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb index 9aab3664e1c..4fa0a57dc82 100644 --- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb @@ -12,22 +12,53 @@ describe Gitlab::Ci::Config::Entry::Cache do context 'when entry config value is correct' do let(:policy) { nil } + let(:key) { 'some key' } let(:config) do - { key: 'some key', + { key: key, untracked: true, paths: ['some/path/'], policy: policy } end describe '#value' do - it 'returns hash value' do - expect(entry.value).to eq(key: 'some key', untracked: true, paths: ['some/path/'], policy: 'pull-push') + shared_examples 'hash key value' do + it 'returns hash value' do + expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push') + end + end + + it_behaves_like 'hash key value' + + context 'with files' do + let(:key) { { files: ['a-file', 'other-file'] } } + + it_behaves_like 'hash key value' + end + + context 'with files and prefix' do + let(:key) { { files: ['a-file', 'other-file'], prefix: 'prefix-value' } } + + it_behaves_like 'hash key value' + end + + context 'with prefix' do + let(:key) { { prefix: 'prefix-value' } } + + it 'key is nil' do + expect(entry.value).to match(a_hash_including(key: nil)) + end end end describe '#valid?' do it { is_expected.to be_valid } + + context 'with files' do + let(:key) { { files: ['a-file', 'other-file'] } } + + it { is_expected.to be_valid } + end end context 'policy is pull-push' do @@ -87,10 +118,44 @@ describe Gitlab::Ci::Config::Entry::Cache do end context 'when descendants are invalid' do - let(:config) { { key: 1 } } + context 'with invalid keys' do + let(:config) { { key: 1 } } - it 'reports error with descendants' do - is_expected.to include 'key config should be a string or symbol' + it 'reports error with descendants' do + is_expected.to include 'key should be a hash, a string or a symbol' + end + end + + context 'with empty key' do + let(:config) { { key: {} } } + + it 'reports error with descendants' do + is_expected.to include 'key config missing required keys: files' + end + end + + context 'with invalid files' do + let(:config) { { key: { files: 'a-file' } } } + + it 'reports error with descendants' do + is_expected.to include 'key:files config should be an array of strings' + end + end + + context 'with prefix without files' do + let(:config) { { key: { prefix: 'a-prefix' } } } + + it 'reports error with descendants' do + is_expected.to include 'key config missing required keys: files' + end + end + + context 'when there is an unknown key present' do + let(:config) { { key: { unknown: 'a-file' } } } + + it 'reports error with descendants' do + is_expected.to include 'key config contains unknown keys: unknown' + end end end diff --git a/spec/lib/gitlab/ci/config/entry/commands_spec.rb b/spec/lib/gitlab/ci/config/entry/commands_spec.rb index 269a3406913..8e7f9ab9706 100644 --- a/spec/lib/gitlab/ci/config/entry/commands_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/commands_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Commands do let(:entry) { described_class.new(config) } - context 'when entry config value is an array' do + context 'when entry config value is an array of strings' do let(:config) { %w(ls pwd) } describe '#value' do @@ -37,13 +37,74 @@ describe Gitlab::Ci::Config::Entry::Commands do end end - context 'when entry value is not valid' do + context 'when entry config value is array of arrays of strings' do + let(:config) { [['ls'], ['pwd', 'echo 1']] } + + describe '#value' do + it 'returns array of strings' do + expect(entry.value).to eq ['ls', 'pwd', 'echo 1'] + end + end + + describe '#errors' do + it 'does not append errors' do + expect(entry.errors).to be_empty + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry config value is array of strings and arrays of strings' do + let(:config) { ['ls', ['pwd', 'echo 1']] } + + describe '#value' do + it 'returns array of strings' do + expect(entry.value).to eq ['ls', 'pwd', 'echo 1'] + end + end + + describe '#errors' do + it 'does not append errors' do + expect(entry.errors).to be_empty + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is integer' do let(:config) { 1 } describe '#errors' do it 'saves errors' do expect(entry.errors) - .to include 'commands config should be an array of strings or a string' + .to include 'commands config should be a string or an array containing strings and arrays of strings' + end + end + end + + context 'when entry value is multi-level nested array' do + let(:config) { [['ls', ['echo 1']], 'pwd'] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'commands config should be a string or an array containing strings and arrays of strings' + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid end end end diff --git a/spec/lib/gitlab/ci/config/entry/default_spec.rb b/spec/lib/gitlab/ci/config/entry/default_spec.rb index 27d63dbd407..dad4f408e50 100644 --- a/spec/lib/gitlab/ci/config/entry/default_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/default_spec.rb @@ -5,6 +5,18 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Default do let(:entry) { described_class.new(config) } + it_behaves_like 'with inheritable CI config' do + let(:inheritable_key) { nil } + let(:inheritable_class) { Gitlab::Ci::Config::Entry::Root } + + # These are entries defined in Root + # that we know that we don't want to inherit + # as they do not have sense in context of Default + let(:ignored_inheritable_columns) do + %i[default include variables stages types workflow] + end + end + describe '.nodes' do it 'returns a hash' do expect(described_class.nodes).to be_a(Hash) @@ -14,7 +26,7 @@ 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]) + after_script cache interruptible]) end end end @@ -87,7 +99,7 @@ describe Gitlab::Ci::Config::Entry::Default do it 'raises error' do expect { entry.compose!(deps) }.to raise_error( - Gitlab::Ci::Config::Entry::Default::DuplicateError) + Gitlab::Ci::Config::Entry::Default::InheritError) end end diff --git a/spec/lib/gitlab/ci/config/entry/files_spec.rb b/spec/lib/gitlab/ci/config/entry/files_spec.rb new file mode 100644 index 00000000000..2bebbd7b198 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/files_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::Files do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is valid' do + let(:config) { ['some/file', 'some/path/'] } + + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq config + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + describe '#errors' do + context 'when entry value is not an array' do + let(:config) { 'string' } + + it 'saves errors' do + expect(entry.errors) + .to include 'files config should be an array of strings' + end + end + + context 'when entry value is not an array of strings' do + let(:config) { [1] } + + it 'saves errors' do + expect(entry.errors) + .to include 'files config should be an array of strings' + end + end + + context 'when entry value contains more than two values' do + let(:config) { %w[file1 file2 file3] } + + it 'saves errors' do + expect(entry.errors) + .to include 'files config has too many items (maximum is 2)' + end + end + 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 1c4887e87c4..fe83171c57a 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -5,14 +5,26 @@ require 'spec_helper' describe Gitlab::Ci::Config::Entry::Job do let(:entry) { described_class.new(config, name: :rspec) } + it_behaves_like 'with inheritable CI config' do + let(:inheritable_key) { 'default' } + let(:inheritable_class) { Gitlab::Ci::Config::Entry::Default } + + # These are entries defined in Default + # that we know that we don't want to inherit + # as they do not have sense in context of Job + let(:ignored_inheritable_columns) do + %i[] + end + end + describe '.nodes' do context 'when filtering all the entry/node names' do subject { described_class.nodes.keys } let(:result) do %i[before_script script stage type after_script cache - image services only except rules variables artifacts - environment coverage retry] + image services only except rules needs variables artifacts + environment coverage retry interruptible] end it { is_expected.to match_array result } @@ -372,21 +384,6 @@ describe Gitlab::Ci::Config::Entry::Job do end context 'when has needs' do - context 'that are not a array of strings' do - let(:config) do - { - stage: 'test', - script: 'echo', - needs: 'build-job' - } - end - - it 'returns error about invalid type' do - expect(entry).not_to be_valid - expect(entry.errors).to include 'job needs should be an array of strings' - end - end - context 'when have dependencies that are not subset of needs' do let(:config) do { diff --git a/spec/lib/gitlab/ci/config/entry/key_spec.rb b/spec/lib/gitlab/ci/config/entry/key_spec.rb index a7874447725..327607e2266 100644 --- a/spec/lib/gitlab/ci/config/entry/key_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/key_spec.rb @@ -6,38 +6,38 @@ describe Gitlab::Ci::Config::Entry::Key do let(:entry) { described_class.new(config) } describe 'validations' do - shared_examples 'key with slash' do - it 'is invalid' do - expect(entry).not_to be_valid - end + it_behaves_like 'key entry validations', 'simple key' - it 'reports errors with config value' do - expect(entry.errors).to include 'key config cannot contain the "/" character' - end - end + context 'when entry config value is correct' do + context 'when key is a hash' do + let(:config) { { files: ['test'], prefix: 'something' } } - shared_examples 'key with only dots' do - it 'is invalid' do - expect(entry).not_to be_valid - end + describe '#value' do + it 'returns key value' do + expect(entry.value).to match(config) + end + end - it 'reports errors with config value' do - expect(entry.errors).to include 'key config cannot be "." or ".."' + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end end - end - context 'when entry config value is correct' do - let(:config) { 'test' } + context 'when key is a symbol' do + let(:config) { :key } - describe '#value' do - it 'returns key value' do - expect(entry.value).to eq 'test' + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq(config.to_s) + end end - end - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end end end end @@ -47,53 +47,11 @@ describe Gitlab::Ci::Config::Entry::Key do describe '#errors' do it 'saves errors' do - expect(entry.errors) - .to include 'key config should be a string or symbol' + expect(entry.errors.first) + .to match /should be a hash, a string or a symbol/ end end end - - context 'when entry value contains slash' do - let(:config) { 'key/with/some/slashes' } - - it_behaves_like 'key with slash' - end - - context 'when entry value contains URI encoded slash (%2F)' do - let(:config) { 'key%2Fwith%2Fsome%2Fslashes' } - - it_behaves_like 'key with slash' - end - - context 'when entry value is a dot' do - let(:config) { '.' } - - it_behaves_like 'key with only dots' - end - - context 'when entry value is two dots' do - let(:config) { '..' } - - it_behaves_like 'key with only dots' - end - - context 'when entry value is a URI encoded dot (%2E)' do - let(:config) { '%2e' } - - it_behaves_like 'key with only dots' - end - - context 'when entry value is two URI encoded dots (%2E)' do - let(:config) { '%2E%2e' } - - it_behaves_like 'key with only dots' - end - - context 'when entry value is one dot and one URI encoded dot' do - let(:config) { '.%2e' } - - it_behaves_like 'key with only dots' - end end describe '.default' do diff --git a/spec/lib/gitlab/ci/config/entry/need_spec.rb b/spec/lib/gitlab/ci/config/entry/need_spec.rb new file mode 100644 index 00000000000..d119e604900 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/need_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +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' } + + 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') + end + end + 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 config can't be blank") + end + 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 new file mode 100644 index 00000000000..f4a76b52d30 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/needs_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::Ci::Config::Entry::Needs do + subject(:needs) { described_class.new(config) } + + before do + needs.metadata[:allowed_needs] = %i[job] + end + + describe 'validations' do + before do + needs.compose! + end + + context 'when entry config value is correct' do + let(:config) { ['job_name'] } + + describe '#valid?' do + it { is_expected.to be_valid } + end + end + + context 'when config value has wrong type' do + let(:config) { 123 } + + 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 include('needs config can only be a hash or an array') + end + end + end + + context 'when wrong needs type is used' do + let(:config) { [123] } + + 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 has an unsupported type') + end + end + end + end + + describe '.compose!' do + context 'when valid job entries composed' do + let(:config) { %w[first_job_name second_job_name] } + + before do + needs.compose! + end + + describe '#value' do + it 'returns key value' do + expect(needs.value).to eq( + job: [ + { name: 'first_job_name' }, + { name: 'second_job_name' } + ] + ) + 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)) + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/prefix_spec.rb b/spec/lib/gitlab/ci/config/entry/prefix_spec.rb new file mode 100644 index 00000000000..8132a674488 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/prefix_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::Prefix do + let(:entry) { described_class.new(config) } + + describe 'validations' do + it_behaves_like 'key entry validations', :prefix + + context 'when entry value is not correct' do + let(:config) { ['incorrect'] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'prefix config should be a string or symbol' + end + end + end + end + + describe '.default' do + it 'returns default key' do + expect(described_class.default).to be_nil + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index 7e1a80414d4..43bd53b780f 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -12,10 +12,14 @@ describe Gitlab::Ci::Config::Entry::Root do context 'when filtering all the entry/node names' do it 'contains the expected node names' do + # No inheritable fields should be added to the `Root` + # + # Inheritable configuration can only be added to `default:` + # + # The purpose of `Root` is have only globally defined configuration. expect(described_class.nodes.keys) - .to match_array(%i[before_script image services - after_script variables cache - stages types include default]) + .to match_array(%i[before_script image services after_script + variables cache stages types include default workflow]) end end end @@ -45,7 +49,7 @@ describe Gitlab::Ci::Config::Entry::Root do end it 'creates node object for each entry' do - expect(root.descendants.count).to eq 10 + expect(root.descendants.count).to eq 11 end it 'creates node object using valid class' do @@ -198,7 +202,7 @@ describe Gitlab::Ci::Config::Entry::Root do describe '#nodes' do it 'instantizes all nodes' do - expect(root.descendants.count).to eq 10 + expect(root.descendants.count).to eq 11 end it 'contains unspecified nodes' do @@ -293,7 +297,7 @@ describe Gitlab::Ci::Config::Entry::Root do describe '#errors' do it 'reports errors from child nodes' do expect(root.errors) - .to include 'before_script config should be an array of strings' + .to include 'before_script config should be an array containing strings and arrays of strings' end end end diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb index 9d4f7153cd0..216f5d0c77d 100644 --- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb @@ -1,10 +1,22 @@ +# frozen_string_literal: true + require 'fast_spec_helper' require 'gitlab_chronic_duration' require 'support/helpers/stub_feature_flags' require_dependency 'active_model' describe Gitlab::Ci::Config::Entry::Rules::Rule do - let(:entry) { described_class.new(config) } + let(:factory) do + Gitlab::Config::Entry::Factory.new(described_class) + .metadata(metadata) + .value(config) + end + + let(:metadata) do + { allowed_when: %w[on_success on_failure always never manual delayed] } + end + + let(:entry) { factory.create! } describe '.new' do subject { entry } @@ -210,6 +222,112 @@ describe Gitlab::Ci::Config::Entry::Rules::Rule do .to include(/should be a hash/) end end + + context 'when: validation' do + context 'with an invalid boolean when:' do + let(:config) do + { if: '$THIS == "that"', when: false } + end + + it { is_expected.to be_a(described_class) } + it { is_expected.not_to be_valid } + + it 'returns an error about invalid when:' do + expect(subject.errors).to include(/when unknown value: false/) + end + + context 'when composed' do + before do + subject.compose! + end + + it { is_expected.not_to be_valid } + + it 'returns an error about invalid when:' do + expect(subject.errors).to include(/when unknown value: false/) + end + end + end + + context 'with an invalid string when:' do + let(:config) do + { if: '$THIS == "that"', when: 'explode' } + end + + it { is_expected.to be_a(described_class) } + it { is_expected.not_to be_valid } + + it 'returns an error about invalid when:' do + expect(subject.errors).to include(/when unknown value: explode/) + end + + context 'when composed' do + before do + subject.compose! + end + + it { is_expected.not_to be_valid } + + it 'returns an error about invalid when:' do + expect(subject.errors).to include(/when unknown value: explode/) + end + end + end + + context 'with a string passed in metadata but not allowed in the class' do + let(:metadata) { { allowed_when: %w[explode] } } + + let(:config) do + { if: '$THIS == "that"', when: 'explode' } + end + + it { is_expected.to be_a(described_class) } + it { is_expected.not_to be_valid } + + it 'returns an error about invalid when:' do + expect(subject.errors).to include(/when unknown value: explode/) + end + + context 'when composed' do + before do + subject.compose! + end + + it { is_expected.not_to be_valid } + + it 'returns an error about invalid when:' do + expect(subject.errors).to include(/when unknown value: explode/) + end + end + end + + context 'with a string allowed in the class but not passed in metadata' do + let(:metadata) { { allowed_when: %w[always never] } } + + let(:config) do + { if: '$THIS == "that"', when: 'on_success' } + end + + it { is_expected.to be_a(described_class) } + it { is_expected.not_to be_valid } + + it 'returns an error about invalid when:' do + expect(subject.errors).to include(/when unknown value: on_success/) + end + + context 'when composed' do + before do + subject.compose! + end + + it { is_expected.not_to be_valid } + + it 'returns an error about invalid when:' do + expect(subject.errors).to include(/when unknown value: on_success/) + end + end + end + end end describe '#value' do diff --git a/spec/lib/gitlab/ci/config/entry/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/rules_spec.rb index 291e7373daf..3c050801023 100644 --- a/spec/lib/gitlab/ci/config/entry/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules_spec.rb @@ -1,9 +1,18 @@ +# frozen_string_literal: true + require 'fast_spec_helper' require 'support/helpers/stub_feature_flags' require_dependency 'active_model' describe Gitlab::Ci::Config::Entry::Rules do - let(:entry) { described_class.new(config) } + let(:factory) do + Gitlab::Config::Entry::Factory.new(described_class) + .metadata(metadata) + .value(config) + end + + let(:metadata) { { allowed_when: %w[always never] } } + let(:entry) { factory.create! } describe '.new' do subject { entry } @@ -16,7 +25,7 @@ describe Gitlab::Ci::Config::Entry::Rules do it { is_expected.to be_a(described_class) } it { is_expected.to be_valid } - context 'after #compose!' do + context 'when composed' do before do subject.compose! end @@ -36,7 +45,7 @@ describe Gitlab::Ci::Config::Entry::Rules do it { is_expected.to be_a(described_class) } it { is_expected.to be_valid } - context 'after #compose!' do + context 'when composed' do before do subject.compose! end @@ -52,48 +61,6 @@ describe Gitlab::Ci::Config::Entry::Rules do it { is_expected.not_to be_valid } end - - context 'with an invalid boolean when:' do - let(:config) do - [{ if: '$THIS == "that"', when: false }] - end - - it { is_expected.to be_a(described_class) } - it { is_expected.to be_valid } - - context 'after #compose!' do - before do - subject.compose! - end - - it { is_expected.not_to be_valid } - - it 'returns an error about invalid when:' do - expect(subject.errors).to include(/when unknown value: false/) - end - end - end - - context 'with an invalid string when:' do - let(:config) do - [{ if: '$THIS == "that"', when: 'explode' }] - end - - it { is_expected.to be_a(described_class) } - it { is_expected.to be_valid } - - context 'after #compose!' do - before do - subject.compose! - end - - it { is_expected.not_to be_valid } - - it 'returns an error about invalid when:' do - expect(subject.errors).to include(/when unknown value: explode/) - end - end - end end describe '#value' do diff --git a/spec/lib/gitlab/ci/config/entry/script_spec.rb b/spec/lib/gitlab/ci/config/entry/script_spec.rb index d523243d3b6..57dc20ea628 100644 --- a/spec/lib/gitlab/ci/config/entry/script_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/script_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Config::Entry::Script do let(:entry) { described_class.new(config) } describe 'validations' do - context 'when entry config value is correct' do + context 'when entry config value is array of strings' do let(:config) { %w(ls pwd) } describe '#value' do @@ -28,13 +28,74 @@ describe Gitlab::Ci::Config::Entry::Script do end end - context 'when entry value is not correct' do + context 'when entry config value is array of arrays of strings' do + let(:config) { [['ls'], ['pwd', 'echo 1']] } + + describe '#value' do + it 'returns array of strings' do + expect(entry.value).to eq ['ls', 'pwd', 'echo 1'] + end + end + + describe '#errors' do + it 'does not append errors' do + expect(entry.errors).to be_empty + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry config value is array containing strings and arrays of strings' do + let(:config) { ['ls', ['pwd', 'echo 1']] } + + describe '#value' do + it 'returns array of strings' do + expect(entry.value).to eq ['ls', 'pwd', 'echo 1'] + end + end + + describe '#errors' do + it 'does not append errors' do + expect(entry.errors).to be_empty + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is string' do let(:config) { 'ls' } describe '#errors' do it 'saves errors' do expect(entry.errors) - .to include 'script config should be an array of strings' + .to include 'script config should be an array containing strings and arrays of strings' + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end + + context 'when entry value is multi-level nested array' do + let(:config) { [['ls', ['echo 1']], 'pwd'] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'script config should be an array containing strings and arrays of strings' end end diff --git a/spec/lib/gitlab/ci/config/entry/workflow_spec.rb b/spec/lib/gitlab/ci/config/entry/workflow_spec.rb new file mode 100644 index 00000000000..f2832b94bf0 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/workflow_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Config::Entry::Workflow do + let(:factory) { Gitlab::Config::Entry::Factory.new(described_class).value(rules_hash) } + let(:config) { factory.create! } + + describe 'validations' do + context 'when work config value is a string' do + let(:rules_hash) { 'build' } + + describe '#valid?' do + it 'is invalid' do + expect(config).not_to be_valid + end + + it 'attaches an error specifying that workflow should point to a hash' do + expect(config.errors).to include('workflow config should be a hash') + end + end + + describe '#value' do + it 'returns the invalid configuration' do + expect(config.value).to eq(rules_hash) + end + end + end + + context 'when work config value is a hash' do + let(:rules_hash) { { rules: [{ if: '$VAR' }] } } + + describe '#valid?' do + it 'is valid' do + expect(config).to be_valid + end + + it 'attaches no errors' do + expect(config.errors).to be_empty + end + end + + describe '#value' do + it 'returns the config' do + expect(config.value).to eq(rules_hash) + end + end + + context 'with an invalid key' do + let(:rules_hash) { { trash: [{ if: '$VAR' }] } } + + describe '#valid?' do + it 'is invalid' do + expect(config).not_to be_valid + end + + it 'attaches an error specifying the unknown key' do + expect(config.errors).to include('workflow config contains unknown keys: trash') + end + end + + describe '#value' do + it 'returns the invalid configuration' do + expect(config.value).to eq(rules_hash) + end + end + end + end + end + + describe '.default' do + it 'is nil' do + expect(described_class.default).to be_nil + end + end +end diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb index 6b766cc37bf..bf880478387 100644 --- a/spec/lib/gitlab/ci/config/normalizer_spec.rb +++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb @@ -7,6 +7,16 @@ describe Gitlab::Ci::Config::Normalizer do let(:job_config) { { script: 'rspec', parallel: 5, name: 'rspec' } } let(:config) { { job_name => job_config } } + let(:expanded_job_names) do + [ + "rspec 1/5", + "rspec 2/5", + "rspec 3/5", + "rspec 4/5", + "rspec 5/5" + ] + end + describe '.normalize_jobs' do subject { described_class.new(config).normalize_jobs } @@ -15,9 +25,7 @@ describe Gitlab::Ci::Config::Normalizer do end it 'has parallelized jobs' do - job_names = [:"rspec 1/5", :"rspec 2/5", :"rspec 3/5", :"rspec 4/5", :"rspec 5/5"] - - is_expected.to include(*job_names) + is_expected.to include(*expanded_job_names.map(&:to_sym)) end it 'sets job instance in options' do @@ -43,49 +51,109 @@ describe Gitlab::Ci::Config::Normalizer do let(:job_name) { :"rspec 35/2" } it 'properly parallelizes job names' do - job_names = [:"rspec 35/2 1/5", :"rspec 35/2 2/5", :"rspec 35/2 3/5", :"rspec 35/2 4/5", :"rspec 35/2 5/5"] + job_names = [ + :"rspec 35/2 1/5", + :"rspec 35/2 2/5", + :"rspec 35/2 3/5", + :"rspec 35/2 4/5", + :"rspec 35/2 5/5" + ] is_expected.to include(*job_names) end end - %i[dependencies needs].each do |context| - context "when job has #{context} on parallelized jobs" do + context 'for dependencies' do + context "when job has dependencies on parallelized jobs" do let(:config) do { job_name => job_config, - other_job: { script: 'echo 1', context => [job_name.to_s] } + other_job: { script: 'echo 1', dependencies: [job_name.to_s] } } end - it "parallelizes #{context}" do - job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"] - - expect(subject[:other_job][context]).to include(*job_names) + it "parallelizes dependencies" do + expect(subject[:other_job][:dependencies]).to eq(expanded_job_names) end it "does not include original job name in #{context}" do - expect(subject[:other_job][context]).not_to include(job_name) + expect(subject[:other_job][:dependencies]).not_to include(job_name) end end - context "when there are #{context} which are both parallelized and not" do + context "when there are dependencies which are both parallelized and not" do let(:config) do { job_name => job_config, other_job: { script: 'echo 1' }, - final_job: { script: 'echo 1', context => [job_name.to_s, "other_job"] } + final_job: { script: 'echo 1', dependencies: [job_name.to_s, "other_job"] } } end - it "parallelizes #{context}" do + it "parallelizes dependencies" do job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"] - expect(subject[:final_job][context]).to include(*job_names) + expect(subject[:final_job][:dependencies]).to include(*job_names) + end + + it "includes the regular job in dependencies" do + expect(subject[:final_job][:dependencies]).to include('other_job') + end + end + end + + context 'for needs' do + let(:expanded_job_attributes) do + expanded_job_names.map do |job_name| + { name: job_name } + end + end + + context "when job has needs on parallelized jobs" do + let(:config) do + { + job_name => job_config, + other_job: { + script: 'echo 1', + needs: { + job: [ + { name: job_name.to_s } + ] + } + } + } + end + + it "parallelizes needs" do + expect(subject.dig(:other_job, :needs, :job)).to eq(expanded_job_attributes) + end + end + + context "when there are dependencies which are both parallelized and not" do + let(:config) do + { + job_name => job_config, + other_job: { + script: 'echo 1' + }, + final_job: { + script: 'echo 1', + needs: { + job: [ + { name: job_name.to_s }, + { name: "other_job" } + ] + } + } + } + end + + it "parallelizes dependencies" do + expect(subject.dig(:final_job, :needs, :job)).to include(*expanded_job_attributes) end - it "includes the regular job in #{context}" do - expect(subject[:final_job][context]).to include('other_job') + it "includes the regular job in dependencies" do + expect(subject.dig(:final_job, :needs, :job)).to include(name: 'other_job') 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 ba4f841cf43..a631cd2777b 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb @@ -11,6 +11,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do [{ key: 'first', secret_value: 'world' }, { key: 'second', secret_value: 'second_world' }] end + let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( source: :push, @@ -51,12 +52,6 @@ describe Gitlab::Ci::Pipeline::Chain::Build do .to eq variables_attributes.map(&:with_indifferent_access) end - it 'sets a valid config source' do - step.perform! - - expect(pipeline.repository_source?).to be true - end - it 'returns a valid pipeline' do step.perform! diff --git a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb new file mode 100644 index 00000000000..7b76adaf683 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:pipeline) { build(:ci_pipeline, project: project) } + + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user) + end + + let(:step) { described_class.new(pipeline, command) } + + describe '#perform!' do + context 'when pipeline has been skipped by workflow configuration' do + before do + allow(step).to receive(:workflow_passed?) + .and_return(false) + + step.perform! + end + + it 'does not save the pipeline' do + expect(pipeline).not_to be_persisted + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + + it 'attaches an error to the pipeline' do + expect(pipeline.errors[:base]).to include('Pipeline filtered out by workflow rules.') + end + end + + context 'when pipeline has not been skipped by workflow configuration' do + before do + allow(step).to receive(:workflow_passed?) + .and_return(true) + + step.perform! + end + + it 'continues the pipeline processing chain' do + expect(step.break?).to be false + end + + it 'does not skip the pipeline' do + expect(pipeline).not_to be_persisted + expect(pipeline).not_to be_skipped + end + + it 'attaches no errors' do + expect(pipeline.errors).to be_empty + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index 9bccd5be4fe..52e9432dc92 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -7,9 +7,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do set(:user) { create(:user) } let(:pipeline) do - build(:ci_pipeline_with_one_job, project: project, - ref: 'master', - user: user) + build(:ci_pipeline, project: project, ref: 'master', user: user) end let(:command) do @@ -20,11 +18,32 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do seeds_block: nil) end + let(:dependencies) do + [ + Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command), + Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command), + Gitlab::Ci::Pipeline::Chain::Seed.new(pipeline, command) + ] + end + let(:step) { described_class.new(pipeline, command) } + let(:config) do + { rspec: { script: 'rspec' } } + end + + def run_chain + dependencies.map(&:perform!) + step.perform! + end + + before do + stub_ci_pipeline_yaml_file(YAML.dump(config)) + end + context 'when pipeline doesn not have seeds block' do before do - step.perform! + run_chain end it 'does not persist the pipeline' do @@ -59,12 +78,8 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do } } end - let(:pipeline) do - build(:ci_pipeline, project: project, config: config) - end - before do - step.perform! + run_chain end it 'breaks the chain' do @@ -82,16 +97,16 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do end describe 'pipeline protect' do - subject { step.perform! } - context 'when ref is protected' do before do allow(project).to receive(:protected_for?).with('master').and_return(true) allow(project).to receive(:protected_for?).with('refs/heads/master').and_return(true) + + dependencies.map(&:perform!) end it 'does not protect the pipeline' do - subject + run_chain expect(pipeline.protected).to eq(true) end @@ -99,7 +114,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do context 'when ref is not protected' do it 'does not protect the pipeline' do - subject + run_chain expect(pipeline.protected).to eq(false) end @@ -112,7 +127,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do end before do - step.perform! + run_chain end it 'breaks the chain' do @@ -144,7 +159,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do end it 'populates pipeline with resources described in the seeds block' do - step.perform! + run_chain expect(pipeline).not_to be_persisted expect(pipeline.variables).not_to be_empty @@ -154,7 +169,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do end it 'has pipeline iid' do - step.perform! + run_chain expect(pipeline.iid).to be > 0 end @@ -166,7 +181,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do end it 'wastes pipeline iid' do - expect { step.perform! }.to raise_error(ActiveRecord::RecordNotSaved) + expect { run_chain }.to raise_error(ActiveRecord::RecordNotSaved) last_iid = InternalId.ci_pipelines .where(project_id: project.id) @@ -181,14 +196,14 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do let(:pipeline) { create(:ci_pipeline, project: project) } it 'raises error' do - expect { step.perform! }.to raise_error(described_class::PopulateError) + expect { run_chain }.to raise_error(described_class::PopulateError) end end context 'when variables policy is specified' do shared_examples_for 'a correct pipeline' do it 'populates pipeline according to used policies' do - step.perform! + run_chain expect(pipeline.stages.size).to eq 1 expect(pipeline.stages.first.statuses.size).to eq 1 @@ -202,10 +217,6 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do prod: { script: 'cap prod', stage: 'deploy', only: ['tags'] } } end - let(:pipeline) do - build(:ci_pipeline, ref: 'master', project: project, config: config) - end - it_behaves_like 'a correct pipeline' context 'when variables expression is specified' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb index 7c1c016b4bb..92eadf5548c 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb @@ -2,32 +2,38 @@ require 'spec_helper' -describe Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do - let(:project) { create(:project, :repository) } +describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do + let(:project) { create(:project) } let(:pipeline) do - build(:ci_pipeline_with_one_job, project: project, ref: 'master') + build(:ci_pipeline, project: project) end let(:command) do - double(:command, project: project, chat_data: { command: 'echo' }) + double(:command, + config_processor: double(:processor, + jobs: { echo: double(:job_echo), rspec: double(:job_rspec) }), + project: project, + chat_data: { command: 'echo' }) end describe '#perform!' do - it 'removes unwanted jobs for chat pipelines' do - allow(pipeline).to receive(:chat?).and_return(true) + subject { described_class.new(pipeline, command).perform! } - pipeline.config_processor.jobs[:echo] = double(:job) + it 'removes unwanted jobs for chat pipelines' do + expect(pipeline).to receive(:chat?).and_return(true) - described_class.new(pipeline, command).perform! + subject - expect(pipeline.config_processor.jobs.keys).to eq([:echo]) + expect(command.config_processor.jobs.keys).to eq([:echo]) end - end - it 'does not remove any jobs for non-chat pipelines' do - described_class.new(pipeline, command).perform! + it 'does not remove any jobs for non chat-pipelines' do + expect(pipeline).to receive(:chat?).and_return(false) - expect(pipeline.config_processor.jobs.keys).to eq([:rspec]) + subject + + expect(command.config_processor.jobs.keys).to eq([:echo, :rspec]) + end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb new file mode 100644 index 00000000000..aa54f19b26c --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::Seed do + let(:project) { create(:project, :repository) } + let(:user) { create(:user, developer_projects: [project]) } + + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new( + project: project, + current_user: user, + origin_ref: 'master', + seeds_block: nil) + end + + def run_chain(pipeline, command) + [ + Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command), + Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command) + ].map(&:perform!) + + described_class.new(pipeline, command).perform! + end + + let(:pipeline) { build(:ci_pipeline, project: project) } + + describe '#perform!' do + before do + stub_ci_pipeline_yaml_file(YAML.dump(config)) + run_chain(pipeline, command) + end + + let(:config) do + { rspec: { script: 'rake' } } + end + + it 'allocates next IID' do + expect(pipeline.iid).to be_present + end + + it 'sets the seeds in the command object' do + expect(command.stage_seeds).to all(be_a Gitlab::Ci::Pipeline::Seed::Base) + expect(command.stage_seeds.count).to eq 1 + end + + context 'when no ref policy is specified' do + let(:config) do + { + production: { stage: 'deploy', script: 'cap prod' }, + rspec: { stage: 'test', script: 'rspec' }, + spinach: { stage: 'test', script: 'spinach' } + } + end + + it 'correctly fabricates a stage seeds object' do + seeds = command.stage_seeds + expect(seeds.size).to eq 2 + expect(seeds.first.attributes[:name]).to eq 'test' + expect(seeds.second.attributes[:name]).to eq 'deploy' + expect(seeds.dig(0, 0, :name)).to eq 'rspec' + expect(seeds.dig(0, 1, :name)).to eq 'spinach' + expect(seeds.dig(1, 0, :name)).to eq 'production' + end + end + + context 'when refs policy is specified' do + let(:pipeline) do + build(:ci_pipeline, project: project, ref: 'feature', tag: true) + end + + let(:config) do + { + production: { stage: 'deploy', script: 'cap prod', only: ['master'] }, + spinach: { stage: 'test', script: 'spinach', only: ['tags'] } + } + end + + it 'returns stage seeds only assigned to master' do + seeds = command.stage_seeds + + expect(seeds.size).to eq 1 + expect(seeds.first.attributes[:name]).to eq 'test' + expect(seeds.dig(0, 0, :name)).to eq 'spinach' + end + end + + context 'when source policy is specified' do + let(:pipeline) { create(:ci_pipeline, source: :schedule) } + + let(:config) do + { + production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] }, + spinach: { stage: 'test', script: 'spinach', only: ['schedules'] } + } + end + + it 'returns stage seeds only assigned to schedules' do + seeds = command.stage_seeds + + expect(seeds.size).to eq 1 + expect(seeds.first.attributes[:name]).to eq 'test' + expect(seeds.dig(0, 0, :name)).to eq 'spinach' + end + end + + context 'when kubernetes policy is specified' do + let(:config) do + { + spinach: { stage: 'test', script: 'spinach' }, + production: { + stage: 'deploy', + script: 'cap', + only: { kubernetes: 'active' } + } + } + end + + context 'when kubernetes is active' do + context 'when user configured kubernetes from CI/CD > Clusters' do + let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } + let(:pipeline) { build(:ci_pipeline, project: project) } + + it 'returns seeds for kubernetes dependent job' do + seeds = command.stage_seeds + + expect(seeds.size).to eq 2 + expect(seeds.dig(0, 0, :name)).to eq 'spinach' + expect(seeds.dig(1, 0, :name)).to eq 'production' + end + end + end + + context 'when kubernetes is not active' do + it 'does not return seeds for kubernetes dependent job' do + seeds = command.stage_seeds + + expect(seeds.size).to eq 1 + expect(seeds.dig(0, 0, :name)).to eq 'spinach' + end + end + end + + context 'when variables policy is specified' do + let(:config) do + { + unit: { script: 'minitest', only: { variables: ['$CI_PIPELINE_SOURCE'] } }, + feature: { script: 'spinach', only: { variables: ['$UNDEFINED'] } } + } + end + + it 'returns stage seeds only when variables expression is truthy' do + seeds = command.stage_seeds + + expect(seeds.size).to eq 1 + expect(seeds.dig(0, 0, :name)).to eq 'unit' + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb deleted file mode 100644 index 79acd3e4f54..00000000000 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb +++ /dev/null @@ -1,148 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Ci::Pipeline::Chain::Validate::Config do - set(:project) { create(:project, :repository) } - set(:user) { create(:user) } - - let(:command) do - Gitlab::Ci::Pipeline::Chain::Command.new( - project: project, - current_user: user, - save_incompleted: true) - end - - let!(:step) { described_class.new(pipeline, command) } - - before do - step.perform! - end - - context 'when pipeline has no YAML configuration' do - let(:pipeline) do - build_stubbed(:ci_pipeline, project: project) - end - - it 'appends errors about missing configuration' do - expect(pipeline.errors.to_a) - .to include 'Missing .gitlab-ci.yml file' - end - - it 'breaks the chain' do - expect(step.break?).to be true - end - end - - context 'when YAML configuration contains errors' do - let(:pipeline) do - build(:ci_pipeline, project: project, config: 'invalid YAML') - end - - it 'appends errors about YAML errors' do - expect(pipeline.errors.to_a) - .to include 'Invalid configuration format' - end - - it 'breaks the chain' do - expect(step.break?).to be true - end - - context 'when saving incomplete pipeline is allowed' do - let(:command) do - double('command', project: project, - current_user: user, - save_incompleted: true) - end - - it 'fails the pipeline' do - expect(pipeline.reload).to be_failed - end - - it 'sets a config error failure reason' do - expect(pipeline.reload.config_error?).to eq true - end - end - - context 'when saving incomplete pipeline is not allowed' do - let(:command) do - double('command', project: project, - current_user: user, - save_incompleted: false) - end - - it 'does not drop pipeline' do - expect(pipeline).not_to be_failed - expect(pipeline).not_to be_persisted - end - end - end - - context 'when pipeline contains configuration validation errors' do - let(:config) do - { - rspec: { - before_script: 10, - script: 'ls -al' - } - } - end - - let(:pipeline) do - build(:ci_pipeline, project: project, config: config) - end - - it 'appends configuration validation errors to pipeline errors' do - expect(pipeline.errors.to_a) - .to include "jobs:rspec:before_script config should be an array of strings" - end - - it 'breaks the chain' do - expect(step.break?).to be true - end - end - - context 'when pipeline is correct and complete' do - let(:pipeline) do - build(:ci_pipeline_with_one_job, project: project) - end - - it 'does not invalidate the pipeline' do - expect(pipeline).to be_valid - end - - it 'does not break the chain' do - expect(step.break?).to be false - end - end - - context 'when pipeline source is merge request' do - before do - stub_ci_pipeline_yaml_file(YAML.dump(config)) - end - - let(:pipeline) { build_stubbed(:ci_pipeline, project: project) } - - let(:merge_request_pipeline) do - build(:ci_pipeline, source: :merge_request_event, project: project) - end - - let(:chain) { described_class.new(merge_request_pipeline, command).tap(&:perform!) } - - context "when config contains 'merge_requests' keyword" do - let(:config) { { rspec: { script: 'echo', only: ['merge_requests'] } } } - - it 'does not break the chain' do - expect(chain).not_to be_break - end - end - - context "when config contains 'merge_request' keyword" do - let(:config) { { rspec: { script: 'echo', only: ['merge_request'] } } } - - it 'does not break the chain' do - expect(chain).not_to be_break - end - end - end -end diff --git a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb new file mode 100644 index 00000000000..6a8b804597c --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb @@ -0,0 +1,261 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Seed::Build::Cache do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:head_sha) { project.repository.head_commit.id } + let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: head_sha) } + + let(:processor) { described_class.new(pipeline, config) } + + describe '#build_attributes' do + subject { processor.build_attributes } + + context 'with cache:key' do + let(:config) do + { + key: 'a-key', + paths: ['vendor/ruby'] + } + end + + it { is_expected.to include(options: { cache: config }) } + end + + context 'with cache:key as a symbol' do + let(:config) do + { + key: :a_key, + paths: ['vendor/ruby'] + } + end + + it { is_expected.to include(options: { cache: config.merge(key: "a_key") }) } + end + + context 'with cache:key:files' do + shared_examples 'default key' do + let(:config) do + { key: { files: files } } + end + + it 'uses default key' do + expected = { options: { cache: { key: 'default' } } } + + is_expected.to include(expected) + end + end + + shared_examples 'version and gemfile files' do + let(:config) do + { + key: { + files: files + }, + paths: ['vendor/ruby'] + } + end + + it 'builds a string key' do + expected = { + options: { + cache: { + key: '703ecc8fef1635427a1f86a8a1a308831c122392', + paths: ['vendor/ruby'] + } + } + } + + is_expected.to include(expected) + end + end + + context 'with existing files' do + let(:files) { ['VERSION', 'Gemfile.zip'] } + + it_behaves_like 'version and gemfile files' + end + + context 'with files starting with ./' do + let(:files) { ['Gemfile.zip', './VERSION'] } + + it_behaves_like 'version and gemfile files' + end + + context 'with feature flag disabled' do + let(:files) { ['VERSION', 'Gemfile.zip'] } + + before do + stub_feature_flags(ci_file_based_cache: false) + end + + it_behaves_like 'default key' + end + + context 'with files ending with /' do + let(:files) { ['Gemfile.zip/'] } + + it_behaves_like 'default key' + end + + context 'with new line in filenames' do + let(:files) { ["Gemfile.zip\nVERSION"] } + + it_behaves_like 'default key' + end + + context 'with missing files' do + let(:files) { ['project-gemfile.lock', ''] } + + it_behaves_like 'default key' + end + + context 'with directories' do + shared_examples 'foo/bar directory key' do + let(:config) do + { + key: { + files: files + } + } + end + + it 'builds a string key' do + expected = { + options: { + cache: { key: '74bf43fb1090f161bdd4e265802775dbda2f03d1' } + } + } + + is_expected.to include(expected) + end + end + + context 'with directory' do + let(:files) { ['foo/bar'] } + + it_behaves_like 'foo/bar directory key' + end + + context 'with directory ending in slash' do + let(:files) { ['foo/bar/'] } + + it_behaves_like 'foo/bar directory key' + end + + context 'with directories ending in slash star' do + let(:files) { ['foo/bar/*'] } + + it_behaves_like 'foo/bar directory key' + end + end + end + + context 'with cache:key:prefix' do + context 'without files' do + let(:config) do + { + key: { + prefix: 'a-prefix' + }, + paths: ['vendor/ruby'] + } + end + + it 'adds prefix to default key' do + expected = { + options: { + cache: { + key: 'a-prefix-default', + paths: ['vendor/ruby'] + } + } + } + + is_expected.to include(expected) + end + end + + context 'with existing files' do + let(:config) do + { + key: { + files: ['VERSION', 'Gemfile.zip'], + prefix: 'a-prefix' + }, + paths: ['vendor/ruby'] + } + end + + it 'adds prefix key' do + expected = { + options: { + cache: { + key: 'a-prefix-703ecc8fef1635427a1f86a8a1a308831c122392', + paths: ['vendor/ruby'] + } + } + } + + is_expected.to include(expected) + end + end + + context 'with missing files' do + let(:config) do + { + key: { + files: ['project-gemfile.lock', ''], + prefix: 'a-prefix' + }, + paths: ['vendor/ruby'] + } + end + + it 'adds prefix to default key' do + expected = { + options: { + cache: { + key: 'a-prefix-default', + paths: ['vendor/ruby'] + } + } + } + + is_expected.to include(expected) + end + end + end + + context 'with all cache option keys' do + let(:config) do + { + key: 'a-key', + paths: ['vendor/ruby'], + untracked: true, + policy: 'push' + } + end + + it { is_expected.to include(options: { cache: config }) } + end + + context 'with unknown cache option keys' do + let(:config) do + { + key: 'a-key', + unknown_key: true + } + end + + it { expect { subject }.to raise_error(ArgumentError, /unknown_key/) } + end + + context 'with empty config' do + let(:config) { {} } + + it { is_expected.to include(options: {}) } + 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 945baf47b7b..53dcb6359fe 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -4,7 +4,8 @@ require 'spec_helper' describe Gitlab::Ci::Pipeline::Seed::Build do let(:project) { create(:project, :repository) } - let(:pipeline) { create(:ci_empty_pipeline, project: project) } + let(:head_sha) { project.repository.head_commit.id } + let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: head_sha) } let(:attributes) { { name: 'rspec', ref: 'master' } } let(:previous_stages) { [] } @@ -69,6 +70,101 @@ describe Gitlab::Ci::Pipeline::Seed::Build do it { is_expected.to include(when: 'never') } end end + + context 'with cache:key' do + let(:attributes) do + { + name: 'rspec', + ref: 'master', + cache: { + key: 'a-value' + } + } + end + + it { is_expected.to include(options: { cache: { key: 'a-value' } }) } + end + + context 'with cache:key:files' do + let(:attributes) do + { + name: 'rspec', + ref: 'master', + cache: { + key: { + files: ['VERSION'] + } + } + } + end + + it 'includes cache options' do + cache_options = { + options: { + cache: { + key: 'f155568ad0933d8358f66b846133614f76dd0ca4' + } + } + } + + is_expected.to include(cache_options) + end + end + + context 'with cache:key:prefix' do + let(:attributes) do + { + name: 'rspec', + ref: 'master', + cache: { + key: { + prefix: 'something' + } + } + } + end + + it { is_expected.to include(options: { cache: { key: 'something-default' } }) } + end + + context 'with cache:key:files and prefix' do + let(:attributes) do + { + name: 'rspec', + ref: 'master', + cache: { + key: { + files: ['VERSION'], + prefix: 'something' + } + } + } + end + + it 'includes cache options' do + cache_options = { + options: { + cache: { + key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4' + } + } + } + + is_expected.to include(cache_options) + end + end + + context 'with empty cache' do + let(:attributes) do + { + name: 'rspec', + ref: 'master', + cache: {} + } + end + + it { is_expected.to include(options: {}) } + end end describe '#bridge?' do @@ -773,10 +869,4 @@ describe Gitlab::Ci::Pipeline::Seed::Build do end end end - - describe '#scoped_variables_hash' do - subject { seed_build.scoped_variables_hash } - - it { is_expected.to eq(seed_build.to_resource.scoped_variables_hash) } - end end diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb index 1725d954b92..857483a9e0a 100644 --- a/spec/lib/gitlab/ci/status/composite_spec.rb +++ b/spec/lib/gitlab/ci/status/composite_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Ci::Status::Composite do diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index 1baea13299b..45b59541ce6 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do @@ -100,7 +102,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do describe '#append' do shared_examples_for 'appends' do it "truncates and append content" do - stream.append("89", 4) + stream.append(+"89", 4) stream.seek(0) expect(stream.size).to eq(6) @@ -108,7 +110,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do end it 'appends in binary mode' do - '๐บ'.force_encoding('ASCII-8BIT').each_char.with_index do |byte, offset| + (+'๐บ').force_encoding('ASCII-8BIT').each_char.with_index do |byte, offset| stream.append(byte, offset) end @@ -154,7 +156,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do describe '#set' do shared_examples_for 'sets' do before do - stream.set("8901") + stream.set(+"8901") end it "overwrite content" do @@ -168,7 +170,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do context 'when stream is StringIO' do let(:stream) do described_class.new do - StringIO.new("12345678") + StringIO.new(+"12345678") end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index cb5ebde16d7..4b1c7483b11 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -108,6 +108,25 @@ module Gitlab it { expect(subject[:interruptible]).to be_falsy } end + + it "returns interruptible when overridden for job" do + config = YAML.dump({ default: { interruptible: true }, + rspec: { script: "rspec" } }) + + config_processor = Gitlab::Ci::YamlProcessor.new(config) + + expect(config_processor.stage_builds_attributes("test").size).to eq(1) + expect(config_processor.stage_builds_attributes("test").first).to eq({ + stage: "test", + stage_idx: 2, + name: "rspec", + options: { script: ["rspec"] }, + interruptible: true, + allow_failure: false, + when: "on_success", + yaml_variables: [] + }) + end end describe 'retry entry' do @@ -249,6 +268,108 @@ module Gitlab end end + describe '#workflow_attributes' do + context 'with disallowed workflow:variables' do + let(:config) do + <<-EOYML + workflow: + rules: + - if: $VAR == "value" + variables: + UNSUPPORTED: "unparsed" + EOYML + end + + it 'parses the workflow:rules configuration' do + expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'workflow config contains unknown keys: variables') + end + end + + context 'with rules and variables' do + let(:config) do + <<-EOYML + variables: + SUPPORTED: "parsed" + + workflow: + rules: + - if: $VAR == "value" + + hello: + script: echo world + EOYML + end + + it 'parses the workflow:rules configuration' do + expect(subject.workflow_attributes[:rules]).to contain_exactly({ if: '$VAR == "value"' }) + end + + it 'parses the root:variables as yaml_variables:' do + expect(subject.workflow_attributes[:yaml_variables]) + .to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true }) + end + end + + context 'with rules and no variables' do + let(:config) do + <<-EOYML + workflow: + rules: + - if: $VAR == "value" + + hello: + script: echo world + EOYML + end + + it 'parses the workflow:rules configuration' do + expect(subject.workflow_attributes[:rules]).to contain_exactly({ if: '$VAR == "value"' }) + end + + it 'parses the root:variables as yaml_variables:' do + expect(subject.workflow_attributes[:yaml_variables]).to eq([]) + end + end + + context 'with variables and no rules' do + let(:config) do + <<-EOYML + variables: + SUPPORTED: "parsed" + + hello: + script: echo world + EOYML + end + + it 'parses the workflow:rules configuration' do + expect(subject.workflow_attributes[:rules]).to be_nil + end + + it 'parses the root:variables as yaml_variables:' do + expect(subject.workflow_attributes[:yaml_variables]) + .to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true }) + end + end + + context 'with no rules and no variables' do + let(:config) do + <<-EOYML + hello: + script: echo world + EOYML + end + + it 'parses the workflow:rules configuration' do + expect(subject.workflow_attributes[:rules]).to be_nil + end + + it 'parses the root:variables as yaml_variables:' do + expect(subject.workflow_attributes[:yaml_variables]).to eq([]) + end + end + end + describe 'only / except policies validations' do context 'when `only` has an invalid value' do let(:config) { { rspec: { script: "rspec", type: "test", only: only } } } @@ -330,7 +451,7 @@ module Gitlab } end - it "return commands with scripts concencaced" do + it "return commands with scripts concatenated" do expect(subject[:options][:before_script]).to eq(["global script"]) end end @@ -343,7 +464,7 @@ module Gitlab } end - it "return commands with scripts concencaced" do + it "return commands with scripts concatenated" do expect(subject[:options][:before_script]).to eq(["global script"]) end end @@ -356,21 +477,48 @@ module Gitlab } end - it "return commands with scripts concencaced" do + it "return commands with scripts concatenated" do expect(subject[:options][:before_script]).to eq(["local script"]) end end + + context 'when script is array of arrays of strings' do + let(:config) do + { + before_script: [["global script", "echo 1"], ["ls"], "pwd"], + test: { script: ["script"] } + } + end + + it "return commands with scripts concatenated" do + expect(subject[:options][:before_script]).to eq(["global script", "echo 1", "ls", "pwd"]) + end + end end describe "script" do - let(:config) do - { - test: { script: ["script"] } - } + context 'when script is array of strings' do + let(:config) do + { + test: { script: ["script"] } + } + end + + it "return commands with scripts concatenated" do + expect(subject[:options][:script]).to eq(["script"]) + end end - it "return commands with scripts concencaced" do - expect(subject[:options][:script]).to eq(["script"]) + context 'when script is array of arrays of strings' do + let(:config) do + { + test: { script: [["script"], ["echo 1"], "ls"] } + } + end + + it "return commands with scripts concatenated" do + expect(subject[:options][:script]).to eq(["script", "echo 1", "ls"]) + end end end @@ -413,6 +561,19 @@ module Gitlab expect(subject[:options][:after_script]).to eq(["local after_script"]) end end + + context 'when script is array of arrays of strings' do + let(:config) do + { + after_script: [["global script", "echo 1"], ["ls"], "pwd"], + test: { script: ["script"] } + } + end + + it "return after_script in options" do + expect(subject[:options][:after_script]).to eq(["global script", "echo 1", "ls", "pwd"]) + end + end end end @@ -891,7 +1052,7 @@ module Gitlab config_processor = Gitlab::Ci::YamlProcessor.new(config) expect(config_processor.stage_builds_attributes("test").size).to eq(1) - expect(config_processor.stage_builds_attributes("test").first[:options][:cache]).to eq( + expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq( paths: ["logs/", "binaries/"], untracked: true, key: 'key', @@ -903,7 +1064,7 @@ module Gitlab config = YAML.dump( { default: { - cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' } + cache: { paths: ["logs/", "binaries/"], untracked: true, key: { files: ['file'] } } }, rspec: { script: "rspec" @@ -913,33 +1074,79 @@ module Gitlab config_processor = Gitlab::Ci::YamlProcessor.new(config) expect(config_processor.stage_builds_attributes("test").size).to eq(1) - expect(config_processor.stage_builds_attributes("test").first[:options][:cache]).to eq( + expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq( paths: ["logs/", "binaries/"], untracked: true, - key: 'key', + key: { files: ['file'] }, policy: 'pull-push' ) end - it "returns cache when defined in a job" do + it 'returns cache key when defined in a job' do config = YAML.dump({ rspec: { - cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' }, - script: "rspec" + cache: { paths: ['logs/', 'binaries/'], untracked: true, key: 'key' }, + script: 'rspec' } }) config_processor = Gitlab::Ci::YamlProcessor.new(config) - expect(config_processor.stage_builds_attributes("test").size).to eq(1) - expect(config_processor.stage_builds_attributes("test").first[:options][:cache]).to eq( - paths: ["logs/", "binaries/"], + expect(config_processor.stage_builds_attributes('test').size).to eq(1) + expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq( + paths: ['logs/', 'binaries/'], untracked: true, key: 'key', policy: 'pull-push' ) end + it 'returns cache files' do + config = YAML.dump( + rspec: { + cache: { + paths: ['logs/', 'binaries/'], + untracked: true, + key: { files: ['file'] } + }, + script: 'rspec' + } + ) + + config_processor = Gitlab::Ci::YamlProcessor.new(config) + + expect(config_processor.stage_builds_attributes('test').size).to eq(1) + expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq( + paths: ['logs/', 'binaries/'], + untracked: true, + key: { files: ['file'] }, + policy: 'pull-push' + ) + end + + it 'returns cache files with prefix' do + config = YAML.dump( + rspec: { + cache: { + paths: ['logs/', 'binaries/'], + untracked: true, + key: { files: ['file'], prefix: 'prefix' } + }, + script: 'rspec' + } + ) + + config_processor = Gitlab::Ci::YamlProcessor.new(config) + + expect(config_processor.stage_builds_attributes('test').size).to eq(1) + expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq( + paths: ['logs/', 'binaries/'], + untracked: true, + key: { files: ['file'], prefix: 'prefix' }, + policy: 'pull-push' + ) + end + it "overwrite cache when defined for a job and globally" do config = YAML.dump({ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' }, @@ -952,7 +1159,7 @@ module Gitlab config_processor = Gitlab::Ci::YamlProcessor.new(config) expect(config_processor.stage_builds_attributes("test").size).to eq(1) - expect(config_processor.stage_builds_attributes("test").first[:options][:cache]).to eq( + expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq( paths: ["test/"], untracked: false, key: 'local', @@ -970,6 +1177,7 @@ module Gitlab rspec: { artifacts: { paths: ["logs/", "binaries/"], + expose_as: "Exposed artifacts", untracked: true, name: "custom_name", expire_in: "7d" @@ -993,6 +1201,7 @@ module Gitlab artifacts: { name: "custom_name", paths: ["logs/", "binaries/"], + expose_as: "Exposed artifacts", untracked: true, expire_in: "7d" } @@ -1251,7 +1460,7 @@ module Gitlab end end - describe "Needs" do + describe "Job Needs" do let(:needs) { } let(:dependencies) { } @@ -1259,6 +1468,7 @@ module Gitlab { build1: { stage: 'build', script: 'test' }, build2: { stage: 'build', script: 'test' }, + parallel: { stage: 'build', script: 'test', parallel: 2 }, test1: { stage: 'test', script: 'test', needs: needs, dependencies: dependencies }, test2: { stage: 'test', script: 'test' }, deploy: { stage: 'test', script: 'test' } @@ -1275,7 +1485,7 @@ module Gitlab let(:needs) { %w(build1 build2) } it "does create jobs with valid specification" do - expect(subject.builds.size).to eq(5) + expect(subject.builds.size).to eq(7) expect(subject.builds[0]).to eq( stage: "build", stage_idx: 1, @@ -1287,16 +1497,11 @@ module Gitlab allow_failure: false, yaml_variables: [] ) - expect(subject.builds[2]).to eq( + expect(subject.builds[4]).to eq( stage: "test", stage_idx: 2, name: "test1", - options: { - script: ["test"], - # This does not make sense, there is a follow-up: - # https://gitlab.com/gitlab-org/gitlab-foss/issues/65569 - bridge_needs: %w[build1 build2] - }, + options: { script: ["test"] }, needs_attributes: [ { name: "build1" }, { name: "build2" } @@ -1308,10 +1513,25 @@ module Gitlab end end - context 'needs two builds defined as symbols' do - let(:needs) { [:build1, :build2] } + context 'needs parallel job' do + let(:needs) { %w(parallel) } - it { expect { subject }.not_to raise_error } + 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", + options: { script: ["test"] }, + needs_attributes: [ + { name: "parallel 1/2" }, + { name: "parallel 2/2" } + ], + when: "on_success", + allow_failure: false, + yaml_variables: [] + ) + end end context 'undefined need' do @@ -1545,28 +1765,42 @@ module Gitlab config = YAML.dump({ before_script: "bundle update", rspec: { script: "test" } }) expect do Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "before_script config should be an array of strings") + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "before_script config should be an array containing strings and arrays of strings") end it "returns errors if job before_script parameter is not an array of strings" do config = YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } }) expect do Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:before_script config should be an array of strings") + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:before_script config should be an array containing strings and arrays of strings") + end + + it "returns errors if job before_script parameter is multi-level nested array of strings" do + config = YAML.dump({ rspec: { script: "test", before_script: [["ls", ["pwd"]], "test"] } }) + expect do + Gitlab::Ci::YamlProcessor.new(config) + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:before_script config should be an array containing strings and arrays of strings") end it "returns errors if after_script parameter is invalid" do config = YAML.dump({ after_script: "bundle update", rspec: { script: "test" } }) expect do Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "after_script config should be an array of strings") + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "after_script config should be an array containing strings and arrays of strings") end it "returns errors if job after_script parameter is not an array of strings" do config = YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } }) expect do Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:after_script config should be an array of strings") + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:after_script config should be an array containing strings and arrays of strings") + end + + it "returns errors if job after_script parameter is multi-level nested array of strings" do + config = YAML.dump({ rspec: { script: "test", after_script: [["ls", ["pwd"]], "test"] } }) + expect do + Gitlab::Ci::YamlProcessor.new(config) + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:after_script config should be an array containing strings and arrays of strings") end it "returns errors if image parameter is invalid" do @@ -1776,14 +2010,42 @@ module Gitlab config = YAML.dump({ cache: { key: 1 }, rspec: { script: "test" } }) expect do Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "cache:key config should be a string or symbol") + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "cache:key should be a hash, a string or a symbol") end it "returns errors if job cache:key is not an a string" do config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: 1 } } }) expect do Gitlab::Ci::YamlProcessor.new(config) - end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:cache:key config should be a string or symbol") + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec:cache:key should be a hash, a string or a symbol") + end + + it 'returns errors if job cache:key:files is not an array of strings' do + config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { files: [1] } } } }) + expect do + Gitlab::Ci::YamlProcessor.new(config) + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:rspec:cache:key:files config should be an array of strings') + end + + it 'returns errors if job cache:key:files is an empty array' do + config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { files: [] } } } }) + expect do + Gitlab::Ci::YamlProcessor.new(config) + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:rspec:cache:key:files config requires at least 1 item') + end + + it 'returns errors if job defines only cache:key:prefix' do + config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { prefix: 'prefix-key' } } } }) + expect do + Gitlab::Ci::YamlProcessor.new(config) + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:rspec:cache:key config missing required keys: files') + end + + it 'returns errors if job cache:key:prefix is not an a string' do + config = YAML.dump({ types: %w(build test), rspec: { script: "test", cache: { key: { prefix: 1, files: ['file'] } } } }) + expect do + Gitlab::Ci::YamlProcessor.new(config) + end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:rspec:cache:key:prefix config should be a string or symbol') end it "returns errors if job cache:untracked is not an array of strings" do 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 974cc2c4660..fc9792e16d7 100644 --- a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb +++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb @@ -21,11 +21,10 @@ describe Gitlab::Cleanup::OrphanJobArtifactFiles do end it 'errors when invalid niceness is given' do + allow(Gitlab::Utils).to receive(:which).with('ionice').and_return('/fake/ionice') cleanup = described_class.new(logger: null_logger, niceness: 'FooBar') - expect(null_logger).to receive(:error).with(/FooBar/) - - cleanup.run! + expect { cleanup.run! }.to raise_error('Invalid niceness') end it 'finds artifacts on disk' do @@ -63,6 +62,8 @@ describe Gitlab::Cleanup::OrphanJobArtifactFiles do def mock_artifacts_found(cleanup, *files) mock = allow(cleanup).to receive(:find_artifacts) - files.each { |file| mock.and_yield(file) } + # Because we shell out to run `find -L ...`, each file actually + # contains a trailing newline + files.each { |file| mock.and_yield("#{file}\n") } end end diff --git a/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb b/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb index 1eddf488c5d..b8ac8c5b95c 100644 --- a/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb +++ b/spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb @@ -8,15 +8,28 @@ describe Gitlab::Cluster::Mixins::PumaCluster do PUMA_STARTUP_TIMEOUT = 30 context 'when running Puma in Cluster-mode' do - %i[USR1 USR2 INT HUP].each do |signal| - it "for #{signal} does execute phased restart block" do + using RSpec::Parameterized::TableSyntax + + where(:signal, :exitstatus, :termsig) do + # executes phased restart block + :USR1 | 140 | nil + :USR2 | 140 | nil + :INT | 140 | nil + :HUP | 140 | nil + + # does not execute phased restart block + :TERM | nil | 15 + end + + with_them do + it 'properly handles process lifecycle' do with_puma(workers: 1) do |pid| Process.kill(signal, pid) child_pid, child_status = Process.wait2(pid) expect(child_pid).to eq(pid) - expect(child_status).to be_exited - expect(child_status.exitstatus).to eq(140) + expect(child_status.exitstatus).to eq(exitstatus) + expect(child_status.termsig).to eq(termsig) end end end @@ -62,8 +75,12 @@ describe Gitlab::Cluster::Mixins::PumaCluster do Puma::Cluster.prepend(#{described_class}) - Gitlab::Cluster::LifecycleEvents.on_before_phased_restart do - exit(140) + mutex = Mutex.new + + Gitlab::Cluster::LifecycleEvents.on_before_blackout_period do + mutex.synchronize do + exit(140) + end end # redirect stderr to stdout diff --git a/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb b/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb index 2b3a267991c..ebe019924d5 100644 --- a/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb +++ b/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb @@ -5,31 +5,30 @@ require 'spec_helper' # For easier debugging set `UNICORN_DEBUG=1` describe Gitlab::Cluster::Mixins::UnicornHttpServer do - UNICORN_STARTUP_TIMEOUT = 10 + UNICORN_STARTUP_TIMEOUT = 30 context 'when running Unicorn' do - %i[USR2].each do |signal| - it "for #{signal} does execute phased restart block" do - with_unicorn(workers: 1) do |pid| - Process.kill(signal, pid) + using RSpec::Parameterized::TableSyntax - child_pid, child_status = Process.wait2(pid) - expect(child_pid).to eq(pid) - expect(child_status).to be_exited - expect(child_status.exitstatus).to eq(140) - end - end + where(:signal, :exitstatus, :termsig) do + # executes phased restart block + :USR2 | 140 | nil + :QUIT | 140 | nil + + # does not execute phased restart block + :INT | 0 | nil + :TERM | 0 | nil end - %i[QUIT TERM INT].each do |signal| - it "for #{signal} does not execute phased restart block" do + with_them do + it 'properly handles process lifecycle' do with_unicorn(workers: 1) do |pid| Process.kill(signal, pid) child_pid, child_status = Process.wait2(pid) expect(child_pid).to eq(pid) - expect(child_status).to be_exited - expect(child_status.exitstatus).to eq(0) + expect(child_status.exitstatus).to eq(exitstatus) + expect(child_status.termsig).to eq(termsig) end end end @@ -74,8 +73,12 @@ describe Gitlab::Cluster::Mixins::UnicornHttpServer do Unicorn::HttpServer.prepend(#{described_class}) - Gitlab::Cluster::LifecycleEvents.on_before_phased_restart do - exit(140) + mutex = Mutex.new + + Gitlab::Cluster::LifecycleEvents.on_before_blackout_period do + mutex.synchronize do + exit(140) + end end # redirect stderr to stdout diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb index a163de07967..9eee7e89062 100644 --- a/spec/lib/gitlab/cycle_analytics/events_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb @@ -129,7 +129,7 @@ describe 'cycle analytics events' do end end - describe '#test_events' do + describe '#test_events', :sidekiq_might_not_need_inline do let(:stage) { :test } let(:merge_request) { MergeRequest.first } @@ -234,7 +234,7 @@ describe 'cycle analytics events' do end end - describe '#staging_events' do + describe '#staging_events', :sidekiq_might_not_need_inline do let(:stage) { :staging } let(:merge_request) { MergeRequest.first } @@ -306,7 +306,7 @@ describe 'cycle analytics events' do end end - describe '#production_events' do + describe '#production_events', :sidekiq_might_not_need_inline do let(:stage) { :production } let!(:context) { create(:issue, project: project, created_at: 2.days.ago) } diff --git a/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb index d5c2f7cc579..664009f140f 100644 --- a/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb @@ -44,6 +44,14 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do expect(subject.first[:value]).to eq(2) end end + + context 'when `from` and `to` parameters are provided' do + subject { described_class.new(group, options: { from: 10.days.ago, to: Time.now, current_user: user }).data } + + it 'finds issues from 5 days ago' do + expect(subject.first[:value]).to eq(2) + end + end end context 'with other projects' do @@ -97,6 +105,14 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do expect(subject.second[:value]).to eq(2) end end + + context 'when `from` and `to` parameters are provided' do + subject { described_class.new(group, options: { from: 10.days.ago, to: Time.now, current_user: user }).data } + + it 'finds deployments from 5 days ago' do + expect(subject.second[:value]).to eq(2) + end + end end context 'with other projects' do diff --git a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb index e568ea633db..d4ab9bc225b 100644 --- a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb @@ -71,7 +71,7 @@ describe Gitlab::CycleAnalytics::UsageData do } end - it 'returns the aggregated usage data of every selected project' do + it 'returns the aggregated usage data of every selected project', :sidekiq_might_not_need_inline do result = subject.to_json expect(result).to have_key(:avg_cycle_analytics) diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb index 1696d3566ad..8056418e697 100644 --- a/spec/lib/gitlab/danger/helper_spec.rb +++ b/spec/lib/gitlab/danger/helper_spec.rb @@ -178,6 +178,7 @@ describe Gitlab::Danger::Helper do 'app/assets/foo' | :frontend 'app/views/foo' | :frontend 'public/foo' | :frontend + 'scripts/frontend/foo' | :frontend 'spec/javascripts/foo' | :frontend 'spec/frontend/bar' | :frontend 'vendor/assets/foo' | :frontend @@ -193,10 +194,8 @@ describe Gitlab::Danger::Helper do 'app/models/foo' | :backend 'bin/foo' | :backend 'config/foo' | :backend - 'danger/foo' | :backend 'lib/foo' | :backend 'rubocop/foo' | :backend - 'scripts/foo' | :backend 'spec/foo' | :backend 'spec/foo/bar' | :backend @@ -209,16 +208,24 @@ describe Gitlab::Danger::Helper do 'vendor/languages.yml' | :backend 'vendor/licenses.csv' | :backend - 'Dangerfile' | :backend 'Gemfile' | :backend 'Gemfile.lock' | :backend 'Procfile' | :backend 'Rakefile' | :backend 'FOO_VERSION' | :backend + 'Dangerfile' | :engineering_productivity + 'danger/commit_messages/Dangerfile' | :engineering_productivity + 'ee/danger/commit_messages/Dangerfile' | :engineering_productivity + 'danger/commit_messages/' | :engineering_productivity + 'ee/danger/commit_messages/' | :engineering_productivity '.gitlab-ci.yml' | :engineering_productivity '.gitlab/ci/cng.gitlab-ci.yml' | :engineering_productivity '.gitlab/ci/ee-specific-checks.gitlab-ci.yml' | :engineering_productivity + 'scripts/foo' | :engineering_productivity + 'lib/gitlab/danger/foo' | :engineering_productivity + 'ee/lib/gitlab/danger/foo' | :engineering_productivity + 'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | :backend 'ee/FOO_VERSION' | :unknown diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb index bd1c2b10dc8..35edfa08a63 100644 --- a/spec/lib/gitlab/danger/teammate_spec.rb +++ b/spec/lib/gitlab/danger/teammate_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::Danger::Teammate do expect(subject.maintainer?(project, :frontend, labels)).to be_truthy end - context 'when labels contain Create and the category is test' 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 @@ -79,6 +79,22 @@ describe Gitlab::Danger::Teammate do it '#maintainer? returns false' do expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_falsey end + + context 'when capabilities include maintainer backend' do + let(:capabilities) { ['maintainer backend'] } + + it '#maintainer? returns true' do + expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_truthy + end + end + + context 'when capabilities include trainee_maintainer backend' do + let(:capabilities) { ['trainee_maintainer backend'] } + + it '#traintainer? returns true' do + expect(subject.traintainer?(project, :engineering_productivity, labels)).to be_truthy + end + end end end end diff --git a/spec/lib/gitlab/data_builder/deployment_spec.rb b/spec/lib/gitlab/data_builder/deployment_spec.rb index 0a6e2302b09..42d7329494d 100644 --- a/spec/lib/gitlab/data_builder/deployment_spec.rb +++ b/spec/lib/gitlab/data_builder/deployment_spec.rb @@ -35,5 +35,12 @@ describe Gitlab::DataBuilder::Deployment do expect(data[:commit_url]).to eq(expected_commit_url) expect(data[:commit_title]).to eq(commit.title) end + + it 'does not include the deployable URL when there is no deployable' do + deployment = create(:deployment, status: :failed, deployable: nil) + data = described_class.build(deployment) + + expect(data[:deployable_url]).to be_nil + end end end diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb index 58509b69463..cbc03fc38eb 100644 --- a/spec/lib/gitlab/data_builder/push_spec.rb +++ b/spec/lib/gitlab/data_builder/push_spec.rb @@ -57,6 +57,32 @@ describe Gitlab::DataBuilder::Push do include_examples 'deprecated repository hook data' end + describe '.sample_data' do + let(:data) { described_class.sample_data } + + it { expect(data).to be_a(Hash) } + it { expect(data[:before]).to eq('95790bf891e76fee5e1747ab589903a6a1f80f22') } + it { expect(data[:after]).to eq('da1560886d4f094c3e6c9ef40349f7d38b5d27d7') } + it { expect(data[:ref]).to eq('refs/heads/master') } + it { expect(data[:project_id]).to eq(15) } + it { expect(data[:commits].size).to eq(1) } + it { expect(data[:total_commits_count]).to eq(1) } + it 'contains project data' do + expect(data[:project]).to be_a(Hash) + expect(data[:project][:id]).to eq(15) + expect(data[:project][:name]).to eq('gitlab') + expect(data[:project][:description]).to eq('') + expect(data[:project][:web_url]).to eq('http://test.example.com/gitlab/gitlab') + expect(data[:project][:avatar_url]).to eq('https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80') + expect(data[:project][:git_http_url]).to eq('http://test.example.com/gitlab/gitlab.git') + expect(data[:project][:git_ssh_url]).to eq('git@test.example.com:gitlab/gitlab.git') + expect(data[:project][:namespace]).to eq('gitlab') + expect(data[:project][:visibility_level]).to eq(0) + expect(data[:project][:path_with_namespace]).to eq('gitlab/gitlab') + expect(data[:project][:default_branch]).to eq('master') + end + end + describe '.build' do let(:data) do described_class.build( diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 49f92f14559..449eee7a371 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -142,7 +142,6 @@ describe Gitlab::Database::MigrationHelpers do allow(model).to receive(:transaction_open?).and_return(false) allow(model).to receive(:index_exists?).and_return(true) allow(model).to receive(:disable_statement_timeout).and_call_original - allow(model).to receive(:supports_drop_index_concurrently?).and_return(true) end describe 'by column name' do 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 aab6fbcbbd1..5b1a17e734d 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 @@ -164,15 +164,6 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do end it_behaves_like 'has prometheus service', 'http://localhost:9090' - - it 'does not overwrite the existing whitelist' do - application_setting.outbound_local_requests_whitelist = ['example.com'] - - expect(result[:status]).to eq(:success) - expect(application_setting.outbound_local_requests_whitelist).to contain_exactly( - 'example.com', 'localhost' - ) - end end context 'with non default prometheus address' do diff --git a/spec/lib/gitlab/devise_failure_spec.rb b/spec/lib/gitlab/devise_failure_spec.rb new file mode 100644 index 00000000000..eee05c7befd --- /dev/null +++ b/spec/lib/gitlab/devise_failure_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::DeviseFailure do + let(:env) do + { + 'REQUEST_URI' => 'http://test.host/', + 'HTTP_HOST' => 'test.host', + 'REQUEST_METHOD' => 'GET', + 'warden.options' => { scope: :user }, + 'rack.session' => {}, + 'rack.session.options' => {}, + 'rack.input' => "", + 'warden' => OpenStruct.new(message: nil) + } + end + + let(:response) { described_class.call(env).to_a } + let(:request) { ActionDispatch::Request.new(env) } + + context 'When redirecting' do + it 'sets the expire_after key' do + response + + expect(env['rack.session.options']).to have_key(:expire_after) + end + + it 'returns to the default redirect location' do + expect(response.first).to eq(302) + expect(request.flash[:alert]).to eq('You need to sign in or sign up before continuing.') + expect(response.second['Location']).to eq('http://test.host/users/sign_in') + end + end +end diff --git a/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb index 35aa663b0a5..a65214fab61 100644 --- a/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb +++ b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Email::Hook::SmimeSignatureInterceptor do diff --git a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb index c3b706fc538..747fe369c78 100644 --- a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb +++ b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb index aed7d8d81ce..0739f622af5 100644 --- a/spec/lib/gitlab/exclusive_lease_spec.rb +++ b/spec/lib/gitlab/exclusive_lease_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb index 2e5fd16d370..9be6ace3be5 100644 --- a/spec/lib/gitlab/experimentation_spec.rb +++ b/spec/lib/gitlab/experimentation_spec.rb @@ -2,81 +2,194 @@ require 'spec_helper' -describe Gitlab::Experimentation::ControllerConcern, type: :controller do - controller(ApplicationController) do - include Gitlab::Experimentation::ControllerConcern +describe Gitlab::Experimentation do + before do + stub_const('Gitlab::Experimentation::EXPERIMENTS', { + test_experiment: { + feature_toggle: feature_toggle, + environment: environment, + enabled_ratio: enabled_ratio, + tracking_category: 'Team' + } + }) - def index - head :ok - end + stub_feature_flags(feature_toggle => true) end - describe '#set_experimentation_subject_id_cookie' do - before do - get :index + let(:feature_toggle) { :test_experiment_toggle } + let(:environment) { Rails.env.test? } + let(:enabled_ratio) { 0.1 } + + describe Gitlab::Experimentation::ControllerConcern, type: :controller do + controller(ApplicationController) do + include Gitlab::Experimentation::ControllerConcern + + def index + head :ok + end end - context 'cookie is present' do + describe '#set_experimentation_subject_id_cookie' do before do - cookies[:experimentation_subject_id] = 'test' + get :index end - it 'does not change the cookie' do - expect(cookies[:experimentation_subject_id]).to eq 'test' + context 'cookie is present' do + before do + cookies[:experimentation_subject_id] = 'test' + end + + it 'does not change the cookie' do + expect(cookies[:experimentation_subject_id]).to eq 'test' + end end - end - context 'cookie is not present' do - it 'sets a permanent signed cookie' do - expect(cookies.permanent.signed[:experimentation_subject_id]).to be_present + context 'cookie is not present' do + it 'sets a permanent signed cookie' do + expect(cookies.permanent.signed[:experimentation_subject_id]).to be_present + end end end - end - describe '#experiment_enabled?' do - context 'cookie is not present' do - it 'calls Gitlab::Experimentation.enabled? with the name of the experiment and an experimentation_subject_index of nil' do - expect(Gitlab::Experimentation).to receive(:enabled?).with(:test_experiment, nil) - controller.experiment_enabled?(:test_experiment) + describe '#experiment_enabled?' do + context 'cookie is not present' do + it 'calls Gitlab::Experimentation.enabled_for_user? with the name of the experiment and an experimentation_subject_index of nil' do + expect(Gitlab::Experimentation).to receive(:enabled_for_user?).with(:test_experiment, nil) # rubocop:disable RSpec/DescribedClass + controller.experiment_enabled?(:test_experiment) + end + end + + context 'cookie is present' do + before do + cookies.permanent.signed[:experimentation_subject_id] = 'abcd-1234' + get :index + end + + it 'calls Gitlab::Experimentation.enabled_for_user? with the name of the experiment and an experimentation_subject_index of the modulo 100 of the hex value of the uuid' do + # 'abcd1234'.hex % 100 = 76 + expect(Gitlab::Experimentation).to receive(:enabled_for_user?).with(:test_experiment, 76) # rubocop:disable RSpec/DescribedClass + controller.experiment_enabled?(:test_experiment) + end + end + + describe 'URL parameter to force enable experiment' do + it 'returns true' do + get :index, params: { force_experiment: :test_experiment } + + expect(controller.experiment_enabled?(:test_experiment)).to be_truthy + end end end - context 'cookie is present' do - before do - cookies.permanent.signed[:experimentation_subject_id] = 'abcd-1234' - get :index + describe '#track_experiment_event' do + context 'when the experiment is enabled' do + before do + stub_experiment(test_experiment: true) + end + + context 'the user is part of the experimental group' do + before do + stub_experiment_for_user(test_experiment: true) + end + + it 'tracks the event with the right parameters' do + expect(Gitlab::Tracking).to receive(:event).with( + 'Team', + 'start', + label: nil, + property: 'experimental_group' + ) + controller.track_experiment_event(:test_experiment, 'start') + end + end + + context 'the user is part of the control group' do + before do + stub_experiment_for_user(test_experiment: false) + end + + it 'tracks the event with the right parameters' do + expect(Gitlab::Tracking).to receive(:event).with( + 'Team', + 'start', + label: nil, + property: 'control_group' + ) + controller.track_experiment_event(:test_experiment, 'start') + end + end end - it 'calls Gitlab::Experimentation.enabled? with the name of the experiment and an experimentation_subject_index of the modulo 100 of the hex value of the uuid' do - # 'abcd1234'.hex % 100 = 76 - expect(Gitlab::Experimentation).to receive(:enabled?).with(:test_experiment, 76) - controller.experiment_enabled?(:test_experiment) + context 'when the experiment is disabled' do + before do + stub_experiment(test_experiment: false) + end + + it 'does not track the event' do + expect(Gitlab::Tracking).not_to receive(:event) + controller.track_experiment_event(:test_experiment, 'start') + end end end - end -end -describe Gitlab::Experimentation do - before do - stub_const('Gitlab::Experimentation::EXPERIMENTS', { - test_experiment: { - feature_toggle: feature_toggle, - environment: environment, - enabled_ratio: enabled_ratio - } - }) + describe '#frontend_experimentation_tracking_data' do + context 'when the experiment is enabled' do + before do + stub_experiment(test_experiment: true) + end - stub_feature_flags(feature_toggle => true) - end + context 'the user is part of the experimental group' do + before do + stub_experiment_for_user(test_experiment: true) + end + + it 'pushes the right parameters to gon' do + controller.frontend_experimentation_tracking_data(:test_experiment, 'start') + expect(Gon.tracking_data).to eq( + { + category: 'Team', + action: 'start', + label: nil, + property: 'experimental_group' + } + ) + end + end - let(:feature_toggle) { :test_experiment_toggle } - let(:environment) { Rails.env.test? } - let(:enabled_ratio) { 0.1 } + 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) + end + + it 'pushes the right parameters to gon' do + controller.frontend_experimentation_tracking_data(:test_experiment, 'start') + expect(Gon.tracking_data).to eq( + { + category: 'Team', + action: 'start', + label: nil, + property: 'control_group' + } + ) + end + end + end - describe '.enabled?' do - subject { described_class.enabled?(:test_experiment, experimentation_subject_index) } + context 'when the experiment is disabled' do + before do + stub_experiment(test_experiment: false) + end - let(:experimentation_subject_index) { 9 } + it 'does not push data to gon' do + expect(Gon.method_defined?(:tracking_data)).to be_falsey + controller.track_experiment_event(:test_experiment, 'start') + end + end + end + end + + describe '.enabled?' do + subject { described_class.enabled?(:test_experiment) } context 'feature toggle is enabled, we are on the right environment and we are selected' do it { is_expected.to be_truthy } @@ -84,7 +197,7 @@ describe Gitlab::Experimentation do describe 'experiment is not defined' do it 'returns false' do - expect(described_class.enabled?(:missing_experiment, experimentation_subject_index)).to be_falsey + expect(described_class.enabled?(:missing_experiment)).to be_falsey end end @@ -127,30 +240,52 @@ describe Gitlab::Experimentation do it { is_expected.to be_falsey } end end + end - describe 'enabled ratio' do - context 'enabled ratio is not set' do - let(:enabled_ratio) { nil } + describe '.enabled_for_user?' do + subject { described_class.enabled_for_user?(:test_experiment, experimentation_subject_index) } - it { is_expected.to be_falsey } + let(:experimentation_subject_index) { 9 } + + context 'experiment is disabled' do + before do + allow(described_class).to receive(:enabled?).and_return(false) end - context 'experimentation_subject_index is not set' do - let(:experimentation_subject_index) { nil } + it { is_expected.to be_falsey } + end - it { is_expected.to be_falsey } + context 'experiment is enabled' do + before do + allow(described_class).to receive(:enabled?).and_return(true) end - context 'experimentation_subject_index is an empty string' do - let(:experimentation_subject_index) { '' } + it { is_expected.to be_truthy } + + context 'enabled ratio is not set' do + let(:enabled_ratio) { nil } it { is_expected.to be_falsey } end - context 'experimentation_subject_index outside enabled ratio' do - let(:experimentation_subject_index) { 11 } + describe 'experimentation_subject_index' do + context 'experimentation_subject_index is not set' do + let(:experimentation_subject_index) { nil } - it { is_expected.to be_falsey } + it { is_expected.to be_falsey } + end + + context 'experimentation_subject_index is an empty string' do + let(:experimentation_subject_index) { '' } + + it { is_expected.to be_falsey } + end + + context 'experimentation_subject_index outside enabled ratio' do + let(:experimentation_subject_index) { 11 } + + it { is_expected.to be_falsey } + end end end end diff --git a/spec/lib/gitlab/external_authorization/access_spec.rb b/spec/lib/gitlab/external_authorization/access_spec.rb index 5dc2521b310..8a08b2a6275 100644 --- a/spec/lib/gitlab/external_authorization/access_spec.rb +++ b/spec/lib/gitlab/external_authorization/access_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do diff --git a/spec/lib/gitlab/external_authorization/cache_spec.rb b/spec/lib/gitlab/external_authorization/cache_spec.rb index 58e7d626707..1f217249f97 100644 --- a/spec/lib/gitlab/external_authorization/cache_spec.rb +++ b/spec/lib/gitlab/external_authorization/cache_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ExternalAuthorization::Cache, :clean_gitlab_redis_cache do diff --git a/spec/lib/gitlab/external_authorization/client_spec.rb b/spec/lib/gitlab/external_authorization/client_spec.rb index a87f50b4586..a17d933e3bb 100644 --- a/spec/lib/gitlab/external_authorization/client_spec.rb +++ b/spec/lib/gitlab/external_authorization/client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ExternalAuthorization::Client do diff --git a/spec/lib/gitlab/external_authorization/logger_spec.rb b/spec/lib/gitlab/external_authorization/logger_spec.rb index 81f1b2390e6..380e765309c 100644 --- a/spec/lib/gitlab/external_authorization/logger_spec.rb +++ b/spec/lib/gitlab/external_authorization/logger_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ExternalAuthorization::Logger do diff --git a/spec/lib/gitlab/external_authorization/response_spec.rb b/spec/lib/gitlab/external_authorization/response_spec.rb index 43211043eca..e1f6e9ac1fa 100644 --- a/spec/lib/gitlab/external_authorization/response_spec.rb +++ b/spec/lib/gitlab/external_authorization/response_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ExternalAuthorization::Response do diff --git a/spec/lib/gitlab/external_authorization_spec.rb b/spec/lib/gitlab/external_authorization_spec.rb index c45fcca3f06..97055e7b3f9 100644 --- a/spec/lib/gitlab/external_authorization_spec.rb +++ b/spec/lib/gitlab/external_authorization_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ExternalAuthorization, :request_store do diff --git a/spec/lib/gitlab/fake_application_settings_spec.rb b/spec/lib/gitlab/fake_application_settings_spec.rb index c81cb83d9f4..6a872185713 100644 --- a/spec/lib/gitlab/fake_application_settings_spec.rb +++ b/spec/lib/gitlab/fake_application_settings_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::FakeApplicationSettings do diff --git a/spec/lib/gitlab/favicon_spec.rb b/spec/lib/gitlab/favicon_spec.rb index 617c0f88a89..884425dab3b 100644 --- a/spec/lib/gitlab/favicon_spec.rb +++ b/spec/lib/gitlab/favicon_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' RSpec.describe Gitlab::Favicon, :request_store do diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb index 4ba9094b24e..f3a9f706e86 100644 --- a/spec/lib/gitlab/file_detector_spec.rb +++ b/spec/lib/gitlab/file_detector_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::FileDetector do diff --git a/spec/lib/gitlab/file_finder_spec.rb b/spec/lib/gitlab/file_finder_spec.rb index b49c5817131..7ea9d43c9f7 100644 --- a/spec/lib/gitlab/file_finder_spec.rb +++ b/spec/lib/gitlab/file_finder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::FileFinder do @@ -6,11 +8,11 @@ describe Gitlab::FileFinder do subject { described_class.new(project, project.default_branch) } it_behaves_like 'file finder' do - let(:expected_file_by_name) { 'files/images/wm.svg' } + let(:expected_file_by_path) { 'files/images/wm.svg' } let(:expected_file_by_content) { 'CHANGELOG' } end - it 'filters by name' do + it 'filters by filename' do results = subject.find('files filename:wm.svg') expect(results.count).to eq(1) diff --git a/spec/lib/gitlab/fogbugz_import/client_spec.rb b/spec/lib/gitlab/fogbugz_import/client_spec.rb index dcd1a2d9813..676511211c8 100644 --- a/spec/lib/gitlab/fogbugz_import/client_spec.rb +++ b/spec/lib/gitlab/fogbugz_import/client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::FogbugzImport::Client do diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index 790b0428d19..026fd1fedde 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Gfm::ReferenceRewriter do diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb index eef3b9de476..5a930d44dcb 100644 --- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Gfm::UploadsRewriter do diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 23651e3d7f2..cdab7127748 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -428,7 +428,9 @@ describe Gitlab::Git::Commit, :seed_helper do end end - shared_examples 'extracting commit signature' do + describe '.extract_signature_lazily' do + subject { described_class.extract_signature_lazily(repository, commit_id).itself } + context 'when the commit is signed' do let(:commit_id) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } @@ -492,10 +494,8 @@ describe Gitlab::Git::Commit, :seed_helper do expect { subject }.to raise_error(ArgumentError) end end - end - describe '.extract_signature_lazily' do - describe 'loading signatures in batch once' do + context 'when loading signatures in batch once' do it 'fetches signatures in batch once' do commit_ids = %w[0b4bc9a49b562e85de7cc9e834518ea6828729b9 4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6] signatures = commit_ids.map do |commit_id| @@ -516,16 +516,6 @@ describe Gitlab::Git::Commit, :seed_helper do 2.times { signatures.each(&:itself) } end end - - subject { described_class.extract_signature_lazily(repository, commit_id).itself } - - it_behaves_like 'extracting commit signature' - end - - describe '.extract_signature' do - subject { described_class.extract_signature(repository, commit_id) } - - it_behaves_like 'extracting commit signature' end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 81dc96b538a..f74cc5623c9 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitAccess do diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 6ba65b56618..99c9369a2b9 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitAccessWiki do diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb index b63389af29f..1531317c514 100644 --- a/spec/lib/gitlab/git_ref_validator_spec.rb +++ b/spec/lib/gitlab/git_ref_validator_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitRefValidator do diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb index 505bc470644..fbc49e05c37 100644 --- a/spec/lib/gitlab/git_spec.rb +++ b/spec/lib/gitlab/git_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git do diff --git a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb index a2770ef2fe4..887a6baf659 100644 --- a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::BlobService do diff --git a/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb index 742b2872c40..e88b86c71f2 100644 --- a/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb +++ b/spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::BlobsStitcher do diff --git a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb index c42332dc27b..c6c7fa1c38a 100644 --- a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::CleanupService do diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 71489adb373..1abdabe17bb 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::CommitService do diff --git a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb index a3602463756..db734b1c129 100644 --- a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb +++ b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::ConflictFilesStitcher do diff --git a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb index 52630ba0223..f19bcae2470 100644 --- a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::ConflictsService do diff --git a/spec/lib/gitlab/gitaly_client/diff_spec.rb b/spec/lib/gitlab/gitaly_client/diff_spec.rb index ec7ab2fdedb..d86497da7f5 100644 --- a/spec/lib/gitlab/gitaly_client/diff_spec.rb +++ b/spec/lib/gitlab/gitaly_client/diff_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::Diff do diff --git a/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb index cd3242b9326..c9d42ad32cf 100644 --- a/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb +++ b/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::DiffStitcher do diff --git a/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb index 2c7e5eb5787..615bc80fff2 100644 --- a/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::HealthCheckService do diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb index f38b8d31237..d4337c51279 100644 --- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::OperationService do @@ -209,10 +211,12 @@ describe Gitlab::GitalyClient::OperationService do end context 'when a create_tree_error is present' do - let(:response) { response_class.new(create_tree_error: "something failed") } + let(:response) { response_class.new(create_tree_error: "something failed", create_tree_error_code: 'EMPTY') } it 'raises a CreateTreeError' do - expect { subject }.to raise_error(Gitlab::Git::Repository::CreateTreeError, "something failed") + expect { subject }.to raise_error(Gitlab::Git::Repository::CreateTreeError) do |error| + expect(error.error_code).to eq(:empty) + end end end diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 0bb6e582159..2b4fe2ea5c0 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::RefService do diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb index d5508dbff5d..929ff5dee5d 100644 --- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::RemoteService do diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb index f4b73931f21..503ac57ade6 100644 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::RepositoryService do diff --git a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb index 2f83e5a5221..a6b29489df3 100644 --- a/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb +++ b/spec/lib/gitlab/gitaly_client/storage_settings_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::StorageSettings do diff --git a/spec/lib/gitlab/gitaly_client/util_spec.rb b/spec/lib/gitlab/gitaly_client/util_spec.rb index 78a5e195ad1..f31b7c349ff 100644 --- a/spec/lib/gitlab/gitaly_client/util_spec.rb +++ b/spec/lib/gitlab/gitaly_client/util_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::Util do diff --git a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb index 4fa8e97aca0..cb04f9a1637 100644 --- a/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/wiki_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GitalyClient::WikiService do diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index b8df9ad642a..b6c0c0ad523 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' # We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want @@ -399,6 +401,8 @@ describe Gitlab::GitalyClient do context 'when the request store is active', :request_store do it 'records call details if a RPC is called' do + expect(described_class).to receive(:measure_timings).and_call_original + gitaly_server.server_version expect(described_class.list_call_details).not_to be_empty diff --git a/spec/lib/gitlab/github_import/bulk_importing_spec.rb b/spec/lib/gitlab/github_import/bulk_importing_spec.rb index 91229d9c7d4..3266ec4ab50 100644 --- a/spec/lib/gitlab/github_import/bulk_importing_spec.rb +++ b/spec/lib/gitlab/github_import/bulk_importing_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::BulkImporting do diff --git a/spec/lib/gitlab/github_import/caching_spec.rb b/spec/lib/gitlab/github_import/caching_spec.rb index 70ecdc16da1..18c3e382532 100644 --- a/spec/lib/gitlab/github_import/caching_spec.rb +++ b/spec/lib/gitlab/github_import/caching_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Caching, :clean_gitlab_redis_cache do diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb index 5b2642d9473..3b269d64b07 100644 --- a/spec/lib/gitlab/github_import/client_spec.rb +++ b/spec/lib/gitlab/github_import/client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Client do 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 1568c657a1e..484458289af 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 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::DiffNoteImporter do diff --git a/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb b/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb index 4713c6795bb..23ed21294e3 100644 --- a/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/diff_notes_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::DiffNotesImporter do diff --git a/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb index 665b31ef244..399e2d9a563 100644 --- a/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/issue_and_label_links_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::IssueAndLabelLinksImporter do 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 dab5767ece1..a003ad7e091 100644 --- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cache do diff --git a/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb index e237e79e94b..8920ef9fedb 100644 --- a/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/issues_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::IssuesImporter do 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 e2a71e78574..19d40b2f380 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 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::LabelLinksImporter do 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 156ef96a0fa..2dcf1433154 100644 --- a/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::LabelsImporter, :clean_gitlab_redis_cache do diff --git a/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb index 8fd328d9c1e..a02b620f131 100644 --- a/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::LfsObjectImporter do diff --git a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb index 50442552eee..bec039a48eb 100644 --- a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::LfsObjectsImporter do 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 120a07ff2b3..eaf63e0e11b 100644 --- a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis_cache do 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 9bdcc42be19..d2b8ba186c8 100644 --- a/spec/lib/gitlab/github_import/importer/note_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/note_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::NoteImporter do diff --git a/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb b/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb index f046d13f879..128f8f95fa0 100644 --- a/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/notes_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::NotesImporter do 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 8331f0b6bc7..50c27e7f4b7 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 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redis_cache do diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb index c51985f00a2..e2d810d5ddc 100644 --- a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::PullRequestsImporter do diff --git a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb index 6a31c57a73d..f8d53208619 100644 --- a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::ReleasesImporter do diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb index 705df1f4fe7..c65b28fafbf 100644 --- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Importer::RepositoryImporter do diff --git a/spec/lib/gitlab/github_import/issuable_finder_spec.rb b/spec/lib/gitlab/github_import/issuable_finder_spec.rb index da69911812a..b8a6feb6c73 100644 --- a/spec/lib/gitlab/github_import/issuable_finder_spec.rb +++ b/spec/lib/gitlab/github_import/issuable_finder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do diff --git a/spec/lib/gitlab/github_import/label_finder_spec.rb b/spec/lib/gitlab/github_import/label_finder_spec.rb index 8ba766944d6..039ae27ad57 100644 --- a/spec/lib/gitlab/github_import/label_finder_spec.rb +++ b/spec/lib/gitlab/github_import/label_finder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do diff --git a/spec/lib/gitlab/github_import/markdown_text_spec.rb b/spec/lib/gitlab/github_import/markdown_text_spec.rb index 1ff5b9d66b3..a1216db7aac 100644 --- a/spec/lib/gitlab/github_import/markdown_text_spec.rb +++ b/spec/lib/gitlab/github_import/markdown_text_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::MarkdownText do diff --git a/spec/lib/gitlab/github_import/milestone_finder_spec.rb b/spec/lib/gitlab/github_import/milestone_finder_spec.rb index dff931a2fe8..407e2e67ec9 100644 --- a/spec/lib/gitlab/github_import/milestone_finder_spec.rb +++ b/spec/lib/gitlab/github_import/milestone_finder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do diff --git a/spec/lib/gitlab/github_import/page_counter_spec.rb b/spec/lib/gitlab/github_import/page_counter_spec.rb index c2613a9a415..87f3ce45fd3 100644 --- a/spec/lib/gitlab/github_import/page_counter_spec.rb +++ b/spec/lib/gitlab/github_import/page_counter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::PageCounter, :clean_gitlab_redis_cache do diff --git a/spec/lib/gitlab/github_import/parallel_importer_spec.rb b/spec/lib/gitlab/github_import/parallel_importer_spec.rb index ecab64a372a..a9b7d3d388c 100644 --- a/spec/lib/gitlab/github_import/parallel_importer_spec.rb +++ b/spec/lib/gitlab/github_import/parallel_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ParallelImporter do diff --git a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb index 98205d3ee25..f4d107e3dce 100644 --- a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb +++ b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ParallelScheduling do diff --git a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb index 7b0a1ea4948..e743a87cdd1 100644 --- a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb +++ b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Representation::DiffNote do diff --git a/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb b/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb index 15de0fe49ff..e3b48df4ae9 100644 --- a/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb +++ b/spec/lib/gitlab/github_import/representation/expose_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Representation::ExposeAttribute do diff --git a/spec/lib/gitlab/github_import/representation/issue_spec.rb b/spec/lib/gitlab/github_import/representation/issue_spec.rb index 99330ce42cb..741a912e53b 100644 --- a/spec/lib/gitlab/github_import/representation/issue_spec.rb +++ b/spec/lib/gitlab/github_import/representation/issue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Representation::Issue do diff --git a/spec/lib/gitlab/github_import/representation/note_spec.rb b/spec/lib/gitlab/github_import/representation/note_spec.rb index f2c1c66b357..a171a38bc9e 100644 --- a/spec/lib/gitlab/github_import/representation/note_spec.rb +++ b/spec/lib/gitlab/github_import/representation/note_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Representation::Note do diff --git a/spec/lib/gitlab/github_import/representation/pull_request_spec.rb b/spec/lib/gitlab/github_import/representation/pull_request_spec.rb index d478e5ae899..b6dcd098c9c 100644 --- a/spec/lib/gitlab/github_import/representation/pull_request_spec.rb +++ b/spec/lib/gitlab/github_import/representation/pull_request_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Representation::PullRequest do diff --git a/spec/lib/gitlab/github_import/representation/to_hash_spec.rb b/spec/lib/gitlab/github_import/representation/to_hash_spec.rb index c296aa0a45b..9c47349b376 100644 --- a/spec/lib/gitlab/github_import/representation/to_hash_spec.rb +++ b/spec/lib/gitlab/github_import/representation/to_hash_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Representation::ToHash do diff --git a/spec/lib/gitlab/github_import/representation/user_spec.rb b/spec/lib/gitlab/github_import/representation/user_spec.rb index 4e63e8ea568..a7ad6bda3ad 100644 --- a/spec/lib/gitlab/github_import/representation/user_spec.rb +++ b/spec/lib/gitlab/github_import/representation/user_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Representation::User do diff --git a/spec/lib/gitlab/github_import/representation_spec.rb b/spec/lib/gitlab/github_import/representation_spec.rb index 0b0610817b0..76753a0ff21 100644 --- a/spec/lib/gitlab/github_import/representation_spec.rb +++ b/spec/lib/gitlab/github_import/representation_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Representation do diff --git a/spec/lib/gitlab/github_import/sequential_importer_spec.rb b/spec/lib/gitlab/github_import/sequential_importer_spec.rb index 05d3243f806..8b1e8fbf3b7 100644 --- a/spec/lib/gitlab/github_import/sequential_importer_spec.rb +++ b/spec/lib/gitlab/github_import/sequential_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::SequentialImporter do diff --git a/spec/lib/gitlab/github_import/user_finder_spec.rb b/spec/lib/gitlab/github_import/user_finder_spec.rb index 29f4c00d9c7..74b5c1c52cd 100644 --- a/spec/lib/gitlab/github_import/user_finder_spec.rb +++ b/spec/lib/gitlab/github_import/user_finder_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do diff --git a/spec/lib/gitlab/github_import_spec.rb b/spec/lib/gitlab/github_import_spec.rb index 496244c91bf..c3ddac01c87 100644 --- a/spec/lib/gitlab/github_import_spec.rb +++ b/spec/lib/gitlab/github_import_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport do diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb index d4b6c629659..3290bef8aa5 100644 --- a/spec/lib/gitlab/gl_repository_spec.rb +++ b/spec/lib/gitlab/gl_repository_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ::Gitlab::GlRepository 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 1dfca0b056c..da307754243 100644 --- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb +++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb @@ -43,7 +43,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do verification_status: 'verified' end - it 'assigns the gpg key to the signature when the missing gpg key is added' do + it 'assigns the gpg key to the signature when the missing gpg key is added', :sidekiq_might_not_need_inline do # InvalidGpgSignatureUpdater is called by the after_create hook gpg_key = create :gpg_key, key: GpgHelpers::User1.public_key, @@ -86,7 +86,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do verification_status: 'unknown_key' end - it 'updates the signature to being valid when the missing gpg key is added' do + it 'updates the signature to being valid when the missing gpg key is added', :sidekiq_might_not_need_inline do # InvalidGpgSignatureUpdater is called by the after_create hook gpg_key = create :gpg_key, key: GpgHelpers::User1.public_key, @@ -133,7 +133,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do verification_status: 'unknown_key' end - it 'updates the signature to being valid when the user updates the email address' do + it 'updates the signature to being valid when the user updates the email address', :sidekiq_might_not_need_inline do gpg_key = create :gpg_key, key: GpgHelpers::User1.public_key, user: user @@ -152,7 +152,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do ) end - it 'keeps the signature at being invalid when the changed email address is still unrelated' do + it 'keeps the signature at being invalid when the changed email address is still unrelated', :sidekiq_might_not_need_inline do gpg_key = create :gpg_key, key: GpgHelpers::User1.public_key, user: user @@ -192,7 +192,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do verification_status: 'unknown_key' end - it 'updates the signature to being valid when the missing gpg key is added' do + it 'updates the signature to being valid when the missing gpg key is added', :sidekiq_might_not_need_inline do # InvalidGpgSignatureUpdater is called by the after_create hook gpg_key = create(:gpg_key, key: GpgHelpers::User3.public_key, user: user) subkey = gpg_key.subkeys.last diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb index 77d318c9b23..52d6a86f7d0 100644 --- a/spec/lib/gitlab/gpg_spec.rb +++ b/spec/lib/gitlab/gpg_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Gpg do @@ -63,7 +65,7 @@ describe Gitlab::Gpg do it 'downcases the email' do public_key = double(:key) fingerprints = double(:fingerprints) - uid = double(:uid, name: 'Nannie Bernhard', email: 'NANNIE.BERNHARD@EXAMPLE.COM') + uid = double(:uid, name: +'Nannie Bernhard', email: +'NANNIE.BERNHARD@EXAMPLE.COM') raw_key = double(:raw_key, uids: [uid]) allow(Gitlab::Gpg::CurrentKeyChain).to receive(:fingerprints_from_key).with(public_key).and_return(fingerprints) allow(GPGME::Key).to receive(:find).with(:public, anything).and_return([raw_key]) @@ -78,8 +80,8 @@ describe Gitlab::Gpg do it 'rejects non UTF-8 names and addresses' do public_key = double(:key) fingerprints = double(:fingerprints) - email = "\xEEch@test.com".force_encoding('ASCII-8BIT') - uid = double(:uid, name: 'Test User', email: email) + email = (+"\xEEch@test.com").force_encoding('ASCII-8BIT') + uid = double(:uid, name: +'Test User', email: email) raw_key = double(:raw_key, uids: [uid]) allow(Gitlab::Gpg::CurrentKeyChain).to receive(:fingerprints_from_key).with(public_key).and_return(fingerprints) allow(GPGME::Key).to receive(:find).with(:public, anything).and_return([raw_key]) @@ -139,6 +141,96 @@ describe Gitlab::Gpg do end end.not_to raise_error end + + it 'keeps track of created and removed keychains in counters' do + created = Gitlab::Metrics.counter(:gpg_tmp_keychains_created_total, 'The number of temporary GPG keychains') + removed = Gitlab::Metrics.counter(:gpg_tmp_keychains_removed_total, 'The number of temporary GPG keychains') + + initial_created = created.get + initial_removed = removed.get + + described_class.using_tmp_keychain do + expect(created.get).to eq(initial_created + 1) + expect(removed.get).to eq(initial_removed) + end + + expect(removed.get).to eq(initial_removed + 1) + end + + it 'cleans up the tmp directory after finishing' do + tmp_directory = nil + + described_class.using_tmp_keychain do + tmp_directory = described_class.current_home_dir + expect(File.exist?(tmp_directory)).to be true + end + + expect(tmp_directory).not_to be_nil + expect(File.exist?(tmp_directory)).to be false + end + + it 'does not fail if the homedir was deleted while running' do + expect do + described_class.using_tmp_keychain do + FileUtils.remove_entry(described_class.current_home_dir) + end + end.not_to raise_error + end + + shared_examples 'multiple deletion attempts of the tmp-dir' do |seconds| + let(:tmp_dir) do + tmp_dir = Dir.mktmpdir + allow(Dir).to receive(:mktmpdir).and_return(tmp_dir) + tmp_dir + end + + before do + # Stub all the other calls for `remove_entry` + allow(FileUtils).to receive(:remove_entry).with(any_args).and_call_original + end + + it "tries for #{seconds}" do + expect(Retriable).to receive(:retriable).with(a_hash_including(max_elapsed_time: seconds)) + + described_class.using_tmp_keychain {} + end + + it 'tries at least 2 times to remove the tmp dir before raising', :aggregate_failures do + expect(Retriable).to receive(:sleep).at_least(2).times + expect(FileUtils).to receive(:remove_entry).with(tmp_dir).at_least(2).times.and_raise('Deletion failed') + + expect { described_class.using_tmp_keychain { } }.to raise_error(described_class::CleanupError) + end + + it 'does not attempt multiple times when the deletion succeeds' do + expect(Retriable).to receive(:sleep).once + expect(FileUtils).to receive(:remove_entry).with(tmp_dir).once.and_raise('Deletion failed') + expect(FileUtils).to receive(:remove_entry).with(tmp_dir).and_call_original + + expect { described_class.using_tmp_keychain { } }.not_to raise_error + + 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 + + context 'when running in Sidekiq' do + before do + allow(Sidekiq).to receive(:server?).and_return(true) + end + + it_behaves_like 'multiple deletion attempts of the tmp-dir', described_class::BG_CLEANUP_RUNTIME_S + 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 new file mode 100644 index 00000000000..8d7826c0a56 --- /dev/null +++ b/spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Gitlab::GrapeLogging::Loggers::ExceptionLogger do + subject { described_class.new } + + let(:mock_request) { OpenStruct.new(env: {}) } + + describe ".parameters" do + describe 'when no exception is available' do + it 'returns an empty hash' do + expect(subject.parameters(mock_request, nil)).to eq({}) + end + end + + describe 'when an exception is available' do + let(:exception) { RuntimeError.new('This is a test') } + let(:mock_request) do + OpenStruct.new( + env: { + ::API::Helpers::API_EXCEPTION_ENV => exception + } + ) + end + + let(:expected) do + { + exception: { + class: 'RuntimeError', + message: 'This is a test' + } + } + end + + it 'returns the correct fields' do + expect(subject.parameters(mock_request, nil)).to eq(expected) + end + + context 'with backtrace' do + before do + current_backtrace = caller + allow(exception).to receive(:backtrace).and_return(current_backtrace) + expected[:exception][:backtrace] = Gitlab::Profiler.clean_backtrace(current_backtrace) + end + + it 'includes the backtrace' do + expect(subject.parameters(mock_request, nil)).to eq(expected) + end + end + end + end +end diff --git a/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb b/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb new file mode 100644 index 00000000000..1fda84f777e --- /dev/null +++ b/spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +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 + + describe '#paged_nodes' do + let(:paged_nodes) { subject.paged_nodes } + + it_behaves_like "connection with paged nodes" + + context 'when callback filters some nodes' do + let(:callback) { proc { |nodes| nodes[1..-1] } } + + it 'does not return filtered elements' do + expect(subject.paged_nodes).to contain_exactly(all_nodes[1], all_nodes[2]) + end + end + end +end diff --git a/spec/lib/gitlab/graphql/connections/keyset/conditions/not_null_condition_spec.rb b/spec/lib/gitlab/graphql/connections/keyset/conditions/not_null_condition_spec.rb new file mode 100644 index 00000000000..d943540fe1f --- /dev/null +++ b/spec/lib/gitlab/graphql/connections/keyset/conditions/not_null_condition_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Connections::Keyset::Conditions::NotNullCondition do + describe '#build' do + let(:condition) { described_class.new(Issue.arel_table, %w(relative_position id), [1500, 500], ['>', '>'], before_or_after) } + + context 'when there is only one ordering field' do + let(:condition) { described_class.new(Issue.arel_table, ['id'], [500], ['>'], :after) } + + it 'generates a single condition sql' do + expected_sql = <<~SQL + ("issues"."id" > 500) + SQL + + expect(condition.build.squish).to eq expected_sql.squish + end + end + + context 'when :after' do + let(:before_or_after) { :after } + + it 'generates :after sql' do + expected_sql = <<~SQL + ("issues"."relative_position" > 1500) + OR ( + "issues"."relative_position" = 1500 + AND + "issues"."id" > 500 + ) + OR ("issues"."relative_position" IS NULL) + SQL + + expect(condition.build.squish).to eq expected_sql.squish + end + end + + context 'when :before' do + let(:before_or_after) { :before } + + it 'generates :before sql' do + expected_sql = <<~SQL + ("issues"."relative_position" > 1500) + OR ( + "issues"."relative_position" = 1500 + AND + "issues"."id" > 500 + ) + SQL + + expect(condition.build.squish).to eq expected_sql.squish + end + end + end +end diff --git a/spec/lib/gitlab/graphql/connections/keyset/conditions/null_condition_spec.rb b/spec/lib/gitlab/graphql/connections/keyset/conditions/null_condition_spec.rb new file mode 100644 index 00000000000..7fce94adb81 --- /dev/null +++ b/spec/lib/gitlab/graphql/connections/keyset/conditions/null_condition_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Connections::Keyset::Conditions::NullCondition do + describe '#build' do + let(:condition) { described_class.new(Issue.arel_table, %w(relative_position id), [nil, 500], [nil, '>'], before_or_after) } + + context 'when :after' do + let(:before_or_after) { :after } + + it 'generates sql' do + expected_sql = <<~SQL + ( + "issues"."relative_position" IS NULL + AND + "issues"."id" > 500 + ) + SQL + + expect(condition.build.squish).to eq expected_sql.squish + end + end + + context 'when :before' do + let(:before_or_after) { :before } + + it 'generates :before sql' do + expected_sql = <<~SQL + ( + "issues"."relative_position" IS NULL + AND + "issues"."id" > 500 + ) + OR ("issues"."relative_position" IS NOT NULL) + SQL + + expect(condition.build.squish).to eq expected_sql.squish + end + end + end +end diff --git a/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb new file mode 100644 index 00000000000..9dda2a41ec6 --- /dev/null +++ b/spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb @@ -0,0 +1,281 @@ +# frozen_string_literal: true + +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 + + def encoded_cursor(node) + described_class.new(nodes, {}).cursor_from_node(node) + end + + def decoded_cursor(cursor) + JSON.parse(Base64Bp.urlsafe_decode64(cursor)) + end + + describe '#cursor_from_nodes' do + let(:project) { create(:project) } + let(:cursor) { connection.cursor_from_node(project) } + + it 'returns an encoded ID' do + expect(decoded_cursor(cursor)).to eq('id' => project.id.to_s) + end + + context 'when an order is specified' do + let(:nodes) { Project.order(:updated_at) } + + it 'returns the encoded value of the order' do + expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s) + end + + it 'includes the :id even when not specified in the order' do + expect(decoded_cursor(cursor)).to include('id' => project.id.to_s) + end + end + + context 'when multiple orders are specified' do + let(:nodes) { Project.order(:updated_at).order(:created_at) } + + it 'returns the encoded value of the order' do + expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s) + end + end + + context 'when multiple orders with SQL are specified' do + let(:nodes) { Project.order(Arel.sql('projects.updated_at IS NULL')).order(:updated_at).order(:id) } + + it 'returns the encoded value of the order' do + expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s) + end + end + end + + describe '#sliced_nodes' do + let(:projects) { create_list(:project, 4) } + + context 'when before is passed' do + let(:arguments) { { before: encoded_cursor(projects[1]) } } + + 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_cursor(projects[1]) } } + + 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_cursor(projects[1]), + before: encoded_cursor(projects[3]) + } + end + + it 'returns the expected set' do + expect(subject.sliced_nodes).to contain_exactly(projects[2]) + end + end + + context 'when multiple orders are defined' do + let!(:project1) { create(:project, last_repository_check_at: 10.days.ago) } # Asc: project5 Desc: project3 + let!(:project2) { create(:project, last_repository_check_at: nil) } # Asc: project1 Desc: project1 + let!(:project3) { create(:project, last_repository_check_at: 5.days.ago) } # Asc: project3 Desc: project5 + let!(:project4) { create(:project, last_repository_check_at: nil) } # Asc: project2 Desc: project2 + let!(:project5) { create(:project, last_repository_check_at: 20.days.ago) } # Asc: project4 Desc: project4 + + context 'when ascending' do + let(:nodes) do + Project.order(Arel.sql('projects.last_repository_check_at IS NULL')).order(last_repository_check_at: :asc).order(id: :asc) + end + + context 'when no cursor is passed' do + let(:arguments) { {} } + + it 'returns projects in ascending order' do + expect(subject.sliced_nodes).to eq([project5, project1, project3, project2, project4]) + end + end + + context 'when before cursor value is NULL' do + let(:arguments) { { before: encoded_cursor(project4) } } + + it 'returns all projects before the cursor' do + expect(subject.sliced_nodes).to eq([project5, project1, project3, project2]) + end + end + + context 'when before cursor value is not NULL' do + let(:arguments) { { before: encoded_cursor(project3) } } + + it 'returns all projects before the cursor' do + expect(subject.sliced_nodes).to eq([project5, project1]) + end + end + + context 'when after cursor value is NULL' do + let(:arguments) { { after: encoded_cursor(project2) } } + + it 'returns all projects after the cursor' do + expect(subject.sliced_nodes).to eq([project4]) + end + end + + context 'when after cursor value is not NULL' do + let(:arguments) { { after: encoded_cursor(project1) } } + + it 'returns all projects after the cursor' do + expect(subject.sliced_nodes).to eq([project3, project2, project4]) + end + end + + context 'when before and after cursor' do + let(:arguments) { { before: encoded_cursor(project4), after: encoded_cursor(project5) } } + + it 'returns all projects after the cursor' do + expect(subject.sliced_nodes).to eq([project1, project3, project2]) + end + end + end + + context 'when descending' do + let(:nodes) do + Project.order(Arel.sql('projects.last_repository_check_at IS NULL')).order(last_repository_check_at: :desc).order(id: :asc) + end + + context 'when no cursor is passed' do + let(:arguments) { {} } + + it 'only returns projects in descending order' do + expect(subject.sliced_nodes).to eq([project3, project1, project5, project2, project4]) + end + end + + context 'when before cursor value is NULL' do + let(:arguments) { { before: encoded_cursor(project4) } } + + it 'returns all projects before the cursor' do + expect(subject.sliced_nodes).to eq([project3, project1, project5, project2]) + end + end + + context 'when before cursor value is not NULL' do + let(:arguments) { { before: encoded_cursor(project5) } } + + it 'returns all projects before the cursor' do + expect(subject.sliced_nodes).to eq([project3, project1]) + end + end + + context 'when after cursor value is NULL' do + let(:arguments) { { after: encoded_cursor(project2) } } + + it 'returns all projects after the cursor' do + expect(subject.sliced_nodes).to eq([project4]) + end + end + + context 'when after cursor value is not NULL' do + let(:arguments) { { after: encoded_cursor(project1) } } + + it 'returns all projects after the cursor' do + expect(subject.sliced_nodes).to eq([project5, project2, project4]) + end + end + + context 'when before and after cursor' do + let(:arguments) { { before: encoded_cursor(project4), after: encoded_cursor(project3) } } + + it 'returns all projects after the cursor' do + expect(subject.sliced_nodes).to eq([project1, project5, project2]) + end + end + 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) } } + + it 'only returns the project before the selected one' do + expect(subject.sliced_nodes).to contain_exactly(projects.first) + end + end + end + + describe '#paged_nodes' do + let_it_be(:all_nodes) { create_list(:project, 5) } + let(:paged_nodes) { subject.paged_nodes } + + it_behaves_like "connection with paged nodes" + + context 'when both are passed' do + let(:arguments) { { first: 2, last: 2 } } + + it 'raises an error' do + expect { paged_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end + end + + context 'when primary key is not in original order' do + let(:nodes) { Project.order(last_repository_check_at: :desc) } + + it 'is added to end' do + sliced = subject.sliced_nodes + last_order_name = sliced.order_values.last.expr.name + + expect(last_order_name).to eq sliced.primary_key + end + end + + context 'when there is no primary key' do + let(:nodes) { NoPrimaryKey.all } + + it 'raises an error' do + expect(NoPrimaryKey.primary_key).to be_nil + expect { subject.sliced_nodes }.to raise_error(ArgumentError, 'Relation must have a primary key') + end + end + end + + class NoPrimaryKey < ActiveRecord::Base + self.table_name = 'no_primary_key' + self.primary_key = nil + 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 new file mode 100644 index 00000000000..aaf28fed684 --- /dev/null +++ b/spec/lib/gitlab/graphql/connections/keyset/legacy_keyset_connection_spec.rb @@ -0,0 +1,127 @@ +# 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/connections/keyset/order_info_spec.rb b/spec/lib/gitlab/graphql/connections/keyset/order_info_spec.rb new file mode 100644 index 00000000000..17ddcaefeeb --- /dev/null +++ b/spec/lib/gitlab/graphql/connections/keyset/order_info_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Connections::Keyset::OrderInfo do + describe '#build_order_list' do + let(:order_list) { described_class.build_order_list(relation) } + + context 'when multiple orders with SQL is specified' do + let(:relation) { Project.order(Arel.sql('projects.updated_at IS NULL')).order(:updated_at).order(:id) } + + it 'ignores the SQL order' do + expect(order_list.count).to eq 2 + expect(order_list.first.attribute_name).to eq 'updated_at' + expect(order_list.first.operator_for(:after)).to eq '>' + expect(order_list.last.attribute_name).to eq 'id' + expect(order_list.last.operator_for(:after)).to eq '>' + end + end + + context 'when order contains NULLS LAST' do + let(:relation) { Project.order(Arel.sql('projects.updated_at Asc Nulls Last')).order(:id) } + + it 'does not ignore the SQL order' do + expect(order_list.count).to eq 2 + expect(order_list.first.attribute_name).to eq 'projects.updated_at' + expect(order_list.first.operator_for(:after)).to eq '>' + expect(order_list.last.attribute_name).to eq 'id' + expect(order_list.last.operator_for(:after)).to eq '>' + end + end + + context 'when order contains invalid formatted NULLS LAST ' do + let(:relation) { Project.order(Arel.sql('projects.updated_at created_at Asc Nulls Last')).order(:id) } + + it 'ignores the SQL order' do + expect(order_list.count).to eq 1 + end + end + end + + describe '#validate_ordering' do + let(:order_list) { described_class.build_order_list(relation) } + + context 'when number of ordering fields is 0' do + let(:relation) { Project.all } + + it 'raises an error' do + expect { described_class.validate_ordering(relation, order_list) } + .to raise_error(ArgumentError, 'A minimum of 1 ordering field is required') + end + end + + context 'when number of ordering fields is over 2' do + let(:relation) { Project.order(last_repository_check_at: :desc).order(updated_at: :desc).order(:id) } + + it 'raises an error' do + expect { described_class.validate_ordering(relation, order_list) } + .to raise_error(ArgumentError, 'A maximum of 2 ordering fields are allowed') + end + end + + context 'when the second (or first) column is nullable' do + let(:relation) { Project.order(last_repository_check_at: :desc).order(updated_at: :desc) } + + it 'raises an error' do + expect { described_class.validate_ordering(relation, order_list) } + .to raise_error(ArgumentError, "Column `updated_at` must not allow NULL") + end + end + + context 'for last ordering field' do + let(:relation) { Project.order(namespace_id: :desc) } + + it 'raises error if primary key is not last field' do + expect { described_class.validate_ordering(relation, order_list) } + .to raise_error(ArgumentError, "Last ordering field must be the primary key, `#{relation.primary_key}`") + end + end + end +end diff --git a/spec/lib/gitlab/graphql/connections/keyset/query_builder_spec.rb b/spec/lib/gitlab/graphql/connections/keyset/query_builder_spec.rb new file mode 100644 index 00000000000..59e153d9e07 --- /dev/null +++ b/spec/lib/gitlab/graphql/connections/keyset/query_builder_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Connections::Keyset::QueryBuilder do + context 'when number of ordering fields is 0' do + it 'raises an error' do + expect { described_class.new(Issue.arel_table, [], {}, :after) } + .to raise_error(ArgumentError, 'No ordering scopes have been supplied') + end + end + + describe '#conditions' do + let(:relation) { Issue.order(relative_position: :desc).order(:id) } + let(:order_list) { Gitlab::Graphql::Connections::Keyset::OrderInfo.build_order_list(relation) } + let(:builder) { described_class.new(arel_table, order_list, decoded_cursor, before_or_after) } + let(:before_or_after) { :after } + + context 'when only a single ordering' do + let(:relation) { Issue.order(id: :desc) } + + context 'when the value is nil' do + let(:decoded_cursor) { { 'id' => nil } } + + it 'raises an error' do + expect { builder.conditions } + .to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'Before/after cursor invalid: `nil` was provided as only sortable value') + end + end + + context 'when value is not nil' do + let(:decoded_cursor) { { 'id' => 100 } } + let(:conditions) { builder.conditions } + + context 'when :after' do + it 'generates the correct condition' do + expect(conditions.strip).to eq '("issues"."id" < 100)' + end + end + + context 'when :before' do + let(:before_or_after) { :before } + + it 'generates the correct condition' do + expect(conditions.strip).to eq '("issues"."id" > 100)' + end + end + end + end + + context 'when two orderings' do + let(:decoded_cursor) { { 'relative_position' => 1500, 'id' => 100 } } + + context 'when no values are nil' do + context 'when :after' do + it 'generates the correct condition' do + conditions = builder.conditions + + expect(conditions).to include '"issues"."relative_position" < 1500' + expect(conditions).to include '"issues"."id" > 100' + expect(conditions).to include 'OR ("issues"."relative_position" IS NULL)' + end + end + + context 'when :before' do + let(:before_or_after) { :before } + + it 'generates the correct condition' do + conditions = builder.conditions + + expect(conditions).to include '("issues"."relative_position" > 1500)' + expect(conditions).to include '"issues"."id" < 100' + expect(conditions).to include '"issues"."relative_position" = 1500' + end + end + end + + context 'when first value is nil' do + let(:decoded_cursor) { { 'relative_position' => nil, 'id' => 100 } } + + context 'when :after' do + it 'generates the correct condition' do + conditions = builder.conditions + + expect(conditions).to include '"issues"."relative_position" IS NULL' + expect(conditions).to include '"issues"."id" > 100' + end + end + + context 'when :before' do + let(:before_or_after) { :before } + + it 'generates the correct condition' do + conditions = builder.conditions + + expect(conditions).to include '"issues"."relative_position" IS NULL' + expect(conditions).to include '"issues"."id" < 100' + expect(conditions).to include 'OR ("issues"."relative_position" IS NOT NULL)' + end + end + end + end + end + + def arel_table + Issue.arel_table + end +end diff --git a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb deleted file mode 100644 index 4eb121794e1..00000000000 --- a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb +++ /dev/null @@ -1,117 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Graphql::Connections::KeysetConnection do - let(:nodes) { Project.all.order(id: :asc) } - let(:arguments) { {} } - subject(:connection) do - described_class.new(nodes, arguments, max_page_size: 3) - 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 diff --git a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb deleted file mode 100644 index 136027736c3..00000000000 --- a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Graphql::Loaders::PipelineForShaLoader do - include GraphqlHelpers - - describe '#find_last' do - it 'batch-resolves latest pipeline' do - project = create(:project, :repository) - pipeline1 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha) - pipeline2 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha) - pipeline3 = create(:ci_pipeline, project: project, ref: 'improve/awesome', sha: project.commit('improve/awesome').sha) - - result = batch_sync(max_queries: 1) do - [pipeline1.sha, pipeline3.sha].map { |sha| described_class.new(project, sha).find_last } - end - - expect(result).to contain_exactly(pipeline2, pipeline3) - end - end -end diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb index 53a91a35ec9..570b0cb7401 100644 --- a/spec/lib/gitlab/group_search_results_spec.rb +++ b/spec/lib/gitlab/group_search_results_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GroupSearchResults do diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb index 8e253b51597..ce7f2c4530d 100644 --- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb +++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb @@ -42,7 +42,7 @@ describe Gitlab::HashedStorage::Migrator, :sidekiq, :redis do subject.bulk_migrate(start: ids.min, finish: ids.max) end - it 'has all projects migrated and set as writable' do + it 'has all projects migrated and set as writable', :sidekiq_might_not_need_inline do perform_enqueued_jobs do subject.bulk_migrate(start: ids.min, finish: ids.max) end @@ -79,7 +79,7 @@ describe Gitlab::HashedStorage::Migrator, :sidekiq, :redis do subject.bulk_rollback(start: ids.min, finish: ids.max) end - it 'has all projects rolledback and set as writable' do + it 'has all projects rolledback and set as writable', :sidekiq_might_not_need_inline do perform_enqueued_jobs do subject.bulk_rollback(start: ids.min, finish: ids.max) end @@ -108,7 +108,7 @@ describe Gitlab::HashedStorage::Migrator, :sidekiq, :redis do expect { subject.migrate(project) }.not_to raise_error end - it 'migrates project storage' do + it 'migrates project storage', :sidekiq_might_not_need_inline do perform_enqueued_jobs do subject.migrate(project) end @@ -154,7 +154,7 @@ describe Gitlab::HashedStorage::Migrator, :sidekiq, :redis do expect { subject.rollback(project) }.not_to raise_error end - it 'rolls-back project storage' do + it 'rolls-back project storage', :sidekiq_might_not_need_inline do perform_enqueued_jobs do subject.rollback(project) end diff --git a/spec/lib/gitlab/health_checks/master_check_spec.rb b/spec/lib/gitlab/health_checks/master_check_spec.rb new file mode 100644 index 00000000000..91441a7ddc3 --- /dev/null +++ b/spec/lib/gitlab/health_checks/master_check_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' +require_relative './simple_check_shared' + +describe Gitlab::HealthChecks::MasterCheck do + let(:result_class) { Gitlab::HealthChecks::Result } + + SUCCESS_CODE = 100 + FAILURE_CODE = 101 + + before do + described_class.register_master + end + + after do + described_class.finish_master + end + + describe '#readiness' do + context 'when master is running' do + it 'worker does return success' do + _, child_status = run_worker + + expect(child_status.exitstatus).to eq(SUCCESS_CODE) + end + end + + context 'when master finishes early' do + before do + described_class.send(:close_write) + end + + it 'worker does return failure' do + _, child_status = run_worker + + expect(child_status.exitstatus).to eq(FAILURE_CODE) + end + end + + def run_worker + pid = fork do + described_class.register_worker + + exit(described_class.readiness.success ? SUCCESS_CODE : FAILURE_CODE) + end + + Process.wait2(pid) + end + end +end diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index 4676db6b8d8..5a45d724b83 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Highlight do diff --git a/spec/lib/gitlab/http_io_spec.rb b/spec/lib/gitlab/http_io_spec.rb index 788bddb8f59..f30528916dc 100644 --- a/spec/lib/gitlab/http_io_spec.rb +++ b/spec/lib/gitlab/http_io_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::HttpIO do diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb index d3f9be845dd..192816ad057 100644 --- a/spec/lib/gitlab/http_spec.rb +++ b/spec/lib/gitlab/http_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::HTTP do diff --git a/spec/lib/gitlab/i18n_spec.rb b/spec/lib/gitlab/i18n_spec.rb index 785035d993f..2664423af88 100644 --- a/spec/lib/gitlab/i18n_spec.rb +++ b/spec/lib/gitlab/i18n_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::I18n do diff --git a/spec/lib/gitlab/identifier_spec.rb b/spec/lib/gitlab/identifier_spec.rb index 1e583f4cee2..9c7972d4bde 100644 --- a/spec/lib/gitlab/identifier_spec.rb +++ b/spec/lib/gitlab/identifier_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Identifier do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 4fd61383c6b..8f627fcc24d 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -29,6 +29,9 @@ issues: - prometheus_alerts - prometheus_alert_events - self_managed_prometheus_alert_events +- zoom_meetings +- vulnerability_links +- related_vulnerabilities events: - author - project @@ -119,6 +122,7 @@ merge_requests: - pipelines_for_merge_request - merge_request_assignees - suggestions +- unresolved_notes - assignees - reviews - approval_rules @@ -338,6 +342,7 @@ project: - triggers - pipeline_schedules - environments +- environments_for_dashboard - deployments - project_feature - auto_devops @@ -421,6 +426,12 @@ project: - pages_metadatum - alerts_service - grafana_integration +- remove_source_branch_after_merge +- deleting_user +- upstream_projects +- downstream_projects +- upstream_project_subscriptions +- downstream_project_subscriptions award_emoji: - awardable - user @@ -528,4 +539,6 @@ versions: &version - issue - designs - actions +zoom_meetings: +- issue design_versions: *version 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 934e676d020..b190a1007a0 100644 --- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb +++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb @@ -132,10 +132,6 @@ describe Gitlab::ImportExport::FastHashSerializer do end it 'has no when YML attributes but only the DB column' do - allow_any_instance_of(Ci::Pipeline) - .to receive(:ci_yaml_file) - .and_return(File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))) - expect_any_instance_of(Gitlab::Ci::YamlProcessor).not_to receive(:build_attributes) subject diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb index 71fd5a51c3b..5752fd8fa0d 100644 --- a/spec/lib/gitlab/import_export/fork_spec.rb +++ b/spec/lib/gitlab/import_export/fork_spec.rb @@ -47,7 +47,7 @@ describe 'forked project import' do end end - it 'can access the MR' do + it 'can access the MR', :sidekiq_might_not_need_inline do project.merge_requests.first.fetch_ref! expect(project.repository.ref_exists?('refs/merge-requests/1/head')).to be_truthy 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 6a803c48b34..1a5cb7806a3 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 @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport::GroupProjectObjectBuilder do let(:project) do - create(:project, + create(:project, :repository, :builds_disabled, :issues_disabled, name: 'project', @@ -11,8 +11,8 @@ describe Gitlab::ImportExport::GroupProjectObjectBuilder do end context 'labels' do - it 'finds the right group label' do - group_label = create(:group_label, 'name': 'group label', 'group': project.group) + it 'finds the existing group label' do + group_label = create(:group_label, name: 'group label', group: project.group) expect(described_class.build(Label, 'title' => 'group label', @@ -31,8 +31,8 @@ describe Gitlab::ImportExport::GroupProjectObjectBuilder do end context 'milestones' do - it 'finds the right group milestone' do - milestone = create(:milestone, 'name' => 'group milestone', 'group' => project.group) + it 'finds the existing group milestone' do + milestone = create(:milestone, name: 'group milestone', group: project.group) expect(described_class.build(Milestone, 'title' => 'group milestone', @@ -49,4 +49,30 @@ describe Gitlab::ImportExport::GroupProjectObjectBuilder do expect(milestone.persisted?).to be true end end + + context 'merge_request' do + it 'finds the existing merge_request' do + merge_request = create(:merge_request, title: 'MergeRequest', iid: 7, target_project: project, source_project: project) + expect(described_class.build(MergeRequest, + 'title' => 'MergeRequest', + 'source_project_id' => project.id, + 'target_project_id' => project.id, + 'source_branch' => 'SourceBranch', + 'iid' => 7, + 'target_branch' => 'TargetBranch', + 'author_id' => project.creator.id)).to eq(merge_request) + end + + it 'creates a new merge_request' do + merge_request = described_class.build(MergeRequest, + 'title' => 'MergeRequest', + 'iid' => 8, + 'source_project_id' => project.id, + 'target_project_id' => project.id, + 'source_branch' => 'SourceBranch', + 'target_branch' => 'TargetBranch', + 'author_id' => project.creator.id) + expect(merge_request.persisted?).to be true + end + end end diff --git a/spec/lib/gitlab/import_export/group_tree_saver_spec.rb b/spec/lib/gitlab/import_export/group_tree_saver_spec.rb new file mode 100644 index 00000000000..b856441981a --- /dev/null +++ b/spec/lib/gitlab/import_export/group_tree_saver_spec.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ImportExport::GroupTreeSaver do + describe 'saves the group tree into a json object' do + let(:shared) { Gitlab::ImportExport::Shared.new(group) } + let(:group_tree_saver) { described_class.new(group: group, current_user: user, shared: shared) } + let(:export_path) { "#{Dir.tmpdir}/group_tree_saver_spec" } + let(:user) { create(:user) } + let!(:group) { setup_group } + + before do + group.add_maintainer(user) + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + end + + after do + FileUtils.rm_rf(export_path) + end + + it 'saves group successfully' do + expect(group_tree_saver.save).to be true + end + + context ':export_fast_serialize feature flag checks' do + before do + expect(Gitlab::ImportExport::Reader).to receive(:new).with(shared: shared, config: group_config).and_return(reader) + expect(reader).to receive(:group_tree).and_return(group_tree) + end + + let(:reader) { instance_double('Gitlab::ImportExport::Reader') } + let(:group_config) { Gitlab::ImportExport::Config.new(config: Gitlab::ImportExport.group_config_file).to_h } + let(:group_tree) do + { + include: [{ milestones: { include: [] } }], + preload: { milestones: nil } + } + end + + context 'when :export_fast_serialize feature is enabled' do + let(:serializer) { instance_double(Gitlab::ImportExport::FastHashSerializer) } + + before do + stub_feature_flags(export_fast_serialize: true) + + expect(Gitlab::ImportExport::FastHashSerializer).to receive(:new).with(group, group_tree).and_return(serializer) + end + + it 'uses FastHashSerializer' do + expect(serializer).to receive(:execute) + + group_tree_saver.save + end + end + + context 'when :export_fast_serialize feature is disabled' do + before do + stub_feature_flags(export_fast_serialize: false) + end + + it 'is serialized via built-in `as_json`' do + expect(group).to receive(:as_json).with(group_tree).and_call_original + + group_tree_saver.save + end + end + end + + # It is mostly duplicated in + # `spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb` + # except: + # context 'with description override' do + # context 'group members' do + # ^ These are specific for the groupTreeSaver + context 'JSON' do + let(:saved_group_json) do + group_tree_saver.save + group_json(group_tree_saver.full_path) + end + + it 'saves the correct json' do + expect(saved_group_json).to include({ 'description' => 'description', 'visibility_level' => 20 }) + end + + it 'has milestones' do + expect(saved_group_json['milestones']).not_to be_empty + end + + it 'has labels' do + expect(saved_group_json['labels']).not_to be_empty + end + + it 'has boards' do + expect(saved_group_json['boards']).not_to be_empty + end + + it 'has group members' do + expect(saved_group_json['members']).not_to be_empty + end + + it 'has priorities associated to labels' do + expect(saved_group_json['labels'].first['priorities']).not_to be_empty + end + + it 'has badges' do + expect(saved_group_json['badges']).not_to be_empty + end + + context 'group children' do + let(:children) { group.children } + + it 'exports group children' do + expect(saved_group_json['children'].length).to eq(children.count) + end + + it 'exports group children of children' do + expect(saved_group_json['children'].first['children'].length).to eq(children.first.children.count) + end + end + + context 'group members' do + let(:user2) { create(:user, email: 'group@member.com') } + let(:member_emails) do + saved_group_json['members'].map do |pm| + pm['user']['email'] + end + end + + before do + group.add_developer(user2) + end + + it 'exports group members as group owner' do + group.add_owner(user) + + expect(member_emails).to include('group@member.com') + end + + context 'as admin' do + let(:user) { create(:admin) } + + it 'exports group members as admin' do + expect(member_emails).to include('group@member.com') + end + + it 'exports group members' do + member_types = saved_group_json['members'].map { |pm| pm['source_type'] } + + expect(member_types).to all(eq('Namespace')) + end + end + end + + context 'group attributes' do + it 'does not contain the runners token' do + expect(saved_group_json).not_to include("runners_token" => 'token') + end + end + end + end + + def setup_group + group = create(:group, description: 'description') + sub_group = create(:group, description: 'description', parent: group) + create(:group, description: 'description', parent: sub_group) + create(:milestone, group: group) + create(:group_badge, group: group) + group_label = create(:group_label, group: group) + create(:label_priority, label: group_label, priority: 1) + create(:board, group: group) + create(:group_badge, group: group) + + group + end + + def group_json(filename) + JSON.parse(IO.read(filename)) + end +end diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb index 40a5f2294a2..a6b0dc758cd 100644 --- a/spec/lib/gitlab/import_export/import_export_spec.rb +++ b/spec/lib/gitlab/import_export/import_export_spec.rb @@ -6,17 +6,17 @@ describe Gitlab::ImportExport do let(:project) { create(:project, :public, path: 'project-path', namespace: group) } it 'contains the project path' do - expect(described_class.export_filename(project: project)).to include(project.path) + expect(described_class.export_filename(exportable: project)).to include(project.path) end it 'contains the namespace path' do - expect(described_class.export_filename(project: project)).to include(project.namespace.full_path.tr('/', '_')) + expect(described_class.export_filename(exportable: project)).to include(project.namespace.full_path.tr('/', '_')) end it 'does not go over a certain length' do project.path = 'a' * 100 - expect(described_class.export_filename(project: project).length).to be < 70 + expect(described_class.export_filename(exportable: project).length).to be < 70 end end end 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 ebd2c6089ce..459b1eed1a7 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' include ImportExport::CommonUtil describe Gitlab::ImportExport::ProjectTreeRestorer do + include ImportExport::CommonUtil + let(:shared) { project.import_export_shared } describe 'restore project tree' do @@ -16,7 +18,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do RSpec::Mocks.with_temporary_scope do @project = create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') @shared = @project.import_export_shared - allow(@shared).to receive(:export_path).and_return('spec/fixtures/lib/gitlab/import_export/') + + setup_import_export_config('complex') allow_any_instance_of(Repository).to receive(:fetch_source_branch!).and_return(true) allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false) @@ -207,10 +210,27 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect(@project.project_badges.count).to eq(2) end + it 'has snippets' do + expect(@project.snippets.count).to eq(1) + end + + it 'has award emoji for a snippet' do + award_emoji = @project.snippets.first.award_emoji + + expect(award_emoji.map(&:name)).to contain_exactly('thumbsup', 'coffee') + end + it 'restores the correct service' do expect(CustomIssueTrackerService.first).not_to be_nil end + it 'restores zoom meetings' do + meetings = @project.issues.first.zoom_meetings + + expect(meetings.count).to eq(1) + expect(meetings.first.url).to eq('https://zoom.us/j/123456789') + 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) @@ -250,9 +270,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end it 'has the correct number of pipelines and statuses' do - expect(@project.ci_pipelines.size).to eq(5) + expect(@project.ci_pipelines.size).to eq(6) - @project.ci_pipelines.zip([2, 2, 2, 2, 2]) + @project.ci_pipelines.order(:id).zip([2, 2, 2, 2, 2, 0]) .each do |(pipeline, expected_status_size)| expect(pipeline.statuses.size).to eq(expected_status_size) end @@ -261,7 +281,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'when restoring hierarchy of pipeline, stages and jobs' do it 'restores pipelines' do - expect(Ci::Pipeline.all.count).to be 5 + expect(Ci::Pipeline.all.count).to be 6 end it 'restores pipeline stages' do @@ -307,21 +327,33 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end end - context 'Light JSON' do + context 'project.json file access check' do 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(:restored_project_json) { project_tree_restorer.restore } - before do - allow(shared).to receive(:export_path).and_return('spec/fixtures/lib/gitlab/import_export/') + it 'does not read a symlink' do + Dir.mktmpdir do |tmpdir| + setup_symlink(tmpdir, 'project.json') + allow(shared).to receive(:export_path).and_call_original + + expect(project_tree_restorer.restore).to eq(false) + expect(shared.errors).to include('Incorrect JSON format') + end end + end + + context 'Light JSON' do + 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(:restored_project_json) { project_tree_restorer.restore } context 'with a simple project' do before do - project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.light.json") - - restored_project_json + setup_import_export_config('light') + expect(restored_project_json).to eq(true) end it_behaves_like 'restores project correctly', @@ -332,19 +364,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do first_issue_labels: 1, services: 1 - context 'project.json file access check' do - it 'does not read a symlink' do - Dir.mktmpdir do |tmpdir| - setup_symlink(tmpdir, 'project.json') - allow(shared).to receive(:export_path).and_call_original - - restored_project_json - - expect(shared.errors).to be_empty - end - end - end - context 'when there is an existing build with build token' do before do create(:ci_build, token: 'abcd') @@ -360,6 +379,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end context 'when the project has overridden params in import data' do + before do + setup_import_export_config('light') + end + it 'handles string versions of visibility_level' do # Project needs to be in a group for visibility level comparison # to happen @@ -368,24 +391,21 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do project.create_import_data(data: { override_params: { visibility_level: Gitlab::VisibilityLevel::INTERNAL.to_s } }) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) end it 'overwrites the params stored in the JSON' do project.create_import_data(data: { override_params: { description: "Overridden" } }) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.description).to eq("Overridden") end it 'does not allow setting params that are excluded from import_export settings' do project.create_import_data(data: { override_params: { lfs_enabled: true } }) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.lfs_enabled).to be_falsey end @@ -401,7 +421,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do project.create_import_data(data: { override_params: disabled_access_levels }) - restored_project_json + expect(restored_project_json).to eq(true) aggregate_failures do access_level_keys.each do |key| @@ -422,9 +442,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end before do - project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.group.json") - - restored_project_json + setup_import_export_config('group') + expect(restored_project_json).to eq(true) end it_behaves_like 'restores project correctly', @@ -456,11 +475,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end before do - project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.light.json") + setup_import_export_config('light') end it 'does not import any templated services' do - restored_project_json + expect(restored_project_json).to eq(true) expect(project.services.where(template: true).count).to eq(0) end @@ -470,8 +489,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.labels.count).to eq(1) end @@ -480,8 +498,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.group.milestones.count).to eq(1) expect(project.milestones.count).to eq(0) end @@ -497,13 +514,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do group: create(:group)) end - it 'preserves the project milestone IID' do - project_tree_restorer.instance_variable_set(:@path, "spec/fixtures/lib/gitlab/import_export/project.milestone-iid.json") + before do + setup_import_export_config('milestone-iid') + end + it 'preserves the project milestone IID' do expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.milestones.count).to eq(2) expect(Milestone.find_by_title('Another milestone').iid).to eq(1) expect(Milestone.find_by_title('Group-level milestone').iid).to eq(2) @@ -511,19 +529,21 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end context 'with external authorization classification labels' do + before do + setup_import_export_config('light') + end + it 'converts empty external classification authorization labels to nil' do project.create_import_data(data: { override_params: { external_authorization_classification_label: "" } }) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.external_authorization_classification_label).to be_nil end it 'preserves valid external classification authorization labels' do project.create_import_data(data: { override_params: { external_authorization_classification_label: "foobar" } }) - restored_project_json - + expect(restored_project_json).to eq(true) expect(project.external_authorization_classification_label).to eq("foobar") 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 ff46e062a5d..97d8b155826 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -203,7 +203,6 @@ describe Gitlab::ImportExport::ProjectTreeSaver do end it 'has no when YML attributes but only the DB column' do - allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file).and_return(File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))) expect_any_instance_of(Gitlab::Ci::YamlProcessor).not_to receive(:build_attributes) saved_project_json diff --git a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb index 472bf55d37e..d62f5725f9e 100644 --- a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb +++ b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Gitlab::ImportExport::RelationRenameService do + include ImportExport::CommonUtil + let(:renames) do { 'example_relation1' => 'new_example_relation1', @@ -21,12 +23,12 @@ describe Gitlab::ImportExport::RelationRenameService do context 'when importing' do let(:project_tree_restorer) { Gitlab::ImportExport::ProjectTreeRestorer.new(user: user, shared: shared, project: project) } - let(:import_path) { 'spec/fixtures/lib/gitlab/import_export' } - let(:file_content) { IO.read("#{import_path}/project.json") } - let!(:json_file) { ActiveSupport::JSON.decode(file_content) } + let(:file_content) { IO.read(File.join(shared.export_path, 'project.json')) } + let(:json_file) { ActiveSupport::JSON.decode(file_content) } before do - allow(shared).to receive(:export_path).and_return(import_path) + setup_import_export_config('complex') + allow(ActiveSupport::JSON).to receive(:decode).and_call_original allow(ActiveSupport::JSON).to receive(:decode).with(file_content).and_return(json_file) end @@ -94,15 +96,20 @@ describe Gitlab::ImportExport::RelationRenameService do let(:export_content_path) { project_tree_saver.full_path } let(:export_content_hash) { ActiveSupport::JSON.decode(File.read(export_content_path)) } let(:injected_hash) { renames.values.product([{}]).to_h } + let(:relation_tree_saver) { Gitlab::ImportExport::RelationTreeSaver.new } let(:project_tree_saver) do Gitlab::ImportExport::ProjectTreeSaver.new( project: project, current_user: user, shared: shared) end + before do + allow(project_tree_saver).to receive(:tree_saver).and_return(relation_tree_saver) + end + it 'adds old relationships to the exported file' do # we inject relations with new names that should be rewritten - expect(project_tree_saver).to receive(:serialize_project_tree).and_wrap_original do |method, *args| + expect(relation_tree_saver).to receive(:serialize).and_wrap_original do |method, *args| method.call(*args).merge(injected_hash) end diff --git a/spec/lib/gitlab/import_export/relation_tree_saver_spec.rb b/spec/lib/gitlab/import_export/relation_tree_saver_spec.rb new file mode 100644 index 00000000000..2fc26c0e3d4 --- /dev/null +++ b/spec/lib/gitlab/import_export/relation_tree_saver_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ImportExport::RelationTreeSaver do + let(:exportable) { create(:group) } + let(:relation_tree_saver) { described_class.new } + let(:tree) { {} } + + describe '#serialize' do + context 'when :export_fast_serialize feature is enabled' do + let(:serializer) { instance_double(Gitlab::ImportExport::FastHashSerializer) } + + before do + stub_feature_flags(export_fast_serialize: true) + end + + it 'uses FastHashSerializer' do + expect(Gitlab::ImportExport::FastHashSerializer) + .to receive(:new) + .with(exportable, tree) + .and_return(serializer) + + expect(serializer).to receive(:execute) + + relation_tree_saver.serialize(exportable, tree) + end + end + + context 'when :export_fast_serialize feature is disabled' do + before do + stub_feature_flags(export_fast_serialize: false) + end + + it 'is serialized via built-in `as_json`' do + expect(exportable).to receive(:as_json).with(tree) + + relation_tree_saver.serialize(exportable, tree) + end + end + end +end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 8ae571a69ef..04fe985cdb5 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -185,6 +185,7 @@ MergeRequest: - merge_when_pipeline_succeeds - merge_user_id - merge_commit_sha +- squash_commit_sha - in_progress_merge_commit_sha - lock_version - milestone_id @@ -512,6 +513,7 @@ Project: - request_access_enabled - has_external_wiki - only_allow_merge_if_all_discussions_are_resolved +- remove_source_branch_after_merge - auto_cancel_pending_pipelines - printing_merge_request_link_enabled - resolve_outdated_diff_discussions @@ -537,7 +539,6 @@ Project: - external_webhook_token - pages_https_only - merge_requests_disable_committers_approval -- merge_requests_require_code_owner_approval - require_password_to_approve ProjectTracingSetting: - external_url @@ -752,4 +753,12 @@ DesignManagement::Version: - created_at - sha - issue_id -- user_id +- author_id +ZoomMeeting: +- id +- issue_id +- project_id +- issue_status +- url +- created_at +- updated_at diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb index d185ff2dfcc..aca63953677 100644 --- a/spec/lib/gitlab/import_export/saver_spec.rb +++ b/spec/lib/gitlab/import_export/saver_spec.rb @@ -5,7 +5,7 @@ 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(project: project, shared: 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) diff --git a/spec/lib/gitlab/import_export/shared_spec.rb b/spec/lib/gitlab/import_export/shared_spec.rb index 62669836973..fc011f7e1be 100644 --- a/spec/lib/gitlab/import_export/shared_spec.rb +++ b/spec/lib/gitlab/import_export/shared_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::ImportExport::Shared do context 'with a repository on disk' do let(:project) { create(:project, :repository) } - let(:base_path) { %(/tmp/project_exports/#{project.disk_path}/) } + let(:base_path) { %(/tmp/gitlab_exports/#{project.disk_path}/) } describe '#archive_path' do it 'uses a random hash to avoid conflicts' do diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb index 8060b5d4448..265241dc2af 100644 --- a/spec/lib/gitlab/import_sources_spec.rb +++ b/spec/lib/gitlab/import_sources_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::ImportSources do diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index 2db62ab983a..598336d0b31 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::IncomingEmail do diff --git a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb index 6532579b1c9..7f20ae98b06 100644 --- a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb +++ b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::InsecureKeyFingerprint do diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb new file mode 100644 index 00000000000..c2674638743 --- /dev/null +++ b/spec/lib/gitlab/instrumentation_helper_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'rspec-parameterized' + +describe Gitlab::InstrumentationHelper do + using RSpec::Parameterized::TableSyntax + + describe '.queue_duration_for_job' do + where(:enqueued_at, :created_at, :time_now, :expected_duration) do + "2019-06-01T00:00:00.000+0000" | nil | "2019-06-01T02:00:00.000+0000" | 2.hours.to_f + "2019-06-01T02:00:00.000+0000" | nil | "2019-06-01T02:00:00.001+0000" | 0.001 + "2019-06-01T02:00:00.000+0000" | "2019-05-01T02:00:00.000+0000" | "2019-06-01T02:00:01.000+0000" | 1 + nil | "2019-06-01T02:00:00.000+0000" | "2019-06-01T02:00:00.001+0000" | 0.001 + nil | nil | "2019-06-01T02:00:00.001+0000" | nil + "2019-06-01T02:00:00.000+0200" | nil | "2019-06-01T02:00:00.000-0200" | 4.hours.to_f + 1571825569.998168 | nil | "2019-10-23T12:13:16.000+0200" | 26.001832 + 1571825569 | nil | "2019-10-23T12:13:16.000+0200" | 27 + "invalid_date" | nil | "2019-10-23T12:13:16.000+0200" | nil + "" | nil | "2019-10-23T12:13:16.000+0200" | nil + 0 | nil | "2019-10-23T12:13:16.000+0200" | nil + -1 | nil | "2019-10-23T12:13:16.000+0200" | nil + "2019-06-01T02:00:00.000+0000" | nil | "2019-06-01T00:00:00.000+0000" | 0 + Time.at(1571999233) | nil | "2019-10-25T12:29:16.000+0200" | 123 + end + + with_them do + let(:job) { { 'enqueued_at' => enqueued_at, 'created_at' => created_at } } + + it "returns the correct duration" do + Timecop.freeze(Time.iso8601(time_now)) do + expect(described_class.queue_duration_for_job(job)).to eq(expected_duration) + end + end + end + end +end diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb index 032467b8b4e..7632bc3060a 100644 --- a/spec/lib/gitlab/issuable_metadata_spec.rb +++ b/spec/lib/gitlab/issuable_metadata_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::IssuableMetadata do diff --git a/spec/lib/gitlab/issuable_sorter_spec.rb b/spec/lib/gitlab/issuable_sorter_spec.rb index 5bd76bc6081..486e9539b92 100644 --- a/spec/lib/gitlab/issuable_sorter_spec.rb +++ b/spec/lib/gitlab/issuable_sorter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::IssuableSorter do diff --git a/spec/lib/gitlab/issuables_count_for_state_spec.rb b/spec/lib/gitlab/issuables_count_for_state_spec.rb index c262fdfcb61..9380aa53470 100644 --- a/spec/lib/gitlab/issuables_count_for_state_spec.rb +++ b/spec/lib/gitlab/issuables_count_for_state_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::IssuablesCountForState do diff --git a/spec/lib/gitlab/job_waiter_spec.rb b/spec/lib/gitlab/job_waiter_spec.rb index b0b4fdc09bc..efa7fd4b975 100644 --- a/spec/lib/gitlab/job_waiter_spec.rb +++ b/spec/lib/gitlab/job_waiter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::JobWaiter do diff --git a/spec/lib/gitlab/json_logger_spec.rb b/spec/lib/gitlab/json_logger_spec.rb index 3d4f9b5db86..5d544198c40 100644 --- a/spec/lib/gitlab/json_logger_spec.rb +++ b/spec/lib/gitlab/json_logger_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::JsonLogger do diff --git a/spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb b/spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb new file mode 100644 index 00000000000..f701643860a --- /dev/null +++ b/spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Kubernetes::ConfigMaps::AwsNodeAuth do + describe '#generate' do + let(:role) { 'arn:aws:iam::123456789012:role/node-instance-role' } + + let(:name) { 'aws-auth' } + let(:namespace) { 'kube-system' } + let(:role_config) do + [{ + 'rolearn' => role, + 'username' => 'system:node:{{EC2PrivateDNSName}}', + 'groups' => [ + 'system:bootstrappers', + 'system:nodes' + ] + }] + end + + subject { described_class.new(role).generate } + + it 'builds a Kubeclient Resource' do + expect(subject).to be_a(Kubeclient::Resource) + + expect(subject.metadata.name).to eq(name) + expect(subject.metadata.namespace).to eq(namespace) + + expect(YAML.safe_load(subject.data.mapRoles)).to eq(role_config) + end + end +end diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb index 9eb3322f1a6..e5a361bdab3 100644 --- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb @@ -86,33 +86,6 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do end end - context 'when there is no repository' do - let(:repository) { nil } - - 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_install_command} - EOS - end - - let(:helm_install_command) 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 there is a pre-install script' do let(:preinstall) { ['/bin/date', '/bin/true'] } diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb index 64cadcc011c..e1b4bd0b664 100644 --- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::Kubernetes::Helm::Pod do it 'generates the appropriate specifications for the container' do container = subject.generate.spec.containers.first expect(container.name).to eq('helm') - expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.14.3-kube-1.11.10') + expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.16.1-kube-1.13.12') expect(container.env.count).to eq(3) expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT]) expect(container.command).to match_array(["/bin/sh"]) diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb index a7ea942960b..31bfd20449d 100644 --- a/spec/lib/gitlab/kubernetes_spec.rb +++ b/spec/lib/gitlab/kubernetes_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Kubernetes do diff --git a/spec/lib/gitlab/language_detection_spec.rb b/spec/lib/gitlab/language_detection_spec.rb index 9636fbd401b..f558ce0d527 100644 --- a/spec/lib/gitlab/language_detection_spec.rb +++ b/spec/lib/gitlab/language_detection_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::LanguageDetection do diff --git a/spec/lib/gitlab/lazy_spec.rb b/spec/lib/gitlab/lazy_spec.rb index 37a3ac74316..19758a18589 100644 --- a/spec/lib/gitlab/lazy_spec.rb +++ b/spec/lib/gitlab/lazy_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Lazy do diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb index af5df1fab43..697bedf7362 100644 --- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb @@ -136,7 +136,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi describe '.find_all_paths' do let(:all_dashboard_paths) { described_class.find_all_paths(project) } - let(:system_dashboard) { { path: system_dashboard_path, display_name: 'Default', default: true } } + let(:system_dashboard) { { path: system_dashboard_path, display_name: 'Default', default: true, system_dashboard: true } } it 'includes only the system dashboard by default' do expect(all_dashboard_paths).to eq([system_dashboard]) @@ -147,7 +147,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi let(:project) { project_with_dashboard(dashboard_path) } it 'includes system and project dashboards' do - project_dashboard = { path: dashboard_path, display_name: 'test.yml', default: false } + project_dashboard = { path: dashboard_path, display_name: 'test.yml', default: false, system_dashboard: false } expect(all_dashboard_paths).to contain_exactly(system_dashboard, project_dashboard) end diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb index e2ce1869810..4fa136bc405 100644 --- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb @@ -25,6 +25,14 @@ describe Gitlab::Metrics::Dashboard::Processor do end end + context 'when the dashboard is not present' do + let(:dashboard_yml) { nil } + + it 'returns nil' do + expect(dashboard).to be_nil + end + end + context 'when dashboard config corresponds to common metrics' do let!(:common_metric) { create(:prometheus_metric, :common, identifier: 'metric_a1') } diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb index 095d0a2df78..0d4562f78f1 100644 --- a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb @@ -75,6 +75,17 @@ describe Gitlab::Metrics::Dashboard::ServiceSelector do it { is_expected.to be Metrics::Dashboard::CustomMetricEmbedService } end + + context 'with a grafana link' do + let(:arguments) do + { + embedded: true, + grafana_url: 'https://grafana.example.com' + } + end + + it { is_expected.to be Metrics::Dashboard::GrafanaMetricEmbedService } + end end end end diff --git a/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb new file mode 100644 index 00000000000..5c2ec6dae6b --- /dev/null +++ b/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter do + include GrafanaApiHelpers + + let_it_be(:namespace) { create(:namespace, name: 'foo') } + let_it_be(:project) { create(:project, namespace: namespace, name: 'bar') } + + describe '#transform!' do + let(:grafana_dashboard) { JSON.parse(fixture_file('grafana/simplified_dashboard_response.json'), symbolize_names: true) } + let(:datasource) { JSON.parse(fixture_file('grafana/datasource_response.json'), symbolize_names: true) } + + let(:dashboard) { described_class.new(project, {}, params).transform! } + + let(:params) do + { + grafana_dashboard: grafana_dashboard, + datasource: datasource, + grafana_url: valid_grafana_dashboard_link('https://grafana.example.com') + } + end + + context 'when the query and resources are configured correctly' do + let(:expected_dashboard) { JSON.parse(fixture_file('grafana/expected_grafana_embed.json'), symbolize_names: true) } + + it 'generates a gitlab-yml formatted dashboard' do + expect(dashboard).to eq(expected_dashboard) + end + end + + context 'when the inputs are invalid' do + shared_examples_for 'processing error' do + it 'raises a processing error' do + expect { dashboard } + .to raise_error(Gitlab::Metrics::Dashboard::Stages::InputFormatValidator::DashboardProcessingError) + end + end + + context 'when the datasource is not proxyable' do + before do + params[:datasource][:access] = 'not-proxy' + end + + it_behaves_like 'processing error' + end + + context 'when query param "panelId" is not specified' do + before do + params[:grafana_url].gsub!('panelId=8', '') + end + + it_behaves_like 'processing error' + end + + context 'when query param "from" is not specified' do + before do + params[:grafana_url].gsub!('from=1570397739557', '') + end + + it_behaves_like 'processing error' + end + + context 'when query param "to" is not specified' do + before do + params[:grafana_url].gsub!('to=1570484139557', '') + end + + it_behaves_like 'processing error' + end + + context 'when the panel is not a graph' do + before do + params[:grafana_dashboard][:dashboard][:panels][0][:type] = 'singlestat' + end + + it_behaves_like 'processing error' + end + + context 'when the panel is not a line graph' do + before do + params[:grafana_dashboard][:dashboard][:panels][0][:lines] = false + end + + it_behaves_like 'processing error' + end + + context 'when the query dashboard includes undefined variables' do + before do + params[:grafana_url].gsub!('&var-instance=localhost:9121', '') + end + + it_behaves_like 'processing error' + end + + context 'when the expression contains unsupported global variables' do + before do + params[:grafana_dashboard][:dashboard][:panels][0][:targets][0][:expr] = 'sum(important_metric[$__interval_ms])' + end + + it_behaves_like 'processing error' + end + end + end +end diff --git a/spec/lib/gitlab/metrics/dashboard/url_spec.rb b/spec/lib/gitlab/metrics/dashboard/url_spec.rb index e0dc6d98efc..daaf66cba46 100644 --- a/spec/lib/gitlab/metrics/dashboard/url_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/url_spec.rb @@ -3,13 +3,41 @@ require 'spec_helper' describe Gitlab::Metrics::Dashboard::Url do - describe '#regex' do - it 'returns a regular expression' do - expect(described_class.regex).to be_a Regexp - end + shared_examples_for 'a regex which matches the expected url' do + it { is_expected.to be_a Regexp } it 'matches a metrics dashboard link with named params' do - url = Gitlab::Routing.url_helpers.metrics_namespace_project_environment_url( + expect(subject).to match url + + subject.match(url) do |m| + expect(m.named_captures).to eq expected_params + end + end + end + + shared_examples_for 'does not match non-matching urls' do + it 'does not match other gitlab urls that contain the term metrics' do + url = Gitlab::Routing.url_helpers.active_common_namespace_project_prometheus_metrics_url('foo', 'bar', :json) + + expect(subject).not_to match url + end + + it 'does not match other gitlab urls' do + url = Gitlab.config.gitlab.url + + expect(subject).not_to match url + end + + it 'does not match non-gitlab urls' do + url = 'https://www.super_awesome_site.com/' + + expect(subject).not_to match url + end + end + + describe '#regex' do + let(:url) do + Gitlab::Routing.url_helpers.metrics_namespace_project_environment_url( 'foo', 'bar', 1, @@ -18,8 +46,10 @@ describe Gitlab::Metrics::Dashboard::Url do group: 'awesome group', anchor: 'title' ) + end - expected_params = { + let(:expected_params) do + { 'url' => url, 'namespace' => 'foo', 'project' => 'bar', @@ -27,31 +57,40 @@ describe Gitlab::Metrics::Dashboard::Url do 'query' => '?dashboard=config%2Fprometheus%2Fcommon_metrics.yml&group=awesome+group&start=2019-08-02T05%3A43%3A09.000Z', 'anchor' => '#title' } - - expect(described_class.regex).to match url - - described_class.regex.match(url) do |m| - expect(m.named_captures).to eq expected_params - end end - it 'does not match other gitlab urls that contain the term metrics' do - url = Gitlab::Routing.url_helpers.active_common_namespace_project_prometheus_metrics_url('foo', 'bar', :json) + subject { described_class.regex } - expect(described_class.regex).not_to match url - end + it_behaves_like 'a regex which matches the expected url' + it_behaves_like 'does not match non-matching urls' + end - it 'does not match other gitlab urls' do - url = Gitlab.config.gitlab.url + describe '#grafana_regex' do + let(:url) do + Gitlab::Routing.url_helpers.namespace_project_grafana_api_metrics_dashboard_url( + 'foo', + 'bar', + start: '2019-08-02T05:43:09.000Z', + dashboard: 'config/prometheus/common_metrics.yml', + group: 'awesome group', + anchor: 'title' + ) + end - expect(described_class.regex).not_to match url + let(:expected_params) do + { + 'url' => url, + 'namespace' => 'foo', + 'project' => 'bar', + 'query' => '?dashboard=config%2Fprometheus%2Fcommon_metrics.yml&group=awesome+group&start=2019-08-02T05%3A43%3A09.000Z', + 'anchor' => '#title' + } end - it 'does not match non-gitlab urls' do - url = 'https://www.super_awesome_site.com/' + subject { described_class.grafana_regex } - expect(described_class.regex).not_to match url - end + it_behaves_like 'a regex which matches the expected url' + it_behaves_like 'does not match non-matching urls' end describe '#build_dashboard_url' do diff --git a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb index 99349934e63..f22993cf057 100644 --- a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb +++ b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb @@ -4,61 +4,41 @@ require 'spec_helper' describe Gitlab::Metrics::Exporter::WebExporter do let(:exporter) { described_class.new } - - context 'when blackout seconds is used' do - let(:blackout_seconds) { 0 } - let(:readiness_probe) { exporter.send(:readiness_probe).execute } - - before do - stub_config( - monitoring: { - web_exporter: { - enabled: true, - port: 0, - address: '127.0.0.1', - blackout_seconds: blackout_seconds - } + let(:readiness_probe) { exporter.send(:readiness_probe).execute } + + before do + stub_config( + monitoring: { + web_exporter: { + enabled: true, + port: 0, + address: '127.0.0.1' } - ) - - exporter.start - end - - after do - exporter.stop - end + } + ) - context 'when running server' do - it 'readiness probe returns succesful status' do - expect(readiness_probe.http_status).to eq(200) - expect(readiness_probe.json).to include(status: 'ok') - expect(readiness_probe.json).to include('web_exporter' => [{ 'status': 'ok' }]) - end - end - - context 'when blackout seconds is 10s' do - let(:blackout_seconds) { 10 } + exporter.start + end - it 'readiness probe returns a failure status' do - # during sleep we check the status of readiness probe - expect(exporter).to receive(:sleep).with(10) do - expect(readiness_probe.http_status).to eq(503) - expect(readiness_probe.json).to include(status: 'failed') - expect(readiness_probe.json).to include('web_exporter' => [{ 'status': 'failed' }]) - end + after do + exporter.stop + end - exporter.stop - end + context 'when running server' do + it 'readiness probe returns succesful status' do + expect(readiness_probe.http_status).to eq(200) + expect(readiness_probe.json).to include(status: 'ok') + expect(readiness_probe.json).to include('web_exporter' => [{ 'status': 'ok' }]) end + end - context 'when blackout is disabled' do - let(:blackout_seconds) { 0 } - - it 'readiness probe returns a failure status' do - expect(exporter).not_to receive(:sleep) + describe '#mark_as_not_running!' do + it 'readiness probe returns a failure status' do + exporter.mark_as_not_running! - exporter.stop - end + expect(readiness_probe.http_status).to eq(503) + expect(readiness_probe.json).to include(status: 'failed') + expect(readiness_probe.json).to include('web_exporter' => [{ 'status': 'failed' }]) end end end diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb index f48cd096a98..335670278c4 100644 --- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb @@ -31,7 +31,7 @@ describe Gitlab::Metrics::RequestsRackMiddleware do end it 'measures execution time' do - expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ status: 200, method: 'get' }, a_positive_execution_time) + expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ status: '200', method: 'get' }, a_positive_execution_time) Timecop.scale(3600) { subject.call(env) } end @@ -69,7 +69,7 @@ describe Gitlab::Metrics::RequestsRackMiddleware do expected_labels = [] described_class::HTTP_METHODS.each do |method, statuses| statuses.each do |status| - expected_labels << { method: method, status: status.to_i } + expected_labels << { method: method, status: status.to_s } end end diff --git a/spec/lib/gitlab/pagination/offset_pagination_spec.rb b/spec/lib/gitlab/pagination/offset_pagination_spec.rb new file mode 100644 index 00000000000..9c7dd385726 --- /dev/null +++ b/spec/lib/gitlab/pagination/offset_pagination_spec.rb @@ -0,0 +1,215 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Pagination::OffsetPagination do + let(:resource) { Project.all } + let(:custom_port) { 8080 } + let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:#{custom_port}/api/v4/projects" } + + before do + stub_config_setting(port: custom_port) + end + + let(:request_context) { double("request_context") } + + subject do + described_class.new(request_context) + end + + describe '#paginate' do + let(:value) { spy('return value') } + let(:base_query) { { foo: 'bar', bar: 'baz' } } + let(:query) { base_query } + + before do + allow(request_context).to receive(:header).and_return(value) + allow(request_context).to receive(:params).and_return(query) + allow(request_context).to receive(:request).and_return(double(url: "#{incoming_api_projects_url}?#{query.to_query}")) + end + + context 'when resource can be paginated' do + before do + create_list(:project, 3) + end + + describe 'first page' do + shared_examples 'response with pagination headers' do + it 'adds appropriate headers' do + expect_header('X-Total', '3') + expect_header('X-Total-Pages', '2') + expect_header('X-Per-Page', '2') + expect_header('X-Page', '1') + expect_header('X-Next-Page', '2') + expect_header('X-Prev-Page', '') + + expect_header('Link', anything) do |_key, val| + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) + expect(val).not_to include('rel="prev"') + end + + subject.paginate(resource) + end + end + + shared_examples 'paginated response' do + it 'returns appropriate amount of resources' do + expect(subject.paginate(resource).count).to eq 2 + end + + it 'executes only one SELECT COUNT query' do + expect { subject.paginate(resource) }.to make_queries_matching(/SELECT COUNT/, 1) + end + end + + let(:query) { base_query.merge(page: 1, per_page: 2) } + + context 'when the api_kaminari_count_with_limit feature flag is unset' do + it_behaves_like 'paginated response' + it_behaves_like 'response with pagination headers' + end + + context 'when the api_kaminari_count_with_limit feature flag is disabled' do + before do + stub_feature_flags(api_kaminari_count_with_limit: false) + end + + it_behaves_like 'paginated response' + it_behaves_like 'response with pagination headers' + end + + context 'when the api_kaminari_count_with_limit feature flag is enabled' do + before do + stub_feature_flags(api_kaminari_count_with_limit: true) + end + + context 'when resources count is less than MAX_COUNT_LIMIT' do + before do + stub_const("::Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT", 4) + end + + it_behaves_like 'paginated response' + it_behaves_like 'response with pagination headers' + end + + context 'when resources count is more than MAX_COUNT_LIMIT' do + before do + stub_const("::Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT", 2) + end + + it_behaves_like 'paginated response' + + it 'does not return the X-Total and X-Total-Pages headers' do + expect_no_header('X-Total') + expect_no_header('X-Total-Pages') + expect_header('X-Per-Page', '2') + expect_header('X-Page', '1') + expect_header('X-Next-Page', '2') + expect_header('X-Prev-Page', '') + + expect_header('Link', anything) do |_key, val| + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) + expect(val).not_to include('rel="last"') + expect(val).not_to include('rel="prev"') + end + + subject.paginate(resource) + end + end + end + end + + describe 'second page' do + let(:query) { base_query.merge(page: 2, per_page: 2) } + + it 'returns appropriate amount of resources' do + expect(subject.paginate(resource).count).to eq 1 + end + + it 'adds appropriate headers' do + expect_header('X-Total', '3') + expect_header('X-Total-Pages', '2') + expect_header('X-Per-Page', '2') + expect_header('X-Page', '2') + expect_header('X-Next-Page', '') + expect_header('X-Prev-Page', '1') + + expect_header('Link', anything) do |_key, val| + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev")) + expect(val).not_to include('rel="next"') + end + + subject.paginate(resource) + end + end + + context 'if order' do + it 'is not present it adds default order(:id) if no order is present' do + resource.order_values = [] + + paginated_relation = subject.paginate(resource) + + expect(resource.order_values).to be_empty + expect(paginated_relation.order_values).to be_present + expect(paginated_relation.order_values.first).to be_ascending + expect(paginated_relation.order_values.first.expr.name).to eq 'id' + end + + it 'is present it does not add anything' do + paginated_relation = subject.paginate(resource.order(created_at: :desc)) + + expect(paginated_relation.order_values).to be_present + expect(paginated_relation.order_values.first).to be_descending + expect(paginated_relation.order_values.first.expr.name).to eq 'created_at' + end + end + end + + context 'when resource empty' do + describe 'first page' do + let(:query) { base_query.merge(page: 1, per_page: 2) } + + it 'returns appropriate amount of resources' do + expect(subject.paginate(resource).count).to eq 0 + end + + it 'adds appropriate headers' do + expect_header('X-Total', '0') + expect_header('X-Total-Pages', '1') + expect_header('X-Per-Page', '2') + expect_header('X-Page', '1') + expect_header('X-Next-Page', '') + expect_header('X-Prev-Page', '') + + expect_header('Link', anything) do |_key, val| + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last")) + expect(val).not_to include('rel="prev"') + expect(val).not_to include('rel="next"') + expect(val).not_to include('page=0') + end + + subject.paginate(resource) + end + end + end + end + + def expect_header(*args, &block) + expect(subject).to receive(:header).with(*args, &block) + end + + def expect_no_header(*args, &block) + expect(subject).not_to receive(:header).with(*args) + end + + def expect_message(method) + expect(subject).to receive(method) + .at_least(:once).and_return(value) + end +end diff --git a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb index e9455b866ac..fd17284eea2 100644 --- a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb +++ b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::PhabricatorImport::ProjectCreator do subject(:creator) { described_class.new(user, params) } describe '#execute' do - it 'creates a project correctly and schedule an import' do + it 'creates a project correctly and schedule an import', :sidekiq_might_not_need_inline do expect_next_instance_of(Gitlab::PhabricatorImport::Importer) do |importer| expect(importer).to receive(:execute) end diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb index 82ccb42f8a6..6e5c36172e2 100644 --- a/spec/lib/gitlab/project_authorizations_spec.rb +++ b/spec/lib/gitlab/project_authorizations_spec.rb @@ -3,48 +3,55 @@ require 'spec_helper' describe Gitlab::ProjectAuthorizations do - let(:group) { create(:group) } - let!(:owned_project) { create(:project) } - let!(:other_project) { create(:project) } - let!(:group_project) { create(:project, namespace: group) } - - let(:user) { owned_project.namespace.owner } - def map_access_levels(rows) rows.each_with_object({}) do |row, hash| hash[row.project_id] = row.access_level end end - before do - other_project.add_reporter(user) - group.add_developer(user) - end - - let(:authorizations) do + subject(:authorizations) do described_class.new(user).calculate end - it 'returns the correct number of authorizations' do - expect(authorizations.length).to eq(3) - end + context 'user added to group and project' do + let(:group) { create(:group) } + let!(:other_project) { create(:project) } + let!(:group_project) { create(:project, namespace: group) } + let!(:owned_project) { create(:project) } + let(:user) { owned_project.namespace.owner } - it 'includes the correct projects' do - expect(authorizations.pluck(:project_id)) - .to include(owned_project.id, other_project.id, group_project.id) - end + before do + other_project.add_reporter(user) + group.add_developer(user) + end + + it 'returns the correct number of authorizations' do + expect(authorizations.length).to eq(3) + end - it 'includes the correct access levels' do - mapping = map_access_levels(authorizations) + it 'includes the correct projects' do + expect(authorizations.pluck(:project_id)) + .to include(owned_project.id, other_project.id, group_project.id) + end + + it 'includes the correct access levels' do + mapping = map_access_levels(authorizations) - expect(mapping[owned_project.id]).to eq(Gitlab::Access::MAINTAINER) - expect(mapping[other_project.id]).to eq(Gitlab::Access::REPORTER) - expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER) + expect(mapping[owned_project.id]).to eq(Gitlab::Access::MAINTAINER) + expect(mapping[other_project.id]).to eq(Gitlab::Access::REPORTER) + expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER) + end end context 'with nested groups' do + let(:group) { create(:group) } let!(:nested_group) { create(:group, parent: group) } let!(:nested_project) { create(:project, namespace: nested_group) } + let(:user) { create(:user) } + + before do + group.add_developer(user) + end it 'includes nested groups' do expect(authorizations.pluck(:project_id)).to include(nested_project.id) @@ -64,4 +71,114 @@ describe Gitlab::ProjectAuthorizations do expect(mapping[nested_project.id]).to eq(Gitlab::Access::MAINTAINER) end end + + context 'with shared groups' do + let(:parent_group_user) { create(:user) } + let(:group_user) { create(:user) } + let(:child_group_user) { create(:user) } + + let_it_be(:group_parent) { create(:group, :private) } + let_it_be(:group) { create(:group, :private, parent: group_parent) } + let_it_be(:group_child) { create(:group, :private, parent: group) } + + let_it_be(:shared_group_parent) { create(:group, :private) } + let_it_be(:shared_group) { create(:group, :private, parent: shared_group_parent) } + let_it_be(:shared_group_child) { create(:group, :private, parent: shared_group) } + + let_it_be(:project_parent) { create(:project, group: shared_group_parent) } + let_it_be(:project) { create(:project, group: shared_group) } + let_it_be(:project_child) { create(:project, group: shared_group_child) } + + before do + group_parent.add_owner(parent_group_user) + group.add_owner(group_user) + group_child.add_owner(child_group_user) + + create(:group_group_link, shared_group: shared_group, shared_with_group: group) + end + + context 'when feature flag share_group_with_group is enabled' do + before do + stub_feature_flags(share_group_with_group: true) + end + + context 'group user' do + let(:user) { group_user } + + it 'creates proper authorizations' do + mapping = map_access_levels(authorizations) + + expect(mapping[project_parent.id]).to be_nil + expect(mapping[project.id]).to eq(Gitlab::Access::DEVELOPER) + expect(mapping[project_child.id]).to eq(Gitlab::Access::DEVELOPER) + end + end + + context 'parent group user' do + let(:user) { parent_group_user } + + it 'creates proper authorizations' do + mapping = map_access_levels(authorizations) + + expect(mapping[project_parent.id]).to be_nil + expect(mapping[project.id]).to be_nil + expect(mapping[project_child.id]).to be_nil + end + end + + context 'child group user' do + let(:user) { child_group_user } + + it 'creates proper authorizations' do + mapping = map_access_levels(authorizations) + + expect(mapping[project_parent.id]).to be_nil + expect(mapping[project.id]).to be_nil + expect(mapping[project_child.id]).to be_nil + end + end + end + + context 'when feature flag share_group_with_group is disabled' do + before do + stub_feature_flags(share_group_with_group: false) + end + + context 'group user' do + let(:user) { group_user } + + it 'creates proper authorizations' do + mapping = map_access_levels(authorizations) + + expect(mapping[project_parent.id]).to be_nil + expect(mapping[project.id]).to be_nil + expect(mapping[project_child.id]).to be_nil + end + end + + context 'parent group user' do + let(:user) { parent_group_user } + + it 'creates proper authorizations' do + mapping = map_access_levels(authorizations) + + expect(mapping[project_parent.id]).to be_nil + expect(mapping[project.id]).to be_nil + expect(mapping[project_child.id]).to be_nil + end + end + + context 'child group user' do + let(:user) { child_group_user } + + it 'creates proper authorizations' do + mapping = map_access_levels(authorizations) + + expect(mapping[project_parent.id]).to be_nil + expect(mapping[project.id]).to be_nil + expect(mapping[project_child.id]).to be_nil + end + end + end + end end diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index d6e50c672e6..99078f19361 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -79,20 +79,20 @@ describe Gitlab::ProjectSearchResults do end it 'finds by name' do - expect(results.map(&:filename)).to include(expected_file_by_name) + expect(results.map(&:path)).to include(expected_file_by_path) end - it "loads all blobs for filename matches in single batch" do + it "loads all blobs for path matches in single batch" do expect(Gitlab::Git::Blob).to receive(:batch).once.and_call_original expected = project.repository.search_files_by_name(query, 'master') - expect(results.map(&:filename)).to include(*expected) + expect(results.map(&:path)).to include(*expected) end it 'finds by content' do - blob = results.select { |result| result.filename == expected_file_by_content }.flatten.last + blob = results.select { |result| result.path == expected_file_by_content }.flatten.last - expect(blob.filename).to eq(expected_file_by_content) + expect(blob.path).to eq(expected_file_by_content) end end @@ -146,7 +146,7 @@ describe Gitlab::ProjectSearchResults do let(:blob_type) { 'blobs' } let(:disabled_project) { create(:project, :public, :repository, :repository_disabled) } let(:private_project) { create(:project, :public, :repository, :repository_private) } - let(:expected_file_by_name) { 'files/images/wm.svg' } + let(:expected_file_by_path) { 'files/images/wm.svg' } let(:expected_file_by_content) { 'CHANGELOG' } end @@ -169,7 +169,7 @@ describe Gitlab::ProjectSearchResults do let(:blob_type) { 'wiki_blobs' } let(:disabled_project) { create(:project, :public, :wiki_repo, :wiki_disabled) } let(:private_project) { create(:project, :public, :wiki_repo, :wiki_private) } - let(:expected_file_by_name) { 'Files/Title.md' } + let(:expected_file_by_path) { 'Files/Title.md' } let(:expected_file_by_content) { 'CHANGELOG.md' } end diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb index 83acd979a80..5559b1e4291 100644 --- a/spec/lib/gitlab/project_template_spec.rb +++ b/spec/lib/gitlab/project_template_spec.rb @@ -22,7 +22,8 @@ describe Gitlab::ProjectTemplate do described_class.new('nfjekyll', 'Netlify/Jekyll', _('A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfjekyll'), 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('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('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') ] expect(described_class.all).to be_an(Array) diff --git a/spec/lib/gitlab/prometheus/internal_spec.rb b/spec/lib/gitlab/prometheus/internal_spec.rb new file mode 100644 index 00000000000..884bdcb4e9b --- /dev/null +++ b/spec/lib/gitlab/prometheus/internal_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Prometheus::Internal do + let(:listen_address) { 'localhost:9090' } + + let(:prometheus_settings) do + { + enable: true, + listen_address: listen_address + } + end + + before do + stub_config(prometheus: prometheus_settings) + end + + describe '.uri' do + shared_examples 'returns valid uri' do |uri_string| + it do + expect(described_class.uri).to eq(uri_string) + expect { Addressable::URI.parse(described_class.uri) }.not_to raise_error + end + end + + it_behaves_like 'returns valid uri', 'http://localhost:9090' + + context 'with non default prometheus address' do + let(:listen_address) { 'https://localhost:9090' } + + it_behaves_like 'returns valid uri', 'https://localhost:9090' + + context 'with :9090 symbol' do + let(:listen_address) { :':9090' } + + it_behaves_like 'returns valid uri', 'http://localhost:9090' + end + + context 'with 0.0.0.0:9090' do + let(:listen_address) { '0.0.0.0:9090' } + + it_behaves_like 'returns valid uri', 'http://localhost:9090' + end + end + + context 'when listen_address is nil' do + let(:listen_address) { nil } + + it 'does not fail' do + expect(described_class.uri).to eq(nil) + end + end + + context 'when prometheus listen address is blank in gitlab.yml' do + let(:listen_address) { '' } + + it 'does not configure prometheus' do + expect(described_class.uri).to eq(nil) + end + end + end + + describe 'prometheus_enabled?' do + it 'returns correct value' do + expect(described_class.prometheus_enabled?).to eq(true) + end + + context 'when prometheus setting is disabled in gitlab.yml' do + let(:prometheus_settings) do + { + enable: false, + listen_address: listen_address + } + end + + it 'returns correct value' do + expect(described_class.prometheus_enabled?).to eq(false) + end + end + + context 'when prometheus setting is not present in gitlab.yml' do + before do + allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting) + end + + it 'does not fail' do + expect(described_class.prometheus_enabled?).to eq(false) + end + end + end + + describe '.listen_address' do + it 'returns correct value' do + expect(described_class.listen_address).to eq(listen_address) + end + + context 'when prometheus setting is not present in gitlab.yml' do + before do + allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting) + end + + it 'does not fail' do + expect(described_class.listen_address).to eq(nil) + end + end + end +end 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 7f6283715f2..6361893c53c 100644 --- a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb @@ -13,14 +13,19 @@ describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do context 'verify queries' do before do - allow(PrometheusMetric).to receive(:find_by_identifier).and_return(create(:prometheus_metric, query: prometheus_istio_query('test-name', 'test-ns'))) - allow(client).to receive(:query_range) + 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))') end it 'has the query, but no data' do - results = subject.query(serverless_func.id) + expect(client).to receive(:query_range).with( + 'sum(ceil(rate(istio_requests_total{destination_service_namespace="test-ns", destination_app=~"test-name.*"}[1m])*60))', + hash_including(:start, :stop) + ) - expect(results.queries[0][:query_range]).to eql('floor(sum(rate(istio_revision_request_count{destination_configuration="test-name", destination_namespace="test-ns"}[1m])*30))') + subject.query(serverless_func.id) end end end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index b557baed258..1397add9f5a 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -66,6 +66,15 @@ describe Gitlab::Regex do end describe '.aws_account_id_regex' do + subject { described_class.aws_account_id_regex } + + it { is_expected.to match('123456789012') } + it { is_expected.not_to match('12345678901') } + it { is_expected.not_to match('1234567890123') } + it { is_expected.not_to match('12345678901a') } + end + + describe '.aws_arn_regex' do subject { described_class.aws_arn_regex } it { is_expected.to match('arn:aws:iam::123456789012:role/role-name') } @@ -75,4 +84,14 @@ describe Gitlab::Regex do it { is_expected.not_to match('123456789012') } it { is_expected.not_to match('role/role-name') } end + + describe '.utc_date_regex' do + subject { described_class.utc_date_regex } + + it { is_expected.to match('2019-10-20') } + it { is_expected.to match('1990-01-01') } + it { is_expected.not_to match('11-1234-90') } + it { is_expected.not_to match('aa-1234-cc') } + it { is_expected.not_to match('9/9/2018') } + end end diff --git a/spec/lib/gitlab/search/found_blob_spec.rb b/spec/lib/gitlab/search/found_blob_spec.rb index a575f6e2f11..07842faa638 100644 --- a/spec/lib/gitlab/search/found_blob_spec.rb +++ b/spec/lib/gitlab/search/found_blob_spec.rb @@ -15,7 +15,6 @@ describe Gitlab::Search::FoundBlob do is_expected.to be_an described_class expect(subject.id).to be_nil expect(subject.path).to eq('CHANGELOG') - expect(subject.filename).to eq('CHANGELOG') expect(subject.basename).to eq('CHANGELOG') expect(subject.ref).to eq('master') expect(subject.startline).to eq(188) @@ -25,12 +24,12 @@ describe Gitlab::Search::FoundBlob do it 'does not parse content if not needed' do expect(subject).not_to receive(:parse_search_result) expect(subject.project_id).to eq(project.id) - expect(subject.binary_filename).to eq('CHANGELOG') + expect(subject.binary_path).to eq('CHANGELOG') end it 'parses content only once when needed' do expect(subject).to receive(:parse_search_result).once.and_call_original - expect(subject.filename).to eq('CHANGELOG') + expect(subject.path).to eq('CHANGELOG') expect(subject.startline).to eq(188) end @@ -38,7 +37,7 @@ describe Gitlab::Search::FoundBlob do let(:search_result) { "master:testdata/project::function1.yaml\x001\x00---\n" } it 'returns a valid FoundBlob' do - expect(subject.filename).to eq('testdata/project::function1.yaml') + expect(subject.path).to eq('testdata/project::function1.yaml') expect(subject.basename).to eq('testdata/project::function1') expect(subject.ref).to eq('master') expect(subject.startline).to eq(1) @@ -50,7 +49,7 @@ describe Gitlab::Search::FoundBlob do let(:search_result) { "master:testdata/foo.txt\x001\x00blah:9:blah" } it 'returns a valid FoundBlob' do - expect(subject.filename).to eq('testdata/foo.txt') + expect(subject.path).to eq('testdata/foo.txt') expect(subject.basename).to eq('testdata/foo') expect(subject.ref).to eq('master') expect(subject.startline).to eq(1) @@ -62,7 +61,7 @@ describe Gitlab::Search::FoundBlob do let(:search_result) { "master:testdata/foo.txt\x001\x00blah\x001\x00foo" } it 'returns a valid FoundBlob' do - expect(subject.filename).to eq('testdata/foo.txt') + expect(subject.path).to eq('testdata/foo.txt') expect(subject.basename).to eq('testdata/foo') expect(subject.ref).to eq('master') expect(subject.startline).to eq(1) @@ -74,7 +73,7 @@ describe Gitlab::Search::FoundBlob do let(:results) { project.repository.search_files_by_content('Role models', 'master') } it 'returns a valid FoundBlob that ends with an empty line' do - expect(subject.filename).to eq('files/markdown/ruby-style-guide.md') + expect(subject.path).to eq('files/markdown/ruby-style-guide.md') expect(subject.basename).to eq('files/markdown/ruby-style-guide') expect(subject.ref).to eq('master') expect(subject.startline).to eq(1) @@ -87,7 +86,7 @@ describe Gitlab::Search::FoundBlob do let(:results) { project.repository.search_files_by_content('ัะฐะนะป', 'master') } it 'returns results as UTF-8' do - expect(subject.filename).to eq('encoding/russian.rb') + expect(subject.path).to eq('encoding/russian.rb') expect(subject.basename).to eq('encoding/russian') expect(subject.ref).to eq('master') expect(subject.startline).to eq(1) @@ -99,7 +98,7 @@ describe Gitlab::Search::FoundBlob do let(:results) { project.repository.search_files_by_content('webhook', 'master') } it 'returns results as UTF-8' do - expect(subject.filename).to eq('encoding/ใในใ.txt') + expect(subject.path).to eq('encoding/ใในใ.txt') expect(subject.basename).to eq('encoding/ใในใ') expect(subject.ref).to eq('master') expect(subject.startline).to eq(3) @@ -111,7 +110,7 @@ describe Gitlab::Search::FoundBlob do let(:search_result) { (+"master:encoding/iso8859.txt\x001\x00\xC4\xFC\nmaster:encoding/iso8859.txt\x002\x00\nmaster:encoding/iso8859.txt\x003\x00foo\n").force_encoding(Encoding::ASCII_8BIT) } it 'returns results as UTF-8' do - expect(subject.filename).to eq('encoding/iso8859.txt') + expect(subject.path).to eq('encoding/iso8859.txt') expect(subject.basename).to eq('encoding/iso8859') expect(subject.ref).to eq('master') expect(subject.startline).to eq(1) @@ -124,7 +123,6 @@ describe Gitlab::Search::FoundBlob do let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" } it { expect(subject.path).to eq('CONTRIBUTE.md') } - it { expect(subject.filename).to eq('CONTRIBUTE.md') } it { expect(subject.basename).to eq('CONTRIBUTE') } end @@ -132,7 +130,6 @@ describe Gitlab::Search::FoundBlob do let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" } it { expect(subject.path).to eq('a/b/c.md') } - it { expect(subject.filename).to eq('a/b/c.md') } it { expect(subject.basename).to eq('a/b/c') } end end @@ -141,7 +138,7 @@ describe Gitlab::Search::FoundBlob do context 'when file is under directory' do let(:path) { 'a/b/c.md' } - subject { described_class.new(blob_filename: path, project: project, ref: 'master') } + subject { described_class.new(blob_path: path, project: project, ref: 'master') } before do allow(Gitlab::Git::Blob).to receive(:batch).and_return([ @@ -150,7 +147,6 @@ describe Gitlab::Search::FoundBlob do end it { expect(subject.path).to eq('a/b/c.md') } - it { expect(subject.filename).to eq('a/b/c.md') } it { expect(subject.basename).to eq('a/b/c') } context 'when filename has multiple extensions' do diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index a17e9a31212..eefc548a4d9 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -310,18 +310,18 @@ describe Gitlab::Shell do let(:disk_path) { "#{project.disk_path}.git" } it 'returns true when the command succeeds' do - expect(gitlab_shell.exists?(project.repository_storage, disk_path)).to be(true) + expect(TestEnv.storage_dir_exists?(project.repository_storage, disk_path)).to be(true) expect(gitlab_shell.remove_repository(project.repository_storage, project.disk_path)).to be(true) - expect(gitlab_shell.exists?(project.repository_storage, disk_path)).to be(false) + expect(TestEnv.storage_dir_exists?(project.repository_storage, disk_path)).to be(false) end it 'keeps the namespace directory' do gitlab_shell.remove_repository(project.repository_storage, project.disk_path) - expect(gitlab_shell.exists?(project.repository_storage, disk_path)).to be(false) - expect(gitlab_shell.exists?(project.repository_storage, project.disk_path.gsub(project.name, ''))).to be(true) + expect(TestEnv.storage_dir_exists?(project.repository_storage, disk_path)).to be(false) + expect(TestEnv.storage_dir_exists?(project.repository_storage, project.disk_path.gsub(project.name, ''))).to be(true) end end @@ -332,18 +332,18 @@ describe Gitlab::Shell do old_path = project2.disk_path new_path = "project/new_path" - expect(gitlab_shell.exists?(project2.repository_storage, "#{old_path}.git")).to be(true) - expect(gitlab_shell.exists?(project2.repository_storage, "#{new_path}.git")).to be(false) + expect(TestEnv.storage_dir_exists?(project2.repository_storage, "#{old_path}.git")).to be(true) + expect(TestEnv.storage_dir_exists?(project2.repository_storage, "#{new_path}.git")).to be(false) expect(gitlab_shell.mv_repository(project2.repository_storage, old_path, new_path)).to be_truthy - expect(gitlab_shell.exists?(project2.repository_storage, "#{old_path}.git")).to be(false) - expect(gitlab_shell.exists?(project2.repository_storage, "#{new_path}.git")).to be(true) + expect(TestEnv.storage_dir_exists?(project2.repository_storage, "#{old_path}.git")).to be(false) + expect(TestEnv.storage_dir_exists?(project2.repository_storage, "#{new_path}.git")).to be(true) end it 'returns false when the command fails' do expect(gitlab_shell.mv_repository(project2.repository_storage, project2.disk_path, '')).to be_falsy - expect(gitlab_shell.exists?(project2.repository_storage, "#{project2.disk_path}.git")).to be(true) + expect(TestEnv.storage_dir_exists?(project2.repository_storage, "#{project2.disk_path}.git")).to be(true) end end @@ -401,68 +401,48 @@ describe Gitlab::Shell do describe '#add_namespace' do it 'creates a namespace' do - subject.add_namespace(storage, "mepmep") + Gitlab::GitalyClient::NamespaceService.allow { subject.add_namespace(storage, "mepmep") } - expect(subject.exists?(storage, "mepmep")).to be(true) + expect(TestEnv.storage_dir_exists?(storage, "mepmep")).to be(true) end end - describe '#exists?' do - context 'when the namespace does not exist' do + describe '#repository_exists?' do + context 'when the repository does not exist' do it 'returns false' do - expect(subject.exists?(storage, "non-existing")).to be(false) + expect(subject.repository_exists?(storage, "non-existing.git")).to be(false) end end - context 'when the namespace exists' do + context 'when the repository exists' do it 'returns true' do - subject.add_namespace(storage, "mepmep") + project = create(:project, :repository, :legacy_storage) - expect(subject.exists?(storage, "mepmep")).to be(true) + expect(subject.repository_exists?(storage, project.repository.disk_path + ".git")).to be(true) end end end - describe '#repository_exists?' do - context 'when the storage path does not exist' do - subject { described_class.new.repository_exists?(storage, "non-existing.git") } - - it { is_expected.to be_falsey } - end - - context 'when the repository does not exist' do - let(:project) { create(:project, :repository, :legacy_storage) } - - subject { described_class.new.repository_exists?(storage, "#{project.repository.disk_path}-some-other-repo.git") } - - it { is_expected.to be_falsey } - end - - context 'when the repository exists' do - let(:project) { create(:project, :repository, :legacy_storage) } - - subject { described_class.new.repository_exists?(storage, "#{project.repository.disk_path}.git") } - - it { is_expected.to be_truthy } - end - end - describe '#remove' do it 'removes the namespace' do - subject.add_namespace(storage, "mepmep") - subject.rm_namespace(storage, "mepmep") + Gitlab::GitalyClient::NamespaceService.allow do + subject.add_namespace(storage, "mepmep") + subject.rm_namespace(storage, "mepmep") + end - expect(subject.exists?(storage, "mepmep")).to be(false) + expect(TestEnv.storage_dir_exists?(storage, "mepmep")).to be(false) end end describe '#mv_namespace' do it 'renames the namespace' do - subject.add_namespace(storage, "mepmep") - subject.mv_namespace(storage, "mepmep", "2mep") + Gitlab::GitalyClient::NamespaceService.allow do + subject.add_namespace(storage, "mepmep") + subject.mv_namespace(storage, "mepmep", "2mep") + end - expect(subject.exists?(storage, "mepmep")).to be(false) - expect(subject.exists?(storage, "2mep")).to be(true) + expect(TestEnv.storage_dir_exists?(storage, "mepmep")).to be(false) + expect(TestEnv.storage_dir_exists?(storage, "2mep")).to be(true) end end end diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb index 46fbc069efb..cb870cc996b 100644 --- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb +++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' describe Gitlab::SidekiqLogging::StructuredLogger do describe '#call' do diff --git a/spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb b/spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb index 8410467ef1f..27eea963402 100644 --- a/spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::SidekiqMiddleware::CorrelationLogger do end end - it 'injects into payload the correlation id' do + it 'injects into payload the correlation id', :sidekiq_might_not_need_inline do expect_any_instance_of(described_class).to receive(:call).and_call_original expect_any_instance_of(TestWorker).to receive(:perform).with(1234) do diff --git a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb index 806112fcb16..0d8cff3a295 100644 --- a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb @@ -1,69 +1,108 @@ # frozen_string_literal: true -require 'spec_helper' +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 + + describe '#initialize' do + it 'sets general metrics' do + expect(concurrency_metric).to receive(:set).with({}, Sidekiq.options[:concurrency].to_i) + + middleware + end + end + + it 'ignore user execution when measured 0' do + allow(completion_seconds_metric).to receive(:observe) + + expect(user_execution_seconds_metric).not_to receive(:observe) + end + describe '#call' do - let(:middleware) { described_class.new } let(:worker) { double(:worker) } - 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') } + let(:job) { {} } + let(:job_status) { :done } + let(:labels) { { queue: :test } } + let(:labels_with_job_status) { { queue: :test, job_status: job_status } } - before do - 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, {}, :livesum).and_return(running_jobs_metric) + let(:thread_cputime_before) { 1 } + let(:thread_cputime_after) { 2 } + let(:thread_cputime_duration) { thread_cputime_after - thread_cputime_before } - allow(running_jobs_metric).to receive(:increment) - end + let(:monotonic_time_before) { 11 } + let(:monotonic_time_after) { 20 } + let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before } - it 'yields block' do - allow(completion_seconds_metric).to receive(:observe) - allow(user_execution_seconds_metric).to receive(:observe) + let(:queue_duration_for_job) { 0.01 } - expect { |b| middleware.call(worker, {}, :test, &b) }.to yield_control.once - end - - it 'sets metrics' do - labels = { queue: :test } - allow(middleware).to receive(:get_thread_cputime).and_return(1, 3) + 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) - expect(user_execution_seconds_metric).to receive(:observe).with(labels, 2) expect(running_jobs_metric).to receive(:increment).with(labels, 1) expect(running_jobs_metric).to receive(:increment).with(labels, -1) - expect(completion_seconds_metric).to receive(:observe).with(labels, kind_of(Numeric)) - middleware.call(worker, {}, :test) { nil } + 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| middleware.call(worker, job, :test, &b) }.to yield_control.once + end + + it 'sets queue specific metrics' do + middleware.call(worker, job, :test) { nil } end - it 'ignore user execution when measured 0' do - allow(completion_seconds_metric).to receive(:observe) - allow(middleware).to receive(:get_thread_cputime).and_return(0, 0) + context 'when job_duration is not available' do + let(:queue_duration_for_job) { nil } - expect(user_execution_seconds_metric).not_to receive(:observe) + it 'does not set the queue_duration_seconds histogram' do + middleware.call(worker, job, :test) { nil } + end end context 'when job is retried' do - it 'sets sidekiq_jobs_retried_total metric' do - allow(completion_seconds_metric).to receive(:observe) - expect(user_execution_seconds_metric).to receive(:observe) + let(:job) { { 'retry_count' => 1 } } + it 'sets sidekiq_jobs_retried_total metric' do expect(retried_total_metric).to receive(:increment) - middleware.call(worker, { 'retry_count' => 1 }, :test) { nil } + middleware.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) - expect { middleware.call(worker, {}, :test) { raise } }.to raise_error + expect(failed_total_metric).to receive(:increment).with(labels, 1) + + expect { middleware.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed") end end end diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb index dc412c80e68..5a8c721a634 100644 --- a/spec/lib/gitlab/slash_commands/command_spec.rb +++ b/spec/lib/gitlab/slash_commands/command_spec.rb @@ -115,5 +115,10 @@ describe Gitlab::SlashCommands::Command 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 end diff --git a/spec/lib/gitlab/slash_commands/issue_comment_spec.rb b/spec/lib/gitlab/slash_commands/issue_comment_spec.rb new file mode 100644 index 00000000000..c6f56d10d1f --- /dev/null +++ b/spec/lib/gitlab/slash_commands/issue_comment_spec.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SlashCommands::IssueComment do + describe '#execute' do + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + let(:user) { issue.author } + let(:chat_name) { double(:chat_name, user: user) } + let(:regex_match) { described_class.match("issue comment #{issue.iid}\nComment body") } + + subject { described_class.new(project, chat_name).execute(regex_match) } + + context 'when the issue exists' do + context 'when project is private' do + let(:project) { create(:project) } + + context 'when the user is not a member of the project' do + let(:chat_name) { double(:chat_name, user: create(:user)) } + + it 'does not allow the user to comment' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to match('not found') + expect(issue.reload.notes.count).to be_zero + end + end + end + + context 'when the user is not a member of the project' do + let(:chat_name) { double(:chat_name, user: create(:user)) } + + context 'when the discussion is locked in the issue' do + before do + issue.update!(discussion_locked: true) + end + + it 'does not allow the user to comment' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to match('You are not allowed') + expect(issue.reload.notes.count).to be_zero + end + end + end + + context 'when the user can comment on the issue' do + context 'when comment body exists' do + it 'creates a new comment' do + expect { subject }.to change { issue.notes.count }.by(1) + end + + it 'a new comment has a correct body' do + subject + + expect(issue.notes.last.note).to eq('Comment body') + end + end + + context 'when comment body does not exist' do + let(:regex_match) { described_class.match("issue comment #{issue.iid}") } + + it 'does not create a new comment' do + expect { subject }.not_to change { issue.notes.count } + end + + it 'displays the errors' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to match("- Note can't be blank") + end + end + end + end + + context 'when the issue does not exist' do + let(:regex_match) { described_class.match("issue comment 2343242\nComment body") } + + it 'returns not found' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to match('not found') + end + end + end + + describe '.match' do + subject(:match) { described_class.match(command) } + + context 'when a command has an issue ID' do + context 'when command has a comment body' do + let(:command) { "issue comment 503\nComment body" } + + it 'matches an issue ID' do + expect(match[:iid]).to eq('503') + end + + it 'matches an note body' do + expect(match[:note_body]).to eq('Comment body') + end + end + end + + context 'when a command has a reference prefix for issue ID' do + let(:command) { "issue comment #503\nComment body" } + + it 'matches an issue ID' do + expect(match[:iid]).to eq('503') + end + end + + context 'when a command does not have an issue ID' do + let(:command) { 'issue comment' } + + it 'does not match' do + is_expected.to be_nil + end + end + end +end diff --git a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb index c7b83467660..804184a7173 100644 --- a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb +++ b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb @@ -22,6 +22,16 @@ describe Gitlab::SlashCommands::Presenters::Access do end end + describe '#generic_access_denied' do + subject { described_class.new.generic_access_denied } + + it { is_expected.to be_a(Hash) } + + it_behaves_like 'displays an error message' do + let(:error_message) { 'You are not allowed to perform the given chatops command.' } + end + end + describe '#deactivated' do subject { described_class.new.deactivated } diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb new file mode 100644 index 00000000000..b5ef417cb93 --- /dev/null +++ b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SlashCommands::Presenters::IssueComment do + let_it_be(:project) { create(:project) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:note) { create(:note, project: project, noteable: issue) } + let(:author) { note.author } + + describe '#present' do + let(:attachment) { subject[:attachments].first } + subject { described_class.new(note).present } + + it { is_expected.to be_a(Hash) } + + it 'sets ephemeral response type' do + expect(subject[:response_type]).to be(:ephemeral) + end + + it 'sets the title' do + expect(attachment[:title]).to eq("#{issue.title} ยท #{issue.to_reference}") + end + + it 'sets the fallback text' do + expect(attachment[:fallback]).to eq("New comment on #{issue.to_reference}: #{issue.title}") + end + + it 'sets the fields' do + expect(attachment[:fields]).to eq([{ title: 'Comment', value: note.note }]) + end + + it 'sets the color' do + expect(attachment[:color]).to eq('#38ae67') + end + end +end diff --git a/spec/lib/gitlab/sourcegraph_spec.rb b/spec/lib/gitlab/sourcegraph_spec.rb new file mode 100644 index 00000000000..e081ae32175 --- /dev/null +++ b/spec/lib/gitlab/sourcegraph_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Sourcegraph do + let_it_be(:user) { create(:user) } + let(:feature_scope) { true } + + before do + Feature.enable(:sourcegraph, feature_scope) + end + + describe '.feature_conditional?' do + subject { described_class.feature_conditional? } + + context 'when feature is enabled globally' do + it { is_expected.to be_falsey } + end + + context 'when feature is enabled only to a resource' do + let(:feature_scope) { user } + + it { is_expected.to be_truthy } + end + end + + describe '.feature_available?' do + subject { described_class.feature_available? } + + context 'when feature is enabled globally' do + it { is_expected.to be_truthy } + end + + context 'when feature is enabled only to a resource' do + let(:feature_scope) { user } + + it { is_expected.to be_truthy } + end + end + + describe '.feature_enabled?' do + let(:current_user) { nil } + + subject { described_class.feature_enabled?(current_user) } + + context 'when feature is enabled globally' do + it { is_expected.to be_truthy } + end + + context 'when feature is enabled only to a resource' do + let(:feature_scope) { user } + + context 'for the same resource' do + let(:current_user) { user } + + it { is_expected.to be_truthy } + end + + context 'for a different resource' do + let(:current_user) { create(:user) } + + it { is_expected.to be_falsey } + end + end + end +end diff --git a/spec/lib/gitlab/sql/recursive_cte_spec.rb b/spec/lib/gitlab/sql/recursive_cte_spec.rb index 20e36c224b0..b15be56dd6d 100644 --- a/spec/lib/gitlab/sql/recursive_cte_spec.rb +++ b/spec/lib/gitlab/sql/recursive_cte_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::SQL::RecursiveCTE do [rel1.except(:order).to_sql, rel2.except(:order).to_sql] end - expect(sql).to eq("#{name} AS (#{sql1}\nUNION\n#{sql2})") + expect(sql).to eq("#{name} AS ((#{sql1})\nUNION\n(#{sql2}))") end end diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb index f8f6da19fa5..f736614ae53 100644 --- a/spec/lib/gitlab/sql/union_spec.rb +++ b/spec/lib/gitlab/sql/union_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::SQL::Union do it 'returns a String joining relations together using a UNION' do union = described_class.new([relation_1, relation_2]) - expect(union.to_sql).to eq("#{to_sql(relation_1)}\nUNION\n#{to_sql(relation_2)}") + expect(union.to_sql).to eq("(#{to_sql(relation_1)})\nUNION\n(#{to_sql(relation_2)})") end it 'skips Model.none segements' do @@ -22,7 +22,7 @@ describe Gitlab::SQL::Union do union = described_class.new([empty_relation, relation_1, relation_2]) expect {User.where("users.id IN (#{union.to_sql})").to_a}.not_to raise_error - expect(union.to_sql).to eq("#{to_sql(relation_1)}\nUNION\n#{to_sql(relation_2)}") + expect(union.to_sql).to eq("(#{to_sql(relation_1)})\nUNION\n(#{to_sql(relation_2)})") end it 'uses UNION ALL when removing duplicates is disabled' do diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb index 50488dba48c..dc877f20cae 100644 --- a/spec/lib/gitlab/tracking_spec.rb +++ b/spec/lib/gitlab/tracking_spec.rb @@ -8,19 +8,23 @@ describe Gitlab::Tracking do stub_application_setting(snowplow_enabled: true) stub_application_setting(snowplow_collector_hostname: 'gitfoo.com') stub_application_setting(snowplow_cookie_domain: '.gitfoo.com') - stub_application_setting(snowplow_site_id: '_abc123_') + stub_application_setting(snowplow_app_id: '_abc123_') + stub_application_setting(snowplow_iglu_registry_url: 'https://example.org') end describe '.snowplow_options' do it 'returns useful client options' do - expect(described_class.snowplow_options(nil)).to eq( + expected_fields = { namespace: 'gl', hostname: 'gitfoo.com', cookieDomain: '.gitfoo.com', appId: '_abc123_', formTracking: true, - linkClickTracking: true - ) + linkClickTracking: true, + igluRegistryUrl: 'https://example.org' + } + + expect(subject.snowplow_options(nil)).to match(expected_fields) end it 'enables features using feature flags' do @@ -29,11 +33,12 @@ describe Gitlab::Tracking do :additional_snowplow_tracking, '_group_' ).and_return(false) - - expect(described_class.snowplow_options('_group_')).to include( + addition_feature_fields = { formTracking: false, linkClickTracking: false - ) + } + + expect(subject.snowplow_options('_group_')).to include(addition_feature_fields) end end diff --git a/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb index 7a01f7d1de8..96ebeb8ff76 100644 --- a/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb @@ -34,22 +34,54 @@ describe Gitlab::UsageDataCounters::WebIdeCounter, :clean_gitlab_redis_shared_st it_behaves_like 'counter examples' end + describe 'previews counter' do + let(:setting_enabled) { true } + + before do + stub_application_setting(web_ide_clientside_preview_enabled: setting_enabled) + end + + context 'when web ide clientside preview is enabled' do + let(:increment_counter_method) { :increment_previews_count } + let(:total_counter_method) { :total_previews_count } + + it_behaves_like 'counter examples' + end + + context 'when web ide clientside preview is not enabled' do + let(:setting_enabled) { false } + + it 'does not increment the counter' do + expect(described_class.total_previews_count).to eq(0) + + 2.times { described_class.increment_previews_count } + + expect(described_class.total_previews_count).to eq(0) + end + end + end + describe '.totals' do commits = 5 merge_requests = 3 views = 2 + previews = 4 before do + stub_application_setting(web_ide_clientside_preview_enabled: true) + commits.times { described_class.increment_commits_count } merge_requests.times { described_class.increment_merge_requests_count } views.times { described_class.increment_views_count } + previews.times { described_class.increment_previews_count } end it 'can report all totals' do expect(described_class.totals).to include( web_ide_commits: commits, web_ide_views: views, - web_ide_merge_requests: merge_requests + web_ide_merge_requests: merge_requests, + web_ide_previews: previews ) end end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index f2e864472c5..484684eeb65 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -17,21 +17,41 @@ describe Gitlab::UsageData do create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true) create(:service, project: projects[1], type: 'SlackService', active: true) create(:service, project: projects[2], type: 'SlackService', active: true) + create(:service, project: projects[2], type: 'MattermostService', active: true) + create(:service, project: projects[2], type: 'JenkinsService', active: true) + create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true) create(:project_error_tracking_setting, project: projects[0]) create(:project_error_tracking_setting, project: projects[1], enabled: false) - - gcp_cluster = create(:cluster, :provided_by_gcp) - create(:cluster, :provided_by_user) - create(:cluster, :provided_by_user, :disabled) + create_list(:issue, 4, project: projects[0]) + create(:zoom_meeting, project: projects[0], issue: projects[0].issues[0], issue_status: :added) + 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) + + # Enabled clusters + gcp_cluster = create(:cluster_provider_gcp, :created).cluster + create(:cluster_provider_aws, :created) + create(:cluster_platform_kubernetes) create(:cluster, :group) + + # Disabled clusters + create(:cluster, :disabled) create(:cluster, :group, :disabled) create(:cluster, :group, :disabled) + + # Applications create(:clusters_applications_helm, :installed, cluster: gcp_cluster) create(:clusters_applications_ingress, :installed, cluster: gcp_cluster) create(:clusters_applications_cert_manager, :installed, cluster: gcp_cluster) create(:clusters_applications_prometheus, :installed, cluster: gcp_cluster) + create(:clusters_applications_crossplane, :installed, cluster: gcp_cluster) create(:clusters_applications_runner, :installed, cluster: gcp_cluster) create(:clusters_applications_knative, :installed, cluster: gcp_cluster) + create(:clusters_applications_elastic_stack, :installed, cluster: gcp_cluster) + + create(:grafana_integration, project: projects[0], enabled: true) + create(:grafana_integration, project: projects[1], enabled: true) + create(:grafana_integration, project: projects[2], enabled: false) ProjectFeature.first.update_attribute('repository_access_level', 0) end @@ -64,6 +84,8 @@ describe Gitlab::UsageData do avg_cycle_analytics influxdb_metrics_enabled prometheus_metrics_enabled + web_ide_clientside_preview_enabled + ingress_modsecurity_enabled )) end @@ -81,6 +103,7 @@ describe Gitlab::UsageData do web_ide_views web_ide_commits web_ide_merge_requests + web_ide_previews navbar_searches cycle_analytics_views productivity_analytics_views @@ -112,17 +135,23 @@ describe Gitlab::UsageData do clusters_disabled project_clusters_disabled group_clusters_disabled + clusters_platforms_eks clusters_platforms_gke clusters_platforms_user clusters_applications_helm clusters_applications_ingress clusters_applications_cert_managers clusters_applications_prometheus + clusters_applications_crossplane clusters_applications_runner clusters_applications_knative + clusters_applications_elastic_stack in_review_folder + grafana_integrated_projects groups issues + issues_with_associated_zoom_link + issues_using_zoom_quick_actions keys label_lists labels @@ -139,6 +168,9 @@ describe Gitlab::UsageData do projects_jira_cloud_active projects_slack_notifications_active projects_slack_slash_active + projects_custom_issue_tracker_active + projects_jenkins_active + projects_mattermost_active projects_prometheus_active projects_with_repositories_enabled projects_with_error_tracking_enabled @@ -172,24 +204,33 @@ describe Gitlab::UsageData do expect(count_data[:projects_jira_cloud_active]).to eq(2) expect(count_data[:projects_slack_notifications_active]).to eq(2) expect(count_data[:projects_slack_slash_active]).to eq(1) + expect(count_data[:projects_custom_issue_tracker_active]).to eq(1) + expect(count_data[:projects_jenkins_active]).to eq(1) + 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_with_associated_zoom_link]).to eq(2) + expect(count_data[:issues_using_zoom_quick_actions]).to eq(3) - expect(count_data[:clusters_enabled]).to eq(7) - expect(count_data[:project_clusters_enabled]).to eq(6) + expect(count_data[:clusters_enabled]).to eq(4) + expect(count_data[:project_clusters_enabled]).to eq(3) expect(count_data[:group_clusters_enabled]).to eq(1) expect(count_data[:clusters_disabled]).to eq(3) expect(count_data[:project_clusters_disabled]).to eq(1) expect(count_data[:group_clusters_disabled]).to eq(2) expect(count_data[:group_clusters_enabled]).to eq(1) + expect(count_data[:clusters_platforms_eks]).to eq(1) expect(count_data[:clusters_platforms_gke]).to eq(1) expect(count_data[:clusters_platforms_user]).to eq(1) expect(count_data[:clusters_applications_helm]).to eq(1) expect(count_data[:clusters_applications_ingress]).to eq(1) expect(count_data[:clusters_applications_cert_managers]).to eq(1) + expect(count_data[:clusters_applications_crossplane]).to eq(1) expect(count_data[:clusters_applications_prometheus]).to eq(1) expect(count_data[:clusters_applications_runner]).to eq(1) expect(count_data[:clusters_applications_knative]).to eq(1) + expect(count_data[:clusters_applications_elastic_stack]).to eq(1) + expect(count_data[:grafana_integrated_projects]).to eq(2) end it 'works when queries time out' do @@ -232,6 +273,7 @@ describe Gitlab::UsageData do expect(subject[:container_registry_enabled]).to eq(Gitlab.config.registry.enabled) expect(subject[:dependency_proxy_enabled]).to eq(Gitlab.config.dependency_proxy.enabled) expect(subject[:gitlab_shared_runners_enabled]).to eq(Gitlab.config.gitlab_ci.shared_runners_enabled) + expect(subject[:web_ide_clientside_preview_enabled]).to eq(Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?) end end diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb index c25bd14fcba..4e7c43a6856 100644 --- a/spec/lib/gitlab/user_access_spec.rb +++ b/spec/lib/gitlab/user_access_spec.rb @@ -148,7 +148,7 @@ describe Gitlab::UserAccess do ) end - it 'allows users that have push access to the canonical project to push to the MR branch' do + it 'allows users that have push access to the canonical project to push to the MR branch', :sidekiq_might_not_need_inline do canonical_project.add_developer(user) expect(access.can_push_to_branch?('awesome-feature')).to be_truthy diff --git a/spec/lib/gitlab/utils/deep_size_spec.rb b/spec/lib/gitlab/utils/deep_size_spec.rb index 47dfc04f46f..ccd202b33f7 100644 --- a/spec/lib/gitlab/utils/deep_size_spec.rb +++ b/spec/lib/gitlab/utils/deep_size_spec.rb @@ -42,4 +42,10 @@ describe Gitlab::Utils::DeepSize do end end end + + describe '.human_default_max_size' do + it 'returns 1 MB' do + expect(described_class.human_default_max_size).to eq('1 MB') + end + end end diff --git a/spec/lib/gitlab/visibility_level_checker_spec.rb b/spec/lib/gitlab/visibility_level_checker_spec.rb index 325ac3c6f31..fc929d5cbbf 100644 --- a/spec/lib/gitlab/visibility_level_checker_spec.rb +++ b/spec/lib/gitlab/visibility_level_checker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::VisibilityLevelChecker do diff --git a/spec/lib/gitlab/wiki_file_finder_spec.rb b/spec/lib/gitlab/wiki_file_finder_spec.rb index fdd95d5e6e6..aeba081f3d3 100644 --- a/spec/lib/gitlab/wiki_file_finder_spec.rb +++ b/spec/lib/gitlab/wiki_file_finder_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::WikiFileFinder do it_behaves_like 'file finder' do subject { described_class.new(project, project.wiki.default_branch) } - let(:expected_file_by_name) { 'Files/Title.md' } + let(:expected_file_by_path) { 'Files/Title.md' } let(:expected_file_by_content) { 'CHANGELOG.md' } end end diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index 6bf837f1d3f..9362ff72fbc 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -96,6 +96,48 @@ describe Gitlab do end end + describe '.canary?' do + it 'is true when CANARY env var is set to true' do + stub_env('CANARY', '1') + + expect(described_class.canary?).to eq true + end + + it 'is false when CANARY env var is set to false' do + stub_env('CANARY', '0') + + expect(described_class.canary?).to eq false + end + end + + describe '.com_and_canary?' do + it 'is true when on .com and canary' do + allow(described_class).to receive_messages(com?: true, canary?: true) + + expect(described_class.com_and_canary?).to eq true + end + + it 'is false when on .com but not on canary' do + allow(described_class).to receive_messages(com?: true, canary?: false) + + expect(described_class.com_and_canary?).to eq false + end + end + + describe '.com_but_not_canary?' do + it 'is false when on .com and canary' do + allow(described_class).to receive_messages(com?: true, canary?: true) + + expect(described_class.com_but_not_canary?).to eq false + end + + it 'is true when on .com but not on canary' do + allow(described_class).to receive_messages(com?: true, canary?: false) + + expect(described_class.com_but_not_canary?).to eq true + end + end + describe '.dev_env_org_or_com?' do it 'is true when on .com' do allow(described_class).to receive_messages(com?: true, org?: false) diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb index 0f7f57095df..473ad639ead 100644 --- a/spec/lib/google_api/cloud_platform/client_spec.rb +++ b/spec/lib/google_api/cloud_platform/client_spec.rb @@ -104,7 +104,8 @@ describe GoogleApi::CloudPlatform::Client do enabled: legacy_abac }, ip_allocation_policy: { - use_ip_aliases: true + use_ip_aliases: true, + cluster_ipv4_cidr_block: '/16' }, addons_config: addons_config } diff --git a/spec/lib/grafana/client_spec.rb b/spec/lib/grafana/client_spec.rb index bd93a3c59a2..699344e940e 100644 --- a/spec/lib/grafana/client_spec.rb +++ b/spec/lib/grafana/client_spec.rb @@ -35,7 +35,7 @@ describe Grafana::Client do it 'does not follow redirects' do expect { subject }.to raise_exception( Grafana::Client::Error, - 'Grafana response status code: 302' + 'Grafana response status code: 302, Message: {}' ) expect(redirect_req_stub).to have_been_requested @@ -67,6 +67,30 @@ describe Grafana::Client do end end + describe '#get_dashboard' do + let(:grafana_api_url) { 'https://grafanatest.com/-/grafana-project/api/dashboards/uid/FndfgnX' } + + subject do + client.get_dashboard(uid: 'FndfgnX') + end + + it_behaves_like 'calls grafana api' + it_behaves_like 'no redirects' + it_behaves_like 'handles exceptions' + end + + describe '#get_datasource' do + let(:grafana_api_url) { 'https://grafanatest.com/-/grafana-project/api/datasources/name/Test%20Name' } + + subject do + client.get_datasource(name: 'Test Name') + end + + it_behaves_like 'calls grafana api' + it_behaves_like 'no redirects' + it_behaves_like 'handles exceptions' + end + describe '#proxy_datasource' do let(:grafana_api_url) do 'https://grafanatest.com/-/grafana-project/' \ diff --git a/spec/lib/omni_auth/strategies/saml_spec.rb b/spec/lib/omni_auth/strategies/saml_spec.rb index 3c59de86d98..73e86872308 100644 --- a/spec/lib/omni_auth/strategies/saml_spec.rb +++ b/spec/lib/omni_auth/strategies/saml_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe OmniAuth::Strategies::SAML, type: :strategy do diff --git a/spec/lib/prometheus/pid_provider_spec.rb b/spec/lib/prometheus/pid_provider_spec.rb index ba843b27254..6fdc11b14c4 100644 --- a/spec/lib/prometheus/pid_provider_spec.rb +++ b/spec/lib/prometheus/pid_provider_spec.rb @@ -18,7 +18,17 @@ describe Prometheus::PidProvider do expect(Sidekiq).to receive(:server?).and_return(true) end - it { is_expected.to eq 'sidekiq' } + context 'in a clustered setup' do + before do + stub_env('SIDEKIQ_WORKER_ID', '123') + end + + it { is_expected.to eq 'sidekiq_123' } + end + + context 'in a single process setup' do + it { is_expected.to eq 'sidekiq' } + end end context 'when running in Unicorn mode' do diff --git a/spec/lib/quality/helm_client_spec.rb b/spec/lib/quality/helm_client_spec.rb index 7abb9688d5a..da5ba4c4d99 100644 --- a/spec/lib/quality/helm_client_spec.rb +++ b/spec/lib/quality/helm_client_spec.rb @@ -107,5 +107,25 @@ RSpec.describe Quality::HelmClient do expect(subject.delete(release_name: release_name)).to eq('') end + + context 'with multiple release names' do + let(:release_name) { ['my-release', 'my-release-2'] } + + 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(' ')})]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) + + expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError) + end + + 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(' ')})]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) + + expect(subject.delete(release_name: release_name)).to eq('') + end + end end end diff --git a/spec/lib/quality/kubernetes_client_spec.rb b/spec/lib/quality/kubernetes_client_spec.rb index 4e77dcc97e6..5bac102ac41 100644 --- a/spec/lib/quality/kubernetes_client_spec.rb +++ b/spec/lib/quality/kubernetes_client_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Quality::KubernetesClient 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 -l release=\"#{release_name}\""]) + "--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) @@ -23,11 +23,59 @@ RSpec.describe Quality::KubernetesClient 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 -l release=\"#{release_name}\""]) + "--now --ignore-not-found --include-uninitialized --wait=true -l release=\"#{release_name}\""]) .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 end + + context 'with multiple releases' do + let(:release_name) { ['my-release', 'my-release-2'] } + + 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))) + + 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))) + + # We're not verifying the output here, just silencing it + expect { subject.cleanup(release_name: release_name) }.to output.to_stdout + end + end + + 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))) + + 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))) + + # 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 end diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb index ca2b17b44e0..8101664d34f 100644 --- a/spec/lib/sentry/client_spec.rb +++ b/spec/lib/sentry/client_spec.rb @@ -192,6 +192,15 @@ describe Sentry::Client do end end + context 'sentry api response too large' do + it 'raises exception' do + deep_size = double('Gitlab::Utils::DeepSize', valid?: false) + allow(Gitlab::Utils::DeepSize).to receive(:new).with(sentry_api_response).and_return(deep_size) + + expect { subject }.to raise_error(Sentry::Client::ResponseInvalidSizeError, 'Sentry API response is too big. Limit is 1 MB.') + end + end + it_behaves_like 'maps exceptions' end |