diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-29 09:06:10 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-29 09:06:10 +0000 |
commit | 833eadad8cac85b99871842854c9a676a607e2da (patch) | |
tree | c1217414611dd62eef1da3e8ed79603222ec263a /spec | |
parent | acdf997e1abea2d1f7b6964b19ee493b1e7c051d (diff) | |
download | gitlab-ce-833eadad8cac85b99871842854c9a676a607e2da.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/helpers/dashboard_helper_spec.rb | 61 | ||||
-rw-r--r-- | spec/lib/api/helpers/pagination_spec.rb | 399 | ||||
-rw-r--r-- | spec/lib/gitlab/pagination/offset_pagination_spec.rb | 215 |
3 files changed, 267 insertions, 408 deletions
diff --git a/spec/helpers/dashboard_helper_spec.rb b/spec/helpers/dashboard_helper_spec.rb index c899c2d9853..8a4ea33ac7c 100644 --- a/spec/helpers/dashboard_helper_spec.rb +++ b/spec/helpers/dashboard_helper_spec.rb @@ -25,39 +25,62 @@ describe DashboardHelper do end describe '#feature_entry' do - context 'when implicitly enabled' do - it 'considers feature enabled by default' do - entry = feature_entry('Demo', href: 'demo.link') + shared_examples "a feature is enabled" do + it { is_expected.to include('<p aria-label="Demo: status on">') } + end + + shared_examples "a feature is disabled" do + it { is_expected.to include('<p aria-label="Demo: status off">') } + end - expect(entry).to include('<p aria-label="Demo: status on">') - expect(entry).to include('<a href="demo.link">Demo</a>') + shared_examples "a feature without link" do + it do + is_expected.not_to have_link('Demo') + is_expected.not_to have_link('Documentation') end end + shared_examples "a feature with configuration" do + it { is_expected.to have_link('Demo', href: 'demo.link') } + end + + shared_examples "a feature with documentation" do + it { is_expected.to have_link('Documentation', href: 'doc.link') } + end + + context 'when implicitly enabled' do + subject { feature_entry('Demo') } + + it_behaves_like 'a feature is enabled' + end + context 'when explicitly enabled' do - it 'returns a link' do - entry = feature_entry('Demo', href: 'demo.link', enabled: true) + context 'without links' do + subject { feature_entry('Demo', enabled: true) } - expect(entry).to include('<p aria-label="Demo: status on">') - expect(entry).to include('<a href="demo.link">Demo</a>') + it_behaves_like 'a feature is enabled' + it_behaves_like 'a feature without link' end - it 'returns text if href is not provided' do - entry = feature_entry('Demo', enabled: true) + context 'with configure link' do + subject { feature_entry('Demo', href: 'demo.link', enabled: true) } - expect(entry).to include('<p aria-label="Demo: status on">') - expect(entry).not_to match(/<a[^>]+>/) + it_behaves_like 'a feature with configuration' + end + + context 'with configure and documentation links' do + subject { feature_entry('Demo', href: 'demo.link', doc_href: 'doc.link', enabled: true) } + + it_behaves_like 'a feature with configuration' + it_behaves_like 'a feature with documentation' end end context 'when disabled' do - it 'returns text without link' do - entry = feature_entry('Demo', href: 'demo.link', enabled: false) + subject { feature_entry('Demo', href: 'demo.link', enabled: false) } - expect(entry).to include('<p aria-label="Demo: status off">') - expect(entry).not_to match(/<a[^>]+>/) - expect(entry).to include('Demo') - end + it_behaves_like 'a feature is disabled' + it_behaves_like 'a feature without link' end end 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/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 |